diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0fa6b8e0..5a79de26 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,17 +1,16 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR}) # icons add_subdirectory(icons) # frontends add_subdirectory(zanshin) -add_subdirectory(renku) # modules add_subdirectory(akonadi) add_subdirectory(domain) add_subdirectory(presentation) add_subdirectory(scripting) add_subdirectory(utils) add_subdirectory(widgets) diff --git a/src/akonadi/CMakeLists.txt b/src/akonadi/CMakeLists.txt index b79a9486..002ebc5f 100644 --- a/src/akonadi/CMakeLists.txt +++ b/src/akonadi/CMakeLists.txt @@ -1,44 +1,39 @@ 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 - akonadinotequeries.cpp - akonadinoterepository.cpp akonadiprojectqueries.cpp akonadiprojectrepository.cpp akonadiserializer.cpp akonadiserializerinterface.cpp akonadistorage.cpp akonadistorageinterface.cpp akonadistoragesettings.cpp akonaditagfetchjobinterface.cpp - akonaditagqueries.cpp - akonaditagrepository.cpp akonaditaskqueries.cpp akonaditaskrepository.cpp akonaditimestampattribute.cpp ) add_library(akonadi STATIC ${akonadi_SRCS}) target_link_libraries(akonadi KF5::AkonadiCalendar KF5::AkonadiCore - KF5::AkonadiNotes KF5::AkonadiWidgets KF5::Mime KF5::CalendarCore KF5::IdentityManagement KF5::ConfigCore KF5::KDELibs4Support ) diff --git a/src/akonadi/akonadicache.cpp b/src/akonadi/akonadicache.cpp index 07e2a8ae..f1b41ab3 100644 --- a/src/akonadi/akonadicache.cpp +++ b/src/akonadi/akonadicache.cpp @@ -1,327 +1,326 @@ /* This file is part of Zanshin Copyright 2017 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "akonadicache.h" using namespace Akonadi; Cache::Cache(const SerializerInterface::Ptr &serializer, const MonitorInterface::Ptr &monitor, QObject *parent) : QObject(parent), m_serializer(serializer), m_monitor(monitor), m_tagListPopulated(false) { connect(m_monitor.data(), &MonitorInterface::collectionAdded, this, &Cache::onCollectionAdded); connect(m_monitor.data(), &MonitorInterface::collectionChanged, this, &Cache::onCollectionChanged); connect(m_monitor.data(), &MonitorInterface::collectionRemoved, this, &Cache::onCollectionRemoved); connect(m_monitor.data(), &MonitorInterface::tagAdded, this, &Cache::onTagAdded); connect(m_monitor.data(), &MonitorInterface::tagChanged, this, &Cache::onTagChanged); connect(m_monitor.data(), &MonitorInterface::tagRemoved, this, &Cache::onTagRemoved); connect(m_monitor.data(), &MonitorInterface::itemAdded, this, &Cache::onItemAdded); connect(m_monitor.data(), &MonitorInterface::itemChanged, this, &Cache::onItemChanged); connect(m_monitor.data(), &MonitorInterface::itemRemoved, this, &Cache::onItemRemoved); } bool Cache::isContentTypesPopulated(StorageInterface::FetchContentTypes contentTypes) const { return m_populatedContentTypes.contains(contentTypes); } Collection::List Cache::collections(StorageInterface::FetchContentTypes contentTypes) const { using namespace std::placeholders; if (contentTypes == StorageInterface::AllContent) return m_collections; auto res = Collection::List(); std::copy_if(m_collections.cbegin(), m_collections.cend(), std::back_inserter(res), [this, contentTypes] (const Collection &collection) { return matchCollection(contentTypes, collection); }); return res; } bool Cache::isCollectionKnown(Collection::Id id) const { return m_collections.contains(Collection(id)); } Collection Cache::collection(Collection::Id id) const { const auto index = m_collections.indexOf(Collection(id)); if (index >= 0) return m_collections.at(index); else return Collection(); } bool Cache::isCollectionPopulated(Collection::Id id) const { return m_collectionItems.contains(id); } Item::List Cache::items(const Collection &collection) const { const auto ids = m_collectionItems.value(collection.id()); auto items = Item::List(); items.reserve(ids.size()); std::transform(ids.cbegin(), ids.cend(), std::back_inserter(items), [this](const Item::Id &id) { return m_items.value(id); }); return items; } void Cache::setCollections(StorageInterface::FetchContentTypes contentTypes, const Collection::List &collections) { m_populatedContentTypes.insert(contentTypes); for (const auto &collection : collections) { const auto index = m_collections.indexOf(collection); if (index >= 0) m_collections[index] = collection; else m_collections.append(collection); } } void Cache::populateCollection(const Collection &collection, const Item::List &items) { auto &ids = m_collectionItems[collection.id()]; for (const auto &item : items) { m_items.insert(item.id(), item); if (!ids.contains(item.id())) ids << item.id(); } } bool Cache::isTagListPopulated() const { return m_tagListPopulated; } Tag::List Cache::tags() const { return m_tags; } bool Cache::isTagKnown(Tag::Id id) const { return m_tags.contains(Tag(id)); } Tag Cache::tag(Tag::Id id) const { const auto index = m_tags.indexOf(Tag(id)); if (index >= 0) return m_tags.at(index); else return Tag(); } bool Cache::isTagPopulated(Tag::Id id) const { return m_tagItems.contains(id); } Item::List Cache::items(const Tag &tag) const { const auto ids = m_tagItems.value(tag.id()); auto items = Item::List(); items.reserve(ids.size()); std::transform(ids.cbegin(), ids.cend(), std::back_inserter(items), [this](const Item::Id &id) { return m_items.value(id); }); return items; } void Cache::setTags(const Tag::List &tags) { m_tags = tags; m_tagListPopulated = true; } void Cache::populateTag(const Tag &tag, const Item::List &items) { auto &ids = m_tagItems[tag.id()]; for (const auto &item : items) { m_items.insert(item.id(), item); if (!ids.contains(item.id())) ids << item.id(); } } Item Cache::item(Item::Id id) const { return m_items.value(id); } void Cache::onCollectionAdded(const Collection &collection) { const auto index = m_collections.indexOf(collection); if (index >= 0) { m_collections[index] = collection; return; } const auto types = std::initializer_list{ StorageInterface::AllContent, StorageInterface::Tasks, StorageInterface::Notes, (StorageInterface::Tasks|StorageInterface::Notes) }; for (const auto &type : types) { if (isContentTypesPopulated(type) && matchCollection(type, collection)) { m_collections << collection; return; } } } void Cache::onCollectionChanged(const Collection &collection) { const auto index = m_collections.indexOf(collection); if (index >= 0) m_collections[index] = collection; } void Cache::onCollectionRemoved(const Collection &collection) { m_collections.removeAll(collection); for (const auto itemId : m_collectionItems.value(collection.id())) { m_items.remove(itemId); for (auto &itemList : m_tagItems) itemList.removeAll(itemId); } m_collectionItems.remove(collection.id()); } void Cache::onTagAdded(const Tag &tag) { if (!m_tagListPopulated) return; const auto index = m_tags.indexOf(tag); if (index >= 0) m_tags[index] = tag; else m_tags.append(tag); } void Cache::onTagChanged(const Tag &tag) { onTagAdded(tag); } void Cache::onTagRemoved(const Tag &tag) { m_tags.removeAll(tag); m_tagItems.remove(tag.id()); } void Cache::onItemAdded(const Item &item) { bool needsInsert = false; const auto it = m_collectionItems.find(item.parentCollection().id()); if (it != m_collectionItems.end()) { *it << item.id(); needsInsert = true; } for (const auto &tag : item.tags()) { const auto it = m_tagItems.find(tag.id()); if (it != m_tagItems.end()) { *it << item.id(); needsInsert = true; } } if (needsInsert) m_items.insert(item.id(), item); } void Cache::onItemChanged(const Item &item) { const auto oldItem = m_items.take(item.id()); const auto oldTags = oldItem.tags(); const auto newTags = item.tags(); if (oldItem.parentCollection() != item.parentCollection()) { auto it = m_collectionItems.find(oldItem.parentCollection().id()); if (it != m_collectionItems.end()) it->removeAll(oldItem.id()); it = m_collectionItems.find(item.parentCollection().id()); if (it != m_collectionItems.end()) it->append(item.id()); } for (const auto &oldTag : oldTags) { if (!newTags.contains(oldTag) && m_tagItems.contains(oldTag.id())) { m_tagItems[oldTag.id()].removeAll(oldTag.id()); } } for (const auto &newTag : newTags) { if (!oldItem.tags().contains(newTag) && m_tagItems.contains(newTag.id())) { m_tagItems[newTag.id()].append(item.id()); } } const auto inPopulatedTag = std::any_of(newTags.cbegin(), newTags.cend(), [this](const Tag &tag) { return m_tagItems.contains(tag.id()); }); if (inPopulatedTag || m_collectionItems.contains(item.parentCollection().id())) { m_items.insert(item.id(), item); } } void Cache::onItemRemoved(const Item &item) { m_items.remove(item.id()); for (auto &itemList : m_collectionItems) itemList.removeAll(item.id()); for (auto &itemList : m_tagItems) itemList.removeAll(item.id()); } bool Cache::matchCollection(StorageInterface::FetchContentTypes contentTypes, const Collection &collection) const { return (contentTypes == StorageInterface::AllContent) - || ((contentTypes & StorageInterface::Tasks) && m_serializer->isTaskCollection(collection)) - || ((contentTypes & StorageInterface::Notes) && m_serializer->isNoteCollection(collection)); + || ((contentTypes & StorageInterface::Tasks) && m_serializer->isTaskCollection(collection)); } diff --git a/src/akonadi/akonadiconfigdialog.cpp b/src/akonadi/akonadiconfigdialog.cpp index 6e9658d7..4400a4ba 100644 --- a/src/akonadi/akonadiconfigdialog.cpp +++ b/src/akonadi/akonadiconfigdialog.cpp @@ -1,159 +1,156 @@ /* This file is part of Zanshin Copyright 2011-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 "akonadiconfigdialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -#include using namespace Akonadi; ConfigDialog::ConfigDialog(StorageInterface::FetchContentTypes types, QWidget *parent) : QDialog(parent), m_agentInstanceWidget(new Akonadi::AgentInstanceWidget(this)), m_types(types) { setWindowTitle(i18n("Configure")); auto description = new QLabel(this); description->setWordWrap(true); description->setText(i18n("Please select or create a resource which will be used by the application to store and query its TODOs.")); applyContentTypes(m_agentInstanceWidget->agentFilterProxyModel()); auto toolBar = new QToolBar(this); toolBar->setIconSize(QSize(16, 16)); toolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); auto addAction = new QAction(this); addAction->setObjectName(QStringLiteral("addAction")); addAction->setText(i18n("Add resource")); addAction->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); connect(addAction, &QAction::triggered, this, &ConfigDialog::onAddTriggered); toolBar->addAction(addAction); auto removeAction = new QAction(this); removeAction->setObjectName(QStringLiteral("removeAction")); removeAction->setText(i18n("Remove resource")); removeAction->setIcon(QIcon::fromTheme(QStringLiteral("list-remove"))); connect(removeAction, &QAction::triggered, this, &ConfigDialog::onRemoveTriggered); toolBar->addAction(removeAction); auto configureAction = new QAction(this); configureAction->setObjectName(QStringLiteral("settingsAction")); configureAction->setText(i18n("Configure resource...")); configureAction->setIcon(QIcon::fromTheme(QStringLiteral("configure"))); connect(configureAction, &QAction::triggered, this, &ConfigDialog::onConfigureTriggered); toolBar->addAction(configureAction); auto buttons = new QDialogButtonBox(this); buttons->setStandardButtons(QDialogButtonBox::Close); connect(buttons, &QDialogButtonBox::accepted, this, &ConfigDialog::accept); connect(buttons, &QDialogButtonBox::rejected, this, &ConfigDialog::reject); auto layout = new QVBoxLayout; layout->addWidget(description); layout->addWidget(m_agentInstanceWidget); auto toolBarLayout = new QHBoxLayout; toolBarLayout->setAlignment(Qt::AlignRight); toolBarLayout->addWidget(toolBar); layout->addLayout(toolBarLayout); layout->addWidget(buttons); setLayout(layout); } void ConfigDialog::onAddTriggered() { auto dlg = QPointer(new AgentTypeDialog(this)); applyContentTypes(dlg->agentFilterProxyModel()); if (dlg->exec()) { if (!dlg) return; const auto agentType = dlg->agentType(); if (agentType.isValid()) { auto job = new Akonadi::AgentInstanceCreateJob(agentType, this); job->configure(this); job->start(); } } delete dlg; } void ConfigDialog::onRemoveTriggered() { auto list = m_agentInstanceWidget->selectedAgentInstances(); if (!list.isEmpty()) { auto answer = QMessageBox::question(this, i18n("Multiple Agent Deletion"), i18n("Do you really want to delete the selected agent instances?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No); if (answer == QMessageBox::Yes) { foreach (const auto &agent, list) { Akonadi::AgentManager::self()->removeInstance(agent); } } } } void ConfigDialog::onConfigureTriggered() { auto agent = m_agentInstanceWidget->currentAgentInstance(); if (agent.isValid()) { AgentConfigurationDialog dialog(agent, this); dialog.exec(); } } void ConfigDialog::applyContentTypes(AgentFilterProxyModel *model) { - if (m_types & StorageInterface::Notes) - model->addMimeTypeFilter(NoteUtils::noteMimeType()); if (m_types & StorageInterface::Tasks) model->addMimeTypeFilter(KCalCore::Todo::todoMimeType()); } diff --git a/src/akonadi/akonadilivequeryintegrator.h b/src/akonadi/akonadilivequeryintegrator.h index 30385cb9..e6ae1dee 100644 --- a/src/akonadi/akonadilivequeryintegrator.h +++ b/src/akonadi/akonadilivequeryintegrator.h @@ -1,405 +1,369 @@ /* This file is part of Zanshin Copyright 2015 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADI_LIVEQUERYINTEGRATOR_H #define AKONADI_LIVEQUERYINTEGRATOR_H #include #include #include #include #include #include #include "akonadi/akonadimonitorinterface.h" #include "akonadi/akonadiserializerinterface.h" #include "domain/livequery.h" namespace Akonadi { class LiveQueryIntegrator : public QObject { Q_OBJECT // Helper type trait to extract parameter and return types from // a function object // Lambda or functors (via method of const method) template struct UnaryFunctionTraits : public UnaryFunctionTraits {}; // Traits definition template struct UnaryFunctionTraits { typedef Return ReturnType; typedef Arg ArgType; }; // Function pointers template struct UnaryFunctionTraits : public UnaryFunctionTraits {}; // Method template struct UnaryFunctionTraits : public UnaryFunctionTraits {}; // Const method template struct UnaryFunctionTraits : public UnaryFunctionTraits {}; // std::function object template struct UnaryFunctionTraits> : public UnaryFunctionTraits {}; // const reference to std::function object template struct UnaryFunctionTraits &> : public UnaryFunctionTraits {}; public: typedef QSharedPointer Ptr; typedef std::function CollectionRemoveHandler; typedef std::function ItemRemoveHandler; typedef std::function TagRemoveHandler; LiveQueryIntegrator(const SerializerInterface::Ptr &serializer, const MonitorInterface::Ptr &monitor, QObject *parent = Q_NULLPTR); template void bind(const QByteArray &debugName, QSharedPointer> &output, FetchFunction fetch, PredicateFunction predicate, ExtraArgs... extra) { typedef UnaryFunctionTraits FetchTraits; typedef UnaryFunctionTraits AddTraits; typedef UnaryFunctionTraits PredicateTraits; typedef typename std::decay::type InputType; // typically Akonadi::Item static_assert(std::is_same::value, "Fetch function must return void"); static_assert(std::is_same::value, "Fetch add function must return void"); static_assert(std::is_same::value, "Predicate function must return bool"); typedef typename std::decay::type AddInputType; static_assert(std::is_same::value, "Fetch add and predicate functions must have the same input type"); if (output) return; using namespace std::placeholders; auto query = Domain::LiveQuery::Ptr::create(); query->setDebugName(debugName); query->setFetchFunction(fetch); query->setPredicateFunction(predicate); query->setConvertFunction(std::bind(&LiveQueryIntegrator::create, this, _1, extra...)); query->setUpdateFunction(std::bind(&LiveQueryIntegrator::update, this, _1, _2, extra...)); query->setRepresentsFunction(std::bind(&LiveQueryIntegrator::represents, this, _1, _2)); inputQueries() << query; output = query; } template void bindRelationship(const QByteArray &debugName, QSharedPointer> &output, FetchFunction fetch, CompareFunction compare, PredicateFunction predicate, ExtraArgs... extra) { typedef UnaryFunctionTraits FetchTraits; typedef UnaryFunctionTraits AddTraits; typedef UnaryFunctionTraits PredicateTraits; typedef typename std::decay::type InputType; // typically Akonadi::Item static_assert(std::is_same::value, "Fetch function must return void"); static_assert(std::is_same::value, "Fetch add function must return void"); static_assert(std::is_same::value, "Predicate function must return bool"); typedef typename std::decay::type AddInputType; static_assert(std::is_same::value, "Fetch add and predicate functions must have the same input type"); if (output) return; using namespace std::placeholders; auto query = Domain::LiveRelationshipQuery::Ptr::create(); query->setDebugName(debugName); query->setFetchFunction(fetch); query->setCompareFunction(compare); query->setPredicateFunction(predicate); query->setConvertFunction(std::bind(&LiveQueryIntegrator::create, this, _1, extra...)); query->setRepresentsFunction(std::bind(&LiveQueryIntegrator::represents, this, _1, _2)); inputQueries() << query; output = query; } void addRemoveHandler(const CollectionRemoveHandler &handler); void addRemoveHandler(const ItemRemoveHandler &handler); void addRemoveHandler(const TagRemoveHandler &handler); private slots: void onCollectionSelectionChanged(); void onCollectionAdded(const Akonadi::Collection &collection); void onCollectionRemoved(const Akonadi::Collection &collection); void onCollectionChanged(const Akonadi::Collection &collection); void onItemAdded(const Akonadi::Item &item); void onItemRemoved(const Akonadi::Item &item); void onItemChanged(const Akonadi::Item &item); void onTagAdded(const Akonadi::Tag &tag); void onTagRemoved(const Akonadi::Tag &tag); void onTagChanged(const Akonadi::Tag &tag); private: void cleanupQueries(); template OutputType create(const InputType &input, ExtraArgs... extra); template void update(const InputType &input, OutputType &output, ExtraArgs... extra); template bool represents(const InputType &input, const OutputType &output); template typename Domain::LiveQueryInput::WeakList &inputQueries(); Domain::LiveQueryInput::WeakList m_collectionInputQueries; Domain::LiveQueryInput::WeakList m_itemInputQueries; Domain::LiveQueryInput::WeakList m_tagInputQueries; QList m_collectionRemoveHandlers; QList m_itemRemoveHandlers; QList m_tagRemoveHandlers; SerializerInterface::Ptr m_serializer; MonitorInterface::Ptr m_monitor; }; template<> inline Domain::Artifact::Ptr LiveQueryIntegrator::create(const Item &input) { return m_serializer->createArtifactFromItem(input); } template<> inline void LiveQueryIntegrator::update(const Item &input, Domain::Artifact::Ptr &output) { m_serializer->updateArtifactFromItem(output, input); } template<> inline bool LiveQueryIntegrator::represents(const Item &input, const Domain::Artifact::Ptr &output) { return m_serializer->representsItem(output, input); } template<> inline Domain::Context::Ptr LiveQueryIntegrator::create(const Tag &input) { return m_serializer->createContextFromTag(input); } template<> inline void LiveQueryIntegrator::update(const Tag &input, Domain::Context::Ptr &output) { m_serializer->updateContextFromTag(output, input); } template<> inline bool LiveQueryIntegrator::represents(const Tag &input, const Domain::Context::Ptr &output) { return m_serializer->isContextTag(output, input); } template<> inline Domain::DataSource::Ptr LiveQueryIntegrator::create(const Collection &input) { return m_serializer->createDataSourceFromCollection(input, SerializerInterface::BaseName); } template<> inline void LiveQueryIntegrator::update(const Collection &input, Domain::DataSource::Ptr &output) { m_serializer->updateDataSourceFromCollection(output, input, SerializerInterface::BaseName); } template<> inline Domain::DataSource::Ptr LiveQueryIntegrator::create(const Collection &input, SerializerInterface::DataSourceNameScheme nameScheme) { return m_serializer->createDataSourceFromCollection(input, nameScheme); } template<> inline void LiveQueryIntegrator::update(const Collection &input, Domain::DataSource::Ptr &output, SerializerInterface::DataSourceNameScheme nameScheme) { m_serializer->updateDataSourceFromCollection(output, input, nameScheme); } template<> inline bool LiveQueryIntegrator::represents(const Collection &input, const Domain::DataSource::Ptr &output) { return m_serializer->representsCollection(output, input); } -template<> -inline Domain::Note::Ptr LiveQueryIntegrator::create(const Item &input) -{ - return m_serializer->createNoteFromItem(input); -} - -template<> -inline void LiveQueryIntegrator::update(const Item &input, Domain::Note::Ptr &output) -{ - m_serializer->updateNoteFromItem(output, input); -} - -template<> -inline bool LiveQueryIntegrator::represents(const Item &input, const Domain::Note::Ptr &output) -{ - return m_serializer->representsItem(output, input); -} - template<> inline Domain::DataSource::Ptr LiveQueryIntegrator::create(const Item &input) { return m_serializer->createDataSourceFromCollection(input.parentCollection(), SerializerInterface::BaseName); } template<> inline void LiveQueryIntegrator::update(const Item &input, Domain::DataSource::Ptr &output) { m_serializer->updateDataSourceFromCollection(output, input.parentCollection(), SerializerInterface::BaseName); } template<> inline bool LiveQueryIntegrator::represents(const Item &input, const Domain::DataSource::Ptr &output) { return m_serializer->representsCollection(output, input.parentCollection()); } template<> inline Domain::Project::Ptr LiveQueryIntegrator::create(const Item &input) { return m_serializer->createProjectFromItem(input); } template<> inline void LiveQueryIntegrator::update(const Item &input, Domain::Project::Ptr &output) { m_serializer->updateProjectFromItem(output, input); } template<> inline bool LiveQueryIntegrator::represents(const Item &input, const Domain::Project::Ptr &output) { return m_serializer->representsItem(output, input); } -template<> -inline Domain::Tag::Ptr LiveQueryIntegrator::create(const Tag &input) -{ - return m_serializer->createTagFromAkonadiTag(input); -} - -template<> -inline void LiveQueryIntegrator::update(const Tag &input, Domain::Tag::Ptr &output) -{ - m_serializer->updateTagFromAkonadiTag(output, input); -} - -template<> -inline bool LiveQueryIntegrator::represents(const Tag &input, const Domain::Tag::Ptr &output) -{ - return m_serializer->representsAkonadiTag(output, input); -} - template<> inline Domain::Task::Ptr LiveQueryIntegrator::create(const Item &input) { return m_serializer->createTaskFromItem(input); } template<> inline void LiveQueryIntegrator::update(const Item &input, Domain::Task::Ptr &output) { m_serializer->updateTaskFromItem(output, input); } template<> inline bool LiveQueryIntegrator::represents(const Item &input, const Domain::Task::Ptr &output) { return m_serializer->representsItem(output, input); } template<> inline typename Domain::LiveQueryInput::WeakList &LiveQueryIntegrator::inputQueries() { return m_collectionInputQueries; } template<> inline typename Domain::LiveQueryInput::WeakList &LiveQueryIntegrator::inputQueries() { return m_itemInputQueries; } template<> inline typename Domain::LiveQueryInput::WeakList &LiveQueryIntegrator::inputQueries() { return m_tagInputQueries; } } #endif // AKONADI_LIVEQUERYINTEGRATOR_H diff --git a/src/akonadi/akonadimonitorimpl.cpp b/src/akonadi/akonadimonitorimpl.cpp index c2b9ad5f..73d4ef1b 100644 --- a/src/akonadi/akonadimonitorimpl.cpp +++ b/src/akonadi/akonadimonitorimpl.cpp @@ -1,131 +1,129 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "akonadimonitorimpl.h" #include #include #include #include #include -#include #include #include #include "akonadi/akonadiapplicationselectedattribute.h" #include "akonadi/akonaditimestampattribute.h" using namespace Akonadi; MonitorImpl::MonitorImpl() : m_monitor(new Akonadi::Monitor(this)) { AttributeFactory::registerAttribute(); AttributeFactory::registerAttribute(); AttributeFactory::registerAttribute(); m_monitor->fetchCollection(true); m_monitor->setCollectionMonitored(Akonadi::Collection::root()); m_monitor->setMimeTypeMonitored(KCalCore::Todo::todoMimeType()); - m_monitor->setMimeTypeMonitored(NoteUtils::noteMimeType()); auto collectionScope = m_monitor->collectionFetchScope(); collectionScope.setContentMimeTypes(m_monitor->mimeTypesMonitored()); collectionScope.setIncludeStatistics(true); collectionScope.setAncestorRetrieval(CollectionFetchScope::All); m_monitor->setCollectionFetchScope(collectionScope); connect(m_monitor, &Akonadi::Monitor::collectionAdded, this, &MonitorImpl::collectionAdded); connect(m_monitor, &Akonadi::Monitor::collectionRemoved, this, &MonitorImpl::collectionRemoved); connect(m_monitor, static_cast &)>(&Akonadi::Monitor::collectionChanged), this, &MonitorImpl::onCollectionChanged); auto itemScope = m_monitor->itemFetchScope(); itemScope.fetchFullPayload(); itemScope.fetchAllAttributes(); itemScope.setFetchTags(true); itemScope.tagFetchScope().setFetchIdOnly(false); itemScope.tagFetchScope().fetchAttribute(); itemScope.setAncestorRetrieval(ItemFetchScope::All); m_monitor->setItemFetchScope(itemScope); auto tagFetchScope = m_monitor->tagFetchScope(); tagFetchScope.setFetchIdOnly(false); tagFetchScope.fetchAttribute(); m_monitor->setTagFetchScope(tagFetchScope); connect(m_monitor, &Akonadi::Monitor::itemAdded, this, &MonitorImpl::itemAdded); connect(m_monitor, &Akonadi::Monitor::itemRemoved, this, &MonitorImpl::itemRemoved); connect(m_monitor, &Akonadi::Monitor::itemChanged, this, &MonitorImpl::itemChanged); connect(m_monitor, &Akonadi::Monitor::itemMoved, this, &MonitorImpl::itemMoved); connect(m_monitor, &Akonadi::Monitor::itemsTagsChanged, this, &MonitorImpl::onItemsTagsChanged); connect(m_monitor, &Akonadi::Monitor::tagAdded, this, &MonitorImpl::tagAdded); connect(m_monitor, &Akonadi::Monitor::tagRemoved, this, &MonitorImpl::tagRemoved); connect(m_monitor, &Akonadi::Monitor::tagChanged, this, &MonitorImpl::tagChanged); } MonitorImpl::~MonitorImpl() { } void MonitorImpl::onCollectionChanged(const Collection &collection, const QSet &parts) { // Will probably need to be expanded and to also fetch the full parent chain before emitting in some cases static const QSet allowedParts = QSet() << "NAME" << "REMOTEID" << "AccessRights" << "ENTITYDISPLAY" << "ZanshinSelected" << "ZanshinTimestamp"; QSet partsIntersection = parts; partsIntersection.intersect(allowedParts); if (!partsIntersection.isEmpty()) emit collectionChanged(collection); if (parts.contains("ZanshinSelected") && hasSupportedMimeTypes(collection)) { emit collectionSelectionChanged(collection); } } void MonitorImpl::onItemsTagsChanged(const Akonadi::Item::List &items, const QSet &addedTags, const QSet &removedTags) { // Because itemChanged is not emitted on tag removal, we need to listen to itemsTagsChanged and // emit the itemChanged only in this case (avoid double emits in case of tag dissociation / association) // So if both list are empty it means we are just seeing a tag being removed so we update its related items if (addedTags.isEmpty() && removedTags.isEmpty()) { foreach (const Item &item, items) emit itemChanged(item); } } bool MonitorImpl::hasSupportedMimeTypes(const Collection &collection) { QSet mimeIntersection = m_monitor->mimeTypesMonitored().toSet(); mimeIntersection.intersect(collection.contentMimeTypes().toSet()); return !mimeIntersection.isEmpty(); } diff --git a/src/akonadi/akonadinotequeries.cpp b/src/akonadi/akonadinotequeries.cpp deleted file mode 100644 index d3425396..00000000 --- a/src/akonadi/akonadinotequeries.cpp +++ /dev/null @@ -1,57 +0,0 @@ -/* This file is part of Zanshin - - Copyright 2014 Kevin Ottens - Copyright 2014 Remi 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 "akonadinotequeries.h" - -using namespace Akonadi; - -NoteQueries::NoteQueries(const StorageInterface::Ptr &storage, - const SerializerInterface::Ptr &serializer, - const MonitorInterface::Ptr &monitor) - : m_serializer(serializer), - m_helpers(new LiveQueryHelpers(serializer, storage)), - m_integrator(new LiveQueryIntegrator(serializer, monitor)) -{ -} - -NoteQueries::NoteResult::Ptr NoteQueries::findAll() const -{ - auto fetch = m_helpers->fetchItems(StorageInterface::Notes); - auto predicate = [this] (const Item &item) { - return m_serializer->isNoteItem(item); - }; - m_integrator->bind("NoteQueries::findAll", m_findAll, fetch, predicate); - return m_findAll->result(); -} - -NoteQueries::NoteResult::Ptr NoteQueries::findInbox() const -{ - auto fetch = m_helpers->fetchItems(StorageInterface::Notes); - auto predicate = [this] (const Item &item) { - return m_serializer->isNoteItem(item) - && !m_serializer->hasAkonadiTags(item); - }; - m_integrator->bind("NoteQueries::findInbox", m_findAll, fetch, predicate); - return m_findAll->result(); -} diff --git a/src/akonadi/akonadinotequeries.h b/src/akonadi/akonadinotequeries.h deleted file mode 100644 index a9b175f8..00000000 --- a/src/akonadi/akonadinotequeries.h +++ /dev/null @@ -1,62 +0,0 @@ -/* This file is part of Zanshin - - Copyright 2014 Kevin Ottens - Copyright 2014 Remi Benoit - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License or (at your option) version 3 or any later version - accepted by the membership of KDE e.V. (or its successor approved - by the membership of KDE e.V.), which shall act as a proxy - defined in Section 14 of version 3 of the license. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - USA. -*/ - -#ifndef AKONADI_NOTEQUERIES_H -#define AKONADI_NOTEQUERIES_H - -#include "domain/notequeries.h" - -#include "akonadi/akonadilivequeryhelpers.h" -#include "akonadi/akonadilivequeryintegrator.h" - -namespace Akonadi { - -class NoteQueries : public Domain::NoteQueries -{ -public: - typedef QSharedPointer Ptr; - - typedef Domain::LiveQueryInput ItemInputQuery; - typedef Domain::LiveQueryOutput NoteQueryOutput; - typedef Domain::QueryResultProvider NoteProvider; - typedef Domain::QueryResult NoteResult; - - NoteQueries(const StorageInterface::Ptr &storage, - const SerializerInterface::Ptr &serializer, - const MonitorInterface::Ptr &monitor); - - NoteResult::Ptr findAll() const Q_DECL_OVERRIDE; - NoteResult::Ptr findInbox() const Q_DECL_OVERRIDE; - -private: - SerializerInterface::Ptr m_serializer; - LiveQueryHelpers::Ptr m_helpers; - LiveQueryIntegrator::Ptr m_integrator; - - mutable NoteQueryOutput::Ptr m_findAll; -}; - -} - -#endif // AKONADI_NOTEQUERIES_H diff --git a/src/akonadi/akonadinoterepository.cpp b/src/akonadi/akonadinoterepository.cpp deleted file mode 100644 index d6a7a95d..00000000 --- a/src/akonadi/akonadinoterepository.cpp +++ /dev/null @@ -1,112 +0,0 @@ -/* This file is part of Zanshin - - Copyright 2014 Kevin Ottens - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License or (at your option) version 3 or any later version - accepted by the membership of KDE e.V. (or its successor approved - by the membership of KDE e.V.), which shall act as a proxy - defined in Section 14 of version 3 of the license. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - USA. -*/ - - -#include "akonadinoterepository.h" - -#include - -#include - -#include "akonadicollectionfetchjobinterface.h" -#include "akonadiitemfetchjobinterface.h" - -#include "utils/compositejob.h" - -using namespace Akonadi; -using namespace Utils; - -NoteRepository::NoteRepository(const StorageInterface::Ptr &storage, - const SerializerInterface::Ptr &serializer) - : m_storage(storage), - m_serializer(serializer) -{ -} - -KJob *NoteRepository::create(Domain::Note::Ptr note) -{ - auto item = m_serializer->createItemFromNote(note); - Q_ASSERT(!item.isValid()); - return createItem(item); -} - -KJob *NoteRepository::createInTag(Domain::Note::Ptr note, Domain::Tag::Ptr tag) -{ - Item item = m_serializer->createItemFromNote(note); - Q_ASSERT(!item.isValid()); - - Tag akonadiTag = m_serializer->createAkonadiTagFromTag(tag); - Q_ASSERT(akonadiTag .isValid()); - item.setTag(akonadiTag); - - return createItem(item); -} - -KJob *NoteRepository::update(Domain::Note::Ptr note) -{ - auto item = m_serializer->createItemFromNote(note); - Q_ASSERT(item.isValid()); - return m_storage->updateItem(item); -} - -KJob *NoteRepository::remove(Domain::Note::Ptr note) -{ - auto item = m_serializer->createItemFromNote(note); - return m_storage->removeItem(item); -} - -KJob *NoteRepository::createItem(const Item &item) -{ - const Akonadi::Collection defaultCollection = m_storage->defaultNoteCollection(); - if (defaultCollection.isValid()) { - return m_storage->createItem(item, defaultCollection); - } else { - auto job = new CompositeJob(); - CollectionFetchJobInterface *fetchCollectionJob = m_storage->fetchCollections(Akonadi::Collection::root(), - StorageInterface::Recursive, - StorageInterface::Notes); - job->install(fetchCollectionJob->kjob(), [fetchCollectionJob, item, job, this] { - if (fetchCollectionJob->kjob()->error() != KJob::NoError) - return; - - Q_ASSERT(fetchCollectionJob->collections().size() > 0); - const Akonadi::Collection::List collections = fetchCollectionJob->collections(); - auto it = std::find_if(collections.constBegin(), collections.constEnd(), - [] (const Akonadi::Collection &c) { - return (c.rights() & Akonadi::Collection::CanCreateItem) - && (c.rights() & Akonadi::Collection::CanChangeItem) - && (c.rights() & Akonadi::Collection::CanDeleteItem); - }); - if (it == collections.constEnd()) { - job->emitError(i18n("Could not find a collection to store the note into!")); - } else { - auto col = *it; - Q_ASSERT(col.isValid()); - auto createJob = m_storage->createItem(item, col); - job->addSubjob(createJob); - createJob->start(); - } - }); - return job; - } -} diff --git a/src/akonadi/akonadinoterepository.h b/src/akonadi/akonadinoterepository.h deleted file mode 100644 index 32d8ebec..00000000 --- a/src/akonadi/akonadinoterepository.h +++ /dev/null @@ -1,59 +0,0 @@ -/* This file is part of Zanshin - - Copyright 2014 Kevin Ottens - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License or (at your option) version 3 or any later version - accepted by the membership of KDE e.V. (or its successor approved - by the membership of KDE e.V.), which shall act as a proxy - defined in Section 14 of version 3 of the license. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - USA. -*/ - -#ifndef AKONADI_NOTEREPOSITORY_H -#define AKONADI_NOTEREPOSITORY_H - -#include "domain/noterepository.h" - -#include "akonadi/akonadiserializerinterface.h" -#include "akonadi/akonadistorageinterface.h" - -#include - -namespace Akonadi { - -class NoteRepository : public QObject, public Domain::NoteRepository -{ - Q_OBJECT -public: - typedef QSharedPointer Ptr; - - NoteRepository(const StorageInterface::Ptr &storage, - const SerializerInterface::Ptr &serializer); - - KJob *create(Domain::Note::Ptr note) Q_DECL_OVERRIDE; - KJob *createInTag(Domain::Note::Ptr note, Domain::Tag::Ptr tag) Q_DECL_OVERRIDE; - KJob *update(Domain::Note::Ptr note) Q_DECL_OVERRIDE; - KJob *remove(Domain::Note::Ptr note) Q_DECL_OVERRIDE; - -private: - StorageInterface::Ptr m_storage; - SerializerInterface::Ptr m_serializer; - - KJob *createItem(const Akonadi::Item &item); -}; - -} - -#endif // AKONADI_NOTEREPOSITORY_H diff --git a/src/akonadi/akonadiprojectrepository.cpp b/src/akonadi/akonadiprojectrepository.cpp index f2c7f468..7cf327c2 100644 --- a/src/akonadi/akonadiprojectrepository.cpp +++ b/src/akonadi/akonadiprojectrepository.cpp @@ -1,149 +1,145 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "akonadiprojectrepository.h" #include "akonadiitemfetchjobinterface.h" #include "utils/compositejob.h" using namespace Akonadi; ProjectRepository::ProjectRepository(const StorageInterface::Ptr &storage, const SerializerInterface::Ptr &serializer) : m_storage(storage), m_serializer(serializer) { } KJob *ProjectRepository::create(Domain::Project::Ptr project, Domain::DataSource::Ptr source) { auto item = m_serializer->createItemFromProject(project); Q_ASSERT(!item.isValid()); auto collection = m_serializer->createCollectionFromDataSource(source); Q_ASSERT(collection.isValid()); return m_storage->createItem(item, collection); } KJob *ProjectRepository::update(Domain::Project::Ptr project) { auto item = m_serializer->createItemFromProject(project); Q_ASSERT(item.isValid()); return m_storage->updateItem(item); } KJob *ProjectRepository::remove(Domain::Project::Ptr project) { auto item = m_serializer->createItemFromProject(project); Q_ASSERT(item.isValid()); return m_storage->removeItem(item); } KJob *ProjectRepository::associate(Domain::Project::Ptr parent, Domain::Artifact::Ptr child) { Item childItem; if (auto task = child.objectCast()) childItem = m_serializer->createItemFromTask(task); - else if (auto note = child.objectCast()) - childItem = m_serializer->createItemFromNote(note); Q_ASSERT(childItem.isValid()); auto job = new Utils::CompositeJob(); ItemFetchJobInterface *fetchItemJob = m_storage->fetchItem(childItem); job->install(fetchItemJob->kjob(), [fetchItemJob, parent, child, job, this] { if (fetchItemJob->kjob()->error() != KJob::NoError) return; Q_ASSERT(fetchItemJob->items().size() == 1); auto childItem = fetchItemJob->items().at(0); m_serializer->updateItemProject(childItem, parent); // Check collections to know if we need to move child auto parentItem = m_serializer->createItemFromProject(parent); ItemFetchJobInterface *fetchParentItemJob = m_storage->fetchItem(parentItem); job->install(fetchParentItemJob->kjob(), [fetchParentItemJob, child, childItem, job, this] { if (fetchParentItemJob->kjob()->error() != KJob::NoError) return; Q_ASSERT(fetchParentItemJob->items().size() == 1); auto parentItem = fetchParentItemJob->items().at(0); const int itemCollectionId = childItem.parentCollection().id(); const int parentCollectionId = parentItem.parentCollection().id(); if (child.objectCast() && itemCollectionId != parentCollectionId) { ItemFetchJobInterface *fetchChildrenItemJob = m_storage->fetchItems(childItem.parentCollection()); job->install(fetchChildrenItemJob->kjob(), [fetchChildrenItemJob, childItem, parentItem, job, this] { if (fetchChildrenItemJob->kjob()->error() != KJob::NoError) return; Item::List childItems = m_serializer->filterDescendantItems(fetchChildrenItemJob->items(), childItem); auto transaction = m_storage->createTransaction(); m_storage->updateItem(childItem, transaction); childItems.push_front(childItem); m_storage->moveItems(childItems, parentItem.parentCollection(), transaction); job->addSubjob(transaction); transaction->start(); }); } else { auto updateJob = m_storage->updateItem(childItem); job->addSubjob(updateJob); updateJob->start(); } }); }); return job; } KJob *ProjectRepository::dissociate(Domain::Artifact::Ptr child) { auto job = new Utils::CompositeJob(); const auto task = child.objectCast(); - const auto note = child.objectCast(); const auto childItem = task ? m_serializer->createItemFromTask(task) - : note ? m_serializer->createItemFromNote(note) : Akonadi::Item(); Q_ASSERT(childItem.isValid()); ItemFetchJobInterface *fetchItemJob = m_storage->fetchItem(childItem); job->install(fetchItemJob->kjob(), [fetchItemJob, job, this] { if (fetchItemJob->kjob()->error() != KJob::NoError) return; Q_ASSERT(fetchItemJob->items().size() == 1); auto childItem = fetchItemJob->items().at(0); m_serializer->removeItemParent(childItem); auto updateJob = m_storage->updateItem(childItem); job->addSubjob(updateJob); updateJob->start(); }); return job; } diff --git a/src/akonadi/akonadiserializer.cpp b/src/akonadi/akonadiserializer.cpp index 336f5ded..aadfa4bf 100644 --- a/src/akonadi/akonadiserializer.cpp +++ b/src/akonadi/akonadiserializer.cpp @@ -1,665 +1,521 @@ /* 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 #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(); } -bool Serializer::representsAkonadiTag(Domain::Tag::Ptr tag, Tag akonadiTag) const -{ - return tag->property("tagId").toLongLong() == akonadiTag.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(NoteUtils::noteMimeType())) - types |= Domain::DataSource::Notes; 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 (!isNoteCollection(collection) && !isTaskCollection(collection)) + if (!isTaskCollection(collection)) return false; if (!collection.hasAttribute()) return true; return collection.attribute()->isSelected(); } -bool Akonadi::Serializer::isNoteCollection(Akonadi::Collection collection) -{ - return collection.contentMimeTypes().contains(NoteUtils::noteMimeType()); -} - 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 if (isNoteItem(item)) { - const auto message = item.payload(); - const auto relatedHeader = message->headerByType("X-Zanshin-RelatedProjectUid"); - return relatedHeader ? relatedHeader->asUnicodeString() : QString(); - } 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()); - - } else if (isNoteItem(item)) { - auto note = item.payload(); - (void)note->removeHeader("X-Zanshin-RelatedProjectUid"); - const QByteArray parentUid = project->property("todoUid").toString().toUtf8(); - if (!parentUid.isEmpty()) { - auto relatedHeader = new KMime::Headers::Generic("X-Zanshin-RelatedProjectUid"); - relatedHeader->from7BitString(parentUid); - note->appendHeader(relatedHeader); - } - note->assemble(); } } 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::isNoteItem(Item item) -{ - return item.hasPayload(); -} - -Domain::Note::Ptr Serializer::createNoteFromItem(Akonadi::Item item) -{ - if (!isNoteItem(item)) - return Domain::Note::Ptr(); - - Domain::Note::Ptr note = Domain::Note::Ptr::create(); - updateNoteFromItem(note, item); - - return note; -} - -void Serializer::updateNoteFromItem(Domain::Note::Ptr note, Item item) -{ - if (!isNoteItem(item)) - return; - - auto message = item.payload(); - - note->setTitle(message->subject(true)->asUnicodeString()); - note->setText(message->mainBodyPart()->decodedText()); - note->setProperty("itemId", item.id()); - - if (auto relatedHeader = message->headerByType("X-Zanshin-RelatedProjectUid")) { - note->setProperty("relatedUid", relatedHeader->asUnicodeString()); - } else { - note->setProperty("relatedUid", QVariant()); - } -} - -Item Serializer::createItemFromNote(Domain::Note::Ptr note) -{ - NoteUtils::NoteMessageWrapper builder; - builder.setTitle(note->title()); - builder.setText(note->text() + '\n'); // Adding an extra '\n' because KMime always removes it... - - KMime::Message::Ptr message = builder.message(); - - if (!note->property("relatedUid").toString().isEmpty()) { - auto relatedHeader = new KMime::Headers::Generic("X-Zanshin-RelatedProjectUid"); - relatedHeader->from7BitString(note->property("relatedUid").toString().toUtf8()); - message->appendHeader(relatedHeader); - } - - Akonadi::Item item; - if (note->property("itemId").isValid()) { - item.setId(note->property("itemId").value()); - } - item.setMimeType(Akonadi::NoteUtils::noteMimeType()); - item.setPayload(message); - return item; -} - 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::hasAkonadiTags(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::isAkonadiTag), this, _1)); -} - bool Serializer::isContext(const Akonadi::Tag &tag) const { return (tag.type() == Akonadi::SerializerInterface::contextTagType()); } -bool Serializer::isAkonadiTag(const Tag &tag) const -{ - return tag.type() == Akonadi::Tag::PLAIN; -} - 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); } - -Domain::Tag::Ptr Serializer::createTagFromAkonadiTag(Akonadi::Tag akonadiTag) -{ - if (!isAkonadiTag(akonadiTag)) - return Domain::Tag::Ptr(); - - auto tag = Domain::Tag::Ptr::create(); - updateTagFromAkonadiTag(tag, akonadiTag); - return tag; -} - -void Serializer::updateTagFromAkonadiTag(Domain::Tag::Ptr tag, Akonadi::Tag akonadiTag) -{ - if (!isAkonadiTag(akonadiTag)) - return; - - tag->setProperty("tagId", akonadiTag.id()); - tag->setName(akonadiTag.name()); -} - -Akonadi::Tag Serializer::createAkonadiTagFromTag(Domain::Tag::Ptr tag) -{ - auto akonadiTag = Akonadi::Tag(); - akonadiTag.setName(tag->name()); - akonadiTag.setType(Akonadi::Tag::PLAIN); - akonadiTag.setGid(QByteArray(tag->name().toLatin1())); - - const auto tagProperty = tag->property("tagId"); - if (tagProperty.isValid()) - akonadiTag.setId(tagProperty.value()); - - return akonadiTag; -} - -bool Serializer::isTagChild(Domain::Tag::Ptr tag, Akonadi::Item item) -{ - if (!tag->property("tagId").isValid()) - return false; - - auto tagId = tag->property("tagId").value(); - Akonadi::Tag akonadiTag(tagId); - - return item.hasTag(akonadiTag); -} diff --git a/src/akonadi/akonadiserializer.h b/src/akonadi/akonadiserializer.h index f622e87a..43e88f5b 100644 --- a/src/akonadi/akonadiserializer.h +++ b/src/akonadi/akonadiserializer.h @@ -1,99 +1,85 @@ /* 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. */ #ifndef AKONADI_SERIALIZER_H #define AKONADI_SERIALIZER_H #include "akonadiserializerinterface.h" namespace Akonadi { class Item; class Tag; class Serializer : public SerializerInterface { public: Serializer(); virtual ~Serializer(); bool representsCollection(QObjectPtr object, Collection collection) Q_DECL_OVERRIDE; bool representsItem(QObjectPtr object, Item item) Q_DECL_OVERRIDE; - bool representsAkonadiTag(Domain::Tag::Ptr tag, Akonadi::Tag akonadiTag) const Q_DECL_OVERRIDE; QString itemUid(const Item &item) Q_DECL_OVERRIDE; Domain::DataSource::Ptr createDataSourceFromCollection(Akonadi::Collection collection, DataSourceNameScheme naming) Q_DECL_OVERRIDE; void updateDataSourceFromCollection(Domain::DataSource::Ptr dataSource, Akonadi::Collection collection, DataSourceNameScheme naming) Q_DECL_OVERRIDE; virtual Akonadi::Collection createCollectionFromDataSource(Domain::DataSource::Ptr dataSource) Q_DECL_OVERRIDE; virtual bool isSelectedCollection(Akonadi::Collection collection) Q_DECL_OVERRIDE; - virtual bool isNoteCollection(Akonadi::Collection collection) Q_DECL_OVERRIDE; virtual bool isTaskCollection(Akonadi::Collection collection) Q_DECL_OVERRIDE; bool isTaskItem(Akonadi::Item item) Q_DECL_OVERRIDE; Domain::Task::Ptr createTaskFromItem(Akonadi::Item item) Q_DECL_OVERRIDE; void updateTaskFromItem(Domain::Task::Ptr task, Akonadi::Item item) Q_DECL_OVERRIDE; Akonadi::Item createItemFromTask(Domain::Task::Ptr task) Q_DECL_OVERRIDE; bool isTaskChild(Domain::Task::Ptr task, Akonadi::Item item) Q_DECL_OVERRIDE; QString relatedUidFromItem(Akonadi::Item item) Q_DECL_OVERRIDE; void updateItemParent(Akonadi::Item item, Domain::Task::Ptr parent) Q_DECL_OVERRIDE; void updateItemProject(Akonadi::Item item, Domain::Project::Ptr project) Q_DECL_OVERRIDE; void removeItemParent(Akonadi::Item item) Q_DECL_OVERRIDE; void promoteItemToProject(Akonadi::Item item) Q_DECL_OVERRIDE; void clearItem(Akonadi::Item *item) Q_DECL_OVERRIDE; Akonadi::Item::List filterDescendantItems(const Akonadi::Item::List &potentialChildren, const Akonadi::Item &ancestorItem) Q_DECL_OVERRIDE; - bool isNoteItem(Akonadi::Item item) Q_DECL_OVERRIDE; - Domain::Note::Ptr createNoteFromItem(Akonadi::Item item) Q_DECL_OVERRIDE; - void updateNoteFromItem(Domain::Note::Ptr note, Akonadi::Item item) Q_DECL_OVERRIDE; - Akonadi::Item createItemFromNote(Domain::Note::Ptr note) Q_DECL_OVERRIDE; - bool isProjectItem(Akonadi::Item item) Q_DECL_OVERRIDE; Domain::Project::Ptr createProjectFromItem(Akonadi::Item item) Q_DECL_OVERRIDE; void updateProjectFromItem(Domain::Project::Ptr project, Akonadi::Item item) Q_DECL_OVERRIDE; Akonadi::Item createItemFromProject(Domain::Project::Ptr project) Q_DECL_OVERRIDE; bool isProjectChild(Domain::Project::Ptr project, Akonadi::Item item) Q_DECL_OVERRIDE; Domain::Context::Ptr createContextFromTag(Akonadi::Tag tag) Q_DECL_OVERRIDE; void updateContextFromTag(Domain::Context::Ptr context, Akonadi::Tag tag) Q_DECL_OVERRIDE; Akonadi::Tag createTagFromContext(Domain::Context::Ptr context) Q_DECL_OVERRIDE; bool isContextTag(const Domain::Context::Ptr &context, const Akonadi::Tag &tag) const Q_DECL_OVERRIDE; bool isContextChild(Domain::Context::Ptr context, Akonadi::Item item) const Q_DECL_OVERRIDE; - Domain::Tag::Ptr createTagFromAkonadiTag(Akonadi::Tag akonadiTag) Q_DECL_OVERRIDE; - void updateTagFromAkonadiTag(Domain::Tag::Ptr tag, Akonadi::Tag akonadiTag) Q_DECL_OVERRIDE; - Akonadi::Tag createAkonadiTagFromTag(Domain::Tag::Ptr tag) Q_DECL_OVERRIDE; - bool isTagChild(Domain::Tag::Ptr tag, Akonadi::Item item) Q_DECL_OVERRIDE; - bool hasContextTags(Akonadi::Item item) const Q_DECL_OVERRIDE; - bool hasAkonadiTags(Akonadi::Item item) const Q_DECL_OVERRIDE; private: bool isContext(const Akonadi::Tag &tag) const; - bool isAkonadiTag(const Akonadi::Tag &tag) const; }; } #endif // AKONADI_SERIALIZERINTERFACE_H diff --git a/src/akonadi/akonadiserializerinterface.cpp b/src/akonadi/akonadiserializerinterface.cpp index f6ef3324..95682ccc 100644 --- a/src/akonadi/akonadiserializerinterface.cpp +++ b/src/akonadi/akonadiserializerinterface.cpp @@ -1,63 +1,58 @@ /* 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 "akonadiserializerinterface.h" using namespace Akonadi; SerializerInterface::SerializerInterface() { } SerializerInterface::~SerializerInterface() { } Domain::Artifact::Ptr SerializerInterface::createArtifactFromItem(const Item &item) { if (isTaskItem(item)) { auto task = createTaskFromItem(item); return Domain::Artifact::Ptr(task); - } else if (isNoteItem(item)) { - auto note = createNoteFromItem(item); - return Domain::Artifact::Ptr(note); } else { return Domain::Artifact::Ptr(); } } void SerializerInterface::updateArtifactFromItem(const Domain::Artifact::Ptr &artifact, const Item &item) { if (auto task = artifact.dynamicCast()) { updateTaskFromItem(task, item); - } else if (auto note = artifact.dynamicCast()) { - updateNoteFromItem(note, item); } } QByteArray SerializerInterface::contextTagType() { return QByteArray("Zanshin-Context"); } diff --git a/src/akonadi/akonadiserializerinterface.h b/src/akonadi/akonadiserializerinterface.h index 9ec9f26e..719c74ee 100644 --- a/src/akonadi/akonadiserializerinterface.h +++ b/src/akonadi/akonadiserializerinterface.h @@ -1,117 +1,101 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADI_SERIALIZERINTERFACE_H #define AKONADI_SERIALIZERINTERFACE_H #include "domain/datasource.h" -#include "domain/tag.h" #include "domain/task.h" -#include "domain/note.h" #include "domain/project.h" #include "domain/context.h" #include namespace Akonadi { class Collection; class Item; class Tag; class SerializerInterface { public: typedef QSharedPointer Ptr; typedef QSharedPointer QObjectPtr; enum DataSourceNameScheme { FullPath, BaseName }; SerializerInterface(); virtual ~SerializerInterface(); virtual bool representsCollection(QObjectPtr object, Akonadi::Collection collection) = 0; virtual bool representsItem(QObjectPtr object, Akonadi::Item item) = 0; - virtual bool representsAkonadiTag(Domain::Tag::Ptr tag, Akonadi::Tag akonadiTag) const = 0; virtual QString itemUid(const Item &item) = 0; virtual Domain::DataSource::Ptr createDataSourceFromCollection(Akonadi::Collection collection, DataSourceNameScheme naming) = 0; virtual void updateDataSourceFromCollection(Domain::DataSource::Ptr dataSource, Akonadi::Collection collection, DataSourceNameScheme naming) = 0; virtual Akonadi::Collection createCollectionFromDataSource(Domain::DataSource::Ptr dataSource) = 0; virtual bool isSelectedCollection(Akonadi::Collection collection) = 0; - virtual bool isNoteCollection(Akonadi::Collection collection) = 0; virtual bool isTaskCollection(Akonadi::Collection collection) = 0; virtual bool isTaskItem(Akonadi::Item item) = 0; virtual Domain::Task::Ptr createTaskFromItem(Akonadi::Item item) = 0; virtual void updateTaskFromItem(Domain::Task::Ptr task, Akonadi::Item item) = 0; virtual Akonadi::Item createItemFromTask(Domain::Task::Ptr task) = 0; virtual bool isTaskChild(Domain::Task::Ptr task, Akonadi::Item item) = 0; virtual QString relatedUidFromItem(Akonadi::Item item) = 0; virtual void updateItemParent(Akonadi::Item item, Domain::Task::Ptr parent) = 0; virtual void updateItemProject(Akonadi::Item item, Domain::Project::Ptr project) = 0; virtual void removeItemParent(Akonadi::Item item) = 0; virtual void promoteItemToProject(Akonadi::Item item) = 0; virtual void clearItem(Akonadi::Item *item) = 0; virtual Akonadi::Item::List filterDescendantItems(const Akonadi::Item::List &potentialChildren, const Akonadi::Item &ancestorItem) = 0; - virtual bool isNoteItem(Akonadi::Item item) = 0; - virtual Domain::Note::Ptr createNoteFromItem(Akonadi::Item item) = 0; - virtual void updateNoteFromItem(Domain::Note::Ptr note, Akonadi::Item item) = 0; - - virtual Akonadi::Item createItemFromNote(Domain::Note::Ptr note) = 0; - Domain::Artifact::Ptr createArtifactFromItem(const Akonadi::Item &item); void updateArtifactFromItem(const Domain::Artifact::Ptr &artifact, const Akonadi::Item &item); virtual bool isProjectItem(Akonadi::Item item) = 0; virtual Domain::Project::Ptr createProjectFromItem(Akonadi::Item item) = 0; virtual void updateProjectFromItem(Domain::Project::Ptr project, Akonadi::Item item) = 0; virtual Akonadi::Item createItemFromProject(Domain::Project::Ptr project) = 0; virtual bool isProjectChild(Domain::Project::Ptr project, Akonadi::Item item) = 0; virtual Domain::Context::Ptr createContextFromTag(Akonadi::Tag tag) = 0; virtual void updateContextFromTag(Domain::Context::Ptr context, Akonadi::Tag tag) = 0; virtual Akonadi::Tag createTagFromContext(Domain::Context::Ptr context) = 0; virtual bool isContextTag(const Domain::Context::Ptr &context, const Akonadi::Tag &tag) const = 0; virtual bool isContextChild(Domain::Context::Ptr context, Akonadi::Item item) const = 0; - virtual Domain::Tag::Ptr createTagFromAkonadiTag(Akonadi::Tag tag) = 0; - virtual void updateTagFromAkonadiTag(Domain::Tag::Ptr tag, Akonadi::Tag akonadiTag) = 0; - virtual Akonadi::Tag createAkonadiTagFromTag(Domain::Tag::Ptr tag) = 0; - virtual bool isTagChild(Domain::Tag::Ptr tag, Akonadi::Item item) = 0; - virtual bool hasContextTags(Akonadi::Item item) const = 0; - virtual bool hasAkonadiTags(Akonadi::Item item) const = 0; static QByteArray contextTagType(); }; } #endif // AKONADI_SERIALIZERINTERFACE_H diff --git a/src/akonadi/akonadistorage.cpp b/src/akonadi/akonadistorage.cpp index 5baf2436..7641c7ab 100644 --- a/src/akonadi/akonadistorage.cpp +++ b/src/akonadi/akonadistorage.cpp @@ -1,317 +1,314 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "akonadistorage.h" #include #include #include #include #include #include #include #include #include #include #include #include -#include #include #include #include #include #include #include #include #include "akonadi/akonadicollectionfetchjobinterface.h" #include "akonadi/akonadiitemfetchjobinterface.h" #include "akonadi/akonaditagfetchjobinterface.h" #include "akonadi/akonadistoragesettings.h" using namespace Akonadi; class CollectionJob : public CollectionFetchJob, public CollectionFetchJobInterface { Q_OBJECT public: CollectionJob(const Collection &collection, Type type = FirstLevel, QObject *parent = Q_NULLPTR) : CollectionFetchJob(collection, type, parent), m_collection(collection), m_type(type) { } Collection::List collections() const Q_DECL_OVERRIDE { auto collections = CollectionFetchJob::collections(); // Memorize them to reconstruct the ancestor chain later QMap collectionsMap; collectionsMap[m_collection.id()] = m_collection; foreach (const auto &collection, collections) { collectionsMap[collection.id()] = collection; } // Why the hell isn't fetchScope() const and returning a reference??? auto self = const_cast(this); const auto allowedMimeTypes = self->fetchScope().contentMimeTypes().toSet(); if (!allowedMimeTypes.isEmpty()) { collections.erase(std::remove_if(collections.begin(), collections.end(), [allowedMimeTypes] (const Collection &collection) { auto mimeTypes = collection.contentMimeTypes().toSet(); return mimeTypes.intersect(allowedMimeTypes).isEmpty(); }), collections.end()); } if (m_type != Base) { // Replace the dummy parents in the ancestor chain with proper ones // full of juicy data std::function reconstructAncestors = [collectionsMap, &reconstructAncestors, this] (const Collection &collection) -> Collection { Q_ASSERT(collection.isValid()); if (collection == m_collection) return collection; auto parent = collection.parentCollection(); auto reconstructedParent = reconstructAncestors(collectionsMap[parent.id()]); auto result = collection; result.setParentCollection(reconstructedParent); return result; }; std::transform(collections.begin(), collections.end(), collections.begin(), reconstructAncestors); } return collections; } void setResource(const QString &resource) Q_DECL_OVERRIDE { fetchScope().setResource(resource); } private: const Collection m_collection; const Type m_type; }; class ItemJob : public ItemFetchJob, public ItemFetchJobInterface { Q_OBJECT public: using ItemFetchJob::ItemFetchJob; Item::List items() const Q_DECL_OVERRIDE { return ItemFetchJob::items(); } void setCollection(const Collection &collection) Q_DECL_OVERRIDE { ItemFetchJob::setCollection(collection); } }; class TagJob : public TagFetchJob, public TagFetchJobInterface { Q_OBJECT public: using TagFetchJob::TagFetchJob; Tag::List tags() const Q_DECL_OVERRIDE { return TagFetchJob::tags(); } }; Storage::Storage() { } Storage::~Storage() { } Collection Storage::defaultTaskCollection() { return StorageSettings::instance().defaultTaskCollection(); } Collection Storage::defaultNoteCollection() { return StorageSettings::instance().defaultNoteCollection(); } KJob *Storage::createItem(Item item, Collection collection) { return new ItemCreateJob(item, collection); } KJob *Storage::updateItem(Item item, QObject *parent) { return new ItemModifyJob(item, parent); } KJob *Storage::removeItem(Item item) { return new ItemDeleteJob(item); } KJob *Storage::removeItems(Item::List items, QObject *parent) { return new ItemDeleteJob(items, parent); } KJob *Storage::moveItem(Item item, Collection collection, QObject *parent) { return new ItemMoveJob(item, collection, parent); } KJob *Storage::moveItems(Item::List items, Collection collection, QObject *parent) { return new ItemMoveJob(items, collection, parent); } KJob *Storage::createCollection(Collection collection, QObject *parent) { return new CollectionCreateJob(collection, parent); } KJob *Storage::updateCollection(Collection collection, QObject *parent) { return new CollectionModifyJob(collection, parent); } KJob *Storage::removeCollection(Collection collection, QObject *parent) { return new CollectionDeleteJob(collection, parent); } KJob *Storage::createTransaction() { return new TransactionSequence(); } KJob *Storage::createTag(Tag tag) { return new TagCreateJob(tag); } KJob *Storage::updateTag(Tag tag) { return new TagModifyJob(tag); } KJob *Storage::removeTag(Tag tag) { return new Akonadi::TagDeleteJob(tag); } CollectionFetchJobInterface *Storage::fetchCollections(Collection collection, StorageInterface::FetchDepth depth, FetchContentTypes types) { QStringList contentMimeTypes; - if (types & Notes) - contentMimeTypes << NoteUtils::noteMimeType(); if (types & Tasks) contentMimeTypes << KCalCore::Todo::todoMimeType(); auto job = new CollectionJob(collection, jobTypeFromDepth(depth)); auto scope = job->fetchScope(); scope.setContentMimeTypes(contentMimeTypes); scope.setIncludeStatistics(true); scope.setAncestorRetrieval(CollectionFetchScope::All); scope.setListFilter(Akonadi::CollectionFetchScope::Display); job->setFetchScope(scope); return job; } ItemFetchJobInterface *Storage::fetchItems(Collection collection) { auto job = new ItemJob(collection); configureItemFetchJob(job); return job; } ItemFetchJobInterface *Storage::fetchItem(Akonadi::Item item) { auto job = new ItemJob(item); configureItemFetchJob(job); return job; } ItemFetchJobInterface *Storage::fetchTagItems(Tag tag) { auto job = new ItemJob(tag); configureItemFetchJob(job); return job; } TagFetchJobInterface *Storage::fetchTags() { return new TagJob; } CollectionFetchJob::Type Storage::jobTypeFromDepth(StorageInterface::FetchDepth depth) { auto jobType = CollectionJob::Base; switch (depth) { case Base: jobType = CollectionJob::Base; break; case FirstLevel: jobType = CollectionJob::FirstLevel; break; case Recursive: jobType = CollectionJob::Recursive; break; default: qFatal("Unexpected enum value"); break; } return jobType; } void Storage::configureItemFetchJob(ItemJob *job) { auto scope = job->fetchScope(); scope.fetchFullPayload(); scope.fetchAllAttributes(); scope.setFetchTags(true); scope.tagFetchScope().setFetchIdOnly(false); scope.setAncestorRetrieval(ItemFetchScope::All); job->setFetchScope(scope); } #include "akonadistorage.moc" diff --git a/src/akonadi/akonaditagqueries.cpp b/src/akonadi/akonaditagqueries.cpp deleted file mode 100644 index 7cc4259f..00000000 --- a/src/akonadi/akonaditagqueries.cpp +++ /dev/null @@ -1,58 +0,0 @@ -/* This file is part of Zanshin - - Copyright 2014 Kevin Ottens - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License or (at your option) version 3 or any later version - accepted by the membership of KDE e.V. (or its successor approved - by the membership of KDE e.V.), which shall act as a proxy - defined in Section 14 of version 3 of the license. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - USA. -*/ - -#include "akonaditagqueries.h" - -using namespace Akonadi; - -TagQueries::TagQueries(const StorageInterface::Ptr &storage, const SerializerInterface::Ptr &serializer, const MonitorInterface::Ptr &monitor) - : m_serializer(serializer), - m_helpers(new LiveQueryHelpers(serializer, storage)), - m_integrator(new LiveQueryIntegrator(serializer, monitor)) -{ - m_integrator->addRemoveHandler([this] (const Tag &tag) { - m_findTopLevel.remove(tag.id()); - }); -} - -TagQueries::TagResult::Ptr TagQueries::findAll() const -{ - auto fetch = m_helpers->fetchTags(); - auto predicate = [this] (const Akonadi::Tag &akonadiTag) { - return akonadiTag.type() == Akonadi::Tag::PLAIN; - }; - m_integrator->bind("TagQueries::findAll", m_findAll, fetch, predicate); - return m_findAll->result(); -} - -TagQueries::NoteResult::Ptr TagQueries::findNotes(Domain::Tag::Ptr tag) const -{ - Akonadi::Tag akonadiTag = m_serializer->createAkonadiTagFromTag(tag); - auto &query = m_findTopLevel[akonadiTag.id()]; - auto fetch = m_helpers->fetchItems(akonadiTag); - auto predicate = [this, tag] (const Akonadi::Item &item) { - return m_serializer->isTagChild(tag, item); - }; - m_integrator->bind("TagQueries::findNotes", query, fetch, predicate); - return query->result(); -} diff --git a/src/akonadi/akonaditagqueries.h b/src/akonadi/akonaditagqueries.h deleted file mode 100644 index 6490277e..00000000 --- a/src/akonadi/akonaditagqueries.h +++ /dev/null @@ -1,68 +0,0 @@ -/* This file is part of Zanshin - - Copyright 2014 Kevin Ottens - Copyright 2014 Franck Arrecot - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License or (at your option) version 3 or any later version - accepted by the membership of KDE e.V. (or its successor approved - by the membership of KDE e.V.), which shall act as a proxy - defined in Section 14 of version 3 of the license. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - USA. -*/ - -#ifndef AKONADI_TAGQUERIES_H -#define AKONADI_TAGQUERIES_H - -#include "domain/tagqueries.h" - -#include "akonadi/akonadilivequeryhelpers.h" -#include "akonadi/akonadilivequeryintegrator.h" - -namespace Akonadi { - -class TagQueries : public Domain::TagQueries -{ -public: - typedef QSharedPointer Ptr; - - typedef Domain::LiveQueryInput TagInputQuery; - typedef Domain::LiveQueryOutput TagQueryOutput; - typedef Domain::QueryResult TagResult; - typedef Domain::QueryResultProvider TagProvider; - - typedef Domain::LiveQueryInput ItemInputQuery; - typedef Domain::LiveQueryOutput NoteQueryOutput; - typedef Domain::QueryResultProvider NoteProvider; - typedef Domain::QueryResult NoteResult; - - TagQueries(const StorageInterface::Ptr &storage, - const SerializerInterface::Ptr &serializer, - const MonitorInterface::Ptr &monitor); - - TagResult::Ptr findAll() const Q_DECL_OVERRIDE; - NoteResult::Ptr findNotes(Domain::Tag::Ptr tag) const Q_DECL_OVERRIDE; - -private: - SerializerInterface::Ptr m_serializer; - LiveQueryHelpers::Ptr m_helpers; - LiveQueryIntegrator::Ptr m_integrator; - - mutable TagQueryOutput::Ptr m_findAll; - mutable QHash m_findTopLevel; -}; - -} // akonadi namespace - -#endif // AKONADI_TAGQUERIES_H diff --git a/src/akonadi/akonaditagrepository.cpp b/src/akonadi/akonaditagrepository.cpp deleted file mode 100644 index cd86289a..00000000 --- a/src/akonadi/akonaditagrepository.cpp +++ /dev/null @@ -1,128 +0,0 @@ -/* This file is part of Zanshin - - Copyright 2014 Kevin Ottens - Copyright 2014 Franck Arrecot - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License or (at your option) version 3 or any later version - accepted by the membership of KDE e.V. (or its successor approved - by the membership of KDE e.V.), which shall act as a proxy - defined in Section 14 of version 3 of the license. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - USA. -*/ - -#include "akonaditagrepository.h" - -#include "akonadiitemfetchjobinterface.h" - -#include "utils/compositejob.h" - -using namespace Akonadi; - - -TagRepository::TagRepository(const StorageInterface::Ptr &storage, - const SerializerInterface::Ptr &serializer) - : m_storage(storage), - m_serializer(serializer) -{ -} - -KJob *TagRepository::create(Domain::Tag::Ptr tag) -{ - auto akonadiTag = m_serializer->createAkonadiTagFromTag(tag); - Q_ASSERT(!akonadiTag.isValid()); - return m_storage->createTag(akonadiTag); -} - -KJob *TagRepository::remove(Domain::Tag::Ptr tag) -{ - auto akonadiTag = m_serializer->createAkonadiTagFromTag(tag); - Q_ASSERT(akonadiTag.isValid()); - return m_storage->removeTag(akonadiTag); -} - -KJob *TagRepository::associate(Domain::Tag::Ptr parent, Domain::Note::Ptr child) -{ - auto akonadiTag = m_serializer->createAkonadiTagFromTag(parent); - Q_ASSERT(akonadiTag.isValid()); - - Item childItem = m_serializer->createItemFromNote(child); - Q_ASSERT(childItem.isValid()); - - auto job = new Utils::CompositeJob(); - ItemFetchJobInterface *fetchItemJob = m_storage->fetchItem(childItem); - job->install(fetchItemJob->kjob(), [akonadiTag, fetchItemJob, parent, job, this] { - if (fetchItemJob->kjob()->error() != KJob::NoError) - return; - - Q_ASSERT(fetchItemJob->items().size() == 1); - auto childItem = fetchItemJob->items().at(0); - childItem.setTag(akonadiTag); - - auto updateJob = m_storage->updateItem(childItem); - job->addSubjob(updateJob); - updateJob->start(); - }); - return job; -} - -KJob *TagRepository::dissociate(Domain::Tag::Ptr parent, Domain::Note::Ptr child) -{ - Item childItem = m_serializer->createItemFromNote(child); - Q_ASSERT(childItem.isValid()); - - auto job = new Utils::CompositeJob(); - ItemFetchJobInterface *fetchItemJob = m_storage->fetchItem(childItem); - job->install(fetchItemJob->kjob(), [fetchItemJob, parent, job, this] { - if (fetchItemJob->kjob()->error() != KJob::NoError) - return; - - Q_ASSERT(fetchItemJob->items().size() == 1); - auto childItem = fetchItemJob->items().at(0); - auto tag = m_serializer->createAkonadiTagFromTag(parent); - Q_ASSERT(tag.isValid()); - childItem.clearTag(tag); - - auto updateJob = m_storage->updateItem(childItem); - job->addSubjob(updateJob); - updateJob->start(); - }); - - return job; -} - -KJob *TagRepository::dissociateAll(Domain::Note::Ptr child) -{ - Item childItem; - - childItem = m_serializer->createItemFromNote(child); - Q_ASSERT(childItem.isValid()); - auto job = new Utils::CompositeJob(); - ItemFetchJobInterface *fetchItemJob = m_storage->fetchItem(childItem); - job->install(fetchItemJob->kjob(), [fetchItemJob, job, this] { - if (fetchItemJob->kjob()->error() != KJob::NoError) - return; - - Q_ASSERT(fetchItemJob->items().size() == 1); - auto childItem = fetchItemJob->items().at(0); - foreach (const Tag &tag, childItem.tags()) - childItem.clearTag(tag); - - auto updateJob = m_storage->updateItem(childItem); - job->addSubjob(updateJob); - updateJob->start(); - }); - - return job; -} diff --git a/src/akonadi/akonaditagrepository.h b/src/akonadi/akonaditagrepository.h deleted file mode 100644 index 7da6a4a1..00000000 --- a/src/akonadi/akonaditagrepository.h +++ /dev/null @@ -1,59 +0,0 @@ -/* This file is part of Zanshin - - Copyright 2014 Kevin Ottens - Copyright 2014 Franck Arrecot - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License or (at your option) version 3 or any later version - accepted by the membership of KDE e.V. (or its successor approved - by the membership of KDE e.V.), which shall act as a proxy - defined in Section 14 of version 3 of the license. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - USA. -*/ - -#ifndef AKONADITAGREPOSITORY_H -#define AKONADITAGREPOSITORY_H - -#include "domain/tagrepository.h" - -#include "akonadi/akonadiserializerinterface.h" -#include "akonadi/akonadistorageinterface.h" - -namespace Akonadi { - -class SerializerInterface; -class StorageInterface; - -class TagRepository : public QObject, public Domain::TagRepository -{ - Q_OBJECT -public: - typedef QSharedPointer Ptr; - - TagRepository(const StorageInterface::Ptr &storage, - const SerializerInterface::Ptr &serializer); - - KJob *create(Domain::Tag::Ptr tag) Q_DECL_OVERRIDE; - KJob *remove(Domain::Tag::Ptr tag) Q_DECL_OVERRIDE; - - KJob *associate(Domain::Tag::Ptr parent, Domain::Note::Ptr child) Q_DECL_OVERRIDE; - KJob *dissociate(Domain::Tag::Ptr parent, Domain::Note::Ptr child) Q_DECL_OVERRIDE; - KJob *dissociateAll(Domain::Note::Ptr child) Q_DECL_OVERRIDE; - -private: - StorageInterface::Ptr m_storage; - SerializerInterface::Ptr m_serializer; -}; -} -#endif // AKONADITAGREPOSITORY_H diff --git a/src/domain/CMakeLists.txt b/src/domain/CMakeLists.txt index a356aba3..451d73ce 100644 --- a/src/domain/CMakeLists.txt +++ b/src/domain/CMakeLists.txt @@ -1,27 +1,21 @@ set(domain_SRCS artifact.cpp context.cpp contextqueries.cpp contextrepository.cpp datasource.cpp datasourcequeries.cpp datasourcerepository.cpp - note.cpp - notequeries.cpp - noterepository.cpp project.cpp projectqueries.cpp projectrepository.cpp queryresult.cpp queryresultinterface.cpp queryresultprovider.cpp - tag.cpp - tagqueries.cpp - tagrepository.cpp task.cpp taskqueries.cpp taskrepository.cpp ) add_library(domain STATIC ${domain_SRCS}) target_link_libraries(domain Qt5::Core) diff --git a/src/domain/note.cpp b/src/domain/note.cpp deleted file mode 100644 index e4c38d42..00000000 --- a/src/domain/note.cpp +++ /dev/null @@ -1,36 +0,0 @@ -/* This file is part of Zanshin - - Copyright 2014 Kevin Ottens - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License or (at your option) version 3 or any later version - accepted by the membership of KDE e.V. (or its successor approved - by the membership of KDE e.V.), which shall act as a proxy - defined in Section 14 of version 3 of the license. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - USA. -*/ - - -#include "note.h" - -using namespace Domain; - -Note::Note(QObject *parent) - : Artifact(parent) -{ -} - -Note::~Note() -{ -} diff --git a/src/domain/note.h b/src/domain/note.h deleted file mode 100644 index a3522b55..00000000 --- a/src/domain/note.h +++ /dev/null @@ -1,48 +0,0 @@ -/* This file is part of Zanshin - - Copyright 2014 Kevin Ottens - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License or (at your option) version 3 or any later version - accepted by the membership of KDE e.V. (or its successor approved - by the membership of KDE e.V.), which shall act as a proxy - defined in Section 14 of version 3 of the license. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - USA. -*/ - - -#ifndef DOMAIN_NOTE_H -#define DOMAIN_NOTE_H - -#include "artifact.h" - -namespace Domain { - -class Note : public Artifact -{ - Q_OBJECT - -public: - typedef QSharedPointer Ptr; - typedef QList List; - - explicit Note(QObject *parent = Q_NULLPTR); - virtual ~Note(); -}; - -} - -Q_DECLARE_METATYPE(Domain::Note::Ptr) - -#endif // DOMAIN_NOTE_H diff --git a/src/domain/notequeries.cpp b/src/domain/notequeries.cpp deleted file mode 100644 index 693b7a5e..00000000 --- a/src/domain/notequeries.cpp +++ /dev/null @@ -1,36 +0,0 @@ -/* This file is part of Zanshin - - Copyright 2014 Kevin Ottens - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License or (at your option) version 3 or any later version - accepted by the membership of KDE e.V. (or its successor approved - by the membership of KDE e.V.), which shall act as a proxy - defined in Section 14 of version 3 of the license. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - USA. -*/ - - -#include "notequeries.h" - -using namespace Domain; - -NoteQueries::NoteQueries() -{ -} - -NoteQueries::~NoteQueries() -{ -} - diff --git a/src/domain/notequeries.h b/src/domain/notequeries.h deleted file mode 100644 index b9db18f6..00000000 --- a/src/domain/notequeries.h +++ /dev/null @@ -1,47 +0,0 @@ -/* This file is part of Zanshin - - Copyright 2014 Kevin Ottens - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License or (at your option) version 3 or any later version - accepted by the membership of KDE e.V. (or its successor approved - by the membership of KDE e.V.), which shall act as a proxy - defined in Section 14 of version 3 of the license. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - USA. -*/ - -#ifndef DOMAIN_NOTEQUERIES_H -#define DOMAIN_NOTEQUERIES_H - -#include "note.h" -#include "project.h" -#include "queryresult.h" - -namespace Domain { - -class NoteQueries -{ -public: - typedef QSharedPointer Ptr; - - NoteQueries(); - virtual ~NoteQueries(); - - virtual QueryResult::Ptr findAll() const = 0; - virtual QueryResult::Ptr findInbox() const = 0; -}; - -} - -#endif // DOMAIN_NOTEQUERIES_H diff --git a/src/domain/noterepository.cpp b/src/domain/noterepository.cpp deleted file mode 100644 index 031ba86f..00000000 --- a/src/domain/noterepository.cpp +++ /dev/null @@ -1,36 +0,0 @@ -/* This file is part of Zanshin - - Copyright 2014 Kevin Ottens - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License or (at your option) version 3 or any later version - accepted by the membership of KDE e.V. (or its successor approved - by the membership of KDE e.V.), which shall act as a proxy - defined in Section 14 of version 3 of the license. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - USA. -*/ - - -#include "noterepository.h" - -using namespace Domain; - -NoteRepository::NoteRepository() -{ -} - -NoteRepository::~NoteRepository() -{ -} - diff --git a/src/domain/noterepository.h b/src/domain/noterepository.h deleted file mode 100644 index 23f2abf7..00000000 --- a/src/domain/noterepository.h +++ /dev/null @@ -1,52 +0,0 @@ -/* This file is part of Zanshin - - Copyright 2014 Kevin Ottens - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License or (at your option) version 3 or any later version - accepted by the membership of KDE e.V. (or its successor approved - by the membership of KDE e.V.), which shall act as a proxy - defined in Section 14 of version 3 of the license. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - USA. -*/ - -#ifndef DOMAIN_NOTEREPOSITORY_H -#define DOMAIN_NOTEREPOSITORY_H - -#include "datasource.h" -#include "note.h" -#include "tag.h" - -class KJob; - -namespace Domain { - -class NoteRepository -{ -public: - typedef QSharedPointer Ptr; - - NoteRepository(); - virtual ~NoteRepository(); - - virtual KJob *create(Note::Ptr task) = 0; - virtual KJob *createInTag(Note::Ptr task, Tag::Ptr tag) = 0; - - virtual KJob *update(Note::Ptr note) = 0; - virtual KJob *remove(Note::Ptr note) = 0; -}; - -} - -#endif // DOMAIN_NOTEREPOSITORY_H diff --git a/src/domain/tag.cpp b/src/domain/tag.cpp deleted file mode 100644 index 1ff1a94c..00000000 --- a/src/domain/tag.cpp +++ /dev/null @@ -1,50 +0,0 @@ -/* This file is part of Zanshin - - Copyright 2014 Kevin Ottens - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License or (at your option) version 3 or any later version - accepted by the membership of KDE e.V. (or its successor approved - by the membership of KDE e.V.), which shall act as a proxy - defined in Section 14 of version 3 of the license. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - USA. -*/ - - -#include "tag.h" - -using namespace Domain; - -Tag::Tag(QObject *parent) - : QObject(parent) -{ -} - -Tag::~Tag() -{ -} - -QString Tag::name() const -{ - return m_name; -} - -void Tag::setName(const QString &name) -{ - if (m_name == name) - return; - - m_name = name; - emit nameChanged(name); -} diff --git a/src/domain/tag.h b/src/domain/tag.h deleted file mode 100644 index 711df1c0..00000000 --- a/src/domain/tag.h +++ /dev/null @@ -1,62 +0,0 @@ -/* This file is part of Zanshin - - Copyright 2014 Kevin Ottens - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License or (at your option) version 3 or any later version - accepted by the membership of KDE e.V. (or its successor approved - by the membership of KDE e.V.), which shall act as a proxy - defined in Section 14 of version 3 of the license. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - USA. -*/ - - -#ifndef DOMAIN_TAG_H -#define DOMAIN_TAG_H - -#include -#include -#include - -namespace Domain { - -class Tag : public QObject -{ - Q_OBJECT - Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) - -public: - typedef QSharedPointer Ptr; - typedef QList List; - - explicit Tag(QObject *parent = Q_NULLPTR); - virtual ~Tag(); - - QString name() const; - -public slots: - void setName(const QString &name); - -signals: - void nameChanged(const QString &name); - -private: - QString m_name; -}; - -} - -Q_DECLARE_METATYPE(Domain::Tag::Ptr) - -#endif // DOMAIN_TAG_H diff --git a/src/domain/tagqueries.cpp b/src/domain/tagqueries.cpp deleted file mode 100644 index d226afc7..00000000 --- a/src/domain/tagqueries.cpp +++ /dev/null @@ -1,36 +0,0 @@ -/* This file is part of Zanshin - - Copyright 2014 Kevin Ottens - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License or (at your option) version 3 or any later version - accepted by the membership of KDE e.V. (or its successor approved - by the membership of KDE e.V.), which shall act as a proxy - defined in Section 14 of version 3 of the license. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - USA. -*/ - - -#include "tagqueries.h" - -using namespace Domain; - -TagQueries::TagQueries() -{ -} - -TagQueries::~TagQueries() -{ -} - diff --git a/src/domain/tagqueries.h b/src/domain/tagqueries.h deleted file mode 100644 index 733cd192..00000000 --- a/src/domain/tagqueries.h +++ /dev/null @@ -1,48 +0,0 @@ -/* This file is part of Zanshin - - Copyright 2014 Kevin Ottens - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License or (at your option) version 3 or any later version - accepted by the membership of KDE e.V. (or its successor approved - by the membership of KDE e.V.), which shall act as a proxy - defined in Section 14 of version 3 of the license. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - USA. -*/ - -#ifndef DOMAIN_TAGQUERIES_H -#define DOMAIN_TAGQUERIES_H - -#include "note.h" -#include "queryresult.h" -#include "tag.h" - -namespace Domain { - -class TagQueries -{ -public: - typedef QSharedPointer Ptr; - - TagQueries(); - virtual ~TagQueries(); - - virtual QueryResult::Ptr findAll() const = 0; - - virtual QueryResult::Ptr findNotes(Tag::Ptr tag) const = 0; -}; - -} - -#endif // DOMAIN_TAGQUERIES_H diff --git a/src/domain/tagrepository.cpp b/src/domain/tagrepository.cpp deleted file mode 100644 index 7fe104dc..00000000 --- a/src/domain/tagrepository.cpp +++ /dev/null @@ -1,36 +0,0 @@ -/* This file is part of Zanshin - - Copyright 2014 Kevin Ottens - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License or (at your option) version 3 or any later version - accepted by the membership of KDE e.V. (or its successor approved - by the membership of KDE e.V.), which shall act as a proxy - defined in Section 14 of version 3 of the license. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - USA. -*/ - - -#include "tagrepository.h" - -using namespace Domain; - -TagRepository::TagRepository() -{ -} - -TagRepository::~TagRepository() -{ -} - diff --git a/src/domain/tagrepository.h b/src/domain/tagrepository.h deleted file mode 100644 index 43ffb5cb..00000000 --- a/src/domain/tagrepository.h +++ /dev/null @@ -1,52 +0,0 @@ -/* This file is part of Zanshin - - Copyright 2014 Kevin Ottens - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License or (at your option) version 3 or any later version - accepted by the membership of KDE e.V. (or its successor approved - by the membership of KDE e.V.), which shall act as a proxy - defined in Section 14 of version 3 of the license. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - USA. -*/ - -#ifndef DOMAIN_TAGREPOSITORY_H -#define DOMAIN_TAGREPOSITORY_H - -#include "note.h" -#include "tag.h" - -class KJob; - -namespace Domain { - -class TagRepository -{ -public: - typedef QSharedPointer Ptr; - - TagRepository(); - virtual ~TagRepository(); - - virtual KJob *create(Tag::Ptr tag) = 0; - virtual KJob *remove(Tag::Ptr tag) = 0; - - virtual KJob *associate(Tag::Ptr parent, Note::Ptr child) = 0; - virtual KJob *dissociate(Tag::Ptr parent, Note::Ptr child) = 0; - virtual KJob *dissociateAll(Note::Ptr child) = 0; -}; - -} - -#endif // DOMAIN_TAGREPOSITORY_H diff --git a/src/presentation/CMakeLists.txt b/src/presentation/CMakeLists.txt index d88885a1..e16bd8cc 100644 --- a/src/presentation/CMakeLists.txt +++ b/src/presentation/CMakeLists.txt @@ -1,28 +1,25 @@ set(presentation_SRCS applicationmodel.cpp artifacteditormodel.cpp artifactfilterproxymodel.cpp - availablenotepagesmodel.cpp availablepagesmodelinterface.cpp availablepagessortfilterproxymodel.cpp availabletaskpagesmodel.cpp availablesourcesmodel.cpp contextpagemodel.cpp errorhandler.cpp errorhandlingmodelbase.cpp metatypes.cpp - noteinboxpagemodel.cpp pagemodel.cpp projectpagemodel.cpp querytreemodelbase.cpp runningtaskmodelinterface.cpp runningtaskmodel.cpp - tagpagemodel.cpp taskinboxpagemodel.cpp tasklistmodel.cpp taskapplicationmodel.cpp workdaypagemodel.cpp ) add_library(presentation STATIC ${presentation_SRCS}) target_link_libraries(presentation Qt5::Core Qt5::Gui KF5::I18n domain utils) diff --git a/src/presentation/applicationmodel.h b/src/presentation/applicationmodel.h index 89e3d4c0..44e51a03 100644 --- a/src/presentation/applicationmodel.h +++ b/src/presentation/applicationmodel.h @@ -1,81 +1,80 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef PRESENTATION_APPLICATIONMODEL_H #define PRESENTATION_APPLICATIONMODEL_H #include #include "domain/datasourcerepository.h" #include "domain/datasourcequeries.h" -#include "domain/noterepository.h" #include "domain/taskrepository.h" #include "presentation/metatypes.h" namespace Presentation { class AvailablePagesModelInterface; class ErrorHandler; class ApplicationModel : public QObject { Q_OBJECT Q_PROPERTY(QObject* availableSources READ availableSources) Q_PROPERTY(QObject* availablePages READ availablePages) Q_PROPERTY(QObject* currentPage READ currentPage WRITE setCurrentPage NOTIFY currentPageChanged) Q_PROPERTY(QObject* editor READ editor) Q_PROPERTY(Presentation::ErrorHandler* errorHandler READ errorHandler WRITE setErrorHandler) public: typedef QSharedPointer Ptr; explicit ApplicationModel(QObject *parent = Q_NULLPTR); ~ApplicationModel(); QObject *availableSources(); QObject *availablePages(); QObject *currentPage(); QObject *editor(); ErrorHandler *errorHandler() const; public slots: void setCurrentPage(QObject *page); void setErrorHandler(ErrorHandler *errorHandler); signals: void currentPageChanged(QObject *page); private: QObjectPtr m_availableSources; QObjectPtr m_availablePages; QObjectPtr m_currentPage; QObjectPtr m_editor; ErrorHandler *m_errorHandler; }; } #endif // PRESENTATION_APPLICATIONMODEL_H diff --git a/src/presentation/availablenotepagesmodel.cpp b/src/presentation/availablenotepagesmodel.cpp deleted file mode 100644 index ee196598..00000000 --- a/src/presentation/availablenotepagesmodel.cpp +++ /dev/null @@ -1,259 +0,0 @@ -/* 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 "availablenotepagesmodel.h" - -#include -#include - -#include - -#include "presentation/availablepagessortfilterproxymodel.h" -#include "presentation/noteinboxpagemodel.h" -#include "presentation/querytreemodel.h" -#include "presentation/tagpagemodel.h" - -#include "utils/jobhandler.h" - -using namespace Presentation; - -AvailableNotePagesModel::AvailableNotePagesModel(const Domain::NoteQueries::Ptr ¬eQueries, - const Domain::NoteRepository::Ptr ¬eRepository, - const Domain::TagQueries::Ptr &tagQueries, - const Domain::TagRepository::Ptr &tagRepository, - QObject *parent) - : AvailablePagesModelInterface(parent), - m_pageListModel(Q_NULLPTR), - m_sortProxyModel(Q_NULLPTR), - m_noteQueries(noteQueries), - m_noteRepository(noteRepository), - m_tagQueries(tagQueries), - m_tagRepository(tagRepository) -{ -} - -QAbstractItemModel *AvailableNotePagesModel::pageListModel() -{ - if (!m_pageListModel) - m_pageListModel = createPageListModel(); - - if (!m_sortProxyModel) { - m_sortProxyModel = new AvailablePagesSortFilterProxyModel(this); - m_sortProxyModel->setSourceModel(m_pageListModel); - } - - return m_sortProxyModel; -} - -bool AvailableNotePagesModel::hasProjectPages() const -{ - return false; -} - -bool AvailableNotePagesModel::hasContextPages() const -{ - return false; -} - -bool AvailableNotePagesModel::hasTagPages() const -{ - return true; -} - -QObject *AvailableNotePagesModel::createPageForIndex(const QModelIndex &index) -{ - QObjectPtr object = index.data(QueryTreeModelBase::ObjectRole).value(); - - if (object == m_inboxObject) { - auto inboxPageModel = new NoteInboxPageModel(m_noteQueries, - m_noteRepository, - this); - inboxPageModel->setErrorHandler(errorHandler()); - return inboxPageModel; - } else if (auto tag = object.objectCast()) { - auto tagPageModel = new TagPageModel(tag, - m_tagQueries, - m_tagRepository, - m_noteRepository, - this); - tagPageModel->setErrorHandler(errorHandler()); - return tagPageModel; - } - - return Q_NULLPTR; -} - -void AvailableNotePagesModel::addProject(const QString &, const Domain::DataSource::Ptr &) -{ - qFatal("Not supported"); -} - -void AvailableNotePagesModel::addContext(const QString &) -{ - qFatal("Not supported"); -} - -void AvailableNotePagesModel::addTag(const QString &name) -{ - auto tag = Domain::Tag::Ptr::create(); - tag->setName(name); - const auto job = m_tagRepository->create(tag); - installHandler(job, i18n("Cannot add tag %1", name)); -} - -void AvailableNotePagesModel::removeItem(const QModelIndex &index) -{ - QObjectPtr object = index.data(QueryTreeModelBase::ObjectRole).value(); - if (auto tag = object.objectCast()) { - const auto job = m_tagRepository->remove(tag); - installHandler(job, i18n("Cannot remove tag %1", tag->name())); - } else { - Q_ASSERT(false); - } -} - -QAbstractItemModel *AvailableNotePagesModel::createPageListModel() -{ - m_inboxObject = QObjectPtr::create(); - m_inboxObject->setProperty("name", i18n("Inbox")); - m_tagsObject = QObjectPtr::create(); - m_tagsObject->setProperty("name", i18n("Tags")); - - m_rootsProvider = Domain::QueryResultProvider::Ptr::create(); - m_rootsProvider->append(m_inboxObject); - m_rootsProvider->append(m_tagsObject); - - auto query = [this](const QObjectPtr &object) -> Domain::QueryResultInterface::Ptr { - if (!object) - return Domain::QueryResult::create(m_rootsProvider); - else if (object == m_tagsObject) - return Domain::QueryResult::copy(m_tagQueries->findAll()); - else - return Domain::QueryResult::Ptr(); - }; - - auto flags = [this](const QObjectPtr &object) { - const Qt::ItemFlags defaultFlags = Qt::ItemIsSelectable - | Qt::ItemIsEnabled - | Qt::ItemIsEditable - | Qt::ItemIsDropEnabled; - const Qt::ItemFlags immutableNodeFlags = Qt::ItemIsSelectable - | Qt::ItemIsEnabled - | Qt::ItemIsDropEnabled; - const Qt::ItemFlags structureNodeFlags = Qt::NoItemFlags; - - return object.objectCast() ? defaultFlags - : object == m_inboxObject ? immutableNodeFlags - : structureNodeFlags; - }; - - auto data = [this](const QObjectPtr &object, int role, int) -> QVariant { - if (role != Qt::DisplayRole - && role != Qt::EditRole - && role != Qt::DecorationRole - && role != QueryTreeModelBase::IconNameRole) { - return QVariant(); - } - - if (role == Qt::EditRole - && (object == m_inboxObject - || object == m_tagsObject)) { - return QVariant(); - } - - if (role == Qt::DisplayRole || role == Qt::EditRole) { - return object->property("name").toString(); - } else if (role == Qt::DecorationRole || role == QueryTreeModelBase::IconNameRole) { - const QString iconName = (object == m_inboxObject) ? QStringLiteral("mail-folder-inbox") - : (object == m_tagsObject) ? QStringLiteral("folder") - : QStringLiteral("view-pim-tasks"); - - if (role == Qt::DecorationRole) - return QVariant::fromValue(QIcon::fromTheme(iconName)); - else - return iconName; - } else { - return QVariant(); - } - }; - - auto setData = [this](const QObjectPtr &object, const QVariant &, int role) { - if (role != Qt::EditRole) { - return false; - } - - if (object == m_inboxObject - || object == m_tagsObject) { - return false; - } - - if (object.objectCast()) { - return false; // Tag renaming is NOT allowed - } else { - Q_ASSERT(false); - } - - return true; - }; - - auto drop = [this](const QMimeData *mimeData, Qt::DropAction, const QObjectPtr &object) { - if (!mimeData->hasFormat(QStringLiteral("application/x-zanshin-object"))) - return false; - - auto droppedArtifacts = mimeData->property("objects").value(); - if (droppedArtifacts.isEmpty()) - return false; - - if (std::any_of(droppedArtifacts.begin(), droppedArtifacts.end(), - [](const Domain::Artifact::Ptr &droppedArtifact) { - return !droppedArtifact.objectCast(); - })) { - return false; - } - - if (auto tag = object.objectCast()) { - foreach (const auto &droppedArtifact, droppedArtifacts) { - auto note = droppedArtifact.staticCast(); - const auto job = m_tagRepository->associate(tag, note); - installHandler(job, i18n("Cannot tag %1 with %2", note->title(), tag->name())); - } - return true; - } else if (object == m_inboxObject) { - foreach (const auto &droppedArtifact, droppedArtifacts) { - auto note = droppedArtifact.staticCast(); - const auto job = m_tagRepository->dissociateAll(note); - installHandler(job, i18n("Cannot move %1 to Inbox", note->title())); - } - return true; - } - - return false; - }; - - auto drag = [](const QObjectPtrList &) -> QMimeData* { - return Q_NULLPTR; - }; - - return new QueryTreeModel(query, flags, data, setData, drop, drag, nullptr, this); -} diff --git a/src/presentation/availablenotepagesmodel.h b/src/presentation/availablenotepagesmodel.h deleted file mode 100644 index ec54ac4f..00000000 --- a/src/presentation/availablenotepagesmodel.h +++ /dev/null @@ -1,83 +0,0 @@ -/* This file is part of Zanshin - - Copyright 2015 Kevin Ottens - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License or (at your option) version 3 or any later version - accepted by the membership of KDE e.V. (or its successor approved - by the membership of KDE e.V.), which shall act as a proxy - defined in Section 14 of version 3 of the license. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - USA. -*/ - - -#ifndef PRESENTATION_AVAILABLENOTEPAGESMODEL_H -#define PRESENTATION_AVAILABLENOTEPAGESMODEL_H - -#include "presentation/availablepagesmodelinterface.h" - -#include "domain/notequeries.h" -#include "domain/noterepository.h" -#include "domain/tagqueries.h" -#include "domain/tagrepository.h" - -#include "presentation/metatypes.h" - -namespace Presentation { - -class AvailablePagesSortFilterProxyModel; - -class AvailableNotePagesModel : public AvailablePagesModelInterface -{ - Q_OBJECT -public: - explicit AvailableNotePagesModel(const Domain::NoteQueries::Ptr ¬eQueries, - const Domain::NoteRepository::Ptr ¬eRepository, - const Domain::TagQueries::Ptr &tagQueries, - const Domain::TagRepository::Ptr &tagRepository, - QObject *parent = Q_NULLPTR); - - QAbstractItemModel *pageListModel() Q_DECL_OVERRIDE; - - bool hasProjectPages() const Q_DECL_OVERRIDE; - bool hasContextPages() const Q_DECL_OVERRIDE; - bool hasTagPages() const Q_DECL_OVERRIDE; - - QObject *createPageForIndex(const QModelIndex &index) Q_DECL_OVERRIDE; - - void addProject(const QString &name, const Domain::DataSource::Ptr &source) Q_DECL_OVERRIDE; - void addContext(const QString &name) Q_DECL_OVERRIDE; - void addTag(const QString &name) Q_DECL_OVERRIDE; - void removeItem(const QModelIndex &index) Q_DECL_OVERRIDE; - -private: - QAbstractItemModel *createPageListModel(); - - QAbstractItemModel *m_pageListModel; - Presentation::AvailablePagesSortFilterProxyModel *m_sortProxyModel; - - Domain::NoteQueries::Ptr m_noteQueries; - Domain::NoteRepository::Ptr m_noteRepository; - - Domain::TagQueries::Ptr m_tagQueries; - Domain::TagRepository::Ptr m_tagRepository; - - Domain::QueryResultProvider::Ptr m_rootsProvider; - QObjectPtr m_inboxObject; - QObjectPtr m_tagsObject; -}; - -} - -#endif // PRESENTATION_AVAILABLENOTEPAGESMODEL_H diff --git a/src/presentation/noteinboxpagemodel.cpp b/src/presentation/noteinboxpagemodel.cpp deleted file mode 100644 index 06afa28d..00000000 --- a/src/presentation/noteinboxpagemodel.cpp +++ /dev/null @@ -1,125 +0,0 @@ -/* 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 "noteinboxpagemodel.h" - -#include - -#include - -#include "presentation/querytreemodel.h" - -using namespace Presentation; - -NoteInboxPageModel::NoteInboxPageModel(const Domain::NoteQueries::Ptr ¬eQueries, - const Domain::NoteRepository::Ptr ¬eRepository, - QObject *parent) - : PageModel(parent), - m_noteQueries(noteQueries), - m_noteRepository(noteRepository) -{ -} - -Domain::Artifact::Ptr NoteInboxPageModel::addItem(const QString &title, const QModelIndex &) -{ - auto note = Domain::Note::Ptr::create(); - note->setTitle(title); - const auto job = m_noteRepository->create(note); - installHandler(job, i18n("Cannot add note %1 in Inbox", title)); - - return note; -} - -void NoteInboxPageModel::removeItem(const QModelIndex &index) -{ - QVariant data = index.data(QueryTreeModelBase::ObjectRole); - auto artifact = data.value(); - auto note = artifact.objectCast(); - Q_ASSERT(note); - const auto job = m_noteRepository->remove(note); - installHandler(job, i18n("Cannot remove note %1 from Inbox", note->title())); -} - -void NoteInboxPageModel::promoteItem(const QModelIndex &) -{ - qFatal("Not supported"); -} - -QAbstractItemModel *NoteInboxPageModel::createCentralListModel() -{ - auto query = [this](const Domain::Note::Ptr ¬e) -> Domain::QueryResultInterface::Ptr { - if (!note) - return m_noteQueries->findInbox(); - else - return Domain::QueryResult::Ptr(); - }; - - auto flags = [](const Domain::Note::Ptr &) { - return Qt::ItemIsSelectable - | Qt::ItemIsEnabled - | Qt::ItemIsEditable - | Qt::ItemIsDragEnabled; - }; - - auto data = [](const Domain::Note::Ptr ¬e, int role, int) -> QVariant { - if (role == Qt::DisplayRole || role == Qt::EditRole) { - return note->title(); - } else { - return QVariant(); - } - }; - - auto setData = [this](const Domain::Note::Ptr ¬e, const QVariant &value, int role) { - if (role != Qt::EditRole) { - return false; - } - - const auto currentTitle = note->title(); - note->setTitle(value.toString()); - const auto job = m_noteRepository->update(note); - installHandler(job, i18n("Cannot modify note %1 in Inbox", currentTitle)); - return true; - }; - - auto drop = [](const QMimeData *, Qt::DropAction, const Domain::Artifact::Ptr &) { - return false; - }; - - auto drag = [](const Domain::Note::List ¬es) -> QMimeData* { - if (notes.isEmpty()) - return Q_NULLPTR; - - auto artifacts = Domain::Artifact::List(); - artifacts.reserve(notes.size()); - std::copy(notes.constBegin(), notes.constEnd(), - std::back_inserter(artifacts)); - - auto data = new QMimeData; - data->setData(QStringLiteral("application/x-zanshin-object"), "object"); - data->setProperty("objects", QVariant::fromValue(artifacts)); - return data; - }; - - return new QueryTreeModel(query, flags, data, setData, drop, drag, nullptr, this); -} diff --git a/src/presentation/noteinboxpagemodel.h b/src/presentation/noteinboxpagemodel.h deleted file mode 100644 index 02ff77ff..00000000 --- a/src/presentation/noteinboxpagemodel.h +++ /dev/null @@ -1,56 +0,0 @@ -/* This file is part of Zanshin - - Copyright 2015 Kevin Ottens - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License or (at your option) version 3 or any later version - accepted by the membership of KDE e.V. (or its successor approved - by the membership of KDE e.V.), which shall act as a proxy - defined in Section 14 of version 3 of the license. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - USA. -*/ - - -#ifndef PRESENTATION_NOTEINBOXPAGEMODEL_H -#define PRESENTATION_NOTEINBOXPAGEMODEL_H - -#include "presentation/pagemodel.h" - -#include "domain/notequeries.h" -#include "domain/noterepository.h" - -namespace Presentation { - -class NoteInboxPageModel : public PageModel -{ - Q_OBJECT -public: - explicit NoteInboxPageModel(const Domain::NoteQueries::Ptr ¬eQueries, - const Domain::NoteRepository::Ptr ¬eRepository, - QObject *parent = Q_NULLPTR); - - Domain::Artifact::Ptr addItem(const QString &title, const QModelIndex &parentIndex = QModelIndex()) Q_DECL_OVERRIDE; - void removeItem(const QModelIndex &index) Q_DECL_OVERRIDE; - void promoteItem(const QModelIndex &index) Q_DECL_OVERRIDE; - -private: - QAbstractItemModel *createCentralListModel() Q_DECL_OVERRIDE; - - Domain::NoteQueries::Ptr m_noteQueries; - Domain::NoteRepository::Ptr m_noteRepository; -}; - -} - -#endif // PRESENTATION_NOTEINBOXPAGEMODEL_H diff --git a/src/presentation/querytreenode.h b/src/presentation/querytreenode.h index 77705599..d8603216 100644 --- a/src/presentation/querytreenode.h +++ b/src/presentation/querytreenode.h @@ -1,187 +1,179 @@ /* This file is part of Zanshin Copyright 2014 Mario Bensi Copyright 2014 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef PRESENTATION_QUERYTREENODE_H #define PRESENTATION_QUERYTREENODE_H #include -// Qt5 TODO, shouldn't be needed anymore, QVariant will do the right thing -#include "domain/note.h" #include "domain/task.h" #include "domain/queryresultinterface.h" #include "querytreemodelbase.h" namespace Presentation { // Qt5 TODO, shouldn't be needed anymore, QVariant will do the right thing namespace Internal { template QVariant variantFromValue(const T &object) { return QVariant::fromValue(object); } - template<> - inline QVariant variantFromValue(const Domain::Note::Ptr &task) - { - return QVariant::fromValue(task.staticCast()); - } - template<> inline QVariant variantFromValue(const Domain::Task::Ptr ¬e) { return QVariant::fromValue(note.staticCast()); } } template class QueryTreeNode : public QueryTreeNodeBase { public: typedef Domain::QueryResultInterface ItemQuery; typedef typename ItemQuery::Ptr ItemQueryPtr; typedef std::function QueryGenerator; typedef std::function FlagsFunction; typedef std::function DataFunction; typedef std::function SetDataFunction; typedef std::function DropFunction; QueryTreeNode(const ItemType &item, QueryTreeNodeBase *parentNode, QueryTreeModelBase *model, const QueryGenerator &queryGenerator, const FlagsFunction &flagsFunction, const DataFunction &dataFunction, const SetDataFunction &setDataFunction) : QueryTreeNodeBase(parentNode, model), m_item(item), m_flagsFunction(flagsFunction), m_dataFunction(dataFunction), m_setDataFunction(setDataFunction) { init(model, queryGenerator); } QueryTreeNode(const ItemType &item, QueryTreeNodeBase *parentNode, QueryTreeModelBase *model, const QueryGenerator &queryGenerator, const FlagsFunction &flagsFunction, const DataFunction &dataFunction, const SetDataFunction &setDataFunction, const DropFunction &dropFunction) : QueryTreeNodeBase(parentNode, model), m_item(item), m_flagsFunction(flagsFunction), m_dataFunction(dataFunction), m_setDataFunction(setDataFunction), m_dropFunction(dropFunction) { init(model, queryGenerator); } ItemType item() const { return m_item; } Qt::ItemFlags flags() const Q_DECL_OVERRIDE { return m_flagsFunction(m_item); } QVariant data(int role) const Q_DECL_OVERRIDE { if (role == QueryTreeModelBase::ObjectRole) return Internal::variantFromValue(m_item); return m_dataFunction(m_item, role, m_additionalInfo); } bool setData(const QVariant &value, int role) Q_DECL_OVERRIDE { return m_setDataFunction(m_item, value, role); } bool dropMimeData(const QMimeData *data, Qt::DropAction action) Q_DECL_OVERRIDE { if (m_dropFunction) return m_dropFunction(data, action, m_item); else return false; } bool hasAdditionalInfo() const { return m_additionalInfo; } void setAdditionalInfo(const AdditionalInfo &info) { m_additionalInfo = info; } private: void init(QueryTreeModelBase *model, const QueryGenerator &queryGenerator) { m_children = queryGenerator(m_item); if (!m_children) return; for (auto child : m_children->data()) { QueryTreeNodeBase *node = new QueryTreeNode(child, this, model, queryGenerator, m_flagsFunction, m_dataFunction, m_setDataFunction, m_dropFunction); appendChild(node); } m_children->addPreInsertHandler([this](const ItemType &, int index) { QModelIndex parentIndex = parent() ? createIndex(row(), 0, this) : QModelIndex(); beginInsertRows(parentIndex, index, index); }); m_children->addPostInsertHandler([this, model, queryGenerator](const ItemType &item, int index) { QueryTreeNodeBase *node = new QueryTreeNode(item, this, model, queryGenerator, m_flagsFunction, m_dataFunction, m_setDataFunction, m_dropFunction); insertChild(index, node); endInsertRows(); }); m_children->addPreRemoveHandler([this](const ItemType &, int index) { QModelIndex parentIndex = parent() ? createIndex(row(), 0, this) : QModelIndex(); beginRemoveRows(parentIndex, index, index); }); m_children->addPostRemoveHandler([this](const ItemType &, int index) { removeChildAt(index); endRemoveRows(); }); m_children->addPostReplaceHandler([this](const ItemType &, int idx) { const QModelIndex parentIndex = parent() ? createIndex(row(), 0, this) : QModelIndex(); const QModelIndex dataIndex = index(idx, 0, parentIndex); emitDataChanged(dataIndex, dataIndex); }); } ItemType m_item; ItemQueryPtr m_children; mutable AdditionalInfo m_additionalInfo; FlagsFunction m_flagsFunction; DataFunction m_dataFunction; SetDataFunction m_setDataFunction; DropFunction m_dropFunction; }; } #endif // PRESENTATION_QUERYTREENODE_H diff --git a/src/presentation/tagpagemodel.cpp b/src/presentation/tagpagemodel.cpp deleted file mode 100644 index 320fcce4..00000000 --- a/src/presentation/tagpagemodel.cpp +++ /dev/null @@ -1,146 +0,0 @@ -/* 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 "tagpagemodel.h" - -#include - -#include - -#include "domain/noterepository.h" -#include "domain/task.h" -#include "domain/tagqueries.h" -#include "domain/tagrepository.h" -#include "domain/taskqueries.h" -#include "domain/taskrepository.h" - -#include "presentation/querytreemodel.h" - -using namespace Presentation; - -TagPageModel::TagPageModel(const Domain::Tag::Ptr &tag, - const Domain::TagQueries::Ptr &tagQueries, - const Domain::TagRepository::Ptr &tagRepository, - const Domain::NoteRepository::Ptr ¬eRepository, - QObject *parent) - : PageModel(parent), - m_tag(tag), - m_tagQueries(tagQueries), - m_tagRepository(tagRepository), - m_noteRepository(noteRepository) -{ - -} - -Domain::Tag::Ptr TagPageModel::tag() const -{ - return m_tag; -} - -Domain::Artifact::Ptr TagPageModel::addItem(const QString &title, const QModelIndex &) -{ - auto note = Domain::Note::Ptr::create(); - note->setTitle(title); - const auto job = m_noteRepository->createInTag(note, m_tag); - installHandler(job, i18n("Cannot add note %1 in tag %2", title, m_tag->name())); - return note; -} - -void TagPageModel::removeItem(const QModelIndex &index) -{ - QVariant data = index.data(QueryTreeModelBase::ObjectRole); - auto artifact = data.value(); - auto note = artifact.objectCast(); - Q_ASSERT(note); - const auto job = m_tagRepository->dissociate(m_tag, note); - installHandler(job, i18n("Cannot remove note %1 from tag %2", note->title(), m_tag->name())); -} - -void TagPageModel::promoteItem(const QModelIndex &) -{ - qFatal("Not supported"); -} - -QAbstractItemModel *TagPageModel::createCentralListModel() -{ - auto query = [this] (const Domain::Note::Ptr ¬e) -> Domain::QueryResultInterface::Ptr { - if (!note) - return m_tagQueries->findNotes(m_tag); - else - return Domain::QueryResult::Ptr(); - }; - - auto flags = [](const Domain::Note::Ptr &) { - return Qt::ItemIsSelectable - | Qt::ItemIsEnabled - | Qt::ItemIsEditable - | Qt::ItemIsDragEnabled; - }; - - auto data = [](const Domain::Note::Ptr ¬e, int role, int) -> QVariant { - if (role != Qt::DisplayRole - && role != Qt::EditRole) { - return QVariant(); - } - - if (role == Qt::DisplayRole || role == Qt::EditRole) { - return note->title(); - } else { - return QVariant(); - } - }; - - auto setData = [this] (const Domain::Note::Ptr ¬e, const QVariant &value, int role) { - if (role != Qt::EditRole) { - return false; - } - - const auto currentTitle = note->title(); - note->setTitle(value.toString()); - const auto job = m_noteRepository->update(note); - installHandler(job, i18n("Cannot modify note %1 in tag %2", currentTitle, m_tag->name())); - return true; - }; - - auto drop = [] (const QMimeData *, Qt::DropAction, const Domain::Note::Ptr &) { - return false; - }; - - auto drag = [] (const Domain::Note::List ¬es) -> QMimeData* { - if (notes.isEmpty()) - return Q_NULLPTR; - - auto draggedArtifacts = Domain::Artifact::List(); - draggedArtifacts.reserve(notes.count()); - std::copy(notes.constBegin(), notes.constEnd(), - std::back_inserter(draggedArtifacts)); - - auto data = new QMimeData; - data->setData(QStringLiteral("application/x-zanshin-object"), "object"); - data->setProperty("objects", QVariant::fromValue(draggedArtifacts)); - return data; - }; - - return new QueryTreeModel(query, flags, data, setData, drop, drag, nullptr, this); -} diff --git a/src/presentation/tagpagemodel.h b/src/presentation/tagpagemodel.h deleted file mode 100644 index 06bda202..00000000 --- a/src/presentation/tagpagemodel.h +++ /dev/null @@ -1,65 +0,0 @@ -/* This file is part of Zanshin - - Copyright 2014 Kevin Ottens - Copyright 2014 Franck Arrecot - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License or (at your option) version 3 or any later version - accepted by the membership of KDE e.V. (or its successor approved - by the membership of KDE e.V.), which shall act as a proxy - defined in Section 14 of version 3 of the license. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - USA. -*/ - - -#ifndef PRESENTATION_TAGPAGEMODEL_H -#define PRESENTATION_TAGPAGEMODEL_H - -#include "presentation/pagemodel.h" - -#include "domain/noterepository.h" -#include "domain/tagqueries.h" -#include "domain/tagrepository.h" -#include "domain/tag.h" - -namespace Presentation { - -class TagPageModel : public PageModel -{ - Q_OBJECT -public: - explicit TagPageModel(const Domain::Tag::Ptr &tag, - const Domain::TagQueries::Ptr &tagQueries, - const Domain::TagRepository::Ptr &tagRepository, - const Domain::NoteRepository::Ptr ¬eRepository, - QObject *parent = Q_NULLPTR); - - Domain::Tag::Ptr tag() const; - - Domain::Artifact::Ptr addItem(const QString &title, const QModelIndex &parentIndex = QModelIndex()) Q_DECL_OVERRIDE; - void removeItem(const QModelIndex &index) Q_DECL_OVERRIDE; - void promoteItem(const QModelIndex &index) Q_DECL_OVERRIDE; - -private: - QAbstractItemModel *createCentralListModel() Q_DECL_OVERRIDE; - - Domain::Tag::Ptr m_tag; - Domain::TagQueries::Ptr m_tagQueries; - Domain::TagRepository::Ptr m_tagRepository; - Domain::NoteRepository::Ptr m_noteRepository; -}; - -} - -#endif // PRESENTATION_TAGPAGEMODEL_H diff --git a/src/presentation/workdaypagemodel.cpp b/src/presentation/workdaypagemodel.cpp index 71ad3e60..75e84ab9 100644 --- a/src/presentation/workdaypagemodel.cpp +++ b/src/presentation/workdaypagemodel.cpp @@ -1,215 +1,214 @@ /* This file is part of Zanshin Copyright 2015 Theo Vaucher 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 "workdaypagemodel.h" #include #include -#include "domain/noterepository.h" #include "domain/taskqueries.h" #include "domain/taskrepository.h" #include "presentation/querytreemodel.h" #include "utils/datetime.h" using namespace Presentation; WorkdayPageModel::WorkdayPageModel(const Domain::TaskQueries::Ptr &taskQueries, const Domain::TaskRepository::Ptr &taskRepository, QObject *parent) : PageModel(parent), m_taskQueries(taskQueries), m_taskRepository(taskRepository) { } Domain::Artifact::Ptr WorkdayPageModel::addItem(const QString &title, const QModelIndex &parentIndex) { const auto parentData = parentIndex.data(QueryTreeModelBase::ObjectRole); const auto parentArtifact = parentData.value(); const auto parentTask = parentArtifact.objectCast(); auto task = Domain::Task::Ptr::create(); task->setTitle(title); if (!parentTask) task->setStartDate(Utils::DateTime::currentDate()); const auto job = parentTask ? m_taskRepository->createChild(task, parentTask) : m_taskRepository->create(task); installHandler(job, i18n("Cannot add task %1 in Workday", title)); return task; } void WorkdayPageModel::removeItem(const QModelIndex &index) { QVariant data = index.data(QueryTreeModelBase::ObjectRole); auto artifact = data.value(); auto task = artifact.objectCast(); if (task) { const auto job = m_taskRepository->remove(task); installHandler(job, i18n("Cannot remove task %1 from Workday", task->title())); } } void WorkdayPageModel::promoteItem(const QModelIndex &index) { QVariant data = index.data(QueryTreeModelBase::ObjectRole); auto artifact = data.value(); auto task = artifact.objectCast(); Q_ASSERT(task); const auto job = m_taskRepository->promoteToProject(task); installHandler(job, i18n("Cannot promote task %1 to be a project", task->title())); } QAbstractItemModel *WorkdayPageModel::createCentralListModel() { auto query = [this](const Domain::Artifact::Ptr &artifact) -> Domain::QueryResultInterface::Ptr { if (!artifact) return Domain::QueryResult::copy(m_taskQueries->findWorkdayTopLevel()); else if (auto task = artifact.dynamicCast()) return Domain::QueryResult::copy(m_taskQueries->findChildren(task)); else return Domain::QueryResult::Ptr(); }; auto flags = [](const Domain::Artifact::Ptr &artifact) { const Qt::ItemFlags defaultFlags = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsDragEnabled; return artifact.dynamicCast() ? (defaultFlags | Qt::ItemIsUserCheckable | Qt::ItemIsDropEnabled) : defaultFlags; }; using AdditionalInfo = Domain::QueryResult::Ptr; // later on we'll want a struct with the context query as well auto data = [](const Domain::Artifact::Ptr &artifact, int role, const AdditionalInfo &projectQueryResult) -> QVariant { switch (role) { case Qt::DisplayRole: case Qt::EditRole: return artifact->title(); case Qt::CheckStateRole: if (auto task = artifact.dynamicCast()) { return task->isDone() ? Qt::Checked : Qt::Unchecked; } break; case Presentation::QueryTreeModelBase::AdditionalInfoRole: if (projectQueryResult && !projectQueryResult->data().isEmpty()) { Domain::Project::Ptr project = projectQueryResult->data().at(0); return i18n("Project: %1", project->name()); } return i18n("Inbox"); // TODO add source name default: break; } return QVariant(); }; auto fetchAdditionalInfo = [this](const QModelIndex &index, const Domain::Artifact::Ptr &artifact) -> AdditionalInfo { if (index.parent().isValid()) // children are in the same collection as their parent, so the same project return nullptr; if (auto task = artifact.dynamicCast()) { AdditionalInfo projectQueryResult = m_taskQueries->findProject(task); if (projectQueryResult) { QPersistentModelIndex persistentIndex(index); projectQueryResult->addPostInsertHandler([persistentIndex](const Domain::Project::Ptr &, int) { // When a project was found (inserted into the result), update the rendering of the item auto model = const_cast(persistentIndex.model()); model->dataChanged(persistentIndex, persistentIndex); }); } return projectQueryResult; } return nullptr; }; auto setData = [this](const Domain::Artifact::Ptr &artifact, const QVariant &value, int role) { if (role != Qt::EditRole && role != Qt::CheckStateRole) { return false; } if (auto task = artifact.dynamicCast()) { const auto currentTitle = task->title(); if (role == Qt::EditRole) task->setTitle(value.toString()); else task->setDone(value.toInt() == Qt::Checked); const auto job = m_taskRepository->update(task); installHandler(job, i18n("Cannot modify task %1 in Workday", currentTitle)); return true; } return false; }; auto drop = [this](const QMimeData *mimeData, Qt::DropAction, const Domain::Artifact::Ptr &artifact) { auto parentTask = artifact.objectCast(); if (!mimeData->hasFormat(QStringLiteral("application/x-zanshin-object"))) return false; auto droppedArtifacts = mimeData->property("objects").value(); if (droppedArtifacts.isEmpty()) return false; if (std::any_of(droppedArtifacts.begin(), droppedArtifacts.end(), [](const Domain::Artifact::Ptr &droppedArtifact) { return !droppedArtifact.objectCast(); })) { return false; } foreach(const auto &droppedArtifact, droppedArtifacts) { auto childTask = droppedArtifact.objectCast(); if (parentTask) { const auto job = m_taskRepository->associate(parentTask, childTask); installHandler(job, i18n("Cannot move task %1 as sub-task of %2", childTask->title(), parentTask->title())); } else { childTask->setStartDate(Utils::DateTime::currentDate()); auto job = m_taskRepository->dissociate(childTask); installHandler(job, i18n("Cannot deparent task %1 from its parent", childTask->title())); } } return true; }; auto drag = [](const Domain::Artifact::List &artifacts) -> QMimeData* { if (artifacts.isEmpty()) return Q_NULLPTR; auto data = new QMimeData; data->setData(QStringLiteral("application/x-zanshin-object"), "object"); data->setProperty("objects", QVariant::fromValue(artifacts)); return data; }; return new QueryTreeModel(query, flags, data, setData, drop, drag, fetchAdditionalInfo, this); } diff --git a/src/renku/CMakeLists.txt b/src/renku/CMakeLists.txt deleted file mode 100644 index 131d6b8d..00000000 --- a/src/renku/CMakeLists.txt +++ /dev/null @@ -1,3 +0,0 @@ -add_subdirectory(app) -add_subdirectory(kontact) - diff --git a/src/renku/app/CMakeLists.txt b/src/renku/app/CMakeLists.txt deleted file mode 100644 index 766341db..00000000 --- a/src/renku/app/CMakeLists.txt +++ /dev/null @@ -1,22 +0,0 @@ -set(app_SRCS - aboutdata.cpp - dependencies.cpp - main.cpp -) -ecm_create_qm_loader(app_SRCS zanshin_qt) - -add_executable(renku ${app_SRCS}) -kde_enable_exceptions() -target_link_libraries(renku - akonadi - domain - presentation - scripting - utils - widgets -) - -install(TARGETS renku DESTINATION ${KDE_INSTALL_BINDIR}) -install(PROGRAMS org.kde.renku.desktop DESTINATION ${KDE_INSTALL_APPDIR}) -install(FILES renkuui.rc DESTINATION ${KDE_INSTALL_KXMLGUI5DIR}/renku) -install(FILES org.kde.renku.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR}) diff --git a/src/renku/app/aboutdata.cpp b/src/renku/app/aboutdata.cpp deleted file mode 100644 index 9900f997..00000000 --- a/src/renku/app/aboutdata.cpp +++ /dev/null @@ -1,51 +0,0 @@ -/* This file is part of Renku Notes - - Copyright 2011-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 "aboutdata.h" -#include "../../appversion.h" -#include -#include - -KAboutData App::getAboutData() -{ - KAboutData about(QStringLiteral("renku"), - i18n("Renku Notes"), QStringLiteral(APPLICATION_VERSION), - i18n("A note taking application which aims at getting your mind like water"), - KAboutLicense::GPL_V3, - i18n("Copyright 2008-2015, Kevin Ottens ")); - - about.addAuthor(i18n("Kevin Ottens"), - i18n("Lead Developer"), - QStringLiteral("ervin@kde.org")); - - about.addAuthor(i18n("Mario Bensi"), - i18n("Developer"), - QStringLiteral("nef@ipsquad.net")); - - about.addAuthor(i18n("Franck Arrecot"), - i18n("Developer"), - QStringLiteral("franck.arrecot@gmail.com")); - - return about; -} - diff --git a/src/renku/app/aboutdata.h b/src/renku/app/aboutdata.h deleted file mode 100644 index bab5b01f..00000000 --- a/src/renku/app/aboutdata.h +++ /dev/null @@ -1,35 +0,0 @@ -/* This file is part of Renku Notes - - Copyright 2011-2015 Kevin Ottens - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License or (at your option) version 3 or any later version - accepted by the membership of KDE e.V. (or its successor approved - by the membership of KDE e.V.), which shall act as a proxy - defined in Section 14 of version 3 of the license. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - USA. -*/ - -#ifndef APP_ABOUTDATA_H -#define APP_ABOUTDATA_H - -#include - -namespace App -{ - KAboutData getAboutData(); -} - -#endif - diff --git a/src/renku/app/dependencies.cpp b/src/renku/app/dependencies.cpp deleted file mode 100644 index a021cb69..00000000 --- a/src/renku/app/dependencies.cpp +++ /dev/null @@ -1,111 +0,0 @@ -/* This file is part of Renku Notes - - 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 "dependencies.h" - -#include "akonadi/akonadidatasourcequeries.h" -#include "akonadi/akonadidatasourcerepository.h" -#include "akonadi/akonadinotequeries.h" -#include "akonadi/akonadinoterepository.h" -#include "akonadi/akonaditagqueries.h" -#include "akonadi/akonaditagrepository.h" - -#include "akonadi/akonadicache.h" -#include "akonadi/akonadicachingstorage.h" -#include "akonadi/akonadimonitorimpl.h" -#include "akonadi/akonadiserializer.h" -#include "akonadi/akonadistorage.h" - -#include "presentation/artifacteditormodel.h" -#include "presentation/availablenotepagesmodel.h" -#include "presentation/availablesourcesmodel.h" - -#include "utils/dependencymanager.h" - -void App::initializeDependencies() -{ - auto &deps = Utils::DependencyManager::globalInstance(); - - deps.add(); - deps.add(); - deps.add(); - deps.add([] (Utils::DependencyManager *deps) { - return new Akonadi::CachingStorage(deps->create(), - Akonadi::StorageInterface::Ptr(new Akonadi::Storage)); - }); - - - deps.add([] (Utils::DependencyManager *deps) { - return new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Notes, - deps->create(), - deps->create(), - deps->create()); - }); - - deps.add([] (Utils::DependencyManager *deps) { - return new Akonadi::DataSourceRepository(Akonadi::StorageInterface::Notes, - deps->create(), - deps->create()); - }); - - deps.add(); - - deps.add(); - - deps.add(); - - deps.add(); - - deps.add([] (Utils::DependencyManager *deps) { - auto model = new Presentation::ArtifactEditorModel; - auto repository = deps->create(); - model->setSaveFunction([repository] (const Domain::Artifact::Ptr &artifact) { - auto note = artifact.objectCast(); - Q_ASSERT(note); - return repository->update(note); - }); - return model; - }); - - deps.add(); - - deps.add(); -} diff --git a/src/renku/app/dependencies.h b/src/renku/app/dependencies.h deleted file mode 100644 index 9d8f1394..00000000 --- a/src/renku/app/dependencies.h +++ /dev/null @@ -1,33 +0,0 @@ -/* This file is part of Renku Notes - - Copyright 2015 Kevin Ottens - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License or (at your option) version 3 or any later version - accepted by the membership of KDE e.V. (or its successor approved - by the membership of KDE e.V.), which shall act as a proxy - defined in Section 14 of version 3 of the license. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - USA. -*/ - -#ifndef APP_DEPENDENCIES_H -#define APP_DEPENDENCIES_H - -namespace App -{ - void initializeDependencies(); -} - -#endif - diff --git a/src/renku/app/main.cpp b/src/renku/app/main.cpp deleted file mode 100644 index e9e8d126..00000000 --- a/src/renku/app/main.cpp +++ /dev/null @@ -1,125 +0,0 @@ -/* This file is part of Renku Notes - - 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 -#include -#include -#include -#include - -#include "widgets/applicationcomponents.h" -#include "widgets/availablepagesview.h" -#include "widgets/availablesourcesview.h" -#include "widgets/editorview.h" -#include "widgets/pageview.h" - -#include "presentation/applicationmodel.h" - -#include "utils/dependencymanager.h" - -#include "aboutdata.h" -#include "dependencies.h" - -int main(int argc, char **argv) -{ - KLocalizedString::setApplicationDomain("zanshin"); - QApplication app(argc, argv); - App::initializeDependencies(); - - auto aboutData = App::getAboutData(); - QCommandLineParser parser; - KAboutData::setApplicationData(aboutData); - parser.addVersionOption(); - parser.addHelpOption(); - aboutData.setupCommandLine(&parser); - parser.process(app); - aboutData.processCommandLine(&parser); - - auto widget = new QWidget; - auto components = new Widgets::ApplicationComponents(widget); - components->setModel(Presentation::ApplicationModel::Ptr::create()); - - auto layout = new QVBoxLayout; - layout->setContentsMargins(0, 0, 0, 0); - layout->addWidget(components->pageView()); - widget->setLayout(layout); - - auto sourcesDock = new QDockWidget(i18n("Sources")); - sourcesDock->setObjectName(QStringLiteral("sourcesDock")); - sourcesDock->setWidget(components->availableSourcesView()); - - auto pagesDock = new QDockWidget(i18n("Pages")); - pagesDock->setObjectName(QStringLiteral("pagesDock")); - pagesDock->setWidget(components->availablePagesView()); - - auto editorDock = new QDockWidget(i18n("Editor")); - editorDock->setObjectName(QStringLiteral("editorDock")); - editorDock->setWidget(components->editorView()); - - auto window = new KXmlGuiWindow; - window->setCentralWidget(widget); - - window->addDockWidget(Qt::RightDockWidgetArea, editorDock); - window->addDockWidget(Qt::LeftDockWidgetArea, pagesDock); - window->addDockWidget(Qt::LeftDockWidgetArea, sourcesDock); - - auto actions = components->globalActions(); - actions.insert(QStringLiteral("dock_sources"), sourcesDock->toggleViewAction()); - actions.insert(QStringLiteral("dock_pages"), pagesDock->toggleViewAction()); - actions.insert(QStringLiteral("dock_editor"), editorDock->toggleViewAction()); - - auto ac = window->actionCollection(); - ac->addAction(KStandardAction::Quit, window, SLOT(close())); - for (auto it = actions.constBegin(); it != actions.constEnd(); ++it) { - auto shortcut = it.value()->shortcut(); - if (!shortcut.isEmpty()) { - ac->setDefaultShortcut(it.value(), shortcut); - } - ac->addAction(it.key(), it.value()); - } - - window->setupGUI(QSize(1024, 600), - KXmlGuiWindow::ToolBar - | KXmlGuiWindow::Keys - | KXmlGuiWindow::Save - | KXmlGuiWindow::Create); - - delete window->findChild("help_contents"); - delete window->findChild("help_whats_this"); - - window->show(); - - { - auto &deps = Utils::DependencyManager::globalInstance(); - auto repo = deps.create(); - repo->windowNeedsDataBackend(window); - } - - return app.exec(); -} diff --git a/src/renku/app/org.kde.renku.appdata.xml b/src/renku/app/org.kde.renku.appdata.xml deleted file mode 100644 index 6bbf44ab..00000000 --- a/src/renku/app/org.kde.renku.appdata.xml +++ /dev/null @@ -1,172 +0,0 @@ - - - org.kde.renku.desktop - CC0-1.0 - GPL-3.0+ - Renku - Renku - Renku - Renku - Renku - Renku - Renku - Renku - Renku - Renku - Renku - Renku - Renku - Renku - Renku - Renku - Renku - Renku - Renku - Renku - Renku - xxRenkuxx - Renku - Note Taking Application - Aplicació per prendre notes - Aplicació per prendre notes - Program til at tage noter - Programm zur Notizenerstellung - Note Taking Application - Aplicación para tomar notas - Muistiinpanosovellus - Application de prise de notes - Aplicativo para tomar notas - Aplikasi Pencatatan - Applicazione per prendere delle note - Toepassing voor notities - Aplikacja do robienia notatek - Aplicação de Apontamento de Notas - Aplicativo de notas - Aplikácia na tvorbu poznámok - Anteckningsprogram - Not Alma Uygulaması - Програма для роботи з нотатками - xxNote Taking Applicationxx - 笔记应用程序 - -

- Renku is an efficient and simple application for managing your notes. - It helps you organize the knowledge you accumulate and acts as a personal wiki. - You'll never forget anything anymore, getting your mind like water. -

-

El Renku és una aplicació eficaç i senzilla per a la gestió de les vostres notes. Ajuda a organitzar el coneixement que acumuleu i actua com un wiki personal. Mai oblidareu res més, fa que la vostra ment sigui com l'aigua.

-

El Renku és una aplicació eficaç i senzilla per a la gestió de les vostres notes. Ajuda a organitzar el coneixement que acumuleu i actua com un wiki personal. Mai oblidareu res més, fa que la vostra ment siga com l'aigua.

-

Renku er et effektivt og simpelt program til at håndtere dine noter. Den hjælper dig med at organisere den viden du indsamler og fungere som en personlig wiki. Du vil aldrig glemme noget længere, med tanker som var de vand.

-

Renku is an efficient and simple application for managing your notes. It helps you organize the knowledge you accumulate and acts as a personal wiki. You'll never forget anything anymore, getting your mind like water.

-

Renku es una aplicación sencilla y eficiente para la gestión de notas. Le ayuda a organizar el conocimiento que vaya acumulando y actúa como una wiki personal. Nunca volverá a olvidarse de nada, manteniendo sus pensamientos claros como el agua.

-

Renku on yksinkertainen ja tehokas sovellus muistiinpanojesi hallitsemiseen. Se auttaa sinua järjestämään keräämiäsi tietoja ja toimii henkilökohtaisena wikinä. Et koskaan enää unohda mitään: se toimii kuin vettä vain.

-

Renku est une application simple et efficace de gestion de vos notes. Elle vous aide à organiser les connaissances que vous accumulez, à la manière d'un wiki personnel. Vous n'oublierez plus jamais rien et votre réflexion n'aura jamais été aussi fluide.

-

Renku é un aplicativo eficiente e sinxelo para xestionar notas. Axúdao a organizar o coñecemento que acumula, e actúa como un wiki persoal. Non volverá esquecer nada nunca máis, convertendo a súa mente en auga.

-

Renku adalah aplikasi yang efisien dan sederhana untuk mengelola catatanmu. Ini membantu kamu mengatur pengetahuan yang kamu kumpulkan dan bertindak sebagai wiki pribadi. Kamu tidak akan pernah melupakan apa pun lagi, membuat pikiranmu seperti air yang mengalir.

-

Renku è un'applicazione semplice ed efficiente per la gestione delle tue note. Consente di organizzare la conoscenza che accumuli e agisce come un wiki personale. Non dimenticherai mai più niente, lasciando la tua mente fluida come l'acqua.

-

Renku is een efficiënte en eenvoudige toepassing voor het beheer van uw notities. Het helpt u de verzamelde kennis te organiseren en werkt als een persoonlijke wiki. U zult nooit meer iets vergeten en uw geheugen is geen zeef meer.

-

Renku jest wydajną i prostą aplikacją do zarządzania notatkami. Pomaga uporządkować zbieraną przez ciebie wiedzę i działa jak osobista wikipedia. Nigdy już nie zapomnisz o niczym, twoje myśli staną się jak woda.

-

O Renku é uma aplicação eficiente e simples para gerir as suas notas. Ajuda-o a organizar o conhecimento que acumula e actua como uma Wiki pessoal. Nunca mais se irá esquecer de nada, deixando a sua mente clara como água.

-

Renku é um aplicativo eficiente e simples para gerenciar suas notas. Ajuda-o a organizar o conhecimento acumulado e atua como uma Wiki pessoal. Nunca mais esqueça de nada, deixando sua mente clara como água.

-

Renku je efektívna a jednoduchá aplikácia na spravovanie vašich poznámok. Pomôže vám organizovať vedomosti, ktoré získate a správa sa ako osobné wiki. Už nikdy nič nezabudnete, čím sa vám prečistí myseľ.

-

Renku är ett effektivt och enkelt program för att hantera anteckningar. Det hjälper dig att organisera insamlad kunskap och fungerar som en personlig wiki. Du kommer aldrig att glömma någonting längre, och ditt sinne blir som flödande vatten.

-

Renku, notlarınızı yönetmek için etkili ve basit bir uygulamadır. Biriktirdiğiniz bilgileri düzenlemenize yardımcı olur ve kişisel bir wiki olarak hareket eder. Dikkatinizi toplayabileceksiniz ve artık bir şey unutmayacaksın.

-

Renku — ефективна і проста програма для керування вашими нотатками. Вона допомагає упорядкувати ваші значення і працює як особиста вікіпедія. Вам легше буде усе пам’ятати, ніщо не заважатиме вашим думкам.

-

xxRenku is an efficient and simple application for managing your notes. It helps you organize the knowledge you accumulate and acts as a personal wiki. You'll never forget anything anymore, getting your mind like water.xx

-

Features:

-

Característiques:

-

Característiques:

-

Vlastnosti:

-

Funktioner:

-

Funktionen:

-

Features:

-

Funciones:

-

Ominaisuuksia:

-

Fonctionnalités :

-

Funcionalidades:

-

Fitur:

-

Funzionalità:

-

Mogelijkheden:

-

Możliwości:

-

Funcionalidades:

-

Funcionalidades:

-

Funkcie:

-

Funktioner:

-

Özellikler:

-

Можливості:

-

xxFeatures:xx

-

功能:

-
    -
  • Collects notes
  • -
  • Recull les notes
  • -
  • Recull les notes
  • -
  • Indsamler noter
  • -
  • Notizen sammeln
  • -
  • Collects notes
  • -
  • Recolección de notas
  • -
  • Kokoaa muistiinpanot
  • -
  • Collection de notes
  • -
  • Recoller notas.
  • -
  • Mengumpulkan catatan
  • -
  • Raccoglie note
  • -
  • Verzamelt notities
  • -
  • Zbieranie notatek
  • -
  • Recolhe notas
  • -
  • Coleta notas
  • -
  • Zbiera poznámky
  • -
  • Samlar anteckningar
  • -
  • Notları toplar
  • -
  • Збирання нотаток.
  • -
  • xxCollects notesxx
  • -
  • 收集便笺
  • -
  • Organize notes in topics
  • -
  • Organitza les notes en temes
  • -
  • Organitza les notes en temes
  • -
  • Organiser noter i emner
  • -
  • Ordnen von Notizen nach Themen
  • -
  • Organise notes in topics
  • -
  • Organización de las notas por temas
  • -
  • Järjestää muistiinpanot aiheiksi
  • -
  • Organisation de notes au sein de sujets
  • -
  • Organizar notas por temas.
  • -
  • Mengatur catatan dalam topik
  • -
  • Organizza le note in argomenti
  • -
  • Organiseert notities in onderwerpen
  • -
  • Porządkowanie notatek w tematy
  • -
  • Organiza as notas por tópicos
  • -
  • Organiza as notas em tópicos
  • -
  • Organizuje poznámky v témach
  • -
  • Organisera anteckningar enligt ämne
  • -
  • Notları, başlıklar şeklinde düzenler
  • -
  • Упорядковування нотаток за темами.
  • -
  • xxOrganize notes in topicsxx
  • -
  • 按主题组织便笺
  • -
  • Akonadi-enabled application allowing to get the data from virtually anywhere (local, imap, web service...)
  • -
  • Habilitant les capacitats de l'Akonadi es permet obtenir les dades des de pràcticament qualsevol lloc (local, IMAP, servei web...)
  • -
  • Habilitant les capacitats de l'Akonadi es permet obtindre les dades des de pràcticament qualsevol lloc (local, IMAP, servei web...)
  • -
  • Akonadi-aktiveret program som giver mulighed for at hente data fra næsten hvor som helst (lokalt, imap, webtjeneste...)
  • -
  • Akonadi-enabled application allowing to get the data from virtually anywhere (local, imap, web service...)
  • -
  • Aplicación que usa Akonadi para permitirle obtener datos de cualquier lugar (local, imap, servicio web...)
  • -
  • Akonadiin perustuva sovellus sallii etsiä tietoa käytännössä mistä vain (kiintolevyltä, IMAPista, verkkopalveluista…)
  • -
  • Prise en charge d'Akonadi dans l'application afin de recevoir les données quelle que soit leur source (local, imap, service web, ...)
  • -
  • O aplicativo é compatíbel con Akonadi, e permítelle obter datos de practicamente calquera lugar (local, IMAP, servizos web).
  • -
  • Aplikasi akonadi difungsikan memungkinkan untuk mendapatkan data dari mana pun secara virtual (layanan local, imap, web...)
  • -
  • Applicazione basata su Akonadi che consente di ottenere i dati praticamente ovunque (locale, imap, servizio web...)
  • -
  • In Akonadi opgenomen toepassing waarmee u de gegevens werkelijk overal vandaan kunt halen (lokaal, imap, webservice...)
  • -
  • Program wykorzystuje Akonadi, więc można pobierać dane praktycznie z każdego miejsca (lokalnie, imap, usługa sieciowa...)
  • -
  • Aplicação com suporte para o Akonadi, permitindo-lhe recolher os dados de praticamente todo o lado (local, IMAP, serviço Web...)
  • -
  • Aplicativo com suporte para o Akonadi, permitindo-lhe coletar os dados de praticamente tudo (local, IMAP, serviço Web...)
  • -
  • Aplikácia používajúca Akonadi umožňujúca získanie údajov zdanlivo odvšadiaľ (miestne, imap, webové služby...)
  • -
  • Program med Akonadi-stöd som gör det möjligt att hämta data från i stort sett var som helst (lokalt, IMAP, webbtjänster ...)
  • -
  • Akonadi-etkin uygulama, sanal bir yerden (yerel, imap, web servisi...) veriyi almanıza imkan sağlar
  • -
  • Інтеграція з Akonadi, що надає програмі змогу отримувати дані практично з будь-яких джерел (локальна мережа, imap, інтернет-служба...)
  • -
  • xxAkonadi-enabled application allowing to get the data from virtually anywhere (local, imap, web service...)xx
  • -
-
- http://zanshin.kde.org - https://bugs.kde.org/enter_bug.cgi?format=guided&product=zanshin - KDE - - renku - -
diff --git a/src/renku/app/org.kde.renku.desktop b/src/renku/app/org.kde.renku.desktop deleted file mode 100755 index 93c5faec..00000000 --- a/src/renku/app/org.kde.renku.desktop +++ /dev/null @@ -1,48 +0,0 @@ -[Desktop Entry] -Type=Application -Categories=Qt;KDE;Utility;X-KDE-Utilities-PIM; -Exec=renku -Name=Renku -Name[ca]=Renku -Name[ca@valencia]=Renku -Name[da]=Renku -Name[de]=Renku -Name[en_GB]=Renku -Name[es]=Renku -Name[fi]=Renku -Name[fr]=Renku -Name[gl]=Renku -Name[it]=Renku -Name[nl]=Renku -Name[pl]=Renku -Name[pt]=Renku -Name[pt_BR]=Renku -Name[sk]=Renku -Name[sv]=Renku -Name[tr]=Renku -Name[uk]=Renku -Name[x-test]=xxRenkuxx -Name[zh_CN]=Renku -Icon=zanshin -GenericName=Note Taking Application -GenericName[ca]=Aplicació per prendre notes -GenericName[ca@valencia]=Aplicació per prendre notes -GenericName[da]=Program til at tage noter -GenericName[de]=Programm zur Notizenerstellung -GenericName[en_GB]=Note Taking Application -GenericName[es]=Aplicación para tomar notas -GenericName[fi]=Muistiinpanosovellus -GenericName[fr]=Application de prise de notes -GenericName[gl]=Aplicativo para tomar notas -GenericName[it]=Applicazione per prendere delle note -GenericName[nl]=Toepassing voor notities -GenericName[pl]=Aplikacja do robienia notatek -GenericName[pt]=Aplicação de Apontamentos de Notas -GenericName[pt_BR]=Aplicativo de notas -GenericName[sk]=Aplikácia na tvorbu poznámok -GenericName[sv]=Anteckningsprogram -GenericName[tr]=Not Alma Uygulaması -GenericName[uk]=Програма для роботи з нотатками -GenericName[x-test]=xxNote Taking Applicationxx -GenericName[zh_CN]=笔记应用程序 -X-DBUS-ServiceName=org.kde.renku diff --git a/src/renku/app/renkuui.rc b/src/renku/app/renkuui.rc deleted file mode 100644 index d3b89776..00000000 --- a/src/renku/app/renkuui.rc +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - &Go - - - - - - - - - - &Panels - - - - - - - - Main Toolbar - - - - - - - - diff --git a/src/renku/kontact/CMakeLists.txt b/src/renku/kontact/CMakeLists.txt deleted file mode 100644 index 1535b653..00000000 --- a/src/renku/kontact/CMakeLists.txt +++ /dev/null @@ -1,34 +0,0 @@ -set(part_SRCS - ../app/aboutdata.cpp - ../app/dependencies.cpp - part.cpp -) - -add_library(renku_part MODULE ${part_SRCS}) -kde_enable_exceptions() -target_link_libraries(renku_part - KF5::Parts - akonadi - domain - presentation - scripting - utils - widgets -) - -install(TARGETS renku_part DESTINATION ${KDE_INSTALL_PLUGINDIR}) -install(FILES renku_part.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}) - -########################## - -set(kontact_SRCS - kontact_plugin.cpp -) - -add_library(kontact_renkuplugin MODULE ${kontact_SRCS}) -target_link_libraries(kontact_renkuplugin KF5::Parts KF5::KontactInterface) - -install(TARGETS kontact_renkuplugin DESTINATION ${KDE_INSTALL_PLUGINDIR}) -install(FILES renku_plugin.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR}/kontact) -install(FILES renku_part.rc DESTINATION ${KDE_INSTALL_KXMLGUI5DIR}/renku) - diff --git a/src/renku/kontact/kontact_plugin.cpp b/src/renku/kontact/kontact_plugin.cpp deleted file mode 100644 index 985fe933..00000000 --- a/src/renku/kontact/kontact_plugin.cpp +++ /dev/null @@ -1,40 +0,0 @@ -/* This file is part of Renku Notes - - 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 "kontact_plugin.h" - -#include - -EXPORT_KONTACT_PLUGIN(Plugin, renku) - -Plugin::Plugin(KontactInterface::Core *core, const QVariantList&) - : KontactInterface::Plugin(core, core, "renku") -{ - setComponentName(QStringLiteral("renku"), QStringLiteral("renku")); -} - -KParts::ReadOnlyPart *Plugin::createPart() -{ - return loadPart(); -} -#include "kontact_plugin.moc" diff --git a/src/renku/kontact/kontact_plugin.h b/src/renku/kontact/kontact_plugin.h deleted file mode 100644 index 95cace03..00000000 --- a/src/renku/kontact/kontact_plugin.h +++ /dev/null @@ -1,43 +0,0 @@ -/* This file is part of Renku Notes - - Copyright 2015 Kevin Ottens - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License or (at your option) version 3 or any later version - accepted by the membership of KDE e.V. (or its successor approved - by the membership of KDE e.V.), which shall act as a proxy - defined in Section 14 of version 3 of the license. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - USA. -*/ - -#ifndef RENKU_KONTACT_PLUGIN_H -#define RENKU_KONTACT_PLUGIN_H - -#include - -class Plugin : public KontactInterface::Plugin -{ - Q_OBJECT - -public: - Plugin(KontactInterface::Core *core, const QVariantList &); - - int weight() const Q_DECL_OVERRIDE { return 449; } - -protected: - KParts::ReadOnlyPart *createPart() Q_DECL_OVERRIDE; -}; - -#endif - diff --git a/src/renku/kontact/part.cpp b/src/renku/kontact/part.cpp deleted file mode 100644 index 5b7fe913..00000000 --- a/src/renku/kontact/part.cpp +++ /dev/null @@ -1,95 +0,0 @@ -/* This file is part of Renku Notes - - 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 "part.h" - -#include -#include - -#include -#include -#include -#include - -#include "../app/aboutdata.h" -#include "../app/dependencies.h" - -#include "presentation/applicationmodel.h" - -#include "widgets/applicationcomponents.h" -#include "widgets/availablepagesview.h" -#include "widgets/availablesourcesview.h" -#include "widgets/editorview.h" -#include "widgets/pageview.h" - -#include "utils/dependencymanager.h" - -K_PLUGIN_FACTORY(PartFactory, registerPlugin();) - -Part::Part(QWidget *parentWidget, QObject *parent, const QVariantList &) - : KParts::ReadOnlyPart(parent) -{ - App::initializeDependencies(); - - setComponentName(QStringLiteral("renku"), QStringLiteral("renku")); - - auto splitter = new QSplitter(parentWidget); - auto sidebar = new QSplitter(Qt::Vertical, parentWidget); - - auto components = new Widgets::ApplicationComponents(parentWidget); - components->setModel(Presentation::ApplicationModel::Ptr::create()); - - sidebar->addWidget(components->availablePagesView()); - sidebar->addWidget(components->availableSourcesView()); - - splitter->addWidget(sidebar); - splitter->addWidget(components->pageView()); - splitter->addWidget(components->editorView()); - setWidget(splitter); - - auto actions = QHash(); - actions.unite(components->availableSourcesView()->globalActions()); - actions.unite(components->availablePagesView()->globalActions()); - actions.unite(components->pageView()->globalActions()); - - auto ac = actionCollection(); - for (auto it = actions.constBegin(); it != actions.constEnd(); ++it) { - auto shortcut = it.value()->shortcut(); - if (!shortcut.isEmpty()) { - ac->setDefaultShortcut(it.value(), shortcut); - } - ac->addAction(it.key(), it.value()); - } - - setXMLFile(QStringLiteral("renku_part.rc"), true); -} - -Part::~Part() -{ -} - -bool Part::openFile() -{ - return false; -} -#include "part.moc" diff --git a/src/renku/kontact/part.h b/src/renku/kontact/part.h deleted file mode 100644 index 14f2457f..00000000 --- a/src/renku/kontact/part.h +++ /dev/null @@ -1,43 +0,0 @@ -/* This file is part of Renku Notes - - Copyright 2015 Kevin Ottens - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License or (at your option) version 3 or any later version - accepted by the membership of KDE e.V. (or its successor approved - by the membership of KDE e.V.), which shall act as a proxy - defined in Section 14 of version 3 of the license. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - USA. -*/ - -#ifndef RENKU_PART_H -#define RENKU_PART_H - -#include -#include - -class Part : public KParts::ReadOnlyPart -{ - Q_OBJECT - -public: - Part(QWidget *parentWidget, QObject *parent, const QVariantList &); - ~Part(); - -protected: - virtual bool openFile() Q_DECL_OVERRIDE; -}; - -#endif - diff --git a/src/renku/kontact/renku_part.desktop b/src/renku/kontact/renku_part.desktop deleted file mode 100644 index 96f56bf2..00000000 --- a/src/renku/kontact/renku_part.desktop +++ /dev/null @@ -1,27 +0,0 @@ -[Desktop Entry] -Name=Renku Part -Name[ca]=Part del Renku -Name[ca@valencia]=Part del Renku -Name[da]=Renku Part -Name[de]=Renku Part -Name[en_GB]=Renku Part -Name[es]=Parte Renku -Name[fi]=Renku-osa -Name[fr]=Composant Renku -Name[gl]=Parte de Renku -Name[it]=Componente di Renku -Name[nl]=Gedeelte voor Renku -Name[pl]=Moduł Renku -Name[pt]=Componente do Renku -Name[pt_BR]=Componente do Renku -Name[sk]=Časť Renku -Name[sv]=Renku-delprogram -Name[tr]=Renku Kısmı -Name[uk]=Модуль Renku -Name[x-test]=xxRenku Partxx -Name[zh_CN]=Renku 组件 -X-KDE-ServiceTypes=KParts/ReadOnlyPart -X-KDE-Library=renku_part -Type=Service -Icon=knotes - diff --git a/src/renku/kontact/renku_part.rc b/src/renku/kontact/renku_part.rc deleted file mode 100644 index ee88aea8..00000000 --- a/src/renku/kontact/renku_part.rc +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - &Go - - - - - - - - - - Main Toolbar - - - - - - - - diff --git a/src/renku/kontact/renku_plugin.desktop b/src/renku/kontact/renku_plugin.desktop deleted file mode 100644 index 98a4c490..00000000 --- a/src/renku/kontact/renku_plugin.desktop +++ /dev/null @@ -1,61 +0,0 @@ -[Desktop Entry] -Type=Service -Icon=knotes -X-KDE-ServiceTypes=Kontact/Plugin,KPluginInfo - -X-KDE-Library=kontact_renkuplugin -X-KDE-KontactPluginVersion=10 -X-KDE-KontactPartLibraryName=renku_part -X-KDE-KontactPartExecutableName=renku -X-KDE-KontactPartLoadOnStart=false -X-KDE-KontactPluginHasSummary=false - -X-KDE-PluginInfo-Name=kontact_renkuplugin -X-KDE-PluginInfo-Version=0.1 -X-KDE-PluginInfo-License=GPL -X-KDE-PluginInfo-EnabledByDefault=true -X-KDE-PluginInfo-AllowEmptySettings=true - -Comment=Kontact Renku Plugin -Comment[ca]=Connector del Renku pel Kontact -Comment[ca@valencia]=Connector del Renku pel Kontact -Comment[da]=Renku-plugin til Kontact -Comment[de]=Renku-Modul für Kontakt -Comment[en_GB]=Kontact Renku Plugin -Comment[es]=Complemento Renku para Kontact -Comment[fi]=Kontactin Renku-osa -Comment[fr]=Module externe Renku pour Kontact -Comment[gl]=Complemento de Renku para Kontact -Comment[it]=Estensione per Kontact di Renku -Comment[nl]=Kontact-plugin voor Renku -Comment[pl]=Wtyczka kontaktów Renku -Comment[pt]='Plugin' do Renku para o Kontact -Comment[pt_BR]=Plugin do Renku para o Kontact -Comment[sk]=Plugin Renku pre Kontact -Comment[sv]=Renku-insticksprogram för Kontact -Comment[tr]=Kontact Renku Eklentisi -Comment[uk]=Додаток Renku до Kontact -Comment[x-test]=xxKontact Renku Pluginxx -Comment[zh_CN]=Kontact Renku 插件 -Name=Notes -Name[ca]=Notes -Name[ca@valencia]=Notes -Name[cs]=Poznámky -Name[da]=Noter -Name[de]=Notizen -Name[en_GB]=Notes -Name[es]=Notas -Name[fi]=Muistiinpanot -Name[fr]=Notes -Name[gl]=Notas -Name[it]=Note -Name[nl]=Notities -Name[pl]=Notatki -Name[pt]=Notas -Name[pt_BR]=Notas -Name[sk]=Poznámky -Name[sv]=Anteckningar -Name[tr]=Notlar -Name[uk]=Нотатки -Name[x-test]=xxNotesxx -Name[zh_CN]=注记 diff --git a/src/widgets/availablepagesview.cpp b/src/widgets/availablepagesview.cpp index e1456cff..622bde65 100644 --- a/src/widgets/availablepagesview.cpp +++ b/src/widgets/availablepagesview.cpp @@ -1,364 +1,359 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "availablepagesview.h" #include #include #include #include #include #include #include #include #include "presentation/metatypes.h" #include "presentation/querytreemodelbase.h" #include "widgets/messagebox.h" #include "widgets/newprojectdialog.h" #include "widgets/quickselectdialog.h" #include "domain/project.h" #include "domain/context.h" -#include "domain/tag.h" using namespace Widgets; using namespace Presentation; AvailablePagesView::AvailablePagesView(QWidget *parent) : QWidget(parent), m_addProjectAction(new QAction(this)), m_addContextAction(new QAction(this)), m_addTagAction(new QAction(this)), m_removeAction(new QAction(this)), m_model(Q_NULLPTR), m_sources(Q_NULLPTR), m_pagesView(new QTreeView(this)) { m_pagesView->setObjectName(QStringLiteral("pagesView")); m_pagesView->header()->hide(); m_pagesView->setDragDropMode(QTreeView::DropOnly); auto actionBar = new QToolBar(this); actionBar->setObjectName(QStringLiteral("actionBar")); actionBar->setIconSize(QSize(16, 16)); m_addProjectAction->setObjectName(QStringLiteral("addProjectAction")); m_addProjectAction->setText(i18n("New Project")); m_addProjectAction->setIcon(QIcon::fromTheme(QStringLiteral("view-pim-tasks"))); connect(m_addProjectAction, &QAction::triggered, this, &AvailablePagesView::onAddProjectTriggered); actionBar->addAction(m_addProjectAction); m_addContextAction->setObjectName(QStringLiteral("addContextAction")); m_addContextAction->setText(i18n("New Context")); m_addContextAction->setIcon(QIcon::fromTheme(QStringLiteral("view-pim-notes"))); connect(m_addContextAction, &QAction::triggered, this, &AvailablePagesView::onAddContextTriggered); actionBar->addAction(m_addContextAction); m_addTagAction->setObjectName(QStringLiteral("addTagAction")); m_addTagAction->setText(i18n("New Tag")); m_addTagAction->setIcon(QIcon::fromTheme(QStringLiteral("view-pim-tasks"))); connect(m_addTagAction, &QAction::triggered, this, &AvailablePagesView::onAddTagTriggered); actionBar->addAction(m_addTagAction); m_removeAction->setObjectName(QStringLiteral("removeAction")); m_removeAction->setText(i18n("Remove Page")); m_removeAction->setIcon(QIcon::fromTheme(QStringLiteral("list-remove"))); connect(m_removeAction, &QAction::triggered, this, &AvailablePagesView::onRemoveTriggered); actionBar->addAction(m_removeAction); auto actionBarLayout = new QHBoxLayout; actionBarLayout->setContentsMargins(0, 0, 0, 0); actionBarLayout->setAlignment(Qt::AlignRight); actionBarLayout->addWidget(actionBar); auto layout = new QVBoxLayout; layout->addWidget(m_pagesView); layout->addLayout(actionBarLayout); setLayout(layout); auto margins = layout->contentsMargins(); margins.setBottom(0); layout->setContentsMargins(margins); m_projectDialogFactory = [] (QWidget *parent) { return NewProjectDialogPtr(new NewProjectDialog(parent)); }; m_quickSelectDialogFactory = [] (QWidget *parent) { return QuickSelectDialogPtr(new QuickSelectDialog(parent)); }; m_messageBoxInterface = MessageBox::Ptr::create(); auto goPreviousAction = new QAction(this); goPreviousAction->setObjectName(QStringLiteral("goPreviousAction")); goPreviousAction->setText(i18n("Previous Page")); goPreviousAction->setIcon(QIcon::fromTheme(QStringLiteral("go-up"))); goPreviousAction->setShortcut(Qt::ALT | Qt::Key_Up); connect(goPreviousAction, &QAction::triggered, this, &AvailablePagesView::onGoPreviousTriggered); auto goNextAction = new QAction(this); goNextAction->setObjectName(QStringLiteral("goNextAction")); goNextAction->setText(i18n("Next Page")); goNextAction->setIcon(QIcon::fromTheme(QStringLiteral("go-down"))); goNextAction->setShortcut(Qt::ALT | Qt::Key_Down); connect(goNextAction, &QAction::triggered, this, &AvailablePagesView::onGoNextTriggered); auto goToAction = new QAction(this); goToAction->setObjectName(QStringLiteral("goToAction")); goToAction->setText(i18n("Go to Page...")); goToAction->setShortcut(Qt::Key_J); connect(goToAction, &QAction::triggered, this, &AvailablePagesView::onGoToTriggered); m_actions.insert(QStringLiteral("pages_project_add"), m_addProjectAction); m_actions.insert(QStringLiteral("pages_context_add"), m_addContextAction); m_actions.insert(QStringLiteral("pages_tag_add"), m_addTagAction); m_actions.insert(QStringLiteral("pages_remove"), m_removeAction); m_actions.insert(QStringLiteral("pages_go_previous"), goPreviousAction); m_actions.insert(QStringLiteral("pages_go_next"), goNextAction); m_actions.insert(QStringLiteral("pages_go_to"), goToAction); } QHash AvailablePagesView::globalActions() const { return m_actions; } QObject *AvailablePagesView::model() const { return m_model; } QAbstractItemModel *AvailablePagesView::projectSourcesModel() const { return m_sources; } Domain::DataSource::Ptr AvailablePagesView::defaultProjectSource() const { return m_defaultSource; } AvailablePagesView::ProjectDialogFactory AvailablePagesView::projectDialogFactory() const { return m_projectDialogFactory; } AvailablePagesView::QuickSelectDialogFactory AvailablePagesView::quickSelectDialogFactory() const { return m_quickSelectDialogFactory; } void AvailablePagesView::setModel(QObject *model) { if (model == m_model) return; if (m_pagesView->selectionModel()) { disconnect(m_pagesView->selectionModel(), Q_NULLPTR, this, Q_NULLPTR); } if (m_pagesView->model()) { disconnect(m_pagesView->model(), &QAbstractItemModel::rowsInserted, m_pagesView, &QTreeView::expand); disconnect(m_pagesView->model(), &QAbstractItemModel::layoutChanged, m_pagesView, &QTreeView::expandAll); disconnect(m_pagesView->model(), &QAbstractItemModel::modelReset, m_pagesView, &QTreeView::expandAll); } m_pagesView->setModel(Q_NULLPTR); m_model = model; setEnabled(m_model); if (!m_model) return; m_addProjectAction->setVisible(m_model->property("hasProjectPages").toBool()); m_addContextAction->setVisible(m_model->property("hasContextPages").toBool()); m_addTagAction->setVisible(m_model->property("hasTagPages").toBool()); QVariant modelProperty = m_model->property("pageListModel"); if (modelProperty.canConvert()) { m_pagesView->setModel(modelProperty.value()); connect(m_pagesView->model(), &QAbstractItemModel::rowsInserted, m_pagesView, &QTreeView::expand); connect(m_pagesView->model(), &QAbstractItemModel::layoutChanged, m_pagesView, &QTreeView::expandAll); connect(m_pagesView->model(), &QAbstractItemModel::modelReset, m_pagesView, &QTreeView::expandAll); } connect(m_pagesView->selectionModel(), &QItemSelectionModel::currentChanged, this, &AvailablePagesView::onCurrentChanged); QMetaObject::invokeMethod(this, "onInitTimeout", Qt::QueuedConnection); } void AvailablePagesView::setProjectSourcesModel(QAbstractItemModel *sources) { m_sources = sources; } void AvailablePagesView::setDefaultProjectSource(const Domain::DataSource::Ptr &source) { m_defaultSource = source; } void AvailablePagesView::setProjectDialogFactory(const AvailablePagesView::ProjectDialogFactory &factory) { m_projectDialogFactory = factory; } void AvailablePagesView::setQuickSelectDialogFactory(const AvailablePagesView::QuickSelectDialogFactory &factory) { m_quickSelectDialogFactory = factory; } void AvailablePagesView::setMessageBoxInterface(const MessageBoxInterface::Ptr &interface) { m_messageBoxInterface = interface; } void AvailablePagesView::onCurrentChanged(const QModelIndex ¤t) { QObject *page = Q_NULLPTR; QMetaObject::invokeMethod(m_model, "createPageForIndex", Q_RETURN_ARG(QObject*, page), Q_ARG(QModelIndex, current)); emit currentPageChanged(page); const auto object = current.data(QueryTreeModelBase::ObjectRole).value(); m_removeAction->setEnabled(object.objectCast() - || object.objectCast() - || object.objectCast()); + || object.objectCast()); } void AvailablePagesView::onAddProjectTriggered() { NewProjectDialogInterface::Ptr dialog = m_projectDialogFactory(this); dialog->setDataSourcesModel(m_sources); if (dialog->exec() == QDialog::Accepted) { m_defaultSource = dialog->dataSource(); QMetaObject::invokeMethod(m_model, "addProject", Q_ARG(QString, dialog->name()), Q_ARG(Domain::DataSource::Ptr, dialog->dataSource())); } } void AvailablePagesView::onAddContextTriggered() { const QString name = m_messageBoxInterface->askTextInput(this, i18n("Add Context"), i18n("Context name")); if (!name.isEmpty()) { QMetaObject::invokeMethod(m_model, "addContext", Q_ARG(QString, name)); } } void AvailablePagesView::onAddTagTriggered() { const QString name = m_messageBoxInterface->askTextInput(this, i18n("Add Tag"), i18n("Tag name")); if (!name.isEmpty()) { QMetaObject::invokeMethod(m_model, "addTag", Q_ARG(QString, name)); } } void AvailablePagesView::onRemoveTriggered() { const QModelIndex current = m_pagesView->currentIndex(); if (!current.isValid()) return; QString title; QString text; QObjectPtr object = current.data(QueryTreeModelBase::ObjectRole).value(); if (!object) { qDebug() << "Model doesn't have ObjectRole for" << current; return; } if (Domain::Project::Ptr project = object.objectCast()) { title = i18n("Delete Project"); text = i18n("Do you really want to delete the project '%1', with all its actions?", project->name()); } else if (Domain::Context::Ptr context = object.objectCast()) { title = i18n("Delete Context"); text = i18n("Do you really want to delete the context '%1'?", context->name()); - } else if (Domain::Tag::Ptr tag = object.objectCast()) { - title = i18n("Delete Tag"); - text = i18n("Do you really want to delete the tag '%1'?", tag->name()); } else { qFatal("Unrecognized object type"); return; } QMessageBox::Button button = m_messageBoxInterface->askConfirmation(this, title, text); if (button != QMessageBox::Yes) { return; } QMetaObject::invokeMethod(m_model, "removeItem", Q_ARG(QModelIndex, current)); } void AvailablePagesView::onGoPreviousTriggered() { auto index = m_pagesView->indexAbove(m_pagesView->currentIndex()); while (index.isValid() && !(index.flags() & Qt::ItemIsSelectable)) { index = m_pagesView->indexAbove(index); } if (index.isValid()) m_pagesView->setCurrentIndex(index); } void AvailablePagesView::onGoNextTriggered() { auto index = m_pagesView->indexBelow(m_pagesView->currentIndex()); while (index.isValid() && !(index.flags() & Qt::ItemIsSelectable)) { index = m_pagesView->indexBelow(index); } if (index.isValid()) m_pagesView->setCurrentIndex(index); } void AvailablePagesView::onGoToTriggered() { QuickSelectDialogInterface::Ptr dialog = m_quickSelectDialogFactory(this); dialog->setModel(m_pagesView->model()); if (dialog->exec() == QDialog::Accepted && dialog->selectedIndex().isValid()) { m_pagesView->setCurrentIndex(dialog->selectedIndex()); } } void AvailablePagesView::onInitTimeout() { if (m_pagesView->model()) { m_pagesView->setCurrentIndex(m_pagesView->model()->index(0, 0)); m_pagesView->expandAll(); } } diff --git a/src/widgets/itemdelegate.cpp b/src/widgets/itemdelegate.cpp index ee1b39d4..65887960 100644 --- a/src/widgets/itemdelegate.cpp +++ b/src/widgets/itemdelegate.cpp @@ -1,171 +1,169 @@ /* This file is part of Zanshin Copyright 2014-2016 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 "itemdelegate.h" #include #include #include #include -#include "domain/note.h" #include "domain/task.h" #include "presentation/pagemodel.h" #include "presentation/querytreemodelbase.h" #include "utils/datetime.h" using namespace Widgets; ItemDelegate::ItemDelegate(QObject *parent) : QStyledItemDelegate(parent) { } QSize ItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { QStyleOptionViewItem opt = option; initStyleOption(&opt, index); // Make sure they all get the height needed for a check indicator opt.features = QStyleOptionViewItem::HasCheckIndicator; // and for a date on the right opt.text += ' ' + QLocale().dateFormat(QLocale::ShortFormat).toUpper() + ' '; QSize sz = QStyledItemDelegate::sizeHint(opt, index); const auto additionalInfo = index.data(Presentation::QueryTreeModelBase::AdditionalInfoRole).toString(); if (!additionalInfo.isEmpty()) sz.rheight() += opt.fontMetrics.height(); return sz; } void ItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { const auto data = index.data(Presentation::QueryTreeModelBase::ObjectRole); auto task = Domain::Task::Ptr(); auto artifact = data.value(); if (artifact) { task = artifact.dynamicCast(); } else { task = data.value(); - auto note = data.value(); artifact = task ? task.staticCast() - : note.staticCast(); + : Domain::Artifact::Ptr(); } auto opt = QStyleOptionViewItem(option); initStyleOption(&opt, index); const auto widget = opt.widget; const auto style = widget ? widget->style() : QApplication::style(); const auto isDone = task ? task->isDone() : false; const auto isEnabled = (opt.state & QStyle::State_Enabled); const auto isActive = (opt.state & QStyle::State_Active); const auto isSelected = (opt.state & QStyle::State_Selected); const auto isEditing = (opt.state & QStyle::State_Editing); const auto startDate = task ? task->startDate() : QDate(); const auto dueDate = task ? task->dueDate() : QDate(); const auto additionalInfo = index.data(Presentation::QueryTreeModelBase::AdditionalInfoRole).toString(); const auto currentDate = Utils::DateTime::currentDate(); const auto onStartDate = startDate.isValid() && startDate <= currentDate; const auto pastDueDate = dueDate.isValid() && dueDate < currentDate; const auto onDueDate = dueDate.isValid() && dueDate == currentDate; const auto baseFont = opt.font; const auto summaryFont = [=] { auto font = baseFont; font.setStrikeOut(isDone); font.setBold(!isDone && (onStartDate || onDueDate || pastDueDate)); return font; }(); const auto summaryMetrics = QFontMetrics(summaryFont); const auto colorGroup = (isEnabled && !isActive) ? QPalette::Inactive : isEnabled ? QPalette::Normal : QPalette::Disabled; const auto colorRole = (isSelected && !isEditing) ? QPalette::HighlightedText : QPalette::Text; const auto baseColor = opt.palette.color(colorGroup, colorRole); const auto summaryColor = isDone ? baseColor : pastDueDate ? QColor(Qt::red) : onDueDate ? QColor("orange") : baseColor; const auto summaryText = opt.text; const auto dueDateText = dueDate.isValid() ? QLocale().toString(dueDate, QLocale::ShortFormat) : QString(); const auto textMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin, nullptr, widget) + 1; const auto dueDateWidth = dueDate.isValid() ? (summaryMetrics.width(dueDateText) + 2 * textMargin) : 0; const auto checkRect = style->subElementRect(QStyle::SE_ItemViewItemCheckIndicator, &opt, widget); const auto textRect = style->subElementRect(QStyle::SE_ItemViewItemText, &opt, widget) .adjusted(textMargin, 0, - textMargin, 0); auto summaryRect = textRect.adjusted(0, 0, -dueDateWidth, 0); if (!additionalInfo.isEmpty()) summaryRect.setHeight(summaryRect.height() - opt.fontMetrics.height()); auto dueDateRect = textRect.adjusted(textRect.width() - dueDateWidth, 0, 0, 0); dueDateRect.setHeight(summaryRect.height()); const auto additionalInfoRect = QRect(textRect.x(), summaryRect.bottom(), textRect.width(), textRect.height() - summaryRect.height()); // Draw background style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, widget); // Draw the check box if (task) { auto checkOption = opt; checkOption.rect = checkRect; checkOption.state = option.state & ~QStyle::State_HasFocus; checkOption.state |= isDone ? QStyle::State_On : QStyle::State_Off; style->drawPrimitive(QStyle::PE_IndicatorViewItemCheck, &checkOption, painter, widget); } // Draw the summary if (!summaryText.isEmpty()) { painter->setPen(summaryColor); painter->setFont(summaryFont); painter->drawText(summaryRect, Qt::AlignVCenter, summaryMetrics.elidedText(summaryText, Qt::ElideRight, summaryRect.width())); } // Draw the due date if (!dueDateText.isEmpty()) { painter->drawText(dueDateRect, Qt::AlignCenter, dueDateText); } // Draw the second line if (!additionalInfo.isEmpty()) { QFont additionalInfoFont = baseFont; additionalInfoFont.setItalic(true); additionalInfoFont.setPointSize(additionalInfoFont.pointSize() - 1); painter->setFont(additionalInfoFont); painter->drawText(additionalInfoRect, Qt::AlignLeft, additionalInfo); } } diff --git a/tests/features/CMakeLists.txt b/tests/features/CMakeLists.txt index 32958b4d..aa3a08a5 100644 --- a/tests/features/CMakeLists.txt +++ b/tests/features/CMakeLists.txt @@ -1,7 +1,6 @@ find_package(Boost "1.40" COMPONENTS system regex unit_test_framework) if(Boost_UNIT_TEST_FRAMEWORK_FOUND) add_subdirectory(zanshin) - add_subdirectory(renku) endif() diff --git a/tests/features/renku/CMakeLists.txt b/tests/features/renku/CMakeLists.txt deleted file mode 100644 index 8c339437..00000000 --- a/tests/features/renku/CMakeLists.txt +++ /dev/null @@ -1,13 +0,0 @@ -add_subdirectory(features) - -add_executable(tests-features-renku ../features-run.cpp) -add_test(NAME tests-features-renku COMMAND tests-features-renku) -add_definitions(-DUSER_XMLDATA="${CMAKE_CURRENT_SOURCE_DIR}/../testenv/data/testdata.xml" - -DCUKE_STEPS="${CMAKE_CURRENT_BINARY_DIR}/features/step_definitions/renku-cuke-steps" - -DFEATURES_DIR="${CMAKE_CURRENT_SOURCE_DIR}") -target_link_libraries(tests-features-renku - Qt5::Core - Qt5::Network -) -# Don't run this at the same time as tests-features-zanshin -set_tests_properties(tests-features-renku PROPERTIES RUN_SERIAL TRUE) diff --git a/tests/features/renku/features/CMakeLists.txt b/tests/features/renku/features/CMakeLists.txt deleted file mode 100644 index d16c6501..00000000 --- a/tests/features/renku/features/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ -add_subdirectory(step_definitions) diff --git a/tests/features/renku/features/datasource/datasource-default-settings.feature b/tests/features/renku/features/datasource/datasource-default-settings.feature deleted file mode 100644 index 1c6347e1..00000000 --- a/tests/features/renku/features/datasource/datasource-default-settings.feature +++ /dev/null @@ -1,15 +0,0 @@ -Feature: Default data source - As an advanced user - I can choose the default data source - In order to quickly store notes - - Scenario: Have a default data source for notes - Given I display the available data sources - When the setting key defaultNoteCollection changes to 6 - Then the default data source is "TestData / Private Notes" - - Scenario: Change the default data source for notes - Given I display the available data sources - And the setting key defaultNoteCollection changes to 42 - When the user changes the default data source to "TestData / Private Notes" - Then the setting key defaultNoteCollection is 6 diff --git a/tests/features/renku/features/datasource/datasource-listing.feature b/tests/features/renku/features/datasource/datasource-listing.feature deleted file mode 100644 index 915f5389..00000000 --- a/tests/features/renku/features/datasource/datasource-listing.feature +++ /dev/null @@ -1,14 +0,0 @@ -Feature: Data sources listing - As an advanced user - I can list sources - In order to list and store notes - - Scenario: All note sources appear in the list - Given I display the available data sources - When I list the items - Then the list is: - | display | icon | - | TestData | folder | - | TestData / Emails | folder | - | TestData / Emails / Notes | folder | - | TestData / Private Notes | folder | diff --git a/tests/features/renku/features/datasource/datasource-selection.feature b/tests/features/renku/features/datasource/datasource-selection.feature deleted file mode 100644 index d7c00fe1..00000000 --- a/tests/features/renku/features/datasource/datasource-selection.feature +++ /dev/null @@ -1,24 +0,0 @@ -Feature: Data sources selection - As an advanced user - I can select or deselect sources - In order to see more or less content - - Scenario: Unchecking impacts the inbox - Given I display the "Inbox" page - And there is an item named "TestData / Emails / Notes" in the available data sources - When I uncheck the item - And I look at the central list - And I list the items - Then the list is: - | display | - - Scenario: Checking impacts the inbox - Given I display the "Inbox" page - And there is an item named "TestData / Emails / Notes" in the available data sources - When I check the item - And I look at the central list - And I list the items - Then the list is: - | display | - | A note about nothing interesting | - diff --git a/tests/features/renku/features/editing/adding-note.feature b/tests/features/renku/features/editing/adding-note.feature deleted file mode 100644 index 78ee9d35..00000000 --- a/tests/features/renku/features/editing/adding-note.feature +++ /dev/null @@ -1,16 +0,0 @@ -Feature: Adding notes - As a note junkie - I can create note by giving a title - In order to collect ideas while reflecting on my life - - Scenario Outline: Adding a note in a page - Given I display the "" page - And I look at the central list - When I add a "note" named "" - And I list the items - Then the list contains "<title>" - - Examples: - | page | title | - | Inbox | A page of diary | - | Tags / Philosophy | A random note on life | diff --git a/tests/features/renku/features/editing/editing-note.feature b/tests/features/renku/features/editing/editing-note.feature deleted file mode 100644 index 0a7e7175..00000000 --- a/tests/features/renku/features/editing/editing-note.feature +++ /dev/null @@ -1,12 +0,0 @@ -Feature: Editing notes - As an organized person - I can edit a previously created note - In order to improve it, logging more ideas - - Scenario: Editing a note text - Given I display the "Inbox" page - And there is an item named "A note about nothing interesting" in the central list - When I open the item in the editor - And I change the editor text to "This is a boring note" - And I open the item in the editor again - Then the editor shows "This is a boring note" as text diff --git a/tests/features/renku/features/editing/removing-note.feature b/tests/features/renku/features/editing/removing-note.feature deleted file mode 100644 index 84a08d60..00000000 --- a/tests/features/renku/features/editing/removing-note.feature +++ /dev/null @@ -1,17 +0,0 @@ -Feature: Removing notes - As a note junkie - I can delete a note so it is removed - In order to clean up the old junk I accumulated - - Scenario Outline: Removing a note from a page - Given I display the "<page>" page - And there is an item named "<title>" in the central list - When I remove the item - And I list the items - Then the list does not contain "<title>" - - Examples: - | page | title | - | Inbox | A note about nothing interesting | - | Tags / Philosophy | A note about philosophy | - diff --git a/tests/features/renku/features/inbox/inbox-display.feature b/tests/features/renku/features/inbox/inbox-display.feature deleted file mode 100644 index 5d111ba1..00000000 --- a/tests/features/renku/features/inbox/inbox-display.feature +++ /dev/null @@ -1,12 +0,0 @@ -Feature: Inbox content - As someone collecting notes - I can display the Inbox - In order to see the notes (e.g. any note not associated to tag) - - Scenario: Unorganized notes appear in the inbox - Given I display the "Inbox" page - And I look at the central list - When I list the items - Then the list is: - | display | - | A note about nothing interesting | diff --git a/tests/features/renku/features/pages/pages-display.feature b/tests/features/renku/features/pages/pages-display.feature deleted file mode 100644 index 2e9a61f5..00000000 --- a/tests/features/renku/features/pages/pages-display.feature +++ /dev/null @@ -1,15 +0,0 @@ -Feature: Available pages content - As someone collecting notes - I can see all the pages available to me - In order to display them and add notes to them - - Scenario: Inbox and tags appear in the list - Given I display the available pages - When I list the items - Then the list is: - | display | icon | - | Inbox | mail-folder-inbox | - | Tags | folder | - | Tags / Philosophy | view-pim-tasks | - | Tags / Physics | view-pim-tasks | - diff --git a/tests/features/renku/features/step_definitions/CMakeLists.txt b/tests/features/renku/features/step_definitions/CMakeLists.txt deleted file mode 100644 index d272e6af..00000000 --- a/tests/features/renku/features/step_definitions/CMakeLists.txt +++ /dev/null @@ -1,27 +0,0 @@ -include_directories(${Boost_INCLUDE_DIRS}) -include_directories(${CMAKE_SOURCE_DIR}/3rdparty/cucumber-cpp/include) - -# Because cucumber-cpp exhibits plenty of warnings -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -w") - -set(steps_SRCS - main.cpp - ../../../cuke-steps.cpp - ${CMAKE_SOURCE_DIR}/src/renku/app/dependencies.cpp -) - -add_executable(renku-cuke-steps ${steps_SRCS}) -target_link_libraries(renku-cuke-steps - cucumber-cpp - Threads::Threads - ${Boost_REGEX_LIBRARY} - ${Boost_SYSTEM_LIBRARY} - ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY} - Qt5::Test - testlib - akonadi - domain - presentation - scripting - utils -) diff --git a/tests/features/renku/features/step_definitions/cucumber.wire b/tests/features/renku/features/step_definitions/cucumber.wire deleted file mode 100644 index da2da560..00000000 --- a/tests/features/renku/features/step_definitions/cucumber.wire +++ /dev/null @@ -1,2 +0,0 @@ -host: localhost -port: 3902 diff --git a/tests/features/renku/features/step_definitions/main.cpp b/tests/features/renku/features/step_definitions/main.cpp deleted file mode 100644 index f242a68b..00000000 --- a/tests/features/renku/features/step_definitions/main.cpp +++ /dev/null @@ -1,33 +0,0 @@ -/* This file is part of Renku - - Copyright 2014-2015 Kevin Ottens <ervin@kde.org> - - 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 "renku/app/dependencies.h" - -namespace CukeSteps -{ - void initializeAppDependencies() - { - App::initializeDependencies(); - } -} - diff --git a/tests/features/renku/features/tags/tag-add.feature b/tests/features/renku/features/tags/tag-add.feature deleted file mode 100644 index 40a063a8..00000000 --- a/tests/features/renku/features/tags/tag-add.feature +++ /dev/null @@ -1,17 +0,0 @@ -Feature: Tag creation - As someone using notes - I can create a tag - In order to give them some semantic - - - Scenario: New tags appear in the list - Given I display the available pages - When I add a "tag" named "Optional" - And I list the items - Then the list is: - | display | icon | - | Inbox | mail-folder-inbox | - | Tags | folder | - | Tags / Optional | view-pim-tasks | - | Tags / Philosophy | view-pim-tasks | - | Tags / Physics | view-pim-tasks | diff --git a/tests/features/renku/features/tags/tag-display.feature b/tests/features/renku/features/tags/tag-display.feature deleted file mode 100644 index a5153b8e..00000000 --- a/tests/features/renku/features/tags/tag-display.feature +++ /dev/null @@ -1,12 +0,0 @@ -Feature: Tag content - As someone collecting notes - I can display a tag - In order to see the notes associated to it - - Scenario: Tag notes appear in the corresponding page - Given I display the "Tags / Philosophy" page - And I look at the central list - When I list the items - Then the list is: - | display | - | A note about philosophy | diff --git a/tests/features/renku/features/tags/tag-drag-and-drop.feature b/tests/features/renku/features/tags/tag-drag-and-drop.feature deleted file mode 100644 index 54504408..00000000 --- a/tests/features/renku/features/tags/tag-drag-and-drop.feature +++ /dev/null @@ -1,17 +0,0 @@ -Feature: Tag note association - As someone collecting notes - I can associate notes to a tag - In order to describe the notes - - Scenario: Dropping a notes on a tag - Given I display the "Inbox" page - And there is an item named "A note about nothing interesting" in the central list - When I drop the item on "Tags / Physics" in the page list - And I display the "Tags / Physics" page - And I look at the central list - And I list the items - Then the list is: - | display | - | A note about physics | - | A note about nothing interesting | - diff --git a/tests/features/renku/features/tags/tag-note-add.feature b/tests/features/renku/features/tags/tag-note-add.feature deleted file mode 100644 index 72893a40..00000000 --- a/tests/features/renku/features/tags/tag-note-add.feature +++ /dev/null @@ -1,14 +0,0 @@ -Feature: Note creation from a tag - As someone collecting notes - I can add a note directly associated to a tag - In order to give my note some semantic - - Scenario: Note added from a tag appear in its list - Given I display the "Tags / Physics" page - When I add a "note" named "Studies in fluid mechanics" - And I look at the central list - When I list the items - Then the list is: - | display | - | A note about physics | - | Studies in fluid mechanics | diff --git a/tests/features/renku/features/tags/tag-note-remove.feature b/tests/features/renku/features/tags/tag-note-remove.feature deleted file mode 100644 index a46ad73c..00000000 --- a/tests/features/renku/features/tags/tag-note-remove.feature +++ /dev/null @@ -1,15 +0,0 @@ -Feature: Tag note dissociation - As someone collecting notes - I can delete a note related to a tag - In order to keep my tag meaningful - - Scenario: Removing a note linked to a tag from the central list - Given I display the "Inbox" page - And I look at the central list - Then the list is: - | display | - And I display the "Tags / Philosophy" page - And there is an item named "A note about philosophy" in the central list - When I remove the item - And I look at the central list - Then the list does not contain "A note about philosophy" diff --git a/tests/features/renku/features/tags/tag-note-reset.feature b/tests/features/renku/features/tags/tag-note-reset.feature deleted file mode 100644 index 38919e5a..00000000 --- a/tests/features/renku/features/tags/tag-note-reset.feature +++ /dev/null @@ -1,19 +0,0 @@ -Feature: Tag reset - As someone using notes - I can reset a note from all its tags - In order to maintain the semantic - - Scenario: Resetting a note dissociate it from all its tags - Given I display the "Tags / Physics" page - And there is an item named "A note about physics" in the central list - When I drop the item on "Inbox" in the page list - And I list the items - And the list does not contain "A note about physics" - And I display the "Inbox" page - And I look at the central list - And I list the items - Then the list is: - | display | - | A note about nothing interesting | - | A note about physics | - diff --git a/tests/features/renku/features/tags/tag-remove.feature b/tests/features/renku/features/tags/tag-remove.feature deleted file mode 100644 index a553d0da..00000000 --- a/tests/features/renku/features/tags/tag-remove.feature +++ /dev/null @@ -1,14 +0,0 @@ -Feature: Tag removal - As someone using notes - I can remove a tag - In order to maintain their semantic - - Scenario: Removed tag disappear from the list - Given I display the available pages - When I remove the page named "Physics" under "Tags" - And I list the items - Then the list is: - | display | icon | - | Inbox | mail-folder-inbox | - | Tags | folder | - | Tags / Philosophy | view-pim-tasks | diff --git a/tests/testlib/CMakeLists.txt b/tests/testlib/CMakeLists.txt index 2c1dea5e..3ea447d4 100644 --- a/tests/testlib/CMakeLists.txt +++ b/tests/testlib/CMakeLists.txt @@ -1,31 +1,30 @@ set(testlib_SRCS akonadidebug.cpp akonadifakedata.cpp akonadifakedataxmlloader.cpp akonadifakejobs.cpp akonadifakemonitor.cpp akonadifakestorage.cpp akonadifakestoragebehavior.cpp akonadistoragetestbase.cpp fakejob.cpp gencollection.cpp - gennote.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/gennote.cpp b/tests/testlib/gennote.cpp deleted file mode 100644 index 0f87ea1d..00000000 --- a/tests/testlib/gennote.cpp +++ /dev/null @@ -1,101 +0,0 @@ -/* This file is part of Zanshin - - Copyright 2015 Kevin Ottens <ervin@kde.org> - - 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 "gennote.h" - -#include <algorithm> - -#include <Akonadi/Notes/NoteUtils> -#include <KMime/Message> - -using namespace Testlib; - -GenNote::GenNote(const Akonadi::Item &item) - : m_item(item) -{ - m_item.setMimeType(Akonadi::NoteUtils::noteMimeType()); - if (!m_item.hasPayload<KMime::Message::Ptr>()) - m_item.setPayload(KMime::Message::Ptr(new KMime::Message)); -} - -GenNote::operator Akonadi::Item() -{ - return m_item; -} - -GenNote &GenNote::withId(Akonadi::Item::Id id) -{ - m_item.setId(id); - return *this; -} - -GenNote &GenNote::withParent(Akonadi::Collection::Id id) -{ - m_item.setParentCollection(Akonadi::Collection(id)); - return *this; -} - -GenNote &GenNote::withTags(const QList<Akonadi::Tag::Id> &ids) -{ - auto tags = Akonadi::Tag::List(); - std::transform(ids.constBegin(), ids.constEnd(), - std::back_inserter(tags), - [] (Akonadi::Tag::Id id) { - return Akonadi::Tag(id); - }); - m_item.setTags(tags); - return *this; -} - -GenNote &GenNote::withParentUid(const QString &uid) -{ - auto message = m_item.payload<KMime::Message::Ptr>(); - if (!uid.isEmpty()) { - auto relatedHeader = new KMime::Headers::Generic("X-Zanshin-RelatedProjectUid"); - relatedHeader->from7BitString(uid.toUtf8()); - message->appendHeader(relatedHeader); - } else { - (void)message->removeHeader("X-Zanshin-RelatedProjectUid"); - } - message->assemble(); - m_item.setPayload(message); - return *this; -} - -GenNote &GenNote::withTitle(const QString &title) -{ - auto message = m_item.payload<KMime::Message::Ptr>(); - Akonadi::NoteUtils::NoteMessageWrapper wrapper(message); - wrapper.setTitle(title); - m_item.setPayload(wrapper.message()); - return *this; -} - -GenNote &GenNote::withText(const QString &text) -{ - auto message = m_item.payload<KMime::Message::Ptr>(); - Akonadi::NoteUtils::NoteMessageWrapper wrapper(message); - wrapper.setText(text); - m_item.setPayload(wrapper.message()); - return *this; -} diff --git a/tests/testlib/gennote.h b/tests/testlib/gennote.h deleted file mode 100644 index 78cef007..00000000 --- a/tests/testlib/gennote.h +++ /dev/null @@ -1,53 +0,0 @@ -/* This file is part of Zanshin - - Copyright 2015 Kevin Ottens <ervin@kde.org> - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License or (at your option) version 3 or any later version - accepted by the membership of KDE e.V. (or its successor approved - by the membership of KDE e.V.), which shall act as a proxy - defined in Section 14 of version 3 of the license. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - USA. -*/ - -#ifndef TESTLIB_GENNOTE_H -#define TESTLIB_GENNOTE_H - -#include <QObject> - -#include <AkonadiCore/Item> - -namespace Testlib { - -class GenNote -{ -public: - explicit GenNote(const Akonadi::Item &item = Akonadi::Item()); - - operator Akonadi::Item(); - - GenNote &withId(Akonadi::Item::Id id); - GenNote &withParent(Akonadi::Collection::Id id); - GenNote &withTags(const QList<Akonadi::Tag::Id> &ids); - GenNote &withParentUid(const QString &uid); - GenNote &withTitle(const QString &title); - GenNote &withText(const QString &text); - -private: - Akonadi::Item m_item; -}; - -} - -#endif // TESTLIB_GENNOTE_H diff --git a/tests/units/akonadi/CMakeLists.txt b/tests/units/akonadi/CMakeLists.txt index b89b6c5b..376aa899 100644 --- a/tests/units/akonadi/CMakeLists.txt +++ b/tests/units/akonadi/CMakeLists.txt @@ -1,32 +1,28 @@ include(MacroAkonadiAutoTests) zanshin_auto_tests( akonadiapplicationselectedattributetest akonadicachetest akonadicachingstoragetest akonadicontextqueriestest akonadicontextrepositorytest akonadidatasourcequeriestest akonadidatasourcerepositorytest akonadilivequeryhelperstest akonadilivequeryintegratortest - akonadinotequeriestest - akonadinoterepositorytest akonadiprojectqueriestest akonadiprojectrepositorytest akonadiserializertest akonadistoragesettingstest - akonaditagqueriestest - akonaditagrepositorytest akonaditaskqueriestest akonaditaskrepositorytest akonaditimestampattributetest ) zanshin_akonadi_auto_tests( akonadistoragetest akonadicachingstorageintegrationtest ) target_link_libraries(tests-units-akonadi-akonadistoragetest akonadi testlib) target_link_libraries(tests-units-akonadi-akonadicachingstorageintegrationtest akonadi testlib) diff --git a/tests/units/akonadi/akonadicachetest.cpp b/tests/units/akonadi/akonadicachetest.cpp index b0adfa7d..3a7ee4f7 100644 --- a/tests/units/akonadi/akonadicachetest.cpp +++ b/tests/units/akonadi/akonadicachetest.cpp @@ -1,724 +1,680 @@ /* This file is part of Zanshin Copyright 2017 Kevin Ottens <ervin@kde.org> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include <testlib/qtest_zanshin.h> #include "akonadi/akonadicache.h" #include "akonadi/akonadiserializer.h" #include "testlib/akonadifakemonitor.h" #include "testlib/gencollection.h" #include "testlib/gentodo.h" #include "testlib/gentag.h" using namespace Testlib; Q_DECLARE_METATYPE(Akonadi::StorageInterface::FetchContentTypes); class AkonadiCacheTest : public QObject { Q_OBJECT private slots: void shouldHaveDefaultState() { // GIVEN auto monitor = AkonadiFakeMonitor::Ptr::create(); auto cache = Akonadi::Cache::Ptr::create(Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer), monitor); // THEN QVERIFY(!cache->isContentTypesPopulated(Akonadi::StorageInterface::AllContent)); QVERIFY(!cache->isContentTypesPopulated(Akonadi::StorageInterface::Tasks)); QVERIFY(!cache->isContentTypesPopulated(Akonadi::StorageInterface::Notes)); QVERIFY(!cache->isContentTypesPopulated(Akonadi::StorageInterface::Notes|Akonadi::StorageInterface::Tasks)); QVERIFY(cache->collections(Akonadi::StorageInterface::AllContent).isEmpty()); QVERIFY(cache->collections(Akonadi::StorageInterface::Tasks).isEmpty()); QVERIFY(cache->collections(Akonadi::StorageInterface::Notes).isEmpty()); QVERIFY(cache->collections(Akonadi::StorageInterface::Notes|Akonadi::StorageInterface::Tasks).isEmpty()); QVERIFY(!cache->isTagListPopulated()); QVERIFY(cache->tags().isEmpty()); } void shouldStoreCollectionsForTypesAndUpdate() { // GIVEN const auto noteCollection = Akonadi::Collection(GenCollection().withRootAsParent() .withId(1) .withName("notes") .withNoteContent()); const auto taskCollection = Akonadi::Collection(GenCollection().withRootAsParent() .withId(2) .withName("tasks") .withTaskContent()); const auto noteTaskCollection = Akonadi::Collection(GenCollection().withRootAsParent() .withId(3) .withName("tasks+notes") .withTaskContent() .withNoteContent()); const auto stuffCollection = Akonadi::Collection(GenCollection().withRootAsParent() .withId(4) .withName("stuff")); auto monitor = AkonadiFakeMonitor::Ptr::create(); auto cache = Akonadi::Cache::Ptr::create(Akonadi::Serializer::Ptr(new Akonadi::Serializer), monitor); // WHEN cache->setCollections(Akonadi::StorageInterface::AllContent, Akonadi::Collection::List() << stuffCollection << noteTaskCollection << taskCollection << noteCollection); // THEN QVERIFY(cache->isContentTypesPopulated(Akonadi::StorageInterface::AllContent)); QVERIFY(cache->isCollectionKnown(stuffCollection.id())); QVERIFY(!cache->isCollectionPopulated(stuffCollection.id())); QVERIFY(cache->items(stuffCollection).isEmpty()); QCOMPARE(cache->collections(Akonadi::StorageInterface::AllContent), Akonadi::Collection::List() << stuffCollection << noteTaskCollection << taskCollection << noteCollection); QCOMPARE(cache->collection(stuffCollection.id()), stuffCollection); QCOMPARE(cache->collection(stuffCollection.id()).name(), stuffCollection.name()); // WHEN monitor->changeCollection(GenCollection(stuffCollection).withName("stuff2")); // THEN QCOMPARE(cache->collection(stuffCollection.id()).name(), QStringLiteral("stuff2")); - // WHEN - cache->setCollections(Akonadi::StorageInterface::Notes|Akonadi::StorageInterface::Tasks, - Akonadi::Collection::List() << noteTaskCollection << taskCollection << noteCollection); - - // THEN - QVERIFY(cache->isContentTypesPopulated(Akonadi::StorageInterface::Notes|Akonadi::StorageInterface::Tasks)); - QVERIFY(cache->isCollectionKnown(noteTaskCollection.id())); - QVERIFY(!cache->isCollectionPopulated(noteTaskCollection.id())); - QVERIFY(cache->items(noteTaskCollection).isEmpty()); - QCOMPARE(cache->collections(Akonadi::StorageInterface::Notes|Akonadi::StorageInterface::Tasks), - Akonadi::Collection::List() << noteTaskCollection << taskCollection << noteCollection); - QCOMPARE(cache->collection(noteTaskCollection.id()), noteTaskCollection); - QCOMPARE(cache->collection(noteTaskCollection.id()).name(), noteTaskCollection.name()); - // WHEN monitor->changeCollection(GenCollection(noteTaskCollection).withName("note+task2")); // THEN QCOMPARE(cache->collection(noteTaskCollection.id()).name(), QStringLiteral("note+task2")); // WHEN cache->setCollections(Akonadi::StorageInterface::Tasks, Akonadi::Collection::List() << noteTaskCollection << taskCollection); // THEN QVERIFY(cache->isContentTypesPopulated(Akonadi::StorageInterface::Tasks)); QVERIFY(cache->isCollectionKnown(taskCollection.id())); QVERIFY(!cache->isCollectionPopulated(taskCollection.id())); QVERIFY(cache->items(taskCollection).isEmpty()); QCOMPARE(cache->collections(Akonadi::StorageInterface::Tasks), Akonadi::Collection::List() << noteTaskCollection << taskCollection); QCOMPARE(cache->collection(taskCollection.id()), taskCollection); QCOMPARE(cache->collection(taskCollection.id()).name(), taskCollection.name()); // WHEN monitor->changeCollection(GenCollection(taskCollection).withName("task2")); // THEN QCOMPARE(cache->collection(taskCollection.id()).name(), QStringLiteral("task2")); - - // WHEN - cache->setCollections(Akonadi::StorageInterface::Notes, - Akonadi::Collection::List() << noteTaskCollection << noteCollection); - - // THEN - QVERIFY(cache->isContentTypesPopulated(Akonadi::StorageInterface::Notes)); - QVERIFY(cache->isCollectionKnown(noteCollection.id())); - QVERIFY(!cache->isCollectionPopulated(noteCollection.id())); - QVERIFY(cache->items(noteCollection).isEmpty()); - QCOMPARE(cache->collections(Akonadi::StorageInterface::Notes), - Akonadi::Collection::List() << noteTaskCollection << noteCollection); - QCOMPARE(cache->collection(noteCollection.id()), noteCollection); - QCOMPARE(cache->collection(noteCollection.id()).name(), noteCollection.name()); } void shouldHandleCollectionAdds_data() { QTest::addColumn<Akonadi::StorageInterface::FetchContentTypes>("contentTypes"); QTest::addColumn<Akonadi::Collection>("collection"); QTest::addColumn<bool>("seen"); - const auto allContent = Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::AllContent); const auto taskContent = Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::Tasks); - const auto noteContent = Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::Notes); - const auto taskNoteContent = Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::Notes - | Akonadi::StorageInterface::Tasks); const auto none = Akonadi::Collection(GenCollection().withRootAsParent() .withId(2) .withName("collection")); const auto task = Akonadi::Collection(GenCollection(none).withTaskContent()); const auto note = Akonadi::Collection(GenCollection(none).withNoteContent()); const auto taskNote = Akonadi::Collection(GenCollection(none).withNoteContent().withTaskContent()); - QTest::newRow("all vs none") << allContent << none << true; - QTest::newRow("all vs task") << allContent << task << true; - QTest::newRow("all vs note") << allContent << note << true; - QTest::newRow("all vs taskNote") << allContent << taskNote << true; QTest::newRow("tasks vs none") << taskContent << none << false; QTest::newRow("tasks vs task") << taskContent << task << true; QTest::newRow("tasks vs note") << taskContent << note << false; QTest::newRow("tasks vs taskNote") << taskContent << taskNote << true; - QTest::newRow("notes vs none") << noteContent << none << false; - QTest::newRow("notes vs task") << noteContent << task << false; - QTest::newRow("notes vs note") << noteContent << note << true; - QTest::newRow("notes vs taskNote") << noteContent << taskNote << true; - QTest::newRow("notes+tasks vs none") << taskNoteContent << none << false; - QTest::newRow("notes+tasks vs task") << taskNoteContent << task << true; - QTest::newRow("notes+tasks vs note") << taskNoteContent << note << true; - QTest::newRow("notes+tasks vs taskNote") << taskNoteContent << taskNote << true; } void shouldHandleCollectionAdds() { // GIVEN QFETCH(Akonadi::StorageInterface::FetchContentTypes, contentTypes); QFETCH(Akonadi::Collection, collection); auto monitor = AkonadiFakeMonitor::Ptr::create(); auto cache = Akonadi::Cache::Ptr::create(Akonadi::Serializer::Ptr(new Akonadi::Serializer), monitor); // WHEN monitor->addCollection(collection); // THEN QVERIFY(!cache->isContentTypesPopulated(contentTypes)); QVERIFY(cache->collections(contentTypes).isEmpty()); QCOMPARE(cache->collection(collection.id()), Akonadi::Collection()); // WHEN cache->setCollections(contentTypes, Akonadi::Collection::List()); monitor->addCollection(collection); // THEN QVERIFY(cache->isContentTypesPopulated(contentTypes)); QFETCH(bool, seen); if (seen) { QVERIFY(!cache->collections(contentTypes).isEmpty()); QCOMPARE(cache->collection(collection.id()), collection); QCOMPARE(cache->collection(collection.id()).name(), collection.name()); } else { QVERIFY(cache->collections(contentTypes).isEmpty()); QCOMPARE(cache->collection(collection.id()), Akonadi::Collection()); } } void shouldHandleCollectionChanges() { // GIVEN const auto collection = Akonadi::Collection(GenCollection().withRootAsParent() .withId(2) .withName("tasks") .withTaskContent()); auto monitor = AkonadiFakeMonitor::Ptr::create(); auto cache = Akonadi::Cache::Ptr::create(Akonadi::Serializer::Ptr(new Akonadi::Serializer), monitor); cache->setCollections(Akonadi::StorageInterface::Tasks, Akonadi::Collection::List() << collection); // WHEN const auto collection2 = Akonadi::Collection(GenCollection().withRootAsParent() .withId(2) .withName("tasks2") .withTaskContent()); monitor->changeCollection(collection2); // THEN QCOMPARE(cache->collection(collection.id()).name(), QStringLiteral("tasks2")); } void shouldStoreTagsAndUpdate() { // GIVEN const auto tag1 = Akonadi::Tag(GenTag().withId(1).asPlain().withName("tag1")); const auto tag2 = Akonadi::Tag(GenTag().withId(2).asContext().withName("tag2")); auto monitor = AkonadiFakeMonitor::Ptr::create(); auto cache = Akonadi::Cache::Ptr::create(Akonadi::Serializer::Ptr(new Akonadi::Serializer), monitor); // THEN QVERIFY(!cache->isTagKnown(tag1.id())); QVERIFY(!cache->isTagPopulated(tag1.id())); QVERIFY(!cache->isTagKnown(tag2.id())); QVERIFY(!cache->isTagPopulated(tag2.id())); // WHEN cache->setTags(Akonadi::Tag::List() << tag1 << tag2); // THEN QVERIFY(cache->isTagListPopulated()); QCOMPARE(cache->tags(), Akonadi::Tag::List() << tag1 << tag2); QCOMPARE(cache->tag(tag1.id()), tag1); QCOMPARE(cache->tag(tag1.id()).name(), tag1.name()); QCOMPARE(cache->tag(tag2.id()), tag2); QCOMPARE(cache->tag(tag2.id()).name(), tag2.name()); QVERIFY(cache->isTagKnown(tag1.id())); QVERIFY(!cache->isTagPopulated(tag1.id())); QVERIFY(cache->isTagKnown(tag2.id())); QVERIFY(!cache->isTagPopulated(tag2.id())); // WHEN cache->setTags(Akonadi::Tag::List() << GenTag(tag1).withName("tag1bis") << tag2); // THEN QCOMPARE(cache->tags(), Akonadi::Tag::List() << tag1 << tag2); QCOMPARE(cache->tag(tag1.id()), tag1); QCOMPARE(cache->tag(tag1.id()).name(), QStringLiteral("tag1bis")); QCOMPARE(cache->tag(tag2.id()), tag2); QCOMPARE(cache->tag(tag2.id()).name(), tag2.name()); QVERIFY(cache->isTagKnown(tag1.id())); QVERIFY(!cache->isTagPopulated(tag1.id())); QVERIFY(cache->isTagKnown(tag2.id())); QVERIFY(!cache->isTagPopulated(tag2.id())); // WHEN monitor->changeTag(GenTag(tag1).withName("tag1ter")); // THEN QCOMPARE(cache->tags(), Akonadi::Tag::List() << tag1 << tag2); QCOMPARE(cache->tag(tag1.id()), tag1); QCOMPARE(cache->tag(tag1.id()).name(), QStringLiteral("tag1ter")); QCOMPARE(cache->tag(tag2.id()), tag2); QCOMPARE(cache->tag(tag2.id()).name(), tag2.name()); QVERIFY(cache->isTagKnown(tag1.id())); QVERIFY(!cache->isTagPopulated(tag1.id())); QVERIFY(cache->isTagKnown(tag2.id())); QVERIFY(!cache->isTagPopulated(tag2.id())); } void shouldHandleTagAdds() { // GIVEN const auto tag = Akonadi::Tag(GenTag().withId(1).asPlain().withName("tag")); auto monitor = AkonadiFakeMonitor::Ptr::create(); auto cache = Akonadi::Cache::Ptr::create(Akonadi::Serializer::Ptr(new Akonadi::Serializer), monitor); // WHEN monitor->addTag(tag); // THEN QVERIFY(cache->tags().isEmpty()); // WHEN cache->setTags(Akonadi::Tag::List()); monitor->addTag(tag); // THEN QVERIFY(cache->isTagListPopulated()); QCOMPARE(cache->tags(), Akonadi::Tag::List() << tag); QCOMPARE(cache->tag(tag.id()), tag); QCOMPARE(cache->tag(tag.id()).name(), QStringLiteral("tag")); } void shouldHandleTagChanges() { // GIVEN const auto tag = Akonadi::Tag(GenTag().withId(1).asPlain().withName("tag")); auto monitor = AkonadiFakeMonitor::Ptr::create(); auto cache = Akonadi::Cache::Ptr::create(Akonadi::Serializer::Ptr(new Akonadi::Serializer), monitor); cache->setTags(Akonadi::Tag::List() << tag); // WHEN const auto tagbis = Akonadi::Tag(GenTag().withId(1).asPlain().withName("tagbis")); monitor->changeTag(tagbis); // THEN QCOMPARE(cache->tag(tag.id()).name(), QStringLiteral("tagbis")); } void shouldPopulateCollectionsWithItems() { // GIVEN const auto taskCollection1 = Akonadi::Collection(GenCollection().withRootAsParent() .withId(1) .withName("tasks1") .withTaskContent()); const auto items1 = Akonadi::Item::List() << Akonadi::Item(GenTodo().withId(1).withTitle("item1")) << Akonadi::Item(GenTodo().withId(2).withTitle("item2")); const auto taskCollection2 = Akonadi::Collection(GenCollection().withRootAsParent() .withId(2) .withName("tasks2") .withTaskContent()); const auto items2 = Akonadi::Item::List() << Akonadi::Item(GenTodo().withId(3).withTitle("item3")) << Akonadi::Item(GenTodo().withId(4).withTitle("item4")); auto monitor = AkonadiFakeMonitor::Ptr::create(); auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); auto cache = Akonadi::Cache::Ptr::create(serializer, monitor); cache->setCollections(Akonadi::StorageInterface::Tasks, Akonadi::Collection::List() << taskCollection1 << taskCollection2); // WHEN cache->populateCollection(taskCollection1, items1); // THEN QVERIFY(cache->isCollectionPopulated(taskCollection1.id())); QCOMPARE(cache->items(taskCollection1), items1); QCOMPARE(cache->item(items1.at(0).id()), items1.at(0)); QCOMPARE(cache->item(items1.at(1).id()), items1.at(1)); // WHEN cache->populateCollection(taskCollection2, items2); // THEN QVERIFY(cache->isCollectionPopulated(taskCollection2.id())); QCOMPARE(cache->items(taskCollection2), items2); QCOMPARE(cache->item(items2.at(0).id()), items2.at(0)); QCOMPARE(cache->item(items2.at(1).id()), items2.at(1)); } void shouldHandleCollectionRemoves() { // GIVEN const auto tag = Akonadi::Tag(GenTag().withId(1).withName("tag")); const auto collection1 = Akonadi::Collection(GenCollection().withRootAsParent() .withId(1) .withName("tasks1") .withTaskContent()); const auto items1 = Akonadi::Item::List() << Akonadi::Item(GenTodo().withId(1).withTitle("item1")) << Akonadi::Item(GenTodo().withId(2).withTitle("item2")); const auto collection2 = Akonadi::Collection(GenCollection().withRootAsParent() .withId(2) .withName("tasks2") .withTaskContent()); const auto items2 = Akonadi::Item::List() << Akonadi::Item(GenTodo().withId(3).withTitle("item3")) << Akonadi::Item(GenTodo().withId(4).withTitle("item4")); auto monitor = AkonadiFakeMonitor::Ptr::create(); auto cache = Akonadi::Cache::Ptr::create(Akonadi::Serializer::Ptr(new Akonadi::Serializer), monitor); cache->setCollections(Akonadi::StorageInterface::Tasks, Akonadi::Collection::List() << collection1 << collection2); cache->populateCollection(collection1, items1); cache->populateCollection(collection2, items2); cache->populateTag(tag, items1 + items2); // WHEN monitor->removeCollection(collection1); // THEN QVERIFY(!cache->isCollectionPopulated(collection1.id())); QVERIFY(cache->items(collection1).isEmpty()); QCOMPARE(cache->item(items1.at(0).id()), Akonadi::Item()); QCOMPARE(cache->item(items1.at(1).id()), Akonadi::Item()); QVERIFY(cache->isCollectionPopulated(collection2.id())); QCOMPARE(cache->items(collection2), items2); QCOMPARE(cache->item(items2.at(0).id()), items2.at(0)); QCOMPARE(cache->item(items2.at(1).id()), items2.at(1)); QVERIFY(cache->isTagPopulated(tag.id())); QCOMPARE(cache->items(tag), items2); } void shouldPopulateTagsWithItems() { // GIVEN const auto tag1 = Akonadi::Tag(GenTag().withId(1).withName("tag1")); const auto items1 = Akonadi::Item::List() << Akonadi::Item(GenTodo().withId(1).withTitle("item1")) << Akonadi::Item(GenTodo().withId(2).withTitle("item2")); const auto tag2 = Akonadi::Tag(GenTag().withId(2).withName("tag2")); const auto items2 = Akonadi::Item::List() << Akonadi::Item(GenTodo().withId(3).withTitle("item3")) << Akonadi::Item(GenTodo().withId(4).withTitle("item4")); auto monitor = AkonadiFakeMonitor::Ptr::create(); auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); auto cache = Akonadi::Cache::Ptr::create(serializer, monitor); cache->setTags(Akonadi::Tag::List() << tag1 << tag2); // WHEN cache->populateTag(tag1, items1); // THEN QVERIFY(cache->isTagPopulated(tag1.id())); QCOMPARE(cache->items(tag1), items1); QCOMPARE(cache->item(items1.at(0).id()), items1.at(0)); QCOMPARE(cache->item(items1.at(1).id()), items1.at(1)); // WHEN cache->populateTag(tag2, items2); // THEN QVERIFY(cache->isTagPopulated(tag2.id())); QCOMPARE(cache->items(tag2), items2); QCOMPARE(cache->item(items2.at(0).id()), items2.at(0)); QCOMPARE(cache->item(items2.at(1).id()), items2.at(1)); } void shouldHandleTagRemoves() { // GIVEN const auto collection = Akonadi::Collection(GenCollection().withRootAsParent() .withId(1) .withName("collection") .withTaskContent()); const auto tag1 = Akonadi::Tag(GenTag().withId(1).withName("tag1")); const auto items1 = Akonadi::Item::List() << Akonadi::Item(GenTodo().withId(1).withTitle("item1")) << Akonadi::Item(GenTodo().withId(2).withTitle("item2")); const auto tag2 = Akonadi::Tag(GenTag().withId(2).withName("tag2")); const auto items2 = Akonadi::Item::List() << Akonadi::Item(GenTodo().withId(3).withTitle("item3")) << Akonadi::Item(GenTodo().withId(4).withTitle("item4")); auto monitor = AkonadiFakeMonitor::Ptr::create(); auto cache = Akonadi::Cache::Ptr::create(Akonadi::Serializer::Ptr(new Akonadi::Serializer), monitor); cache->setTags(Akonadi::Tag::List() << tag1 << tag2); cache->populateCollection(collection, items1 + items2); cache->populateTag(tag1, items1); cache->populateTag(tag2, items2); // WHEN monitor->removeTag(tag1); // THEN QVERIFY(!cache->isTagPopulated(tag1.id())); QVERIFY(cache->items(tag1).isEmpty()); QCOMPARE(cache->item(items1.at(0).id()), items1.at(0)); QCOMPARE(cache->item(items1.at(1).id()), items1.at(1)); QVERIFY(cache->isTagPopulated(tag2.id())); QCOMPARE(cache->items(tag2), items2); QCOMPARE(cache->item(items2.at(0).id()), items2.at(0)); QCOMPARE(cache->item(items2.at(1).id()), items2.at(1)); QVERIFY(cache->isCollectionPopulated(collection.id())); QCOMPARE(cache->items(collection), items1 + items2); } void shouldHandleItemChanges() { // GIVEN const auto collection1 = Akonadi::Collection(GenCollection().withRootAsParent() .withId(1) .withName("tasks1") .withTaskContent()); const auto collection2 = Akonadi::Collection(GenCollection().withRootAsParent() .withId(2) .withName("tasks2") .withTaskContent()); const auto collection3 = Akonadi::Collection(GenCollection().withRootAsParent() .withId(3) .withName("tasks3") .withTaskContent()); const auto tag1 = Akonadi::Tag(GenTag().withId(1).withName("tag1")); const auto tag2 = Akonadi::Tag(GenTag().withId(2).withName("tag2")); const auto tag3 = Akonadi::Tag(GenTag().withId(3).withName("tag3")); const auto items = Akonadi::Item::List() << Akonadi::Item(GenTodo().withId(1).withParent(1).withTags({1}).withTitle("item1")) << Akonadi::Item(GenTodo().withId(2).withParent(1).withTags({1}).withTitle("item2")); const auto item3 = Akonadi::Item(GenTodo().withId(3).withParent(1).withTags({1}).withTitle("item3")); auto monitor = AkonadiFakeMonitor::Ptr::create(); auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); auto cache = Akonadi::Cache::Ptr::create(serializer, monitor); cache->setCollections(Akonadi::StorageInterface::Tasks, Akonadi::Collection::List() << collection1 << collection2 << collection3); cache->populateCollection(collection1, items); cache->populateCollection(collection2, Akonadi::Item::List()); cache->populateTag(tag1, items); cache->populateTag(tag2, Akonadi::Item::List()); // WHEN monitor->changeItem(GenTodo(items.at(0)).withTitle("item1bis")); // THEN auto todo = serializer->createTaskFromItem(cache->item(items.at(0).id())); QCOMPARE(todo->title(), QStringLiteral("item1bis")); QVERIFY(cache->isCollectionPopulated(collection1.id())); QVERIFY(cache->items(collection1).contains(items.at(0))); QVERIFY(cache->isCollectionPopulated(collection2.id())); QVERIFY(!cache->items(collection2).contains(items.at(0))); QVERIFY(!cache->isCollectionPopulated(collection3.id())); QVERIFY(!cache->items(collection3).contains(items.at(0))); QVERIFY(cache->isTagPopulated(tag1.id())); QVERIFY(cache->items(tag1).contains(items.at(0))); QVERIFY(cache->isTagPopulated(tag2.id())); QVERIFY(!cache->items(tag2).contains(items.at(0))); QVERIFY(!cache->isTagPopulated(tag3.id())); QVERIFY(!cache->items(tag3).contains(items.at(0))); // WHEN monitor->changeItem(GenTodo(items.at(0)).withParent(2)); // THEN QVERIFY(cache->isCollectionPopulated(collection1.id())); QVERIFY(!cache->items(collection1).contains(items.at(0))); QVERIFY(cache->isCollectionPopulated(collection2.id())); QVERIFY(cache->items(collection2).contains(items.at(0))); QVERIFY(!cache->isCollectionPopulated(collection3.id())); QVERIFY(!cache->items(collection3).contains(items.at(0))); QVERIFY(cache->isTagPopulated(tag1.id())); QVERIFY(cache->items(tag1).contains(items.at(0))); QVERIFY(cache->isTagPopulated(tag2.id())); QVERIFY(!cache->items(tag2).contains(items.at(0))); QVERIFY(!cache->isTagPopulated(tag3.id())); QVERIFY(!cache->items(tag3).contains(items.at(0))); // WHEN monitor->changeItem(GenTodo(items.at(0)).withParent(3)); // THEN QVERIFY(cache->isCollectionPopulated(collection1.id())); QVERIFY(!cache->items(collection1).contains(items.at(0))); QVERIFY(cache->isCollectionPopulated(collection2.id())); QVERIFY(!cache->items(collection2).contains(items.at(0))); QVERIFY(!cache->isCollectionPopulated(collection3.id())); QVERIFY(!cache->items(collection3).contains(items.at(0))); QVERIFY(cache->isTagPopulated(tag1.id())); QVERIFY(cache->items(tag1).contains(items.at(0))); QVERIFY(cache->isTagPopulated(tag2.id())); QVERIFY(!cache->items(tag2).contains(items.at(0))); QVERIFY(!cache->isTagPopulated(tag3.id())); QVERIFY(!cache->items(tag3).contains(items.at(0))); // WHEN monitor->changeItem(GenTodo().withId(1).withParent(2).withTags({2}).withTitle("item1")); // THEN QVERIFY(cache->isCollectionPopulated(collection1.id())); QVERIFY(!cache->items(collection1).contains(items.at(0))); QVERIFY(cache->isCollectionPopulated(collection2.id())); QVERIFY(cache->items(collection2).contains(items.at(0))); QVERIFY(!cache->isCollectionPopulated(collection3.id())); QVERIFY(!cache->items(collection3).contains(items.at(0))); QVERIFY(cache->isTagPopulated(tag1.id())); QVERIFY(!cache->items(tag1).contains(items.at(0))); QVERIFY(cache->isTagPopulated(tag2.id())); QVERIFY(cache->items(tag2).contains(items.at(0))); QVERIFY(!cache->isTagPopulated(tag3.id())); QVERIFY(!cache->items(tag3).contains(items.at(0))); // WHEN monitor->changeItem(item3); // THEN QVERIFY(cache->items(collection1).contains(item3)); QVERIFY(cache->items(tag1).contains(item3)); } void shouldHandleItemAdds() { // GIVEN const auto collection1 = Akonadi::Collection(GenCollection().withRootAsParent() .withId(1) .withName("tasks1") .withTaskContent()); const auto collection2 = Akonadi::Collection(GenCollection().withRootAsParent() .withId(2) .withName("tasks2") .withTaskContent()); const auto tag1 = Akonadi::Tag(GenTag().withId(1).withName("tag1")); const auto tag2 = Akonadi::Tag(GenTag().withId(2).withName("tag2")); const auto item1 = Akonadi::Item(GenTodo().withId(1).withParent(1).withTags({1}).withTitle("item1")); const auto item2 = Akonadi::Item(GenTodo().withId(2).withParent(2).withTags({2}).withTitle("item2")); auto monitor = AkonadiFakeMonitor::Ptr::create(); auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); auto cache = Akonadi::Cache::Ptr::create(serializer, monitor); cache->setCollections(Akonadi::StorageInterface::Tasks, Akonadi::Collection::List() << collection1 << collection2); cache->populateCollection(collection1, Akonadi::Item::List()); cache->populateTag(tag1, Akonadi::Item::List()); // WHEN monitor->addItem(item1); // THEN QVERIFY(cache->isCollectionPopulated(collection1.id())); QVERIFY(!cache->items(collection1).isEmpty()); QCOMPARE(cache->items(collection1), Akonadi::Item::List() << item1); QVERIFY(!cache->isCollectionPopulated(collection2.id())); QVERIFY(cache->items(collection2).isEmpty()); QVERIFY(cache->isTagPopulated(tag1.id())); QVERIFY(!cache->items(tag1).isEmpty()); QCOMPARE(cache->items(tag1), Akonadi::Item::List() << item1); QVERIFY(!cache->isTagPopulated(tag2.id())); QVERIFY(cache->items(tag2).isEmpty()); // WHEN monitor->addItem(item2); // THEN QVERIFY(cache->isCollectionPopulated(collection1.id())); QVERIFY(!cache->items(collection1).isEmpty()); QCOMPARE(cache->items(collection1), Akonadi::Item::List() << item1); QVERIFY(!cache->isCollectionPopulated(collection2.id())); QVERIFY(cache->items(collection2).isEmpty()); QVERIFY(cache->isTagPopulated(tag1.id())); QVERIFY(!cache->items(tag1).isEmpty()); QCOMPARE(cache->items(tag1), Akonadi::Item::List() << item1); QVERIFY(!cache->isTagPopulated(tag2.id())); QVERIFY(cache->items(tag2).isEmpty()); } void shouldHandleItemRemoves() { // GIVEN const auto collection = Akonadi::Collection(GenCollection().withRootAsParent() .withId(1) .withName("tasks") .withTaskContent()); const auto tag = Akonadi::Tag(GenTag().withId(1).withName("tag")); const auto items = Akonadi::Item::List() << Akonadi::Item(GenTodo().withId(1).withTitle("item1")) << Akonadi::Item(GenTodo().withId(2).withTitle("item2")); auto monitor = AkonadiFakeMonitor::Ptr::create(); auto cache = Akonadi::Cache::Ptr::create(Akonadi::Serializer::Ptr(new Akonadi::Serializer), monitor); cache->setCollections(Akonadi::StorageInterface::Tasks, Akonadi::Collection::List() << collection); cache->setTags(Akonadi::Tag::List() << tag); cache->populateCollection(collection, items); cache->populateTag(tag, items); // WHEN monitor->removeItem(items.at(0)); // THEN QVERIFY(cache->isCollectionPopulated(collection.id())); QCOMPARE(cache->items(collection), Akonadi::Item::List() << items.at(1)); QVERIFY(cache->isTagPopulated(tag.id())); QCOMPARE(cache->items(tag), Akonadi::Item::List() << items.at(1)); QCOMPARE(cache->item(items.at(0).id()), Akonadi::Item()); QCOMPARE(cache->item(items.at(1).id()), items.at(1)); } }; ZANSHIN_TEST_MAIN(AkonadiCacheTest) #include "akonadicachetest.moc" diff --git a/tests/units/akonadi/akonadicachingstoragetest.cpp b/tests/units/akonadi/akonadicachingstoragetest.cpp index 8fc8250d..9c96ed85 100644 --- a/tests/units/akonadi/akonadicachingstoragetest.cpp +++ b/tests/units/akonadi/akonadicachingstoragetest.cpp @@ -1,481 +1,441 @@ /* This file is part of Zanshin Copyright 2017 Kevin Ottens <ervin@kde.org> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include <testlib/qtest_zanshin.h> #include "akonadi/akonadicachingstorage.h" #include "akonadi/akonadiserializer.h" #include "akonadi/akonadicollectionfetchjobinterface.h" #include "akonadi/akonadiitemfetchjobinterface.h" #include "akonadi/akonaditagfetchjobinterface.h" #include "testlib/akonadifakedata.h" #include "testlib/gencollection.h" #include "testlib/gentodo.h" #include "testlib/gentag.h" #include "testlib/testhelpers.h" Q_DECLARE_METATYPE(Akonadi::StorageInterface::FetchDepth) using namespace Testlib; class AkonadiCachingStorageTest : public QObject { Q_OBJECT public: explicit AkonadiCachingStorageTest(QObject *parent = nullptr) : QObject(parent) { qRegisterMetaType<Akonadi::Collection>(); qRegisterMetaType<Akonadi::Item>(); qRegisterMetaType<Akonadi::Tag>(); } QStringList onlyWithSuffix(const QStringList &list, const QString &suffix) { return onlyWithSuffixes(list, {suffix}); } QStringList onlyWithSuffixes(const QStringList &list, const QStringList &suffixes) { auto res = QStringList(); std::copy_if(list.cbegin(), list.cend(), std::back_inserter(res), [suffixes](const QString &entry) { for (const auto &suffix : suffixes) { if (entry.endsWith(suffix)) return true; } return false; }); return res; } private slots: void shouldCacheAllCollectionsPerFetchType_data() { QTest::addColumn<Akonadi::Collection>("rootCollection"); QTest::addColumn<Akonadi::StorageInterface::FetchDepth>("fetchDepth"); QTest::addColumn<int>("contentTypesInt"); QTest::addColumn<QStringList>("expectedFetchNames"); QTest::addColumn<QStringList>("expectedCachedNames"); const auto allCollections = QStringList() << "42Task" << "43Task" << "44Note" << "45Stuff" << "46Note" << "47Task" << "48Note" << "49Stuff" << "50Stuff" << "51Task" << "52Note" << "53Stuff" << "54Task" << "55Task" << "56Task" << "57Note" << "58Note" << "59Note" << "60Stuff" << "61Stuff" << "62Stuff"; - const auto noteCollections = QStringList() << "42Task" << "44Note" - << "46Note" << "48Note" - << "50Stuff" << "52Note" - << "57Note" << "58Note" << "59Note"; - const auto taskCollections = QStringList() << "42Task" << "43Task" << "46Note" << "47Task" << "50Stuff" << "51Task" << "54Task" << "55Task" << "56Task"; - const auto noteTaskCollections = QStringList() << "42Task" << "43Task" << "44Note" - << "46Note" << "47Task" << "48Note" - << "50Stuff" << "51Task" << "52Note" - << "54Task" << "55Task" << "56Task" - << "57Note" << "58Note" << "59Note"; - QTest::newRow("rootRecursiveAll") << Akonadi::Collection::root() << Akonadi::StorageInterface::Recursive << int(Akonadi::StorageInterface::AllContent) << allCollections << allCollections; QTest::newRow("rootRecursiveTask") << Akonadi::Collection::root() << Akonadi::StorageInterface::Recursive << int(Akonadi::StorageInterface::Tasks) << onlyWithSuffix(taskCollections, "Task") << taskCollections; - QTest::newRow("rootRecursiveNote") << Akonadi::Collection::root() << Akonadi::StorageInterface::Recursive << int(Akonadi::StorageInterface::Notes) - << onlyWithSuffix(noteCollections, "Note") << noteCollections; - QTest::newRow("rootRecursiveNoteTask") << Akonadi::Collection::root() << Akonadi::StorageInterface::Recursive - << int(Akonadi::StorageInterface::Notes|Akonadi::StorageInterface::Tasks) - << onlyWithSuffixes(noteTaskCollections, {"Task", "Note"}) << noteTaskCollections; QTest::newRow("60RecursiveAll") << Akonadi::Collection(60) << Akonadi::StorageInterface::Recursive << int(Akonadi::StorageInterface::AllContent) << (QStringList() << "61Stuff" << "62Stuff") << allCollections; QTest::newRow("54RecursiveTask") << Akonadi::Collection(54) << Akonadi::StorageInterface::Recursive << int(Akonadi::StorageInterface::Tasks) << (QStringList() << "55Task" << "56Task") << taskCollections; - QTest::newRow("57RecursiveNote") << Akonadi::Collection(57) << Akonadi::StorageInterface::Recursive << int(Akonadi::StorageInterface::Notes) - << (QStringList() << "58Note" << "59Note") << noteCollections; - QTest::newRow("54RecursiveNoteTask") << Akonadi::Collection(54) << Akonadi::StorageInterface::Recursive - << int(Akonadi::StorageInterface::Notes|Akonadi::StorageInterface::Tasks) - << (QStringList() << "55Task" << "56Task") << noteTaskCollections; - QTest::newRow("57RecursiveNoteTask") << Akonadi::Collection(57) << Akonadi::StorageInterface::Recursive - << int(Akonadi::StorageInterface::Notes|Akonadi::StorageInterface::Tasks) - << (QStringList() << "58Note" << "59Note") << noteTaskCollections; QTest::newRow("60FirstLevelAll") << Akonadi::Collection(60) << Akonadi::StorageInterface::FirstLevel << int(Akonadi::StorageInterface::AllContent) << (QStringList() << "61Stuff") << allCollections; QTest::newRow("54FirstLevelTask") << Akonadi::Collection(54) << Akonadi::StorageInterface::FirstLevel << int(Akonadi::StorageInterface::Tasks) << (QStringList() << "55Task") << taskCollections; - QTest::newRow("57FirstLevelNote") << Akonadi::Collection(57) << Akonadi::StorageInterface::FirstLevel << int(Akonadi::StorageInterface::Notes) - << (QStringList() << "58Note") << noteCollections; - QTest::newRow("54FirstLevelNoteTask") << Akonadi::Collection(54) << Akonadi::StorageInterface::FirstLevel - << int(Akonadi::StorageInterface::Notes|Akonadi::StorageInterface::Tasks) - << (QStringList() << "55Task") << noteTaskCollections; - QTest::newRow("57FirstLevelNoteTask") << Akonadi::Collection(57) << Akonadi::StorageInterface::FirstLevel - << int(Akonadi::StorageInterface::Notes|Akonadi::StorageInterface::Tasks) - << (QStringList() << "58Note") << noteTaskCollections; QTest::newRow("60BaseAll") << Akonadi::Collection(60) << Akonadi::StorageInterface::Base << int(Akonadi::StorageInterface::AllContent) << (QStringList() << "60Stuff") << allCollections; QTest::newRow("54BaseTask") << Akonadi::Collection(54) << Akonadi::StorageInterface::Base << int(Akonadi::StorageInterface::Tasks) << (QStringList() << "54Task") << taskCollections; - QTest::newRow("57BaseNote") << Akonadi::Collection(57) << Akonadi::StorageInterface::Base << int(Akonadi::StorageInterface::Notes) - << (QStringList() << "57Note") << noteCollections; - QTest::newRow("54BaseNoteTask") << Akonadi::Collection(54) << Akonadi::StorageInterface::Base - << int(Akonadi::StorageInterface::Notes|Akonadi::StorageInterface::Tasks) - << (QStringList() << "54Task") << noteTaskCollections; - QTest::newRow("57BaseNoteTask") << Akonadi::Collection(57) << Akonadi::StorageInterface::Base - << int(Akonadi::StorageInterface::Notes|Akonadi::StorageInterface::Tasks) - << (QStringList() << "57Note") << noteTaskCollections; } void shouldCacheAllCollectionsPerFetchType() { // GIVEN AkonadiFakeData data; data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Task")).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43Task")).withParent(42).withTaskContent()); data.createCollection(GenCollection().withId(44).withName(QStringLiteral("44Note")).withParent(42).withNoteContent()); data.createCollection(GenCollection().withId(45).withName(QStringLiteral("45Stuff")).withParent(42)); data.createCollection(GenCollection().withId(46).withName(QStringLiteral("46Note")).withRootAsParent().withNoteContent()); data.createCollection(GenCollection().withId(47).withName(QStringLiteral("47Task")).withParent(46).withTaskContent()); data.createCollection(GenCollection().withId(48).withName(QStringLiteral("48Note")).withParent(46).withNoteContent()); data.createCollection(GenCollection().withId(49).withName(QStringLiteral("49Stuff")).withParent(46)); data.createCollection(GenCollection().withId(50).withName(QStringLiteral("50Stuff")).withRootAsParent()); data.createCollection(GenCollection().withId(51).withName(QStringLiteral("51Task")).withParent(50).withTaskContent()); data.createCollection(GenCollection().withId(52).withName(QStringLiteral("52Note")).withParent(50).withNoteContent()); data.createCollection(GenCollection().withId(53).withName(QStringLiteral("53Stuff")).withParent(50)); data.createCollection(GenCollection().withId(54).withName(QStringLiteral("54Task")).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(55).withName(QStringLiteral("55Task")).withParent(54).withTaskContent()); data.createCollection(GenCollection().withId(56).withName(QStringLiteral("56Task")).withParent(55).withTaskContent()); data.createCollection(GenCollection().withId(57).withName(QStringLiteral("57Note")).withRootAsParent().withNoteContent()); data.createCollection(GenCollection().withId(58).withName(QStringLiteral("58Note")).withParent(57).withNoteContent()); data.createCollection(GenCollection().withId(59).withName(QStringLiteral("59Note")).withParent(58).withNoteContent()); data.createCollection(GenCollection().withId(60).withName(QStringLiteral("60Stuff")).withRootAsParent()); data.createCollection(GenCollection().withId(61).withName(QStringLiteral("61Stuff")).withParent(60)); data.createCollection(GenCollection().withId(62).withName(QStringLiteral("62Stuff")).withParent(61)); auto cache = Akonadi::Cache::Ptr::create(Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor())); Akonadi::CachingStorage storage(cache, Akonadi::StorageInterface::Ptr(data.createStorage())); // WHEN QFETCH(Akonadi::Collection, rootCollection); QFETCH(Akonadi::StorageInterface::FetchDepth, fetchDepth); QFETCH(int, contentTypesInt); const auto contentTypes = Akonadi::StorageInterface::FetchContentTypes(contentTypesInt); auto job = storage.fetchCollections(rootCollection, fetchDepth, contentTypes); QVERIFY2(job->kjob()->exec(), qPrintable(job->kjob()->errorString())); // THEN const auto toCollectionNames = [](const Akonadi::Collection::List &collections) { auto res = QStringList(); res.reserve(collections.size()); std::transform(collections.cbegin(), collections.cend(), std::back_inserter(res), std::mem_fn(&Akonadi::Collection::name)); res.sort(); return res; }; QFETCH(QStringList, expectedFetchNames); QFETCH(QStringList, expectedCachedNames); { const auto collectionFetchNames = toCollectionNames(job->collections()); QCOMPARE(collectionFetchNames, expectedFetchNames); const auto collections = cache->collections(Akonadi::StorageInterface::AllContent); const auto collectionCachedNames = toCollectionNames(collections); QCOMPARE(collectionCachedNames, expectedCachedNames); } // WHEN (second time shouldn't hit the original storage) data.storageBehavior().setFetchCollectionsBehavior(rootCollection.id(), AkonadiFakeStorageBehavior::EmptyFetch); data.storageBehavior().setFetchCollectionsErrorCode(rootCollection.id(), 128); job = storage.fetchCollections(rootCollection, fetchDepth, contentTypes); QVERIFY2(job->kjob()->exec(), qPrintable(job->kjob()->errorString())); { const auto collectionFetchNames = toCollectionNames(job->collections()); QCOMPARE(collectionFetchNames, expectedFetchNames); const auto collections = cache->collections(Akonadi::StorageInterface::AllContent); const auto collectionCachedNames = toCollectionNames(collections); QCOMPARE(collectionCachedNames, expectedCachedNames); } } void shouldCacheAllItemsPerCollection() { // GIVEN AkonadiFakeData data; data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Col")).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43Col")).withRootAsParent().withTaskContent()); data.createItem(GenTodo().withId(42).withTitle(QStringLiteral("42Task")).withParent(42)); data.createItem(GenTodo().withId(45).withTitle(QStringLiteral("45Task")).withParent(42)); data.createItem(GenTodo().withId(52).withTitle(QStringLiteral("52Task")).withParent(42)); data.createItem(GenTodo().withId(44).withTitle(QStringLiteral("44Task")).withParent(43)); data.createItem(GenTodo().withId(48).withTitle(QStringLiteral("48Task")).withParent(43)); data.createItem(GenTodo().withId(50).withTitle(QStringLiteral("50Task")).withParent(43)); auto cache = Akonadi::Cache::Ptr::create(Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor())); Akonadi::CachingStorage storage(cache, Akonadi::StorageInterface::Ptr(data.createStorage())); // WHEN auto job = storage.fetchItems(Akonadi::Collection(42)); QVERIFY2(job->kjob()->exec(), qPrintable(job->kjob()->errorString())); // THEN const auto toItemIds = [](const Akonadi::Item::List &items) { auto res = QVector<Akonadi::Item::Id>(); res.reserve(items.size()); std::transform(items.cbegin(), items.cend(), std::back_inserter(res), std::mem_fn(&Akonadi::Item::id)); std::sort(res.begin(), res.end()); return res; }; auto expectedIds = QVector<Akonadi::Item::Id>() << 42 << 45 << 52; { const auto itemFetchIds = toItemIds(job->items()); QCOMPARE(itemFetchIds, expectedIds); const auto items = cache->items(Akonadi::Collection(42)); const auto itemCachedIds = toItemIds(items); QCOMPARE(itemCachedIds, expectedIds); } // WHEN (second time shouldn't hit the original storage) data.storageBehavior().setFetchItemsBehavior(42, AkonadiFakeStorageBehavior::EmptyFetch); data.storageBehavior().setFetchItemsErrorCode(42, 128); job = storage.fetchItems(Akonadi::Collection(42)); QVERIFY2(job->kjob()->exec(), qPrintable(job->kjob()->errorString())); { const auto itemFetchIds = toItemIds(job->items()); QCOMPARE(itemFetchIds, expectedIds); const auto items = cache->items(Akonadi::Collection(42)); const auto itemCachedIds = toItemIds(items); QCOMPARE(itemCachedIds, expectedIds); } } void shouldCacheSingleItems() { // GIVEN AkonadiFakeData data; data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Col")).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43Col")).withRootAsParent().withTaskContent()); data.createItem(GenTodo().withId(42).withTitle(QStringLiteral("42Task")).withParent(42)); data.createItem(GenTodo().withId(45).withTitle(QStringLiteral("45Task")).withParent(42)); data.createItem(GenTodo().withId(52).withTitle(QStringLiteral("52Task")).withParent(42)); data.createItem(GenTodo().withId(44).withTitle(QStringLiteral("44Task")).withParent(43)); data.createItem(GenTodo().withId(48).withTitle(QStringLiteral("48Task")).withParent(43)); data.createItem(GenTodo().withId(50).withTitle(QStringLiteral("50Task")).withParent(43)); auto cache = Akonadi::Cache::Ptr::create(Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor())); Akonadi::CachingStorage storage(cache, Akonadi::StorageInterface::Ptr(data.createStorage())); // WHEN auto job = storage.fetchItem(Akonadi::Item(44)); QVERIFY2(job->kjob()->exec(), qPrintable(job->kjob()->errorString())); // THEN const auto toItemIds = [](const Akonadi::Item::List &items) { auto res = QVector<Akonadi::Item::Id>(); res.reserve(items.size()); std::transform(items.cbegin(), items.cend(), std::back_inserter(res), std::mem_fn(&Akonadi::Item::id)); std::sort(res.begin(), res.end()); return res; }; auto expectedIds = QVector<Akonadi::Item::Id>() << 44; { const auto itemFetchIds = toItemIds(job->items()); QCOMPARE(itemFetchIds, expectedIds); QVERIFY(!cache->item(44).isValid()); } // WHEN (if collection is populated, shouldn't hit the original storage) job = storage.fetchItems(Akonadi::Collection(43)); QVERIFY2(job->kjob()->exec(), qPrintable(job->kjob()->errorString())); data.storageBehavior().setFetchItemBehavior(44, AkonadiFakeStorageBehavior::EmptyFetch); data.storageBehavior().setFetchItemErrorCode(44, 128); job = storage.fetchItem(Akonadi::Item(44)); QVERIFY2(job->kjob()->exec(), qPrintable(job->kjob()->errorString())); { const auto itemFetchIds = toItemIds(job->items()); QCOMPARE(itemFetchIds, expectedIds); QVERIFY(cache->item(44).isValid()); } } void shouldCacheAllItemsPerTag() { // GIVEN AkonadiFakeData data; data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Col")).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43Col")).withRootAsParent().withTaskContent()); data.createTag(GenTag().withId(42).withName(QStringLiteral("42Plain")).asPlain()); data.createTag(GenTag().withId(43).withName(QStringLiteral("43Context")).asContext()); data.createItem(GenTodo().withId(42).withTitle(QStringLiteral("42Task")).withParent(42).withTags({42})); data.createItem(GenTodo().withId(45).withTitle(QStringLiteral("45Task")).withParent(42).withTags({42, 43})); data.createItem(GenTodo().withId(52).withTitle(QStringLiteral("52Task")).withParent(42).withTags({43})); data.createItem(GenTodo().withId(44).withTitle(QStringLiteral("44Task")).withParent(43).withTags({42})); data.createItem(GenTodo().withId(48).withTitle(QStringLiteral("48Task")).withParent(43).withTags({42, 43})); data.createItem(GenTodo().withId(50).withTitle(QStringLiteral("50Task")).withParent(43).withTags({43})); auto cache = Akonadi::Cache::Ptr::create(Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor())); Akonadi::CachingStorage storage(cache, Akonadi::StorageInterface::Ptr(data.createStorage())); // WHEN auto job = storage.fetchTagItems(Akonadi::Tag(43)); QVERIFY2(job->kjob()->exec(), qPrintable(job->kjob()->errorString())); // THEN const auto toItemIds = [](const Akonadi::Item::List &items) { auto res = QVector<Akonadi::Item::Id>(); res.reserve(items.size()); std::transform(items.cbegin(), items.cend(), std::back_inserter(res), std::mem_fn(&Akonadi::Item::id)); std::sort(res.begin(), res.end()); return res; }; auto expectedIds = QVector<Akonadi::Item::Id>() << 45 << 48 << 50 << 52; { const auto itemFetchIds = toItemIds(job->items()); QCOMPARE(itemFetchIds, expectedIds); const auto items = cache->items(Akonadi::Tag(43)); const auto itemCachedIds = toItemIds(items); QCOMPARE(itemCachedIds, expectedIds); } // WHEN (second time shouldn't hit the original storage) data.storageBehavior().setFetchTagItemsBehavior(43, AkonadiFakeStorageBehavior::EmptyFetch); data.storageBehavior().setFetchTagItemsErrorCode(43, 128); job = storage.fetchTagItems(Akonadi::Tag(43)); QVERIFY2(job->kjob()->exec(), qPrintable(job->kjob()->errorString())); { const auto itemFetchIds = toItemIds(job->items()); QCOMPARE(itemFetchIds, expectedIds); const auto items = cache->items(Akonadi::Tag(43)); const auto itemCachedIds = toItemIds(items); QCOMPARE(itemCachedIds, expectedIds); } } void shouldCacheTags() { // GIVEN AkonadiFakeData data; data.createTag(GenTag().withId(42).withName(QStringLiteral("42Plain")).asPlain()); data.createTag(GenTag().withId(43).withName(QStringLiteral("43Context")).asContext()); data.createTag(GenTag().withId(44).withName(QStringLiteral("44Plain")).asPlain()); auto cache = Akonadi::Cache::Ptr::create(Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor())); Akonadi::CachingStorage storage(cache, Akonadi::StorageInterface::Ptr(data.createStorage())); // WHEN auto job = storage.fetchTags(); QVERIFY2(job->kjob()->exec(), qPrintable(job->kjob()->errorString())); // THEN const auto toTagNames = [](const Akonadi::Tag::List &tags) { auto res = QStringList(); res.reserve(tags.size()); std::transform(tags.cbegin(), tags.cend(), std::back_inserter(res), std::mem_fn(&Akonadi::Tag::name)); res.sort(); return res; }; auto expectedNames = QStringList() << "42Plain" << "43Context" << "44Plain"; { const auto tagFetchNames = toTagNames(job->tags()); QCOMPARE(tagFetchNames, expectedNames); const auto tags = cache->tags(); const auto tagCachedNames = toTagNames(tags); QCOMPARE(tagCachedNames, expectedNames); } // WHEN (second time shouldn't hit the original storage) data.storageBehavior().setFetchTagsBehavior(AkonadiFakeStorageBehavior::EmptyFetch); data.storageBehavior().setFetchTagsErrorCode(128); job = storage.fetchTags(); QVERIFY2(job->kjob()->exec(), qPrintable(job->kjob()->errorString())); { const auto tagFetchNames = toTagNames(job->tags()); QCOMPARE(tagFetchNames, expectedNames); const auto tags = cache->tags(); const auto tagCachedNames = toTagNames(tags); QCOMPARE(tagCachedNames, expectedNames); } } }; ZANSHIN_TEST_MAIN(AkonadiCachingStorageTest) #include "akonadicachingstoragetest.moc" diff --git a/tests/units/akonadi/akonadidatasourcequeriestest.cpp b/tests/units/akonadi/akonadidatasourcequeriestest.cpp index 883133f8..b2429167 100644 --- a/tests/units/akonadi/akonadidatasourcequeriestest.cpp +++ b/tests/units/akonadi/akonadidatasourcequeriestest.cpp @@ -1,979 +1,956 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens <ervin@kde.org> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include <testlib/qtest_zanshin.h> #include "akonadi/akonadidatasourcequeries.h" #include "akonadi/akonadiserializer.h" #include "akonadi/akonadistoragesettings.h" #include "utils/jobhandler.h" #include "utils/mem_fn.h" #include "testlib/akonadifakedata.h" #include "testlib/gencollection.h" #include "testlib/gentodo.h" #include "testlib/testhelpers.h" using namespace Testlib; typedef std::function<Domain::QueryResult<Domain::DataSource::Ptr>::Ptr(Domain::DataSourceQueries*)> QueryFunction; Q_DECLARE_METATYPE(QueryFunction) typedef std::function<void(Akonadi::StorageSettings *, const Akonadi::Collection &)> SetDefaultCollectionFunction; Q_DECLARE_METATYPE(SetDefaultCollectionFunction) typedef std::function<Akonadi::Collection(Akonadi::StorageSettings *)> GetDefaultCollectionFunction; Q_DECLARE_METATYPE(GetDefaultCollectionFunction) Q_DECLARE_METATYPE(Akonadi::StorageInterface::FetchContentType) class AkonadiDataSourceQueriesTest : public QObject { Q_OBJECT public: explicit AkonadiDataSourceQueriesTest(QObject *parent = Q_NULLPTR) : QObject(parent) { qRegisterMetaType<QueryFunction>(); } private slots: void shouldCheckIfASourceIsDefaultFromSettings_data() { QTest::addColumn<Akonadi::StorageInterface::FetchContentType>("contentType"); QTest::addColumn<SetDefaultCollectionFunction>("setDefaultCollection"); QTest::addColumn<GetDefaultCollectionFunction>("getDefaultCollection"); { SetDefaultCollectionFunction setFunction = Utils::mem_fn(&Akonadi::StorageSettings::setDefaultNoteCollection); GetDefaultCollectionFunction getFunction = Utils::mem_fn(&Akonadi::StorageSettings::defaultNoteCollection); QTest::newRow("notes") << Akonadi::StorageInterface::Notes << setFunction << getFunction; } { SetDefaultCollectionFunction setFunction = Utils::mem_fn(&Akonadi::StorageSettings::setDefaultTaskCollection); GetDefaultCollectionFunction getFunction = Utils::mem_fn(&Akonadi::StorageSettings::defaultTaskCollection); QTest::newRow("tasks") << Akonadi::StorageInterface::Tasks << setFunction << getFunction; } } void shouldCheckIfASourceIsDefaultFromSettings() { // GIVEN const auto minId = qint64(42); const auto maxId = qint64(44); const auto defaultId = qint64(43); QFETCH(Akonadi::StorageInterface::FetchContentType, contentType); QFETCH(SetDefaultCollectionFunction, setDefaultCollection); // A default collection for saving setDefaultCollection(&Akonadi::StorageSettings::instance(), Akonadi::Collection(defaultId)); // A few data sources AkonadiFakeData data; for (auto id = minId; id <= maxId; ++id) { auto col = GenCollection().withId(id).withName(QString::number(id)).withRootAsParent(); if (contentType == Akonadi::StorageInterface::Tasks) data.createCollection(col.withTaskContent()); else data.createCollection(col.withTaskContent()); } // WHEN auto serializer = Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer); QScopedPointer<Domain::DataSourceQueries> queries(new Akonadi::DataSourceQueries(contentType, Akonadi::StorageInterface::Ptr(data.createStorage()), serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()))); // THEN for (auto id = minId; id <= maxId; ++id) { auto source = serializer->createDataSourceFromCollection(data.collection(id), Akonadi::SerializerInterface::BaseName); QCOMPARE(queries->isDefaultSource(source), id == defaultId); } } void shouldStoreDefaultSourceInTheSettingsAndNotify_data() { shouldCheckIfASourceIsDefaultFromSettings_data(); } void shouldStoreDefaultSourceInTheSettingsAndNotify() { // GIVEN const auto minId = qint64(42); const auto maxId = qint64(44); const auto defaultId = qint64(43); QFETCH(Akonadi::StorageInterface::FetchContentType, contentType); QFETCH(SetDefaultCollectionFunction, setDefaultCollection); // A default collection for saving setDefaultCollection(&Akonadi::StorageSettings::instance(), Akonadi::Collection(minId)); // A few data sources AkonadiFakeData data; for (auto id = minId; id <= maxId; ++id) { auto col = GenCollection().withId(id).withName(QString::number(id)).withRootAsParent(); if (contentType == Akonadi::StorageInterface::Tasks) data.createCollection(col.withTaskContent()); else data.createCollection(col.withTaskContent()); } // WHEN auto serializer = Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer); QScopedPointer<Domain::DataSourceQueries> queries(new Akonadi::DataSourceQueries(contentType, Akonadi::StorageInterface::Ptr(data.createStorage()), serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()))); QSignalSpy spy(queries->notifier(), &Domain::DataSourceQueriesNotifier::defaultSourceChanged); auto defaultSource = serializer->createDataSourceFromCollection(data.collection(defaultId), Akonadi::SerializerInterface::BaseName); queries->setDefaultSource(defaultSource); // THEN QFETCH(GetDefaultCollectionFunction, getDefaultCollection); QCOMPARE(getDefaultCollection(&Akonadi::StorageSettings::instance()).id(), defaultId); QCOMPARE(spy.count(), 1); } void shouldLookInAllReportedForTopLevelSources_data() { QTest::addColumn<int>("contentTypes"); QTest::addColumn<QStringList>("expectedNames"); auto expectedNames = QStringList(); expectedNames << QStringLiteral("42Task") << QStringLiteral("44Note"); QTest::newRow("tasks and notes") << int(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes) << expectedNames; expectedNames.clear(); expectedNames << QStringLiteral("42Task"); QTest::newRow("tasks") << int(Akonadi::StorageInterface::Tasks) << expectedNames; expectedNames.clear(); expectedNames << QStringLiteral("44Note"); QTest::newRow("notes") << int(Akonadi::StorageInterface::Notes) << expectedNames; } void shouldLookInAllReportedForTopLevelSources() { // GIVEN AkonadiFakeData data; // Two top level collections, one with tasks, one with notes data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Task")).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(44).withName(QStringLiteral("44Note")).withRootAsParent().withNoteContent()); // One with a note child collection data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43TaskChild")).withParent(42).withTaskContent()); // One with a task child collection data.createCollection(GenCollection().withId(45).withName(QStringLiteral("45NoteChild")).withParent(44).withNoteContent()); // WHEN QFETCH(int, contentTypes); QScopedPointer<Domain::DataSourceQueries> queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::FetchContentTypes(contentTypes), Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()))); Domain::QueryResult<Domain::DataSource::Ptr>::Ptr result = queries->findTopLevel(); result->data(); result = queries->findTopLevel(); // Should not cause any problem or wrong data // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); const auto sources = result->data(); auto actualNames = QStringList(); std::transform(sources.constBegin(), sources.constEnd(), std::back_inserter(actualNames), [] (const Domain::DataSource::Ptr &source) { return source->name(); }); actualNames.sort(); QFETCH(QStringList, expectedNames); expectedNames.sort(); QCOMPARE(actualNames, expectedNames); } void shouldReactToCollectionAddsForTopLevelSources() { // GIVEN AkonadiFakeData data; QScopedPointer<Domain::DataSourceQueries> queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes, Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()))); Domain::QueryResult<Domain::DataSource::Ptr>::Ptr result = queries->findTopLevel(); TestHelpers::waitForEmptyJobQueue(); QVERIFY(result->data().isEmpty()); // WHEN data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Task")).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43Note")).withRootAsParent().withNoteContent()); // THEN QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42Task")); QCOMPARE(result->data().at(1)->name(), QStringLiteral("43Note")); } void shouldReactToCollectionRemovesForTopLevelSources() { // GIVEN AkonadiFakeData data; // Two top level collections and two child collections data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Task")).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43Note")).withRootAsParent().withNoteContent()); data.createCollection(GenCollection().withId(44).withName(QStringLiteral("43TaskChild")).withParent(42).withTaskContent()); data.createCollection(GenCollection().withId(45).withName(QStringLiteral("43NoteChild")).withParent(43).withNoteContent()); QScopedPointer<Domain::DataSourceQueries> queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes, Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()))); Domain::QueryResult<Domain::DataSource::Ptr>::Ptr result = queries->findTopLevel(); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); // WHEN data.removeCollection(Akonadi::Collection(42)); data.removeCollection(Akonadi::Collection(43)); // THEN QCOMPARE(result->data().size(), 0); } void shouldReactToItemChangesForTopLevelTasks() { // GIVEN AkonadiFakeData data; // Two top level collections and one child collection data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Task")).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43Note")).withRootAsParent().withNoteContent()); data.createCollection(GenCollection().withId(44).withName(QStringLiteral("44NoteChild")).withParent(43).withNoteContent()); QScopedPointer<Domain::DataSourceQueries> queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes, Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()))); Domain::QueryResult<Domain::DataSource::Ptr>::Ptr result = queries->findTopLevel(); bool replaceHandlerCalled = false; result->addPostReplaceHandler([&replaceHandlerCalled](const Domain::DataSource::Ptr &, int) { replaceHandlerCalled = true; }); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); // WHEN data.modifyCollection(GenCollection(data.collection(42)).withName(QStringLiteral("42TaskBis"))); data.modifyCollection(GenCollection(data.collection(43)).withName(QStringLiteral("43NoteBis"))); TestHelpers::waitForEmptyJobQueue(); // THEN QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42TaskBis")); QCOMPARE(result->data().at(1)->name(), QStringLiteral("43NoteBis")); QVERIFY(replaceHandlerCalled); } void shouldNotCrashDuringFindTopLevelWhenFetchJobFailedOrEmpty_data() { QTest::addColumn<int>("colErrorCode"); QTest::addColumn<int>("colFetchBehavior"); QTest::addColumn<bool>("deleteQuery"); QTest::newRow("No error with empty collection list") << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << false; QTest::newRow("No error with empty collection list (+ query delete)") << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << true; QTest::newRow("Error with empty collection list") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << false; QTest::newRow("Error with empty collection list (+ query delete)") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << true; QTest::newRow("Error with collection list") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::NormalFetch) << false; QTest::newRow("Error with collection list (+ query delete)") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::NormalFetch) << true; } void shouldNotCrashDuringFindTopLevelWhenFetchJobFailedOrEmpty() { // GIVEN AkonadiFakeData data; // Two top level collections data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withRootAsParent().withNoteContent()); QScopedPointer<Domain::DataSourceQueries> queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes, Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()))); QFETCH(int, colErrorCode); QFETCH(int, colFetchBehavior); data.storageBehavior().setFetchCollectionsErrorCode(Akonadi::Collection::root().id(), colErrorCode); data.storageBehavior().setFetchCollectionsBehavior(Akonadi::Collection::root().id(), AkonadiFakeStorageBehavior::FetchBehavior(colFetchBehavior)); QFETCH(bool, deleteQuery); // WHEN Domain::QueryResult<Domain::DataSource::Ptr>::Ptr result = queries->findTopLevel(); if (deleteQuery) delete queries.take(); // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 0); } void shouldLookInAllReportedForChildSources_data() { QTest::addColumn<int>("contentTypes"); QTest::addColumn<QStringList>("expectedNames"); auto expectedNames = QStringList(); expectedNames << QStringLiteral("43TaskFirstChild") << QStringLiteral("45NoteSecondChild"); QTest::newRow("tasks and notes") << int(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes) << expectedNames; expectedNames.clear(); expectedNames << QStringLiteral("43TaskFirstChild"); QTest::newRow("tasks") << int(Akonadi::StorageInterface::Tasks) << expectedNames; expectedNames.clear(); expectedNames << QStringLiteral("45NoteSecondChild"); QTest::newRow("notes") << int(Akonadi::StorageInterface::Notes) << expectedNames; } void shouldLookInAllReportedForChildSources() { // GIVEN AkonadiFakeData data; // One top level collection with two children (one of them also having a child) data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Task")).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43TaskFirstChild")).withParent(42).withTaskContent()); data.createCollection(GenCollection().withId(44).withName(QStringLiteral("44TaskFirstChildChild")).withParent(43).withTaskContent()); data.createCollection(GenCollection().withId(45).withName(QStringLiteral("45NoteSecondChild")).withParent(42).withNoteContent()); // Serializer auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); Domain::DataSource::Ptr topLevelDataSource = serializer->createDataSourceFromCollection(data.collection(42), Akonadi::SerializerInterface::BaseName); // WHEN QFETCH(int, contentTypes); QScopedPointer<Domain::DataSourceQueries> queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::FetchContentTypes(contentTypes), Akonadi::StorageInterface::Ptr(data.createStorage()), serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()))); Domain::QueryResult<Domain::DataSource::Ptr>::Ptr result = queries->findChildren(topLevelDataSource); result->data(); result = queries->findChildren(topLevelDataSource); // Should not cause any problem or wrong data // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); const auto sources = result->data(); auto actualNames = QStringList(); std::transform(sources.constBegin(), sources.constEnd(), std::back_inserter(actualNames), [] (const Domain::DataSource::Ptr &source) { return source->name(); }); actualNames.sort(); QFETCH(QStringList, expectedNames); expectedNames.sort(); QCOMPARE(actualNames, expectedNames); } void shouldReactToCollectionAddsForChildSources() { // GIVEN AkonadiFakeData data; // One top level collection with no child yet data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Task")).withRootAsParent().withTaskContent()); // Serializer auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); Domain::DataSource::Ptr topLevelDataSource = serializer->createDataSourceFromCollection(data.collection(42), Akonadi::SerializerInterface::BaseName); QScopedPointer<Domain::DataSourceQueries> queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes, Akonadi::StorageInterface::Ptr(data.createStorage()), serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()))); Domain::QueryResult<Domain::DataSource::Ptr>::Ptr result = queries->findChildren(topLevelDataSource); result->data(); result = queries->findChildren(topLevelDataSource); // Should not cause any problem or wrong data TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 0); // WHEN data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43TaskChild")).withParent(42).withTaskContent()); // THEN QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().first()->name(), QStringLiteral("43TaskChild")); } void shouldReactToCollectionRemovesForChildSources() { // GIVEN AkonadiFakeData data; // One top level collection with two children data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Task")).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43TaskFirstChild")).withParent(42).withTaskContent()); data.createCollection(GenCollection().withId(44).withName(QStringLiteral("45NoteSecondChild")).withParent(42).withNoteContent()); // Serializer auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); Domain::DataSource::Ptr topLevelDataSource = serializer->createDataSourceFromCollection(data.collection(42), Akonadi::SerializerInterface::BaseName); QScopedPointer<Domain::DataSourceQueries> queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes, Akonadi::StorageInterface::Ptr(data.createStorage()), serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()))); Domain::QueryResult<Domain::DataSource::Ptr>::Ptr result = queries->findChildren(topLevelDataSource); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); // WHEN data.removeCollection(Akonadi::Collection(44)); // THEN QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().first()->name(), QStringLiteral("43TaskFirstChild")); } void shouldReactToCollectionChangesForChildSources() { // GIVEN AkonadiFakeData data; // One top level collection with two children data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Task")).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43TaskFirstChild")).withParent(42).withTaskContent()); data.createCollection(GenCollection().withId(44).withName(QStringLiteral("44NoteSecondChild")).withParent(42).withNoteContent()); // Serializer auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); Domain::DataSource::Ptr topLevelDataSource = serializer->createDataSourceFromCollection(data.collection(42), Akonadi::SerializerInterface::BaseName); QScopedPointer<Domain::DataSourceQueries> queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes, Akonadi::StorageInterface::Ptr(data.createStorage()), serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()))); Domain::QueryResult<Domain::DataSource::Ptr>::Ptr result = queries->findChildren(topLevelDataSource); bool replaceHandlerCalled = false; result->addPostReplaceHandler([&replaceHandlerCalled](const Domain::DataSource::Ptr &, int) { replaceHandlerCalled = true; }); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); // WHEN data.modifyCollection(GenCollection(data.collection(43)).withName(QStringLiteral("43TaskFirstChildBis"))); // THEN TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().first()->name(), QStringLiteral("43TaskFirstChildBis")); QCOMPARE(result->data().at(1)->name(), QStringLiteral("44NoteSecondChild")); QVERIFY(replaceHandlerCalled); } void shouldNotCrashDuringFindChildrenWhenFetchJobFailedOrEmpty_data() { QTest::addColumn<int>("colErrorCode"); QTest::addColumn<int>("colFetchBehavior"); QTest::addColumn<bool>("deleteQuery"); QTest::newRow("No error with empty collection list") << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << false; QTest::newRow("No error with empty collection list (+ query delete)") << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << true; QTest::newRow("Error with empty collection list") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << false; QTest::newRow("Error with empty collection list (+ query delete)") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << true; QTest::newRow("Error with collection list") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::NormalFetch) << false; QTest::newRow("Error with collection list (+ query delete)") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::NormalFetch) << true; } void shouldNotCrashDuringFindChildrenWhenFetchJobFailedOrEmpty() { // GIVEN AkonadiFakeData data; // One top level collection with two children data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Task")).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43TaskFirstChild")).withParent(42).withTaskContent()); data.createCollection(GenCollection().withId(44).withName(QStringLiteral("44NoteSecondChild")).withParent(42).withNoteContent()); // Serializer auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); Domain::DataSource::Ptr topLevelDataSource = serializer->createDataSourceFromCollection(data.collection(42), Akonadi::SerializerInterface::BaseName); QFETCH(bool, deleteQuery); QFETCH(int, colErrorCode); QFETCH(int, colFetchBehavior); data.storageBehavior().setFetchCollectionsErrorCode(data.collection(42).id(), colErrorCode); data.storageBehavior().setFetchCollectionsBehavior(data.collection(42).id(), AkonadiFakeStorageBehavior::FetchBehavior(colFetchBehavior)); // WHEN QScopedPointer<Domain::DataSourceQueries> queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes, Akonadi::StorageInterface::Ptr(data.createStorage()), serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()))); Domain::QueryResult<Domain::DataSource::Ptr>::Ptr result = queries->findChildren(topLevelDataSource); if (deleteQuery) delete queries.take(); // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 0); } - void shouldLookInAllReportedForSelectedSources_data() - { - QTest::addColumn<int>("contentTypes"); - QTest::addColumn<QStringList>("expectedNames"); - - auto expectedNames = QStringList(); - expectedNames << QStringLiteral("42Task » 43Task") << QStringLiteral("44Note » 45Note"); - QTest::newRow("tasks and notes") << int(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes) << expectedNames; - - expectedNames.clear(); - expectedNames << QStringLiteral("42Task » 43Task"); - QTest::newRow("tasks") << int(Akonadi::StorageInterface::Tasks) << expectedNames; - - expectedNames.clear(); - expectedNames << QStringLiteral("44Note » 45Note"); - QTest::newRow("notes") << int(Akonadi::StorageInterface::Notes) << expectedNames; - } - void shouldLookInAllReportedForSelectedSources() { // GIVEN AkonadiFakeData data; // Two top level collections, one with tasks, one with notes and two child collections data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Task")).withRootAsParent().withTaskContent().selected(false)); data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43Task")).withParent(42).withTaskContent().selected(true)); data.createCollection(GenCollection().withId(44).withName(QStringLiteral("44Note")).withRootAsParent().withNoteContent().selected(false)); data.createCollection(GenCollection().withId(45).withName(QStringLiteral("45Note")).withParent(44).withNoteContent().selected(true)); // WHEN - QFETCH(int, contentTypes); - QScopedPointer<Domain::DataSourceQueries> queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::FetchContentTypes(contentTypes), + QScopedPointer<Domain::DataSourceQueries> queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Tasks, Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()))); Domain::QueryResult<Domain::DataSource::Ptr>::Ptr result = queries->findAllSelected(); result->data(); result = queries->findAllSelected(); // Should not cause any problem or wrong data // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); const auto sources = result->data(); auto actualNames = QStringList(); std::transform(sources.constBegin(), sources.constEnd(), std::back_inserter(actualNames), [] (const Domain::DataSource::Ptr &source) { return source->name(); }); actualNames.sort(); - QFETCH(QStringList, expectedNames); - expectedNames.sort(); - QCOMPARE(actualNames, expectedNames); + QCOMPARE(actualNames, QStringList() << QStringLiteral("42Task » 43Task")); } void shouldReactToCollectionAddsForSelectedSources() { // GIVEN AkonadiFakeData data; - QScopedPointer<Domain::DataSourceQueries> queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes, + QScopedPointer<Domain::DataSourceQueries> queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Tasks, Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()))); Domain::QueryResult<Domain::DataSource::Ptr>::Ptr result = queries->findAllSelected(); TestHelpers::waitForEmptyJobQueue(); QVERIFY(result->data().isEmpty()); // WHEN data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Task")).withRootAsParent().withTaskContent().selected(false)); data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43Task")).withParent(42).withTaskContent().selected(true)); data.createCollection(GenCollection().withId(44).withName(QStringLiteral("44Note")).withRootAsParent().withNoteContent().selected(false)); data.createCollection(GenCollection().withId(45).withName(QStringLiteral("45Note")).withParent(44).withNoteContent().selected(true)); // THEN - QCOMPARE(result->data().size(), 2); + QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42Task » 43Task")); - QCOMPARE(result->data().at(1)->name(), QStringLiteral("44Note » 45Note")); } void shouldReactToCollectionRemovesForSelectedSources() { // GIVEN AkonadiFakeData data; // Two top level collections and two child collections data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Task")).withRootAsParent().withTaskContent().selected(false)); data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43Task")).withParent(42).withTaskContent().selected(true)); data.createCollection(GenCollection().withId(44).withName(QStringLiteral("44Note")).withRootAsParent().withNoteContent().selected(false)); data.createCollection(GenCollection().withId(45).withName(QStringLiteral("45Note")).withParent(44).withNoteContent().selected(true)); - QScopedPointer<Domain::DataSourceQueries> queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes, + QScopedPointer<Domain::DataSourceQueries> queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Tasks, Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()))); Domain::QueryResult<Domain::DataSource::Ptr>::Ptr result = queries->findAllSelected(); TestHelpers::waitForEmptyJobQueue(); - QCOMPARE(result->data().size(), 2); + QCOMPARE(result->data().size(), 1); // WHEN data.removeCollection(Akonadi::Collection(43)); data.removeCollection(Akonadi::Collection(45)); // THEN QCOMPARE(result->data().size(), 0); } void shouldReactToCollectionChangesForSelectedSources() { // GIVEN AkonadiFakeData data; // Two top level collections and one child collection data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Task")).withRootAsParent().withTaskContent().selected(false)); data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43Task")).withParent(42).withTaskContent().selected(true)); data.createCollection(GenCollection().withId(44).withName(QStringLiteral("44Note")).withRootAsParent().withNoteContent().selected(false)); data.createCollection(GenCollection().withId(45).withName(QStringLiteral("45Note")).withParent(44).withNoteContent().selected(true)); - QScopedPointer<Domain::DataSourceQueries> queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes, + QScopedPointer<Domain::DataSourceQueries> queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Tasks, Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()))); Domain::QueryResult<Domain::DataSource::Ptr>::Ptr result = queries->findAllSelected(); bool replaceHandlerCalled = false; result->addPostReplaceHandler([&replaceHandlerCalled](const Domain::DataSource::Ptr &, int) { replaceHandlerCalled = true; }); TestHelpers::waitForEmptyJobQueue(); - QCOMPARE(result->data().size(), 2); + QCOMPARE(result->data().size(), 1); // WHEN data.modifyCollection(GenCollection(data.collection(43)).withName(QStringLiteral("43TaskBis"))); data.modifyCollection(GenCollection(data.collection(45)).withName(QStringLiteral("45NoteBis"))); TestHelpers::waitForEmptyJobQueue(); // THEN - QCOMPARE(result->data().size(), 2); + QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42Task » 43TaskBis")); - QCOMPARE(result->data().at(1)->name(), QStringLiteral("44Note » 45NoteBis")); QVERIFY(replaceHandlerCalled); } void shouldNotCrashDuringFindAllSelectedWhenFetchJobFailedOrEmpty_data() { QTest::addColumn<int>("colErrorCode"); QTest::addColumn<int>("colFetchBehavior"); QTest::addColumn<bool>("deleteQuery"); QTest::newRow("No error with empty collection list") << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << false; QTest::newRow("No error with empty collection list (+ query delete)") << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << true; QTest::newRow("Error with empty collection list") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << false; QTest::newRow("Error with empty collection list (+ query delete)") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << true; QTest::newRow("Error with collection list") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::NormalFetch) << false; QTest::newRow("Error with collection list (+ query delete)") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::NormalFetch) << true; } void shouldNotCrashDuringFindAllSelectedWhenFetchJobFailedOrEmpty() { // GIVEN AkonadiFakeData data; // Two top level collections and two child collections data.createCollection(GenCollection().withId(42).withName(QStringLiteral("42Task")).withRootAsParent().withTaskContent().selected(false)); data.createCollection(GenCollection().withId(43).withName(QStringLiteral("43Task")).withParent(42).withTaskContent().selected(true)); data.createCollection(GenCollection().withId(44).withName(QStringLiteral("44Note")).withRootAsParent().withNoteContent().selected(false)); data.createCollection(GenCollection().withId(45).withName(QStringLiteral("45Note")).withParent(44).withNoteContent().selected(true)); QScopedPointer<Domain::DataSourceQueries> queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes, Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()))); QFETCH(int, colErrorCode); QFETCH(int, colFetchBehavior); data.storageBehavior().setFetchCollectionsErrorCode(Akonadi::Collection::root().id(), colErrorCode); data.storageBehavior().setFetchCollectionsBehavior(Akonadi::Collection::root().id(), AkonadiFakeStorageBehavior::FetchBehavior(colFetchBehavior)); QFETCH(bool, deleteQuery); // WHEN Domain::QueryResult<Domain::DataSource::Ptr>::Ptr result = queries->findAllSelected(); if (deleteQuery) delete queries.take(); // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 0); } void shouldLookInCollectionForProjects() { // GIVEN AkonadiFakeData data; // Two top level collections data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withRootAsParent().withTaskContent()); // One project in the first collection data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42")).asProject()); // Two projects in the second collection data.createItem(GenTodo().withId(43).withParent(43).withTitle(QStringLiteral("43")).asProject()); data.createItem(GenTodo().withId(44).withParent(43).withTitle(QStringLiteral("44")).asProject()); // WHEN auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); QScopedPointer<Domain::DataSourceQueries> queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes, Akonadi::StorageInterface::Ptr(data.createStorage()), serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()))); Domain::DataSource::Ptr dataSource1 = serializer->createDataSourceFromCollection(data.collection(42), Akonadi::SerializerInterface::BaseName); auto result1 = queries->findProjects(dataSource1); result1->data(); result1 = queries->findProjects(dataSource1); // Should not cause any problem or wrong data // THEN QVERIFY(result1->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result1->data().size(), 1); QCOMPARE(result1->data().at(0)->name(), QStringLiteral("42")); // WHEN Domain::DataSource::Ptr dataSource2 = serializer->createDataSourceFromCollection(data.collection(43), Akonadi::SerializerInterface::BaseName); auto result2 = queries->findProjects(dataSource2); result2->data(); result2 = queries->findProjects(dataSource2); // Should not cause any problem or wrong data // THEN QVERIFY(result2->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result1->data().size(), 1); QCOMPARE(result1->data().at(0)->name(), QStringLiteral("42")); QCOMPARE(result2->data().size(), 2); QCOMPARE(result2->data().at(0)->name(), QStringLiteral("43")); QCOMPARE(result2->data().at(1)->name(), QStringLiteral("44")); } void shouldIgnoreItemsWhichAreNotProjects() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // One project and one regular task in the collection data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42")).asProject()); data.createItem(GenTodo().withId(43).withParent(42).withTitle(QStringLiteral("43"))); // WHEN auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); QScopedPointer<Domain::DataSourceQueries> queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes, Akonadi::StorageInterface::Ptr(data.createStorage()), serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()))); Domain::DataSource::Ptr dataSource = serializer->createDataSourceFromCollection(data.collection(42), Akonadi::SerializerInterface::BaseName); auto result = queries->findProjects(dataSource); QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); // THEN QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42")); } void shouldReactToItemAddsForProjectsOnly() { // GIVEN AkonadiFakeData data; // One empty collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); QScopedPointer<Domain::DataSourceQueries> queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes, Akonadi::StorageInterface::Ptr(data.createStorage()), serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()))); Domain::DataSource::Ptr dataSource = serializer->createDataSourceFromCollection(data.collection(42), Akonadi::SerializerInterface::BaseName); auto result = queries->findProjects(dataSource); TestHelpers::waitForEmptyJobQueue(); QVERIFY(result->data().isEmpty()); // WHEN data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42")).asProject()); data.createItem(GenTodo().withId(43).withParent(42).withTitle(QStringLiteral("43"))); // THEN QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42")); } void shouldReactToItemRemovesForProjects() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three projects in the collection data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42")).asProject()); data.createItem(GenTodo().withId(43).withParent(42).withTitle(QStringLiteral("43")).asProject()); data.createItem(GenTodo().withId(44).withParent(42).withTitle(QStringLiteral("44")).asProject()); auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); QScopedPointer<Domain::DataSourceQueries> queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes, Akonadi::StorageInterface::Ptr(data.createStorage()), serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()))); Domain::DataSource::Ptr dataSource = serializer->createDataSourceFromCollection(data.collection(42), Akonadi::SerializerInterface::BaseName); auto result = queries->findProjects(dataSource); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 3); // WHEN data.removeItem(Akonadi::Item(43)); // THEN QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42")); QCOMPARE(result->data().at(1)->name(), QStringLiteral("44")); } void shouldReactToItemChangesForProjects() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three projects in the collection data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42")).asProject()); data.createItem(GenTodo().withId(43).withParent(42).withTitle(QStringLiteral("43")).asProject()); data.createItem(GenTodo().withId(44).withParent(42).withTitle(QStringLiteral("44")).asProject()); auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); QScopedPointer<Domain::DataSourceQueries> queries(new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes, Akonadi::StorageInterface::Ptr(data.createStorage()), serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()))); Domain::DataSource::Ptr dataSource = serializer->createDataSourceFromCollection(data.collection(42), Akonadi::SerializerInterface::BaseName); auto result = queries->findProjects(dataSource); // Even though the pointer didn't change it's convenient to user if we call // the replace handlers bool replaceHandlerCalled = false; result->addPostReplaceHandler([&replaceHandlerCalled](const Domain::Project::Ptr &, int) { replaceHandlerCalled = true; }); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 3); // WHEN data.modifyItem(GenTodo(data.item(43)).withTitle(QStringLiteral("43bis"))); // THEN QCOMPARE(result->data().size(), 3); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42")); QCOMPARE(result->data().at(1)->name(), QStringLiteral("43bis")); QCOMPARE(result->data().at(2)->name(), QStringLiteral("44")); QVERIFY(replaceHandlerCalled); } }; ZANSHIN_TEST_MAIN(AkonadiDataSourceQueriesTest) #include "akonadidatasourcequeriestest.moc" diff --git a/tests/units/akonadi/akonadilivequeryhelperstest.cpp b/tests/units/akonadi/akonadilivequeryhelperstest.cpp index a55759b5..48041a75 100644 --- a/tests/units/akonadi/akonadilivequeryhelperstest.cpp +++ b/tests/units/akonadi/akonadilivequeryhelperstest.cpp @@ -1,661 +1,641 @@ /* This file is part of Zanshin Copyright 2015 Kevin Ottens <ervin@kde.org> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include <testlib/qtest_zanshin.h> #include "akonadi/akonadilivequeryhelpers.h" #include <functional> #include <KCalCore/Todo> #include <KMime/Message> #include <Akonadi/Notes/NoteUtils> #include "akonadi/akonadiserializer.h" #include "testlib/akonadifakedata.h" #include "testlib/gencollection.h" -#include "testlib/gennote.h" #include "testlib/gentag.h" #include "testlib/gentodo.h" #include "testlib/testhelpers.h" using namespace Testlib; using namespace std::placeholders; Q_DECLARE_METATYPE(Akonadi::StorageInterface::FetchContentTypes) static QString titleFromItem(const Akonadi::Item &item) { if (item.hasPayload<KCalCore::Todo::Ptr>()) { const auto todo = item.payload<KCalCore::Todo::Ptr>(); return todo->summary(); } else if (item.hasPayload<KMime::Message::Ptr>()) { const auto message = item.payload<KMime::Message::Ptr>(); const Akonadi::NoteUtils::NoteMessageWrapper note(message); return note.title(); } else { return QString(); } } class AkonadiLiveQueryHelpersTest : public QObject { Q_OBJECT private: Akonadi::LiveQueryHelpers::Ptr createHelpers(AkonadiFakeData &data) { return Akonadi::LiveQueryHelpers::Ptr(new Akonadi::LiveQueryHelpers(createSerializer(), createStorage(data))); } Akonadi::StorageInterface::Ptr createStorage(AkonadiFakeData &data) { return Akonadi::StorageInterface::Ptr(data.createStorage()); } Akonadi::SerializerInterface::Ptr createSerializer() { return Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer); } private slots: void shouldFetchAllCollectionsForType_data() { QTest::addColumn<Akonadi::StorageInterface::FetchContentTypes>("contentTypes"); QTest::newRow("task collections only") << Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::Tasks); QTest::newRow("note collections only") << Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::Notes); QTest::newRow("task and note collections only") << (Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes); QTest::newRow("all collections") << Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::AllContent); } void shouldFetchAllCollectionsForType() { // GIVEN auto data = AkonadiFakeData(); auto helpers = createHelpers(data); // Three top level collections (any content, tasks and notes) data.createCollection(GenCollection().withId(42).withRootAsParent().withName(QStringLiteral("42"))); data.createCollection(GenCollection().withId(43).withRootAsParent().withName(QStringLiteral("43")).withTaskContent()); data.createCollection(GenCollection().withId(44).withRootAsParent().withName(QStringLiteral("44")).withNoteContent()); // Three children under each of the top level for each content type data.createCollection(GenCollection().withId(45).withParent(42).withName(QStringLiteral("45"))); data.createCollection(GenCollection().withId(46).withParent(42).withName(QStringLiteral("46")).withTaskContent()); data.createCollection(GenCollection().withId(47).withParent(42).withName(QStringLiteral("47")).withNoteContent()); data.createCollection(GenCollection().withId(48).withParent(43).withName(QStringLiteral("48"))); data.createCollection(GenCollection().withId(49).withParent(43).withName(QStringLiteral("49")).withTaskContent()); data.createCollection(GenCollection().withId(50).withParent(43).withName(QStringLiteral("50")).withNoteContent()); data.createCollection(GenCollection().withId(51).withParent(44).withName(QStringLiteral("51"))); data.createCollection(GenCollection().withId(52).withParent(44).withName(QStringLiteral("52")).withTaskContent()); data.createCollection(GenCollection().withId(53).withParent(44).withName(QStringLiteral("53")).withNoteContent()); // The list which will be filled by the fetch function auto collections = Akonadi::Collection::List(); auto add = [&collections] (const Akonadi::Collection &collection) { collections.append(collection); }; // WHEN QFETCH(Akonadi::StorageInterface::FetchContentTypes, contentTypes); auto fetch = helpers->fetchAllCollections(contentTypes); fetch(add); TestHelpers::waitForEmptyJobQueue(); auto result = QStringList(); std::transform(collections.constBegin(), collections.constEnd(), std::back_inserter(result), std::bind(&Akonadi::Collection::displayName, _1)); result.sort(); // THEN auto expected = QStringList(); if (contentTypes == Akonadi::StorageInterface::AllContent) { expected << QStringLiteral("42") << QStringLiteral("45") << QStringLiteral("48") << QStringLiteral("51"); } if ((contentTypes & Akonadi::StorageInterface::Tasks) || contentTypes == Akonadi::StorageInterface::AllContent) { expected << QStringLiteral("43") << QStringLiteral("46") << QStringLiteral("49") << QStringLiteral("52"); } if ((contentTypes & Akonadi::StorageInterface::Notes) || contentTypes == Akonadi::StorageInterface::AllContent) { expected << QStringLiteral("44") << QStringLiteral("47") << QStringLiteral("50") << QStringLiteral("53"); } expected.sort(); QCOMPARE(result, expected); // WHEN (should not crash when the helpers object is deleted) helpers.clear(); collections.clear(); fetch(add); TestHelpers::waitForEmptyJobQueue(); // THEN result.clear(); std::transform(collections.constBegin(), collections.constEnd(), std::back_inserter(result), std::bind(&Akonadi::Collection::displayName, _1)); result.sort(); QCOMPARE(result, expected); } void shouldFetchCollectionsForRootAndType_data() { QTest::addColumn<Akonadi::Collection>("root"); QTest::addColumn<Akonadi::StorageInterface::FetchContentTypes>("contentTypes"); QTest::newRow("task collections only from root") << Akonadi::Collection::root() << Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::Tasks); QTest::newRow("note collections only from root") << Akonadi::Collection::root() << Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::Notes); QTest::newRow("task and note collections only from root") << Akonadi::Collection::root() << (Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes); QTest::newRow("all collections from root") << Akonadi::Collection::root() << Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::AllContent); QTest::newRow("task collections only from 'all branch'") << Akonadi::Collection(42) << Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::Tasks); QTest::newRow("note collections only from 'all branch'") << Akonadi::Collection(42) << Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::Notes); QTest::newRow("task and note collections only from 'all branch'") << Akonadi::Collection(42) << (Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes); QTest::newRow("all collections from 'all branch'") << Akonadi::Collection(42) << Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::AllContent); QTest::newRow("task collections only from 'task branch'") << Akonadi::Collection(43) << Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::Tasks); QTest::newRow("note collections only from 'task branch'") << Akonadi::Collection(43) << Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::Notes); QTest::newRow("task and note collections only from 'task branch'") << Akonadi::Collection(43) << (Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes); QTest::newRow("all collections from 'task branch'") << Akonadi::Collection(43) << Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::AllContent); QTest::newRow("task collections only from 'note branch'") << Akonadi::Collection(44) << Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::Tasks); QTest::newRow("note collections only from 'note branch'") << Akonadi::Collection(44) << Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::Notes); QTest::newRow("task and note collections only from 'note branch'") << Akonadi::Collection(44) << (Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes); QTest::newRow("all collections from 'note branch'") << Akonadi::Collection(44) << Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::AllContent); } void shouldFetchCollectionsForRootAndType() { // GIVEN auto data = AkonadiFakeData(); auto helpers = createHelpers(data); // Three top level collections (any content, tasks and notes) data.createCollection(GenCollection().withId(42).withRootAsParent().withName(QStringLiteral("42"))); data.createCollection(GenCollection().withId(43).withRootAsParent().withName(QStringLiteral("43")).withTaskContent()); data.createCollection(GenCollection().withId(44).withRootAsParent().withName(QStringLiteral("44")).withNoteContent()); // Three children under each of the top level for each content type data.createCollection(GenCollection().withId(45).withParent(42).withName(QStringLiteral("45"))); data.createCollection(GenCollection().withId(46).withParent(42).withName(QStringLiteral("46")).withTaskContent()); data.createCollection(GenCollection().withId(47).withParent(42).withName(QStringLiteral("47")).withNoteContent()); data.createCollection(GenCollection().withId(48).withParent(43).withName(QStringLiteral("48"))); data.createCollection(GenCollection().withId(49).withParent(43).withName(QStringLiteral("49")).withTaskContent()); data.createCollection(GenCollection().withId(50).withParent(43).withName(QStringLiteral("50")).withNoteContent()); data.createCollection(GenCollection().withId(51).withParent(44).withName(QStringLiteral("51"))); data.createCollection(GenCollection().withId(52).withParent(44).withName(QStringLiteral("52")).withTaskContent()); data.createCollection(GenCollection().withId(53).withParent(44).withName(QStringLiteral("53")).withNoteContent()); // The list which will be filled by the fetch function auto collections = Akonadi::Collection::List(); auto add = [&collections] (const Akonadi::Collection &collection) { collections.append(collection); }; // WHEN QFETCH(Akonadi::Collection, root); QFETCH(Akonadi::StorageInterface::FetchContentTypes, contentTypes); auto fetch = helpers->fetchCollections(root, contentTypes); fetch(add); TestHelpers::waitForEmptyJobQueue(); auto result = QStringList(); std::transform(collections.constBegin(), collections.constEnd(), std::back_inserter(result), std::bind(&Akonadi::Collection::displayName, _1)); result.sort(); // THEN auto expected = QStringList(); if (root == Akonadi::Collection::root()) { expected << QStringLiteral("42") << QStringLiteral("43") << QStringLiteral("44"); } else { const qint64 baseId = root.id() == 42 ? 45 : root.id() == 43 ? 48 : root.id() == 44 ? 51 : -1; QVERIFY(baseId > 0); if (contentTypes == Akonadi::StorageInterface::AllContent) { expected << QString::number(baseId); } if ((contentTypes & Akonadi::StorageInterface::Tasks) || contentTypes == Akonadi::StorageInterface::AllContent) { expected << QString::number(baseId + 1); } if ((contentTypes & Akonadi::StorageInterface::Notes) || contentTypes == Akonadi::StorageInterface::AllContent) { expected << QString::number(baseId + 2); } } expected.sort(); QCOMPARE(result, expected); // WHEN (should not crash when the helpers object is deleted) helpers.clear(); collections.clear(); fetch(add); TestHelpers::waitForEmptyJobQueue(); // THEN result.clear(); std::transform(collections.constBegin(), collections.constEnd(), std::back_inserter(result), std::bind(&Akonadi::Collection::displayName, _1)); result.sort(); QCOMPARE(result, expected); } - void shouldFetchItemsByContentTypes_data() - { - QTest::addColumn<Akonadi::StorageInterface::FetchContentTypes>("contentTypes"); - - QTest::newRow("task collections only") << Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::Tasks); - QTest::newRow("note collections only") << Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::Notes); - QTest::newRow("task and note collections only") << (Akonadi::StorageInterface::Tasks | Akonadi::StorageInterface::Notes); - QTest::newRow("all collections") << Akonadi::StorageInterface::FetchContentTypes(Akonadi::StorageInterface::AllContent); - } - void shouldFetchItemsByContentTypes() { // GIVEN auto data = AkonadiFakeData(); auto helpers = createHelpers(data); // Two top level collections, one with no particular content, one with tasks data.createCollection(GenCollection().withId(42).withRootAsParent().withName(QStringLiteral("42"))); data.createCollection(GenCollection().withId(43).withRootAsParent().withName(QStringLiteral("43")).withTaskContent()); // One note collection as child of the first one data.createCollection(GenCollection().withId(44).withParent(42).withName(QStringLiteral("44")).withNoteContent()); // One task collection as child of the note collection data.createCollection(GenCollection().withId(45).withParent(44).withName(QStringLiteral("45")).withTaskContent()); // One task in the first collection data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42"))); - // Two items (tasks or notes) in all the other collections + // 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(GenNote().withId(45).withParent(44).withTitle(QStringLiteral("45"))); - data.createItem(GenNote().withId(46).withParent(44).withTitle(QStringLiteral("46"))); + 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 - QFETCH(Akonadi::StorageInterface::FetchContentTypes, contentTypes); - auto fetch = helpers->fetchItems(contentTypes); + auto fetch = helpers->fetchItems(Akonadi::StorageInterface::Tasks); fetch(add); TestHelpers::waitForEmptyJobQueue(); auto result = QStringList(); std::transform(items.constBegin(), items.constEnd(), std::back_inserter(result), titleFromItem); result.sort(); // THEN auto expected = QStringList(); - - if ((contentTypes & Akonadi::StorageInterface::Tasks) || contentTypes == Akonadi::StorageInterface::AllContent) { - expected << QStringLiteral("43") << QStringLiteral("44") << QStringLiteral("47") << QStringLiteral("48"); - } - - if ((contentTypes & Akonadi::StorageInterface::Notes) || contentTypes == Akonadi::StorageInterface::AllContent) { - expected << QStringLiteral("45") << QStringLiteral("46"); - } - + expected << 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<Akonadi::Collection>("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<Akonadi::Tag>("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<Akonadi::Item>("item"); QTest::newRow("item in first collection") << Akonadi::Item(43); QTest::newRow("item in second collection") << Akonadi::Item(48); } void shouldFetchSiblings() { // GIVEN auto data = AkonadiFakeData(); auto helpers = createHelpers(data); // Two top level collections (one with notes, one with tasks) data.createCollection(GenCollection().withId(42).withRootAsParent().withName(QStringLiteral("42")).withTaskContent()); data.createCollection(GenCollection().withId(43).withRootAsParent().withName(QStringLiteral("43")).withNoteContent()); // Four items in each collection data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42"))); data.createItem(GenTodo().withId(43).withParent(42).withTitle(QStringLiteral("43"))); data.createItem(GenTodo().withId(44).withParent(42).withTitle(QStringLiteral("44"))); data.createItem(GenTodo().withId(45).withParent(42).withTitle(QStringLiteral("45"))); - data.createItem(GenNote().withId(46).withParent(43).withTitle(QStringLiteral("46"))); - data.createItem(GenNote().withId(47).withParent(43).withTitle(QStringLiteral("47"))); - data.createItem(GenNote().withId(48).withParent(43).withTitle(QStringLiteral("48"))); - data.createItem(GenNote().withId(49).withParent(43).withTitle(QStringLiteral("49"))); + 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 c1d12b1c..4011c3a5 100644 --- a/tests/units/akonadi/akonadilivequeryintegratortest.cpp +++ b/tests/units/akonadi/akonadilivequeryintegratortest.cpp @@ -1,1505 +1,1127 @@ /* This file is part of Zanshin Copyright 2015 Kevin Ottens <ervin@kde.org> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include <testlib/qtest_zanshin.h> #include <KCalCore/Todo> #include <KMime/Message> #include <Akonadi/Notes/NoteUtils> #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/gennote.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<KCalCore::Todo::Ptr>()) { const auto todo = item.payload<KCalCore::Todo::Ptr>(); return todo->summary(); } else if (item.hasPayload<KMime::Message::Ptr>()) { const auto message = item.payload<KMime::Message::Ptr>(); 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<Akonadi::Collection>::AddFunction &add) { auto job = storage->fetchCollections(Akonadi::Collection::root(), Akonadi::Storage::Recursive, Akonadi::Storage::AllContent); 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<Akonadi::Item>::AddFunction &add) { auto job = storage->fetchCollections(Akonadi::Collection::root(), Akonadi::Storage::Recursive, Akonadi::Storage::AllContent); 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<Akonadi::Item>::AddFunction &add) { auto job = storage->fetchCollections(Akonadi::Collection::root(), Akonadi::Storage::Recursive, Akonadi::Storage::AllContent); 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 shouldBindArtifactQueries() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withName(QStringLiteral("42"))); // Three artifacts in the collection, one not matching the predicate data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42-in"))); - data.createItem(GenNote().withId(43).withParent(42).withTitle(QStringLiteral("43-in"))); - data.createItem(GenNote().withId(44).withParent(42).withTitle(QStringLiteral("44-ex"))); + 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(40).withParent(42).asProject().withTitle(QStringLiteral("40"))); data.createItem(GenTodo().withId(41).withParent(42).asProject().withTitle(QStringLiteral("41-in"))); auto integrator = createIntegrator(data); auto storage = createStorage(data); auto query = Domain::LiveQueryOutput<Domain::Artifact::Ptr>::Ptr(); auto fetch = fetchItemsInAllCollectionsFunction(storage); auto predicate = [] (const Akonadi::Item &item) { return titleFromItem(item).endsWith(QLatin1String("-in")); }; // Initial listing // WHEN integrator->bind("artifact1", query, fetch, predicate); auto result = query->result(); result->data(); integrator->bind("artifact2", 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(GenNote(data.item(44)).withTitle(QStringLiteral("44-in"))); + 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(GenNote(data.item(44)).withTitle(QStringLiteral("44-ex"))); + 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("artifactN", query, fetch, predicate); auto result = query->result(); // THEN * 2 QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QVERIFY(!result->data().isEmpty()); } } void shouldMoveArtifactsBetweenQueries() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withName(QStringLiteral("42"))); - // One task and one note which show in one query and not the other + // Two tasks: one which shows in one query and not the other data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42-in"))); - data.createItem(GenNote().withId(43).withParent(42).withTitle(QStringLiteral("43-in"))); + data.createItem(GenTodo().withId(43).withParent(42).withTitle(QStringLiteral("43-in"))); // Couple of projects in the collection which should not appear or create trouble data.createItem(GenTodo().withId(39).withParent(42).asProject().withTitle(QStringLiteral("39"))); data.createItem(GenTodo().withId(40).withParent(42).asProject().withTitle(QStringLiteral("40-ex"))); data.createItem(GenTodo().withId(41).withParent(42).asProject().withTitle(QStringLiteral("41-in"))); auto integrator = createIntegrator(data); auto storage = createStorage(data); auto inQuery = Domain::LiveQueryOutput<Domain::Artifact::Ptr>::Ptr(); auto exQuery = Domain::LiveQueryOutput<Domain::Artifact::Ptr>::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("artifact-in", inQuery, fetch, inPredicate); auto inResult = inQuery->result(); integrator->bind("artifact-ex", exQuery, fetch, exPredicate); auto exResult = exQuery->result(); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(inResult->data().size(), 2); QCOMPARE(exResult->data().size(), 0); // WHEN data.modifyItem(GenTodo(data.item(42)).withTitle(QStringLiteral("42-ex"))); - data.modifyItem(GenNote(data.item(43)).withTitle(QStringLiteral("43-ex"))); + data.modifyItem(GenTodo(data.item(43)).withTitle(QStringLiteral("43-ex"))); // THEN QCOMPARE(inResult->data().size(), 0); QCOMPARE(exResult->data().size(), 2); } void shouldReactToCollectionSelectionChangesForArtifactQueries() { // 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<Domain::Artifact::Ptr>::Ptr(); auto fetch = fetchItemsInSelectedCollectionsFunction(storage, serializer); auto predicate = [] (const Akonadi::Item &) { return true; }; integrator->bind("artifact 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 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<Domain::Context::Ptr>::Ptr(); auto fetch = [storage] (const Domain::LiveQueryInput<Akonadi::Tag>::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<Domain::Context::Ptr>::Ptr(); auto exQuery = Domain::LiveQueryOutput<Domain::Context::Ptr>::Ptr(); auto fetch = [storage] (const Domain::LiveQueryInput<Akonadi::Tag>::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"))); data.createCollection(GenCollection().withId(43).withRootAsParent().withName(QStringLiteral("43-in"))); data.createCollection(GenCollection().withId(44).withRootAsParent().withName(QStringLiteral("44-ex"))); auto integrator = createIntegrator(data); auto storage = createStorage(data); auto query = Domain::LiveQueryOutput<Domain::DataSource::Ptr>::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"))); auto integrator = createIntegrator(data); auto storage = createStorage(data); auto inQuery = Domain::LiveQueryOutput<Domain::DataSource::Ptr>::Ptr(); auto exQuery = Domain::LiveQueryOutput<Domain::DataSource::Ptr>::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 shouldBindNoteQueries() - { - // GIVEN - AkonadiFakeData data; - - // One top level collection - data.createCollection(GenCollection().withId(42).withRootAsParent().withName(QStringLiteral("42"))); - - // Three notes in the collection, one not matching the predicate - data.createItem(GenNote().withId(42).withParent(42).withTitle(QStringLiteral("42-in"))); - data.createItem(GenNote().withId(43).withParent(42).withTitle(QStringLiteral("43-in"))); - data.createItem(GenNote().withId(44).withParent(42).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<Domain::Note::Ptr>::Ptr(); - auto fetch = fetchItemsInAllCollectionsFunction(storage); - auto predicate = [] (const Akonadi::Item &item) { - return titleFromItem(item).endsWith(QLatin1String("-in")); - }; - - // Initial listing - // WHEN - integrator->bind("note1", query, fetch, predicate); - auto result = query->result(); - result->data(); - integrator->bind("note2", 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(GenNote().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(GenNote(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(GenNote(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(GenNote(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("noteN", query, fetch, predicate); - auto result = query->result(); - - // THEN * 2 - QVERIFY(result->data().isEmpty()); - TestHelpers::waitForEmptyJobQueue(); - QVERIFY(!result->data().isEmpty()); - } - } - - void shouldMoveNotesBetweenQueries() - { - // GIVEN - AkonadiFakeData data; - - // One top level collection - data.createCollection(GenCollection().withId(42).withRootAsParent().withName(QStringLiteral("42"))); - - // One note which shows in one query and not the other - data.createItem(GenNote().withId(42).withParent(42).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<Domain::Note::Ptr>::Ptr(); - auto exQuery = Domain::LiveQueryOutput<Domain::Note::Ptr>::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("note-in", inQuery, fetch, inPredicate); - auto inResult = inQuery->result(); - - integrator->bind("note-ex", exQuery, fetch, exPredicate); - auto exResult = exQuery->result(); - - TestHelpers::waitForEmptyJobQueue(); - - QCOMPARE(inResult->data().size(), 1); - QCOMPARE(exResult->data().size(), 0); - - // WHEN - data.modifyItem(GenNote(data.item(42)).withTitle(QStringLiteral("42-ex"))); - - // THEN - QCOMPARE(inResult->data().size(), 0); - QCOMPARE(exResult->data().size(), 1); - } - - void shouldReactToCollectionSelectionChangesForNoteQueries() - { - // GIVEN - AkonadiFakeData data; - - // Two top level collections - data.createCollection(GenCollection().withId(42).withRootAsParent().withNoteContent()); - data.createCollection(GenCollection().withId(43).withRootAsParent().withNoteContent()); - - // One note in each collection - data.createItem(GenNote().withId(42).withParent(42).withTitle(QStringLiteral("42"))); - data.createItem(GenNote().withId(43).withParent(43).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<Domain::Note::Ptr>::Ptr(); - auto fetch = fetchItemsInSelectedCollectionsFunction(storage, serializer); - auto predicate = [] (const Akonadi::Item &) { - return true; - }; - - integrator->bind("note 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 shouldBindProjectQueries() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withName(QStringLiteral("42"))); // 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<Domain::Project::Ptr>::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"))); // 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<Domain::Project::Ptr>::Ptr(); auto exQuery = Domain::LiveQueryOutput<Domain::Project::Ptr>::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<Domain::Project::Ptr>::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 shouldBindTagQueries() - { - // GIVEN - AkonadiFakeData data; - - // Three plain tags, one not matching the predicate - data.createTag(GenTag().withId(42).asPlain().withName(QStringLiteral("42-in"))); - data.createTag(GenTag().withId(43).asPlain().withName(QStringLiteral("43-in"))); - data.createTag(GenTag().withId(44).asPlain().withName(QStringLiteral("44-ex"))); - - // Couple of context tags which should not appear or create trouble - data.createTag(GenTag().withId(40).asContext().withName(QStringLiteral("40"))); - data.createTag(GenTag().withId(41).asContext().withName(QStringLiteral("41-in"))); - - auto integrator = createIntegrator(data); - auto storage = createStorage(data); - - auto query = Domain::LiveQueryOutput<Domain::Tag::Ptr>::Ptr(); - auto fetch = [storage] (const Domain::LiveQueryInput<Akonadi::Tag>::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("tag1", query, fetch, predicate); - auto result = query->result(); - result->data(); - integrator->bind("tag2", 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).asPlain().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("tagN", query, fetch, predicate); - auto result = query->result(); - - // THEN * 2 - QVERIFY(result->data().isEmpty()); - TestHelpers::waitForEmptyJobQueue(); - QVERIFY(!result->data().isEmpty()); - } - } - - void shouldMoveTagBetweenQueries() - { - // GIVEN - AkonadiFakeData data; - - // One plain tag which shows in one query not the other - data.createTag(GenTag().withId(42).asPlain().withName(QStringLiteral("42-in"))); - - // Couple of context tags which should not appear or create trouble - data.createTag(GenTag().withId(39).asContext().withName(QStringLiteral("39"))); - data.createTag(GenTag().withId(40).asContext().withName(QStringLiteral("40-ex"))); - data.createTag(GenTag().withId(41).asContext().withName(QStringLiteral("41-in"))); - - auto integrator = createIntegrator(data); - auto storage = createStorage(data); - - auto inQuery = Domain::LiveQueryOutput<Domain::Tag::Ptr>::Ptr(); - auto exQuery = Domain::LiveQueryOutput<Domain::Tag::Ptr>::Ptr(); - auto fetch = [storage] (const Domain::LiveQueryInput<Akonadi::Tag>::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("tag-in", inQuery, fetch, inPredicate); - auto inResult = inQuery->result(); - - integrator->bind("tag-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 shouldBindTaskQueries() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withName(QStringLiteral("42"))); // 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 notes and projects in the collection which should not appear or create trouble + // 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"))); - data.createItem(GenNote().withId(40).withParent(42).withTitle(QStringLiteral("40"))); - data.createItem(GenNote().withId(41).withParent(42).withTitle(QStringLiteral("41-in"))); auto integrator = createIntegrator(data); auto storage = createStorage(data); auto query = Domain::LiveQueryOutput<Domain::Task::Ptr>::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"))); // One task which shows in one query and not the other data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42-in"))); - // Couple of notes in the collection which should not appear or create trouble - data.createItem(GenNote().withId(39).withParent(42).withTitle(QStringLiteral("39"))); - data.createItem(GenNote().withId(40).withParent(42).withTitle(QStringLiteral("40-ex"))); - data.createItem(GenNote().withId(41).withParent(42).withTitle(QStringLiteral("41-in"))); - auto integrator = createIntegrator(data); auto storage = createStorage(data); auto inQuery = Domain::LiveQueryOutput<Domain::Task::Ptr>::Ptr(); auto exQuery = Domain::LiveQueryOutput<Domain::Task::Ptr>::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<Domain::Task::Ptr>::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/akonadinotequeriestest.cpp b/tests/units/akonadi/akonadinotequeriestest.cpp deleted file mode 100644 index 77062075..00000000 --- a/tests/units/akonadi/akonadinotequeriestest.cpp +++ /dev/null @@ -1,487 +0,0 @@ -/* This file is part of Zanshin - - Copyright 2014 Kevin Ottens <ervin@kde.org> - Copyright 2014 Remi Benoit <r3m1.benoit@gmail.com> - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License or (at your option) version 3 or any later version - accepted by the membership of KDE e.V. (or its successor approved - by the membership of KDE e.V.), which shall act as a proxy - defined in Section 14 of version 3 of the license. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - USA. -*/ - -#include <testlib/qtest_zanshin.h> - -#include "akonadi/akonadinotequeries.h" -#include "akonadi/akonadiserializer.h" - -#include "testlib/akonadifakedata.h" -#include "testlib/gencollection.h" -#include "testlib/gennote.h" -#include "testlib/gentag.h" -#include "testlib/gentodo.h" -#include "testlib/testhelpers.h" - -using namespace Testlib; - -class AkonadiNoteQueriesTest : public QObject -{ - Q_OBJECT -private slots: - void shouldLookInAllReportedForAllNotes() - { - // GIVEN - AkonadiFakeData data; - - // Two top level collections - data.createCollection(GenCollection().withId(42).withRootAsParent().withNoteContent()); - data.createCollection(GenCollection().withId(43).withRootAsParent().withNoteContent()); - - // One note in the first collection - data.createItem(GenNote().withId(42).withParent(42).withTitle(QStringLiteral("42"))); - - // Two notes in the second collection - data.createItem(GenNote().withId(43).withParent(43).withTitle(QStringLiteral("43"))); - data.createItem(GenNote().withId(44).withParent(43).withTitle(QStringLiteral("44"))); - - // WHEN - QScopedPointer<Domain::NoteQueries> queries(new Akonadi::NoteQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), - Akonadi::Serializer::Ptr(new Akonadi::Serializer), - Akonadi::MonitorInterface::Ptr(data.createMonitor()))); - auto result = queries->findAll(); - result->data(); - result = queries->findAll(); // Should not cause any problem or wrong data - - // THEN - QVERIFY(result->data().isEmpty()); - TestHelpers::waitForEmptyJobQueue(); - - QCOMPARE(result->data().size(), 3); - QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); - QCOMPARE(result->data().at(1)->title(), QStringLiteral("43")); - QCOMPARE(result->data().at(2)->title(), QStringLiteral("44")); - } - - void shouldIgnoreItemsWhichAreNotNotes() - { - // GIVEN - AkonadiFakeData data; - - // One top level collection - data.createCollection(GenCollection().withId(42).withRootAsParent().withNoteContent()); - - // Two notes in the collection - data.createItem(GenNote().withId(42).withParent(42).withTitle(QStringLiteral("42"))); - auto item = Akonadi::Item(43); - item.setPayloadFromData("FooBar"); - item.setParentCollection(Akonadi::Collection(42)); - data.createItem(item); - - // WHEN - QScopedPointer<Domain::NoteQueries> queries(new Akonadi::NoteQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), - Akonadi::Serializer::Ptr(new Akonadi::Serializer), - Akonadi::MonitorInterface::Ptr(data.createMonitor()))); - auto result = queries->findAll(); - - // THEN - QVERIFY(result->data().isEmpty()); - TestHelpers::waitForEmptyJobQueue(); - - QCOMPARE(result->data().size(), 1); - QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); - } - - void shouldReactToItemAddsForNotesOnly() - { - // GIVEN - AkonadiFakeData data; - - // One empty collection - data.createCollection(GenCollection().withId(42).withRootAsParent().withNoteContent()); - - QScopedPointer<Domain::NoteQueries> queries(new Akonadi::NoteQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), - Akonadi::Serializer::Ptr(new Akonadi::Serializer), - Akonadi::MonitorInterface::Ptr(data.createMonitor()))); - auto result = queries->findAll(); - TestHelpers::waitForEmptyJobQueue(); - QVERIFY(result->data().isEmpty()); - - // WHEN - data.createItem(GenNote().withId(42).withParent(42).withTitle(QStringLiteral("42"))); - auto item = Akonadi::Item(43); - item.setPayloadFromData("FooBar"); - item.setParentCollection(Akonadi::Collection(42)); - data.createItem(item); - - // THEN - QCOMPARE(result->data().size(), 1); - QCOMPARE(result->data().first()->title(), QStringLiteral("42")); - } - - void shouldReactToItemRemovesForAllNotes() - { - // GIVEN - AkonadiFakeData data; - - // One top level collection - data.createCollection(GenCollection().withId(42).withRootAsParent().withNoteContent()); - - // Three notes in the collection - data.createItem(GenNote().withId(42).withParent(42).withTitle(QStringLiteral("42"))); - data.createItem(GenNote().withId(43).withParent(42).withTitle(QStringLiteral("43"))); - data.createItem(GenNote().withId(44).withParent(42).withTitle(QStringLiteral("44"))); - - QScopedPointer<Domain::NoteQueries> queries(new Akonadi::NoteQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), - Akonadi::Serializer::Ptr(new Akonadi::Serializer), - Akonadi::MonitorInterface::Ptr(data.createMonitor()))); - auto result = queries->findAll(); - TestHelpers::waitForEmptyJobQueue(); - QCOMPARE(result->data().size(), 3); - - // WHEN - data.removeItem(Akonadi::Item(43)); - - // THEN - QCOMPARE(result->data().size(), 2); - QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); - QCOMPARE(result->data().at(1)->title(), QStringLiteral("44")); - } - - void shouldReactToItemChangesForAllNotes() - { - // GIVEN - AkonadiFakeData data; - - // One top level collection - data.createCollection(GenCollection().withId(42).withRootAsParent().withNoteContent()); - - // Three Note in the collection - data.createItem(GenNote().withId(42).withParent(42).withTitle(QStringLiteral("42"))); - data.createItem(GenNote().withId(43).withParent(42).withTitle(QStringLiteral("43"))); - data.createItem(GenNote().withId(44).withParent(42).withTitle(QStringLiteral("44"))); - - QScopedPointer<Domain::NoteQueries> queries(new Akonadi::NoteQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), - Akonadi::Serializer::Ptr(new Akonadi::Serializer), - Akonadi::MonitorInterface::Ptr(data.createMonitor()))); - Domain::QueryResult<Domain::Note::Ptr>::Ptr result = queries->findAll(); - // Even though the pointer didn't change it's convenient to user if we call - // the replace handlers - bool replaceHandlerCalled = false; - result->addPostReplaceHandler([&replaceHandlerCalled](const Domain::Note::Ptr &, int) { - replaceHandlerCalled = true; - }); - TestHelpers::waitForEmptyJobQueue(); - QCOMPARE(result->data().size(), 3); - - // WHEN - data.modifyItem(GenNote(data.item(43)).withTitle(QStringLiteral("43bis"))); - - // THEN - QCOMPARE(result->data().size(), 3); - QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); - QCOMPARE(result->data().at(1)->title(), QStringLiteral("43bis")); - QCOMPARE(result->data().at(2)->title(), QStringLiteral("44")); - QVERIFY(replaceHandlerCalled); - } - - void shouldLookInAllSelectedCollectionsForInboxNotes() - { - // GIVEN - AkonadiFakeData data; - - // Three top level collections - data.createCollection(GenCollection().withId(42).withRootAsParent().withNoteContent()); - data.createCollection(GenCollection().withId(43).withRootAsParent().withNoteContent()); - data.createCollection(GenCollection().withId(44).withRootAsParent().withNoteContent().selected(false)); - - // One note in the first collection - data.createItem(GenNote().withId(42).withParent(42).withTitle(QStringLiteral("42"))); - - // Two notes in the second collection - data.createItem(GenNote().withId(43).withParent(43).withTitle(QStringLiteral("43"))); - data.createItem(GenNote().withId(44).withParent(43).withTitle(QStringLiteral("44"))); - - // One note in the third collection - data.createItem(GenNote().withId(45).withParent(44).withTitle(QStringLiteral("45"))); - - // WHEN - QScopedPointer<Domain::NoteQueries> queries(new Akonadi::NoteQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), - Akonadi::Serializer::Ptr(new Akonadi::Serializer), - Akonadi::MonitorInterface::Ptr(data.createMonitor()))); - auto result = queries->findInbox(); - result->data(); - result = queries->findInbox(); // Should not cause any problem or wrong data - - // THEN - QVERIFY(result->data().isEmpty()); - TestHelpers::waitForEmptyJobQueue(); - - QCOMPARE(result->data().size(), 3); - QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); - QCOMPARE(result->data().at(1)->title(), QStringLiteral("43")); - QCOMPARE(result->data().at(2)->title(), QStringLiteral("44")); - } - - void shouldIgnoreItemsWhichAreNotNotesInInbox() - { - // GIVEN - AkonadiFakeData data; - - // One top level collection - data.createCollection(GenCollection().withId(42).withRootAsParent().withNoteContent()); - - // Three items in the collection - data.createItem(GenNote().withId(42).withParent(42).withTitle(QStringLiteral("42"))); - // One of them is a task - data.createItem(GenTodo().withId(43).withParent(42).withTitle(QStringLiteral("43"))); - // One of them is not a task or a note - auto item = Akonadi::Item(44); - item.setPayloadFromData("FooBar"); - item.setParentCollection(Akonadi::Collection(42)); - data.createItem(item); - - // WHEN - QScopedPointer<Domain::NoteQueries> queries(new Akonadi::NoteQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), - Akonadi::Serializer::Ptr(new Akonadi::Serializer), - Akonadi::MonitorInterface::Ptr(data.createMonitor()))); - auto result = queries->findInbox(); - - // THEN - QVERIFY(result->data().isEmpty()); - TestHelpers::waitForEmptyJobQueue(); - - QCOMPARE(result->data().size(), 1); - QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); - } - - void shouldNotHaveNotesWithTagsInInbox_data() - { - QTest::addColumn<bool>("hasTags"); - QTest::addColumn<bool>("isExpectedInInbox"); - - QTest::newRow("note with no tags") << false << true; - QTest::newRow("note with tags") << true << false; - } - - void shouldNotHaveNotesWithTagsInInbox() - { - // GIVEN - AkonadiFakeData data; - - // One top level collection - data.createCollection(GenCollection().withId(42).withRootAsParent().withNoteContent()); - - // One plain tag - data.createTag(GenTag().withId(42).withName(QStringLiteral("tag-42")).asPlain()); - - // One item in the collection - QFETCH(bool, hasTags); - auto tagIds = QList<Akonadi::Tag::Id>(); - if (hasTags) tagIds << 42; - data.createItem(GenNote().withId(42).withParent(42).withTitle(QStringLiteral("42")).withTags(tagIds)); - - // WHEN - QScopedPointer<Domain::NoteQueries> queries(new Akonadi::NoteQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), - Akonadi::Serializer::Ptr(new Akonadi::Serializer), - Akonadi::MonitorInterface::Ptr(data.createMonitor()))); - auto result = queries->findInbox(); - - // THEN - QVERIFY(result->data().isEmpty()); - TestHelpers::waitForEmptyJobQueue(); - - QFETCH(bool, isExpectedInInbox); - if (isExpectedInInbox) { - QCOMPARE(result->data().size(), 1); - QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); - } else { - QVERIFY(result->data().isEmpty()); - } - } - - void shouldReactToItemAddsForInbox_data() - { - QTest::addColumn<bool>("reactionExpected"); - QTest::addColumn<bool>("isTodo"); - QTest::addColumn<bool>("hasTags"); - - QTest::newRow("task with no tag") << false << true << false; - QTest::newRow("task with tag") << false << true << true; - - QTest::newRow("note which should be in inbox") << true << false << false; - QTest::newRow("note with tag") << false << false << true; - } - - void shouldReactToItemAddsForInbox() - { - // GIVEN - AkonadiFakeData data; - - // One top level collection - data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent().withNoteContent()); - - // One plain tag - data.createTag(GenTag().withId(42).withName(QStringLiteral("tag-42")).asPlain()); - - QScopedPointer<Domain::NoteQueries> queries(new Akonadi::NoteQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), - Akonadi::Serializer::Ptr(new Akonadi::Serializer), - Akonadi::MonitorInterface::Ptr(data.createMonitor()))); - auto result = queries->findInbox(); - TestHelpers::waitForEmptyJobQueue(); - QVERIFY(result->data().isEmpty()); - - // WHEN - QFETCH(bool, hasTags); - auto tagIds = QList<Akonadi::Tag::Id>(); - if (hasTags) tagIds << 42; - - QFETCH(bool, isTodo); - if (isTodo) { - data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42")).withTags(tagIds)); - } else { - data.createItem(GenNote().withId(42).withParent(42).withTitle(QStringLiteral("42")).withTags(tagIds)); - } - - // THEN - QFETCH(bool, reactionExpected); - if (reactionExpected) { - QCOMPARE(result->data().size(), 1); - QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); - } else { - QVERIFY(result->data().isEmpty()); - } - } - - void shouldReactToItemRemovesForInbox() - { - // GIVEN - AkonadiFakeData data; - - // One top level collection - data.createCollection(GenCollection().withId(42).withRootAsParent().withNoteContent()); - - // One item in the collection - data.createItem(GenNote().withId(42).withParent(42).withTitle(QStringLiteral("42"))); - - // WHEN - QScopedPointer<Domain::NoteQueries> queries(new Akonadi::NoteQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), - Akonadi::Serializer::Ptr(new Akonadi::Serializer), - Akonadi::MonitorInterface::Ptr(data.createMonitor()))); - auto result = queries->findInbox(); - TestHelpers::waitForEmptyJobQueue(); - QCOMPARE(result->data().size(), 1); - QCOMPARE(result->data().first()->title(), QStringLiteral("42")); - - // WHEN - data.removeItem(Akonadi::Item(42)); - - // THEN - QVERIFY(result->data().isEmpty()); - } - - void shouldReactToItemChangesForInbox_data() - { - QTest::addColumn<bool>("hasTagsBefore"); - QTest::addColumn<bool>("hasTagsAfter"); - QTest::addColumn<bool>("inListAfterChange"); - - QTest::newRow("note appears in inbox") << true << false << true; - QTest::newRow("note disappears from inbox") << false << true << false; - } - - void shouldReactToItemChangesForInbox() - { - // GIVEN - AkonadiFakeData data; - - // One top level collection - data.createCollection(GenCollection().withId(42).withRootAsParent().withNoteContent()); - - // One plain tag - data.createTag(GenTag().withId(42).withName(QStringLiteral("tag-42")).asPlain()); - - // Note data - QFETCH(bool, hasTagsBefore); - - auto tagIds = QList<Akonadi::Tag::Id>(); - if (hasTagsBefore) tagIds << 42; - data.createItem(GenNote().withId(42).withParent(42).withTitle(QStringLiteral("42")).withTags(tagIds)); - - QScopedPointer<Domain::NoteQueries> queries(new Akonadi::NoteQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), - Akonadi::Serializer::Ptr(new Akonadi::Serializer), - Akonadi::MonitorInterface::Ptr(data.createMonitor()))); - auto result = queries->findInbox(); - TestHelpers::waitForEmptyJobQueue(); - - QFETCH(bool, inListAfterChange); - - if (inListAfterChange) { - QVERIFY(result->data().isEmpty()); - } else { - QCOMPARE(result->data().size(), 1); - QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); - } - - // WHEN - QFETCH(bool, hasTagsAfter); - - tagIds.clear(); - if (hasTagsAfter) tagIds << 42; - data.modifyItem(GenNote(data.item(42)).withTags(tagIds)); - - // THEN - if (inListAfterChange) { - QCOMPARE(result->data().size(), 1); - QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); - } else { - QVERIFY(result->data().isEmpty()); - } - } - - void shouldReactToCollectionSelectionChangesForInbox() - { - // GIVEN - AkonadiFakeData data; - - // Two top level collections - data.createCollection(GenCollection().withId(42).withRootAsParent().withNoteContent()); - data.createCollection(GenCollection().withId(43).withRootAsParent().withNoteContent()); - - // One note in each collection - data.createItem(GenNote().withId(42).withParent(42).withTitle(QStringLiteral("42"))); - data.createItem(GenNote().withId(43).withParent(43).withTitle(QStringLiteral("43"))); - - QScopedPointer<Domain::NoteQueries> queries(new Akonadi::NoteQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), - Akonadi::Serializer::Ptr(new Akonadi::Serializer), - Akonadi::MonitorInterface::Ptr(data.createMonitor()))); - auto result = queries->findInbox(); - TestHelpers::waitForEmptyJobQueue(); - QCOMPARE(result->data().size(), 2); - QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); - QCOMPARE(result->data().at(1)->title(), QStringLiteral("43")); - - // WHEN - data.modifyCollection(GenCollection(data.collection(43)).selected(false)); - TestHelpers::waitForEmptyJobQueue(); - - // THEN - QCOMPARE(result->data().size(), 1); - QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); - } -}; - -ZANSHIN_TEST_MAIN(AkonadiNoteQueriesTest) - -#include "akonadinotequeriestest.moc" diff --git a/tests/units/akonadi/akonadinoterepositorytest.cpp b/tests/units/akonadi/akonadinoterepositorytest.cpp deleted file mode 100644 index f1f9bd30..00000000 --- a/tests/units/akonadi/akonadinoterepositorytest.cpp +++ /dev/null @@ -1,284 +0,0 @@ -/* This file is part of Zanshin - - Copyright 2014 Kevin Ottens <ervin@kde.org> - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License or (at your option) version 3 or any later version - accepted by the membership of KDE e.V. (or its successor approved - by the membership of KDE e.V.), which shall act as a proxy - defined in Section 14 of version 3 of the license. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - USA. -*/ - -#include <testlib/qtest_zanshin.h> - -#include <AkonadiCore/Collection> -#include <AkonadiCore/Item> - -#include "utils/mockobject.h" - -#include "testlib/akonadifakejobs.h" -#include "testlib/akonadifakemonitor.h" - -#include "akonadi/akonadinoterepository.h" -#include "akonadi/akonadiserializerinterface.h" -#include "akonadi/akonadistorageinterface.h" - -using namespace mockitopp; - -Q_DECLARE_METATYPE(Testlib::AkonadiFakeItemFetchJob*) - -class AkonadiNoteRepositoryTest : public QObject -{ - Q_OBJECT -private slots: - void shouldCreateNewItems() - { - // GIVEN - - // A default collection for saving - auto col = Akonadi::Collection(42); - - // A note and its corresponding item not existing in storage yet - auto item = Akonadi::Item(); - auto note = Domain::Note::Ptr::create(); - - // A mock create job - auto itemCreateJob = new FakeJob(this); - - // Storage mock returning the create job - Utils::MockObject<Akonadi::StorageInterface> storageMock; - storageMock(&Akonadi::StorageInterface::defaultNoteCollection).when().thenReturn(col); - storageMock(&Akonadi::StorageInterface::createItem).when(item, col) - .thenReturn(itemCreateJob); - - // Serializer mock returning the item for the note - Utils::MockObject<Akonadi::SerializerInterface> serializerMock; - serializerMock(&Akonadi::SerializerInterface::createItemFromNote).when(note).thenReturn(item); - - // WHEN - QScopedPointer<Akonadi::NoteRepository> repository(new Akonadi::NoteRepository(storageMock.getInstance(), - serializerMock.getInstance())); - repository->create(note)->exec(); - - // THEN - QVERIFY(serializerMock(&Akonadi::SerializerInterface::createItemFromNote).when(note).exactly(1)); - QVERIFY(storageMock(&Akonadi::StorageInterface::defaultNoteCollection).when().exactly(1)); - QVERIFY(storageMock(&Akonadi::StorageInterface::createItem).when(item, col).exactly(1)); - } - - void shouldCreateNewItemsInFirstWritableCollectionIfNothingInSettings() - { - // GIVEN - - // A few collections - auto col1 = Akonadi::Collection(42); - col1.setRights(Akonadi::Collection::ReadOnly); - auto col2 = Akonadi::Collection(43); - col2.setRights(Akonadi::Collection::CanCreateItem); - auto col3 = Akonadi::Collection(44); - col3.setRights(Akonadi::Collection::CanCreateItem - | Akonadi::Collection::CanChangeItem - | Akonadi::Collection::CanDeleteItem); - auto collectionFetchJob = new Testlib::AkonadiFakeCollectionFetchJob; - collectionFetchJob->setCollections(Akonadi::Collection::List() << col1 << col2 << col3); - - // A note and its corresponding item not existing in storage yet - auto item = Akonadi::Item(); - auto note = Domain::Note::Ptr::create(); - - // A mock create job - auto itemCreateJob = new FakeJob(this); - - // Storage mock returning the create job and with no default collection - Utils::MockObject<Akonadi::StorageInterface> storageMock; - storageMock(&Akonadi::StorageInterface::defaultNoteCollection).when().thenReturn(Akonadi::Collection()); - storageMock(&Akonadi::StorageInterface::fetchCollections).when(Akonadi::Collection::root(), - Akonadi::StorageInterface::Recursive, - Akonadi::StorageInterface::Notes) - .thenReturn(collectionFetchJob); - storageMock(&Akonadi::StorageInterface::createItem).when(item, col3) - .thenReturn(itemCreateJob); - - // Serializer mock returning the item for the note - Utils::MockObject<Akonadi::SerializerInterface> serializerMock; - serializerMock(&Akonadi::SerializerInterface::createItemFromNote).when(note).thenReturn(item); - - // WHEN - QScopedPointer<Akonadi::NoteRepository> repository(new Akonadi::NoteRepository(storageMock.getInstance(), - serializerMock.getInstance())); - repository->create(note)->exec(); - - // THEN - QVERIFY(serializerMock(&Akonadi::SerializerInterface::createItemFromNote).when(note).exactly(1)); - QVERIFY(storageMock(&Akonadi::StorageInterface::defaultNoteCollection).when().exactly(1)); - QVERIFY(storageMock(&Akonadi::StorageInterface::createItem).when(item, col3).exactly(1)); - } - - void shouldEmitErrorIfNoFallbackCollectionIsFound() - { - // GIVEN - - // A few collections - auto col1 = Akonadi::Collection(42); - col1.setRights(Akonadi::Collection::ReadOnly); - auto col2 = Akonadi::Collection(43); - col2.setRights(Akonadi::Collection::ReadOnly); - auto col3 = Akonadi::Collection(44); - col3.setRights(Akonadi::Collection::ReadOnly); - auto collectionFetchJob = new Testlib::AkonadiFakeCollectionFetchJob; - collectionFetchJob->setCollections(Akonadi::Collection::List() << col1 << col2 << col3); - - // A task and its corresponding item not existing in storage yet - Akonadi::Item item; - Domain::Note::Ptr note(new Domain::Note); - - // Storage mock returning the create job and with no default collection - Utils::MockObject<Akonadi::StorageInterface> storageMock; - storageMock(&Akonadi::StorageInterface::defaultNoteCollection).when().thenReturn(Akonadi::Collection()); - storageMock(&Akonadi::StorageInterface::fetchCollections).when(Akonadi::Collection::root(), - Akonadi::StorageInterface::Recursive, - Akonadi::StorageInterface::Notes) - .thenReturn(collectionFetchJob); - - // Serializer mock returning the item for the task - Utils::MockObject<Akonadi::SerializerInterface> serializerMock; - serializerMock(&Akonadi::SerializerInterface::createItemFromNote).when(note).thenReturn(item); - - // WHEN - QScopedPointer<Akonadi::NoteRepository> repository(new Akonadi::NoteRepository(storageMock.getInstance(), - serializerMock.getInstance())); - auto job = repository->create(note); - job->exec(); - - // THEN - QVERIFY(serializerMock(&Akonadi::SerializerInterface::createItemFromNote).when(note).exactly(1)); - QVERIFY(storageMock(&Akonadi::StorageInterface::defaultNoteCollection).when().exactly(1)); - QVERIFY(job->error()); - QVERIFY(!job->errorText().isEmpty()); - } - - void shouldCreateNewItemsInTag() - { - // GIVEN - // a tag - Akonadi::Tag akonadiTag; - akonadiTag.setName(QStringLiteral("akonadiTag42")); - akonadiTag.setId(42); - - // the domain Tag related to the Akonadi Tag - auto tag = Domain::Tag::Ptr::create(); - - // a default collection - Akonadi::Collection defaultCollection(42); - - // A note and its corresponding item not existing in storage yet - Akonadi::Item noteItem; - auto note = Domain::Note::Ptr::create(); - - // A mock create job - auto itemCreateJob = new FakeJob(this); - - // serializer mock returning the item for the task - Utils::MockObject<Akonadi::SerializerInterface> serializerMock; - - serializerMock(&Akonadi::SerializerInterface::createAkonadiTagFromTag).when(tag).thenReturn(akonadiTag); - serializerMock(&Akonadi::SerializerInterface::createItemFromNote).when(note).thenReturn(noteItem); - - // Storage mock returning the create job - Utils::MockObject<Akonadi::StorageInterface> storageMock; - - storageMock(&Akonadi::StorageInterface::defaultNoteCollection).when().thenReturn(defaultCollection); - storageMock(&Akonadi::StorageInterface::createItem).when(noteItem, defaultCollection).thenReturn(itemCreateJob); - - // WHEN - QScopedPointer<Akonadi::NoteRepository> repository(new Akonadi::NoteRepository(storageMock.getInstance(), - serializerMock.getInstance())); - - repository->createInTag(note, tag)->exec(); - - // THEN - - QVERIFY(serializerMock(&Akonadi::SerializerInterface::createItemFromNote).when(note).exactly(1)); - QVERIFY(serializerMock(&Akonadi::SerializerInterface::createAkonadiTagFromTag).when(tag).exactly(1)); - - QVERIFY(storageMock(&Akonadi::StorageInterface::defaultNoteCollection).when().exactly(1)); - QVERIFY(storageMock(&Akonadi::StorageInterface::createItem).when(noteItem, defaultCollection).exactly(1)); - } - - void shouldUpdateExistingItems() - { - // GIVEN - - // A default collection for saving - auto col = Akonadi::Collection(42); - - // A note and its corresponding item already existing in storage - auto item = Akonadi::Item(42); - auto note = Domain::Note::Ptr::create(); - - // A mock create job - auto itemModifyJob = new FakeJob(this); - - // Storage mock returning the create job - Utils::MockObject<Akonadi::StorageInterface> storageMock; - storageMock(&Akonadi::StorageInterface::updateItem).when(item, Q_NULLPTR) - .thenReturn(itemModifyJob); - - // Serializer mock returning the item for the task - Utils::MockObject<Akonadi::SerializerInterface> serializerMock; - serializerMock(&Akonadi::SerializerInterface::createItemFromNote).when(note).thenReturn(item); - - // WHEN - QScopedPointer<Akonadi::NoteRepository> repository(new Akonadi::NoteRepository(storageMock.getInstance(), - serializerMock.getInstance())); - repository->update(note)->exec(); - - // THEN - QVERIFY(serializerMock(&Akonadi::SerializerInterface::createItemFromNote).when(note).exactly(1)); - QVERIFY(storageMock(&Akonadi::StorageInterface::updateItem).when(item, Q_NULLPTR).exactly(1)); - } - - void shouldRemoveANote() - { - // GIVEN - Akonadi::Item item(42); - Domain::Note::Ptr note(new Domain::Note); - - // A mock delete job - auto itemDeleteJob = new FakeJob(this); - - // Storage mock returning the delete job - Utils::MockObject<Akonadi::StorageInterface> storageMock; - storageMock(&Akonadi::StorageInterface::removeItem).when(item) - .thenReturn(itemDeleteJob); - - // Serializer mock returning the item for the note - Utils::MockObject<Akonadi::SerializerInterface> serializerMock; - serializerMock(&Akonadi::SerializerInterface::createItemFromNote).when(note).thenReturn(item); - - // WHEN - QScopedPointer<Akonadi::NoteRepository> repository(new Akonadi::NoteRepository(storageMock.getInstance(), - serializerMock.getInstance())); - repository->remove(note)->exec(); - - // THEN - QVERIFY(serializerMock(&Akonadi::SerializerInterface::createItemFromNote).when(note).exactly(1)); - QVERIFY(storageMock(&Akonadi::StorageInterface::removeItem).when(item).exactly(1)); - } -}; - -ZANSHIN_TEST_MAIN(AkonadiNoteRepositoryTest) - -#include "akonadinoterepositorytest.moc" diff --git a/tests/units/akonadi/akonadiprojectrepositorytest.cpp b/tests/units/akonadi/akonadiprojectrepositorytest.cpp index e7c97975..e5efd1bc 100644 --- a/tests/units/akonadi/akonadiprojectrepositorytest.cpp +++ b/tests/units/akonadi/akonadiprojectrepositorytest.cpp @@ -1,405 +1,358 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens <ervin@kde.org> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include <testlib/qtest_zanshin.h> #include "utils/mockobject.h" #include "testlib/akonadifakejobs.h" #include "testlib/akonadifakemonitor.h" #include "akonadi/akonadiprojectrepository.h" #include "akonadi/akonadiserializerinterface.h" #include "akonadi/akonadistorageinterface.h" using namespace mockitopp; Q_DECLARE_METATYPE(Testlib::AkonadiFakeItemFetchJob*) class AkonadiProjectRepositoryTest : public QObject { Q_OBJECT private slots: void shouldCreateProjectInDataSource() { // GIVEN // A project and its corresponding item already not existing in storage Akonadi::Item item; auto project = Domain::Project::Ptr::create(); // A data source and its corresponding collection existing in storage Akonadi::Collection collection(42); auto source = Domain::DataSource::Ptr::create(); // A mock create job auto itemCreateJob = new FakeJob(this); // Storage mock returning the create job Utils::MockObject<Akonadi::StorageInterface> storageMock; storageMock(&Akonadi::StorageInterface::createItem).when(item, collection) .thenReturn(itemCreateJob); // Serializer mock Utils::MockObject<Akonadi::SerializerInterface> serializerMock; serializerMock(&Akonadi::SerializerInterface::createItemFromProject).when(project).thenReturn(item); serializerMock(&Akonadi::SerializerInterface::createCollectionFromDataSource).when(source).thenReturn(collection); // WHEN QScopedPointer<Akonadi::ProjectRepository> repository(new Akonadi::ProjectRepository(storageMock.getInstance(), serializerMock.getInstance())); repository->create(project, source)->exec(); // THEN QVERIFY(storageMock(&Akonadi::StorageInterface::createItem).when(item, collection).exactly(1)); } void shouldUpdateExistingProject() { // GIVEN // A project and its corresponding item already existing in storage Akonadi::Item item(42); Domain::Project::Ptr project(new Domain::Project); // A mock modify job auto itemModifyJob = new FakeJob(this); // Storage mock returning the create job Utils::MockObject<Akonadi::StorageInterface> storageMock; storageMock(&Akonadi::StorageInterface::updateItem).when(item, Q_NULLPTR) .thenReturn(itemModifyJob); // Serializer mock returning the item for the project Utils::MockObject<Akonadi::SerializerInterface> serializerMock; serializerMock(&Akonadi::SerializerInterface::createItemFromProject).when(project).thenReturn(item); // WHEN QScopedPointer<Akonadi::ProjectRepository> repository(new Akonadi::ProjectRepository(storageMock.getInstance(), serializerMock.getInstance())); repository->update(project)->exec(); // THEN QVERIFY(serializerMock(&Akonadi::SerializerInterface::createItemFromProject).when(project).exactly(1)); QVERIFY(storageMock(&Akonadi::StorageInterface::updateItem).when(item, Q_NULLPTR).exactly(1)); } void shouldRemoveExistingProject() { // GIVEN // A project and its corresponding item already existing in storage Akonadi::Item item(42); auto project = Domain::Project::Ptr::create(); // A mock remove job auto itemRemoveJob = new FakeJob(this); // Storage mock returning the create job Utils::MockObject<Akonadi::StorageInterface> storageMock; storageMock(&Akonadi::StorageInterface::removeItem).when(item) .thenReturn(itemRemoveJob); // Serializer mock returning the item for the project Utils::MockObject<Akonadi::SerializerInterface> serializerMock; serializerMock(&Akonadi::SerializerInterface::createItemFromProject).when(project).thenReturn(item); // WHEN QScopedPointer<Akonadi::ProjectRepository> repository(new Akonadi::ProjectRepository(storageMock.getInstance(), serializerMock.getInstance())); repository->remove(project)->exec(); // THEN QVERIFY(storageMock(&Akonadi::StorageInterface::removeItem).when(item).exactly(1)); } void shouldAssociateAnArtifactToAProject_data() { QTest::addColumn<Akonadi::Item>("childItem"); QTest::addColumn<Akonadi::Item>("parentItem"); QTest::addColumn<Domain::Artifact::Ptr>("child"); QTest::addColumn<Domain::Project::Ptr>("parent"); QTest::addColumn<Testlib::AkonadiFakeItemFetchJob*>("itemFetchJob1"); QTest::addColumn<Testlib::AkonadiFakeItemFetchJob*>("itemFetchJob2"); QTest::addColumn<Testlib::AkonadiFakeItemFetchJob*>("itemFetchJob3"); QTest::addColumn<bool>("execJob"); QTest::addColumn<bool>("execParentJob"); QTest::addColumn<Akonadi::Item::List>("list"); Akonadi::Collection col(40); Akonadi::Item childItem(42); childItem.setParentCollection(col); Domain::Artifact::Ptr childTask(new Domain::Task); - Domain::Artifact::Ptr childNote(new Domain::Note); Akonadi::Item parentItem(41); parentItem.setParentCollection(col); auto parent = Domain::Project::Ptr::create(); auto itemFetchJob1 = new Testlib::AkonadiFakeItemFetchJob(this); itemFetchJob1->setItems(Akonadi::Item::List() << childItem); auto itemFetchJob2 = new Testlib::AkonadiFakeItemFetchJob(this); itemFetchJob2->setItems(Akonadi::Item::List() << parentItem); auto itemFetchJob3 = new Testlib::AkonadiFakeItemFetchJob(this); Akonadi::Item::List list; QTest::newRow("nominal case (task)") << childItem << parentItem << childTask << parent << itemFetchJob1 << itemFetchJob2 << itemFetchJob3 << true << true << list; - itemFetchJob1 = new Testlib::AkonadiFakeItemFetchJob(this); - itemFetchJob1->setItems(Akonadi::Item::List() << childItem); - itemFetchJob2 = new Testlib::AkonadiFakeItemFetchJob(this); - itemFetchJob2->setItems(Akonadi::Item::List() << parentItem); - itemFetchJob3 = new Testlib::AkonadiFakeItemFetchJob(this); - QTest::newRow("nominal case (note)") << childItem << parentItem << childNote << parent << itemFetchJob1 << itemFetchJob2 << itemFetchJob3 << true << true << list; - itemFetchJob1 = new Testlib::AkonadiFakeItemFetchJob(this); itemFetchJob1->setExpectedError(KJob::KilledJobError); QTest::newRow("child job error with empty list") << childItem << parentItem << childTask << parent << itemFetchJob1 << itemFetchJob2 << itemFetchJob3 << false << false << list; itemFetchJob1 = new Testlib::AkonadiFakeItemFetchJob(this); itemFetchJob1->setExpectedError(KJob::KilledJobError); itemFetchJob1->setItems(Akonadi::Item::List() << childItem); QTest::newRow("child job error with item (task)") << childItem << parentItem << childTask << parent << itemFetchJob1 << itemFetchJob2 << itemFetchJob3 << false << false << list; - itemFetchJob1 = new Testlib::AkonadiFakeItemFetchJob(this); - itemFetchJob1->setExpectedError(KJob::KilledJobError); - itemFetchJob1->setItems(Akonadi::Item::List() << childItem); - QTest::newRow("child job error with item (note)") << childItem << parentItem << childNote << parent << itemFetchJob1 << itemFetchJob2 << itemFetchJob3 << false << false << list; - itemFetchJob1 = new Testlib::AkonadiFakeItemFetchJob(this); itemFetchJob1->setItems(Akonadi::Item::List() << childItem); itemFetchJob2 = new Testlib::AkonadiFakeItemFetchJob(this); itemFetchJob2->setExpectedError(KJob::KilledJobError); QTest::newRow("parent job error with empty list (task)") << childItem << parentItem << childTask << parent << itemFetchJob1 << itemFetchJob2 << itemFetchJob3 << true << false << list; - itemFetchJob1 = new Testlib::AkonadiFakeItemFetchJob(this); - itemFetchJob1->setItems(Akonadi::Item::List() << childItem); - itemFetchJob2 = new Testlib::AkonadiFakeItemFetchJob(this); - itemFetchJob2->setExpectedError(KJob::KilledJobError); - QTest::newRow("parent job error with empty list (note)") << childItem << parentItem << childNote << parent << itemFetchJob1 << itemFetchJob2 << itemFetchJob3 << true << false << list; - itemFetchJob1 = new Testlib::AkonadiFakeItemFetchJob(this); itemFetchJob1->setItems(Akonadi::Item::List() << childItem); itemFetchJob2 = new Testlib::AkonadiFakeItemFetchJob(this); itemFetchJob2->setExpectedError(KJob::KilledJobError); itemFetchJob2->setItems(Akonadi::Item::List() << parentItem); QTest::newRow("parent job error with item (task)") << childItem << parentItem << childTask << parent << itemFetchJob1 << itemFetchJob2 << itemFetchJob3 << true << false << list; - itemFetchJob1 = new Testlib::AkonadiFakeItemFetchJob(this); - itemFetchJob1->setItems(Akonadi::Item::List() << childItem); - itemFetchJob2 = new Testlib::AkonadiFakeItemFetchJob(this); - itemFetchJob2->setExpectedError(KJob::KilledJobError); - itemFetchJob2->setItems(Akonadi::Item::List() << parentItem); - QTest::newRow("parent job error with item (note)") << childItem << parentItem << childNote << parent << itemFetchJob1 << itemFetchJob2 << itemFetchJob3 << true << false << list; - itemFetchJob1 = new Testlib::AkonadiFakeItemFetchJob(this); itemFetchJob1->setItems(Akonadi::Item::List() << childItem); itemFetchJob2 = new Testlib::AkonadiFakeItemFetchJob(this); Akonadi::Collection col2(39); Akonadi::Item parentItem2(41); parentItem2.setParentCollection(col2); itemFetchJob2->setItems(Akonadi::Item::List() << parentItem2); itemFetchJob3 = new Testlib::AkonadiFakeItemFetchJob(this); QTest::newRow("update and move item (task)") << childItem << parentItem2 << childTask << parent << itemFetchJob1 << itemFetchJob2 << itemFetchJob3 << true << true << list; itemFetchJob1 = new Testlib::AkonadiFakeItemFetchJob(this); itemFetchJob1->setItems(Akonadi::Item::List() << childItem); itemFetchJob2 = new Testlib::AkonadiFakeItemFetchJob(this); itemFetchJob2->setItems(Akonadi::Item::List() << parentItem2); itemFetchJob3 = new Testlib::AkonadiFakeItemFetchJob(this); Akonadi::Item childItem2(43); Akonadi::Item::List list2; list2 << childItem2; itemFetchJob3->setItems(list2); QTest::newRow("update and move item and his child (task)") << childItem << parentItem2 << childTask << parent << itemFetchJob1 << itemFetchJob2 << itemFetchJob3 << true << true << list2; } void shouldAssociateAnArtifactToAProject() { // GIVEN QFETCH(Akonadi::Item, childItem); QFETCH(Akonadi::Item, parentItem); QFETCH(Domain::Artifact::Ptr, child); QFETCH(Domain::Project::Ptr, parent); QFETCH(Testlib::AkonadiFakeItemFetchJob*, itemFetchJob1); QFETCH(Testlib::AkonadiFakeItemFetchJob*, itemFetchJob2); QFETCH(Testlib::AkonadiFakeItemFetchJob*, itemFetchJob3); QFETCH(bool, execJob); QFETCH(bool, execParentJob); QFETCH(Akonadi::Item::List, list); // A mock create job auto itemModifyJob = new FakeJob(this); auto transactionJob = new FakeJob(this); auto itemsMoveJob = new FakeJob(this); Akonadi::Item::List movedList; movedList << childItem << list; // Storage mock returning the create job Utils::MockObject<Akonadi::StorageInterface> storageMock; storageMock(&Akonadi::StorageInterface::fetchItem).when(childItem) .thenReturn(itemFetchJob1); storageMock(&Akonadi::StorageInterface::fetchItem).when(parentItem) .thenReturn(itemFetchJob2); if (child.objectCast<Domain::Task>() && parentItem.parentCollection().id() != childItem.parentCollection().id()) { storageMock(&Akonadi::StorageInterface::fetchItems).when(childItem.parentCollection()) .thenReturn(itemFetchJob3); storageMock(&Akonadi::StorageInterface::createTransaction).when().thenReturn(transactionJob); storageMock(&Akonadi::StorageInterface::updateItem).when(childItem, transactionJob) .thenReturn(itemModifyJob); storageMock(&Akonadi::StorageInterface::moveItems).when(movedList, parentItem.parentCollection(), transactionJob) .thenReturn(itemsMoveJob); } else { storageMock(&Akonadi::StorageInterface::updateItem).when(childItem, Q_NULLPTR) .thenReturn(itemModifyJob); } // Serializer mock returning the item for the task Utils::MockObject<Akonadi::SerializerInterface> serializerMock; serializerMock(&Akonadi::SerializerInterface::createItemFromTask).when(child.objectCast<Domain::Task>()).thenReturn(childItem); - serializerMock(&Akonadi::SerializerInterface::createItemFromNote).when(child.objectCast<Domain::Note>()).thenReturn(childItem); serializerMock(&Akonadi::SerializerInterface::createItemFromProject).when(parent).thenReturn(parentItem); serializerMock(&Akonadi::SerializerInterface::updateItemProject).when(childItem, parent).thenReturn(); if (execParentJob) serializerMock(&Akonadi::SerializerInterface::filterDescendantItems).when(list, childItem).thenReturn(list); // WHEN QScopedPointer<Akonadi::ProjectRepository> repository(new Akonadi::ProjectRepository(storageMock.getInstance(), serializerMock.getInstance())); auto associateJob = repository->associate(parent, child); if (execJob) associateJob->exec(); // THEN QVERIFY(storageMock(&Akonadi::StorageInterface::fetchItem).when(childItem).exactly(1)); if (execJob) { QVERIFY(serializerMock(&Akonadi::SerializerInterface::updateItemProject).when(childItem, parent).exactly(1)); QVERIFY(serializerMock(&Akonadi::SerializerInterface::createItemFromProject).when(parent).exactly(1)); QVERIFY(storageMock(&Akonadi::StorageInterface::fetchItem).when(parentItem).exactly(1)); if (execParentJob) { if (child.objectCast<Domain::Task>() && parentItem.parentCollection().id() != childItem.parentCollection().id()) { QVERIFY(storageMock(&Akonadi::StorageInterface::fetchItems).when(childItem.parentCollection()).exactly(1)); QVERIFY(storageMock(&Akonadi::StorageInterface::createTransaction).when().thenReturn(transactionJob).exactly(1)); QVERIFY(storageMock(&Akonadi::StorageInterface::updateItem).when(childItem, transactionJob).exactly(1)); QVERIFY(storageMock(&Akonadi::StorageInterface::moveItems).when(movedList, parentItem.parentCollection(), transactionJob).exactly(1)); } else { QVERIFY(storageMock(&Akonadi::StorageInterface::updateItem).when(childItem, Q_NULLPTR).exactly(1)); } } } } void shouldDissociateAnArtifactFromItsProject_data() { QTest::addColumn<Domain::Artifact::Ptr>("child"); QTest::addColumn<Akonadi::Item>("childItem"); QTest::addColumn<Testlib::AkonadiFakeItemFetchJob*>("itemFetchJob"); QTest::addColumn<bool>("fetchJobFailed"); Domain::Artifact::Ptr taskChild(new Domain::Task); - Domain::Artifact::Ptr noteChild(new Domain::Note); Akonadi::Item childItem(42); auto itemFetchJob = new Testlib::AkonadiFakeItemFetchJob(this); itemFetchJob->setItems(Akonadi::Item::List() << childItem); QTest::newRow("task nominal case") << taskChild << childItem << itemFetchJob << false; itemFetchJob = new Testlib::AkonadiFakeItemFetchJob(this); itemFetchJob->setExpectedError(KJob::KilledJobError); QTest::newRow("task job error with empty list") << taskChild << childItem << itemFetchJob << true; itemFetchJob = new Testlib::AkonadiFakeItemFetchJob(this); itemFetchJob->setExpectedError(KJob::KilledJobError); itemFetchJob->setItems(Akonadi::Item::List() << childItem); QTest::newRow("task job error with item") << taskChild << childItem << itemFetchJob << true; - - itemFetchJob = new Testlib::AkonadiFakeItemFetchJob(this); - itemFetchJob->setItems(Akonadi::Item::List() << childItem); - QTest::newRow("note nominal case") << noteChild << childItem << itemFetchJob << false; - - itemFetchJob = new Testlib::AkonadiFakeItemFetchJob(this); - itemFetchJob->setExpectedError(KJob::KilledJobError); - QTest::newRow("note job error with empty list") << noteChild << childItem << itemFetchJob << true; - - itemFetchJob = new Testlib::AkonadiFakeItemFetchJob(this); - itemFetchJob->setExpectedError(KJob::KilledJobError); - itemFetchJob->setItems(Akonadi::Item::List() << childItem); - QTest::newRow("note job error with item") << noteChild << childItem << itemFetchJob << true; } void shouldDissociateAnArtifactFromItsProject() { // GIVEN QFETCH(Domain::Artifact::Ptr, child); QFETCH(Akonadi::Item, childItem); QFETCH(Testlib::AkonadiFakeItemFetchJob*, itemFetchJob); QFETCH(bool, fetchJobFailed); auto itemModifyJob = new FakeJob(this); // Storage mock returning the delete job Utils::MockObject<Akonadi::StorageInterface> storageMock; storageMock(&Akonadi::StorageInterface::updateItem).when(childItem, Q_NULLPTR) .thenReturn(itemModifyJob); storageMock(&Akonadi::StorageInterface::fetchItem).when(childItem) .thenReturn(itemFetchJob); // Serializer mock returning the item for the task Utils::MockObject<Akonadi::SerializerInterface> serializerMock; - if (child.objectCast<Domain::Task>()) - serializerMock(&Akonadi::SerializerInterface::createItemFromTask).when(child.objectCast<Domain::Task>()).thenReturn(childItem); - else - serializerMock(&Akonadi::SerializerInterface::createItemFromNote).when(child.objectCast<Domain::Note>()).thenReturn(childItem); + serializerMock(&Akonadi::SerializerInterface::createItemFromTask).when(child.objectCast<Domain::Task>()).thenReturn(childItem); serializerMock(&Akonadi::SerializerInterface::removeItemParent).when(childItem).thenReturn(); // WHEN QScopedPointer<Akonadi::ProjectRepository> repository(new Akonadi::ProjectRepository(storageMock.getInstance(), serializerMock.getInstance())); repository->dissociate(child)->exec(); // THEN - if (child.objectCast<Domain::Task>()) - QVERIFY(serializerMock(&Akonadi::SerializerInterface::createItemFromTask).when(child.objectCast<Domain::Task>()).exactly(1)); - else - QVERIFY(serializerMock(&Akonadi::SerializerInterface::createItemFromNote).when(child.objectCast<Domain::Note>()).exactly(1)); + QVERIFY(serializerMock(&Akonadi::SerializerInterface::createItemFromTask).when(child.objectCast<Domain::Task>()).exactly(1)); QVERIFY(storageMock(&Akonadi::StorageInterface::fetchItem).when(childItem).exactly(1)); if (!fetchJobFailed) { QVERIFY(serializerMock(&Akonadi::SerializerInterface::removeItemParent).when(childItem).exactly(1));; QVERIFY(storageMock(&Akonadi::StorageInterface::updateItem).when(childItem, Q_NULLPTR).exactly(1)); } // Give a chance to job to delete themselves // in case of an error (since they use deleteLater() internally) QTest::qWait(10); } }; ZANSHIN_TEST_MAIN(AkonadiProjectRepositoryTest) #include "akonadiprojectrepositorytest.moc" diff --git a/tests/units/akonadi/akonadiserializertest.cpp b/tests/units/akonadi/akonadiserializertest.cpp index 15bd38c8..05f1e85d 100644 --- a/tests/units/akonadi/akonadiserializertest.cpp +++ b/tests/units/akonadi/akonadiserializertest.cpp @@ -1,2597 +1,2117 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens <ervin@kde.org> Copyright 2014 Rémi Benoit <r3m1.benoit@gmail.com> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include <testlib/qtest_zanshin.h> #include "akonadi/akonadiserializer.h" #include "akonadi/akonadiapplicationselectedattribute.h" #include "akonadi/akonaditimestampattribute.h" #include <AkonadiCore/Collection> #include <AkonadiCore/EntityDisplayAttribute> #include <AkonadiCore/Item> #include <Akonadi/Notes/NoteUtils> #include <AkonadiCore/Tag> #include <KCalCore/Todo> #include <KMime/Message> 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 shouldKnowWhenAnAkonadiTagRepresentsATag() - { - // GIVEN - Akonadi::Serializer serializer; - Akonadi::Tag akondiTag(42); - auto tag = Domain::Tag::Ptr::create(); - - // WHEN - // Nothing yet - // THEN - QVERIFY(!serializer.representsAkonadiTag(tag, akondiTag)); - - // WHEN - tag->setProperty("tagId", 42); - - // THEN - QVERIFY(serializer.representsAkonadiTag(tag, akondiTag)); - - // WHEN - tag->setProperty("tagId", 43); - - // THEN - QVERIFY(!serializer.representsAkonadiTag(tag, akondiTag)); - } - void shouldKnowTaskItemUid_data() { QTest::addColumn<Akonadi::Item>("item"); QTest::addColumn<QString>("expectedUid"); Akonadi::Item item1; KCalCore::Todo::Ptr todo1(new KCalCore::Todo); todo1->setUid(QString()); item1.setPayload<KCalCore::Todo::Ptr>(todo1); Akonadi::Item item2; KCalCore::Todo::Ptr todo2(new KCalCore::Todo); todo2->setUid(QStringLiteral("1")); item2.setPayload<KCalCore::Todo::Ptr>(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<KMime::Message::Ptr>(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<QString>("name"); QTest::addColumn<QString>("iconName"); QTest::addColumn<QStringList>("mimeTypes"); QTest::addColumn<bool>("hasSelectedAttribute"); QTest::addColumn<bool>("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("text/x-vnd.akonadi.note"))) { - expectedContentTypes |= Domain::DataSource::Notes; - } 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<Akonadi::Collection::Id>(), 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<QString>("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<QString>("name"); QTest::addColumn<QString>("iconName"); QTest::addColumn<Domain::DataSource::ContentTypes>("contentTypes"); QTest::addColumn<bool>("isSelected"); const auto noType = Domain::DataSource::ContentTypes(Domain::DataSource::NoContent); const auto taskType = Domain::DataSource::ContentTypes(Domain::DataSource::Tasks); const auto noteType = Domain::DataSource::ContentTypes(Domain::DataSource::Notes); const auto allTypes = taskType | noteType; QTest::newRow("nominal case") << "name" << "icon-name" << allTypes << true; QTest::newRow("only notes") << "name" << "icon-name" << noteType << true; 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" << allTypes << false; QTest::newRow("selected") << "name" << "icon-name" << allTypes << 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"); if (contentTypes & Domain::DataSource::Notes) mimeTypes << QStringLiteral("text/x-vnd.akonadi.note"); // ... 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<Akonadi::Collection::Id>()); QVERIFY(collection.hasAttribute<Akonadi::ApplicationSelectedAttribute>()); QCOMPARE(collection.attribute<Akonadi::ApplicationSelectedAttribute>()->isSelected(), isSelected); QVERIFY(collection.hasAttribute<Akonadi::TimestampAttribute>()); QVERIFY(collection.attribute<Akonadi::TimestampAttribute>()->timestamp() >= timestamp); } void shouldVerifyIfCollectionIsSelected_data() { QTest::addColumn<QStringList>("mimeTypes"); QTest::addColumn<bool>("hasSelectedAttribute"); QTest::addColumn<bool>("isSelected"); QTest::addColumn<bool>("expectedSelected"); - 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; + const auto allMimeTypes = taskMimeTypes + bogusMimeTypes; QTest::newRow("nominal case") << allMimeTypes << true << false << false; - QTest::newRow("only notes") << noteMimeTypes << true << false << false; QTest::newRow("only tasks") << taskMimeTypes << true << false << false; QTest::newRow("only bogus") << bogusMimeTypes << true << false << false; - QTest::newRow("selected, only notes") << noteMimeTypes << true << true << true; 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("text/x-vnd.akonadi.note"))) { expectedContentTypes |= Domain::DataSource::Notes; } 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<QString>("mimeType"); - QTest::addColumn<bool>("expectedNotes"); QTest::addColumn<bool>("expectedTasks"); - QTest::newRow("task collection") << "application/x-vnd.akonadi.calendar.todo" << false << true; - QTest::newRow("note collection") << "text/x-vnd.akonadi.note" << true << false; + 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, expectedNotes); QFETCH(bool, expectedTasks); // THEN - QCOMPARE(serializer.isNoteCollection(collection), expectedNotes); QCOMPARE(serializer.isTaskCollection(collection), expectedTasks); } void shouldCreateTaskFromItem_data() { QTest::addColumn<QString>("summary"); QTest::addColumn<QString>("content"); QTest::addColumn<bool>("isDone"); QTest::addColumn<QDate>("doneDate"); QTest::addColumn<QDate>("startDate"); QTest::addColumn<QDate>("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<KCalCore::Todo::Ptr>(todo); // which has a parent collection Akonadi::Collection collection(43); item.setParentCollection(collection); // WHEN Akonadi::Serializer serializer; Domain::Task::Ptr task = serializer.createTaskFromItem(item); auto artifact = serializer.createArtifactFromItem(item).dynamicCast<Domain::Task>(); // 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("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()); QVERIFY(!artifact.isNull()); QCOMPARE(artifact->title(), summary); QCOMPARE(artifact->text(), content); QCOMPARE(artifact->isDone(), isDone); QCOMPARE(artifact->doneDate(), doneDate); QCOMPARE(artifact->startDate(), startDate); QCOMPARE(artifact->dueDate(), dueDate); QCOMPARE(artifact->property("todoUid").toString(), todo->uid()); QCOMPARE(artifact->property("relatedUid").toString(), todo->relatedTo()); QCOMPARE(artifact->property("itemId").toLongLong(), item.id()); QCOMPARE(artifact->property("parentCollectionId").toLongLong(), collection.id()); } void shouldCreateNullTaskFromInvalidItem() { // GIVEN Akonadi::Item item; // WHEN Akonadi::Serializer serializer; Domain::Task::Ptr task = serializer.createTaskFromItem(item); auto artifact = serializer.createArtifactFromItem(item); // THEN QVERIFY(task.isNull()); QVERIFY(artifact.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<KCalCore::Todo::Ptr>(todo); // WHEN Akonadi::Serializer serializer; Domain::Task::Ptr task = serializer.createTaskFromItem(item); auto artifact = serializer.createArtifactFromItem(item); // THEN QVERIFY(task.isNull()); QVERIFY(artifact.isNull()); } void shouldUpdateTaskFromItem_data() { QTest::addColumn<QString>("updatedSummary"); QTest::addColumn<QString>("updatedContent"); QTest::addColumn<bool>("updatedDone"); QTest::addColumn<QDate>("updatedDoneDate"); QTest::addColumn<QDate>("updatedStartDate"); QTest::addColumn<QDate>("updatedDueDate"); QTest::addColumn<QString>("updatedRelated"); QTest::addColumn<bool>("updatedRecurs"); QTest::addColumn<QByteArrayList>("updatedAttachmentData"); QTest::addColumn<QStringList>("updatedAttachmentUris"); QTest::addColumn<QStringList>("updatedAttachmentLabels"); QTest::addColumn<QStringList>("updatedAttachmentMimeTypes"); QTest::addColumn<QStringList>("updatedAttachmentIconNames"); QTest::addColumn<bool>("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<KCalCore::Todo::Ptr>(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); auto artifact = serializer.createArtifactFromItem(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<KCalCore::Todo::Ptr>(updatedTodo); // ... which has a new parent collection Akonadi::Collection updatedCollection(45); updatedItem.setParentCollection(updatedCollection); serializer.updateTaskFromItem(task, updatedItem); serializer.updateArtifactFromItem(artifact, 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); task = artifact.dynamicCast<Domain::Task>(); 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<int>("todoRecurrence"); QTest::addColumn<Domain::Task::Recurrence>("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<KCalCore::Todo::Ptr>(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<KCalCore::Todo::Ptr>(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<KCalCore::Todo::Ptr>(originalTodo); // ... deserialized as a task Akonadi::Serializer serializer; auto task = serializer.createTaskFromItem(originalItem); auto artifact = serializer.createArtifactFromItem(originalItem); // WHEN Akonadi::Item invalidItem; serializer.updateTaskFromItem(task, invalidItem); serializer.updateArtifactFromItem(artifact, 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()); task = artifact.dynamicCast<Domain::Task>(); 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<KCalCore::Todo::Ptr>(originalTodo); // ... deserialized as a task Akonadi::Serializer serializer; auto task = serializer.createTaskFromItem(originalItem); auto artifact = serializer.createArtifactFromItem(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<KCalCore::Todo::Ptr>(projectTodo); serializer.updateTaskFromItem(task, projectItem); serializer.updateArtifactFromItem(artifact, 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()); task = artifact.dynamicCast<Domain::Task>(); 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<QString>("summary"); QTest::addColumn<QString>("content"); QTest::addColumn<bool>("isDone"); QTest::addColumn<QDate>("doneDate"); QTest::addColumn<QDate>("startDate"); QTest::addColumn<QDate>("dueDate"); QTest::addColumn<qint64>("itemId"); QTest::addColumn<qint64>("parentCollectionId"); QTest::addColumn<QString>("todoUid"); QTest::addColumn<Domain::Task::Recurrence>("recurrence"); QTest::addColumn<Domain::Task::Attachments>("attachments"); QTest::addColumn<bool>("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<KCalCore::Todo::Ptr>(); 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<Domain::Task::Ptr>("task"); QTest::addColumn<Akonadi::Item>("item"); QTest::addColumn<bool>("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<KCalCore::Todo::Ptr>(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<KCalCore::Todo::Ptr>(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<Akonadi::Item>("item"); QTest::addColumn<QString>("expectedUid"); Akonadi::Item item1; KCalCore::Todo::Ptr todo1(new KCalCore::Todo); item1.setPayload<KCalCore::Todo::Ptr>(todo1); Akonadi::Item item2; KCalCore::Todo::Ptr todo2(new KCalCore::Todo); todo2->setRelatedTo(QStringLiteral("1")); item2.setPayload<KCalCore::Todo::Ptr>(todo2); - Akonadi::Item item3; - KMime::Message::Ptr message1(new KMime::Message); - message1->subject(true)->fromUnicodeString(QStringLiteral("foo"), "utf-8"); - message1->mainBodyPart()->fromUnicodeString(QStringLiteral("bar")); - item3.setMimeType(Akonadi::NoteUtils::noteMimeType()); - item3.setPayload<KMime::Message::Ptr>(message1); - - Akonadi::Item item4; - KMime::Message::Ptr message2(new KMime::Message); - message2->subject(true)->fromUnicodeString(QStringLiteral("foo"), "utf-8"); - message2->mainBodyPart()->fromUnicodeString(QStringLiteral("bar")); - auto relatedHeader1 = new KMime::Headers::Generic("X-Zanshin-RelatedProjectUid"); - relatedHeader1->from7BitString("1"); - message2->appendHeader(relatedHeader1); - item4.setMimeType(Akonadi::NoteUtils::noteMimeType()); - item4.setPayload<KMime::Message::Ptr>(message2); - - Akonadi::Item item5; - KMime::Message::Ptr message3(new KMime::Message); - message3->subject(true)->fromUnicodeString(QStringLiteral("foo"), "utf-8"); - message3->mainBodyPart()->fromUnicodeString(QStringLiteral("bar")); - auto relatedHeader2 = new KMime::Headers::Generic("X-Zanshin-RelatedProjectUid"); - message3->appendHeader(relatedHeader2); - item5.setMimeType(Akonadi::NoteUtils::noteMimeType()); - item5.setPayload<KMime::Message::Ptr>(message3); - QTest::newRow("task without related") << item1 << QString(); QTest::newRow("task with related") << item2 << "1"; - QTest::newRow("note without related") << item3 << QString(); - QTest::newRow("note with related") << item4 << "1"; - QTest::newRow("note with empty related") << item5 << QString(); } 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<QString>("title"); QTest::addColumn<QString>("text"); QTest::addColumn<QString>("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 shouldCreateNoteFromItem() - { - // GIVEN - - // Data... - QFETCH(QString, title); - QFETCH(QString, text); - QFETCH(QString, relatedUid); - - // ... stored in a message... - KMime::Message::Ptr message(new KMime::Message); - message->subject(true)->fromUnicodeString(title, "utf-8"); - message->mainBodyPart()->fromUnicodeString(text); - - if (!relatedUid.isEmpty()) { - auto relatedHeader = new KMime::Headers::Generic("X-Zanshin-RelatedProjectUid"); - relatedHeader->from7BitString(relatedUid.toUtf8()); - message->appendHeader(relatedHeader); - } - - // ... as payload of an item. - Akonadi::Item item; - item.setMimeType(Akonadi::NoteUtils::noteMimeType()); - item.setPayload<KMime::Message::Ptr>(message); - - // WHEN - Akonadi::Serializer serializer; - Domain::Note::Ptr note = serializer.createNoteFromItem(item); - auto artifact = serializer.createArtifactFromItem(item).dynamicCast<Domain::Note>(); - - // THEN - const auto expectedText = text.endsWith('\n') ? (text.chop(1), text) : text; - - QCOMPARE(note->title(), title); - QCOMPARE(note->text(), expectedText); - QCOMPARE(note->property("itemId").toLongLong(), item.id()); - QCOMPARE(note->property("relatedUid").toString(), relatedUid); - - QVERIFY(!artifact.isNull()); - QCOMPARE(artifact->title(), title); - QCOMPARE(artifact->text(), expectedText); - QCOMPARE(artifact->property("itemId").toLongLong(), item.id()); - QCOMPARE(artifact->property("relatedUid").toString(), relatedUid); - } - - void shouldCreateNullNoteFromInvalidItem() - { - // GIVEN - Akonadi::Item item; - - // WHEN - Akonadi::Serializer serializer; - Domain::Note::Ptr note = serializer.createNoteFromItem(item); - auto artifact = serializer.createArtifactFromItem(item); - - // THEN - QVERIFY(note.isNull()); - QVERIFY(artifact.isNull()); - } - - void shouldUpdateNoteFromItem_data() - { - QTest::addColumn<QString>("updatedTitle"); - QTest::addColumn<QString>("updatedText"); - QTest::addColumn<QString>("updatedRelatedUid"); - - QTest::newRow("no change") << "title" << "content" << "parent-uid"; - QTest::newRow("data changed (with related)") << "A new title" << "A new content" << "new-parent-uid"; - QTest::newRow("data changed (with no related)") << "A new title" << "A new content" << QString(); - } - - void shouldUpdateNoteFromItem() - { - // GIVEN - - // A message... - KMime::Message::Ptr message(new KMime::Message); - message->subject(true)->fromUnicodeString(QStringLiteral("title"), "utf-8"); - message->mainBodyPart()->fromUnicodeString(QStringLiteral("text")); - auto relatedHeader = new KMime::Headers::Generic("X-Zanshin-RelatedProjectUid"); - relatedHeader->from7BitString("parent-uid"); - message->appendHeader(relatedHeader); - - //... as the payload of an item... - Akonadi::Item item; - item.setMimeType(Akonadi::NoteUtils::noteMimeType()); - item.setPayload<KMime::Message::Ptr>(message); - - //... deserialized as a note - Akonadi::Serializer serializer; - auto note = serializer.createNoteFromItem(item); - auto artifact = serializer.createNoteFromItem(item); - - // WHEN - - // Data... - QFETCH(QString, updatedTitle); - QFETCH(QString, updatedText); - QFETCH(QString, updatedRelatedUid); - - //... stored in a new message... - KMime::Message::Ptr updatedMessage(new KMime::Message); - updatedMessage->subject(true)->fromUnicodeString(updatedTitle, "utf-8"); - updatedMessage->mainBodyPart()->fromUnicodeString(updatedText); - - if (!updatedRelatedUid.isEmpty()) { - relatedHeader = new KMime::Headers::Generic("X-Zanshin-RelatedProjectUid"); - relatedHeader->from7BitString(updatedRelatedUid.toUtf8()); - updatedMessage->appendHeader(relatedHeader); - } - - //... as the payload of a new item... - Akonadi::Item updatedItem; - updatedItem.setMimeType(Akonadi::NoteUtils::noteMimeType()); - updatedItem.setPayload<KMime::Message::Ptr>(updatedMessage); - - serializer.updateNoteFromItem(note, updatedItem); - serializer.updateArtifactFromItem(artifact, updatedItem); - - // THEN - QCOMPARE(note->title(), updatedTitle); - QCOMPARE(note->text(), updatedText); - QCOMPARE(note->property("itemId").toLongLong(), updatedItem.id()); - QCOMPARE(note->property("relatedUid").toString(), updatedRelatedUid); - - note = artifact.dynamicCast<Domain::Note>(); - QCOMPARE(note->title(), updatedTitle); - QCOMPARE(note->text(), updatedText); - QCOMPARE(note->property("itemId").toLongLong(), updatedItem.id()); - QCOMPARE(note->property("relatedUid").toString(), updatedRelatedUid); - } - - void shouldNotUpdateNoteFromInvalidItem() - { - // GIVEN - - // Data... - QString title = QStringLiteral("A title"); - QString text = QStringLiteral("A note content"); - - // ... stored in a message... - KMime::Message::Ptr message(new KMime::Message); - message->subject(true)->fromUnicodeString(title, "utf-8"); - message->mainBodyPart()->fromUnicodeString(text); - - //... as the payload of an item... - Akonadi::Item item; - item.setMimeType(Akonadi::NoteUtils::noteMimeType()); - item.setPayload<KMime::Message::Ptr>(message); - - //... deserialized as a note - Akonadi::Serializer serializer; - auto note = serializer.createNoteFromItem(item); - auto artifact = serializer.createArtifactFromItem(item); - - // WHEN - Akonadi::Item invalidItem; - - serializer.updateNoteFromItem(note, invalidItem); - serializer.updateArtifactFromItem(artifact, invalidItem); - - //THEN - QCOMPARE(note->title(), title); - QCOMPARE(note->text(), text); - QCOMPARE(note->property("itemId").toLongLong(), item.id()); - - note = artifact.dynamicCast<Domain::Note>(); - QCOMPARE(note->title(), title); - QCOMPARE(note->text(), text); - QCOMPARE(note->property("itemId").toLongLong(), item.id()); - } - - void shouldCreateItemFromNote_data() - { - QTest::addColumn<QString>("title"); - QTest::addColumn<QString>("content"); - QTest::addColumn<QString>("expectedTitle"); - QTest::addColumn<QString>("expectedContent"); - QTest::addColumn<qint64>("itemId"); - QTest::addColumn<QString>("relatedUid"); - - QTest::newRow("nominal case (no id)") << "title" << "content" << "title" << "content" << qint64(-1) << QString(); - QTest::newRow("empty case (no id)") << QString() << QString() << "New Note" << QString() << qint64(-1) << QString(); - - QTest::newRow("nominal case (with id)") << "title" << "content" << "title" << "content" << qint64(42) << "parent-uid"; - QTest::newRow("empty case (with id)") << QString() << QString() << "New Note" << QString() << qint64(42) << "parent-uid"; - - QTest::newRow("empty line at the end") << "title" << "content\n\n\n" << "title" << "content\n\n\n" << qint64(-1) << QString(); - } - - void shouldCreateItemFromNote() - { - // GIVEN - - // Data... - QFETCH(QString, title); - QFETCH(QString, content); - QFETCH(qint64, itemId); - QFETCH(QString, relatedUid); - - // ... stored in a note - auto note = Domain::Note::Ptr::create(); - note->setTitle(title); - note->setText(content); - - if (itemId > 0) - note->setProperty("itemId", itemId); - - if (!relatedUid.isEmpty()) - note->setProperty("relatedUid", relatedUid); - - // WHEN - Akonadi::Serializer serializer; - auto item = serializer.createItemFromNote(note); - - // THEN - QCOMPARE(item.mimeType(), Akonadi::NoteUtils::noteMimeType()); - - QCOMPARE(item.isValid(), itemId > 0); - if (itemId > 0) { - QCOMPARE(item.id(), itemId); - } - - QFETCH(QString, expectedTitle); - QFETCH(QString, expectedContent); - auto message = item.payload<KMime::Message::Ptr>(); - QCOMPARE(message->subject(false)->asUnicodeString(), expectedTitle); - QCOMPARE(message->mainBodyPart()->decodedText(), expectedContent); - - if (relatedUid.isEmpty()) { - QVERIFY(!message->headerByType("X-Zanshin-RelatedProjectUid")); - } else { - QVERIFY(message->headerByType("X-Zanshin-RelatedProjectUid")); - QCOMPARE(message->headerByType("X-Zanshin-RelatedProjectUid")->asUnicodeString(), relatedUid); - } - } - void shouldCreateProjectFromItem_data() { QTest::addColumn<QString>("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<KCalCore::Todo::Ptr>(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<KCalCore::Todo::Ptr>(todo); // WHEN Akonadi::Serializer serializer; Domain::Project::Ptr project = serializer.createProjectFromItem(item); // THEN QVERIFY(project.isNull()); } void shouldUpdateProjectFromItem_data() { QTest::addColumn<QString>("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<KCalCore::Todo::Ptr>(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<KCalCore::Todo::Ptr>(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<KCalCore::Todo::Ptr>(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<KCalCore::Todo::Ptr>(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<KCalCore::Todo::Ptr>(projectTodo); serializer.updateProjectFromItem(project, projectItem); // THEN QCOMPARE(project->name(), summary); } void shouldCreateItemFromProject_data() { QTest::addColumn<QString>("summary"); QTest::addColumn<qint64>("itemId"); QTest::addColumn<qint64>("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<KCalCore::Todo::Ptr>(); QCOMPARE(todo->summary(), summary); QCOMPARE(todo->uid(), todoUid); QVERIFY(!todo->customProperty("Zanshin", "Project").isEmpty()); } void shouldVerifyIfAnItemIsAProjectChild_data() { QTest::addColumn<Domain::Project::Ptr>("project"); QTest::addColumn<Akonadi::Item>("item"); QTest::addColumn<bool>("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<KCalCore::Todo::Ptr>(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<KCalCore::Todo::Ptr>(childTodo); QTest::newRow("child todo") << project << childTodoItem << true; - // Create unrelated note - KMime::Message::Ptr unrelatedNote(new KMime::Message); - unrelatedNote->subject(true)->fromUnicodeString(QStringLiteral("subject"), "utf-8"); - Akonadi::Item unrelatedNoteItem; - unrelatedNoteItem.setMimeType(Akonadi::NoteUtils::noteMimeType()); - unrelatedNoteItem.setPayload<KMime::Message::Ptr>(unrelatedNote); - - QTest::newRow("unrelated note") << project << unrelatedNoteItem << false; - - // Create child note - KMime::Message::Ptr childNote(new KMime::Message); - childNote->subject(true)->fromUnicodeString(QStringLiteral("subject"), "utf-8"); - auto relatedHeader = new KMime::Headers::Generic("X-Zanshin-RelatedProjectUid"); - relatedHeader->from7BitString("1"); - childNote->appendHeader(relatedHeader); - Akonadi::Item childNoteItem; - childNoteItem.setMimeType(Akonadi::NoteUtils::noteMimeType()); - childNoteItem.setPayload<KMime::Message::Ptr>(childNote); - - QTest::newRow("child todo") << project << childNoteItem << true; - auto invalidProject = Domain::Project::Ptr::create(); - QTest::newRow("invalid project") << invalidProject << unrelatedNoteItem << false; + 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<Akonadi::Item>("item"); QTest::addColumn<Domain::Task::Ptr>("parent"); QTest::addColumn<QString>("expectedRelatedToUid"); Akonadi::Item item1; KCalCore::Todo::Ptr todo1(new KCalCore::Todo); item1.setPayload<KCalCore::Todo::Ptr>(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<KCalCore::Todo::Ptr>()) { auto todo = item.payload<KCalCore::Todo::Ptr>(); QString relatedUid = todo->relatedTo(); QCOMPARE(relatedUid, expectedRelatedToUid); } } void shouldUpdateItemProject_data() { QTest::addColumn<Akonadi::Item>("item"); QTest::addColumn<Domain::Project::Ptr>("parent"); QTest::addColumn<QString>("expectedRelatedToUid"); Akonadi::Item todoItem; KCalCore::Todo::Ptr todo(new KCalCore::Todo); todoItem.setPayload<KCalCore::Todo::Ptr>(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 noteItem; - KMime::Message::Ptr note(new KMime::Message); - noteItem.setPayload<KMime::Message::Ptr>(note); - - QTest::newRow("nominal note case") << noteItem << parent << "1"; - QTest::newRow("update note item with a empty parent uid") << noteItem << 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<KCalCore::Todo::Ptr>()) { auto todo = item.payload<KCalCore::Todo::Ptr>(); const QString relatedUid = todo->relatedTo(); QCOMPARE(relatedUid, expectedRelatedToUid); } else if (item.hasPayload<KMime::Message::Ptr>()) { auto note = item.payload<KMime::Message::Ptr>(); 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<Akonadi::Item>("item"); QTest::addColumn<Akonadi::Item::List>("items"); QTest::addColumn<int>("size"); Akonadi::Item item(12); KCalCore::Todo::Ptr todo(new KCalCore::Todo); todo->setUid(QStringLiteral("1")); item.setPayload<KCalCore::Todo::Ptr>(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<KCalCore::Todo::Ptr>(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<KCalCore::Todo::Ptr>(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<KCalCore::Todo::Ptr>(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<Akonadi::Item>("item"); Akonadi::Item item(15); KCalCore::Todo::Ptr todo(new KCalCore::Todo); todo->setRelatedTo(QStringLiteral("3")); item.setPayload<KCalCore::Todo::Ptr>(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<KCalCore::Todo::Ptr>()) QCOMPARE(item.payload<KCalCore::Todo::Ptr>()->relatedTo(), QString()); } void shouldPromoteItemToProject_data() { QTest::addColumn<Akonadi::Item>("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<KCalCore::Todo::Ptr>()) { auto todo = item.payload<KCalCore::Todo::Ptr>(); QCOMPARE(todo->relatedTo(), QString()); QVERIFY(!todo->customProperty("Zanshin", "Project").isEmpty()); } } void shouldClearItem_data() { QTest::addColumn<Akonadi::Item*>("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<KCalCore::Todo::Ptr>(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<QByteArray>("type"); QTest::addColumn<QString>("name"); QTest::addColumn<Akonadi::Tag::Id>("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<Domain::Context::Ptr>("context"); QTest::addColumn<Akonadi::Item>("item"); QTest::addColumn<bool>("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 shouldCheckIfAnItemHasContextsOrTags_data() + void shouldCheckIfAnItemHasContexts_data() { QTest::addColumn<Akonadi::Item>("item"); QTest::addColumn<bool>("contextsExpected"); - QTest::addColumn<bool>("tagsExpected"); 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 << false; + QTest::newRow("no tags") << item << false; item.setTags({ unrelatedTag }); - QTest::newRow("unrelated tags") << item << false << false; + QTest::newRow("unrelated tags") << item << false; item.setTags({ unrelatedTag, contextTag }); - QTest::newRow("has contexts") << item << true << false; + QTest::newRow("has contexts") << item << true; item.setTags({ unrelatedTag, akonadiTag }); - QTest::newRow("has tags") << item << false << true; + QTest::newRow("has tags") << item << false; item.setTags({ unrelatedTag, contextTag, akonadiTag }); - QTest::newRow("has both") << item << true << true; + QTest::newRow("has both") << item << true; } - void shouldCheckIfAnItemHasContextsOrTags() + void shouldCheckIfAnItemHasContexts() { // GIVEN QFETCH(Akonadi::Item, item); QFETCH(bool, contextsExpected); - QFETCH(bool, tagsExpected); Akonadi::Serializer serializer; // WHEN const bool hasContexts = serializer.hasContextTags(item); - const bool hasTags = serializer.hasAkonadiTags(item); // THEN QCOMPARE(hasContexts, contextsExpected); - QCOMPARE(hasTags, tagsExpected); } void shouldCreateTagFromContext_data() { QTest::addColumn<QString>("name"); QTest::addColumn<qint64>("tagId"); QTest::addColumn<QByteArray>("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()); } } - void shouldCreateTagFromAkonadiTag_data() - { - QTest::addColumn<QString>("name"); - QTest::addColumn<qint64>("tagId"); - QTest::addColumn<QByteArray>("type"); - - QString tagName = QStringLiteral("Optional"); - QByteArray plainType = Akonadi::Tag::PLAIN; - - QTest::newRow("nominal case") << tagName << qint64(42) << plainType; - QTest::newRow("null name case") << QString() << qint64(42) << plainType; - QTest::newRow("null tagId case") << tagName << qint64(-1) << plainType; - QTest::newRow("totally null tag case") << QString() << qint64(-1) << plainType; - } - - void shouldCreateTagFromAkonadiTag() - { - // GIVEN - QFETCH(QString, name); - QFETCH(qint64, tagId); - QFETCH(QByteArray, type); - - auto akonadiTag = Akonadi::Tag(); - akonadiTag.setName(name); - akonadiTag.setId(tagId); - akonadiTag.setType(type); - - // WHEN - Akonadi::Serializer serializer; - Domain::Tag::Ptr resultTag = serializer.createTagFromAkonadiTag(akonadiTag); - - // THEN - QCOMPARE(resultTag->name(), akonadiTag.name()); - QCOMPARE(resultTag->property("tagId").toLongLong(), akonadiTag.id()); - } - - void shouldUpdateTagFromAkonadiTag_data() - { - shouldCreateTagFromAkonadiTag_data(); - } - - void shouldUpdateTagFromAkonadiTag() - { - // GIVEN - QFETCH(QString, name); - QFETCH(qint64, tagId); - QFETCH(QByteArray, type); - - // ... stored as an Akonadi Tag - Akonadi::Tag akonadiTag(name); - akonadiTag.setId(tagId); - akonadiTag.setType(type); - - // WHEN - Akonadi::Serializer serializer; - auto tag = Domain::Tag::Ptr::create(); - tag->setName(QStringLiteral("tag42")); - - serializer.updateTagFromAkonadiTag(tag, akonadiTag); - - // THEN - QCOMPARE(tag->name(), akonadiTag.name()); - QCOMPARE(tag->property("tagId").toLongLong(), akonadiTag.id()); - } - - void shouldCreateAkonadiTagFromTag_data() - { - // GIVEN - QTest::addColumn<QString>("name"); - QTest::addColumn<qint64>("tagId"); - QTest::addColumn<QByteArray>("tagGid"); - - const QByteArray namePhilo = "Philosophy"; - - QTest::newRow("nominal case") << QString(namePhilo) << qint64(42) << namePhilo; - QTest::newRow("null name case") << QString() << qint64(42) << QByteArray(); - QTest::newRow("null tagId case") << QString(namePhilo) << qint64(-1) << namePhilo; - QTest::newRow("totally null tag case") << QString() << qint64(-1) << QByteArray(); - } - - void shouldCreateAkonadiTagFromTag() - { - // GIVEN - QFETCH(QString, name); - QFETCH(qint64, tagId); - QFETCH(QByteArray, tagGid); - - // WHEN - auto tag = Domain::Tag::Ptr::create(); - tag->setProperty("tagId", tagId); - tag->setName(name); - - Akonadi::Serializer serializer; - Akonadi::Tag akonadiTag = serializer.createAkonadiTagFromTag(tag); - - // THEN - QCOMPARE(akonadiTag.name(), name); - QCOMPARE(akonadiTag.isValid(), tagId > 0); - - if (tagId > 0) { - QCOMPARE(akonadiTag.id(), tagId); - QCOMPARE(akonadiTag.gid(), tagGid); - QCOMPARE(akonadiTag.type(), QByteArray(Akonadi::Tag::PLAIN)); - } - } - - void shouldVerifyIfAnItemIsATagChild_data() - { - QTest::addColumn<Domain::Tag::Ptr>("tag"); - QTest::addColumn<Akonadi::Item>("item"); - QTest::addColumn<bool>("isChild"); - - // Create a Tag - auto tag = Domain::Tag::Ptr::create(); - tag->setProperty("tagId", qint64(43)); - Akonadi::Tag akonadiTag(Akonadi::Tag::Id(43)); - - Akonadi::Item unrelatedItem; - QTest::newRow("Unrelated item") << tag << unrelatedItem << false; - - Akonadi::Item relatedItem; - relatedItem.setTag(akonadiTag); - QTest::newRow("Related item") << tag << relatedItem << true; - - auto invalidTag = Domain::Tag::Ptr::create(); - QTest::newRow("Invalid Tag") << invalidTag << relatedItem << false; - - Akonadi::Item invalidItem; - QTest::newRow("Invalid Item") << tag << invalidItem << false; - - QTest::newRow("both invalid") << invalidTag << invalidItem << false; - } - - void shouldVerifyIfAnItemIsATagChild() - { - // GIVEN - QFETCH(Domain::Tag::Ptr, tag); - QFETCH(Akonadi::Item, item); - QFETCH(bool, isChild); - - // WHEN - Akonadi::Serializer serializer; - bool value = serializer.isTagChild(tag, item); - - // THEN - QCOMPARE(value, isChild); - } - // 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" diff --git a/tests/units/akonadi/akonaditagqueriestest.cpp b/tests/units/akonadi/akonaditagqueriestest.cpp deleted file mode 100644 index 5820e93a..00000000 --- a/tests/units/akonadi/akonaditagqueriestest.cpp +++ /dev/null @@ -1,302 +0,0 @@ -/* This file is part of Zanshin - - Copyright 2014 Kevin Ottens <ervin@kde.org> - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License or (at your option) version 3 or any later version - accepted by the membership of KDE e.V. (or its successor approved - by the membership of KDE e.V.), which shall act as a proxy - defined in Section 14 of version 3 of the license. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - USA. -*/ - -#include <testlib/qtest_zanshin.h> - -#include "akonadi/akonaditagqueries.h" -#include "akonadi/akonadiserializer.h" - -#include "testlib/akonadifakedata.h" -#include "testlib/gencollection.h" -#include "testlib/gennote.h" -#include "testlib/gentag.h" -#include "testlib/testhelpers.h" - -using namespace Testlib; - -class AkonadiTagQueriesTest : public QObject -{ - Q_OBJECT -private slots: - void shouldLookInAllReportedForAllTag() - { - // GIVEN - AkonadiFakeData data; - - // Two plain tags - data.createTag(GenTag().withId(42).withName(QStringLiteral("42")).asPlain()); - data.createTag(GenTag().withId(43).withName(QStringLiteral("43")).asPlain()); - - // WHEN - QScopedPointer<Domain::TagQueries> queries(new Akonadi::TagQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), - Akonadi::Serializer::Ptr(new Akonadi::Serializer), - Akonadi::MonitorInterface::Ptr(data.createMonitor()))); - auto result = queries->findAll(); - result->data(); - result = queries->findAll(); // Should not cause any problem or wrong data - - // THEN - QVERIFY(result->data().isEmpty()); - TestHelpers::waitForEmptyJobQueue(); - - QCOMPARE(result->data().size(), 2); - QCOMPARE(result->data().at(0)->name(), QStringLiteral("42")); - QCOMPARE(result->data().at(1)->name(), QStringLiteral("43")); - } - - void shouldReactToTagAdded() - { - // GIVEN - AkonadiFakeData data; - - QScopedPointer<Domain::TagQueries> queries(new Akonadi::TagQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), - Akonadi::Serializer::Ptr(new Akonadi::Serializer), - Akonadi::MonitorInterface::Ptr(data.createMonitor()))); - auto result = queries->findAll(); - TestHelpers::waitForEmptyJobQueue(); - QVERIFY(result->data().isEmpty()); - - // WHEN - data.createTag(GenTag().withId(42).withName(QStringLiteral("42")).asPlain()); - data.createTag(GenTag().withId(43).withName(QStringLiteral("43")).asPlain()); - - // THEN - QCOMPARE(result->data().size(), 2); - QCOMPARE(result->data().at(0)->name(), QStringLiteral("42")); - QCOMPARE(result->data().at(1)->name(), QStringLiteral("43")); - } - - void shouldReactToTagRemoved() - { - // GIVEN - AkonadiFakeData data; - - // Two plain tags - data.createTag(GenTag().withId(42).withName(QStringLiteral("42")).asPlain()); - data.createTag(GenTag().withId(43).withName(QStringLiteral("43")).asPlain()); - - QScopedPointer<Domain::TagQueries> queries(new Akonadi::TagQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), - Akonadi::Serializer::Ptr(new Akonadi::Serializer), - Akonadi::MonitorInterface::Ptr(data.createMonitor()))); - auto result = queries->findAll(); - QVERIFY(result->data().isEmpty()); - TestHelpers::waitForEmptyJobQueue(); - QCOMPARE(result->data().size(), 2); - - // WHEN - data.removeTag(Akonadi::Tag(43)); - - // THEN - QCOMPARE(result->data().size(), 1); - QCOMPARE(result->data().at(0)->name(), QStringLiteral("42")); - } - - void shouldReactToTagChanges() - { - // GIVEN - AkonadiFakeData data; - - // Two plain tags - data.createTag(GenTag().withId(42).withName(QStringLiteral("42")).asPlain()); - data.createTag(GenTag().withId(43).withName(QStringLiteral("43")).asPlain()); - - QScopedPointer<Domain::TagQueries> queries(new Akonadi::TagQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), - Akonadi::Serializer::Ptr(new Akonadi::Serializer), - Akonadi::MonitorInterface::Ptr(data.createMonitor()))); - auto result = queries->findAll(); - QVERIFY(result->data().isEmpty()); - TestHelpers::waitForEmptyJobQueue(); - QCOMPARE(result->data().size(), 2); - - // WHEN - data.modifyTag(GenTag(data.tag(43)).withName(QStringLiteral("43bis"))); - - // THEN - QCOMPARE(result->data().size(), 2); - QCOMPARE(result->data().at(0)->name(), QStringLiteral("42")); - QCOMPARE(result->data().at(1)->name(), QStringLiteral("43bis")); - } - - void shouldLookInAllCollectionsForTagTopLevelArtifacts() - { - // GIVEN - AkonadiFakeData data; - - // Two top level collections - data.createCollection(GenCollection().withId(42).withRootAsParent().withNoteContent()); - data.createCollection(GenCollection().withId(43).withRootAsParent().withNoteContent()); - - // One plain tag - data.createTag(GenTag().withId(42).withName(QStringLiteral("42")).asPlain()); - - // Two notes in the first collection - data.createItem(GenNote().withParent(42).withId(42).withTitle(QStringLiteral("42")).withTags({42})); - data.createItem(GenNote().withParent(42).withId(43).withTitle(QStringLiteral("43"))); - - // Two notes in the second collection - data.createItem(GenNote().withParent(43).withId(44).withTitle(QStringLiteral("44")).withTags({42})); - data.createItem(GenNote().withParent(43).withId(45).withTitle(QStringLiteral("45"))); - - // WHEN - auto serializer = Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer); - QScopedPointer<Domain::TagQueries> queries(new Akonadi::TagQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), - serializer, - Akonadi::MonitorInterface::Ptr(data.createMonitor()))); - - auto tag = serializer->createTagFromAkonadiTag(data.tag(42)); - auto result = queries->findNotes(tag); - result->data(); - result = queries->findNotes(tag); // Should not cause any problem or wrong data - - // THEN - QVERIFY(result->data().isEmpty()); - TestHelpers::waitForEmptyJobQueue(); - - QCOMPARE(result->data().size(), 2); - QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); - QCOMPARE(result->data().at(1)->title(), QStringLiteral("44")); - - // Should not change nothing - result = queries->findNotes(tag); - TestHelpers::waitForEmptyJobQueue(); - - QCOMPARE(result->data().size(), 2); - QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); - QCOMPARE(result->data().at(1)->title(), QStringLiteral("44")); - } - - void shouldReactToItemAddedForTag() - { - // GIVEN - AkonadiFakeData data; - - // Two top level collections - data.createCollection(GenCollection().withId(42).withRootAsParent().withNoteContent()); - - // One plain tag - data.createTag(GenTag().withId(42).withName(QStringLiteral("tag-42")).asPlain()); - - auto serializer = Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer); - QScopedPointer<Domain::TagQueries> queries(new Akonadi::TagQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), - serializer, - Akonadi::MonitorInterface::Ptr(data.createMonitor()))); - - auto tag = serializer->createTagFromAkonadiTag(data.tag(42)); - auto result = queries->findNotes(tag); - TestHelpers::waitForEmptyJobQueue(); - QVERIFY(result->data().isEmpty()); - - // WHEN - data.createItem(GenNote().withParent(42).withId(42).withTitle(QStringLiteral("42")).withTags({42})); - - // THEN - QCOMPARE(result->data().size(), 1); - QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); - } - - void shouldReactToItemNewlyAssociatedToTag() - { - // GIVEN - AkonadiFakeData data; - - // One plain tag - data.createTag(GenTag().withId(42).withName(QStringLiteral("tag-42")).asPlain()); - - // One top level collection with a note - data.createCollection(GenCollection().withId(42).withRootAsParent().withNoteContent()); - data.createItem(GenNote().withId(42).withParent(42).withTitle(QStringLiteral("42"))); - - auto serializer = Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer); - QScopedPointer<Domain::TagQueries> queries(new Akonadi::TagQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), - serializer, - Akonadi::MonitorInterface::Ptr(data.createMonitor()))); - - auto tag = serializer->createTagFromAkonadiTag(data.tag(42)); - auto result = queries->findNotes(tag); - - bool insertHandlerCalled = false; - result->addPostInsertHandler([&insertHandlerCalled](const Domain::Artifact::Ptr &, int) { - insertHandlerCalled = true; - }); - - TestHelpers::waitForEmptyJobQueue(); - QVERIFY(result->data().isEmpty()); - - // WHEN - data.modifyItem(GenNote(data.item(42)).withTags({42})); - - // THEN - QCOMPARE(result->data().size(), 1); - QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); - - QVERIFY(insertHandlerCalled); - } - - void shouldReactWhenAnItemTaggedIsRemoved() - { - // GIVEN - - AkonadiFakeData data; - - // One plain tag - data.createTag(GenTag().withId(42).withName(QStringLiteral("tag-42")).asPlain()); - - // One top level collection - data.createCollection(GenCollection().withId(42).withRootAsParent().withNoteContent()); - - // Two notes related to the tag - data.createItem(GenNote().withId(42).withParent(42).withTitle(QStringLiteral("42")).withTags({42})); - data.createItem(GenNote().withId(43).withParent(42).withTitle(QStringLiteral("43")).withTags({42})); - - - auto serializer = Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer); - QScopedPointer<Domain::TagQueries> queries(new Akonadi::TagQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), - serializer, - Akonadi::MonitorInterface::Ptr(data.createMonitor()))); - - auto tag = serializer->createTagFromAkonadiTag(data.tag(42)); - auto result = queries->findNotes(tag); - - bool removeHandlerCalled = false; - result->addPostRemoveHandler([&removeHandlerCalled](const Domain::Artifact::Ptr &, int) { - removeHandlerCalled = true; - }); - - TestHelpers::waitForEmptyJobQueue(); - QCOMPARE(result->data().size(), 2); - QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); - QCOMPARE(result->data().at(1)->title(), QStringLiteral("43")); - - // WHEN - data.removeItem(Akonadi::Item(43)); - - // THEN - QCOMPARE(result->data().size(), 1); - QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); - QVERIFY(removeHandlerCalled); - } -}; - -ZANSHIN_TEST_MAIN(AkonadiTagQueriesTest) - -#include "akonaditagqueriestest.moc" diff --git a/tests/units/akonadi/akonaditagrepositorytest.cpp b/tests/units/akonadi/akonaditagrepositorytest.cpp deleted file mode 100644 index 8e1c1e1c..00000000 --- a/tests/units/akonadi/akonaditagrepositorytest.cpp +++ /dev/null @@ -1,260 +0,0 @@ -/* This file is part of Zanshin - - Copyright 2014 Kevin Ottens <ervin@kde.org> - Copyright 2014 Franck Arrecot <franck.arrecot@gmail.com> - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License or (at your option) version 3 or any later version - accepted by the membership of KDE e.V. (or its successor approved - by the membership of KDE e.V.), which shall act as a proxy - defined in Section 14 of version 3 of the license. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - USA. -*/ - -#include <testlib/qtest_zanshin.h> - -#include "utils/mockobject.h" - -#include "testlib/akonadifakejobs.h" -#include "testlib/akonadifakemonitor.h" - -#include "akonadi/akonaditagrepository.h" -#include "akonadi/akonadiserializerinterface.h" -#include "akonadi/akonadistorageinterface.h" - -using namespace mockitopp; -using namespace mockitopp::matcher; - -Q_DECLARE_METATYPE(Testlib::AkonadiFakeItemFetchJob*) -Q_DECLARE_METATYPE(Testlib::AkonadiFakeTagFetchJob*) - -class AkonadiTagRepositoryTest : public QObject -{ - Q_OBJECT -private slots: - void shouldCreateTag() - { - // GIVEN - - // A Tag and its corresponding Akonadi Tag - Akonadi::Tag akonadiTag; // not existing yet - auto tag = Domain::Tag::Ptr::create(); - - // A mock creating job - auto tagCreateJob = new FakeJob(this); - - // Storage mock returning the tagCreatejob - Utils::MockObject<Akonadi::StorageInterface> storageMock; - storageMock(&Akonadi::StorageInterface::createTag).when(akonadiTag) - .thenReturn(tagCreateJob); - - // Serializer mock - Utils::MockObject<Akonadi::SerializerInterface> serializerMock; - serializerMock(&Akonadi::SerializerInterface::createAkonadiTagFromTag).when(tag).thenReturn(akonadiTag); - - - // WHEN - QScopedPointer<Akonadi::TagRepository> repository(new Akonadi::TagRepository(storageMock.getInstance(), - serializerMock.getInstance())); - repository->create(tag)->exec(); - - //THEN - QVERIFY(storageMock(&Akonadi::StorageInterface::createTag).when(akonadiTag).exactly(1)); - } - - void shouldRemoveTag() - { - // GIVEN - Akonadi::Tag akonadiTag(42); - auto tag = Domain::Tag::Ptr::create(); - tag->setProperty("tagId", qint64(42)); // must be set - tag->setName(QStringLiteral("42")); - - // A mock of removal job - auto tagRemoveJob = new FakeJob(this); - - // Storage mock returning the tagCreatejob - Utils::MockObject<Akonadi::StorageInterface> storageMock; - storageMock(&Akonadi::StorageInterface::removeTag).when(akonadiTag) - .thenReturn(tagRemoveJob); - // Serializer mock - Utils::MockObject<Akonadi::SerializerInterface> serializerMock; - serializerMock(&Akonadi::SerializerInterface::createAkonadiTagFromTag).when(tag).thenReturn(akonadiTag); - - // WHEN - QScopedPointer<Akonadi::TagRepository> repository(new Akonadi::TagRepository(storageMock.getInstance(), - serializerMock.getInstance())); - repository->remove(tag)->exec(); - - // THEN - QVERIFY(storageMock(&Akonadi::StorageInterface::removeTag).when(akonadiTag).exactly(1)); - } - - void shouldAssociateNoteToTag() - { - // GIVEN - auto tag = Domain::Tag::Ptr::create(); - Akonadi::Tag akonadiTag(42); - - auto note = Domain::Note::Ptr::create(); - Akonadi::Item noteItem(42); - - // A mock of update job - auto itemModifyJob = new FakeJob(this); - - auto itemFetchJob = new Testlib::AkonadiFakeItemFetchJob(this); - itemFetchJob->setItems(Akonadi::Item::List() << noteItem); - - // Storage mock returning the tagCreatejob - Utils::MockObject<Akonadi::StorageInterface> storageMock; - storageMock(&Akonadi::StorageInterface::updateItem).when(noteItem, Q_NULLPTR) - .thenReturn(itemModifyJob); - - storageMock(&Akonadi::StorageInterface::fetchItem).when(noteItem) - .thenReturn(itemFetchJob); - // Serializer mock - Utils::MockObject<Akonadi::SerializerInterface> serializerMock; - serializerMock(&Akonadi::SerializerInterface::createAkonadiTagFromTag).when(tag).thenReturn(akonadiTag); - serializerMock(&Akonadi::SerializerInterface::createItemFromNote).when(note).thenReturn(noteItem); - - // WHEN - QScopedPointer<Akonadi::TagRepository> repository(new Akonadi::TagRepository(storageMock.getInstance(), - serializerMock.getInstance())); - repository->associate(tag, note)->exec(); - - // THEN - QVERIFY(serializerMock(&Akonadi::SerializerInterface::createAkonadiTagFromTag).when(tag).exactly(1)); - QVERIFY(serializerMock(&Akonadi::SerializerInterface::createItemFromNote).when(note).exactly(1)); - - QVERIFY(storageMock(&Akonadi::StorageInterface::updateItem).when(noteItem, Q_NULLPTR).exactly(1)); - } - - void shouldDissociateNoteFromTag() - { - // GIVEN - Akonadi::Item item(42); - Domain::Note::Ptr note(new Domain::Note); - - Akonadi::Tag akonadiTag(qint64(42)); - auto tag = Domain::Tag::Ptr::create(); - - auto itemFetchJob = new Testlib::AkonadiFakeItemFetchJob(this); - itemFetchJob->setExpectedError(KJob::KilledJobError); - - auto itemFetchJobFilled = new Testlib::AkonadiFakeItemFetchJob(this); - itemFetchJobFilled->setItems(Akonadi::Item::List() << item); - - // A mock update job - auto itemModifyJob = new FakeJob(this); - - // Storage mock returning the create job - Utils::MockObject<Akonadi::StorageInterface> storageMock; - storageMock(&Akonadi::StorageInterface::fetchItem).when(item) - .thenReturn(itemFetchJob) - .thenReturn(itemFetchJobFilled); - storageMock(&Akonadi::StorageInterface::updateItem).when(item, Q_NULLPTR) - .thenReturn(itemModifyJob); - - // Serializer mock returning the item for the note - Utils::MockObject<Akonadi::SerializerInterface> serializerMock; - serializerMock(&Akonadi::SerializerInterface::createItemFromNote).when(note) - .thenReturn(item); - serializerMock(&Akonadi::SerializerInterface::createAkonadiTagFromTag).when(tag) - .thenReturn(akonadiTag); - // WHEN - QScopedPointer<Akonadi::TagRepository> repository(new Akonadi::TagRepository(storageMock.getInstance(), - serializerMock.getInstance())); - repository->dissociate(tag, note)->exec(); - - // THEN - QVERIFY(storageMock(&Akonadi::StorageInterface::fetchItem).when(item).exactly(1)); - QVERIFY(serializerMock(&Akonadi::SerializerInterface::createAkonadiTagFromTag).when(tag).exactly(0)); - QVERIFY(storageMock(&Akonadi::StorageInterface::updateItem).when(item, Q_NULLPTR).exactly(0)); - - // WHEN - repository->dissociate(tag, note)->exec(); - - // THEN - QVERIFY(storageMock(&Akonadi::StorageInterface::fetchItem).when(item).exactly(2)); - QVERIFY(serializerMock(&Akonadi::SerializerInterface::createAkonadiTagFromTag).when(tag).exactly(1)); - QVERIFY(storageMock(&Akonadi::StorageInterface::updateItem).when(item, Q_NULLPTR).exactly(1)); - } - - void shouldDissociateNoteFromAllTags_data() - { - QTest::addColumn<Akonadi::Item>("item"); - QTest::addColumn<Domain::Note::Ptr>("note"); - QTest::addColumn<Testlib::AkonadiFakeItemFetchJob*>("itemFetchJob"); - QTest::addColumn<bool>("execJob"); - - Akonadi::Item item(42); - auto note = Domain::Note::Ptr::create(); - - auto itemFetchJob = new Testlib::AkonadiFakeItemFetchJob(this); - itemFetchJob->setItems(Akonadi::Item::List() << item); - QTest::newRow("nominal case") << item << note << itemFetchJob << true; - - itemFetchJob = new Testlib::AkonadiFakeItemFetchJob(this); - itemFetchJob->setExpectedError(KJob::KilledJobError); - QTest::newRow("task job error, cannot find task") << item << note << itemFetchJob << false; - } - - void shouldDissociateNoteFromAllTags() - { - QFETCH(Akonadi::Item,item); - QFETCH(Domain::Note::Ptr, note); - QFETCH(Testlib::AkonadiFakeItemFetchJob*,itemFetchJob); - QFETCH(bool,execJob); - - // A mock update job - auto itemModifyJob = new FakeJob(this); - - // Storage mock returning the create job - Utils::MockObject<Akonadi::StorageInterface> storageMock; - storageMock(&Akonadi::StorageInterface::fetchItem).when(item) - .thenReturn(itemFetchJob); - storageMock(&Akonadi::StorageInterface::updateItem).when(item, Q_NULLPTR) - .thenReturn(itemModifyJob); - - // Serializer mock returning the item for the task - Utils::MockObject<Akonadi::SerializerInterface> serializerMock; - serializerMock(&Akonadi::SerializerInterface::createItemFromNote).when(note) - .thenReturn(item); - - // WHEN - QScopedPointer<Akonadi::TagRepository> repository(new Akonadi::TagRepository(storageMock.getInstance(), - serializerMock.getInstance())); - - auto dissociateJob = repository->dissociateAll(note); - - if (execJob) - dissociateJob->exec(); - - // THEN - QVERIFY(storageMock(&Akonadi::StorageInterface::fetchItem).when(item).exactly(1)); - if (execJob) { - QVERIFY(storageMock(&Akonadi::StorageInterface::updateItem).when(item, Q_NULLPTR).exactly(1)); - } else { - delete dissociateJob; - } - - // Give a chance to itemFetchJob to delete itself - // in case of an error (since it uses deleteLater() internally) - QTest::qWait(10); - } -}; - -ZANSHIN_TEST_MAIN(AkonadiTagRepositoryTest) - -#include "akonaditagrepositorytest.moc" diff --git a/tests/units/akonadi/akonaditaskqueriestest.cpp b/tests/units/akonadi/akonaditaskqueriestest.cpp index 66066c5a..2099d2d1 100644 --- a/tests/units/akonadi/akonaditaskqueriestest.cpp +++ b/tests/units/akonadi/akonaditaskqueriestest.cpp @@ -1,1819 +1,1815 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens <ervin@kde.org> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include <testlib/qtest_zanshin.h> #include "akonadi/akonadicachingstorage.h" #include "akonadi/akonaditaskqueries.h" #include "akonadi/akonadiserializer.h" #include "akonadi/akonadiitemfetchjobinterface.h" #include "testlib/akonadifakedata.h" #include "testlib/gencollection.h" -#include "testlib/gennote.h" #include "testlib/gentag.h" #include "testlib/gentodo.h" #include "testlib/testhelpers.h" #include "utils/datetime.h" using namespace Testlib; class AkonadiTaskQueriesTest : public QObject { Q_OBJECT private: Akonadi::StorageInterface::Ptr createCachingStorage(AkonadiFakeData &data, const Akonadi::Cache::Ptr &cache) { auto storage = Akonadi::StorageInterface::Ptr(data.createStorage()); return Akonadi::StorageInterface::Ptr(new Akonadi::CachingStorage(cache, storage)); } private slots: void shouldLookInAllReportedForAllTasks() { // GIVEN AkonadiFakeData data; // Two top level collections data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withRootAsParent().withTaskContent()); // One task in the first collection data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42"))); // Two tasks in the second collection data.createItem(GenTodo().withId(43).withParent(43).withTitle(QStringLiteral("43"))); data.createItem(GenTodo().withId(44).withParent(43).withTitle(QStringLiteral("44"))); // WHEN QScopedPointer<Domain::TaskQueries> queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto result = queries->findAll(); result->data(); result = queries->findAll(); // Should not cause any problem or wrong data // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 3); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("43")); QCOMPARE(result->data().at(2)->title(), QStringLiteral("44")); } void shouldIgnoreItemsWhichAreNotTasks() { // GIVEN AkonadiFakeData data; // Two top level collections data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Two items in the collection data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42"))); // One of them is not a task auto item = Akonadi::Item(43); item.setPayloadFromData("FooBar"); item.setParentCollection(Akonadi::Collection(42)); data.createItem(item); // WHEN QScopedPointer<Domain::TaskQueries> queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto result = queries->findAll(); // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); } void shouldReactToItemAddsForTasksOnly() { // GIVEN AkonadiFakeData data; // One empty collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); QScopedPointer<Domain::TaskQueries> queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto result = queries->findAll(); TestHelpers::waitForEmptyJobQueue(); QVERIFY(result->data().isEmpty()); // WHEN data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42"))); auto item = Akonadi::Item(43); item.setPayloadFromData("FooBar"); item.setParentCollection(Akonadi::Collection(42)); data.createItem(item); // THEN QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().first()->title(), QStringLiteral("42")); } void shouldReactToItemRemovesForAllTasks() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three task in the collection data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42"))); data.createItem(GenTodo().withId(43).withParent(42).withTitle(QStringLiteral("43"))); data.createItem(GenTodo().withId(44).withParent(42).withTitle(QStringLiteral("44"))); QScopedPointer<Domain::TaskQueries> queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto result = queries->findAll(); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 3); // WHEN data.removeItem(Akonadi::Item(43)); // THEN QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("44")); } void shouldReactToItemChangesForAllTasks() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three task in the collection data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42"))); data.createItem(GenTodo().withId(43).withParent(42).withTitle(QStringLiteral("43"))); data.createItem(GenTodo().withId(44).withParent(42).withTitle(QStringLiteral("44"))); QScopedPointer<Domain::TaskQueries> queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto result = queries->findAll(); // Even though the pointer didn't change it's convenient to user if we call // the replace handlers bool replaceHandlerCalled = false; result->addPostReplaceHandler([&replaceHandlerCalled](const Domain::Task::Ptr &, int) { replaceHandlerCalled = true; }); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 3); // WHEN data.modifyItem(GenTodo(data.item(43)).withTitle(QStringLiteral("43bis"))); // THEN QCOMPARE(result->data().size(), 3); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("43bis")); QCOMPARE(result->data().at(2)->title(), QStringLiteral("44")); QVERIFY(replaceHandlerCalled); } void shouldLookInAllChildrenReportedForAllChildrenTask() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three tasks in the collection (two being children of the first one) data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(43).withParent(42) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43")) .withParentUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(44).withParent(42) .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44")) .withParentUid(QStringLiteral("uid-42"))); // WHEN auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); QScopedPointer<Domain::TaskQueries> queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto task = serializer->createTaskFromItem(data.item(42)); auto result = queries->findChildren(task); result->data(); result = queries->findChildren(task); // Should not cause any problem or wrong data // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->title(), QStringLiteral("43")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("44")); // Should not change nothing result = queries->findChildren(task); QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->title(), QStringLiteral("43")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("44")); } void shouldNotCrashWhenWeAskAgainTheSameChildrenList() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // One task in the collection data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); QScopedPointer<Domain::TaskQueries> queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto task = serializer->createTaskFromItem(data.item(42)); // The bug we're trying to hit here is the following: // - when findChildren is called the first time a provider is created internally // - result is deleted at the end of the loop, no one holds the provider with // a strong reference anymore so it is deleted as well // - when findChildren is called the second time, there's a risk of a dangling // pointer if the recycling of providers is wrongly implemented which can lead // to a crash, if it is properly done no crash will occur for (int i = 0; i < 2; i++) { // WHEN * 2 auto result = queries->findChildren(task); // THEN * 2 QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QVERIFY(result->data().isEmpty()); } } void shouldReactToItemAddsForChildrenTask() { // GIVEN AkonadiFakeData data; // One top level collections data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // One task in the collection data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); QScopedPointer<Domain::TaskQueries> queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto task = serializer->createTaskFromItem(data.item(42)); auto result = queries->findChildren(task); TestHelpers::waitForEmptyJobQueue(); QVERIFY(result->data().isEmpty()); // WHEN data.createItem(GenTodo().withId(43).withParent(42) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43")) .withParentUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(44).withParent(42) .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44")) .withParentUid(QStringLiteral("uid-42"))); // THEN QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->title(), QStringLiteral("43")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("44")); } void shouldReactToItemChangesForChildrenTask() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three tasks in the collection (two being children of the first one) data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(43).withParent(42) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43")) .withParentUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(44).withParent(42) .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44")) .withParentUid(QStringLiteral("uid-42"))); auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); QScopedPointer<Domain::TaskQueries> queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto task = serializer->createTaskFromItem(data.item(42)); auto result = queries->findChildren(task); bool replaceHandlerCalled = false; result->addPostReplaceHandler([&replaceHandlerCalled](const Domain::Task::Ptr &, int) { replaceHandlerCalled = true; }); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); // WHEN data.modifyItem(GenTodo(data.item(43)).withTitle(QStringLiteral("43bis"))); // THEN QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->title(), QStringLiteral("43bis")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("44")); QVERIFY(replaceHandlerCalled); } void shouldRemoveItemFromCorrespondingResultWhenRelatedItemChangeForChildrenTask() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three tasks in the collection (two being children of the first one) data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(43).withParent(42) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43")) .withParentUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(44).withParent(42) .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44")) .withParentUid(QStringLiteral("uid-42"))); auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); QScopedPointer<Domain::TaskQueries> queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto task = serializer->createTaskFromItem(data.item(42)); auto result = queries->findChildren(task); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); // WHEN data.modifyItem(GenTodo(data.item(43)).withParentUid(QLatin1String(""))); // THEN QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("44")); } void shouldAddItemToCorrespondingResultWhenRelatedItemChangeForChildrenTask() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three tasks in the collection (two being top level) data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(43).withParent(42) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43"))); data.createItem(GenTodo().withId(44).withParent(42) .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44")) .withParentUid(QStringLiteral("uid-42"))); auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); QScopedPointer<Domain::TaskQueries> queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto task = serializer->createTaskFromItem(data.item(42)); auto result = queries->findChildren(task); bool replaceHandlerCalled = false; result->addPostReplaceHandler([&replaceHandlerCalled](const Domain::Task::Ptr &, int) { replaceHandlerCalled = true; }); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 1); // WHEN data.modifyItem(GenTodo(data.item(43)).withParentUid(QStringLiteral("uid-42"))); // THEN QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->title(), QStringLiteral("44")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("43")); QVERIFY(!replaceHandlerCalled); } void shouldMoveItemToCorrespondingResultWhenRelatedItemChangeForChildTask() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three tasks in the collection (two being top level) data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(43).withParent(42) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43"))); data.createItem(GenTodo().withId(44).withParent(42) .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44")) .withParentUid(QStringLiteral("uid-42"))); auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); QScopedPointer<Domain::TaskQueries> queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto task1 = serializer->createTaskFromItem(data.item(42)); auto task2 = serializer->createTaskFromItem(data.item(43)); auto result1 = queries->findChildren(task1); auto result2 = queries->findChildren(task2); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result1->data().size(), 1); QCOMPARE(result1->data().at(0)->title(), QStringLiteral("44")); QCOMPARE(result2->data().size(), 0); // WHEN data.modifyItem(GenTodo(data.item(44)).withParentUid(QStringLiteral("uid-43"))); // THEN QCOMPARE(result1->data().size(), 0); QCOMPARE(result2->data().size(), 1); QCOMPARE(result2->data().at(0)->title(), QStringLiteral("44")); } void shouldReactToItemRemovesForChildrenTask() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three tasks in the collection (two being children of the first one) data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(43).withParent(42) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43")) .withParentUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(44).withParent(42) .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44")) .withParentUid(QStringLiteral("uid-42"))); auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); QScopedPointer<Domain::TaskQueries> queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto task = serializer->createTaskFromItem(data.item(42)); auto result = queries->findChildren(task); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); // WHEN data.removeItem(Akonadi::Item(43)); // THEN QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("44")); } void shouldLookInAllReportedForTopLevelTasks() { // GIVEN AkonadiFakeData data; // Two top level collections data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withRootAsParent().withTaskContent()); // One task in the first collection data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42"))); // Two tasks in the second collection data.createItem(GenTodo().withId(43).withParent(43).withTitle(QStringLiteral("43"))); data.createItem(GenTodo().withId(44).withParent(43).withTitle(QStringLiteral("44")).withParentUid(QStringLiteral("2"))); // WHEN QScopedPointer<Domain::TaskQueries> queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto result = queries->findTopLevel(); result->data(); result = queries->findTopLevel(); // Should not cause any problem or wrong data // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("43")); } void shouldReactToItemAddsForTopLevelTasks() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); QScopedPointer<Domain::TaskQueries> queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto result = queries->findTopLevel(); TestHelpers::waitForEmptyJobQueue(); QVERIFY(result->data().isEmpty()); // WHEN data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42"))); data.createItem(GenTodo().withId(43).withParent(42).withTitle(QStringLiteral("43")).withParentUid(QStringLiteral("2"))); // THEN QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().first()->title(), QStringLiteral("42")); } void shouldReactToItemRemovesForTopLevelTasks() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three tasks in the collection (one being child of the second one) data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(43).withParent(42) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43"))); data.createItem(GenTodo().withId(44).withParent(42) .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44")) .withParentUid(QStringLiteral("uid-43"))); QScopedPointer<Domain::TaskQueries> queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto result = queries->findTopLevel(); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); // WHEN data.removeItem(Akonadi::Item(43)); // THEN QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); } void shouldReactToItemChangesForTopLevelTasks() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three tasks in the collection (one being child of the second one) data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(43).withParent(42) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43"))); data.createItem(GenTodo().withId(44).withParent(42) .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44")) .withParentUid(QStringLiteral("uid-43"))); QScopedPointer<Domain::TaskQueries> queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto result = queries->findTopLevel(); // Even though the pointer didn't change it's convenient to user if we call // the replace handlers bool replaceHandlerCalled = false; result->addPostReplaceHandler([&replaceHandlerCalled](const Domain::Task::Ptr &, int) { replaceHandlerCalled = true; }); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); // WHEN data.modifyItem(GenTodo(data.item(43)).withTitle(QStringLiteral("43bis"))); // THEN QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("43bis")); QVERIFY(replaceHandlerCalled); } void shouldRemoveItemFromTopLevelResultWhenRelatedItemChangeForTopLevelTask() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three tasks in the collection (one being child of the second one) data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(43).withParent(42) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43"))); data.createItem(GenTodo().withId(44).withParent(42) .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44")) .withParentUid(QStringLiteral("uid-43"))); QScopedPointer<Domain::TaskQueries> queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto result = queries->findTopLevel(); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); // WHEN data.modifyItem(GenTodo(data.item(43)).withParentUid(QStringLiteral("uid-42"))); // THEN QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); } void shouldAddItemToTopLevelResultWhenRelatedItemChangeForChildrenTask() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three tasks in the collection (one being child of the second one) data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(43).withParent(42) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43")) .withParentUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(44).withParent(42) .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44")) .withParentUid(QStringLiteral("uid-43"))); QScopedPointer<Domain::TaskQueries> queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto result = queries->findTopLevel(); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 1); // WHEN data.modifyItem(GenTodo(data.item(43)).withParentUid(QString())); // THEN QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("43")); } void shouldRemoveParentNodeAndMoveChildrenInTopLevelResult() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three tasks in the collection (one being child of the second one) data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(43).withParent(42) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43"))); data.createItem(GenTodo().withId(44).withParent(42) .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44")) .withParentUid(QStringLiteral("uid-43"))); QScopedPointer<Domain::TaskQueries> queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto result = queries->findTopLevel(); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); auto resultChild = queries->findChildren(result->data().at(1)); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(resultChild->data().size(), 1); // WHEN data.removeItem(Akonadi::Item(43)); // THEN QCOMPARE(resultChild->data().size(), 0); QCOMPARE(result->data().size(), 1); // FIXME: Should become 2 once we got a proper cache in place } void shouldNotCrashDuringFindChildrenWhenJobIsKilled() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three tasks in the collection (two being children of the first one) data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(43).withParent(42) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43"))); data.createItem(GenTodo().withId(44).withParent(42) .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44")) .withParentUid(QStringLiteral("uid-42"))); auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); QScopedPointer<Domain::TaskQueries> queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); data.storageBehavior().setFetchItemErrorCode(42, KJob::KilledJobError); // WHEN auto task1 = serializer->createTaskFromItem(data.item(42)); auto result = queries->findChildren(task1); // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QVERIFY(result->data().isEmpty()); } void shouldNotCrashDuringFindChildrenWhenItemsJobReceiveResult_data() { QTest::addColumn<int>("errorCode"); QTest::addColumn<int>("fetchBehavior"); QTest::addColumn<bool>("deleteQuery"); QTest::newRow("No error with empty list") << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << false; QTest::newRow("Error with empty list") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << true; QTest::newRow("Error with list") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::NormalFetch) << true; } void shouldNotCrashDuringFindChildrenWhenItemsJobReceiveResult() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three tasks in the collection (two being children of the first one) data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(43).withParent(42) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43"))); data.createItem(GenTodo().withId(44).withParent(42) .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44")) .withParentUid(QStringLiteral("uid-42"))); auto storage = Akonadi::StorageInterface::Ptr(data.createStorage()); auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); auto monitor = Akonadi::MonitorInterface::Ptr(data.createMonitor()); QScopedPointer<Domain::TaskQueries> queries(new Akonadi::TaskQueries(storage, serializer, monitor, Akonadi::Cache::Ptr())); QFETCH(int, errorCode); QFETCH(int, fetchBehavior); QFETCH(bool, deleteQuery); data.storageBehavior().setFetchItemsErrorCode(42, errorCode); data.storageBehavior().setFetchItemsBehavior(42, AkonadiFakeStorageBehavior::FetchBehavior(fetchBehavior)); // WHEN auto task1 = serializer->createTaskFromItem(data.item(42)); auto result = queries->findChildren(task1); if (deleteQuery) delete queries.take(); // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QVERIFY(result->data().isEmpty()); } void shouldNotCrashDuringFindAllWhenFetchJobFailedOrEmpty_data() { QTest::addColumn<int>("colErrorCode"); QTest::addColumn<int>("colFetchBehavior"); QTest::addColumn<int>("itemsErrorCode"); QTest::addColumn<int>("itemsFetchBehavior"); QTest::addColumn<bool>("deleteQuery"); QTest::newRow("No error with empty collection list") << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::NormalFetch) << false; QTest::newRow("Error with empty collection list") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::NormalFetch) << true; QTest::newRow("Error with collection list") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::NormalFetch) << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::NormalFetch) << true; QTest::newRow("No error with empty item list") << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::NormalFetch) << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << false; QTest::newRow("Error with empty item list") << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::NormalFetch) << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << false; QTest::newRow("Error with item list") << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::NormalFetch) << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::NormalFetch) << false; } void shouldNotCrashDuringFindAllWhenFetchJobFailedOrEmpty() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three tasks in the collection data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(43).withParent(42) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43"))); data.createItem(GenTodo().withId(44).withParent(42) .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44"))); auto storage = Akonadi::StorageInterface::Ptr(data.createStorage()); auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); auto monitor = Akonadi::MonitorInterface::Ptr(data.createMonitor()); QScopedPointer<Domain::TaskQueries> queries(new Akonadi::TaskQueries(storage, serializer, monitor, Akonadi::Cache::Ptr())); QFETCH(int, colErrorCode); QFETCH(int, colFetchBehavior); data.storageBehavior().setFetchCollectionsErrorCode(Akonadi::Collection::root().id(), colErrorCode); data.storageBehavior().setFetchCollectionsBehavior(Akonadi::Collection::root().id(), AkonadiFakeStorageBehavior::FetchBehavior(colFetchBehavior)); QFETCH(int, itemsErrorCode); QFETCH(int, itemsFetchBehavior); data.storageBehavior().setFetchItemsErrorCode(42, itemsErrorCode); data.storageBehavior().setFetchItemsBehavior(42, AkonadiFakeStorageBehavior::FetchBehavior(itemsFetchBehavior)); QFETCH(bool, deleteQuery); // WHEN auto result = queries->findAll(); if (deleteQuery) delete queries.take(); // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QVERIFY(result->data().isEmpty()); } void shouldNotCrashDuringFindTopLevelWhenFetchJobFailedOrEmpty_data() { QTest::addColumn<int>("colErrorCode"); QTest::addColumn<int>("colFetchBehavior"); QTest::addColumn<int>("itemsErrorCode"); QTest::addColumn<int>("itemsFetchBehavior"); QTest::addColumn<bool>("deleteQuery"); QTest::newRow("No error with empty collection list") << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::NormalFetch) << false; QTest::newRow("Error with empty collection list") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::NormalFetch) << true; QTest::newRow("Error with collection list") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::NormalFetch) << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::NormalFetch) << true; QTest::newRow("No error with empty item list") << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::NormalFetch) << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << false; QTest::newRow("Error with empty item list") << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::NormalFetch) << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << false; QTest::newRow("Error with item list") << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::NormalFetch) << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::NormalFetch) << false; } void shouldNotCrashDuringFindTopLevelWhenFetchJobFailedOrEmpty() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three tasks in the collection (third one being child of the first one) data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(43).withParent(42) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43"))); data.createItem(GenTodo().withId(44).withParent(42) .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44")) .withParentUid(QStringLiteral("uid-42"))); auto storage = Akonadi::StorageInterface::Ptr(data.createStorage()); auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); auto monitor = Akonadi::MonitorInterface::Ptr(data.createMonitor()); QScopedPointer<Domain::TaskQueries> queries(new Akonadi::TaskQueries(storage, serializer, monitor, Akonadi::Cache::Ptr())); QFETCH(int, colErrorCode); QFETCH(int, colFetchBehavior); data.storageBehavior().setFetchCollectionsErrorCode(Akonadi::Collection::root().id(), colErrorCode); data.storageBehavior().setFetchCollectionsBehavior(Akonadi::Collection::root().id(), AkonadiFakeStorageBehavior::FetchBehavior(colFetchBehavior)); QFETCH(int, itemsErrorCode); QFETCH(int, itemsFetchBehavior); data.storageBehavior().setFetchItemsErrorCode(42, itemsErrorCode); data.storageBehavior().setFetchItemsBehavior(42, AkonadiFakeStorageBehavior::FetchBehavior(itemsFetchBehavior)); QFETCH(bool, deleteQuery); // WHEN auto result = queries->findTopLevel(); if (deleteQuery) delete queries.take(); // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QVERIFY(result->data().isEmpty()); } void shouldIgnoreProjectsWhenReportingTopLevelTasks() { // GIVEN AkonadiFakeData data; // Two top level collections data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withRootAsParent().withTaskContent()); // One task in the first collection data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); // Two tasks and one project in the second collection data.createItem(GenTodo().withId(43).withParent(43) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43"))); data.createItem(GenTodo().withId(44).withParent(43) .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44")) .withParentUid(QStringLiteral("uid-43"))); data.createItem(GenTodo().withId(45).withParent(43) .withTitle(QStringLiteral("45")).withUid(QStringLiteral("uid-45")) .asProject()); QScopedPointer<Domain::TaskQueries> queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto result = queries->findTopLevel(); result->data(); result = queries->findTopLevel(); // Should not cause any problem or wrong data // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("43")); } void shouldLookInAllSelectedCollectionsForInboxTopLevel() { // GIVEN AkonadiFakeData data; // Three top level collections data.createCollection(GenCollection().withId(42).withRootAsParent().withNoteContent()); data.createCollection(GenCollection().withId(43).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(44).withRootAsParent().withTaskContent().selected(false)); - // One note in the first collection - data.createItem(GenNote().withId(42).withParent(42).withTitle(QStringLiteral("42"))); - // Two tasks in the second collection data.createItem(GenTodo().withId(43).withParent(43).withTitle(QStringLiteral("43"))); data.createItem(GenTodo().withId(44).withParent(43).withTitle(QStringLiteral("44"))); // One task in the third collection data.createItem(GenTodo().withId(45).withParent(44).withTitle(QStringLiteral("45"))); // WHEN QScopedPointer<Domain::TaskQueries> queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto result = queries->findInboxTopLevel(); result->data(); result = queries->findInboxTopLevel(); // Should not cause any problem or wrong data // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->title(), QStringLiteral("43")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("44")); } void shouldIgnoreItemsWhichAreNotTasksInInboxTopLevel() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Two items in the collection data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42"))); // One of them is not a task auto item = Akonadi::Item(43); item.setPayloadFromData("FooBar"); item.setParentCollection(Akonadi::Collection(42)); data.createItem(item); // WHEN QScopedPointer<Domain::TaskQueries> queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto result = queries->findInboxTopLevel(); // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); } void shouldNotHaveTasksWithParentsInInboxTopLevel() { // TODO: Note that this specification is kind of an over simplification which // assumes that all the underlying data is correct. Ideally it should be checked // that the uid referred to actually points to a todo which exists in a proper // collection. We will need a cache to be able to implement that properly though. // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three items in the collection data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(43).withParent(42).withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43")).withParentUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(44).withParent(42).withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44")).withParentUid(QStringLiteral("foo"))); // WHEN QScopedPointer<Domain::TaskQueries> queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto result = queries->findInboxTopLevel(); // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); } void shouldHaveTasksWithContextsInInboxTopLevel_data() { QTest::addColumn<bool>("hasContexts"); QTest::addColumn<bool>("isExpectedInInbox"); QTest::newRow("task with no context") << false << true; QTest::newRow("task with contexts") << true << true; } void shouldHaveTasksWithContextsInInboxTopLevel() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // One context tag data.createTag(GenTag().withId(42).withName(QStringLiteral("tag-42")).asContext()); // One item in the collection QFETCH(bool, hasContexts); auto tagIds = QList<Akonadi::Tag::Id>(); if (hasContexts) tagIds << 42; data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42")).withTags(tagIds)); // WHEN QScopedPointer<Domain::TaskQueries> queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto result = queries->findInboxTopLevel(); // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QFETCH(bool, isExpectedInInbox); if (isExpectedInInbox) { QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); } else { QVERIFY(result->data().isEmpty()); } } void shouldReactToItemAddsForInboxTopLevel_data() { QTest::addColumn<bool>("reactionExpected"); QTest::addColumn<QString>("relatedUid"); QTest::addColumn<bool>("hasContexts"); QTest::newRow("task which should be in inbox") << true << QString() << false; QTest::newRow("task with related uid") << false << "foo" << false; QTest::newRow("task with context") << true << QString() << true; } void shouldReactToItemAddsForInboxTopLevel() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // One context tag data.createTag(GenTag().withId(42).withName(QStringLiteral("tag-42")).asContext()); QScopedPointer<Domain::TaskQueries> queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto result = queries->findInboxTopLevel(); TestHelpers::waitForEmptyJobQueue(); QVERIFY(result->data().isEmpty()); // WHEN QFETCH(QString, relatedUid); QFETCH(bool, hasContexts); auto tagIds = QList<Akonadi::Tag::Id>(); if (hasContexts) tagIds << 42; data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42")).withTags(tagIds).withParentUid(relatedUid)); // THEN QFETCH(bool, reactionExpected); if (reactionExpected) { QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); } else { QVERIFY(result->data().isEmpty()); } } void shouldReactToItemRemovesForInboxTopLevel() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // One item in the collection data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42"))); // WHEN QScopedPointer<Domain::TaskQueries> queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto result = queries->findInboxTopLevel(); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().first()->title(), QStringLiteral("42")); // WHEN data.removeItem(Akonadi::Item(42)); // THEN QVERIFY(result->data().isEmpty()); } void shouldReactToItemChangesForInboxTopLevel_data() { QTest::addColumn<bool>("inListAfterChange"); QTest::addColumn<QString>("relatedUidBefore"); QTest::addColumn<QString>("relatedUidAfter"); QTest::newRow("task appears in inbox (related uid)") << true << "foo" << QString(); QTest::newRow("task disappears from inbox (related uid)") << false << QString() << "foo"; } void shouldReactToItemChangesForInboxTopLevel() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // One context tag data.createTag(GenTag().withId(42).withName(QStringLiteral("tag-42")).asContext()); // Artifact data QFETCH(QString, relatedUidBefore); data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42")).withParentUid(relatedUidBefore)); QScopedPointer<Domain::TaskQueries> queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto result = queries->findInboxTopLevel(); TestHelpers::waitForEmptyJobQueue(); QFETCH(bool, inListAfterChange); if (inListAfterChange) { QVERIFY(result->data().isEmpty()); } else { QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); } // WHEN QFETCH(QString, relatedUidAfter); data.modifyItem(GenTodo(data.item(42)).withParentUid(relatedUidAfter)); // THEN if (inListAfterChange) { QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); } else { QVERIFY(result->data().isEmpty()); } } void shouldReactToCollectionSelectionChangesForInboxTopLevel() { // GIVEN AkonadiFakeData data; // Two top level collections data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withRootAsParent().withTaskContent()); // One task in each collection data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42"))); data.createItem(GenTodo().withId(43).withParent(43).withTitle(QStringLiteral("43"))); QScopedPointer<Domain::TaskQueries> queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto result = queries->findInboxTopLevel(); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("43")); // WHEN data.modifyCollection(GenCollection(data.collection(43)).selected(false)); TestHelpers::waitForEmptyJobQueue(); // THEN QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); } void shouldLookInAllWorkdayReportedForAllTasks_data() { QTest::addColumn<bool>("isExpectedInWorkday"); QTest::addColumn<Akonadi::Item>("item2"); const auto today = Utils::DateTime::currentDate(); QTest::newRow("todayTask") << true << Akonadi::Item(GenTodo() .withStartDate(today) .withDueDate(today)); QTest::newRow("pastTask") << true << Akonadi::Item(GenTodo() .withStartDate(today.addDays(-42)) .withDueDate(today.addDays(-41))); QTest::newRow("startTodayTask") << true << Akonadi::Item(GenTodo() .withStartDate(today)); QTest::newRow("endTodayTask") << true << Akonadi::Item(GenTodo() .withStartDate(today)); QTest::newRow("futureTask") << false << Akonadi::Item(GenTodo() .withStartDate(today.addDays(41)) .withDueDate(today.addDays(42))); QTest::newRow("pastDoneTask") << false << Akonadi::Item(GenTodo() .withStartDate(today.addDays(-42)) .withDueDate(today.addDays(-41)) .done() .withDoneDate(today.addDays(-30))); QTest::newRow("todayDoneTask") << true << Akonadi::Item(GenTodo() .withStartDate(today) .withDueDate(today) .done() .withDoneDate(today)); QTest::newRow("startTodayDoneTask") << true << Akonadi::Item(GenTodo() .withStartDate(today) .done() .withDoneDate(today)); QTest::newRow("endTodayDoneTask") << true << Akonadi::Item(GenTodo() .withDueDate(today) .done() .withDoneDate(today)); } void shouldLookInAllWorkdayReportedForAllTasks() { // GIVEN const auto today = Utils::DateTime::currentDate(); AkonadiFakeData data; // Two top level collections data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withRootAsParent().withTaskContent()); // One task in the first collection data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42")) .withStartDate(today)); // One task in the second collection (from data driven set) QFETCH(Akonadi::Item, item2); data.createItem(GenTodo(item2).withId(43).withParent(43) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43"))); // WHEN auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); auto cache = Akonadi::Cache::Ptr::create(serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor())); QScopedPointer<Domain::TaskQueries> queries(new Akonadi::TaskQueries(createCachingStorage(data, cache), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), cache)); auto result = queries->findWorkdayTopLevel(); result->data(); result = queries->findWorkdayTopLevel(); // Should not cause any problem or wrong data // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QFETCH(bool, isExpectedInWorkday); const int sizeExpected = (isExpectedInWorkday) ? 2 : 1; QCOMPARE(result->data().size(), sizeExpected); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); if (isExpectedInWorkday) QCOMPARE(result->data().at(1)->title(), QStringLiteral("43")); } void shouldLookInAllWorkdayReportedForAllTasksWhenOverrideDate() { // GIVEN qputenv("ZANSHIN_OVERRIDE_DATE", "2015-03-10"); const auto today = Utils::DateTime::currentDate(); AkonadiFakeData data; // Two top level collections data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withRootAsParent().withTaskContent()); // One task in the first collection data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42")) .withStartDate(today)); // One task in the second collection data.createItem(GenTodo().withId(43).withParent(43) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43")) .withStartDate(today)); // WHEN auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); auto cache = Akonadi::Cache::Ptr::create(serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor())); QScopedPointer<Domain::TaskQueries> queries(new Akonadi::TaskQueries(createCachingStorage(data, cache), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), cache)); auto result = queries->findWorkdayTopLevel(); result->data(); result = queries->findWorkdayTopLevel(); // Should not cause any problem or wrong data // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("43")); } void shouldPollForCurrentDayToListWorkday() { // GIVEN qputenv("ZANSHIN_OVERRIDE_DATE", "2015-03-10"); const auto today = Utils::DateTime::currentDate(); AkonadiFakeData data; // Two top level collections data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withRootAsParent().withTaskContent()); // One task in the first collection data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42")) .withStartDate(today)); // One task in the second collection data.createItem(GenTodo().withId(43).withParent(43) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43")) .withStartDate(today.addDays(1))); QScopedPointer<Domain::TaskQueries> queries; { auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); auto cache = Akonadi::Cache::Ptr::create(serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor())); auto akqueries = new Akonadi::TaskQueries(createCachingStorage(data, cache), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), cache); QCOMPARE(akqueries->workdayPollInterval(), 30000); akqueries->setWorkdayPollInterval(500); queries.reset(akqueries); } auto result = queries->findWorkdayTopLevel(); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); // WHEN qputenv("ZANSHIN_OVERRIDE_DATE", "2015-03-11"); QTest::qWait(1000); TestHelpers::waitForEmptyJobQueue(); // THEN QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("43")); } void shouldNotListWorkdayTasksTwiceIfTheyHaveAParentInWorkday() { // GIVEN const auto today = Utils::DateTime::currentDate(); AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // One context tag data.createTag(GenTag().withId(42).withName(QStringLiteral("42")).asContext()); // Five tasks in the collection, two start today, three not, all forming an ancestry line data.createItem(GenTodo().withParent(42).withId(42).withTitle(QStringLiteral("42")).withUid("42")); data.createItem(GenTodo().withParent(42).withId(43).withTitle(QStringLiteral("43")).withUid("43").withParentUid("42").withStartDate(today)); data.createItem(GenTodo().withParent(42).withId(44).withTitle(QStringLiteral("44")).withUid("44").withParentUid("43")); data.createItem(GenTodo().withParent(42).withId(45).withTitle(QStringLiteral("45")).withUid("45").withParentUid("44").withStartDate(today)); data.createItem(GenTodo().withParent(42).withId(46).withTitle(QStringLiteral("46")).withUid("46").withParentUid("45")); // WHEN auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); auto cache = Akonadi::Cache::Ptr::create(serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor())); QScopedPointer<Domain::TaskQueries> queries(new Akonadi::TaskQueries(createCachingStorage(data, cache), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), cache)); auto result = queries->findWorkdayTopLevel(); result->data(); result = queries->findWorkdayTopLevel(); // Should not cause any problem or wrong data // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("43")); // Should not change anything result = queries->findWorkdayTopLevel(); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("43")); } void findProjectShouldLookInCollection() { // GIVEN AkonadiFakeData data; // One top level collection auto collection = GenCollection().withId(42).withRootAsParent().withTaskContent(); data.createCollection(collection); // Three tasks in the collection (two being children of the first one) data.createItem(GenTodo().withId(42).asProject().withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(43).withParent(42) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43")) .withParentUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(44).withParent(42) .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44")) .withParentUid(QStringLiteral("uid-42"))); // WHEN auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); auto cache = Akonadi::Cache::Ptr::create(serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor())); auto storage = createCachingStorage(data, cache); QScopedPointer<Domain::TaskQueries> queries(new Akonadi::TaskQueries(storage, serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()), cache)); auto task = serializer->createTaskFromItem(data.item(44)); // populate cache for collection auto *fetchJob = storage->fetchItems(collection); QVERIFY2(fetchJob->kjob()->exec(), qPrintable(fetchJob->kjob()->errorString())); auto result = queries->findProject(task); // THEN QVERIFY(result); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42")); // Should not change anything result = queries->findProject(task); QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->name(), QStringLiteral("42")); } void findProjectShouldReactToRelationshipChange() { // GIVEN AkonadiFakeData data; // One top level collection const Akonadi::Collection::Id colId = 42; auto collection = GenCollection().withId(colId).withRootAsParent().withTaskContent(); data.createCollection(collection); // Three tasks in the collection (two being children of the first one) // 1->2->3 (project) where 1 changes to 1->4->5 (project) data.createItem(GenTodo().withId(3).asProject().withParent(colId) .withTitle(QStringLiteral("Project 3")).withUid(QStringLiteral("uid-3"))); data.createItem(GenTodo().withId(2).withParent(colId) .withTitle(QStringLiteral("Intermediate item 2")).withUid(QStringLiteral("uid-2")) .withParentUid(QStringLiteral("uid-3"))); data.createItem(GenTodo().withId(1).withParent(colId) .withTitle(QStringLiteral("Item 1")).withUid(QStringLiteral("uid-1")) .withParentUid(QStringLiteral("uid-2"))); data.createItem(GenTodo().withId(5).asProject().withParent(colId) .withTitle(QStringLiteral("Project 5")).withUid(QStringLiteral("uid-5"))); data.createItem(GenTodo().withId(4).withParent(colId) .withTitle(QStringLiteral("Intermediate item 4")).withUid(QStringLiteral("uid-4")) .withParentUid(QStringLiteral("uid-5"))); auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); auto cache = Akonadi::Cache::Ptr::create(serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor())); auto storage = createCachingStorage(data, cache); QScopedPointer<Domain::TaskQueries> queries(new Akonadi::TaskQueries(storage, serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()), cache)); auto task = serializer->createTaskFromItem(data.item(1)); auto result = queries->findProject(task); QVERIFY(result); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->name(), QStringLiteral("Project 3")); // WHEN data.modifyItem(GenTodo(data.item(1)).withParentUid(QStringLiteral("uid-4"))); // THEN TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->name(), QStringLiteral("Project 5")); } void findProjectShouldReactToIntermediateParentChange() { // GIVEN AkonadiFakeData data; // One top level collection const Akonadi::Collection::Id colId = 42; data.createCollection(GenCollection().withId(colId).withRootAsParent().withTaskContent()); // Three tasks in the collection (two being children of the first one) // 1->2->3 (project) where 2 changes to 1->2->4 (project) data.createItem(GenTodo().withId(3).asProject().withParent(colId) .withTitle(QStringLiteral("Project 3")).withUid(QStringLiteral("uid-3"))); data.createItem(GenTodo().withId(2).withParent(colId) .withTitle(QStringLiteral("Intermediate item 2")).withUid(QStringLiteral("uid-2")) .withParentUid(QStringLiteral("uid-3"))); data.createItem(GenTodo().withId(1).withParent(colId) .withTitle(QStringLiteral("Item 1")).withUid(QStringLiteral("uid-1")) .withParentUid(QStringLiteral("uid-2"))); data.createItem(GenTodo().withId(4).asProject().withParent(colId) .withTitle(QStringLiteral("Project 4")).withUid(QStringLiteral("uid-4"))); auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); auto cache = Akonadi::Cache::Ptr::create(serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor())); auto storage = createCachingStorage(data, cache); QScopedPointer<Domain::TaskQueries> queries(new Akonadi::TaskQueries(storage, serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()), cache)); auto task = serializer->createTaskFromItem(data.item(1)); auto result = queries->findProject(task); QVERIFY(result); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->name(), QStringLiteral("Project 3")); // WHEN data.modifyItem(GenTodo(data.item(2)).withParentUid(QStringLiteral("uid-4"))); // THEN TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->name(), QStringLiteral("Project 4")); // AND WHEN data.removeItem(GenTodo(data.item(2))); // THEN TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 0); } void findProjectShouldReactToChildItemRemoved() { // GIVEN AkonadiFakeData data; // One top level collection const Akonadi::Collection::Id colId = 42; data.createCollection(GenCollection().withId(colId).withRootAsParent().withTaskContent()); // Three task in the collection: 1->2(project) data.createItem(GenTodo().withId(2).asProject().withParent(colId) .withTitle(QStringLiteral("Project 2")).withUid(QStringLiteral("uid-2"))); data.createItem(GenTodo().withId(1).withParent(colId) .withTitle(QStringLiteral("Item 1")).withUid(QStringLiteral("uid-1")) .withParentUid(QStringLiteral("uid-2"))); auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); auto cache = Akonadi::Cache::Ptr::create(serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor())); auto storage = createCachingStorage(data, cache); QScopedPointer<Domain::TaskQueries> queries(new Akonadi::TaskQueries(storage, serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()), cache)); auto task = serializer->createTaskFromItem(data.item(1)); auto result = queries->findProject(task); QVERIFY(result); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->name(), QStringLiteral("Project 2")); // WHEN data.removeItem(Akonadi::Item(1)); // THEN TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 0); } }; ZANSHIN_TEST_MAIN(AkonadiTaskQueriesTest) #include "akonaditaskqueriestest.moc" diff --git a/tests/units/domain/CMakeLists.txt b/tests/units/domain/CMakeLists.txt index 2a98fa7b..fa1c67ef 100644 --- a/tests/units/domain/CMakeLists.txt +++ b/tests/units/domain/CMakeLists.txt @@ -1,13 +1,11 @@ zanshin_auto_tests( artifacttest contexttest datasourcetest livequerytest liverelationshipquerytest mockitotest - notetest projecttest queryresulttest - tagtest tasktest ) diff --git a/tests/units/domain/notetest.cpp b/tests/units/domain/notetest.cpp deleted file mode 100644 index 76a5430a..00000000 --- a/tests/units/domain/notetest.cpp +++ /dev/null @@ -1,44 +0,0 @@ -/* This file is part of Zanshin - - Copyright 2014 Kevin Ottens <ervin@kde.org> - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License or (at your option) version 3 or any later version - accepted by the membership of KDE e.V. (or its successor approved - by the membership of KDE e.V.), which shall act as a proxy - defined in Section 14 of version 3 of the license. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - USA. -*/ - -#include <testlib/qtest_zanshin.h> - -#include "domain/note.h" - -using namespace Domain; - -class NoteTest : public QObject -{ - Q_OBJECT -private slots: - void shouldHaveEmptyPropertiesByDefault() - { - Note n; - QCOMPARE(n.text(), QString()); - QCOMPARE(n.title(), QString()); - } -}; - -ZANSHIN_TEST_MAIN(NoteTest) - -#include "notetest.moc" diff --git a/tests/units/domain/tagtest.cpp b/tests/units/domain/tagtest.cpp deleted file mode 100644 index be24d87e..00000000 --- a/tests/units/domain/tagtest.cpp +++ /dev/null @@ -1,61 +0,0 @@ -/* This file is part of Zanshin - - Copyright 2014 Kevin Ottens <ervin@kde.org> - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License or (at your option) version 3 or any later version - accepted by the membership of KDE e.V. (or its successor approved - by the membership of KDE e.V.), which shall act as a proxy - defined in Section 14 of version 3 of the license. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - USA. -*/ - -#include <testlib/qtest_zanshin.h> - -#include "domain/tag.h" - -using namespace Domain; - -class TagTest : public QObject -{ - Q_OBJECT -private slots: - void shouldHaveEmptyPropertiesByDefault() - { - Tag t; - QCOMPARE(t.name(), QString()); - } - - void shouldNotifyNameChanges() - { - Tag t; - QSignalSpy spy(&t, &Tag::nameChanged); - t.setName(QStringLiteral("foo")); - QCOMPARE(spy.count(), 1); - QCOMPARE(spy.first().first().toString(), QStringLiteral("foo")); - } - - void shouldNotNotifyIdenticalNameChanges() - { - Tag t; - t.setName(QStringLiteral("foo")); - QSignalSpy spy(&t, &Tag::nameChanged); - t.setName(QStringLiteral("foo")); - QCOMPARE(spy.count(), 0); - } -}; - -ZANSHIN_TEST_MAIN(TagTest) - -#include "tagtest.moc" diff --git a/tests/units/presentation/CMakeLists.txt b/tests/units/presentation/CMakeLists.txt index e1f740af..70fafd35 100644 --- a/tests/units/presentation/CMakeLists.txt +++ b/tests/units/presentation/CMakeLists.txt @@ -1,23 +1,20 @@ zanshin_auto_tests( applicationmodeltest artifacteditormodeltest artifactfilterproxymodeltest - availablenotepagesmodeltest availablepagessortfilterproxymodeltest availablesourcesmodeltest availabletaskpagesmodeltest errorhandlertest errorhandlingmodelbasetest metatypestest - noteinboxpagemodeltest pagemodeltest projectpagemodeltest querytreemodeltest runningtaskmodeltest - tagpagemodeltest taskapplicationmodeltest taskinboxpagemodeltest tasklistmodeltest contextpagemodeltest workdaypagemodeltest ) diff --git a/tests/units/presentation/artifacteditormodeltest.cpp b/tests/units/presentation/artifacteditormodeltest.cpp index b4c8de79..9df38a2f 100644 --- a/tests/units/presentation/artifacteditormodeltest.cpp +++ b/tests/units/presentation/artifacteditormodeltest.cpp @@ -1,587 +1,532 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens <ervin@kde.org> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include <testlib/qtest_zanshin.h> #include "utils/mockobject.h" #include <QSignalSpy> #include <QTemporaryFile> #include "testlib/fakejob.h" #include "domain/task.h" -#include "domain/note.h" #include "presentation/artifacteditormodel.h" #include "presentation/errorhandler.h" using namespace mockitopp; class FakeErrorHandler : public Presentation::ErrorHandler { public: void doDisplayMessage(const QString &message) override { m_message = message; } QString m_message; }; class ArtifactEditorModelTest : public QObject { Q_OBJECT public: explicit ArtifactEditorModelTest(QObject *parent = Q_NULLPTR) : QObject(parent) { qRegisterMetaType<Domain::Task::Recurrence>(); Presentation::ArtifactEditorModel::setAutoSaveDelay(50); } private slots: void shouldHaveEmptyDefaultState() { // GIVEN Presentation::ArtifactEditorModel model; // WHEN // Nothing // THEN QVERIFY(model.artifact().isNull()); QVERIFY(!model.hasTaskProperties()); QVERIFY(model.text().isEmpty()); QVERIFY(model.title().isEmpty()); QVERIFY(!model.isDone()); QVERIFY(model.startDate().isNull()); QVERIFY(model.dueDate().isNull()); QCOMPARE(model.recurrence(), Domain::Task::NoRecurrence); QVERIFY(model.attachmentModel() != nullptr); QVERIFY(!model.hasSaveFunction()); auto am = model.attachmentModel(); QCOMPARE(am->rowCount(), 0); } void shouldHaveTaskProperties() { // GIVEN Presentation::ArtifactEditorModel model; QSignalSpy textSpy(&model, &Presentation::ArtifactEditorModel::textChanged); QSignalSpy titleSpy(&model, &Presentation::ArtifactEditorModel::titleChanged); QSignalSpy doneSpy(&model, &Presentation::ArtifactEditorModel::doneChanged); QSignalSpy startSpy(&model, &Presentation::ArtifactEditorModel::startDateChanged); QSignalSpy dueSpy(&model, &Presentation::ArtifactEditorModel::dueDateChanged); QSignalSpy recurrenceSpy(&model, &Presentation::ArtifactEditorModel::recurrenceChanged); QSignalSpy attachmentSpy(model.attachmentModel(), &QAbstractItemModel::modelReset); 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); auto task = Domain::Task::Ptr::create(); task->setText(QStringLiteral("description")); task->setTitle(QStringLiteral("title")); task->setDone(true); task->setStartDate(QDate::currentDate()); task->setDueDate(QDate::currentDate().addDays(2)); task->setRecurrence(Domain::Task::RecursDaily); task->setAttachments(attachments); // WHEN model.setArtifact(task); // To make sure we don't signal too much model.setText(task->text()); model.setTitle(task->title()); model.setDone(task->isDone()); model.setStartDate(task->startDate()); model.setDueDate(task->dueDate()); model.setRecurrence(task->recurrence()); // THEN QVERIFY(model.hasTaskProperties()); QCOMPARE(textSpy.size(), 1); QCOMPARE(textSpy.takeFirst().at(0).toString(), task->text()); QCOMPARE(model.property("text").toString(), task->text()); QCOMPARE(titleSpy.size(), 1); QCOMPARE(titleSpy.takeFirst().at(0).toString(), task->title()); QCOMPARE(model.property("title").toString(), task->title()); QCOMPARE(doneSpy.size(), 1); QCOMPARE(doneSpy.takeFirst().at(0).toBool(), task->isDone()); QCOMPARE(model.property("done").toBool(), task->isDone()); QCOMPARE(startSpy.size(), 1); QCOMPARE(startSpy.takeFirst().at(0).toDate(), task->startDate()); QCOMPARE(model.property("startDate").toDate(), task->startDate()); QCOMPARE(dueSpy.size(), 1); QCOMPARE(dueSpy.takeFirst().at(0).toDate(), task->dueDate()); QCOMPARE(model.property("dueDate").toDate(), task->dueDate()); QCOMPARE(recurrenceSpy.size(), 1); QCOMPARE(recurrenceSpy.takeFirst().at(0).value<Domain::Task::Recurrence>(), task->recurrence()); QCOMPARE(model.property("recurrence").value<Domain::Task::Recurrence>(), task->recurrence()); QCOMPARE(attachmentSpy.size(), 1); auto am = model.attachmentModel(); QCOMPARE(am->rowCount(), 2); QCOMPARE(am->data(am->index(0, 0), Qt::DisplayRole).toString(), QStringLiteral("dataAttachment")); QCOMPARE(am->data(am->index(0, 0), Qt::DecorationRole).value<QIcon>(), QIcon::fromTheme("text-plain")); QCOMPARE(am->data(am->index(1, 0), Qt::DisplayRole).toString(), QStringLiteral("uriAttachment")); QCOMPARE(am->data(am->index(1, 0), Qt::DecorationRole).value<QIcon>(), QIcon::fromTheme("text-html")); } - void shouldHaveNoteProperties() - { - // GIVEN - Presentation::ArtifactEditorModel model; - QSignalSpy textSpy(&model, &Presentation::ArtifactEditorModel::textChanged); - QSignalSpy titleSpy(&model, &Presentation::ArtifactEditorModel::titleChanged); - QSignalSpy doneSpy(&model, &Presentation::ArtifactEditorModel::doneChanged); - QSignalSpy startSpy(&model, &Presentation::ArtifactEditorModel::startDateChanged); - QSignalSpy dueSpy(&model, &Presentation::ArtifactEditorModel::dueDateChanged); - - auto note = Domain::Note::Ptr::create(); - note->setText(QStringLiteral("description")); - note->setTitle(QStringLiteral("title")); - - // WHEN - model.setArtifact(note); - // To make sure we don't signal too much - model.setText(note->text()); - model.setTitle(note->title()); - - // THEN - QVERIFY(!model.hasTaskProperties()); - - QCOMPARE(textSpy.size(), 1); - QCOMPARE(textSpy.takeFirst().at(0).toString(), note->text()); - QCOMPARE(model.property("text").toString(), note->text()); - - QCOMPARE(titleSpy.size(), 1); - QCOMPARE(titleSpy.takeFirst().at(0).toString(), note->title()); - QCOMPARE(model.property("title").toString(), note->title()); - - QCOMPARE(doneSpy.size(), 1); - QCOMPARE(doneSpy.takeFirst().at(0).toBool(), false); - QCOMPARE(model.property("done").toBool(), false); - - QCOMPARE(startSpy.size(), 1); - QVERIFY(startSpy.takeFirst().at(0).toDate().isNull()); - QVERIFY(model.property("startDate").toDate().isNull()); - - QCOMPARE(dueSpy.size(), 1); - QVERIFY(dueSpy.takeFirst().at(0).toDate().isNull()); - QVERIFY(model.property("dueDate").toDate().isNull()); - } - void shouldReactToArtifactPropertyChanges_data() { QTest::addColumn<Domain::Artifact::Ptr>("artifact"); QTest::addColumn<QByteArray>("propertyName"); QTest::addColumn<QVariant>("propertyValue"); QTest::addColumn<QByteArray>("signal"); - QTest::newRow("note text") << Domain::Artifact::Ptr(Domain::Note::Ptr::create()) - << QByteArray("text") - << QVariant("new text") - << QByteArray(SIGNAL(textChanged(QString))); - - QTest::newRow("note title") << Domain::Artifact::Ptr(Domain::Note::Ptr::create()) - << QByteArray("title") - << QVariant("new title") - << QByteArray(SIGNAL(titleChanged(QString))); - QTest::newRow("task text") << Domain::Artifact::Ptr(Domain::Task::Ptr::create()) << QByteArray("text") << QVariant("new text") << QByteArray(SIGNAL(textChanged(QString))); QTest::newRow("task title") << Domain::Artifact::Ptr(Domain::Task::Ptr::create()) << QByteArray("title") << QVariant("new title") << QByteArray(SIGNAL(titleChanged(QString))); QTest::newRow("task done") << Domain::Artifact::Ptr(Domain::Task::Ptr::create()) << QByteArray("done") << QVariant(true) << QByteArray(SIGNAL(doneChanged(bool))); QTest::newRow("task start") << Domain::Artifact::Ptr(Domain::Task::Ptr::create()) << QByteArray("startDate") << QVariant(QDate::currentDate()) << QByteArray(SIGNAL(startDateChanged(QDate))); QTest::newRow("task due") << Domain::Artifact::Ptr(Domain::Task::Ptr::create()) << QByteArray("dueDate") << QVariant(QDate::currentDate().addDays(2)) << QByteArray(SIGNAL(dueDateChanged(QDate))); QTest::newRow("task recurrence") << Domain::Artifact::Ptr(Domain::Task::Ptr::create()) << QByteArray("recurrence") << QVariant::fromValue(Domain::Task::RecursDaily) << QByteArray(SIGNAL(recurrenceChanged(Domain::Task::Recurrence))); } void shouldReactToArtifactPropertyChanges() { // GIVEN QFETCH(Domain::Artifact::Ptr, artifact); QFETCH(QByteArray, propertyName); QFETCH(QVariant, propertyValue); QFETCH(QByteArray, signal); Presentation::ArtifactEditorModel model; model.setArtifact(artifact); QSignalSpy spy(&model, signal.constData()); // WHEN artifact->setProperty(propertyName, propertyValue); // THEN QCOMPARE(spy.size(), 1); QCOMPARE(spy.takeFirst().at(0), propertyValue); QCOMPARE(model.property(propertyName), propertyValue); } void shouldNotReactToArtifactPropertyChangesWhenEditing_data() { shouldReactToArtifactPropertyChanges_data(); } void shouldNotReactToArtifactPropertyChangesWhenEditing() { // GIVEN QFETCH(Domain::Artifact::Ptr, artifact); QFETCH(QByteArray, propertyName); QFETCH(QVariant, propertyValue); QFETCH(QByteArray, signal); Presentation::ArtifactEditorModel model; model.setArtifact(artifact); QSignalSpy spy(&model, signal.constData()); // WHEN const auto oldPropertyValue = artifact->property(propertyName); model.setEditingInProgress(true); artifact->setProperty(propertyName, propertyValue); // THEN QVERIFY(spy.isEmpty()); QCOMPARE(model.property(propertyName), oldPropertyValue); } void shouldApplyChangesBackToArtifactAfterADelay_data() { shouldReactToArtifactPropertyChanges_data(); } void shouldApplyChangesBackToArtifactAfterADelay() { // GIVEN QFETCH(Domain::Artifact::Ptr, artifact); QFETCH(QByteArray, propertyName); QFETCH(QVariant, propertyValue); QFETCH(QByteArray, signal); auto savedArtifact = Domain::Artifact::Ptr(); auto save = [this, &savedArtifact] (const Domain::Artifact::Ptr &artifact) { savedArtifact = artifact; return new FakeJob(this); }; Presentation::ArtifactEditorModel model; model.setSaveFunction(save); model.setArtifact(artifact); QSignalSpy spy(&model, signal.constData()); // WHEN model.setProperty(propertyName, propertyValue); // THEN QCOMPARE(spy.size(), 1); QCOMPARE(spy.takeFirst().at(0), propertyValue); QCOMPARE(model.property(propertyName), propertyValue); QVERIFY(artifact->property(propertyName) != propertyValue); QVERIFY(!savedArtifact); // WHEN (apply after delay) QTest::qWait(model.autoSaveDelay() + 50); // THEN QCOMPARE(savedArtifact, artifact); QCOMPARE(artifact->property(propertyName), propertyValue); } void shouldApplyChangesImmediatelyIfANewArtifactIsSet_data() { shouldReactToArtifactPropertyChanges_data(); } void shouldApplyChangesImmediatelyIfANewArtifactIsSet() { // GIVEN QFETCH(Domain::Artifact::Ptr, artifact); QFETCH(QByteArray, propertyName); QFETCH(QVariant, propertyValue); QFETCH(QByteArray, signal); auto savedArtifact = Domain::Artifact::Ptr(); auto save = [this, &savedArtifact] (const Domain::Artifact::Ptr &artifact) { savedArtifact = artifact; return new FakeJob(this); }; Presentation::ArtifactEditorModel model; model.setSaveFunction(save); QVERIFY(model.hasSaveFunction()); model.setArtifact(artifact); QSignalSpy spy(&model, signal.constData()); // WHEN model.setProperty(propertyName, propertyValue); // THEN QCOMPARE(spy.size(), 1); QCOMPARE(spy.takeFirst().at(0), propertyValue); QCOMPARE(model.property(propertyName), propertyValue); QVERIFY(artifact->property(propertyName) != propertyValue); QVERIFY(!savedArtifact); // WHEN (apply immediately) model.setArtifact(Domain::Task::Ptr::create()); // THEN QCOMPARE(savedArtifact, artifact); QCOMPARE(artifact->property(propertyName), propertyValue); savedArtifact.clear(); // WHEN (nothing else happens after a delay) QTest::qWait(model.autoSaveDelay() + 50); // THEN QVERIFY(!savedArtifact); QCOMPARE(artifact->property(propertyName), propertyValue); } void shouldApplyChangesImmediatelyIfDeleted_data() { shouldReactToArtifactPropertyChanges_data(); } void shouldApplyChangesImmediatelyIfDeleted() { // GIVEN QFETCH(Domain::Artifact::Ptr, artifact); QFETCH(QByteArray, propertyName); QFETCH(QVariant, propertyValue); QFETCH(QByteArray, signal); auto savedArtifact = Domain::Artifact::Ptr(); auto save = [this, &savedArtifact] (const Domain::Artifact::Ptr &artifact) { savedArtifact = artifact; return new FakeJob(this); }; auto model = new Presentation::ArtifactEditorModel; model->setSaveFunction(save); QVERIFY(model->hasSaveFunction()); model->setArtifact(artifact); QSignalSpy spy(model, signal.constData()); // WHEN model->setProperty(propertyName, propertyValue); // THEN QCOMPARE(spy.size(), 1); QCOMPARE(spy.takeFirst().at(0), propertyValue); QCOMPARE(model->property(propertyName), propertyValue); QVERIFY(artifact->property(propertyName) != propertyValue); QVERIFY(!savedArtifact); // WHEN (apply immediately) delete model; // THEN QCOMPARE(savedArtifact, artifact); QCOMPARE(artifact->property(propertyName), propertyValue); } void shouldGetAnErrorMessageWhenSaveFailed() { // GIVEN auto task = Domain::Task::Ptr::create(); task->setTitle(QStringLiteral("Task 1")); auto savedArtifact = Domain::Artifact::Ptr(); auto save = [this, &savedArtifact] (const Domain::Artifact::Ptr &artifact) { savedArtifact = artifact; auto job = new FakeJob(this); job->setExpectedError(KJob::KilledJobError, QStringLiteral("Foo")); return job; }; auto model = new Presentation::ArtifactEditorModel; model->setSaveFunction(save); QVERIFY(model->hasSaveFunction()); FakeErrorHandler errorHandler; model->setErrorHandler(&errorHandler); model->setArtifact(task); // WHEN model->setProperty("title", "Foo"); delete model; // THEN QTest::qWait(150); QCOMPARE(errorHandler.m_message, QStringLiteral("Cannot modify task Task 1: Foo")); } void shouldDisconnectFromPreviousArtifact_data() { shouldReactToArtifactPropertyChanges_data(); } void shouldDisconnectFromPreviousArtifact() { // GIVEN QFETCH(Domain::Artifact::Ptr, artifact); QFETCH(QByteArray, propertyName); QFETCH(QVariant, propertyValue); QFETCH(QByteArray, signal); Presentation::ArtifactEditorModel model; model.setArtifact(artifact); QSignalSpy spy(&model, signal.constData()); Domain::Artifact::Ptr newArtifact = Domain::Task::Ptr::create(); // WHEN model.setArtifact(newArtifact); // modifying the *old* artifact should have no effect. artifact->setProperty(propertyName, propertyValue); // THEN QCOMPARE(spy.size(), 1); // emitted by setArtifact QVERIFY(model.property(propertyName) != artifact->property(propertyName)); } void shouldAddAttachments() { // GIVEN QTemporaryFile temporaryFile(QDir::tempPath() + "/artifacteditormodeltest_XXXXXX.txt"); temporaryFile.open(); temporaryFile.write("foo bar"); temporaryFile.close(); auto fileName = temporaryFile.fileName().mid(QDir::tempPath().size() + 1); auto task = Domain::Task::Ptr::create(); auto savedArtifact = Domain::Artifact::Ptr(); auto save = [this, &savedArtifact] (const Domain::Artifact::Ptr &artifact) { savedArtifact = artifact; return new FakeJob(this); }; Presentation::ArtifactEditorModel model; model.setSaveFunction(save); model.setArtifact(task); QSignalSpy spy(model.attachmentModel(), &QAbstractItemModel::modelReset); // WHEN model.addAttachment(temporaryFile.fileName()); // THEN QCOMPARE(spy.size(), 1); QCOMPARE(model.attachmentModel()->rowCount(), 1); QVERIFY(!savedArtifact); // WHEN (nothing else happens after a delay) QTest::qWait(model.autoSaveDelay() + 50); // THEN QCOMPARE(savedArtifact.objectCast<Domain::Task>(), task); QCOMPARE(task->attachments().size(), 1); QCOMPARE(task->attachments().first().label(), fileName); QCOMPARE(task->attachments().first().mimeType(), QStringLiteral("text/plain")); QCOMPARE(task->attachments().first().iconName(), QStringLiteral("text-plain")); QCOMPARE(task->attachments().first().data(), QByteArrayLiteral("foo bar")); } void shouldRemoveAttachments() { // GIVEN auto task = Domain::Task::Ptr::create(); task->setAttachments(Domain::Task::Attachments() << Domain::Task::Attachment("foo") << Domain::Task::Attachment("bar")); auto savedArtifact = Domain::Artifact::Ptr(); auto save = [this, &savedArtifact] (const Domain::Artifact::Ptr &artifact) { savedArtifact = artifact; return new FakeJob(this); }; Presentation::ArtifactEditorModel model; model.setSaveFunction(save); model.setArtifact(task); QSignalSpy spy(model.attachmentModel(), &QAbstractItemModel::modelReset); // WHEN model.removeAttachment(model.attachmentModel()->index(0, 0)); // THEN QCOMPARE(spy.size(), 1); QCOMPARE(model.attachmentModel()->rowCount(), 1); QVERIFY(!savedArtifact); // WHEN (nothing else happens after a delay) QTest::qWait(model.autoSaveDelay() + 50); // THEN QCOMPARE(savedArtifact.objectCast<Domain::Task>(), task); QCOMPARE(task->attachments().size(), 1); QCOMPARE(task->attachments().first().data(), QByteArrayLiteral("bar")); } }; ZANSHIN_TEST_MAIN(ArtifactEditorModelTest) #include "artifacteditormodeltest.moc" diff --git a/tests/units/presentation/artifactfilterproxymodeltest.cpp b/tests/units/presentation/artifactfilterproxymodeltest.cpp index dc2e569d..215efeda 100644 --- a/tests/units/presentation/artifactfilterproxymodeltest.cpp +++ b/tests/units/presentation/artifactfilterproxymodeltest.cpp @@ -1,296 +1,271 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens <ervin@kde.org> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include <testlib/qtest_zanshin.h> #include <QStandardItemModel> -#include "domain/note.h" #include "domain/task.h" #include "presentation/artifactfilterproxymodel.h" #include "presentation/querytreemodelbase.h" #include "utils/datetime.h" Q_DECLARE_METATYPE(QList<QStandardItem*>) class ArtifactFilterProxyModelTest : public QObject { Q_OBJECT private: QStandardItem *createTaskItem(const QString &title, const QString &text, const QDate &start = QDate(), const QDate &due = QDate()) const { auto task = Domain::Task::Ptr::create(); task->setTitle(title); task->setText(text); task->setStartDate(start); task->setDueDate(due); auto item = new QStandardItem; item->setData(task->title(), Qt::DisplayRole); item->setData(QVariant::fromValue(Domain::Artifact::Ptr(task)), Presentation::QueryTreeModelBase::ObjectRole); return item; } - QStandardItem *createNoteItem(const QString &title, const QString &text) const - { - auto note = Domain::Note::Ptr::create(); - note->setTitle(title); - note->setText(text); - - auto item = new QStandardItem; - item->setData(note->title(), Qt::DisplayRole); - item->setData(QVariant::fromValue(Domain::Artifact::Ptr(note)), - Presentation::QueryTreeModelBase::ObjectRole); - return item; - } - private slots: void initTestCase() { qputenv("ZANSHIN_OVERRIDE_DATE", "2015-03-11"); } void shouldHaveDefaultState() { Presentation::ArtifactFilterProxyModel proxy; QVERIFY(!proxy.sourceModel()); QCOMPARE(proxy.sortColumn(), 0); QCOMPARE(proxy.sortOrder(), Qt::AscendingOrder); QCOMPARE(proxy.sortType(), Presentation::ArtifactFilterProxyModel::TitleSort); QCOMPARE(proxy.sortCaseSensitivity(), Qt::CaseInsensitive); QVERIFY(!proxy.showFutureTasks()); } void shouldFilterByTextAndTitle() { // GIVEN QStandardItemModel input; input.appendRow(createTaskItem(QStringLiteral("1. foo"), QStringLiteral("find me"))); input.appendRow(createTaskItem(QStringLiteral("2. Find Me"), QStringLiteral("bar"))); input.appendRow(createTaskItem(QStringLiteral("3. baz"), QStringLiteral("baz"))); - input.appendRow(createNoteItem(QStringLiteral("4. foo"), QStringLiteral("find me"))); - input.appendRow(createNoteItem(QStringLiteral("5. find me"), QStringLiteral("bar"))); - input.appendRow(createNoteItem(QStringLiteral("6. baz"), QStringLiteral("baz"))); Presentation::ArtifactFilterProxyModel output; output.setSourceModel(&input); // WHEN output.setFilterFixedString(QStringLiteral("find me")); // THEN - QCOMPARE(output.rowCount(), 4); + QCOMPARE(output.rowCount(), 2); QCOMPARE(output.index(0, 0).data().toString(), QStringLiteral("1. foo")); QCOMPARE(output.index(1, 0).data().toString(), QStringLiteral("2. Find Me")); - QCOMPARE(output.index(2, 0).data().toString(), QStringLiteral("4. foo")); - QCOMPARE(output.index(3, 0).data().toString(), QStringLiteral("5. find me")); } void shouldFilterByStartDate() { // GIVEN QStandardItemModel input; const auto today = Utils::DateTime::currentDate(); input.appendRow(createTaskItem(QStringLiteral("1. past"), QStringLiteral(""), today.addDays(-1))); input.appendRow(createTaskItem(QStringLiteral("2. present"), QStringLiteral(""), today)); input.appendRow(createTaskItem(QStringLiteral("3. future"), QStringLiteral(""), today.addDays(1))); input.appendRow(createTaskItem(QStringLiteral("4. whatever"), QStringLiteral(""))); Presentation::ArtifactFilterProxyModel output; output.setSourceModel(&input); // WHEN output.setShowFutureTasks(true); // THEN QCOMPARE(output.rowCount(), 4); QCOMPARE(output.index(0, 0).data().toString(), QStringLiteral("1. past")); QCOMPARE(output.index(1, 0).data().toString(), QStringLiteral("2. present")); QCOMPARE(output.index(2, 0).data().toString(), QStringLiteral("3. future")); QCOMPARE(output.index(3, 0).data().toString(), QStringLiteral("4. whatever")); // WHEN output.setShowFutureTasks(false); // THEN QCOMPARE(output.rowCount(), 3); QCOMPARE(output.index(0, 0).data().toString(), QStringLiteral("1. past")); QCOMPARE(output.index(1, 0).data().toString(), QStringLiteral("2. present")); QCOMPARE(output.index(2, 0).data().toString(), QStringLiteral("4. whatever")); } void shouldKeepRowIfItHasAcceptableChildren() { // GIVEN QStandardItemModel input; input.appendRow(createTaskItem(QStringLiteral("1. foo"), QStringLiteral("find me"))); QStandardItem *item = createTaskItem(QStringLiteral("2. baz"), QStringLiteral("baz")); item->appendRow(createTaskItem(QStringLiteral("21. bar"), QStringLiteral("bar"))); - item->appendRow(createNoteItem(QStringLiteral("22. foo"), QStringLiteral("Find Me"))); - item->appendRow(createTaskItem(QStringLiteral("23. find me"), QStringLiteral("foo"))); + item->appendRow(createTaskItem(QStringLiteral("22. find me"), QStringLiteral("foo"))); input.appendRow(item); input.appendRow(createTaskItem(QStringLiteral("3. baz"), QStringLiteral("baz"))); Presentation::ArtifactFilterProxyModel output; output.setSourceModel(&input); // WHEN output.setFilterFixedString(QStringLiteral("find me")); // THEN QCOMPARE(output.rowCount(), 2); QCOMPARE(output.index(0, 0).data().toString(), QStringLiteral("1. foo")); QCOMPARE(output.index(1, 0).data().toString(), QStringLiteral("2. baz")); const QModelIndex parent = output.index(1, 0); - QCOMPARE(output.rowCount(parent), 2); - QCOMPARE(output.index(0, 0, parent).data().toString(), QStringLiteral("22. foo")); - QCOMPARE(output.index(1, 0, parent).data().toString(), QStringLiteral("23. find me")); + QCOMPARE(output.rowCount(parent), 1); + QCOMPARE(output.index(0, 0, parent).data().toString(), QStringLiteral("22. find me")); } void shouldSortFollowingType_data() { QTest::addColumn<int>("sortType"); QTest::addColumn<int>("sortOrder"); QTest::addColumn<QList<QStandardItem*>>("inputItems"); QTest::addColumn<QStringList>("expectedOutputTitles"); QList<QStandardItem*> inputItems; QStringList expectedOutputTitles; inputItems.clear(); expectedOutputTitles.clear(); - inputItems << createTaskItem(QStringLiteral("B"), QStringLiteral("foo")) << createNoteItem(QStringLiteral("A"), QStringLiteral("foo")) << createTaskItem(QStringLiteral("C"), QStringLiteral("foo")); - expectedOutputTitles << QStringLiteral("A") << QStringLiteral("B") << QStringLiteral("C"); + inputItems << createTaskItem(QStringLiteral("B"), QStringLiteral("foo")) << createTaskItem(QStringLiteral("C"), QStringLiteral("foo")); + expectedOutputTitles << QStringLiteral("B") << QStringLiteral("C"); QTest::newRow("title ascending") << int(Presentation::ArtifactFilterProxyModel::TitleSort) << int(Qt::AscendingOrder) << inputItems << expectedOutputTitles; inputItems.clear(); expectedOutputTitles.clear(); - inputItems << createTaskItem(QStringLiteral("B"), QStringLiteral("foo")) << createNoteItem(QStringLiteral("A"), QStringLiteral("foo")) << createTaskItem(QStringLiteral("C"), QStringLiteral("foo")); - expectedOutputTitles << QStringLiteral("C") << QStringLiteral("B") << QStringLiteral("A"); + inputItems << createTaskItem(QStringLiteral("B"), QStringLiteral("foo")) << createTaskItem(QStringLiteral("C"), QStringLiteral("foo")); + expectedOutputTitles << QStringLiteral("C") << QStringLiteral("B"); QTest::newRow("title descending") << int(Presentation::ArtifactFilterProxyModel::TitleSort) << int(Qt::DescendingOrder) << inputItems << expectedOutputTitles; inputItems.clear(); expectedOutputTitles.clear(); inputItems << createTaskItem(QStringLiteral("B"), QStringLiteral("foo"), QDate(2014, 03, 10)) - << createNoteItem(QStringLiteral("A"), QStringLiteral("foo")) << createTaskItem(QStringLiteral("C"), QStringLiteral("foo"), QDate(2014, 03, 01)) << createTaskItem(QStringLiteral("D"), QStringLiteral("foo")); - expectedOutputTitles << QStringLiteral("C") << QStringLiteral("B") << QStringLiteral("D") << QStringLiteral("A"); + expectedOutputTitles << QStringLiteral("C") << QStringLiteral("B") << QStringLiteral("D"); QTest::newRow("start date ascending") << int(Presentation::ArtifactFilterProxyModel::DateSort) << int(Qt::AscendingOrder) << inputItems << expectedOutputTitles; inputItems.clear(); expectedOutputTitles.clear(); inputItems << createTaskItem(QStringLiteral("B"), QStringLiteral("foo"), QDate(2014, 03, 10)) - << createNoteItem(QStringLiteral("A"), QStringLiteral("foo")) << createTaskItem(QStringLiteral("C"), QStringLiteral("foo"), QDate(2014, 03, 01)) << createTaskItem(QStringLiteral("D"), QStringLiteral("foo")); - expectedOutputTitles << QStringLiteral("A") << QStringLiteral("D") << QStringLiteral("B") << QStringLiteral("C"); + expectedOutputTitles << QStringLiteral("D") << QStringLiteral("B") << QStringLiteral("C"); QTest::newRow("start date descending") << int(Presentation::ArtifactFilterProxyModel::DateSort) << int(Qt::DescendingOrder) << inputItems << expectedOutputTitles; inputItems.clear(); expectedOutputTitles.clear(); inputItems << createTaskItem(QStringLiteral("B"), QStringLiteral("foo"), QDate(), QDate(2014, 03, 10)) - << createNoteItem(QStringLiteral("A"), QStringLiteral("foo")) << createTaskItem(QStringLiteral("C"), QStringLiteral("foo"), QDate(), QDate(2014, 03, 01)) << createTaskItem(QStringLiteral("D"), QStringLiteral("foo")); - expectedOutputTitles << QStringLiteral("C") << QStringLiteral("B") << QStringLiteral("D") << QStringLiteral("A"); + expectedOutputTitles << QStringLiteral("C") << QStringLiteral("B") << QStringLiteral("D"); QTest::newRow("due date ascending") << int(Presentation::ArtifactFilterProxyModel::DateSort) << int(Qt::AscendingOrder) << inputItems << expectedOutputTitles; inputItems.clear(); expectedOutputTitles.clear(); inputItems << createTaskItem(QStringLiteral("B"), QStringLiteral("foo"), QDate(), QDate(2014, 03, 10)) - << createNoteItem(QStringLiteral("A"), QStringLiteral("foo")) << createTaskItem(QStringLiteral("C"), QStringLiteral("foo"), QDate(), QDate(2014, 03, 01)) << createTaskItem(QStringLiteral("D"), QStringLiteral("foo")); - expectedOutputTitles << QStringLiteral("A") << QStringLiteral("D") << QStringLiteral("B") << QStringLiteral("C"); + expectedOutputTitles << QStringLiteral("D") << QStringLiteral("B") << QStringLiteral("C"); QTest::newRow("due date descending") << int(Presentation::ArtifactFilterProxyModel::DateSort) << int(Qt::DescendingOrder) << inputItems << expectedOutputTitles; inputItems.clear(); expectedOutputTitles.clear(); inputItems << createTaskItem(QStringLiteral("A"), QStringLiteral("foo"), QDate(2014, 03, 01), QDate(2014, 03, 10)) << createTaskItem(QStringLiteral("B"), QStringLiteral("foo"), QDate(2014, 03, 10), QDate(2014, 03, 01)); expectedOutputTitles << QStringLiteral("B") << QStringLiteral("A"); QTest::newRow("due date over start date") << int(Presentation::ArtifactFilterProxyModel::DateSort) << int(Qt::AscendingOrder) << inputItems << expectedOutputTitles; inputItems.clear(); expectedOutputTitles.clear(); inputItems << createTaskItem(QStringLiteral("A"), QStringLiteral("foo"), QDate(), QDate(2014, 03, 10)) << createTaskItem(QStringLiteral("B"), QStringLiteral("foo"), QDate(2014, 03, 01), QDate()); expectedOutputTitles << QStringLiteral("B") << QStringLiteral("A"); QTest::newRow("due date over start date") << int(Presentation::ArtifactFilterProxyModel::DateSort) << int(Qt::AscendingOrder) << inputItems << expectedOutputTitles; } void shouldSortFollowingType() { // GIVEN QFETCH(int, sortType); QFETCH(int, sortOrder); QFETCH(QList<QStandardItem*>, inputItems); QFETCH(QStringList, expectedOutputTitles); QStandardItemModel input; foreach (QStandardItem *item, inputItems) { input.appendRow(item); } // WHEN Presentation::ArtifactFilterProxyModel output; output.setSourceModel(&input); output.setSortType(Presentation::ArtifactFilterProxyModel::SortType(sortType)); output.setSortOrder(Qt::SortOrder(sortOrder)); QStringList outputTitles; outputTitles.reserve(output.rowCount()); for (int row = 0; row < output.rowCount(); row++) { outputTitles << output.index(row, 0).data().toString(); } // THEN QCOMPARE(outputTitles, expectedOutputTitles); } }; ZANSHIN_TEST_MAIN(ArtifactFilterProxyModelTest) #include "artifactfilterproxymodeltest.moc" diff --git a/tests/units/presentation/availablenotepagesmodeltest.cpp b/tests/units/presentation/availablenotepagesmodeltest.cpp deleted file mode 100644 index 21ef1bf8..00000000 --- a/tests/units/presentation/availablenotepagesmodeltest.cpp +++ /dev/null @@ -1,470 +0,0 @@ -/* This file is part of Zanshin - - Copyright 2015 Kevin Ottens <ervin@kde.org> - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License or (at your option) version 3 or any later version - accepted by the membership of KDE e.V. (or its successor approved - by the membership of KDE e.V.), which shall act as a proxy - defined in Section 14 of version 3 of the license. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - USA. -*/ - -#include <testlib/qtest_zanshin.h> - -#include <memory> - -#include <QMimeData> - -#include <KLocalizedString> - -#include "utils/mockobject.h" - -#include "presentation/availablenotepagesmodel.h" -#include "presentation/errorhandler.h" -#include "presentation/noteinboxpagemodel.h" -#include "presentation/querytreemodelbase.h" -#include "presentation/tagpagemodel.h" - -#include "testlib/fakejob.h" - -using namespace mockitopp; -using namespace mockitopp::matcher; - -class FakeErrorHandler : public Presentation::ErrorHandler -{ -public: - void doDisplayMessage(const QString &message) override - { - m_message = message; - } - - QString m_message; -}; - -class AvailableNotePagesModelTest : public QObject -{ - Q_OBJECT -private slots: - void shouldDeclareOnlyProjectAndContextPages() - { - // GIVEN - Presentation::AvailableNotePagesModel pages({}, {}, {}, {}); - - // THEN - QVERIFY(!pages.hasProjectPages()); - QVERIFY(!pages.hasContextPages()); - QVERIFY(pages.hasTagPages()); - } - - void shouldListAvailablePages() - { - // GIVEN - - // Two tags - auto tag1 = Domain::Tag::Ptr::create(); - tag1->setName(QStringLiteral("Tag 1")); - auto tag2 = Domain::Tag::Ptr::create(); - tag2->setName(QStringLiteral("Tag 2")); - auto tagProvider = Domain::QueryResultProvider<Domain::Tag::Ptr>::Ptr::create(); - auto tagResult = Domain::QueryResult<Domain::Tag::Ptr>::create(tagProvider); - tagProvider->append(tag1); - tagProvider->append(tag2); - - // One note (used for dropping later on) - auto noteToDrop = Domain::Note::Ptr::create(); - - Utils::MockObject<Domain::TagQueries> tagQueriesMock; - tagQueriesMock(&Domain::TagQueries::findAll).when().thenReturn(tagResult); - - Utils::MockObject<Domain::TagRepository> tagRepositoryMock; - - Presentation::AvailableNotePagesModel pages(Domain::NoteQueries::Ptr(), - Domain::NoteRepository::Ptr(), - tagQueriesMock.getInstance(), - tagRepositoryMock.getInstance()); - - // WHEN - QAbstractItemModel *model = pages.pageListModel(); - - // THEN - const QModelIndex inboxIndex = model->index(0, 0); - const QModelIndex tagsIndex = model->index(1, 0); - const QModelIndex tag1Index = model->index(0, 0, tagsIndex); - const QModelIndex tag2Index = model->index(1, 0, tagsIndex); - - QCOMPARE(model->rowCount(), 2); - QCOMPARE(model->rowCount(inboxIndex), 0); - QCOMPARE(model->rowCount(tagsIndex), 2); - QCOMPARE(model->rowCount(tag1Index), 0); - - const Qt::ItemFlags defaultFlags = Qt::ItemIsSelectable - | Qt::ItemIsEnabled - | Qt::ItemIsEditable; - QCOMPARE(model->flags(inboxIndex), (defaultFlags & ~(Qt::ItemIsEditable)) | Qt::ItemIsDropEnabled); - QCOMPARE(model->flags(tagsIndex), Qt::NoItemFlags); - QCOMPARE(model->flags(tag1Index), defaultFlags | Qt::ItemIsDropEnabled); - QCOMPARE(model->flags(tag2Index), defaultFlags | Qt::ItemIsDropEnabled); - - QCOMPARE(model->data(inboxIndex).toString(), i18n("Inbox")); - QCOMPARE(model->data(tagsIndex).toString(), i18n("Tags")); - QCOMPARE(model->data(tag1Index).toString(), tag1->name()); - QCOMPARE(model->data(tag2Index).toString(), tag2->name()); - - QVERIFY(!model->data(inboxIndex, Qt::EditRole).isValid()); - QVERIFY(!model->data(tagsIndex, Qt::EditRole).isValid()); - QCOMPARE(model->data(tag1Index, Qt::EditRole).toString(), tag1->name()); - QCOMPARE(model->data(tag2Index, Qt::EditRole).toString(), tag2->name()); - - QCOMPARE(model->data(inboxIndex, Presentation::QueryTreeModelBase::IconNameRole).toString(), QStringLiteral("mail-folder-inbox")); - QCOMPARE(model->data(tagsIndex, Presentation::QueryTreeModelBase::IconNameRole).toString(), QStringLiteral("folder")); - QCOMPARE(model->data(tag1Index, Presentation::QueryTreeModelBase::IconNameRole).toString(), QStringLiteral("view-pim-tasks")); - QCOMPARE(model->data(tag2Index, Presentation::QueryTreeModelBase::IconNameRole).toString(), QStringLiteral("view-pim-tasks")); - - QVERIFY(!model->data(inboxIndex, Qt::CheckStateRole).isValid()); - QVERIFY(!model->data(tagsIndex, Qt::CheckStateRole).isValid()); - QVERIFY(!model->data(tag1Index, Qt::CheckStateRole).isValid()); - QVERIFY(!model->data(tag2Index, Qt::CheckStateRole).isValid()); - - QVERIFY(!model->setData(inboxIndex, "foo", Qt::EditRole)); - QVERIFY(!model->setData(tagsIndex, "foo", Qt::EditRole)); - QVERIFY(!model->setData(tag1Index, "foo", Qt::EditRole)); - QVERIFY(!model->setData(tag2Index, "foo", Qt::EditRole)); - - // WHEN - tagRepositoryMock(&Domain::TagRepository::associate).when(tag1, noteToDrop).thenReturn(new FakeJob(this)); - auto data = std::make_unique<QMimeData>(); - data->setData(QStringLiteral("application/x-zanshin-object"), "object"); - data->setProperty("objects", QVariant::fromValue(Domain::Artifact::List() << noteToDrop)); - model->dropMimeData(data.get(), Qt::MoveAction, -1, -1, tag1Index); - - // THEN - QVERIFY(tagRepositoryMock(&Domain::TagRepository::associate).when(tag1, noteToDrop).exactly(1)); - - // WHEN - tagRepositoryMock(&Domain::TagRepository::dissociateAll).when(noteToDrop).thenReturn(new FakeJob(this)); - data.reset(new QMimeData); - data->setData(QStringLiteral("application/x-zanshin-object"), "object"); - data->setProperty("objects", QVariant::fromValue(Domain::Artifact::List() << noteToDrop)); - model->dropMimeData(data.get(), Qt::MoveAction, -1, -1, inboxIndex); - QTest::qWait(150); - - // THEN - QVERIFY(tagRepositoryMock(&Domain::TagRepository::dissociateAll).when(noteToDrop).exactly(1)); - } - - void shouldCreateInboxPage() - { - // GIVEN - - // Empty tag provider - auto tagProvider = Domain::QueryResultProvider<Domain::Tag::Ptr>::Ptr::create(); - auto tagResult = Domain::QueryResult<Domain::Tag::Ptr>::create(tagProvider); - - Utils::MockObject<Domain::TagQueries> tagQueriesMock; - tagQueriesMock(&Domain::TagQueries::findAll).when().thenReturn(tagResult); - - Presentation::AvailableNotePagesModel pages(Domain::NoteQueries::Ptr(), - Domain::NoteRepository::Ptr(), - tagQueriesMock.getInstance(), - Domain::TagRepository::Ptr()); - - // WHEN - QAbstractItemModel *model = pages.pageListModel(); - - // THEN - const QModelIndex inboxIndex = model->index(0, 0); - - QObject *inboxPage = pages.createPageForIndex(inboxIndex); - QVERIFY(qobject_cast<Presentation::NoteInboxPageModel*>(inboxPage)); - } - - void shouldCreateTagsPage() - { - // GIVEN - - // Two tags - auto tag1 = Domain::Tag::Ptr::create(); - tag1->setName(QStringLiteral("tag 1")); - auto tag2 = Domain::Tag::Ptr::create(); - tag2->setName(QStringLiteral("tag 2")); - auto tagProvider = Domain::QueryResultProvider<Domain::Tag::Ptr>::Ptr::create(); - auto tagResult = Domain::QueryResult<Domain::Tag::Ptr>::create(tagProvider); - tagProvider->append(tag1); - tagProvider->append(tag2); - - // tags mocking - Utils::MockObject<Domain::TagQueries> tagQueriesMock; - tagQueriesMock(&Domain::TagQueries::findAll).when().thenReturn(tagResult); - - - Presentation::AvailableNotePagesModel pages(Domain::NoteQueries::Ptr(), - Domain::NoteRepository::Ptr(), - tagQueriesMock.getInstance(), - Domain::TagRepository::Ptr()); - - // WHEN - QAbstractItemModel *model = pages.pageListModel(); - - // THEN - const QModelIndex tagsIndex = model->index(1, 0); - const QModelIndex tag1Index = model->index(0, 0, tagsIndex); - const QModelIndex tag2Index = model->index(1, 0, tagsIndex); - - QObject *tagsPage = pages.createPageForIndex(tagsIndex); - QObject *tag1Page = pages.createPageForIndex(tag1Index); - QObject *tag2Page = pages.createPageForIndex(tag2Index); - - QVERIFY(!tagsPage); - QVERIFY(qobject_cast<Presentation::TagPageModel*>(tag1Page)); - QCOMPARE(qobject_cast<Presentation::TagPageModel*>(tag1Page)->tag(), tag1); - QVERIFY(qobject_cast<Presentation::TagPageModel*>(tag2Page)); - QCOMPARE(qobject_cast<Presentation::TagPageModel*>(tag2Page)->tag(), tag2); - } - - void shouldAddTags() - { - // GIVEN - - Utils::MockObject<Domain::TagRepository> tagRepositoryMock; - tagRepositoryMock(&Domain::TagRepository::create).when(any<Domain::Tag::Ptr>()) - .thenReturn(new FakeJob(this)); - - Presentation::AvailableNotePagesModel pages(Domain::NoteQueries::Ptr(), - Domain::NoteRepository::Ptr(), - Domain::TagQueries::Ptr(), - tagRepositoryMock.getInstance()); - - // WHEN - pages.addTag(QStringLiteral("Foo")); - - // THEN - QVERIFY(tagRepositoryMock(&Domain::TagRepository::create).when(any<Domain::Tag::Ptr>()) - .exactly(1)); - } - - void shouldGetAnErrorMessageWhenAddTagFailed() - { - // GIVEN - - Utils::MockObject<Domain::TagRepository> tagRepositoryMock; - auto job = new FakeJob(this); - job->setExpectedError(KJob::KilledJobError, QStringLiteral("Foo")); - tagRepositoryMock(&Domain::TagRepository::create).when(any<Domain::Tag::Ptr>()) - .thenReturn(job); - - Presentation::AvailableNotePagesModel pages(Domain::NoteQueries::Ptr(), - Domain::NoteRepository::Ptr(), - Domain::TagQueries::Ptr(), - tagRepositoryMock.getInstance()); - FakeErrorHandler errorHandler; - pages.setErrorHandler(&errorHandler); - - // WHEN - pages.addTag(QStringLiteral("Foo")); - - // THEN - QTest::qWait(150); - QCOMPARE(errorHandler.m_message, QStringLiteral("Cannot add tag Foo: Foo")); - } - - void shouldRemoveTag() - { - // GIVEN - - // Two tags - auto tag1 = Domain::Tag::Ptr::create(); - tag1->setName(QStringLiteral("tag 1")); - auto tag2 = Domain::Tag::Ptr::create(); - tag2->setName(QStringLiteral("tag 2")); - auto tagProvider = Domain::QueryResultProvider<Domain::Tag::Ptr>::Ptr::create(); - auto tagResult = Domain::QueryResult<Domain::Tag::Ptr>::create(tagProvider); - tagProvider->append(tag1); - tagProvider->append(tag2); - - // tags mocking - Utils::MockObject<Domain::TagQueries> tagQueriesMock; - tagQueriesMock(&Domain::TagQueries::findAll).when().thenReturn(tagResult); - - Utils::MockObject<Domain::TagRepository> tagRepositoryMock; - - Presentation::AvailableNotePagesModel pages(Domain::NoteQueries::Ptr(), - Domain::NoteRepository::Ptr(), - tagQueriesMock.getInstance(), - tagRepositoryMock.getInstance()); - FakeErrorHandler errorHandler; - pages.setErrorHandler(&errorHandler); - - QAbstractItemModel *model = pages.pageListModel(); - - const QModelIndex tagsIndex = model->index(1, 0); - const QModelIndex tag1Index = model->index(0, 0, tagsIndex); - - auto job = new FakeJob(this); - tagRepositoryMock(&Domain::TagRepository::remove).when(tag1).thenReturn(job); - - // WHEN - pages.removeItem(tag1Index); - - // THEN - QTest::qWait(150); - QVERIFY(errorHandler.m_message.isEmpty()); - QVERIFY(tagRepositoryMock(&Domain::TagRepository::remove).when(tag1).exactly(1)); - } - - void shouldGetAnErrorMessageWhenRemoveTagFailed() - { - // GIVEN - - // Two tags - auto tag1 = Domain::Tag::Ptr::create(); - tag1->setName(QStringLiteral("tag 1")); - auto tag2 = Domain::Tag::Ptr::create(); - tag2->setName(QStringLiteral("tag 2")); - auto tagProvider = Domain::QueryResultProvider<Domain::Tag::Ptr>::Ptr::create(); - auto tagResult = Domain::QueryResult<Domain::Tag::Ptr>::create(tagProvider); - tagProvider->append(tag1); - tagProvider->append(tag2); - - // tags mocking - Utils::MockObject<Domain::TagQueries> tagQueriesMock; - tagQueriesMock(&Domain::TagQueries::findAll).when().thenReturn(tagResult); - - Utils::MockObject<Domain::TagRepository> tagRepositoryMock; - - Presentation::AvailableNotePagesModel pages(Domain::NoteQueries::Ptr(), - Domain::NoteRepository::Ptr(), - tagQueriesMock.getInstance(), - tagRepositoryMock.getInstance()); - FakeErrorHandler errorHandler; - pages.setErrorHandler(&errorHandler); - - QAbstractItemModel *model = pages.pageListModel(); - - const QModelIndex tagsIndex = model->index(1, 0); - const QModelIndex tag1Index = model->index(0, 0, tagsIndex); - - auto job = new FakeJob(this); - job->setExpectedError(KJob::KilledJobError, QStringLiteral("Foo")); - tagRepositoryMock(&Domain::TagRepository::remove).when(tag1).thenReturn(job); - - // WHEN - pages.removeItem(tag1Index); - - // THEN - QTest::qWait(150); - QCOMPARE(errorHandler.m_message, QStringLiteral("Cannot remove tag tag 1: Foo")); - } - - void shouldGetAnErrorMessageWhenAssociateTagFailed() - { - // GIVEN - - // Two tags - auto tag1 = Domain::Tag::Ptr::create(); - tag1->setName(QStringLiteral("Tag 1")); - auto tag2 = Domain::Tag::Ptr::create(); - tag2->setName(QStringLiteral("Tag 2")); - auto tagProvider = Domain::QueryResultProvider<Domain::Tag::Ptr>::Ptr::create(); - auto tagResult = Domain::QueryResult<Domain::Tag::Ptr>::create(tagProvider); - tagProvider->append(tag1); - tagProvider->append(tag2); - - // One note (used for dropping later on) - auto noteToDrop = Domain::Note::Ptr::create(); - noteToDrop->setTitle(QStringLiteral("noteDropped")); - - // tags mocking - Utils::MockObject<Domain::TagQueries> tagQueriesMock; - tagQueriesMock(&Domain::TagQueries::findAll).when().thenReturn(tagResult); - - Utils::MockObject<Domain::TagRepository> tagRepositoryMock; - - Presentation::AvailableNotePagesModel pages(Domain::NoteQueries::Ptr(), - Domain::NoteRepository::Ptr(), - tagQueriesMock.getInstance(), - tagRepositoryMock.getInstance()); - FakeErrorHandler errorHandler; - pages.setErrorHandler(&errorHandler); - - QAbstractItemModel *model = pages.pageListModel(); - const QModelIndex tagsIndex = model->index(1, 0); - const QModelIndex tag1Index = model->index(0, 0, tagsIndex); - - // WHEN - auto job = new FakeJob(this); - job->setExpectedError(KJob::KilledJobError, QStringLiteral("Foo")); - tagRepositoryMock(&Domain::TagRepository::associate).when(tag1, noteToDrop).thenReturn(job); - auto data = std::make_unique<QMimeData>(); - data->setData(QStringLiteral("application/x-zanshin-object"), "object"); - data->setProperty("objects", QVariant::fromValue(Domain::Artifact::List() << noteToDrop)); - model->dropMimeData(data.get(), Qt::MoveAction, -1, -1, tag1Index); - - // THEN - QTest::qWait(150); - QCOMPARE(errorHandler.m_message, QStringLiteral("Cannot tag noteDropped with Tag 1: Foo")); - } - - void shouldGetAnErrorMessageWhenDissociateTaskFailed() - { - // GIVEN - - // Two tags - auto tag1 = Domain::Tag::Ptr::create(); - tag1->setName(QStringLiteral("tag 1")); - auto tag2 = Domain::Tag::Ptr::create(); - tag2->setName(QStringLiteral("tag 2")); - auto tagProvider = Domain::QueryResultProvider<Domain::Tag::Ptr>::Ptr::create(); - auto tagResult = Domain::QueryResult<Domain::Tag::Ptr>::create(tagProvider); - tagProvider->append(tag1); - tagProvider->append(tag2); - - // One note (used for dropping later on) - auto noteToDrop = Domain::Note::Ptr::create(); - noteToDrop->setTitle(QStringLiteral("noteDropped")); - - // tags mocking - Utils::MockObject<Domain::TagQueries> tagQueriesMock; - tagQueriesMock(&Domain::TagQueries::findAll).when().thenReturn(tagResult); - - Utils::MockObject<Domain::TagRepository> tagRepositoryMock; - - Presentation::AvailableNotePagesModel pages(Domain::NoteQueries::Ptr(), - Domain::NoteRepository::Ptr(), - tagQueriesMock.getInstance(), - tagRepositoryMock.getInstance()); - FakeErrorHandler errorHandler; - pages.setErrorHandler(&errorHandler); - - QAbstractItemModel *model = pages.pageListModel(); - const QModelIndex inboxIndex = model->index(0, 0); - - // WHEN - auto job = new FakeJob(this); - job->setExpectedError(KJob::KilledJobError, QStringLiteral("Foo")); - tagRepositoryMock(&Domain::TagRepository::dissociateAll).when(noteToDrop).thenReturn(job); - auto data = std::make_unique<QMimeData>(); - data->setData(QStringLiteral("application/x-zanshin-object"), "object"); - data->setProperty("objects", QVariant::fromValue(Domain::Artifact::List() << noteToDrop)); - model->dropMimeData(data.get(), Qt::MoveAction, -1, -1, inboxIndex); - - // THEN - QTest::qWait(150); - QCOMPARE(errorHandler.m_message, QStringLiteral("Cannot move noteDropped to Inbox: Foo")); - } -}; - -ZANSHIN_TEST_MAIN(AvailableNotePagesModelTest) - -#include "availablenotepagesmodeltest.moc" diff --git a/tests/units/presentation/availablepagessortfilterproxymodeltest.cpp b/tests/units/presentation/availablepagessortfilterproxymodeltest.cpp index 7f1b94da..c83b7b15 100644 --- a/tests/units/presentation/availablepagessortfilterproxymodeltest.cpp +++ b/tests/units/presentation/availablepagessortfilterproxymodeltest.cpp @@ -1,110 +1,106 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens <ervin@kde.org> Copyright 2015 David Faure <faure@kde.org> 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 <QStandardItem> #include <testlib/qtest_zanshin.h> #include <mockitopp/mockitopp.hpp> #include "utils/mockobject.h" #include "domain/contextqueries.h" #include "domain/contextrepository.h" #include "domain/projectqueries.h" #include "domain/projectrepository.h" -#include "domain/note.h" -#include "domain/tag.h" -#include "domain/tagqueries.h" -#include "domain/tagrepository.h" #include "domain/task.h" #include "domain/taskrepository.h" #include "presentation/availablepagessortfilterproxymodel.h" #include "presentation/availabletaskpagesmodel.h" #include "presentation/contextpagemodel.h" #include "presentation/projectpagemodel.h" #include "presentation/querytreemodelbase.h" #include "testlib/fakejob.h" using namespace mockitopp; using namespace mockitopp::matcher; static QString extractChildRowsTexts( QAbstractItemModel *model, int row, const QModelIndex &parent = QModelIndex() ) { QString result; const QModelIndex index = model->index(row, 0, parent); const int children = model->rowCount(index); for (int row = 0; row < children; ++row) { const QString txt = model->index(row, 0, index).data().toString(); result += txt.isEmpty() ? QStringLiteral(" ") : txt; if ( row + 1 < children ) result += ';'; } return result; } class AvailablePagesSortFilterProxyModelTest : public QObject { Q_OBJECT private slots: void shouldSortSecondLevel() { // GIVEN a tree model as source QStandardItemModel sourceModel; sourceModel.appendRow(new QStandardItem(QStringLiteral("Projects"))); sourceModel.item(0, 0)->appendRow(new QStandardItem(QStringLiteral("D"))); sourceModel.item(0, 0)->appendRow(new QStandardItem(QStringLiteral("A"))); sourceModel.item(0, 0)->appendRow(new QStandardItem(QStringLiteral("F"))); sourceModel.appendRow(new QStandardItem(QStringLiteral("Contexts"))); sourceModel.item(1, 0)->appendRow(new QStandardItem(QStringLiteral("K"))); sourceModel.item(1, 0)->appendRow(new QStandardItem(QStringLiteral("D"))); sourceModel.item(1, 0)->appendRow(new QStandardItem(QStringLiteral("E"))); sourceModel.appendRow(new QStandardItem(QStringLiteral("Tags"))); QCOMPARE(sourceModel.index(0, 0).data().toString(), QStringLiteral("Projects")); QCOMPARE(extractChildRowsTexts(&sourceModel, 0), QStringLiteral("D;A;F")); QCOMPARE(sourceModel.index(1, 0).data().toString(), QStringLiteral("Contexts")); QCOMPARE(extractChildRowsTexts(&sourceModel, 1), QStringLiteral("K;D;E")); QCOMPARE(sourceModel.index(2, 0).data().toString(), QStringLiteral("Tags")); // WHEN putting an AvailablePagesSortFilterProxyModel on top Presentation::AvailablePagesSortFilterProxyModel proxy; proxy.setSourceModel(&sourceModel); // THEN the projects and contexts should be sorted (but not the toplevel items) QCOMPARE(proxy.index(0, 0).data().toString(), QStringLiteral("Projects")); QCOMPARE(extractChildRowsTexts(&proxy, 0), QStringLiteral("A;D;F")); QCOMPARE(proxy.index(1, 0).data().toString(), QStringLiteral("Contexts")); QCOMPARE(extractChildRowsTexts(&proxy, 1), QStringLiteral("D;E;K")); QCOMPARE(proxy.index(2, 0).data().toString(), QStringLiteral("Tags")); } private: }; ZANSHIN_TEST_MAIN(AvailablePagesSortFilterProxyModelTest) #include "availablepagessortfilterproxymodeltest.moc" diff --git a/tests/units/presentation/availabletaskpagesmodeltest.cpp b/tests/units/presentation/availabletaskpagesmodeltest.cpp index 1c7a2451..2e15fc15 100644 --- a/tests/units/presentation/availabletaskpagesmodeltest.cpp +++ b/tests/units/presentation/availabletaskpagesmodeltest.cpp @@ -1,1283 +1,1258 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens <ervin@kde.org> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include <testlib/qtest_zanshin.h> #include <memory> #include <QMimeData> #include <KLocalizedString> #include "utils/mockobject.h" #include "utils/datetime.h" -#include "domain/note.h" - #include "presentation/availabletaskpagesmodel.h" #include "presentation/contextpagemodel.h" #include "presentation/errorhandler.h" #include "presentation/projectpagemodel.h" #include "presentation/querytreemodelbase.h" #include "presentation/taskinboxpagemodel.h" #include "presentation/workdaypagemodel.h" #include "testlib/fakejob.h" using namespace mockitopp; using namespace mockitopp::matcher; class FakeErrorHandler : public Presentation::ErrorHandler { public: void doDisplayMessage(const QString &message) override { m_message = message; } QString m_message; }; class AvailableTaskPagesModelTest : public QObject { Q_OBJECT private slots: void shouldDeclareOnlyProjectAndContextPages() { // GIVEN Presentation::AvailableTaskPagesModel pages({}, {}, {}, {}, {}, {}, {}); // THEN QVERIFY(pages.hasProjectPages()); QVERIFY(pages.hasContextPages()); QVERIFY(!pages.hasTagPages()); } void shouldListAvailablePages() { // GIVEN // Two selected data sources auto source1 = Domain::DataSource::Ptr::create(); source1->setName("source1"); auto source2 = Domain::DataSource::Ptr::create(); source2->setName("source2"); auto sourceProvider = Domain::QueryResultProvider<Domain::DataSource::Ptr>::Ptr::create(); auto sourceResult = Domain::QueryResult<Domain::DataSource::Ptr>::create(sourceProvider); sourceProvider->append(source1); sourceProvider->append(source2); // Two projects under source 1 auto project11 = Domain::Project::Ptr::create(); project11->setName(QStringLiteral("Project 11")); auto project12 = Domain::Project::Ptr::create(); project12->setName(QStringLiteral("Project 12")); auto project1Provider = Domain::QueryResultProvider<Domain::Project::Ptr>::Ptr::create(); auto project1Result = Domain::QueryResult<Domain::Project::Ptr>::create(project1Provider); project1Provider->append(project12); project1Provider->append(project11); // note: reversed order, to test sorting // Two projects under source 2 auto project21 = Domain::Project::Ptr::create(); project21->setName(QStringLiteral("Project 21")); auto project22 = Domain::Project::Ptr::create(); project22->setName(QStringLiteral("Project 22")); auto project2Provider = Domain::QueryResultProvider<Domain::Project::Ptr>::Ptr::create(); auto project2Result = Domain::QueryResult<Domain::Project::Ptr>::create(project2Provider); project2Provider->append(project22); project2Provider->append(project21); // note: reversed order, to test sorting // Two contexts auto context1 = Domain::Context::Ptr::create(); context1->setName(QStringLiteral("context 1")); auto context2 = Domain::Context::Ptr::create(); context2->setName(QStringLiteral("context 2")); auto contextProvider = Domain::QueryResultProvider<Domain::Context::Ptr>::Ptr::create(); auto contextResult = Domain::QueryResult<Domain::Context::Ptr>::create(contextProvider); contextProvider->append(context1); contextProvider->append(context2); - // Two artifacts (used for dropping later on) + // One artifact (used for dropping later on) Domain::Artifact::Ptr taskToDrop(new Domain::Task); - Domain::Artifact::Ptr noteToDrop(new Domain::Note); Utils::MockObject<Domain::DataSourceQueries> dataSourceQueriesMock; dataSourceQueriesMock(&Domain::DataSourceQueries::findAllSelected).when().thenReturn(sourceResult); dataSourceQueriesMock(&Domain::DataSourceQueries::findProjects).when(source1).thenReturn(project1Result); dataSourceQueriesMock(&Domain::DataSourceQueries::findProjects).when(source2).thenReturn(project2Result); Utils::MockObject<Domain::ProjectRepository> projectRepositoryMock; Utils::MockObject<Domain::TaskRepository> taskRepositoryMock; Utils::MockObject<Domain::ContextQueries> contextQueriesMock; contextQueriesMock(&Domain::ContextQueries::findAll).when().thenReturn(contextResult); Utils::MockObject<Domain::ContextRepository> contextRepositoryMock; Presentation::AvailableTaskPagesModel pages(dataSourceQueriesMock.getInstance(), Domain::ProjectQueries::Ptr(), projectRepositoryMock.getInstance(), contextQueriesMock.getInstance(), contextRepositoryMock.getInstance(), Domain::TaskQueries::Ptr(), taskRepositoryMock.getInstance()); // WHEN QAbstractItemModel *model = pages.pageListModel(); // THEN const QModelIndex inboxIndex = model->index(0, 0); const QModelIndex workdayIndex = model->index(1, 0); const QModelIndex projectsIndex = model->index(2, 0); const QModelIndex source1Index = model->index(0, 0, projectsIndex); const QModelIndex project11Index = model->index(0, 0, source1Index); const QModelIndex project12Index = model->index(1, 0, source1Index); const QModelIndex source2Index = model->index(1, 0, projectsIndex); const QModelIndex project21Index = model->index(0, 0, source2Index); const QModelIndex project22Index = model->index(1, 0, source2Index); const QModelIndex contextsIndex = model->index(3, 0); const QModelIndex context1Index = model->index(0, 0, contextsIndex); const QModelIndex context2Index = model->index(1, 0, contextsIndex); QCOMPARE(model->rowCount(), 4); QCOMPARE(model->rowCount(inboxIndex), 0); QCOMPARE(model->rowCount(workdayIndex), 0); QCOMPARE(model->rowCount(projectsIndex), 2); QCOMPARE(model->rowCount(source1Index), 2); QCOMPARE(model->rowCount(project11Index), 0); QCOMPARE(model->rowCount(project12Index), 0); QCOMPARE(model->rowCount(source2Index), 2); QCOMPARE(model->rowCount(project21Index), 0); QCOMPARE(model->rowCount(project22Index), 0); QCOMPARE(model->rowCount(contextsIndex), 2); QCOMPARE(model->rowCount(context1Index), 0); QCOMPARE(model->rowCount(context2Index), 0); const Qt::ItemFlags defaultFlags = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable; QCOMPARE(model->flags(inboxIndex), (defaultFlags & ~(Qt::ItemIsEditable)) | Qt::ItemIsDropEnabled); QCOMPARE(model->flags(workdayIndex), (defaultFlags & ~(Qt::ItemIsEditable)) | Qt::ItemIsDropEnabled); QCOMPARE(model->flags(projectsIndex), Qt::NoItemFlags); QCOMPARE(model->flags(source1Index), Qt::NoItemFlags); QCOMPARE(model->flags(project11Index), defaultFlags | Qt::ItemIsDropEnabled); QCOMPARE(model->flags(project12Index), defaultFlags | Qt::ItemIsDropEnabled); QCOMPARE(model->flags(source2Index), Qt::NoItemFlags); QCOMPARE(model->flags(project21Index), defaultFlags | Qt::ItemIsDropEnabled); QCOMPARE(model->flags(project22Index), defaultFlags | Qt::ItemIsDropEnabled); QCOMPARE(model->flags(contextsIndex), Qt::NoItemFlags); QCOMPARE(model->flags(context1Index), defaultFlags | Qt::ItemIsDropEnabled); QCOMPARE(model->flags(context2Index), defaultFlags | Qt::ItemIsDropEnabled); QCOMPARE(model->data(inboxIndex).toString(), i18n("Inbox")); QCOMPARE(model->data(workdayIndex).toString(), i18n("Workday")); QCOMPARE(model->data(projectsIndex).toString(), i18n("Projects")); QCOMPARE(model->data(source1Index).toString(), source1->name()); QCOMPARE(model->data(project11Index).toString(), project11->name()); QCOMPARE(model->data(project12Index).toString(), project12->name()); QCOMPARE(model->data(source2Index).toString(), source2->name()); QCOMPARE(model->data(project21Index).toString(), project21->name()); QCOMPARE(model->data(project22Index).toString(), project22->name()); QCOMPARE(model->data(contextsIndex).toString(), i18n("Contexts")); QCOMPARE(model->data(context1Index).toString(), context1->name()); QCOMPARE(model->data(context2Index).toString(), context2->name()); QVERIFY(!model->data(inboxIndex, Qt::EditRole).isValid()); QVERIFY(!model->data(workdayIndex, Qt::EditRole).isValid()); QVERIFY(!model->data(projectsIndex, Qt::EditRole).isValid()); QVERIFY(!model->data(source1Index, Qt::EditRole).isValid()); QCOMPARE(model->data(project11Index, Qt::EditRole).toString(), project11->name()); QCOMPARE(model->data(project12Index, Qt::EditRole).toString(), project12->name()); QVERIFY(!model->data(source2Index, Qt::EditRole).isValid()); QCOMPARE(model->data(project21Index, Qt::EditRole).toString(), project21->name()); QCOMPARE(model->data(project22Index, Qt::EditRole).toString(), project22->name()); QVERIFY(!model->data(contextsIndex, Qt::EditRole).isValid()); QCOMPARE(model->data(context1Index, Qt::EditRole).toString(), context1->name()); QCOMPARE(model->data(context2Index, Qt::EditRole).toString(), context2->name()); QCOMPARE(model->data(inboxIndex, Presentation::QueryTreeModelBase::IconNameRole).toString(), QStringLiteral("mail-folder-inbox")); QCOMPARE(model->data(workdayIndex, Presentation::QueryTreeModelBase::IconNameRole).toString(), QStringLiteral("go-jump-today")); QCOMPARE(model->data(projectsIndex, Presentation::QueryTreeModelBase::IconNameRole).toString(), QStringLiteral("folder")); QCOMPARE(model->data(source1Index, Presentation::QueryTreeModelBase::IconNameRole).toString(), QStringLiteral("folder")); QCOMPARE(model->data(project11Index, Presentation::QueryTreeModelBase::IconNameRole).toString(), QStringLiteral("view-pim-tasks")); QCOMPARE(model->data(project12Index, Presentation::QueryTreeModelBase::IconNameRole).toString(), QStringLiteral("view-pim-tasks")); QCOMPARE(model->data(source2Index, Presentation::QueryTreeModelBase::IconNameRole).toString(), QStringLiteral("folder")); QCOMPARE(model->data(project21Index, Presentation::QueryTreeModelBase::IconNameRole).toString(), QStringLiteral("view-pim-tasks")); QCOMPARE(model->data(project22Index, Presentation::QueryTreeModelBase::IconNameRole).toString(), QStringLiteral("view-pim-tasks")); QCOMPARE(model->data(contextsIndex, Presentation::QueryTreeModelBase::IconNameRole).toString(), QStringLiteral("folder")); QCOMPARE(model->data(context1Index, Presentation::QueryTreeModelBase::IconNameRole).toString(), QStringLiteral("view-pim-notes")); QCOMPARE(model->data(context2Index, Presentation::QueryTreeModelBase::IconNameRole).toString(), QStringLiteral("view-pim-notes")); QVERIFY(!model->data(inboxIndex, Qt::CheckStateRole).isValid()); QVERIFY(!model->data(workdayIndex, Qt::CheckStateRole).isValid()); QVERIFY(!model->data(projectsIndex, Qt::CheckStateRole).isValid()); QVERIFY(!model->data(source1Index, Qt::CheckStateRole).isValid()); QVERIFY(!model->data(project11Index, Qt::CheckStateRole).isValid()); QVERIFY(!model->data(project12Index, Qt::CheckStateRole).isValid()); QVERIFY(!model->data(source2Index, Qt::CheckStateRole).isValid()); QVERIFY(!model->data(project21Index, Qt::CheckStateRole).isValid()); QVERIFY(!model->data(project22Index, Qt::CheckStateRole).isValid()); QVERIFY(!model->data(contextsIndex, Qt::CheckStateRole).isValid()); QVERIFY(!model->data(context1Index, Qt::CheckStateRole).isValid()); QVERIFY(!model->data(context2Index, Qt::CheckStateRole).isValid()); // WHEN projectRepositoryMock(&Domain::ProjectRepository::update).when(project11).thenReturn(new FakeJob(this)); projectRepositoryMock(&Domain::ProjectRepository::update).when(project12).thenReturn(new FakeJob(this)); projectRepositoryMock(&Domain::ProjectRepository::update).when(project21).thenReturn(new FakeJob(this)); projectRepositoryMock(&Domain::ProjectRepository::update).when(project22).thenReturn(new FakeJob(this)); contextRepositoryMock(&Domain::ContextRepository::update).when(context1).thenReturn(new FakeJob(this)); contextRepositoryMock(&Domain::ContextRepository::update).when(context2).thenReturn(new FakeJob(this)); QVERIFY(!model->setData(inboxIndex, "Foo")); QVERIFY(!model->setData(projectsIndex, "Foo")); QVERIFY(!model->setData(source1Index, "Foo")); QVERIFY(model->setData(project11Index, "New Project 11")); QVERIFY(model->setData(project12Index, "New Project 12")); QVERIFY(!model->setData(source2Index, "Foo")); QVERIFY(model->setData(project21Index, "New Project 21")); QVERIFY(model->setData(project22Index, "New Project 22")); QVERIFY(!model->setData(contextsIndex, "Foo")); QVERIFY(model->setData(context1Index, "New Context 1")); QVERIFY(model->setData(context2Index, "New Context 2")); // THEN QVERIFY(projectRepositoryMock(&Domain::ProjectRepository::update).when(project11).exactly(1)); QVERIFY(projectRepositoryMock(&Domain::ProjectRepository::update).when(project12).exactly(1)); QVERIFY(projectRepositoryMock(&Domain::ProjectRepository::update).when(project21).exactly(1)); QVERIFY(projectRepositoryMock(&Domain::ProjectRepository::update).when(project22).exactly(1)); QVERIFY(contextRepositoryMock(&Domain::ContextRepository::update).when(context1).exactly(1)); QVERIFY(contextRepositoryMock(&Domain::ContextRepository::update).when(context2).exactly(1)); QCOMPARE(project11->name(), QStringLiteral("New Project 11")); QCOMPARE(project12->name(), QStringLiteral("New Project 12")); QCOMPARE(project21->name(), QStringLiteral("New Project 21")); QCOMPARE(project22->name(), QStringLiteral("New Project 22")); QCOMPARE(context1->name(), QStringLiteral("New Context 1")); QCOMPARE(context2->name(), QStringLiteral("New Context 2")); // WHEN projectRepositoryMock(&Domain::ProjectRepository::associate).when(project11, taskToDrop).thenReturn(new FakeJob(this)); auto data = std::make_unique<QMimeData>(); data->setData(QStringLiteral("application/x-zanshin-object"), "object"); data->setProperty("objects", QVariant::fromValue(Domain::Artifact::List() << taskToDrop)); model->dropMimeData(data.get(), Qt::MoveAction, -1, -1, project11Index); // THEN QVERIFY(projectRepositoryMock(&Domain::ProjectRepository::associate).when(project11, taskToDrop).exactly(1)); // WHEN a task is dropped on a context contextRepositoryMock(&Domain::ContextRepository::associate).when(context1, taskToDrop.objectCast<Domain::Task>()).thenReturn(new FakeJob(this)); data.reset(new QMimeData); data->setData(QStringLiteral("application/x-zanshin-object"), "object"); data->setProperty("objects", QVariant::fromValue(Domain::Artifact::List() << taskToDrop)); model->dropMimeData(data.get(), Qt::MoveAction, -1, -1, context1Index); // THEN QVERIFY(contextRepositoryMock(&Domain::ContextRepository::associate).when(context1, taskToDrop.objectCast<Domain::Task>()).exactly(1)); // WHEN projectRepositoryMock(&Domain::ProjectRepository::dissociate).when(taskToDrop).thenReturn(new FakeJob(this)); taskRepositoryMock(&Domain::TaskRepository::dissociateAll).when(taskToDrop.objectCast<Domain::Task>()).thenReturn(new FakeJob(this)); data.reset(new QMimeData); data->setData(QStringLiteral("application/x-zanshin-object"), "object"); data->setProperty("objects", QVariant::fromValue(Domain::Artifact::List() << taskToDrop)); model->dropMimeData(data.get(), Qt::MoveAction, -1, -1, inboxIndex); QTest::qWait(150); // THEN QVERIFY(projectRepositoryMock(&Domain::ProjectRepository::dissociate).when(taskToDrop).exactly(1)); QVERIFY(taskRepositoryMock(&Domain::TaskRepository::dissociateAll).when(taskToDrop.objectCast<Domain::Task>()).exactly(1)); - // WHEN - projectRepositoryMock(&Domain::ProjectRepository::associate).when(project12, noteToDrop).thenReturn(new FakeJob(this)); - data.reset(new QMimeData); - data->setData(QStringLiteral("application/x-zanshin-object"), "object"); - data->setProperty("objects", QVariant::fromValue(Domain::Artifact::List() << noteToDrop)); - model->dropMimeData(data.get(), Qt::MoveAction, -1, -1, project12Index); - - // THEN - QVERIFY(projectRepositoryMock(&Domain::ProjectRepository::associate).when(project12, noteToDrop).exactly(1)); - // WHEN Domain::Artifact::Ptr taskToDrop2(new Domain::Task); - Domain::Artifact::Ptr noteToDrop2(new Domain::Note); projectRepositoryMock(&Domain::ProjectRepository::associate).when(project11, taskToDrop2).thenReturn(new FakeJob(this)); - projectRepositoryMock(&Domain::ProjectRepository::associate).when(project11, noteToDrop2).thenReturn(new FakeJob(this)); data.reset(new QMimeData); data->setData(QStringLiteral("application/x-zanshin-object"), "object"); - data->setProperty("objects", QVariant::fromValue(Domain::Artifact::List() << taskToDrop2 << noteToDrop2)); + data->setProperty("objects", QVariant::fromValue(Domain::Artifact::List() << taskToDrop2)); model->dropMimeData(data.get(), Qt::MoveAction, -1, -1, project11Index); // THEN QVERIFY(projectRepositoryMock(&Domain::ProjectRepository::associate).when(project11, taskToDrop2).exactly(1)); - QVERIFY(projectRepositoryMock(&Domain::ProjectRepository::associate).when(project11, noteToDrop2).exactly(1)); - - // WHEN a task and a note are dropped on a context - data.reset(new QMimeData); - data->setData(QStringLiteral("application/x-zanshin-object"), "object"); - data->setProperty("objects", QVariant::fromValue(Domain::Artifact::List() << taskToDrop2 << noteToDrop2)); - model->dropMimeData(data.get(), Qt::MoveAction, -1, -1, context1Index); - - // THEN - QVERIFY(contextRepositoryMock(&Domain::ContextRepository::associate).when(context1, taskToDrop2.objectCast<Domain::Task>()).exactly(0)); // WHEN two tasks are dropped on a context Domain::Task::Ptr taskToDrop3(new Domain::Task); Domain::Task::Ptr taskToDrop4(new Domain::Task); contextRepositoryMock(&Domain::ContextRepository::associate).when(context1, taskToDrop3).thenReturn(new FakeJob(this)); contextRepositoryMock(&Domain::ContextRepository::associate).when(context1, taskToDrop4).thenReturn(new FakeJob(this)); data.reset(new QMimeData); data->setData(QStringLiteral("application/x-zanshin-object"), "object"); data->setProperty("objects", QVariant::fromValue(Domain::Artifact::List() << taskToDrop3 << taskToDrop4)); model->dropMimeData(data.get(), Qt::MoveAction, -1, -1, context1Index); // THEN QVERIFY(contextRepositoryMock(&Domain::ContextRepository::associate).when(context1, taskToDrop3).exactly(1)); QVERIFY(contextRepositoryMock(&Domain::ContextRepository::associate).when(context1, taskToDrop4).exactly(1)); // WHEN a task is drop on the workday Domain::Task::Ptr taskToDrop5(new Domain::Task); taskRepositoryMock(&Domain::TaskRepository::update).when(taskToDrop5).thenReturn(new FakeJob(this)); data.reset(new QMimeData); data->setData(QStringLiteral("application/x-zanshin-object"), "object"); data->setProperty("objects", QVariant::fromValue(Domain::Artifact::List() << taskToDrop5)); model->dropMimeData(data.get(), Qt::MoveAction, -1, -1, workdayIndex); // THEN QCOMPARE(taskToDrop5->startDate(), Utils::DateTime::currentDate()); // WHEN two task are drop on the workday Domain::Task::Ptr taskToDrop6(new Domain::Task); Domain::Task::Ptr taskToDrop7(new Domain::Task); taskRepositoryMock(&Domain::TaskRepository::update).when(taskToDrop6).thenReturn(new FakeJob(this)); taskRepositoryMock(&Domain::TaskRepository::update).when(taskToDrop7).thenReturn(new FakeJob(this)); data.reset(new QMimeData); data->setData(QStringLiteral("application/x-zanshin-object"), "object"); data->setProperty("objects", QVariant::fromValue(Domain::Artifact::List() << taskToDrop6 << taskToDrop7)); model->dropMimeData(data.get(), Qt::MoveAction, -1, -1, workdayIndex); // THEN QCOMPARE(taskToDrop6->startDate(), Utils::DateTime::currentDate()); QCOMPARE(taskToDrop7->startDate(), Utils::DateTime::currentDate()); } void shouldCreateInboxPage() { // GIVEN // Empty sources provider auto sourceProvider = Domain::QueryResultProvider<Domain::DataSource::Ptr>::Ptr::create(); auto sourceResult = Domain::QueryResult<Domain::DataSource::Ptr>::create(sourceProvider); // Empty context provider auto contextProvider = Domain::QueryResultProvider<Domain::Context::Ptr>::Ptr::create(); auto contextResult = Domain::QueryResult<Domain::Context::Ptr>::create(contextProvider); // context mocking Utils::MockObject<Domain::ContextQueries> contextQueriesMock; contextQueriesMock(&Domain::ContextQueries::findAll).when().thenReturn(contextResult); Utils::MockObject<Domain::ContextRepository> contextRepositoryMock; // sources mocking Utils::MockObject<Domain::DataSourceQueries> dataSourceQueriesMock; dataSourceQueriesMock(&Domain::DataSourceQueries::findAllSelected).when().thenReturn(sourceResult); Utils::MockObject<Domain::ProjectRepository> projectRepositoryMock; Presentation::AvailableTaskPagesModel pages(dataSourceQueriesMock.getInstance(), Domain::ProjectQueries::Ptr(), projectRepositoryMock.getInstance(), contextQueriesMock.getInstance(), contextRepositoryMock.getInstance(), Domain::TaskQueries::Ptr(), Domain::TaskRepository::Ptr()); // WHEN QAbstractItemModel *model = pages.pageListModel(); // THEN const QModelIndex inboxIndex = model->index(0, 0); QObject *inboxPage = pages.createPageForIndex(inboxIndex); QVERIFY(qobject_cast<Presentation::TaskInboxPageModel*>(inboxPage)); } void shouldCreateWorkdayPage() { // GIVEN // Empty sources provider auto sourceProvider = Domain::QueryResultProvider<Domain::DataSource::Ptr>::Ptr::create(); auto sourceResult = Domain::QueryResult<Domain::DataSource::Ptr>::create(sourceProvider); // Empty context provider auto contextProvider = Domain::QueryResultProvider<Domain::Context::Ptr>::Ptr::create(); auto contextResult = Domain::QueryResult<Domain::Context::Ptr>::create(contextProvider); // context mocking Utils::MockObject<Domain::ContextQueries> contextQueriesMock; contextQueriesMock(&Domain::ContextQueries::findAll).when().thenReturn(contextResult); Utils::MockObject<Domain::ContextRepository> contextRepositoryMock; // sources mocking Utils::MockObject<Domain::DataSourceQueries> dataSourceQueriesMock; dataSourceQueriesMock(&Domain::DataSourceQueries::findAllSelected).when().thenReturn(sourceResult); Utils::MockObject<Domain::ProjectRepository> projectRepositoryMock; Presentation::AvailableTaskPagesModel pages(dataSourceQueriesMock.getInstance(), Domain::ProjectQueries::Ptr(), projectRepositoryMock.getInstance(), contextQueriesMock.getInstance(), contextRepositoryMock.getInstance(), Domain::TaskQueries::Ptr(), Domain::TaskRepository::Ptr()); // WHEN QAbstractItemModel *model = pages.pageListModel(); // THEN const QModelIndex workdayIndex = model->index(1, 0); QObject *workdayPage = pages.createPageForIndex(workdayIndex); QVERIFY(qobject_cast<Presentation::WorkdayPageModel*>(workdayPage)); } void shouldCreateProjectsPage() { // GIVEN // One selected data source auto source = Domain::DataSource::Ptr::create(); source->setName("source"); auto sourceProvider = Domain::QueryResultProvider<Domain::DataSource::Ptr>::Ptr::create(); auto sourceResult = Domain::QueryResult<Domain::DataSource::Ptr>::create(sourceProvider); sourceProvider->append(source); // Two projects auto project1 = Domain::Project::Ptr::create(); project1->setName(QStringLiteral("Project 11")); auto project2 = Domain::Project::Ptr::create(); project2->setName(QStringLiteral("Project 12")); auto projectProvider = Domain::QueryResultProvider<Domain::Project::Ptr>::Ptr::create(); auto projectResult = Domain::QueryResult<Domain::Project::Ptr>::create(projectProvider); projectProvider->append(project1); projectProvider->append(project2); // No contexts auto contextProvider = Domain::QueryResultProvider<Domain::Context::Ptr>::Ptr::create(); auto contextResult = Domain::QueryResult<Domain::Context::Ptr>::create(contextProvider); // data source mocking Utils::MockObject<Domain::DataSourceQueries> dataSourceQueriesMock; dataSourceQueriesMock(&Domain::DataSourceQueries::findAllSelected).when().thenReturn(sourceResult); dataSourceQueriesMock(&Domain::DataSourceQueries::findProjects).when(source).thenReturn(projectResult); // projects mocking Utils::MockObject<Domain::ProjectRepository> projectRepositoryMock; // contexts mocking Utils::MockObject<Domain::ContextQueries> contextQueriesMock; contextQueriesMock(&Domain::ContextQueries::findAll).when().thenReturn(contextResult); Presentation::AvailableTaskPagesModel pages(dataSourceQueriesMock.getInstance(), Domain::ProjectQueries::Ptr(), projectRepositoryMock.getInstance(), contextQueriesMock.getInstance(), Domain::ContextRepository::Ptr(), Domain::TaskQueries::Ptr(), Domain::TaskRepository::Ptr()); // WHEN QAbstractItemModel *model = pages.pageListModel(); // THEN const QModelIndex projectsIndex = model->index(2, 0); const QModelIndex sourceIndex = model->index(0, 0, projectsIndex); const QModelIndex project1Index = model->index(0, 0, sourceIndex); const QModelIndex project2Index = model->index(1, 0, sourceIndex); QObject *projectsPage = pages.createPageForIndex(projectsIndex); QObject *sourcesPage = pages.createPageForIndex(sourceIndex); QObject *project1Page = pages.createPageForIndex(project1Index); QObject *project2Page = pages.createPageForIndex(project2Index); QVERIFY(!projectsPage); QVERIFY(!sourcesPage); QVERIFY(qobject_cast<Presentation::ProjectPageModel*>(project1Page)); QCOMPARE(qobject_cast<Presentation::ProjectPageModel*>(project1Page)->project(), project1); QVERIFY(qobject_cast<Presentation::ProjectPageModel*>(project2Page)); QCOMPARE(qobject_cast<Presentation::ProjectPageModel*>(project2Page)->project(), project2); } void shouldCreateContextsPage() { // GIVEN // Two contexts auto context1 = Domain::Context::Ptr::create(); context1->setName(QStringLiteral("context 1")); auto context2 = Domain::Context::Ptr::create(); context2->setName(QStringLiteral("context 2")); auto contextProvider = Domain::QueryResultProvider<Domain::Context::Ptr>::Ptr::create(); auto contextResult = Domain::QueryResult<Domain::Context::Ptr>::create(contextProvider); contextProvider->append(context1); contextProvider->append(context2); // Empty sources provider auto sourceProvider = Domain::QueryResultProvider<Domain::DataSource::Ptr>::Ptr::create(); auto sourceResult = Domain::QueryResult<Domain::DataSource::Ptr>::create(sourceProvider); // contexts mocking Utils::MockObject<Domain::ContextQueries> contextQueriesMock; contextQueriesMock(&Domain::ContextQueries::findAll).when().thenReturn(contextResult); Utils::MockObject<Domain::ContextRepository> contextRepositoryMock; // sources mocking Utils::MockObject<Domain::DataSourceQueries> dataSourceQueriesMock; dataSourceQueriesMock(&Domain::DataSourceQueries::findAllSelected).when().thenReturn(sourceResult); // projects mocking Utils::MockObject<Domain::ProjectRepository> projectRepositoryMock; Presentation::AvailableTaskPagesModel pages(dataSourceQueriesMock.getInstance(), Domain::ProjectQueries::Ptr(), projectRepositoryMock.getInstance(), contextQueriesMock.getInstance(), contextRepositoryMock.getInstance(), Domain::TaskQueries::Ptr(), Domain::TaskRepository::Ptr()); // WHEN QAbstractItemModel *model = pages.pageListModel(); // THEN const QModelIndex contextsIndex = model->index(3, 0); const QModelIndex context1Index = model->index(0, 0, contextsIndex); const QModelIndex context2Index = model->index(1, 0, contextsIndex); QObject *contextsPage = pages.createPageForIndex(contextsIndex); QObject *context1Page = pages.createPageForIndex(context1Index); QObject *context2Page = pages.createPageForIndex(context2Index); QVERIFY(!contextsPage); QVERIFY(qobject_cast<Presentation::ContextPageModel*>(context1Page)); QCOMPARE(qobject_cast<Presentation::ContextPageModel*>(context1Page)->context(), context1); QVERIFY(qobject_cast<Presentation::ContextPageModel*>(context2Page)); QCOMPARE(qobject_cast<Presentation::ContextPageModel*>(context2Page)->context(), context2); } void shouldAddProjects() { // GIVEN auto source = Domain::DataSource::Ptr::create(); Utils::MockObject<Domain::ProjectRepository> projectRepositoryMock; projectRepositoryMock(&Domain::ProjectRepository::create).when(any<Domain::Project::Ptr>(), any<Domain::DataSource::Ptr>()) .thenReturn(new FakeJob(this)); Presentation::AvailableTaskPagesModel pages(Domain::DataSourceQueries::Ptr(), Domain::ProjectQueries::Ptr(), projectRepositoryMock.getInstance(), Domain::ContextQueries::Ptr(), Domain::ContextRepository::Ptr(), Domain::TaskQueries::Ptr(), Domain::TaskRepository::Ptr()); // WHEN pages.addProject(QStringLiteral("Foo"), source); // THEN QVERIFY(projectRepositoryMock(&Domain::ProjectRepository::create).when(any<Domain::Project::Ptr>(), any<Domain::DataSource::Ptr>()) .exactly(1)); } void shouldGetAnErrorMessageWhenAddProjectFailed() { // GIVEN auto source = Domain::DataSource::Ptr::create(); source->setName(QStringLiteral("Source1")); Utils::MockObject<Domain::ProjectRepository> projectRepositoryMock; auto job = new FakeJob(this); job->setExpectedError(KJob::KilledJobError, QStringLiteral("Foo")); projectRepositoryMock(&Domain::ProjectRepository::create).when(any<Domain::Project::Ptr>(), any<Domain::DataSource::Ptr>()) .thenReturn(job); Presentation::AvailableTaskPagesModel pages(Domain::DataSourceQueries::Ptr(), Domain::ProjectQueries::Ptr(), projectRepositoryMock.getInstance(), Domain::ContextQueries::Ptr(), Domain::ContextRepository::Ptr(), Domain::TaskQueries::Ptr(), Domain::TaskRepository::Ptr()); FakeErrorHandler errorHandler; pages.setErrorHandler(&errorHandler); // WHEN pages.addProject(QStringLiteral("Foo"), source); // THEN QTest::qWait(150); QCOMPARE(errorHandler.m_message, QStringLiteral("Cannot add project Foo in dataSource Source1: Foo")); } void shouldAddContexts() { // GIVEN Utils::MockObject<Domain::ContextRepository> contextRepositoryMock; contextRepositoryMock(&Domain::ContextRepository::create).when(any<Domain::Context::Ptr>()) .thenReturn(new FakeJob(this)); Presentation::AvailableTaskPagesModel pages(Domain::DataSourceQueries::Ptr(), Domain::ProjectQueries::Ptr(), Domain::ProjectRepository::Ptr(), Domain::ContextQueries::Ptr(), contextRepositoryMock.getInstance(), Domain::TaskQueries::Ptr(), Domain::TaskRepository::Ptr()); // WHEN pages.addContext(QStringLiteral("Foo")); // THEN QVERIFY(contextRepositoryMock(&Domain::ContextRepository::create).when(any<Domain::Context::Ptr>()) .exactly(1)); } void shouldGetAnErrorMessageWhenAddContextFailed() { // GIVEN Utils::MockObject<Domain::ContextRepository> contextRepositoryMock; auto job = new FakeJob(this); job->setExpectedError(KJob::KilledJobError, QStringLiteral("Foo")); contextRepositoryMock(&Domain::ContextRepository::create).when(any<Domain::Context::Ptr>()) .thenReturn(job); Presentation::AvailableTaskPagesModel pages(Domain::DataSourceQueries::Ptr(), Domain::ProjectQueries::Ptr(), Domain::ProjectRepository::Ptr(), Domain::ContextQueries::Ptr(), contextRepositoryMock.getInstance(), Domain::TaskQueries::Ptr(), Domain::TaskRepository::Ptr()); FakeErrorHandler errorHandler; pages.setErrorHandler(&errorHandler); // WHEN pages.addContext(QStringLiteral("Foo")); // THEN QTest::qWait(150); QCOMPARE(errorHandler.m_message, QStringLiteral("Cannot add context Foo: Foo")); } void shouldRemoveProject() { // GIVEN // One selected data source auto source = Domain::DataSource::Ptr::create(); source->setName("source"); auto sourceProvider = Domain::QueryResultProvider<Domain::DataSource::Ptr>::Ptr::create(); auto sourceResult = Domain::QueryResult<Domain::DataSource::Ptr>::create(sourceProvider); sourceProvider->append(source); // Two projects auto project1 = Domain::Project::Ptr::create(); project1->setName(QStringLiteral("Project 1")); auto project2 = Domain::Project::Ptr::create(); project2->setName(QStringLiteral("Project 2")); auto projectProvider = Domain::QueryResultProvider<Domain::Project::Ptr>::Ptr::create(); auto projectResult = Domain::QueryResult<Domain::Project::Ptr>::create(projectProvider); projectProvider->append(project1); projectProvider->append(project2); // No contexts auto contextProvider = Domain::QueryResultProvider<Domain::Context::Ptr>::Ptr::create(); auto contextResult = Domain::QueryResult<Domain::Context::Ptr>::create(contextProvider); // data source mocking Utils::MockObject<Domain::DataSourceQueries> dataSourceQueriesMock; dataSourceQueriesMock(&Domain::DataSourceQueries::findAllSelected).when().thenReturn(sourceResult); dataSourceQueriesMock(&Domain::DataSourceQueries::findProjects).when(source).thenReturn(projectResult); // projects mocking Utils::MockObject<Domain::ProjectRepository> projectRepositoryMock; // contexts mocking Utils::MockObject<Domain::ContextQueries> contextQueriesMock; contextQueriesMock(&Domain::ContextQueries::findAll).when().thenReturn(contextResult); Presentation::AvailableTaskPagesModel pages(dataSourceQueriesMock.getInstance(), Domain::ProjectQueries::Ptr(), projectRepositoryMock.getInstance(), contextQueriesMock.getInstance(), Domain::ContextRepository::Ptr(), Domain::TaskQueries::Ptr(), Domain::TaskRepository::Ptr()); QAbstractItemModel *model = pages.pageListModel(); const QModelIndex projectsIndex = model->index(2, 0); const QModelIndex sourceIndex = model->index(0, 0, projectsIndex); const QModelIndex project1Index = model->index(0, 0, sourceIndex); projectRepositoryMock(&Domain::ProjectRepository::remove).when(project1).thenReturn(new FakeJob(this)); // WHEN pages.removeItem(project1Index); // THEN QVERIFY(projectRepositoryMock(&Domain::ProjectRepository::remove).when(project1).exactly(1)); } void shouldGetAnErrorMessageWhenRemoveProjectFailed() { // GIVEN // One selected data source auto source = Domain::DataSource::Ptr::create(); source->setName("source"); auto sourceProvider = Domain::QueryResultProvider<Domain::DataSource::Ptr>::Ptr::create(); auto sourceResult = Domain::QueryResult<Domain::DataSource::Ptr>::create(sourceProvider); sourceProvider->append(source); // Two projects auto project1 = Domain::Project::Ptr::create(); project1->setName(QStringLiteral("Project 1")); auto project2 = Domain::Project::Ptr::create(); project2->setName(QStringLiteral("Project 2")); auto projectProvider = Domain::QueryResultProvider<Domain::Project::Ptr>::Ptr::create(); auto projectResult = Domain::QueryResult<Domain::Project::Ptr>::create(projectProvider); projectProvider->append(project1); projectProvider->append(project2); // No contexts auto contextProvider = Domain::QueryResultProvider<Domain::Context::Ptr>::Ptr::create(); auto contextResult = Domain::QueryResult<Domain::Context::Ptr>::create(contextProvider); // data source mocking Utils::MockObject<Domain::DataSourceQueries> dataSourceQueriesMock; dataSourceQueriesMock(&Domain::DataSourceQueries::findAllSelected).when().thenReturn(sourceResult); dataSourceQueriesMock(&Domain::DataSourceQueries::findProjects).when(source).thenReturn(projectResult); // projects mocking Utils::MockObject<Domain::ProjectRepository> projectRepositoryMock; // contexts mocking Utils::MockObject<Domain::ContextQueries> contextQueriesMock; contextQueriesMock(&Domain::ContextQueries::findAll).when().thenReturn(contextResult); Presentation::AvailableTaskPagesModel pages(dataSourceQueriesMock.getInstance(), Domain::ProjectQueries::Ptr(), projectRepositoryMock.getInstance(), contextQueriesMock.getInstance(), Domain::ContextRepository::Ptr(), Domain::TaskQueries::Ptr(), Domain::TaskRepository::Ptr()); FakeErrorHandler errorHandler; pages.setErrorHandler(&errorHandler); QAbstractItemModel *model = pages.pageListModel(); const QModelIndex projectsIndex = model->index(2, 0); const QModelIndex sourceIndex = model->index(0, 0, projectsIndex); const QModelIndex project1Index = model->index(0, 0, sourceIndex); auto job = new FakeJob(this); job->setExpectedError(KJob::KilledJobError, QStringLiteral("Foo")); projectRepositoryMock(&Domain::ProjectRepository::remove).when(project1).thenReturn(job); // WHEN pages.removeItem(project1Index); // THEN QTest::qWait(150); QCOMPARE(errorHandler.m_message, QStringLiteral("Cannot remove project Project 1: Foo")); } void shouldRemoveContext() { // GIVEN // Two contexts auto context1 = Domain::Context::Ptr::create(); context1->setName(QStringLiteral("context 1")); auto contextProvider = Domain::QueryResultProvider<Domain::Context::Ptr>::Ptr::create(); auto contextResult = Domain::QueryResult<Domain::Context::Ptr>::create(contextProvider); contextProvider->append(context1); // Empty sources provider auto sourceProvider = Domain::QueryResultProvider<Domain::DataSource::Ptr>::Ptr::create(); auto sourceResult = Domain::QueryResult<Domain::DataSource::Ptr>::create(sourceProvider); // contexts mocking Utils::MockObject<Domain::ContextQueries> contextQueriesMock; contextQueriesMock(&Domain::ContextQueries::findAll).when().thenReturn(contextResult); Utils::MockObject<Domain::ContextRepository> contextRepositoryMock; // sources mocking Utils::MockObject<Domain::DataSourceQueries> dataSourceQueriesMock; dataSourceQueriesMock(&Domain::DataSourceQueries::findAllSelected).when().thenReturn(sourceResult); Presentation::AvailableTaskPagesModel pages(dataSourceQueriesMock.getInstance(), Domain::ProjectQueries::Ptr(), Domain::ProjectRepository::Ptr(), contextQueriesMock.getInstance(), contextRepositoryMock.getInstance(), Domain::TaskQueries::Ptr(), Domain::TaskRepository::Ptr()); QAbstractItemModel *model = pages.pageListModel(); const QModelIndex contextsIndex = model->index(3, 0); const QModelIndex context1Index = model->index(0, 0, contextsIndex); contextRepositoryMock(&Domain::ContextRepository::remove).when(context1).thenReturn(new FakeJob(this)); // WHEN pages.removeItem(context1Index); // THEN QVERIFY(contextRepositoryMock(&Domain::ContextRepository::remove).when(context1).exactly(1)); } void shouldGetAnErrorMessageWhenRemoveContextFailed() { // GIVEN // Two contexts auto context1 = Domain::Context::Ptr::create(); context1->setName(QStringLiteral("context 1")); auto contextProvider = Domain::QueryResultProvider<Domain::Context::Ptr>::Ptr::create(); auto contextResult = Domain::QueryResult<Domain::Context::Ptr>::create(contextProvider); contextProvider->append(context1); // Empty sources provider auto sourceProvider = Domain::QueryResultProvider<Domain::DataSource::Ptr>::Ptr::create(); auto sourceResult = Domain::QueryResult<Domain::DataSource::Ptr>::create(sourceProvider); // contexts mocking Utils::MockObject<Domain::ContextQueries> contextQueriesMock; contextQueriesMock(&Domain::ContextQueries::findAll).when().thenReturn(contextResult); Utils::MockObject<Domain::ContextRepository> contextRepositoryMock; // sources mocking Utils::MockObject<Domain::DataSourceQueries> dataSourceQueriesMock; dataSourceQueriesMock(&Domain::DataSourceQueries::findAllSelected).when().thenReturn(sourceResult); Presentation::AvailableTaskPagesModel pages(dataSourceQueriesMock.getInstance(), Domain::ProjectQueries::Ptr(), Domain::ProjectRepository::Ptr(), contextQueriesMock.getInstance(), contextRepositoryMock.getInstance(), Domain::TaskQueries::Ptr(), Domain::TaskRepository::Ptr()); FakeErrorHandler errorHandler; pages.setErrorHandler(&errorHandler); QAbstractItemModel *model = pages.pageListModel(); const QModelIndex contextsIndex = model->index(3, 0); const QModelIndex context1Index = model->index(0, 0, contextsIndex); auto job = new FakeJob(this); job->setExpectedError(KJob::KilledJobError, QStringLiteral("Foo")); contextRepositoryMock(&Domain::ContextRepository::remove).when(context1).thenReturn(job); // WHEN pages.removeItem(context1Index); // THEN QTest::qWait(150); QCOMPARE(errorHandler.m_message, QStringLiteral("Cannot remove context context 1: Foo")); } void shouldGetAnErrorMessageWhenUpdateProjectFailed() { // GIVEN // One selected data source auto source = Domain::DataSource::Ptr::create(); source->setName("source1"); auto sourceProvider = Domain::QueryResultProvider<Domain::DataSource::Ptr>::Ptr::create(); auto sourceResult = Domain::QueryResult<Domain::DataSource::Ptr>::create(sourceProvider); sourceProvider->append(source); // Two projects under the source auto project1 = Domain::Project::Ptr::create(); project1->setName(QStringLiteral("Project 1")); auto project2 = Domain::Project::Ptr::create(); project2->setName(QStringLiteral("Project 2")); auto projectProvider = Domain::QueryResultProvider<Domain::Project::Ptr>::Ptr::create(); auto projectResult = Domain::QueryResult<Domain::Project::Ptr>::create(projectProvider); projectProvider->append(project1); projectProvider->append(project2); // Two contexts auto context1 = Domain::Context::Ptr::create(); context1->setName(QStringLiteral("context 1")); auto context2 = Domain::Context::Ptr::create(); context2->setName(QStringLiteral("context 2")); auto contextProvider = Domain::QueryResultProvider<Domain::Context::Ptr>::Ptr::create(); auto contextResult = Domain::QueryResult<Domain::Context::Ptr>::create(contextProvider); contextProvider->append(context1); contextProvider->append(context2); Utils::MockObject<Domain::DataSourceQueries> dataSourceQueriesMock; dataSourceQueriesMock(&Domain::DataSourceQueries::findAllSelected).when().thenReturn(sourceResult); dataSourceQueriesMock(&Domain::DataSourceQueries::findProjects).when(source).thenReturn(projectResult); Utils::MockObject<Domain::ProjectRepository> projectRepositoryMock; Utils::MockObject<Domain::ContextQueries> contextQueriesMock; contextQueriesMock(&Domain::ContextQueries::findAll).when().thenReturn(contextResult); Utils::MockObject<Domain::ContextRepository> contextRepositoryMock; Presentation::AvailableTaskPagesModel pages(dataSourceQueriesMock.getInstance(), Domain::ProjectQueries::Ptr(), projectRepositoryMock.getInstance(), contextQueriesMock.getInstance(), contextRepositoryMock.getInstance(), Domain::TaskQueries::Ptr(), Domain::TaskRepository::Ptr()); FakeErrorHandler errorHandler; pages.setErrorHandler(&errorHandler); QAbstractItemModel *model = pages.pageListModel(); const QModelIndex projectsIndex = model->index(2, 0); const QModelIndex sourceIndex = model->index(0, 0, projectsIndex); const QModelIndex project1Index = model->index(0, 0, sourceIndex); // WHEN auto job = new FakeJob(this); job->setExpectedError(KJob::KilledJobError, QStringLiteral("Foo")); projectRepositoryMock(&Domain::ProjectRepository::update).when(project1).thenReturn(job); QVERIFY(model->setData(project1Index, "New Project 1")); // THEN QTest::qWait(150); QCOMPARE(errorHandler.m_message, QStringLiteral("Cannot modify project Project 1: Foo")); } void shouldGetAnErrorMessageWhenUpdateContextFailed() { // GIVEN // One selected data source auto source = Domain::DataSource::Ptr::create(); source->setName("source1"); auto sourceProvider = Domain::QueryResultProvider<Domain::DataSource::Ptr>::Ptr::create(); auto sourceResult = Domain::QueryResult<Domain::DataSource::Ptr>::create(sourceProvider); sourceProvider->append(source); // Two projects under the source auto project1 = Domain::Project::Ptr::create(); project1->setName(QStringLiteral("Project 1")); auto project2 = Domain::Project::Ptr::create(); project2->setName(QStringLiteral("Project 2")); auto projectProvider = Domain::QueryResultProvider<Domain::Project::Ptr>::Ptr::create(); auto projectResult = Domain::QueryResult<Domain::Project::Ptr>::create(projectProvider); projectProvider->append(project1); projectProvider->append(project2); // Two contexts auto context1 = Domain::Context::Ptr::create(); context1->setName(QStringLiteral("context 1")); auto context2 = Domain::Context::Ptr::create(); context2->setName(QStringLiteral("context 2")); auto contextProvider = Domain::QueryResultProvider<Domain::Context::Ptr>::Ptr::create(); auto contextResult = Domain::QueryResult<Domain::Context::Ptr>::create(contextProvider); contextProvider->append(context1); contextProvider->append(context2); Utils::MockObject<Domain::DataSourceQueries> dataSourceQueriesMock; dataSourceQueriesMock(&Domain::DataSourceQueries::findAllSelected).when().thenReturn(sourceResult); dataSourceQueriesMock(&Domain::DataSourceQueries::findProjects).when(source).thenReturn(projectResult); Utils::MockObject<Domain::ProjectRepository> projectRepositoryMock; Utils::MockObject<Domain::ContextQueries> contextQueriesMock; contextQueriesMock(&Domain::ContextQueries::findAll).when().thenReturn(contextResult); Utils::MockObject<Domain::ContextRepository> contextRepositoryMock; Presentation::AvailableTaskPagesModel pages(dataSourceQueriesMock.getInstance(), Domain::ProjectQueries::Ptr(), projectRepositoryMock.getInstance(), contextQueriesMock.getInstance(), contextRepositoryMock.getInstance(), Domain::TaskQueries::Ptr(), Domain::TaskRepository::Ptr()); FakeErrorHandler errorHandler; pages.setErrorHandler(&errorHandler); QAbstractItemModel *model = pages.pageListModel(); const QModelIndex contextsIndex = model->index(3, 0); const QModelIndex context1Index = model->index(0, 0, contextsIndex); // WHEN auto job = new FakeJob(this); job->setExpectedError(KJob::KilledJobError, QStringLiteral("Foo")); contextRepositoryMock(&Domain::ContextRepository::update).when(context1).thenReturn(job); QVERIFY(model->setData(context1Index, "New Context 1")); // THEN QTest::qWait(150); QCOMPARE(errorHandler.m_message, QStringLiteral("Cannot modify context context 1: Foo")); } void shouldGetAnErrorMessageWhenAssociateProjectFailed() { // GIVEN // One selected data source auto source = Domain::DataSource::Ptr::create(); source->setName("source1"); auto sourceProvider = Domain::QueryResultProvider<Domain::DataSource::Ptr>::Ptr::create(); auto sourceResult = Domain::QueryResult<Domain::DataSource::Ptr>::create(sourceProvider); sourceProvider->append(source); // Two projects under the source auto project1 = Domain::Project::Ptr::create(); project1->setName(QStringLiteral("Project 1")); auto project2 = Domain::Project::Ptr::create(); project2->setName(QStringLiteral("Project 2")); auto projectProvider = Domain::QueryResultProvider<Domain::Project::Ptr>::Ptr::create(); auto projectResult = Domain::QueryResult<Domain::Project::Ptr>::create(projectProvider); projectProvider->append(project1); projectProvider->append(project2); // Two contexts auto context1 = Domain::Context::Ptr::create(); context1->setName(QStringLiteral("context 1")); auto context2 = Domain::Context::Ptr::create(); context2->setName(QStringLiteral("context 2")); auto contextProvider = Domain::QueryResultProvider<Domain::Context::Ptr>::Ptr::create(); auto contextResult = Domain::QueryResult<Domain::Context::Ptr>::create(contextProvider); contextProvider->append(context1); contextProvider->append(context2); // One task (used for dropping later on) Domain::Artifact::Ptr taskToDrop(new Domain::Task); taskToDrop->setTitle(QStringLiteral("taskDropped")); Utils::MockObject<Domain::DataSourceQueries> dataSourceQueriesMock; dataSourceQueriesMock(&Domain::DataSourceQueries::findAllSelected).when().thenReturn(sourceResult); dataSourceQueriesMock(&Domain::DataSourceQueries::findProjects).when(source).thenReturn(projectResult); Utils::MockObject<Domain::ProjectRepository> projectRepositoryMock; Utils::MockObject<Domain::ContextQueries> contextQueriesMock; contextQueriesMock(&Domain::ContextQueries::findAll).when().thenReturn(contextResult); Utils::MockObject<Domain::ContextRepository> contextRepositoryMock; Utils::MockObject<Domain::TaskRepository> taskRepositoryMock; Presentation::AvailableTaskPagesModel pages(dataSourceQueriesMock.getInstance(), Domain::ProjectQueries::Ptr(), projectRepositoryMock.getInstance(), contextQueriesMock.getInstance(), contextRepositoryMock.getInstance(), Domain::TaskQueries::Ptr(), taskRepositoryMock.getInstance()); FakeErrorHandler errorHandler; pages.setErrorHandler(&errorHandler); QAbstractItemModel *model = pages.pageListModel(); const QModelIndex projectsIndex = model->index(2, 0); const QModelIndex sourceIndex = model->index(0, 0, projectsIndex); const QModelIndex project1Index = model->index(0, 0, sourceIndex); // WHEN auto job = new FakeJob(this); job->setExpectedError(KJob::KilledJobError, QStringLiteral("Foo")); projectRepositoryMock(&Domain::ProjectRepository::associate).when(project1, taskToDrop).thenReturn(job); auto data = std::make_unique<QMimeData>(); data->setData(QStringLiteral("application/x-zanshin-object"), "object"); data->setProperty("objects", QVariant::fromValue(Domain::Artifact::List() << taskToDrop)); model->dropMimeData(data.get(), Qt::MoveAction, -1, -1, project1Index); // THEN QTest::qWait(150); QCOMPARE(errorHandler.m_message, QStringLiteral("Cannot add taskDropped to project Project 1: Foo")); } void shouldGetAnErrorMessageWhenAssociateContextFailed() { // GIVEN // One selected data source auto source = Domain::DataSource::Ptr::create(); source->setName("source1"); auto sourceProvider = Domain::QueryResultProvider<Domain::DataSource::Ptr>::Ptr::create(); auto sourceResult = Domain::QueryResult<Domain::DataSource::Ptr>::create(sourceProvider); sourceProvider->append(source); // Two projects under the source auto project1 = Domain::Project::Ptr::create(); project1->setName(QStringLiteral("Project 1")); auto project2 = Domain::Project::Ptr::create(); project2->setName(QStringLiteral("Project 2")); auto projectProvider = Domain::QueryResultProvider<Domain::Project::Ptr>::Ptr::create(); auto projectResult = Domain::QueryResult<Domain::Project::Ptr>::create(projectProvider); projectProvider->append(project1); projectProvider->append(project2); // Two contexts auto context1 = Domain::Context::Ptr::create(); context1->setName(QStringLiteral("context 1")); auto context2 = Domain::Context::Ptr::create(); context2->setName(QStringLiteral("context 2")); auto contextProvider = Domain::QueryResultProvider<Domain::Context::Ptr>::Ptr::create(); auto contextResult = Domain::QueryResult<Domain::Context::Ptr>::create(contextProvider); contextProvider->append(context1); contextProvider->append(context2); // One task (used for dropping later on) Domain::Artifact::Ptr taskToDrop(new Domain::Task); taskToDrop->setTitle(QStringLiteral("taskDropped")); Utils::MockObject<Domain::DataSourceQueries> dataSourceQueriesMock; dataSourceQueriesMock(&Domain::DataSourceQueries::findAllSelected).when().thenReturn(sourceResult); dataSourceQueriesMock(&Domain::DataSourceQueries::findProjects).when(source).thenReturn(projectResult); Utils::MockObject<Domain::ProjectRepository> projectRepositoryMock; Utils::MockObject<Domain::ContextQueries> contextQueriesMock; contextQueriesMock(&Domain::ContextQueries::findAll).when().thenReturn(contextResult); Utils::MockObject<Domain::ContextRepository> contextRepositoryMock; Utils::MockObject<Domain::TaskRepository> taskRepositoryMock; Presentation::AvailableTaskPagesModel pages(dataSourceQueriesMock.getInstance(), Domain::ProjectQueries::Ptr(), projectRepositoryMock.getInstance(), contextQueriesMock.getInstance(), contextRepositoryMock.getInstance(), Domain::TaskQueries::Ptr(), taskRepositoryMock.getInstance()); FakeErrorHandler errorHandler; pages.setErrorHandler(&errorHandler); QAbstractItemModel *model = pages.pageListModel(); const QModelIndex contextsIndex = model->index(3, 0); const QModelIndex context1Index = model->index(0, 0, contextsIndex); // WHEN auto job = new FakeJob(this); job->setExpectedError(KJob::KilledJobError, QStringLiteral("Foo")); contextRepositoryMock(&Domain::ContextRepository::associate).when(context1, taskToDrop.objectCast<Domain::Task>()).thenReturn(job); auto data = std::make_unique<QMimeData>(); data->setData(QStringLiteral("application/x-zanshin-object"), "object"); data->setProperty("objects", QVariant::fromValue(Domain::Artifact::List() << taskToDrop)); model->dropMimeData(data.get(), Qt::MoveAction, -1, -1, context1Index); // THEN QTest::qWait(150); QCOMPARE(errorHandler.m_message, QStringLiteral("Cannot add taskDropped to context context 1: Foo")); } void shouldGetAnErrorMessageWhenDissociateFailed() { // GIVEN // Empty source provider auto sourceProvider = Domain::QueryResultProvider<Domain::DataSource::Ptr>::Ptr::create(); auto sourceResult = Domain::QueryResult<Domain::DataSource::Ptr>::create(sourceProvider); // Empty context provider auto contextProvider = Domain::QueryResultProvider<Domain::Context::Ptr>::Ptr::create(); auto contextResult = Domain::QueryResult<Domain::Context::Ptr>::create(contextProvider); // One task (used for dropping later on) Domain::Artifact::Ptr taskToDrop(new Domain::Task); taskToDrop->setTitle(QStringLiteral("taskDropped")); // context mocking Utils::MockObject<Domain::ContextQueries> contextQueriesMock; contextQueriesMock(&Domain::ContextQueries::findAll).when().thenReturn(contextResult); Utils::MockObject<Domain::ContextRepository> contextRepositoryMock; // sources mocking Utils::MockObject<Domain::DataSourceQueries> dataSourceQueriesMock; dataSourceQueriesMock(&Domain::DataSourceQueries::findAllSelected).when().thenReturn(sourceResult); Utils::MockObject<Domain::ProjectRepository> projectRepositoryMock; Utils::MockObject<Domain::TaskRepository> taskRepositoryMock; Presentation::AvailableTaskPagesModel pages(dataSourceQueriesMock.getInstance(), Domain::ProjectQueries::Ptr(), projectRepositoryMock.getInstance(), contextQueriesMock.getInstance(), contextRepositoryMock.getInstance(), Domain::TaskQueries::Ptr(), taskRepositoryMock.getInstance()); FakeErrorHandler errorHandler; pages.setErrorHandler(&errorHandler); QAbstractItemModel *model = pages.pageListModel(); const QModelIndex inboxIndex = model->index(0, 0); // WHEN auto job = new FakeJob(this); job->setExpectedError(KJob::KilledJobError, QStringLiteral("Foo")); projectRepositoryMock(&Domain::ProjectRepository::dissociate).when(taskToDrop).thenReturn(job); taskRepositoryMock(&Domain::TaskRepository::dissociateAll).when(taskToDrop.objectCast<Domain::Task>()).thenReturn(new FakeJob(this)); auto data = std::make_unique<QMimeData>(); data->setData(QStringLiteral("application/x-zanshin-object"), "object"); data->setProperty("objects", QVariant::fromValue(Domain::Artifact::List() << taskToDrop)); model->dropMimeData(data.get(), Qt::MoveAction, -1, -1, inboxIndex); // THEN QTest::qWait(150); QCOMPARE(errorHandler.m_message, QStringLiteral("Cannot move taskDropped to Inbox: Foo")); } }; ZANSHIN_TEST_MAIN(AvailableTaskPagesModelTest) #include "availabletaskpagesmodeltest.moc" diff --git a/tests/units/presentation/contextpagemodeltest.cpp b/tests/units/presentation/contextpagemodeltest.cpp index 0b5adbf2..f80ce7d5 100644 --- a/tests/units/presentation/contextpagemodeltest.cpp +++ b/tests/units/presentation/contextpagemodeltest.cpp @@ -1,721 +1,708 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens <ervin@kde.org> Copyright 2014 Rémi Benoit <r3m1.benoit@gmail.com> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include <testlib/qtest_zanshin.h> #include <memory> #include <QMimeData> #include "utils/mockobject.h" #include "domain/context.h" #include "domain/task.h" #include "domain/contextqueries.h" #include "domain/contextrepository.h" #include "domain/taskqueries.h" #include "domain/taskrepository.h" -#include "domain/noterepository.h" #include "presentation/contextpagemodel.h" #include "presentation/errorhandler.h" #include "testlib/fakejob.h" using namespace mockitopp; using namespace mockitopp::matcher; class FakeErrorHandler : public Presentation::ErrorHandler { public: void doDisplayMessage(const QString &message) override { m_message = message; } QString m_message; }; class ContextPageModelTest : public QObject { Q_OBJECT private slots: void shouldListAssociatedTaskInContextCentralListView() { // GIVEN // A context auto context = Domain::Context::Ptr::create(); // Three tasks auto task1 = Domain::Task::Ptr::create(); task1->setTitle(QStringLiteral("task1")); auto task2 = Domain::Task::Ptr::create(); task2->setTitle(QStringLiteral("task2")); auto task3 = Domain::Task::Ptr::create(); task3->setTitle(QStringLiteral("task3")); auto taskProvider = Domain::QueryResultProvider<Domain::Task::Ptr>::Ptr::create(); auto taskResult = Domain::QueryResult<Domain::Task::Ptr>::create(taskProvider); taskProvider->append(task1); taskProvider->append(task2); taskProvider->append(task3); // Two tasks under the task1 auto childTask11 = Domain::Task::Ptr::create(); childTask11->setTitle(QStringLiteral("childTask11")); auto childTask12 = Domain::Task::Ptr::create(); childTask12->setTitle(QStringLiteral("childTask12")); auto childTaskProvider = Domain::QueryResultProvider<Domain::Task::Ptr>::Ptr::create(); auto childTaskResult = Domain::QueryResult<Domain::Task::Ptr>::create(childTaskProvider); taskProvider->append(childTask12); childTaskProvider->append(childTask11); childTaskProvider->append(childTask12); Utils::MockObject<Domain::ContextQueries> contextQueriesMock; contextQueriesMock(&Domain::ContextQueries::findTopLevelTasks).when(context).thenReturn(taskResult); Utils::MockObject<Domain::TaskQueries> taskQueriesMock; taskQueriesMock(&Domain::TaskQueries::findChildren).when(task1).thenReturn(childTaskResult); taskQueriesMock(&Domain::TaskQueries::findChildren).when(task2).thenReturn(Domain::QueryResult<Domain::Task::Ptr>::Ptr()); taskQueriesMock(&Domain::TaskQueries::findChildren).when(task3).thenReturn(Domain::QueryResult<Domain::Task::Ptr>::Ptr()); taskQueriesMock(&Domain::TaskQueries::findChildren).when(childTask11).thenReturn(Domain::QueryResult<Domain::Task::Ptr>::Ptr()); taskQueriesMock(&Domain::TaskQueries::findChildren).when(childTask12).thenReturn(Domain::QueryResult<Domain::Task::Ptr>::Ptr()); taskQueriesMock(&Domain::TaskQueries::findProject).when(any<Domain::Task::Ptr>()).thenReturn(Domain::QueryResult<Domain::Project::Ptr>::Ptr()); Utils::MockObject<Domain::ContextRepository> contextRepositoryMock; Utils::MockObject<Domain::TaskRepository> taskRepositoryMock; Presentation::ContextPageModel page(context, contextQueriesMock.getInstance(), contextRepositoryMock.getInstance(), taskQueriesMock.getInstance(), taskRepositoryMock.getInstance()); // WHEN QAbstractItemModel *model = page.centralListModel(); // THEN const QModelIndex task1Index = model->index(0, 0); const QModelIndex task2Index = model->index(1, 0); const QModelIndex task3Index = model->index(2, 0); const QModelIndex taskChildTask12Index = model->index(3, 0); const QModelIndex childTask11Index = model->index(0, 0, task1Index); const QModelIndex childTask12Index = model->index(1, 0, task1Index); QCOMPARE(page.context(), context); QCOMPARE(model->rowCount(), 4); QCOMPARE(model->rowCount(task1Index), 2); QCOMPARE(model->rowCount(task2Index), 0); QCOMPARE(model->rowCount(task3Index), 0); QCOMPARE(model->rowCount(taskChildTask12Index), 0); QVERIFY(childTask11Index.isValid()); QVERIFY(childTask12Index.isValid()); QCOMPARE(model->rowCount(childTask11Index), 0); QCOMPARE(model->rowCount(childTask12Index), 0); const Qt::ItemFlags taskFlags = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsDragEnabled | Qt::ItemIsUserCheckable | Qt::ItemIsDropEnabled; QCOMPARE(model->flags(task1Index), taskFlags); QCOMPARE(model->flags(childTask11Index), taskFlags); QCOMPARE(model->flags(childTask12Index), taskFlags); QCOMPARE(model->flags(task2Index), taskFlags); QCOMPARE(model->flags(task3Index), taskFlags); QCOMPARE(model->flags(taskChildTask12Index), taskFlags); QCOMPARE(model->data(task1Index).toString(), task1->title()); QCOMPARE(model->data(childTask11Index).toString(), childTask11->title()); QCOMPARE(model->data(childTask12Index).toString(), childTask12->title()); QCOMPARE(model->data(task2Index).toString(), task2->title()); QCOMPARE(model->data(task3Index).toString(), task3->title()); QCOMPARE(model->data(taskChildTask12Index).toString(), childTask12->title()); QCOMPARE(model->data(task1Index, Qt::EditRole).toString(), task1->title()); QCOMPARE(model->data(childTask11Index, Qt::EditRole).toString(), childTask11->title()); QCOMPARE(model->data(childTask12Index, Qt::EditRole).toString(), childTask12->title()); QCOMPARE(model->data(task2Index, Qt::EditRole).toString(), task2->title()); QCOMPARE(model->data(task3Index, Qt::EditRole).toString(), task3->title()); QCOMPARE(model->data(taskChildTask12Index, Qt::EditRole).toString(), childTask12->title()); QVERIFY(model->data(task1Index, Qt::CheckStateRole).isValid()); QVERIFY(model->data(childTask11Index, Qt::CheckStateRole).isValid()); QVERIFY(model->data(childTask12Index, Qt::CheckStateRole).isValid()); QVERIFY(model->data(task2Index, Qt::CheckStateRole).isValid()); QVERIFY(model->data(task3Index, Qt::CheckStateRole).isValid()); QVERIFY(model->data(taskChildTask12Index, Qt::CheckStateRole).isValid()); QCOMPARE(model->data(task1Index, Qt::CheckStateRole).toBool(), task1->isDone()); QCOMPARE(model->data(childTask11Index, Qt::CheckStateRole).toBool(), childTask11->isDone()); QCOMPARE(model->data(childTask12Index, Qt::CheckStateRole).toBool(), childTask12->isDone()); QCOMPARE(model->data(task2Index, Qt::CheckStateRole).toBool(), task2->isDone()); QCOMPARE(model->data(task3Index, Qt::CheckStateRole).toBool(), task3->isDone()); QCOMPARE(model->data(taskChildTask12Index, Qt::CheckStateRole).toBool(), childTask12->isDone()); // WHEN taskRepositoryMock(&Domain::TaskRepository::update).when(task1).thenReturn(new FakeJob(this)); taskRepositoryMock(&Domain::TaskRepository::update).when(childTask11).thenReturn(new FakeJob(this)); taskRepositoryMock(&Domain::TaskRepository::update).when(childTask12).thenReturn(new FakeJob(this)); taskRepositoryMock(&Domain::TaskRepository::update).when(task2).thenReturn(new FakeJob(this)); taskRepositoryMock(&Domain::TaskRepository::update).when(task3).thenReturn(new FakeJob(this)); QVERIFY(model->setData(task1Index, "newTask1")); QVERIFY(model->setData(childTask11Index, "newChildTask11")); QVERIFY(model->setData(task2Index, "newTask2")); QVERIFY(model->setData(task3Index, "newTask3")); QVERIFY(model->setData(taskChildTask12Index, "newChildTask12")); QVERIFY(model->setData(task1Index, Qt::Unchecked, Qt::CheckStateRole)); QVERIFY(model->setData(childTask11Index, Qt::Unchecked, Qt::CheckStateRole)); QVERIFY(model->setData(task2Index, Qt::Checked, Qt::CheckStateRole)); QVERIFY(model->setData(task3Index, Qt::Unchecked, Qt::CheckStateRole)); QVERIFY(model->setData(taskChildTask12Index, Qt::Checked, Qt::CheckStateRole)); // THEN QVERIFY(taskRepositoryMock(&Domain::TaskRepository::update).when(task1).exactly(2)); QVERIFY(taskRepositoryMock(&Domain::TaskRepository::update).when(childTask11).exactly(2)); QVERIFY(taskRepositoryMock(&Domain::TaskRepository::update).when(childTask12).exactly(2)); QVERIFY(taskRepositoryMock(&Domain::TaskRepository::update).when(task2).exactly(2)); QVERIFY(taskRepositoryMock(&Domain::TaskRepository::update).when(task3).exactly(2)); QCOMPARE(task1->title(), QStringLiteral("newTask1")); QCOMPARE(childTask11->title(), QStringLiteral("newChildTask11")); QCOMPARE(childTask12->title(), QStringLiteral("newChildTask12")); QCOMPARE(task2->title(), QStringLiteral("newTask2")); QCOMPARE(task3->title(), QStringLiteral("newTask3")); QCOMPARE(task1->isDone(), false); QCOMPARE(childTask11->isDone(), false); QCOMPARE(childTask12->isDone(), true); QCOMPARE(task2->isDone(), true); QCOMPARE(task3->isDone(), false); // WHEN QVERIFY(!model->setData(task1Index, QVariant(), Qt::WhatsThisRole)); QVERIFY(!model->setData(task1Index, QVariant(), Qt::ForegroundRole)); QVERIFY(!model->setData(task1Index, QVariant(), Qt::InitialSortOrderRole)); // THEN QVERIFY(taskRepositoryMock(&Domain::TaskRepository::update).when(task1).exactly(2)); QCOMPARE(task1->title(), QStringLiteral("newTask1")); QCOMPARE(task2->title(), QStringLiteral("newTask2")); - // WHEN a task is dragged auto data = std::unique_ptr<QMimeData>(model->mimeData(QModelIndexList() << task2Index)); // THEN QVERIFY(data->hasFormat(QStringLiteral("application/x-zanshin-object"))); QCOMPARE(data->property("objects").value<Domain::Artifact::List>(), Domain::Artifact::List() << task2); // WHEN a task is dropped auto childTask2 = Domain::Task::Ptr::create(); taskRepositoryMock(&Domain::TaskRepository::associate).when(task1, childTask2).thenReturn(new FakeJob(this)); data.reset(new QMimeData); data->setData(QStringLiteral("application/x-zanshin-object"), "object"); data->setProperty("objects", QVariant::fromValue(Domain::Artifact::List() << childTask2)); model->dropMimeData(data.get(), Qt::MoveAction, -1, -1, task1Index); // THEN QVERIFY(taskRepositoryMock(&Domain::TaskRepository::associate).when(task1, childTask2).exactly(1)); // WHEN two tasks are dropped auto childTask3 = Domain::Task::Ptr::create(); auto childTask4 = Domain::Task::Ptr::create(); taskRepositoryMock(&Domain::TaskRepository::associate).when(task1, childTask3).thenReturn(new FakeJob(this)); taskRepositoryMock(&Domain::TaskRepository::associate).when(task1, childTask4).thenReturn(new FakeJob(this)); data.reset(new QMimeData); data->setData(QStringLiteral("application/x-zanshin-object"), "object"); data->setProperty("objects", QVariant::fromValue(Domain::Artifact::List() << childTask3 << childTask4)); model->dropMimeData(data.get(), Qt::MoveAction, -1, -1, task1Index); // THEN QVERIFY(taskRepositoryMock(&Domain::TaskRepository::associate).when(task1, childTask3).exactly(1)); QVERIFY(taskRepositoryMock(&Domain::TaskRepository::associate).when(task1, childTask4).exactly(1)); - - // WHEN a task and a note are dropped - Domain::Artifact::Ptr childTask5(new Domain::Task); - Domain::Artifact::Ptr childNote(new Domain::Note); - data.reset(new QMimeData); - data->setData(QStringLiteral("application/x-zanshin-object"), "object"); - data->setProperty("objects", QVariant::fromValue(Domain::Artifact::List() << childTask5 << childNote)); - model->dropMimeData(data.get(), Qt::MoveAction, -1, -1, task1Index); - - // THEN - QVERIFY(taskRepositoryMock(&Domain::TaskRepository::associate).when(task1, childTask5.objectCast<Domain::Task>()).exactly(0)); } void shouldAddTasksInContext() { // GIVEN // One Context auto context = Domain::Context::Ptr::create(); // ... in fact we won't list any model Utils::MockObject<Domain::ContextQueries> contextQueriesMock; Utils::MockObject<Domain::ContextRepository> contextRepositoryMock; Utils::MockObject<Domain::TaskQueries> taskQueriesMock; // We'll gladly create a task though Utils::MockObject<Domain::TaskRepository> taskRepositoryMock; taskRepositoryMock(&Domain::TaskRepository::createInContext).when(any<Domain::Task::Ptr>(), any<Domain::Context::Ptr>()) .thenReturn(new FakeJob(this)); Presentation::ContextPageModel page(context, contextQueriesMock.getInstance(), contextRepositoryMock.getInstance(), taskQueriesMock.getInstance(), taskRepositoryMock.getInstance()); // WHEN auto title = QStringLiteral("New task"); auto task = page.addItem(title).objectCast<Domain::Task>(); // THEN QVERIFY(taskRepositoryMock(&Domain::TaskRepository::createInContext).when(any<Domain::Task::Ptr>(), any<Domain::Context::Ptr>()) .exactly(1)); QVERIFY(task); QCOMPARE(task->title(), title); } void shouldAddChildTask() { // GIVEN // One Context auto context = Domain::Context::Ptr::create(); // A task auto task = Domain::Task::Ptr::create(); auto taskProvider = Domain::QueryResultProvider<Domain::Task::Ptr>::Ptr::create(); auto taskResult = Domain::QueryResult<Domain::Task::Ptr>::create(taskProvider); taskProvider->append(task); auto childProvider = Domain::QueryResultProvider<Domain::Task::Ptr>::Ptr::create(); auto childResult = Domain::QueryResult<Domain::Task::Ptr>::create(childProvider); Utils::MockObject<Domain::ContextQueries> contextQueriesMock; contextQueriesMock(&Domain::ContextQueries::findTopLevelTasks).when(context).thenReturn(taskResult); Utils::MockObject<Domain::ContextRepository> contextRepositoryMock; Utils::MockObject<Domain::TaskQueries> taskQueriesMock; taskQueriesMock(&Domain::TaskQueries::findChildren).when(task).thenReturn(childResult); taskQueriesMock(&Domain::TaskQueries::findProject).when(any<Domain::Task::Ptr>()).thenReturn(Domain::QueryResult<Domain::Project::Ptr>::Ptr()); Utils::MockObject<Domain::TaskRepository> taskRepositoryMock; taskRepositoryMock(&Domain::TaskRepository::createChild).when(any<Domain::Task::Ptr>(), any<Domain::Task::Ptr>()) .thenReturn(new FakeJob(this)); Presentation::ContextPageModel page(context, contextQueriesMock.getInstance(), contextRepositoryMock.getInstance(), taskQueriesMock.getInstance(), taskRepositoryMock.getInstance()); // WHEN const auto title = QStringLiteral("New task"); const auto parentIndex = page.centralListModel()->index(0, 0); const auto createdTask = page.addItem(title, parentIndex).objectCast<Domain::Task>(); // THEN QVERIFY(taskRepositoryMock(&Domain::TaskRepository::createChild).when(any<Domain::Task::Ptr>(), any<Domain::Task::Ptr>()) .exactly(1)); QVERIFY(createdTask); QCOMPARE(createdTask->title(), title); } void shouldRemoveTopLevelItem() { // GIVEN // One context auto context = Domain::Context::Ptr::create(); // A task auto task = Domain::Task::Ptr::create(); auto taskProvider = Domain::QueryResultProvider<Domain::Task::Ptr>::Ptr::create(); auto taskResult = Domain::QueryResult<Domain::Task::Ptr>::create(taskProvider); taskProvider->append(task); auto childProvider = Domain::QueryResultProvider<Domain::Task::Ptr>::Ptr::create(); auto childResult = Domain::QueryResult<Domain::Task::Ptr>::create(childProvider); Utils::MockObject<Domain::ContextQueries> contextQueriesMock; contextQueriesMock(&Domain::ContextQueries::findTopLevelTasks).when(context).thenReturn(taskResult); Utils::MockObject<Domain::ContextRepository> contextRepositoryMock; contextRepositoryMock(&Domain::ContextRepository::dissociate).when(context, task).thenReturn(new FakeJob(this)); Utils::MockObject<Domain::TaskQueries> taskQueriesMock; taskQueriesMock(&Domain::TaskQueries::findChildren).when(task).thenReturn(childResult); taskQueriesMock(&Domain::TaskQueries::findProject).when(any<Domain::Task::Ptr>()).thenReturn(Domain::QueryResult<Domain::Project::Ptr>::Ptr()); Utils::MockObject<Domain::TaskRepository> taskRepositoryMock; Presentation::ContextPageModel page(context, contextQueriesMock.getInstance(), contextRepositoryMock.getInstance(), taskQueriesMock.getInstance(), taskRepositoryMock.getInstance()); // WHEN const QModelIndex indexTask = page.centralListModel()->index(0, 0); page.removeItem(indexTask); // THEN QVERIFY(contextRepositoryMock(&Domain::ContextRepository::dissociate).when(context, task).exactly(1)); } void shouldRemoveChildItem() { // GIVEN // One context auto context = Domain::Context::Ptr::create(); // A task... auto task = Domain::Task::Ptr::create(); auto taskProvider = Domain::QueryResultProvider<Domain::Task::Ptr>::Ptr::create(); auto taskResult = Domain::QueryResult<Domain::Task::Ptr>::create(taskProvider); taskProvider->append(task); // ... with a child auto childTask = Domain::Task::Ptr::create(); auto childProvider = Domain::QueryResultProvider<Domain::Task::Ptr>::Ptr::create(); auto childResult = Domain::QueryResult<Domain::Task::Ptr>::create(childProvider); childProvider->append(childTask); auto emptyProvider = Domain::QueryResultProvider<Domain::Task::Ptr>::Ptr::create(); auto emptyResult = Domain::QueryResult<Domain::Task::Ptr>::create(emptyProvider); Utils::MockObject<Domain::ContextQueries> contextQueriesMock; contextQueriesMock(&Domain::ContextQueries::findTopLevelTasks).when(context).thenReturn(taskResult); Utils::MockObject<Domain::ContextRepository> contextRepositoryMock; Utils::MockObject<Domain::TaskQueries> taskQueriesMock; taskQueriesMock(&Domain::TaskQueries::findChildren).when(task).thenReturn(childResult); taskQueriesMock(&Domain::TaskQueries::findChildren).when(childTask).thenReturn(emptyResult); taskQueriesMock(&Domain::TaskQueries::findProject).when(any<Domain::Task::Ptr>()).thenReturn(Domain::QueryResult<Domain::Project::Ptr>::Ptr()); Utils::MockObject<Domain::TaskRepository> taskRepositoryMock; taskRepositoryMock(&Domain::TaskRepository::dissociate).when(childTask).thenReturn(new FakeJob(this)); Presentation::ContextPageModel page(context, contextQueriesMock.getInstance(), contextRepositoryMock.getInstance(), taskQueriesMock.getInstance(), taskRepositoryMock.getInstance()); // WHEN const auto taskIndex = page.centralListModel()->index(0, 0); const auto childTaskIndex = page.centralListModel()->index(0, 0, taskIndex); page.removeItem(childTaskIndex); // THEN QVERIFY(taskRepositoryMock(&Domain::TaskRepository::dissociate).when(childTask).exactly(1)); } void shouldPromoteItem() { // GIVEN // One context auto context = Domain::Context::Ptr::create(); // A task auto task = Domain::Task::Ptr::create(); auto taskProvider = Domain::QueryResultProvider<Domain::Task::Ptr>::Ptr::create(); auto taskResult = Domain::QueryResult<Domain::Task::Ptr>::create(taskProvider); taskProvider->append(task); auto childProvider = Domain::QueryResultProvider<Domain::Task::Ptr>::Ptr::create(); auto childResult = Domain::QueryResult<Domain::Task::Ptr>::create(childProvider); Utils::MockObject<Domain::ContextQueries> contextQueriesMock; contextQueriesMock(&Domain::ContextQueries::findTopLevelTasks).when(context).thenReturn(taskResult); Utils::MockObject<Domain::ContextRepository> contextRepositoryMock; Utils::MockObject<Domain::TaskQueries> taskQueriesMock; taskQueriesMock(&Domain::TaskQueries::findChildren).when(task).thenReturn(childResult); taskQueriesMock(&Domain::TaskQueries::findProject).when(any<Domain::Task::Ptr>()).thenReturn(Domain::QueryResult<Domain::Project::Ptr>::Ptr()); Utils::MockObject<Domain::TaskRepository> taskRepositoryMock; taskRepositoryMock(&Domain::TaskRepository::promoteToProject).when(task).thenReturn(new FakeJob(this)); Presentation::ContextPageModel page(context, contextQueriesMock.getInstance(), contextRepositoryMock.getInstance(), taskQueriesMock.getInstance(), taskRepositoryMock.getInstance()); // WHEN const QModelIndex indexTask = page.centralListModel()->index(0, 0); page.promoteItem(indexTask); // THEN QVERIFY(taskRepositoryMock(&Domain::TaskRepository::promoteToProject).when(task).exactly(1)); } void shouldGetAnErrorMessageWhenAddTaskFailed() { // GIVEN // One Context auto context = Domain::Context::Ptr::create(); context->setName(QStringLiteral("Context1")); // ... in fact we won't list any model Utils::MockObject<Domain::ContextQueries> contextQueriesMock; Utils::MockObject<Domain::ContextRepository> contextRepositoryMock; Utils::MockObject<Domain::TaskQueries> taskQueriesMock; // We'll gladly create a task though Utils::MockObject<Domain::TaskRepository> taskRepositoryMock; auto job = new FakeJob(this); job->setExpectedError(KJob::KilledJobError, QStringLiteral("Foo")); taskRepositoryMock(&Domain::TaskRepository::createInContext).when(any<Domain::Task::Ptr>(), any<Domain::Context::Ptr>()) .thenReturn(job); Presentation::ContextPageModel page(context, contextQueriesMock.getInstance(), contextRepositoryMock.getInstance(), taskQueriesMock.getInstance(), taskRepositoryMock.getInstance()); FakeErrorHandler errorHandler; page.setErrorHandler(&errorHandler); // WHEN page.addItem(QStringLiteral("New task")); // THEN QTest::qWait(150); QCOMPARE(errorHandler.m_message, QStringLiteral("Cannot add task New task in context Context1: Foo")); } void shouldGetAnErrorMessageWhenUpdateTaskFailed() { // GIVEN // A context auto context = Domain::Context::Ptr::create(); context->setName(QStringLiteral("Context1")); // A task auto task = Domain::Task::Ptr::create(); task->setTitle(QStringLiteral("A task")); auto taskProvider = Domain::QueryResultProvider<Domain::Task::Ptr>::Ptr::create(); auto taskResult = Domain::QueryResult<Domain::Task::Ptr>::create(taskProvider); taskProvider->append(task); auto childProvider = Domain::QueryResultProvider<Domain::Task::Ptr>::Ptr::create(); auto childResult = Domain::QueryResult<Domain::Task::Ptr>::create(childProvider); Utils::MockObject<Domain::ContextQueries> contextQueriesMock; contextQueriesMock(&Domain::ContextQueries::findTopLevelTasks).when(context).thenReturn(taskResult); Utils::MockObject<Domain::TaskQueries> taskQueriesMock; taskQueriesMock(&Domain::TaskQueries::findChildren).when(task).thenReturn(childResult); taskQueriesMock(&Domain::TaskQueries::findProject).when(any<Domain::Task::Ptr>()).thenReturn(Domain::QueryResult<Domain::Project::Ptr>::Ptr()); Utils::MockObject<Domain::ContextRepository> contextRepositoryMock; Utils::MockObject<Domain::TaskRepository> taskRepositoryMock; Presentation::ContextPageModel page(context, contextQueriesMock.getInstance(), contextRepositoryMock.getInstance(), taskQueriesMock.getInstance(), taskRepositoryMock.getInstance()); QAbstractItemModel *model = page.centralListModel(); const QModelIndex taskIndex = model->index(0, 0); FakeErrorHandler errorHandler; page.setErrorHandler(&errorHandler); // WHEN auto job = new FakeJob(this); job->setExpectedError(KJob::KilledJobError, QStringLiteral("Foo")); taskRepositoryMock(&Domain::TaskRepository::update).when(task).thenReturn(job); QVERIFY(model->setData(taskIndex, "newTask")); // THEN QTest::qWait(150); QCOMPARE(errorHandler.m_message, QStringLiteral("Cannot modify task A task in context Context1: Foo")); } void shouldGetAnErrorMessageWhenAssociateTaskFailed() { // GIVEN // A context auto context = Domain::Context::Ptr::create(); context->setName(QStringLiteral("Context1")); // A parent task and a child task auto parentTask = Domain::Task::Ptr::create(); parentTask->setTitle(QStringLiteral("A parent task")); auto childTask = Domain::Task::Ptr::create(); childTask->setTitle(QStringLiteral("A child task")); auto taskProvider = Domain::QueryResultProvider<Domain::Task::Ptr>::Ptr::create(); auto taskResult = Domain::QueryResult<Domain::Task::Ptr>::create(taskProvider); taskProvider->append(parentTask); taskProvider->append(childTask); auto childProvider = Domain::QueryResultProvider<Domain::Task::Ptr>::Ptr::create(); auto childResult = Domain::QueryResult<Domain::Task::Ptr>::create(childProvider); Utils::MockObject<Domain::ContextQueries> contextQueriesMock; contextQueriesMock(&Domain::ContextQueries::findTopLevelTasks).when(context).thenReturn(taskResult); Utils::MockObject<Domain::ContextRepository> contextRepositoryMock; Utils::MockObject<Domain::TaskQueries> taskQueriesMock; taskQueriesMock(&Domain::TaskQueries::findChildren).when(parentTask).thenReturn(childResult); taskQueriesMock(&Domain::TaskQueries::findChildren).when(childTask).thenReturn(childResult); taskQueriesMock(&Domain::TaskQueries::findProject).when(any<Domain::Task::Ptr>()).thenReturn(Domain::QueryResult<Domain::Project::Ptr>::Ptr()); Utils::MockObject<Domain::TaskRepository> taskRepositoryMock; Presentation::ContextPageModel page(context, contextQueriesMock.getInstance(), contextRepositoryMock.getInstance(), taskQueriesMock.getInstance(), taskRepositoryMock.getInstance()); // WHEN QAbstractItemModel *model = page.centralListModel(); const QModelIndex parentTaskIndex = model->index(0, 0); FakeErrorHandler errorHandler; page.setErrorHandler(&errorHandler); // WHEN a task is dropped auto childTask2 = Domain::Task::Ptr::create(); childTask2->setTitle(QStringLiteral("childTask2")); auto job = new FakeJob(this); job->setExpectedError(KJob::KilledJobError, QStringLiteral("Foo")); taskRepositoryMock(&Domain::TaskRepository::associate).when(parentTask, childTask2).thenReturn(job); auto data = std::make_unique<QMimeData>(); data->setData(QStringLiteral("application/x-zanshin-object"), "object"); data->setProperty("objects", QVariant::fromValue(Domain::Artifact::List() << childTask2)); model->dropMimeData(data.get(), Qt::MoveAction, -1, -1, parentTaskIndex); // THEN QTest::qWait(150); QCOMPARE(errorHandler.m_message, QStringLiteral("Cannot move task childTask2 as sub-task of A parent task: Foo")); } void shouldAssociateToContextWithNoParentWhenDroppingOnEmptyArea() { // GIVEN // One context auto context = Domain::Context::Ptr::create(); context->setName(QStringLiteral("Context")); // One top level task auto topLevelTask = Domain::Task::Ptr::create(); topLevelTask->setTitle(QStringLiteral("rootTask")); auto topLevelProvider = Domain::QueryResultProvider<Domain::Task::Ptr>::Ptr::create(); auto topLevelResult = Domain::QueryResult<Domain::Task::Ptr>::create(topLevelProvider); topLevelProvider->append(topLevelTask); // Two tasks under the top level task auto childTask1 = Domain::Task::Ptr::create(); childTask1->setTitle(QStringLiteral("childTask1")); childTask1->setDone(true); auto childTask2 = Domain::Task::Ptr::create(); childTask2->setTitle(QStringLiteral("childTask2")); childTask2->setDone(false); auto taskProvider = Domain::QueryResultProvider<Domain::Task::Ptr>::Ptr::create(); auto taskResult = Domain::QueryResult<Domain::Task::Ptr>::create(taskProvider); taskProvider->append(childTask1); taskProvider->append(childTask2); Utils::MockObject<Domain::ContextQueries> contextQueriesMock; contextQueriesMock(&Domain::ContextQueries::findTopLevelTasks).when(context).thenReturn(topLevelResult); Utils::MockObject<Domain::ContextRepository> contextRepositoryMock; Utils::MockObject<Domain::TaskQueries> taskQueriesMock; taskQueriesMock(&Domain::TaskQueries::findChildren).when(topLevelTask).thenReturn(taskResult); taskQueriesMock(&Domain::TaskQueries::findChildren).when(childTask1).thenReturn(Domain::QueryResult<Domain::Task::Ptr>::Ptr()); taskQueriesMock(&Domain::TaskQueries::findChildren).when(childTask2).thenReturn(Domain::QueryResult<Domain::Task::Ptr>::Ptr()); taskQueriesMock(&Domain::TaskQueries::findProject).when(any<Domain::Task::Ptr>()).thenReturn(Domain::QueryResult<Domain::Project::Ptr>::Ptr()); Utils::MockObject<Domain::TaskRepository> taskRepositoryMock; Presentation::ContextPageModel page(context, contextQueriesMock.getInstance(), contextRepositoryMock.getInstance(), taskQueriesMock.getInstance(), taskRepositoryMock.getInstance()); auto model = page.centralListModel(); // WHEN taskRepositoryMock(&Domain::TaskRepository::dissociate).when(childTask1).thenReturn(new FakeJob(this)); taskRepositoryMock(&Domain::TaskRepository::dissociate).when(childTask2).thenReturn(new FakeJob(this)); contextRepositoryMock(&Domain::ContextRepository::associate).when(context, childTask1).thenReturn(new FakeJob(this)); contextRepositoryMock(&Domain::ContextRepository::associate).when(context, childTask2).thenReturn(new FakeJob(this)); auto data = std::make_unique<QMimeData>(); data->setData(QStringLiteral("application/x-zanshin-object"), "object"); data->setProperty("objects", QVariant::fromValue(Domain::Artifact::List() << childTask1 << childTask2)); // both will be DnD on the empty part model->dropMimeData(data.get(), Qt::MoveAction, -1, -1, QModelIndex()); // THEN QTest::qWait(150); QVERIFY(taskRepositoryMock(&Domain::TaskRepository::dissociate).when(childTask1).exactly(1)); QVERIFY(taskRepositoryMock(&Domain::TaskRepository::dissociate).when(childTask2).exactly(1)); QVERIFY(contextRepositoryMock(&Domain::ContextRepository::associate).when(context, childTask1).exactly(1)); QVERIFY(contextRepositoryMock(&Domain::ContextRepository::associate).when(context, childTask2).exactly(1)); } }; ZANSHIN_TEST_MAIN(ContextPageModelTest) #include "contextpagemodeltest.moc" diff --git a/tests/units/presentation/noteinboxpagemodeltest.cpp b/tests/units/presentation/noteinboxpagemodeltest.cpp deleted file mode 100644 index 75120f21..00000000 --- a/tests/units/presentation/noteinboxpagemodeltest.cpp +++ /dev/null @@ -1,291 +0,0 @@ -/* This file is part of Zanshin - - Copyright 2014 Kevin Ottens <ervin@kde.org> - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License or (at your option) version 3 or any later version - accepted by the membership of KDE e.V. (or its successor approved - by the membership of KDE e.V.), which shall act as a proxy - defined in Section 14 of version 3 of the license. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - USA. -*/ - -#include <testlib/qtest_zanshin.h> - -#include <memory> - -#include <QMimeData> - -#include "utils/mockobject.h" - -#include "presentation/noteinboxpagemodel.h" -#include "presentation/errorhandler.h" - -#include "testlib/fakejob.h" - -using namespace mockitopp; -using namespace mockitopp::matcher; - -class FakeErrorHandler : public Presentation::ErrorHandler -{ -public: - void doDisplayMessage(const QString &message) override - { - m_message = message; - } - - QString m_message; -}; - -class NoteInboxPageModelTest : public QObject -{ - Q_OBJECT -private slots: - void shouldListInboxInCentralListModel() - { - // GIVEN - - // Two notes - auto note1 = Domain::Note::Ptr::create(); - note1->setTitle(QStringLiteral("note1")); - auto note2 = Domain::Note::Ptr::create(); - note2->setTitle(QStringLiteral("note2")); - auto noteProvider = Domain::QueryResultProvider<Domain::Note::Ptr>::Ptr::create(); - auto noteResult = Domain::QueryResult<Domain::Note::Ptr>::create(noteProvider); - noteProvider->append(note1); - noteProvider->append(note2); - - Utils::MockObject<Domain::NoteQueries> noteQueriesMock; - noteQueriesMock(&Domain::NoteQueries::findInbox).when().thenReturn(noteResult); - - Utils::MockObject<Domain::NoteRepository> noteRepositoryMock; - - Presentation::NoteInboxPageModel inbox(noteQueriesMock.getInstance(), - noteRepositoryMock.getInstance()); - - // WHEN - QAbstractItemModel *model = inbox.centralListModel(); - - // THEN - const QModelIndex note1Index = model->index(0, 0); - const QModelIndex note2Index = model->index(1, 0); - - QCOMPARE(model->rowCount(), 2); - QCOMPARE(model->rowCount(note1Index), 0); - QCOMPARE(model->rowCount(note2Index), 0); - - const Qt::ItemFlags defaultFlags = Qt::ItemIsSelectable - | Qt::ItemIsEnabled - | Qt::ItemIsEditable - | Qt::ItemIsDragEnabled; - QCOMPARE(model->flags(note1Index), defaultFlags); - QCOMPARE(model->flags(note2Index), defaultFlags); - - QCOMPARE(model->data(note1Index).toString(), note1->title()); - QCOMPARE(model->data(note2Index).toString(), note2->title()); - - QCOMPARE(model->data(note1Index, Qt::EditRole).toString(), note1->title()); - QCOMPARE(model->data(note2Index, Qt::EditRole).toString(), note2->title()); - - QVERIFY(!model->data(note1Index, Qt::CheckStateRole).isValid()); - QVERIFY(!model->data(note2Index, Qt::CheckStateRole).isValid()); - - // WHEN - noteRepositoryMock(&Domain::NoteRepository::update).when(note1).thenReturn(new FakeJob(this)); - noteRepositoryMock(&Domain::NoteRepository::update).when(note2).thenReturn(new FakeJob(this)); - - QVERIFY(model->setData(note1Index, "newNote1")); - QVERIFY(model->setData(note2Index, "newNote2")); - - QVERIFY(!model->setData(note1Index, Qt::Checked, Qt::CheckStateRole)); - QVERIFY(!model->setData(note2Index, Qt::Checked, Qt::CheckStateRole)); - - // THEN - QVERIFY(noteRepositoryMock(&Domain::NoteRepository::update).when(note1).exactly(1)); - QVERIFY(noteRepositoryMock(&Domain::NoteRepository::update).when(note2).exactly(1)); - - QCOMPARE(note1->title(), QStringLiteral("newNote1")); - QCOMPARE(note2->title(), QStringLiteral("newNote2")); - - // WHEN - auto data = std::unique_ptr<QMimeData>(model->mimeData(QModelIndexList() << note2Index)); - - // THEN - QVERIFY(data->hasFormat(QStringLiteral("application/x-zanshin-object"))); - QCOMPARE(data->property("objects").value<Domain::Artifact::List>(), - Domain::Artifact::List() << note2); - } - - void shouldAddNotes() - { - // GIVEN - - // ... in fact we won't list any model - Utils::MockObject<Domain::NoteQueries> noteQueriesMock; - - // We'll gladly create a note though - Utils::MockObject<Domain::NoteRepository> noteRepositoryMock; - noteRepositoryMock(&Domain::NoteRepository::create).when(any<Domain::Note::Ptr>()).thenReturn(new FakeJob(this)); - - - Presentation::NoteInboxPageModel inbox(noteQueriesMock.getInstance(), - noteRepositoryMock.getInstance()); - - // WHEN - auto title = QStringLiteral("New note"); - auto note = inbox.addItem(title).objectCast<Domain::Note>(); - - // THEN - QVERIFY(noteRepositoryMock(&Domain::NoteRepository::create).when(any<Domain::Note::Ptr>()).exactly(1)); - QVERIFY(note); - QCOMPARE(note->title(), title); - } - - void shouldGetAnErrorMessageWhenAddNoteFailed() - { - // GIVEN - - // ... in fact we won't list any model - Utils::MockObject<Domain::NoteQueries> noteQueriesMock; - - // We'll gladly create a note though - Utils::MockObject<Domain::NoteRepository> noteRepositoryMock; - auto job = new FakeJob(this); - job->setExpectedError(KJob::KilledJobError, QStringLiteral("Foo")); - noteRepositoryMock(&Domain::NoteRepository::create).when(any<Domain::Note::Ptr>()).thenReturn(job); - - Presentation::NoteInboxPageModel inbox(noteQueriesMock.getInstance(), - noteRepositoryMock.getInstance()); - - FakeErrorHandler errorHandler; - inbox.setErrorHandler(&errorHandler); - - // WHEN - inbox.addItem(QStringLiteral("New note")); - - // THEN - QTest::qWait(150); - QCOMPARE(errorHandler.m_message, QStringLiteral("Cannot add note New note in Inbox: Foo")); - } - - void shouldDeleteItems() - { - // GIVEN - - // Two notes - auto note1 = Domain::Note::Ptr::create(); - note1->setTitle(QStringLiteral("note1")); - auto note2 = Domain::Note::Ptr::create(); - note2->setTitle(QStringLiteral("note2")); - auto noteProvider = Domain::QueryResultProvider<Domain::Note::Ptr>::Ptr::create(); - auto noteResult = Domain::QueryResult<Domain::Note::Ptr>::create(noteProvider); - noteProvider->append(note1); - noteProvider->append(note2); - - Utils::MockObject<Domain::NoteQueries> noteQueriesMock; - noteQueriesMock(&Domain::NoteQueries::findInbox).when().thenReturn(noteResult); - - Utils::MockObject<Domain::NoteRepository> noteRepositoryMock; - noteRepositoryMock(&Domain::NoteRepository::remove).when(note2).thenReturn(new FakeJob(this)); - - Presentation::NoteInboxPageModel inbox(noteQueriesMock.getInstance(), - noteRepositoryMock.getInstance()); - - // WHEN - const QModelIndex index = inbox.centralListModel()->index(1, 0); - inbox.removeItem(index); - - // THEN - QVERIFY(noteRepositoryMock(&Domain::NoteRepository::remove).when(note2).exactly(1)); - } - - void shouldGetAnErrorMessageWhenDeleteItemsFailed() - { - // GIVEN - - // Two notes - auto note1 = Domain::Note::Ptr::create(); - note1->setTitle(QStringLiteral("note1")); - auto note2 = Domain::Note::Ptr::create(); - note2->setTitle(QStringLiteral("note2")); - auto noteProvider = Domain::QueryResultProvider<Domain::Note::Ptr>::Ptr::create(); - auto noteResult = Domain::QueryResult<Domain::Note::Ptr>::create(noteProvider); - noteProvider->append(note1); - noteProvider->append(note2); - - Utils::MockObject<Domain::NoteQueries> noteQueriesMock; - noteQueriesMock(&Domain::NoteQueries::findInbox).when().thenReturn(noteResult); - - Utils::MockObject<Domain::NoteRepository> noteRepositoryMock; - auto job = new FakeJob(this); - job->setExpectedError(KJob::KilledJobError, QStringLiteral("Foo")); - noteRepositoryMock(&Domain::NoteRepository::remove).when(note2).thenReturn(job); - - Presentation::NoteInboxPageModel inbox(noteQueriesMock.getInstance(), - noteRepositoryMock.getInstance()); - FakeErrorHandler errorHandler; - inbox.setErrorHandler(&errorHandler); - - // WHEN - const QModelIndex index = inbox.centralListModel()->index(1, 0); - inbox.removeItem(index); - - // THEN - QTest::qWait(150); - QCOMPARE(errorHandler.m_message, QStringLiteral("Cannot remove note note2 from Inbox: Foo")); - } - - void shouldGetAnErrorMessageWhenUpdateNoteFailed() - { - // GIVEN - - // Two notes - auto note1 = Domain::Note::Ptr::create(); - note1->setTitle(QStringLiteral("note1")); - auto note2 = Domain::Note::Ptr::create(); - note2->setTitle(QStringLiteral("note2")); - auto noteProvider = Domain::QueryResultProvider<Domain::Note::Ptr>::Ptr::create(); - auto noteResult = Domain::QueryResult<Domain::Note::Ptr>::create(noteProvider); - noteProvider->append(note1); - noteProvider->append(note2); - - Utils::MockObject<Domain::NoteQueries> noteQueriesMock; - noteQueriesMock(&Domain::NoteQueries::findInbox).when().thenReturn(noteResult); - - Utils::MockObject<Domain::NoteRepository> noteRepositoryMock; - - Presentation::NoteInboxPageModel inbox(noteQueriesMock.getInstance(), - noteRepositoryMock.getInstance()); - - QAbstractItemModel *model = inbox.centralListModel(); - const QModelIndex note1Index = model->index(0, 0); - FakeErrorHandler errorHandler; - inbox.setErrorHandler(&errorHandler); - - // WHEN - auto job = new FakeJob(this); - job->setExpectedError(KJob::KilledJobError, QStringLiteral("Foo")); - noteRepositoryMock(&Domain::NoteRepository::update).when(note1).thenReturn(job); - - QVERIFY(model->setData(note1Index, "newNote1")); - - // THEN - QTest::qWait(150); - QCOMPARE(errorHandler.m_message, QStringLiteral("Cannot modify note note1 in Inbox: Foo")); - } -}; - -ZANSHIN_TEST_MAIN(NoteInboxPageModelTest) - -#include "noteinboxpagemodeltest.moc" diff --git a/tests/units/presentation/tagpagemodeltest.cpp b/tests/units/presentation/tagpagemodeltest.cpp deleted file mode 100644 index 0d745a88..00000000 --- a/tests/units/presentation/tagpagemodeltest.cpp +++ /dev/null @@ -1,340 +0,0 @@ -/* This file is part of Zanshin - - Copyright 2014 Kevin Ottens <ervin@kde.org> - Copyright 2014 Franck Arrecot <franck.arrecot@gmail.com> - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License or (at your option) version 3 or any later version - accepted by the membership of KDE e.V. (or its successor approved - by the membership of KDE e.V.), which shall act as a proxy - defined in Section 14 of version 3 of the license. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - USA. -*/ - -#include <testlib/qtest_zanshin.h> - -#include <memory> - -#include <QMimeData> - -#include "utils/mockobject.h" - -#include "domain/noterepository.h" -#include "domain/tagqueries.h" -#include "domain/tagrepository.h" - -#include "presentation/tagpagemodel.h" -#include "presentation/errorhandler.h" - -#include "testlib/fakejob.h" - -using namespace mockitopp; -using namespace mockitopp::matcher; - -class FakeErrorHandler : public Presentation::ErrorHandler -{ -public: - void doDisplayMessage(const QString &message) override - { - m_message = message; - } - - QString m_message; -}; - -class TagPageModelTest : public QObject -{ - Q_OBJECT -private slots: - void shouldListTagNotesInCentralListModel() - { - // GIVEN - - // One Tag - auto tag = Domain::Tag::Ptr::create(); - - // Two notes - auto note1 = Domain::Note::Ptr::create(); - note1->setTitle(QStringLiteral("note1")); - auto note2 = Domain::Note::Ptr::create(); - note2->setTitle(QStringLiteral("note2")); - auto noteProvider = Domain::QueryResultProvider<Domain::Note::Ptr>::Ptr::create(); - auto noteResult = Domain::QueryResult<Domain::Note::Ptr>::create(noteProvider); - noteProvider->append(note1); - noteProvider->append(note2); - - Utils::MockObject<Domain::TagQueries> tagQueriesMock; - tagQueriesMock(&Domain::TagQueries::findNotes).when(tag).thenReturn(noteResult); - - Utils::MockObject<Domain::TagRepository> tagRepositoryMock; - Utils::MockObject<Domain::NoteRepository> noteRepositoryMock; - - Presentation::TagPageModel page(tag, - tagQueriesMock.getInstance(), - tagRepositoryMock.getInstance(), - noteRepositoryMock.getInstance()); - - // WHEN - QAbstractItemModel *model = page.centralListModel(); - - // THEN - const QModelIndex note1Index = model->index(0, 0); - const QModelIndex note2Index = model->index(1, 0); - - QCOMPARE(page.tag(), tag); - - QCOMPARE(model->rowCount(), 2); - QCOMPARE(model->rowCount(note1Index), 0); - QCOMPARE(model->rowCount(note2Index), 0); - - const Qt::ItemFlags defaultFlags = Qt::ItemIsSelectable - | Qt::ItemIsEnabled - | Qt::ItemIsEditable - | Qt::ItemIsDragEnabled; - QCOMPARE(model->flags(note1Index), defaultFlags); - QCOMPARE(model->flags(note2Index), defaultFlags); - - QCOMPARE(model->data(note1Index).toString(), note1->title()); - QCOMPARE(model->data(note2Index).toString(), note2->title()); - - QCOMPARE(model->data(note1Index, Qt::EditRole).toString(), note1->title()); - QCOMPARE(model->data(note2Index, Qt::EditRole).toString(), note2->title()); - - QVERIFY(!model->data(note1Index, Qt::CheckStateRole).isValid()); - QVERIFY(!model->data(note2Index, Qt::CheckStateRole).isValid()); - - // WHEN - noteRepositoryMock(&Domain::NoteRepository::update).when(note1).thenReturn(new FakeJob(this)); - noteRepositoryMock(&Domain::NoteRepository::update).when(note2).thenReturn(new FakeJob(this)); - - QVERIFY(model->setData(note1Index, "newNote1")); - QVERIFY(model->setData(note2Index, "newNote2")); - - QVERIFY(!model->setData(note1Index, Qt::Checked, Qt::CheckStateRole)); - QVERIFY(!model->setData(note2Index, Qt::Checked, Qt::CheckStateRole)); - - // THEN - QVERIFY(noteRepositoryMock(&Domain::NoteRepository::update).when(note1).exactly(1)); - QVERIFY(noteRepositoryMock(&Domain::NoteRepository::update).when(note2).exactly(1)); - - QCOMPARE(note1->title(), QStringLiteral("newNote1")); - QCOMPARE(note2->title(), QStringLiteral("newNote2")); - - // WHEN - auto data = std::unique_ptr<QMimeData>(model->mimeData(QModelIndexList() << note2Index)); - - // THEN - QVERIFY(data->hasFormat(QStringLiteral("application/x-zanshin-object"))); - QCOMPARE(data->property("objects").value<Domain::Artifact::List>(), - Domain::Artifact::List() << note2); - } - - void shouldAddNotes() - { - // GIVEN - - // One Tag - auto tag = Domain::Tag::Ptr::create(); - - // ... in fact we won't list any model - Utils::MockObject<Domain::TagQueries> tagQueriesMock; - Utils::MockObject<Domain::TagRepository> tagRepositoryMock; - - // We'll gladly create a note though - Utils::MockObject<Domain::NoteRepository> noteRepositoryMock; - noteRepositoryMock(&Domain::NoteRepository::createInTag).when(any<Domain::Note::Ptr>(), - any<Domain::Tag::Ptr>()) - .thenReturn(new FakeJob(this)); - - Presentation::TagPageModel page(tag, - tagQueriesMock.getInstance(), - tagRepositoryMock.getInstance(), - noteRepositoryMock.getInstance()); - - // WHEN - auto title = QStringLiteral("New note"); - auto note = page.addItem(title).objectCast<Domain::Note>(); - - // THEN - QVERIFY(noteRepositoryMock(&Domain::NoteRepository::createInTag).when(any<Domain::Note::Ptr>(), - any<Domain::Tag::Ptr>()) - .exactly(1)); - QVERIFY(note); - QCOMPARE(note->title(), title); - } - - void shouldRemoveItem() - { - // GIVEN - - // One domain tag - auto tag = Domain::Tag::Ptr::create(); - - // Two notes - auto note1 = Domain::Note::Ptr::create(); - auto note2 = Domain::Note::Ptr::create(); - - auto noteProvider = Domain::QueryResultProvider<Domain::Note::Ptr>::Ptr::create(); - auto noteResult = Domain::QueryResult<Domain::Note::Ptr>::create(noteProvider); - noteProvider->append(note1); - noteProvider->append(note2); - - Utils::MockObject<Domain::TagQueries> tagQueriesMock; - tagQueriesMock(&Domain::TagQueries::findNotes).when(tag).thenReturn(noteResult); - - Utils::MockObject<Domain::TagRepository> tagRepositoryMock; - tagRepositoryMock(&Domain::TagRepository::dissociate).when(tag, note2).thenReturn(new FakeJob(this)); - - Utils::MockObject<Domain::NoteRepository> noteRepositoryMock; - - Presentation::TagPageModel page(tag, - tagQueriesMock.getInstance(), - tagRepositoryMock.getInstance(), - noteRepositoryMock.getInstance()); - - // WHEN - const QModelIndex indexNote2 = page.centralListModel()->index(1, 0); - page.removeItem(indexNote2); - - // THEN - QVERIFY(tagRepositoryMock(&Domain::TagRepository::dissociate).when(tag, note2).exactly(1)); - } - - void shouldGetAnErrorMessageWhenRemoveItemFailed() - { - // GIVEN - - // One domain tag - auto tag = Domain::Tag::Ptr::create(); - tag->setName(QStringLiteral("Tag1")); - - // Two notes - auto note1 = Domain::Note::Ptr::create(); - note1->setTitle(QStringLiteral("Note 1")); - auto note2 = Domain::Note::Ptr::create(); - note2->setTitle(QStringLiteral("Note 2")); - - auto noteProvider = Domain::QueryResultProvider<Domain::Note::Ptr>::Ptr::create(); - auto noteResult = Domain::QueryResult<Domain::Note::Ptr>::create(noteProvider); - noteProvider->append(note1); - noteProvider->append(note2); - - Utils::MockObject<Domain::TagQueries> tagQueriesMock; - tagQueriesMock(&Domain::TagQueries::findNotes).when(tag).thenReturn(noteResult); - - Utils::MockObject<Domain::TagRepository> tagRepositoryMock; - auto job = new FakeJob(this); - job->setExpectedError(KJob::KilledJobError, QStringLiteral("Foo")); - tagRepositoryMock(&Domain::TagRepository::dissociate).when(tag, note2).thenReturn(job); - - Utils::MockObject<Domain::NoteRepository> noteRepositoryMock; - - Presentation::TagPageModel page(tag, - tagQueriesMock.getInstance(), - tagRepositoryMock.getInstance(), - noteRepositoryMock.getInstance()); - FakeErrorHandler errorHandler; - page.setErrorHandler(&errorHandler); - - // WHEN - const QModelIndex indexNote2 = page.centralListModel()->index(1, 0); - page.removeItem(indexNote2); - - // THEN - QTest::qWait(150); - QCOMPARE(errorHandler.m_message, QStringLiteral("Cannot remove note Note 2 from tag Tag1: Foo")); - } - - void shouldGetAnErrorMessageWhenAddNoteFailed() - { - // GIVEN - - // One Tag - auto tag = Domain::Tag::Ptr::create(); - tag->setName(QStringLiteral("Tag1")); - - // ... in fact we won't list any model - Utils::MockObject<Domain::TagQueries> tagQueriesMock; - Utils::MockObject<Domain::TagRepository> tagRepositoryMock; - - // We'll gladly create a note though - Utils::MockObject<Domain::NoteRepository> noteRepositoryMock; - auto job = new FakeJob(this); - job->setExpectedError(KJob::KilledJobError, QStringLiteral("Foo")); - noteRepositoryMock(&Domain::NoteRepository::createInTag).when(any<Domain::Note::Ptr>(), - any<Domain::Tag::Ptr>()) - .thenReturn(job); - - Presentation::TagPageModel page(tag, - tagQueriesMock.getInstance(), - tagRepositoryMock.getInstance(), - noteRepositoryMock.getInstance()); - FakeErrorHandler errorHandler; - page.setErrorHandler(&errorHandler); - - // WHEN - page.addItem(QStringLiteral("New note")); - - // THEN - QTest::qWait(150); - QCOMPARE(errorHandler.m_message, QStringLiteral("Cannot add note New note in tag Tag1: Foo")); - } - - void shouldGetAnErrorMessageWhenUpdateNoteFailed() - { - // GIVEN - - // One Tag - auto tag = Domain::Tag::Ptr::create(); - tag->setName(QStringLiteral("Tag1")); - - // One note - auto rootNote = Domain::Note::Ptr::create(); - rootNote->setTitle(QStringLiteral("rootNote")); - auto noteProvider = Domain::QueryResultProvider<Domain::Note::Ptr>::Ptr::create(); - auto noteResult = Domain::QueryResult<Domain::Note::Ptr>::create(noteProvider); - noteProvider->append(rootNote); - - Utils::MockObject<Domain::TagQueries> tagQueriesMock; - tagQueriesMock(&Domain::TagQueries::findNotes).when(tag).thenReturn(noteResult); - - Utils::MockObject<Domain::NoteRepository> noteRepositoryMock; - Utils::MockObject<Domain::TagRepository> tagRepositoryMock; - - Presentation::TagPageModel page(tag, - tagQueriesMock.getInstance(), - tagRepositoryMock.getInstance(), - noteRepositoryMock.getInstance()); - - QAbstractItemModel *model = page.centralListModel(); - const QModelIndex rootNoteIndex = model->index(0, 0); - FakeErrorHandler errorHandler; - page.setErrorHandler(&errorHandler); - - // WHEN - auto job = new FakeJob(this); - job->setExpectedError(KJob::KilledJobError, QStringLiteral("Foo")); - noteRepositoryMock(&Domain::NoteRepository::update).when(rootNote).thenReturn(job); - - QVERIFY(model->setData(rootNoteIndex, "newRootNote")); - - // THEN - QTest::qWait(150); - QCOMPARE(errorHandler.m_message, QStringLiteral("Cannot modify note rootNote in tag Tag1: Foo")); - } -}; - -ZANSHIN_TEST_MAIN(TagPageModelTest) - -#include "tagpagemodeltest.moc" diff --git a/tests/units/presentation/workdaypagemodeltest.cpp b/tests/units/presentation/workdaypagemodeltest.cpp index 294aa4a5..773fe91a 100644 --- a/tests/units/presentation/workdaypagemodeltest.cpp +++ b/tests/units/presentation/workdaypagemodeltest.cpp @@ -1,399 +1,398 @@ /* This file is part of Zanshin Copyright 2015 Theo Vaucher <theo.vaucher@gmail.com> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include <testlib/qtest_zanshin.h> #include <memory> #include <QMimeData> #include <testlib/fakejob.h> #include "utils/datetime.h" #include "utils/mockobject.h" -#include "domain/noterepository.h" #include "domain/taskqueries.h" #include "domain/taskrepository.h" #include "presentation/workdaypagemodel.h" #include "presentation/querytreemodelbase.h" using namespace mockitopp; using namespace mockitopp::matcher; class WorkdayPageModelTest : public QObject { Q_OBJECT private slots: void shouldListWorkdayInCentralListModel() { // GIVEN const auto today = Utils::DateTime::currentDate(); // Three tasks auto task1 = Domain::Task::Ptr::create(); task1->setTitle(QStringLiteral("task1")); task1->setStartDate(today.addDays(-10)); task1->setDueDate(today); auto task2 = Domain::Task::Ptr::create(); task2->setTitle(QStringLiteral("task2")); task2->setStartDate(today); task2->setDueDate(today.addDays(10)); auto task3 = Domain::Task::Ptr::create(); task3->setTitle(QStringLiteral("task3")); task3->setStartDate(today.addYears(-4)); task3->setDueDate(today.addYears(-3)); auto taskProvider = Domain::QueryResultProvider<Domain::Task::Ptr>::Ptr::create(); auto taskResult = Domain::QueryResult<Domain::Task::Ptr>::create(taskProvider); taskProvider->append(task1); taskProvider->append(task2); taskProvider->append(task3); // Two tasks under the task1 auto childTask11 = Domain::Task::Ptr::create(); childTask11->setTitle(QStringLiteral("childTask11")); auto childTask12 = Domain::Task::Ptr::create(); childTask12->setTitle(QStringLiteral("childTask12")); childTask12->setStartDate(today); childTask12->setDueDate(today); auto childTaskProvider = Domain::QueryResultProvider<Domain::Task::Ptr>::Ptr::create(); auto childTaskResult = Domain::QueryResult<Domain::Task::Ptr>::create(childTaskProvider); taskProvider->append(childTask12); childTaskProvider->append(childTask11); childTaskProvider->append(childTask12); // One project auto project = Domain::Project::Ptr::create(); project->setName("KDE"); auto projectProvider = Domain::QueryResultProvider<Domain::Project::Ptr>::Ptr::create(); auto projectResult = Domain::QueryResult<Domain::Project::Ptr>::create(projectProvider); projectProvider->append(project); Utils::MockObject<Domain::TaskQueries> taskQueriesMock; taskQueriesMock(&Domain::TaskQueries::findWorkdayTopLevel).when().thenReturn(taskResult); taskQueriesMock(&Domain::TaskQueries::findChildren).when(task1).thenReturn(childTaskResult); taskQueriesMock(&Domain::TaskQueries::findChildren).when(task2).thenReturn(Domain::QueryResult<Domain::Task::Ptr>::Ptr()); taskQueriesMock(&Domain::TaskQueries::findChildren).when(task3).thenReturn(Domain::QueryResult<Domain::Task::Ptr>::Ptr()); taskQueriesMock(&Domain::TaskQueries::findChildren).when(childTask11).thenReturn(Domain::QueryResult<Domain::Task::Ptr>::Ptr()); taskQueriesMock(&Domain::TaskQueries::findChildren).when(childTask12).thenReturn(Domain::QueryResult<Domain::Task::Ptr>::Ptr()); taskQueriesMock(&Domain::TaskQueries::findProject).when(task1).thenReturn(Domain::QueryResult<Domain::Project::Ptr>::Ptr()); taskQueriesMock(&Domain::TaskQueries::findProject).when(task2).thenReturn(projectResult); taskQueriesMock(&Domain::TaskQueries::findProject).when(task3).thenReturn(Domain::QueryResult<Domain::Project::Ptr>::Ptr()); taskQueriesMock(&Domain::TaskQueries::findProject).when(childTask11).thenReturn(Domain::QueryResult<Domain::Project::Ptr>::Ptr()); taskQueriesMock(&Domain::TaskQueries::findProject).when(childTask12).thenReturn(Domain::QueryResult<Domain::Project::Ptr>::Ptr()); Utils::MockObject<Domain::TaskRepository> taskRepositoryMock; Presentation::WorkdayPageModel workday(taskQueriesMock.getInstance(), taskRepositoryMock.getInstance()); // WHEN QAbstractItemModel *model = workday.centralListModel(); // THEN const QModelIndex task1Index = model->index(0, 0); const QModelIndex task2Index = model->index(1, 0); const QModelIndex task3Index = model->index(2, 0); const QModelIndex taskChildTask12Index = model->index(3, 0); const QModelIndex childTask11Index = model->index(0, 0, task1Index); const QModelIndex childTask12Index = model->index(1, 0, task1Index); QCOMPARE(model->rowCount(), 4); QCOMPARE(model->rowCount(task1Index), 2); QCOMPARE(model->rowCount(task2Index), 0); QCOMPARE(model->rowCount(task3Index), 0); QCOMPARE(model->rowCount(taskChildTask12Index), 0); QVERIFY(childTask11Index.isValid()); QVERIFY(childTask12Index.isValid()); QCOMPARE(model->rowCount(childTask11Index), 0); QCOMPARE(model->rowCount(childTask12Index), 0); const Qt::ItemFlags defaultFlags = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsDragEnabled; QCOMPARE(model->flags(task1Index), defaultFlags | Qt::ItemIsUserCheckable | Qt::ItemIsDropEnabled); QCOMPARE(model->flags(childTask11Index), defaultFlags | Qt::ItemIsUserCheckable | Qt::ItemIsDropEnabled); QCOMPARE(model->flags(childTask12Index), defaultFlags | Qt::ItemIsUserCheckable | Qt::ItemIsDropEnabled); QCOMPARE(model->flags(task2Index), defaultFlags | Qt::ItemIsUserCheckable | Qt::ItemIsDropEnabled); QCOMPARE(model->flags(task3Index), defaultFlags | Qt::ItemIsUserCheckable | Qt::ItemIsDropEnabled); QCOMPARE(model->flags(taskChildTask12Index), defaultFlags | Qt::ItemIsUserCheckable | Qt::ItemIsDropEnabled); QCOMPARE(model->data(task1Index).toString(), task1->title()); QCOMPARE(model->data(childTask11Index).toString(), childTask11->title()); QCOMPARE(model->data(childTask12Index).toString(), childTask12->title()); QCOMPARE(model->data(task2Index).toString(), task2->title()); QCOMPARE(model->data(task3Index).toString(), task3->title()); QCOMPARE(model->data(taskChildTask12Index).toString(), childTask12->title()); QCOMPARE(model->data(task1Index, Qt::EditRole).toString(), task1->title()); QCOMPARE(model->data(childTask11Index, Qt::EditRole).toString(), childTask11->title()); QCOMPARE(model->data(childTask12Index, Qt::EditRole).toString(), childTask12->title()); QCOMPARE(model->data(task2Index, Qt::EditRole).toString(), task2->title()); QCOMPARE(model->data(task3Index, Qt::EditRole).toString(), task3->title()); QCOMPARE(model->data(taskChildTask12Index, Qt::EditRole).toString(), childTask12->title()); QVERIFY(model->data(task1Index, Qt::CheckStateRole).isValid()); QVERIFY(model->data(childTask11Index, Qt::CheckStateRole).isValid()); QVERIFY(model->data(childTask12Index, Qt::CheckStateRole).isValid()); QVERIFY(model->data(task2Index, Qt::CheckStateRole).isValid()); QVERIFY(model->data(task3Index, Qt::CheckStateRole).isValid()); QVERIFY(model->data(taskChildTask12Index, Qt::CheckStateRole).isValid()); QCOMPARE(model->data(task1Index, Qt::CheckStateRole).toBool(), task1->isDone()); QCOMPARE(model->data(childTask11Index, Qt::CheckStateRole).toBool(), childTask11->isDone()); QCOMPARE(model->data(childTask12Index, Qt::CheckStateRole).toBool(), childTask12->isDone()); QCOMPARE(model->data(task2Index, Qt::CheckStateRole).toBool(), task2->isDone()); QCOMPARE(model->data(task3Index, Qt::CheckStateRole).toBool(), task3->isDone()); QCOMPARE(model->data(taskChildTask12Index, Qt::CheckStateRole).toBool(), childTask12->isDone()); QCOMPARE(model->data(task1Index, Presentation::QueryTreeModelBase::AdditionalInfoRole).toString(), QString("Inbox")); QCOMPARE(model->data(task2Index, Presentation::QueryTreeModelBase::AdditionalInfoRole).toString(), QString("Project: KDE")); // WHEN taskRepositoryMock(&Domain::TaskRepository::update).when(task1).thenReturn(new FakeJob(this)); taskRepositoryMock(&Domain::TaskRepository::update).when(childTask11).thenReturn(new FakeJob(this)); taskRepositoryMock(&Domain::TaskRepository::update).when(childTask12).thenReturn(new FakeJob(this)); taskRepositoryMock(&Domain::TaskRepository::update).when(task2).thenReturn(new FakeJob(this)); taskRepositoryMock(&Domain::TaskRepository::update).when(task3).thenReturn(new FakeJob(this)); QVERIFY(model->setData(task1Index, "newTask1")); QVERIFY(model->setData(childTask11Index, "newChildTask11")); QVERIFY(model->setData(task2Index, "newTask2")); QVERIFY(model->setData(task3Index, "newTask3")); QVERIFY(model->setData(taskChildTask12Index, "newChildTask12")); QVERIFY(model->setData(task1Index, Qt::Unchecked, Qt::CheckStateRole)); QVERIFY(model->setData(childTask11Index, Qt::Unchecked, Qt::CheckStateRole)); QVERIFY(model->setData(task2Index, Qt::Checked, Qt::CheckStateRole)); QVERIFY(model->setData(task3Index, Qt::Unchecked, Qt::CheckStateRole)); QVERIFY(model->setData(taskChildTask12Index, Qt::Checked, Qt::CheckStateRole)); // THEN QVERIFY(taskRepositoryMock(&Domain::TaskRepository::update).when(task1).exactly(2)); QVERIFY(taskRepositoryMock(&Domain::TaskRepository::update).when(childTask11).exactly(2)); QVERIFY(taskRepositoryMock(&Domain::TaskRepository::update).when(childTask12).exactly(2)); QVERIFY(taskRepositoryMock(&Domain::TaskRepository::update).when(task2).exactly(2)); QVERIFY(taskRepositoryMock(&Domain::TaskRepository::update).when(task3).exactly(2)); QCOMPARE(task1->title(), QStringLiteral("newTask1")); QCOMPARE(childTask11->title(), QStringLiteral("newChildTask11")); QCOMPARE(childTask12->title(), QStringLiteral("newChildTask12")); QCOMPARE(task2->title(), QStringLiteral("newTask2")); QCOMPARE(task3->title(), QStringLiteral("newTask3")); QCOMPARE(task1->isDone(), false); QCOMPARE(childTask11->isDone(), false); QCOMPARE(childTask12->isDone(), true); QCOMPARE(task2->isDone(), true); QCOMPARE(task3->isDone(), false); // WHEN auto data = std::unique_ptr<QMimeData>(model->mimeData(QModelIndexList() << childTask12Index)); // THEN QVERIFY(data->hasFormat(QStringLiteral("application/x-zanshin-object"))); QCOMPARE(data->property("objects").value<Domain::Artifact::List>(), Domain::Artifact::List() << childTask12); // WHEN auto childTask2 = Domain::Task::Ptr::create(); taskRepositoryMock(&Domain::TaskRepository::associate).when(childTask11, childTask2).thenReturn(new FakeJob(this)); data.reset(new QMimeData); data->setData(QStringLiteral("application/x-zanshin-object"), "object"); data->setProperty("objects", QVariant::fromValue(Domain::Artifact::List() << childTask2)); model->dropMimeData(data.get(), Qt::MoveAction, -1, -1, childTask11Index); // THEN QVERIFY(taskRepositoryMock(&Domain::TaskRepository::associate).when(childTask11, childTask2).exactly(1)); // WHEN auto childTask3 = Domain::Task::Ptr::create(); auto childTask4 = Domain::Task::Ptr::create(); taskRepositoryMock(&Domain::TaskRepository::associate).when(childTask12, childTask3).thenReturn(new FakeJob(this)); taskRepositoryMock(&Domain::TaskRepository::associate).when(childTask12, childTask4).thenReturn(new FakeJob(this)); data.reset(new QMimeData); data->setData(QStringLiteral("application/x-zanshin-object"), "object"); data->setProperty("objects", QVariant::fromValue(Domain::Artifact::List() << childTask3 << childTask4)); model->dropMimeData(data.get(), Qt::MoveAction, -1, -1, childTask12Index); // THEN QVERIFY(taskRepositoryMock(&Domain::TaskRepository::associate).when(childTask12, childTask3).exactly(1)); QVERIFY(taskRepositoryMock(&Domain::TaskRepository::associate).when(childTask12, childTask4).exactly(1)); // WHEN auto childTask5 = Domain::Task::Ptr::create(); QVERIFY(!childTask5->startDate().isValid()); taskRepositoryMock(&Domain::TaskRepository::dissociate).when(childTask5).thenReturn(new FakeJob(this)); data.reset(new QMimeData); data->setData(QStringLiteral("application/x-zanshin-object"), "object"); data->setProperty("objects", QVariant::fromValue(Domain::Artifact::List() << childTask5)); model->dropMimeData(data.get(), Qt::MoveAction, -1, -1, QModelIndex()); // THEN QVERIFY(taskRepositoryMock(&Domain::TaskRepository::dissociate).when(childTask5).exactly(1)); QVERIFY(taskRepositoryMock(&Domain::TaskRepository::update).when(childTask5).exactly(0)); QCOMPARE(childTask5->startDate(), today); } void shouldAddTasksInWorkdayPage() { // GIVEN // ... in fact we won't list any model Utils::MockObject<Domain::TaskQueries> taskQueriesMock; // We'll gladly create a task though Utils::MockObject<Domain::TaskRepository> taskRepositoryMock; taskRepositoryMock(&Domain::TaskRepository::create).when(any<Domain::Task::Ptr>()).thenReturn(new FakeJob(this)); Presentation::WorkdayPageModel workday(taskQueriesMock.getInstance(), taskRepositoryMock.getInstance()); // WHEN auto title = QStringLiteral("New task"); auto today = Utils::DateTime::currentDate(); auto task = workday.addItem(title).objectCast<Domain::Task>(); // THEN QVERIFY(taskRepositoryMock(&Domain::TaskRepository::create).when(any<Domain::Task::Ptr>()).exactly(1)); QVERIFY(task); QCOMPARE(task->title(), title); QCOMPARE(task->startDate(), today); } void shouldAddChildTask() { // GIVEN // Two tasks auto task1 = Domain::Task::Ptr::create(); auto task2 = Domain::Task::Ptr::create(); auto taskProvider = Domain::QueryResultProvider<Domain::Task::Ptr>::Ptr::create(); auto taskResult = Domain::QueryResult<Domain::Task::Ptr>::create(taskProvider); taskProvider->append(task1); taskProvider->append(task2); Utils::MockObject<Domain::TaskQueries> taskQueriesMock; taskQueriesMock(&Domain::TaskQueries::findWorkdayTopLevel).when().thenReturn(taskResult); taskQueriesMock(&Domain::TaskQueries::findChildren).when(task1).thenReturn(Domain::QueryResult<Domain::Task::Ptr>::Ptr()); taskQueriesMock(&Domain::TaskQueries::findChildren).when(task2).thenReturn(Domain::QueryResult<Domain::Task::Ptr>::Ptr()); taskQueriesMock(&Domain::TaskQueries::findProject).when(any<Domain::Task::Ptr>()).thenReturn(Domain::QueryResult<Domain::Project::Ptr>::Ptr()); Utils::MockObject<Domain::TaskRepository> taskRepositoryMock; taskRepositoryMock(&Domain::TaskRepository::createChild).when(any<Domain::Task::Ptr>(), any<Domain::Task::Ptr>()) .thenReturn(new FakeJob(this)); Presentation::WorkdayPageModel workday(taskQueriesMock.getInstance(), taskRepositoryMock.getInstance()); // WHEN const auto title = QStringLiteral("New task"); const auto parentIndex = workday.centralListModel()->index(0, 0); const auto createdTask = workday.addItem(title, parentIndex).objectCast<Domain::Task>(); // THEN QVERIFY(taskRepositoryMock(&Domain::TaskRepository::createChild).when(any<Domain::Task::Ptr>(), any<Domain::Task::Ptr>()) .exactly(1)); QVERIFY(createdTask); QCOMPARE(createdTask->title(), title); QVERIFY(!createdTask->startDate().isValid()); } void shouldDeleteItems() { // GIVEN // Two tasks auto task1 = Domain::Task::Ptr::create(); auto task2 = Domain::Task::Ptr::create(); auto taskProvider = Domain::QueryResultProvider<Domain::Task::Ptr>::Ptr::create(); auto taskResult = Domain::QueryResult<Domain::Task::Ptr>::create(taskProvider); taskProvider->append(task1); taskProvider->append(task2); Utils::MockObject<Domain::TaskQueries> taskQueriesMock; taskQueriesMock(&Domain::TaskQueries::findWorkdayTopLevel).when().thenReturn(taskResult); taskQueriesMock(&Domain::TaskQueries::findChildren).when(task1).thenReturn(Domain::QueryResult<Domain::Task::Ptr>::Ptr()); taskQueriesMock(&Domain::TaskQueries::findChildren).when(task2).thenReturn(Domain::QueryResult<Domain::Task::Ptr>::Ptr()); taskQueriesMock(&Domain::TaskQueries::findProject).when(any<Domain::Task::Ptr>()).thenReturn(Domain::QueryResult<Domain::Project::Ptr>::Ptr()); Utils::MockObject<Domain::TaskRepository> taskRepositoryMock; taskRepositoryMock(&Domain::TaskRepository::remove).when(task2).thenReturn(new FakeJob(this)); Presentation::WorkdayPageModel workday(taskQueriesMock.getInstance(), taskRepositoryMock.getInstance()); // WHEN const QModelIndex index = workday.centralListModel()->index(1, 0); workday.removeItem(index); // THEN QVERIFY(taskRepositoryMock(&Domain::TaskRepository::remove).when(task2).exactly(1)); } void shouldPromoteItem() { // GIVEN // Two tasks auto task1 = Domain::Task::Ptr::create(); auto task2 = Domain::Task::Ptr::create(); auto taskProvider = Domain::QueryResultProvider<Domain::Task::Ptr>::Ptr::create(); auto taskResult = Domain::QueryResult<Domain::Task::Ptr>::create(taskProvider); taskProvider->append(task1); taskProvider->append(task2); Utils::MockObject<Domain::TaskQueries> taskQueriesMock; taskQueriesMock(&Domain::TaskQueries::findWorkdayTopLevel).when().thenReturn(taskResult); taskQueriesMock(&Domain::TaskQueries::findChildren).when(task1).thenReturn(Domain::QueryResult<Domain::Task::Ptr>::Ptr()); taskQueriesMock(&Domain::TaskQueries::findChildren).when(task2).thenReturn(Domain::QueryResult<Domain::Task::Ptr>::Ptr()); taskQueriesMock(&Domain::TaskQueries::findProject).when(any<Domain::Task::Ptr>()).thenReturn(Domain::QueryResult<Domain::Project::Ptr>::Ptr()); Utils::MockObject<Domain::TaskRepository> taskRepositoryMock; taskRepositoryMock(&Domain::TaskRepository::promoteToProject).when(task2).thenReturn(new FakeJob(this)); Presentation::WorkdayPageModel workday(taskQueriesMock.getInstance(), taskRepositoryMock.getInstance()); // WHEN const QModelIndex index = workday.centralListModel()->index(1, 0); workday.promoteItem(index); // THEN QVERIFY(taskRepositoryMock(&Domain::TaskRepository::promoteToProject).when(task2).exactly(1)); } }; ZANSHIN_TEST_MAIN(WorkdayPageModelTest) #include "workdaypagemodeltest.moc" diff --git a/tests/units/testlib/CMakeLists.txt b/tests/units/testlib/CMakeLists.txt index 04b77060..2017b11c 100644 --- a/tests/units/testlib/CMakeLists.txt +++ b/tests/units/testlib/CMakeLists.txt @@ -1,12 +1,11 @@ add_definitions(-DSOURCE_DIR=\"${CMAKE_CURRENT_SOURCE_DIR}\") zanshin_auto_tests( akonadifakedatatest akonadifakedataxmlloadertest akonadifakestoragetest gencollectiontest - gennotetest gentagtest gentodotest monitorspytest ) diff --git a/tests/units/testlib/gennotetest.cpp b/tests/units/testlib/gennotetest.cpp deleted file mode 100644 index 1f2175d0..00000000 --- a/tests/units/testlib/gennotetest.cpp +++ /dev/null @@ -1,112 +0,0 @@ -/* This file is part of Zanshin - - Copyright 2015 Kevin Ottens <ervin@kde.org> - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License or (at your option) version 3 or any later version - accepted by the membership of KDE e.V. (or its successor approved - by the membership of KDE e.V.), which shall act as a proxy - defined in Section 14 of version 3 of the license. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - USA. -*/ - -#include "testlib/gennote.h" - -#include <Akonadi/Notes/NoteUtils> -#include <KMime/Message> - -#include <testlib/qtest_zanshin.h> - -using namespace Testlib; - -class GenNoteTest : public QObject -{ - Q_OBJECT -private slots: - void shouldImplicitlyConvertBackToItem() - { - // GIVEN - auto item = Akonadi::Item(42); - auto gen = GenNote(item); - - // WHEN - Akonadi::Item newItem = gen; - - // THEN - QCOMPARE(newItem, item); - QCOMPARE(newItem.mimeType(), Akonadi::NoteUtils::noteMimeType()); - QVERIFY(newItem.hasPayload<KMime::Message::Ptr>()); - } - - void shouldAllowToSetId() - { - // GIVEN - Akonadi::Item item = GenNote().withId(42); - - // THEN - QCOMPARE(item.id(), 42LL); - } - - void shouldAllowToSetParent() - { - // GIVEN - Akonadi::Item item = GenNote().withParent(42); - - // THEN - QCOMPARE(item.parentCollection().id(), 42LL); - } - - void shouldAllowToSetTags() - { - // GIVEN - Akonadi::Item item = GenNote().withTags({42, 43, 44}); - - // THEN - QCOMPARE(item.tags().size(), 3); - QCOMPARE(item.tags().at(0).id(), 42LL); - QCOMPARE(item.tags().at(1).id(), 43LL); - QCOMPARE(item.tags().at(2).id(), 44LL); - } - - void shouldAllowToSetParentUid() - { - // GIVEN - Akonadi::Item item = GenNote().withParentUid(QStringLiteral("42")); - - // THEN - QCOMPARE(item.payload<KMime::Message::Ptr>()->headerByType("X-Zanshin-RelatedProjectUid")->asUnicodeString(), QStringLiteral("42")); - } - - void shouldAllowToSetTitle() - { - // GIVEN - Akonadi::Item item = GenNote().withTitle(QStringLiteral("42")); - - // THEN - QCOMPARE(item.payload<KMime::Message::Ptr>()->subject()->asUnicodeString(), QStringLiteral("42")); - } - - void shouldAllowToSetText() - { - // GIVEN - Akonadi::Item item = GenNote().withText(QStringLiteral("42")); - - // THEN - QCOMPARE(item.payload<KMime::Message::Ptr>()->body(), QByteArray("42")); - } -}; - -ZANSHIN_TEST_MAIN(GenNoteTest) - -#include "gennotetest.moc" diff --git a/tests/units/widgets/applicationcomponentstest.cpp b/tests/units/widgets/applicationcomponentstest.cpp index 2ddaf0fd..81f1819e 100644 --- a/tests/units/widgets/applicationcomponentstest.cpp +++ b/tests/units/widgets/applicationcomponentstest.cpp @@ -1,688 +1,687 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens <ervin@kde.org> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include <testlib/qtest_gui_zanshin.h> #include <QMimeData> #include <QStandardItemModel> #include <QStringListModel> #include <QTreeView> #include <QWidgetAction> #include <algorithm> #include "utils/mem_fn.h" -#include "domain/note.h" #include "domain/task.h" #include "presentation/artifactfilterproxymodel.h" #include "presentation/querytreemodelbase.h" #include "widgets/applicationcomponents.h" #include "widgets/availablepagesview.h" #include "widgets/availablesourcesview.h" #include "widgets/editorview.h" #include "widgets/filterwidget.h" #include "widgets/pageview.h" #include "widgets/pageviewerrorhandler.h" #include "widgets/quickselectdialog.h" class CustomModelStub : public QStandardItemModel { Q_OBJECT QMimeData *mimeData(const QModelIndexList &indexes) const override { QStringList dataString; std::transform(indexes.begin(), indexes.end(), std::back_inserter(dataString), [] (const QModelIndex &index) { return index.data().toString(); }); auto data = new QMimeData; data->setData(QStringLiteral("application/x-zanshin-object"), "object"); data->setProperty("objects", QVariant::fromValue(dataString)); return data; } bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &destination) override { Q_UNUSED(action); Q_ASSERT(row == -1); Q_ASSERT(column == -1); Q_ASSERT(destination.isValid()); Q_ASSERT(data->hasFormat(QStringLiteral("application/x-zanshin-object"))); auto dataString = data->property("objects").toStringList(); Q_ASSERT(!dataString.isEmpty()); droppedItemDataString = dataString; dropDestination = destination.data().toString(); return true; } public: QStringList droppedItemDataString; QString dropDestination; }; class ApplicationModelStub : public QObject { Q_OBJECT Q_PROPERTY(QObject* currentPage READ currentPage WRITE setCurrentPage) public: typedef QSharedPointer<ApplicationModelStub> Ptr; explicit ApplicationModelStub(QObject *parent = Q_NULLPTR) : QObject(parent), m_currentPage(Q_NULLPTR) {} QObject *currentPage() { return m_currentPage; } void setCurrentPage(QObject *page) { if (page == m_currentPage) return; m_currentPage = page; emit currentPageChanged(m_currentPage); } signals: void currentPageChanged(QObject *page); private: QObject *m_currentPage; }; class AvailablePagesModelStub : public QObject { Q_OBJECT Q_PROPERTY(QAbstractItemModel* pageListModel READ pageListModel) public: explicit AvailablePagesModelStub(QObject *parent = Q_NULLPTR) : QObject(parent) { QStandardItem *inbox = new QStandardItem; inbox->setData("Inbox", Qt::DisplayRole); itemModel.appendRow(inbox); QStandardItem *project = new QStandardItem; project->setData("Project", Qt::DisplayRole); itemModel.appendRow(project); } QAbstractItemModel *pageListModel() { return &itemModel; } Q_SCRIPTABLE QObject *createPageForIndex(const QModelIndex &index) { auto page = new QObject(this); auto model = new QStringListModel(page); model->setStringList(QStringList() << QStringLiteral("Items") << QStringLiteral("from") << index.data().toString()); page->setProperty("centralListModel", QVariant::fromValue<QAbstractItemModel*>(model)); createdPages << page; return page; } public: QList<QObject*> createdPages; CustomModelStub itemModel; }; class PageModelStub : public QObject { Q_OBJECT Q_PROPERTY(QAbstractItemModel* centralListModel READ centralListModel) public: QAbstractItemModel *centralListModel() { return &itemModel; } template<typename T> void addItem(const QString &title) { auto artifact = T::Ptr::create(); artifact->setTitle(title); addItem(artifact); } void addItem(const Domain::Artifact::Ptr &artifact) { QStandardItem *item = new QStandardItem; item->setData(QVariant::fromValue(artifact), Presentation::QueryTreeModelBase::ObjectRole); item->setData(artifact->title(), Qt::DisplayRole); itemModel.appendRow(item); } Domain::Artifact::Ptr itemAtRow(int row) const { return itemModel.index(row, 0).data(Presentation::QueryTreeModelBase::ObjectRole) .value<Domain::Artifact::Ptr>(); } QModelIndexList selectedIndexes() const { return selectedItems; } public: QModelIndexList selectedItems; CustomModelStub itemModel; }; class EditorModelStub : public QObject { Q_OBJECT public: explicit EditorModelStub(QObject *parent = Q_NULLPTR) : QObject(parent) { } void setPropertyAndSignal(const QByteArray &name, const QVariant &value) { if (property(name) == value) return; setProperty(name, value); if (name == "text") emit textChanged(value.toString()); else if (name == "title") emit titleChanged(value.toString()); else if (name == "done") emit doneChanged(value.toBool()); else if (name == "startDate") emit startDateChanged(value.toDate()); else if (name == "dueDate") emit dueDateChanged(value.toDate()); else if (name == "hasTaskProperties") emit hasTaskPropertiesChanged(value.toBool()); else qFatal("Unsupported property %s", name.constData()); } public slots: void setTitle(const QString &title) { setPropertyAndSignal("title", title); } void setText(const QString &text) { setPropertyAndSignal("text", text); } void setDone(bool done) { setPropertyAndSignal("done", done); } void setStartDate(const QDate &start) { setPropertyAndSignal("startDate", start); } void setDueDate(const QDate &due) { setPropertyAndSignal("dueDate", due); } signals: void hasTaskPropertiesChanged(bool hasTaskProperties); void textChanged(const QString &text); void titleChanged(const QString &title); void doneChanged(bool done); void startDateChanged(const QDate &date); void dueDateChanged(const QDate &due); }; class QuickSelectDialogStub : public Widgets::QuickSelectDialogInterface { public: typedef QSharedPointer<QuickSelectDialogStub> Ptr; explicit QuickSelectDialogStub() : parent(Q_NULLPTR), execCount(0), itemModel(Q_NULLPTR) { } int exec() Q_DECL_OVERRIDE { execCount++; return QDialog::Accepted; } void setModel(QAbstractItemModel *model) Q_DECL_OVERRIDE { itemModel = model; } QPersistentModelIndex selectedIndex() const Q_DECL_OVERRIDE { return index; } QWidget *parent; int execCount; QAbstractItemModel *itemModel; QPersistentModelIndex index; }; class ApplicationComponentsTest : public QObject { Q_OBJECT public: explicit ApplicationComponentsTest(QObject *parent = Q_NULLPTR) : QObject(parent) { qputenv("ZANSHIN_UNIT_TEST_RUN", "1"); } private slots: void shouldHaveApplicationModelAndSetErrorHandler() { // GIVEN Widgets::ApplicationComponents components; auto model = QObjectPtr::create(); // WHEN components.setModel(model); // THEN QCOMPARE(components.model(), model); auto errorHandlerBase = model->property("errorHandler").value<Presentation::ErrorHandler*>(); QVERIFY(errorHandlerBase); auto errorHandler = static_cast<Widgets::PageViewErrorHandler*>(errorHandlerBase); QVERIFY(errorHandler); QVERIFY(!errorHandler->pageView()); // WHEN auto pageView = components.pageView(); // THEN QCOMPARE(errorHandler->pageView(), pageView); } void shouldApplyAvailableSourcesModelToAvailableSourcesView() { // GIVEN Widgets::ApplicationComponents components; auto model = QObjectPtr::create(); QObject availableSources; model->setProperty("availableSources", QVariant::fromValue(&availableSources)); // WHEN components.setModel(model); // THEN QCOMPARE(components.availableSourcesView()->model(), &availableSources); } void shouldApplyAvailableSourcesModelAlsoToCreatedAvailableSourcesView() { // GIVEN Widgets::ApplicationComponents components; // Force creation components.availableSourcesView(); auto model = QObjectPtr::create(); QObject availableSources; model->setProperty("availableSources", QVariant::fromValue(&availableSources)); // WHEN components.setModel(model); // THEN QCOMPARE(components.availableSourcesView()->model(), &availableSources); } void shouldApplyAvailablePagesModelToAvailablePagesView() { // GIVEN Widgets::ApplicationComponents components; auto model = QObjectPtr::create(); QObject availablePages; model->setProperty("availablePages", QVariant::fromValue(&availablePages)); QObject availableSources; QAbstractItemModel *sourcesModel = new QStandardItemModel(model.data()); availableSources.setProperty("sourceListModel", QVariant::fromValue(sourcesModel)); model->setProperty("availableSources", QVariant::fromValue(&availableSources)); // WHEN components.setModel(model); // THEN QCOMPARE(components.availablePagesView()->model(), &availablePages); QCOMPARE(components.availablePagesView()->projectSourcesModel(), sourcesModel); } void shouldApplyAvailablePagesModelAlsoToCreatedAvailablePagesView() { // GIVEN Widgets::ApplicationComponents components; // Force creation components.availablePagesView(); auto model = QObjectPtr::create(); QObject availablePages; QAbstractItemModel *sourcesModel = new QStandardItemModel(model.data()); model->setProperty("dataSourcesModel", QVariant::fromValue(sourcesModel)); model->setProperty("availablePages", QVariant::fromValue(&availablePages)); // WHEN components.setModel(model); // THEN QCOMPARE(components.availablePagesView()->model(), &availablePages); QCOMPARE(components.availablePagesView()->projectSourcesModel(), sourcesModel); } void shouldApplyCurrentPageModelToPageView() { // GIVEN Widgets::ApplicationComponents components; auto model = QObjectPtr::create(); QObject currentPage; model->setProperty("currentPage", QVariant::fromValue(¤tPage)); // WHEN components.setModel(model); // THEN QCOMPARE(components.pageView()->model(), ¤tPage); } void shouldApplyCurrentPageModelAlsoToCreatedPageView() { // GIVEN Widgets::ApplicationComponents components; // Force creation components.pageView(); auto model = QObjectPtr::create(); QObject currentPage; model->setProperty("currentPage", QVariant::fromValue(¤tPage)); // WHEN components.setModel(model); // THEN QCOMPARE(components.pageView()->model(), ¤tPage); } void shouldApplyEditorModelToEditorView() { // GIVEN Widgets::ApplicationComponents components; auto model = QObjectPtr::create(); QObject *editorModel = new EditorModelStub(model.data()); model->setProperty("editor", QVariant::fromValue(editorModel)); // WHEN components.setModel(model); // THEN QCOMPARE(components.editorView()->model(), editorModel); } void shouldApplyEditorModelAltoToCreatedPageView() { // GIVEN Widgets::ApplicationComponents components; // Force creation components.editorView(); auto model = QObjectPtr::create(); QObject *editorModel = new EditorModelStub(model.data()); model->setProperty("editor", QVariant::fromValue(editorModel)); // WHEN components.setModel(model); // THEN QCOMPARE(components.editorView()->model(), editorModel); } void shouldPropageNullModelsToViews() { // GIVEN Widgets::ApplicationComponents components; auto model = QObjectPtr::create(); auto availableSources = new QObject(model.data()); model->setProperty("availableSources", QVariant::fromValue(availableSources)); auto availablePages = new QObject(model.data()); model->setProperty("availablePages", QVariant::fromValue(availablePages)); auto currentPage = new QObject(model.data()); model->setProperty("currentPage", QVariant::fromValue(currentPage)); auto editorModel = new EditorModelStub(model.data()); model->setProperty("editor", QVariant::fromValue<QObject*>(editorModel)); components.setModel(model); // WHEN components.setModel(QObjectPtr()); components.availableSourcesView(); components.availablePagesView(); components.pageView(); components.editorView(); // THEN QVERIFY(!components.availableSourcesView()->model()); QVERIFY(!components.availablePagesView()->model()); QVERIFY(!components.pageView()->model()); QVERIFY(!components.editorView()->model()); } void shouldPropageNullModelsToCreatedViews() { // GIVEN Widgets::ApplicationComponents components; components.availableSourcesView(); components.availablePagesView(); components.pageView(); components.editorView(); auto model = QObjectPtr::create(); auto availableSources = new QObject(model.data()); model->setProperty("availableSources", QVariant::fromValue(availableSources)); auto availablePages = new QObject(model.data()); model->setProperty("availablePages", QVariant::fromValue(availablePages)); auto currentPage = new QObject(model.data()); model->setProperty("currentPage", QVariant::fromValue(currentPage)); auto editorModel = new EditorModelStub(model.data()); model->setProperty("editor", QVariant::fromValue<QObject*>(editorModel)); components.setModel(model); // WHEN components.setModel(QObjectPtr()); // THEN QVERIFY(!components.availableSourcesView()->model()); QVERIFY(!components.availablePagesView()->model()); QVERIFY(!components.pageView()->model()); QVERIFY(!components.editorView()->model()); } void shouldApplyAvailablePagesSelectionToApplicationModel() { // GIVEN auto model = ApplicationModelStub::Ptr::create(); AvailablePagesModelStub availablePagesModel; model->setProperty("availablePages", QVariant::fromValue<QObject*>(&availablePagesModel)); model->setProperty("currentPage", QVariant::fromValue<QObject*>(Q_NULLPTR)); QObject editorModel; editorModel.setProperty("artifact", QVariant::fromValue<Domain::Artifact::Ptr>(Domain::Task::Ptr::create())); model->setProperty("editor", QVariant::fromValue<QObject*>(&editorModel)); Widgets::ApplicationComponents components; components.setModel(model); Widgets::AvailablePagesView *availablePagesView = components.availablePagesView(); auto pagesView = availablePagesView->findChild<QTreeView*>(QStringLiteral("pagesView")); QVERIFY(pagesView); Widgets::PageView *pageView = components.pageView(); QVERIFY(pageView); QVERIFY(!pageView->model()); QModelIndex index = pagesView->model()->index(0, 0); // WHEN pagesView->selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect); // THEN QCOMPARE(availablePagesModel.createdPages.size(), 1); QCOMPARE(model->property("currentPage").value<QObject*>(), availablePagesModel.createdPages.first()); QCOMPARE(pageView->model(), availablePagesModel.createdPages.first()); QVERIFY(editorModel.property("artifact").value<Domain::Artifact::Ptr>().isNull()); } void shouldApplyPageViewSelectionToEditorModel() { // GIVEN auto model = QObjectPtr::create(); PageModelStub pageModel; pageModel.addItem<Domain::Task>(QStringLiteral("0. First task")); - pageModel.addItem<Domain::Note>(QStringLiteral("1. A note")); - pageModel.addItem<Domain::Task>(QStringLiteral("2. Second task")); - pageModel.addItem<Domain::Note>(QStringLiteral("3. Another note")); + pageModel.addItem<Domain::Task>(QStringLiteral("1. Second task")); + pageModel.addItem<Domain::Task>(QStringLiteral("2. Third task")); + pageModel.addItem<Domain::Task>(QStringLiteral("3. Yet another task")); model->setProperty("currentPage", QVariant::fromValue<QObject*>(&pageModel)); EditorModelStub editorModel; model->setProperty("editor", QVariant::fromValue<QObject*>(&editorModel)); Widgets::ApplicationComponents components; components.setModel(model); Widgets::PageView *pageView = components.pageView(); auto centralView = pageView->findChild<QTreeView*>(QStringLiteral("centralView")); QModelIndex index = centralView->model()->index(2, 0); // WHEN centralView->selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect); // THEN QCOMPARE(editorModel.property("artifact").value<Domain::Artifact::Ptr>(), pageModel.itemAtRow(index.row())); } void shouldHaveDefaultActionsList() { // GIVEN Widgets::ApplicationComponents components; // WHEN auto actions = components.globalActions(); // THEN // availablePages view auto available = components.availablePagesView(); foreach (const auto &key, available->globalActions().keys()) QCOMPARE(actions.value(key), available->globalActions().value(key)); // availableSources view auto availableSources = components.availableSourcesView(); foreach (const auto &key, availableSources->globalActions().keys()) QCOMPARE(actions.value(key), availableSources->globalActions().value(key)); // page view auto page = components.pageView(); foreach (const auto &key, page->globalActions().keys()) QCOMPARE(actions.value(key), page->globalActions().value(key)); // application component own action auto moveAction = components.findChild<QAction*>(QStringLiteral("moveItemAction")); QCOMPARE(actions.value(QStringLiteral("page_view_move")), moveAction); } void shouldMoveItem() { // GIVEN auto model = QObjectPtr::create(); PageModelStub pageModel; pageModel.addItem<Domain::Task>(QStringLiteral("0. First task")); - pageModel.addItem<Domain::Note>(QStringLiteral("1. A note")); - pageModel.addItem<Domain::Task>(QStringLiteral("2. Second task")); - pageModel.addItem<Domain::Note>(QStringLiteral("3. Another note")); + pageModel.addItem<Domain::Task>(QStringLiteral("1. Second task")); + pageModel.addItem<Domain::Task>(QStringLiteral("2. Third task")); + pageModel.addItem<Domain::Task>(QStringLiteral("3. Yet another task")); model->setProperty("currentPage", QVariant::fromValue<QObject*>(&pageModel)); AvailablePagesModelStub availablePagesModelStub; model->setProperty("availablePages", QVariant::fromValue<QObject*>(&availablePagesModelStub)); QWidget *mainWidget = new QWidget; Widgets::ApplicationComponents components(mainWidget); components.setModel(model); auto availablePageView = components.availablePagesView(); auto availablePagesTreeView = availablePageView->findChild<QTreeView*>(QStringLiteral("pagesView")); Q_ASSERT(availablePagesTreeView); auto dialogStub = QuickSelectDialogStub::Ptr::create(); dialogStub->index = availablePagesModelStub.pageListModel()->index(0, 0); // inbox selected components.setQuickSelectDialogFactory([dialogStub] (QWidget *parent) { dialogStub->parent = parent; return dialogStub; }); auto pageView = components.pageView(); auto centralView = pageView->findChild<QTreeView*>(QStringLiteral("centralView")); QModelIndex index1 = pageModel.itemModel.index(0,0); QModelIndex index2 = pageModel.itemModel.index(2,0); auto filterWidget = pageView->findChild<Widgets::FilterWidget*>(QStringLiteral("filterWidget")); auto displayedModel = filterWidget->proxyModel(); auto displayedIndex = displayedModel->index(0, 0); auto displayedIndex2 = displayedModel->index(2, 0); auto moveAction = components.findChild<QAction*>(QStringLiteral("moveItemAction")); // WHEN pageModel.selectedItems << index1 << index2; centralView->selectionModel()->setCurrentIndex(displayedIndex, QItemSelectionModel::ClearAndSelect); centralView->selectionModel()->setCurrentIndex(displayedIndex2, QItemSelectionModel::Select); moveAction->trigger(); // THEN QCOMPARE(dialogStub->execCount, 1); QCOMPARE(dialogStub->parent, pageView); QCOMPARE(dialogStub->itemModel, availablePagesModelStub.pageListModel()); QCOMPARE(availablePagesModelStub.itemModel.dropDestination, QStringLiteral("Inbox")); QCOMPARE(availablePagesModelStub.itemModel.droppedItemDataString.size(), 2); QCOMPARE(availablePagesModelStub.itemModel.droppedItemDataString.at(0), index1.data().toString()); QCOMPARE(availablePagesModelStub.itemModel.droppedItemDataString.at(1), index2.data().toString()); } }; ZANSHIN_TEST_MAIN(ApplicationComponentsTest) #include "applicationcomponentstest.moc" diff --git a/tests/units/widgets/availablepagesviewtest.cpp b/tests/units/widgets/availablepagesviewtest.cpp index b6709aa1..28a90039 100644 --- a/tests/units/widgets/availablepagesviewtest.cpp +++ b/tests/units/widgets/availablepagesviewtest.cpp @@ -1,598 +1,593 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens <ervin@kde.org> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include <testlib/qtest_gui_zanshin.h> #include <QAbstractItemModel> #include <QAction> #include <QDebug> #include <QHeaderView> #include <QStandardItemModel> #include <QStringListModel> #include <QToolBar> #include <QTreeView> #include "domain/project.h" #include "domain/context.h" -#include "domain/tag.h" #include "presentation/metatypes.h" #include "presentation/querytreemodelbase.h" #include "widgets/availablepagesview.h" #include "widgets/newprojectdialog.h" #include "widgets/quickselectdialog.h" #include "messageboxstub.h" class NewProjectDialogStub : public Widgets::NewProjectDialogInterface { public: typedef QSharedPointer<NewProjectDialogStub> Ptr; explicit NewProjectDialogStub() : parent(Q_NULLPTR), execCount(0), sourceModel(Q_NULLPTR), source(Domain::DataSource::Ptr::create()) { } int exec() Q_DECL_OVERRIDE { execCount++; return QDialog::Accepted; } void setDataSourcesModel(QAbstractItemModel *model) Q_DECL_OVERRIDE { sourceModel = model; } QString name() const Q_DECL_OVERRIDE { return QStringLiteral("name"); } Domain::DataSource::Ptr dataSource() const Q_DECL_OVERRIDE { return source; } QWidget *parent; int execCount; QAbstractItemModel *sourceModel; Domain::DataSource::Ptr source; }; class QuickSelectDialogStub : public Widgets::QuickSelectDialogInterface { public: typedef QSharedPointer<QuickSelectDialogStub> Ptr; explicit QuickSelectDialogStub() : parent(Q_NULLPTR), execCount(0), itemModel(Q_NULLPTR) { } int exec() Q_DECL_OVERRIDE { execCount++; return QDialog::Accepted; } void setModel(QAbstractItemModel *model) Q_DECL_OVERRIDE { itemModel = model; } QPersistentModelIndex selectedIndex() const Q_DECL_OVERRIDE { return index; } QWidget *parent; int execCount; QAbstractItemModel *itemModel; QPersistentModelIndex index; }; class AvailablePagesModelStub : public QObject { Q_OBJECT public: explicit AvailablePagesModelStub(QObject *parent = Q_NULLPTR) : QObject(parent) { } public slots: void addProject(const QString &name, const Domain::DataSource::Ptr &source) { projectNames << name; sources << source; } void addContext(const QString &name) { contextNames << name; } void addTag(const QString &name) { tagNames << name; } void removeItem(const QModelIndex &index) { projectRemoved = index.data().toString(); } public Q_SLOTS: QObject *createPageForIndex(const QModelIndex &) { return Q_NULLPTR; } public: QStringList projectNames; QStringList contextNames; QStringList tagNames; QList<Domain::DataSource::Ptr> sources; QString projectRemoved; }; class AvailablePagesViewTest : public QObject { Q_OBJECT private slots: void shouldHaveDefaultState() { Widgets::AvailablePagesView available; QVERIFY(!available.model()); QVERIFY(!available.projectSourcesModel()); QVERIFY(available.defaultProjectSource().isNull()); auto pagesView = available.findChild<QTreeView*>(QStringLiteral("pagesView")); QVERIFY(pagesView); QVERIFY(pagesView->isVisibleTo(&available)); QVERIFY(!pagesView->header()->isVisibleTo(&available)); QCOMPARE(pagesView->dragDropMode(), QTreeView::DropOnly); auto actionBar = available.findChild<QToolBar*>(QStringLiteral("actionBar")); QVERIFY(actionBar); QVERIFY(actionBar->isVisibleTo(&available)); auto addProjectAction = available.findChild<QAction*>(QStringLiteral("addProjectAction")); QVERIFY(addProjectAction); auto addContextAction = available.findChild<QAction*>(QStringLiteral("addContextAction")); QVERIFY(addContextAction); auto addTagAction = available.findChild<QAction*>(QStringLiteral("addTagAction")); QVERIFY(addTagAction); auto removeAction = available.findChild<QAction*>(QStringLiteral("removeAction")); QVERIFY(removeAction); auto goPreviousAction = available.findChild<QAction*>(QStringLiteral("goPreviousAction")); QVERIFY(goPreviousAction); auto goNextAction = available.findChild<QAction*>(QStringLiteral("goNextAction")); QVERIFY(goNextAction); auto goToAction = available.findChild<QAction*>(QStringLiteral("goToAction")); QVERIFY(goToAction); auto projectDialogFactory = available.projectDialogFactory(); QVERIFY(projectDialogFactory(&available).dynamicCast<Widgets::NewProjectDialog>()); auto quickSelectDialogFactory = available.quickSelectDialogFactory(); QVERIFY(quickSelectDialogFactory(&available).dynamicCast<Widgets::QuickSelectDialog>()); auto actions = available.globalActions(); QCOMPARE(actions.value(QStringLiteral("pages_project_add")), addProjectAction); QCOMPARE(actions.value(QStringLiteral("pages_context_add")), addContextAction); QCOMPARE(actions.value(QStringLiteral("pages_tag_add")), addTagAction); QCOMPARE(actions.value(QStringLiteral("pages_remove")), removeAction); QCOMPARE(actions.value(QStringLiteral("pages_go_previous")), goPreviousAction); QCOMPARE(actions.value(QStringLiteral("pages_go_next")), goNextAction); QCOMPARE(actions.value(QStringLiteral("pages_go_to")), goToAction); } void shouldShowOnlyAddActionsNeededByTheModel_data() { QTest::addColumn<bool>("hasProjects"); QTest::addColumn<bool>("hasContexts"); QTest::addColumn<bool>("hasTags"); QTest::newRow("!projects !contexts !tags") << false << false << false; QTest::newRow("!projects !contexts tags") << false << false << true; QTest::newRow("!projects contexts !tags") << false << true << false; QTest::newRow("!projects contexts tags") << false << true << true; QTest::newRow("projects !contexts !tags") << true << false << false; QTest::newRow("projects !contexts tags") << true << false << true; QTest::newRow("projects contexts !tags") << true << true << false; QTest::newRow("projects contexts tags") << true << true << true; } void shouldShowOnlyAddActionsNeededByTheModel() { // GIVEN QFETCH(bool, hasProjects); QFETCH(bool, hasContexts); QFETCH(bool, hasTags); AvailablePagesModelStub stubPagesModel; stubPagesModel.setProperty("hasProjectPages", hasProjects); stubPagesModel.setProperty("hasContextPages", hasContexts); stubPagesModel.setProperty("hasTagPages", hasTags); Widgets::AvailablePagesView available; auto addProjectAction = available.findChild<QAction*>(QStringLiteral("addProjectAction")); QVERIFY(addProjectAction); auto addContextAction = available.findChild<QAction*>(QStringLiteral("addContextAction")); QVERIFY(addContextAction); auto addTagAction = available.findChild<QAction*>(QStringLiteral("addTagAction")); QVERIFY(addTagAction); // WHEN available.setModel(&stubPagesModel); // THEN QCOMPARE(addProjectAction->isVisible(), hasProjects); QCOMPARE(addContextAction->isVisible(), hasContexts); QCOMPARE(addTagAction->isVisible(), hasTags); } void shouldDisplayListFromPageModel() { // GIVEN QStringListModel model(QStringList() << QStringLiteral("A") << QStringLiteral("B") << QStringLiteral("C") ); AvailablePagesModelStub stubPagesModel; stubPagesModel.setProperty("pageListModel", QVariant::fromValue(static_cast<QAbstractItemModel*>(&model))); Widgets::AvailablePagesView available; auto pagesView = available.findChild<QTreeView*>(QStringLiteral("pagesView")); QVERIFY(pagesView); QVERIFY(!pagesView->model()); // WHEN available.setModel(&stubPagesModel); QTest::qWait(10); // THEN QCOMPARE(pagesView->model(), &model); QCOMPARE(pagesView->selectionModel()->currentIndex(), model.index(0, 0)); } void shouldNotCrashWithNullModel() { // GIVEN QStringListModel model(QStringList() << QStringLiteral("A") << QStringLiteral("B") << QStringLiteral("C") ); AvailablePagesModelStub stubPagesModel; stubPagesModel.setProperty("pageListModel", QVariant::fromValue(static_cast<QAbstractItemModel*>(&model))); Widgets::AvailablePagesView available; available.setModel(&stubPagesModel); QTest::qWait(10); auto pagesView = available.findChild<QTreeView*>(QStringLiteral("pagesView")); QVERIFY(pagesView); QCOMPARE(pagesView->model(), &model); // WHEN available.setModel(Q_NULLPTR); QTest::qWait(10); // THEN QVERIFY(!available.isEnabled()); QVERIFY(!pagesView->model()); } void shouldAddNewProjects() { // GIVEN AvailablePagesModelStub model; QStringListModel sourceModel; auto dialogStub = NewProjectDialogStub::Ptr::create(); auto source = Domain::DataSource::Ptr::create(); Widgets::AvailablePagesView available; available.setModel(&model); available.setProjectSourcesModel(&sourceModel); available.setDefaultProjectSource(source); available.setProjectDialogFactory([dialogStub] (QWidget *parent) { dialogStub->parent = parent; return dialogStub; }); auto addProjectAction = available.findChild<QAction*>(QStringLiteral("addProjectAction")); // WHEN addProjectAction->trigger(); // THEN QCOMPARE(dialogStub->execCount, 1); QCOMPARE(dialogStub->parent, &available); QCOMPARE(dialogStub->sourceModel, &sourceModel); QCOMPARE(model.projectNames.size(), 1); QCOMPARE(model.projectNames.first(), dialogStub->name()); QCOMPARE(model.sources.size(), 1); QCOMPARE(model.sources.first(), dialogStub->dataSource()); QCOMPARE(available.defaultProjectSource(), dialogStub->dataSource()); } void shouldAddNewContexts() { // GIVEN AvailablePagesModelStub model; QStringListModel sourceModel; auto dialogStub = NewProjectDialogStub::Ptr::create(); auto source = Domain::DataSource::Ptr::create(); auto msgBoxStub = MessageBoxStub::Ptr::create(); msgBoxStub->setTextInput(QStringLiteral("Foo")); Widgets::AvailablePagesView available; available.setModel(&model); available.setProjectSourcesModel(&sourceModel); available.setDefaultProjectSource(source); available.setMessageBoxInterface(msgBoxStub); auto addContextAction = available.findChild<QAction*>(QStringLiteral("addContextAction")); // WHEN addContextAction->trigger(); // THEN QVERIFY(msgBoxStub->called()); QCOMPARE(model.contextNames.size(), 1); QCOMPARE(model.contextNames.first(), QStringLiteral("Foo")); } void shouldAddNewTags() { // GIVEN AvailablePagesModelStub model; QStringListModel sourceModel; auto dialogStub = NewProjectDialogStub::Ptr::create(); auto source = Domain::DataSource::Ptr::create(); auto msgBoxStub = MessageBoxStub::Ptr::create(); msgBoxStub->setTextInput(QStringLiteral("Foo")); Widgets::AvailablePagesView available; available.setModel(&model); available.setProjectSourcesModel(&sourceModel); available.setDefaultProjectSource(source); available.setMessageBoxInterface(msgBoxStub); auto addTagAction = available.findChild<QAction*>(QStringLiteral("addTagAction")); // WHEN addTagAction->trigger(); // THEN QVERIFY(msgBoxStub->called()); QCOMPARE(model.tagNames.size(), 1); QCOMPARE(model.tagNames.first(), QStringLiteral("Foo")); } void shouldRemoveAPage_data() { QTest::addColumn<QObjectPtr>("object"); QTest::addColumn<bool>("actionEnabled"); auto project1 = Domain::Project::Ptr::create(); project1->setName(QStringLiteral("Project 1")); QTest::newRow("project") << QObjectPtr(project1) << true; auto context1 = Domain::Context::Ptr::create(); context1->setName(QStringLiteral("Context 1")); QTest::newRow("context") << QObjectPtr(context1) << true; - auto tag1 = Domain::Tag::Ptr::create(); - tag1->setName(QStringLiteral("Tag 1")); - QTest::newRow("tag") << QObjectPtr(tag1) << true; - QTest::newRow("non removable") << QObjectPtr::create() << false; } void shouldRemoveAPage() { QFETCH(QObjectPtr, object); QFETCH(bool, actionEnabled); // GIVEN QStringList list; list << QStringLiteral("A") << QStringLiteral("B") << QStringLiteral("C"); QStandardItemModel model; for (int row = 0; row < list.count(); ++row) { model.setItem(row, new QStandardItem(list.at(row))); } QVERIFY(model.setData(model.index(0, 0), QVariant::fromValue(object), Presentation::QueryTreeModelBase::ObjectRole)); AvailablePagesModelStub stubPagesModel; stubPagesModel.setProperty("pageListModel", QVariant::fromValue(static_cast<QAbstractItemModel*>(&model))); Widgets::AvailablePagesView available; auto pagesView = available.findChild<QTreeView*>(QStringLiteral("pagesView")); QVERIFY(pagesView); QVERIFY(!pagesView->model()); available.setModel(&stubPagesModel); QTest::qWait(10); auto removeAction = available.findChild<QAction*>(QStringLiteral("removeAction")); auto msgbox = MessageBoxStub::Ptr::create(); available.setMessageBoxInterface(msgbox); // WHEN if (actionEnabled) removeAction->trigger(); // THEN QCOMPARE(removeAction->isEnabled(), actionEnabled); if (actionEnabled) { QCOMPARE(stubPagesModel.projectRemoved, list.first()); } } void shouldGoToPreviousSelectablePage() { // GIVEN QStandardItemModel model; model.appendRow(new QStandardItem(QStringLiteral("Inbox"))); auto projects = new QStandardItem(QStringLiteral("Projects")); projects->setFlags(Qt::NoItemFlags); model.appendRow(projects); projects->appendRow(new QStandardItem(QStringLiteral("Project 1"))); projects->appendRow(new QStandardItem(QStringLiteral("Project 2"))); AvailablePagesModelStub stubPagesModel; stubPagesModel.setProperty("pageListModel", QVariant::fromValue(static_cast<QAbstractItemModel*>(&model))); Widgets::AvailablePagesView available; auto pagesView = available.findChild<QTreeView*>(QStringLiteral("pagesView")); QVERIFY(pagesView); QVERIFY(!pagesView->model()); available.setModel(&stubPagesModel); QTest::qWait(10); auto goPreviousAction = available.findChild<QAction*>(QStringLiteral("goPreviousAction")); pagesView->setCurrentIndex(model.index(1, 0, model.indexFromItem(projects))); // WHEN goPreviousAction->trigger(); // THEN QCOMPARE(pagesView->currentIndex(), model.index(0, 0, model.indexFromItem(projects))); // WHEN goPreviousAction->trigger(); // THEN QCOMPARE(pagesView->currentIndex(), model.index(0, 0)); // WHEN goPreviousAction->trigger(); // THEN QCOMPARE(pagesView->currentIndex(), model.index(0, 0)); } void shouldGoToNextSelectablePage() { // GIVEN QStandardItemModel model; model.appendRow(new QStandardItem(QStringLiteral("Inbox"))); auto projects = new QStandardItem(QStringLiteral("Projects")); projects->setFlags(Qt::NoItemFlags); model.appendRow(projects); projects->appendRow(new QStandardItem(QStringLiteral("Project 1"))); projects->appendRow(new QStandardItem(QStringLiteral("Project 2"))); AvailablePagesModelStub stubPagesModel; stubPagesModel.setProperty("pageListModel", QVariant::fromValue(static_cast<QAbstractItemModel*>(&model))); Widgets::AvailablePagesView available; auto pagesView = available.findChild<QTreeView*>(QStringLiteral("pagesView")); QVERIFY(pagesView); QVERIFY(!pagesView->model()); available.setModel(&stubPagesModel); QTest::qWait(10); auto goNextAction = available.findChild<QAction*>(QStringLiteral("goNextAction")); pagesView->setCurrentIndex(model.index(0, 0)); // WHEN goNextAction->trigger(); // THEN QCOMPARE(pagesView->currentIndex(), model.index(0, 0, model.indexFromItem(projects))); // WHEN goNextAction->trigger(); // THEN QCOMPARE(pagesView->currentIndex(), model.index(1, 0, model.indexFromItem(projects))); // WHEN goNextAction->trigger(); // THEN QCOMPARE(pagesView->currentIndex(), model.index(1, 0, model.indexFromItem(projects))); } void shouldGoToUserSelectedIndex() { // GIVEN QStandardItemModel model; model.appendRow(new QStandardItem(QStringLiteral("Inbox"))); auto projects = new QStandardItem(QStringLiteral("Projects")); projects->setFlags(Qt::NoItemFlags); model.appendRow(projects); projects->appendRow(new QStandardItem(QStringLiteral("Project 1"))); projects->appendRow(new QStandardItem(QStringLiteral("Project 2"))); AvailablePagesModelStub stubPagesModel; stubPagesModel.setProperty("pageListModel", QVariant::fromValue(static_cast<QAbstractItemModel*>(&model))); auto dialogStub = QuickSelectDialogStub::Ptr::create(); // Project 2 will be selected dialogStub->index = model.index(1, 0, model.index(1, 0)); Widgets::AvailablePagesView available; available.setModel(&stubPagesModel); available.setQuickSelectDialogFactory([dialogStub] (QWidget *parent) { dialogStub->parent = parent; return dialogStub; }); auto pagesView = available.findChild<QTreeView*>(QStringLiteral("pagesView")); QVERIFY(pagesView); QCOMPARE(pagesView->model(), &model); auto goToAction = available.findChild<QAction*>(QStringLiteral("goToAction")); // WHEN goToAction->trigger(); // THEN QCOMPARE(dialogStub->execCount, 1); QCOMPARE(dialogStub->parent, &available); QCOMPARE(dialogStub->itemModel, &model); QCOMPARE(QPersistentModelIndex(pagesView->currentIndex()), dialogStub->index); } }; ZANSHIN_TEST_MAIN(AvailablePagesViewTest) #include "availablepagesviewtest.moc" diff --git a/tests/units/widgets/editorviewtest.cpp b/tests/units/widgets/editorviewtest.cpp index 11f4da0a..d667cdf8 100644 --- a/tests/units/widgets/editorviewtest.cpp +++ b/tests/units/widgets/editorviewtest.cpp @@ -1,719 +1,718 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens <ervin@kde.org> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include <testlib/qtest_gui_zanshin.h> #include <QAbstractButton> #include <QLabel> #include <QListView> #include <QPlainTextEdit> #include <QStandardItemModel> #include <QToolButton> #include <KLocalizedString> -#include "domain/note.h" #include "domain/task.h" #include "widgets/editorview.h" #include "kdateedit.h" class EditorModelStub : public QObject { Q_OBJECT public: EditorModelStub() { setProperty("editingInProgress", false); setProperty("attachmentModel", QVariant::fromValue(&attachmentModel)); } void setPropertyAndSignal(const QByteArray &name, const QVariant &value) { if (property(name) == value) return; if (property("editingInProgress").toBool()) return; setProperty(name, value); if (name == "artifact") emit artifactChanged(value.value<Domain::Artifact::Ptr>()); else if (name == "text") emit textChanged(value.toString()); else if (name == "title") emit titleChanged(value.toString()); else if (name == "done") emit doneChanged(value.toBool()); else if (name == "startDate") emit startDateChanged(value.toDate()); else if (name == "dueDate") emit dueDateChanged(value.toDate()); else if (name == "recurrence") emit recurrenceChanged(value.value<Domain::Task::Recurrence>()); else if (name == "hasTaskProperties") emit hasTaskPropertiesChanged(value.toBool()); else qFatal("Unsupported property %s", name.constData()); } public slots: void setArtifact(const Domain::Artifact::Ptr &artifact) { setPropertyAndSignal("artifact", QVariant::fromValue(artifact)); } void setTitle(const QString &title) { setPropertyAndSignal("title", title); } void setText(const QString &text) { setPropertyAndSignal("text", text); } void setDone(bool done) { setPropertyAndSignal("done", done); } void setStartDate(const QDate &start) { setPropertyAndSignal("startDate", start); } void setDueDate(const QDate &due) { setPropertyAndSignal("dueDate", due); } void setRecurrence(Domain::Task::Recurrence recurrence) { setPropertyAndSignal("recurrence", QVariant::fromValue(recurrence)); } void makeTaskAvailable() { setArtifact(Domain::Artifact::Ptr(new Domain::Task)); } void addAttachment(const QString &fileName) { auto item = new QStandardItem(fileName); attachmentModel.appendRow(QList<QStandardItem*>() << item); } void removeAttachment(const QModelIndex &index) { if (index.isValid()) attachmentModel.removeRows(index.row(), 1, QModelIndex()); } signals: void artifactChanged(const Domain::Artifact::Ptr &artifact); void hasTaskPropertiesChanged(bool hasTaskProperties); void textChanged(const QString &text); void titleChanged(const QString &title); void doneChanged(bool done); void startDateChanged(const QDate &date); void dueDateChanged(const QDate &due); void recurrenceChanged(Domain::Task::Recurrence recurrence); public: QStandardItemModel attachmentModel; }; class EditorViewTest : public QObject { Q_OBJECT public: explicit EditorViewTest(QObject *parent = Q_NULLPTR) : QObject(parent) { qputenv("ZANSHIN_UNIT_TEST_RUN", "1"); } private slots: void shouldHaveDefaultState() { Widgets::EditorView editor; QVERIFY(!editor.isEnabled()); auto textEdit = editor.findChild<QPlainTextEdit*>(QStringLiteral("textEdit")); QVERIFY(textEdit); QVERIFY(textEdit->isVisibleTo(&editor)); auto startDateEdit = editor.findChild<KPIM::KDateEdit*>(QStringLiteral("startDateEdit")); QVERIFY(startDateEdit); QVERIFY(!startDateEdit->isVisibleTo(&editor)); auto dueDateEdit = editor.findChild<KPIM::KDateEdit*>(QStringLiteral("dueDateEdit")); QVERIFY(dueDateEdit); QVERIFY(!dueDateEdit->isVisibleTo(&editor)); auto recurrenceCombo = editor.findChild<QComboBox*>(QStringLiteral("recurrenceCombo")); QVERIFY(recurrenceCombo); QVERIFY(!recurrenceCombo->isVisibleTo(&editor)); auto doneButton = editor.findChild<QAbstractButton*>(QStringLiteral("doneButton")); QVERIFY(doneButton); QVERIFY(!doneButton->isVisibleTo(&editor)); auto attachmentList = editor.findChild<QListView*>(QStringLiteral("attachmentList")); QVERIFY(attachmentList); QVERIFY(!attachmentList->isVisibleTo(&editor)); auto addAttachmentButton = editor.findChild<QToolButton*>(QStringLiteral("addAttachmentButton")); QVERIFY(addAttachmentButton); QVERIFY(!addAttachmentButton->isVisibleTo(&editor)); auto removeAttachmentButton = editor.findChild<QToolButton*>(QStringLiteral("removeAttachmentButton")); QVERIFY(removeAttachmentButton); QVERIFY(!removeAttachmentButton->isVisibleTo(&editor)); } void shouldNotCrashForNullModel() { // GIVEN Widgets::EditorView editor; EditorModelStub model; model.setTitle(QStringLiteral("Foo")); model.setText(QStringLiteral("Bar")); model.setPropertyAndSignal("hasTaskProperties", true); editor.setModel(&model); auto textEdit = editor.findChild<QPlainTextEdit*>(QStringLiteral("textEdit")); QVERIFY(textEdit); auto startDateEdit = editor.findChild<KPIM::KDateEdit*>(QStringLiteral("startDateEdit")); QVERIFY(startDateEdit); auto dueDateEdit = editor.findChild<KPIM::KDateEdit*>(QStringLiteral("dueDateEdit")); QVERIFY(dueDateEdit); auto recurrenceCombo = editor.findChild<QComboBox*>(QStringLiteral("recurrenceCombo")); QVERIFY(recurrenceCombo); auto doneButton = editor.findChild<QAbstractButton*>(QStringLiteral("doneButton")); QVERIFY(doneButton); auto attachmentList = editor.findChild<QListView*>(QStringLiteral("attachmentList")); QVERIFY(attachmentList); QCOMPARE(attachmentList->model(), &model.attachmentModel); auto addAttachmentButton = editor.findChild<QToolButton*>(QStringLiteral("addAttachmentButton")); QVERIFY(addAttachmentButton); auto removeAttachmentButton = editor.findChild<QToolButton*>(QStringLiteral("removeAttachmentButton")); QVERIFY(removeAttachmentButton); // WHEN editor.setModel(Q_NULLPTR); // THEN QVERIFY(!editor.isEnabled()); QVERIFY(textEdit->toPlainText().isEmpty()); QVERIFY(!startDateEdit->isVisibleTo(&editor)); QVERIFY(!dueDateEdit->isVisibleTo(&editor)); QVERIFY(!recurrenceCombo->isVisibleTo(&editor)); QVERIFY(!doneButton->isVisibleTo(&editor)); QVERIFY(!attachmentList->isVisibleTo(&editor)); QVERIFY(attachmentList->model() == nullptr); QVERIFY(!addAttachmentButton->isVisibleTo(&editor)); QVERIFY(!removeAttachmentButton->isVisibleTo(&editor)); } void shouldShowTaskPropertiesEditorsOnlyForTasks() { // GIVEN Widgets::EditorView editor; EditorModelStub model; model.setPropertyAndSignal("hasTaskProperties", true); auto startDateEdit = editor.findChild<KPIM::KDateEdit*>(QStringLiteral("startDateEdit")); QVERIFY(!startDateEdit->isVisibleTo(&editor)); auto dueDateEdit = editor.findChild<KPIM::KDateEdit*>(QStringLiteral("dueDateEdit")); QVERIFY(!dueDateEdit->isVisibleTo(&editor)); auto recurrenceCombo = editor.findChild<QComboBox*>(QStringLiteral("recurrenceCombo")); QVERIFY(recurrenceCombo); auto doneButton = editor.findChild<QAbstractButton*>(QStringLiteral("doneButton")); QVERIFY(!doneButton->isVisibleTo(&editor)); auto attachmentList = editor.findChild<QListView*>(QStringLiteral("attachmentList")); QVERIFY(attachmentList); auto addAttachmentButton = editor.findChild<QToolButton*>(QStringLiteral("addAttachmentButton")); QVERIFY(addAttachmentButton); auto removeAttachmentButton = editor.findChild<QToolButton*>(QStringLiteral("removeAttachmentButton")); QVERIFY(removeAttachmentButton); // WHEN editor.setModel(&model); // THEN QVERIFY(startDateEdit->isVisibleTo(&editor)); QVERIFY(dueDateEdit->isVisibleTo(&editor)); QVERIFY(recurrenceCombo->isVisibleTo(&editor)); QVERIFY(doneButton->isVisibleTo(&editor)); QVERIFY(attachmentList->isVisibleTo(&editor)); QVERIFY(addAttachmentButton->isVisibleTo(&editor)); QVERIFY(removeAttachmentButton->isVisibleTo(&editor)); } void shouldBeEnabledOnlyWhenAnArtifactIsAvailable() { // GIVEN Widgets::EditorView editor; EditorModelStub model; // WHEN editor.setModel(&model); // THEN QVERIFY(!editor.isEnabled()); // WHEN // like model.makeTaskAvailable() does: Domain::Artifact::Ptr artifact(new Domain::Task); model.setPropertyAndSignal("artifact", QVariant::fromValue(artifact)); // THEN QVERIFY(editor.isEnabled()); // WHEN model.setPropertyAndSignal("artifact", QVariant::fromValue(Domain::Artifact::Ptr())); // THEN QVERIFY(!editor.isEnabled()); // GIVEN EditorModelStub model2; model2.setPropertyAndSignal("artifact", QVariant::fromValue(artifact)); // WHEN editor.setModel(&model2); // THEN QVERIFY(editor.isEnabled()); } void shouldReactToHasTaskPropertiesChanged() { // GIVEN Widgets::EditorView editor; EditorModelStub model; model.makeTaskAvailable(); editor.setModel(&model); auto startDateEdit = editor.findChild<KPIM::KDateEdit*>(QStringLiteral("startDateEdit")); QVERIFY(!startDateEdit->isVisibleTo(&editor)); auto dueDateEdit = editor.findChild<KPIM::KDateEdit*>(QStringLiteral("dueDateEdit")); QVERIFY(!dueDateEdit->isVisibleTo(&editor)); auto doneButton = editor.findChild<QAbstractButton*>(QStringLiteral("doneButton")); QVERIFY(!doneButton->isVisibleTo(&editor)); // WHEN model.setPropertyAndSignal("hasTaskProperties", true); // THEN QVERIFY(startDateEdit->isVisibleTo(&editor)); QVERIFY(dueDateEdit->isVisibleTo(&editor)); QVERIFY(doneButton->isVisibleTo(&editor)); } void shouldDisplayModelProperties() { // GIVEN Widgets::EditorView editor; EditorModelStub model; model.makeTaskAvailable(); model.setProperty("hasTaskProperties", true); model.setProperty("title", "My title"); model.setProperty("text", "\nMy text"); model.setProperty("startDate", QDate::currentDate()); model.setProperty("dueDate", QDate::currentDate().addDays(2)); model.setProperty("recurrence", QVariant::fromValue(Domain::Task::RecursWeekly)); model.setProperty("done", true); auto textEdit = editor.findChild<QPlainTextEdit*>(QStringLiteral("textEdit")); auto startDateEdit = editor.findChild<KPIM::KDateEdit*>(QStringLiteral("startDateEdit")); auto dueDateEdit = editor.findChild<KPIM::KDateEdit*>(QStringLiteral("dueDateEdit")); auto recurrenceCombo = editor.findChild<QComboBox*>(QStringLiteral("recurrenceCombo")); auto doneButton = editor.findChild<QAbstractButton*>(QStringLiteral("doneButton")); // WHEN editor.setModel(&model); // THEN QCOMPARE(textEdit->toPlainText(), QString(model.property("title").toString() + '\n' + model.property("text").toString())); QCOMPARE(startDateEdit->date(), model.property("startDate").toDate()); QCOMPARE(dueDateEdit->date(), model.property("dueDate").toDate()); QCOMPARE(recurrenceCombo->currentData().value<Domain::Task::Recurrence>(), model.property("recurrence").value<Domain::Task::Recurrence>()); QCOMPARE(doneButton->isChecked(), model.property("done").toBool()); } void shouldNotReactToChangesWhileEditing() { // GIVEN Widgets::EditorView editor; EditorModelStub model; model.makeTaskAvailable(); model.setProperty("title", "My title"); model.setProperty("text", "\nMy text"); model.setProperty("startDate", QDate::currentDate()); model.setProperty("dueDate", QDate::currentDate().addDays(2)); model.setProperty("recurrence", QVariant::fromValue(Domain::Task::RecursWeekly)); model.setProperty("done", true); editor.setModel(&model); auto textEdit = editor.findChild<QPlainTextEdit*>(QStringLiteral("textEdit")); auto startDateEdit = editor.findChild<KPIM::KDateEdit*>(QStringLiteral("startDateEdit")); auto dueDateEdit = editor.findChild<KPIM::KDateEdit*>(QStringLiteral("dueDateEdit")); auto recurrenceCombo = editor.findChild<QComboBox*>(QStringLiteral("recurrenceCombo")); auto doneButton = editor.findChild<QAbstractButton*>(QStringLiteral("doneButton")); editor.setModel(&model); // WHEN editor.show(); QVERIFY(QTest::qWaitForWindowShown(&editor)); editor.activateWindow(); textEdit->setFocus(); model.setTitle("New title"); model.setText("New text"); startDateEdit->setFocus(); model.setStartDate(QDate::currentDate().addDays(1)); dueDateEdit->setFocus(); model.setDueDate(QDate::currentDate().addDays(3)); recurrenceCombo->setFocus(); model.setRecurrence(Domain::Task::RecursDaily); doneButton->setFocus(); model.setDone(false); // THEN (nothing changed) QCOMPARE(textEdit->toPlainText(), QStringLiteral("My title\n\nMy text")); QCOMPARE(startDateEdit->date(), QDate::currentDate()); QCOMPARE(dueDateEdit->date(), QDate::currentDate().addDays(2)); QCOMPARE(recurrenceCombo->currentData().value<Domain::Task::Recurrence>(), Domain::Task::RecursWeekly); QVERIFY(doneButton->isChecked()); } void shouldReactToTitleChanges() { // GIVEN Widgets::EditorView editor; EditorModelStub model; model.makeTaskAvailable(); model.setProperty("title", "My title"); model.setProperty("text", "\nMy text"); model.setProperty("startDate", QDate::currentDate()); model.setProperty("dueDate", QDate::currentDate().addDays(2)); model.setProperty("done", true); editor.setModel(&model); auto textEdit = editor.findChild<QPlainTextEdit*>(QStringLiteral("textEdit")); // WHEN model.setPropertyAndSignal("title", "New title"); // THEN QCOMPARE(textEdit->toPlainText(), QString(model.property("title").toString() + '\n' + model.property("text").toString())); } void shouldReactToTextChanges() { // GIVEN Widgets::EditorView editor; EditorModelStub model; model.makeTaskAvailable(); model.setProperty("title", "My title"); model.setProperty("text", "\nMy text"); model.setProperty("startDate", QDate::currentDate()); model.setProperty("dueDate", QDate::currentDate().addDays(2)); model.setProperty("done", true); editor.setModel(&model); auto textEdit = editor.findChild<QPlainTextEdit*>(QStringLiteral("textEdit")); // WHEN model.setPropertyAndSignal("text", "\nNew text"); // THEN QCOMPARE(textEdit->toPlainText(), QString(model.property("title").toString() + '\n' + model.property("text").toString())); } void shouldApplyTextEditChanges_data() { QTest::addColumn<QString>("plainText"); QTest::addColumn<QString>("expectedTitle"); QTest::addColumn<QString>("expectedText"); QTest::newRow("nominal case") << "Title\n\nText" << "Title" << "\nText"; QTest::newRow("single line") << "Title" << "Title" << ""; } void shouldApplyTextEditChanges() { // GIVEN Widgets::EditorView editor; EditorModelStub model; model.makeTaskAvailable(); editor.setModel(&model); auto textEdit = editor.findChild<QPlainTextEdit*>(QStringLiteral("textEdit")); // WHEN QFETCH(QString, plainText); textEdit->setPlainText(plainText); // THEN QFETCH(QString, expectedTitle); QCOMPARE(model.property("title").toString(), expectedTitle); QFETCH(QString, expectedText); QCOMPARE(model.property("text").toString(), expectedText); } void shouldReactToDoneChanges() { // GIVEN Widgets::EditorView editor; EditorModelStub model; model.makeTaskAvailable(); model.setProperty("title", "My title"); model.setProperty("text", "\nMy text"); model.setProperty("startDate", QDate::currentDate()); model.setProperty("dueDate", QDate::currentDate().addDays(2)); model.setProperty("done", false); editor.setModel(&model); auto doneButton = editor.findChild<QAbstractButton*>(QStringLiteral("doneButton")); QVERIFY(!doneButton->isChecked()); // WHEN model.setPropertyAndSignal("done", true); // THEN QVERIFY(doneButton->isChecked()); } void shouldApplyDoneButtonChanges() { // GIVEN Widgets::EditorView editor; EditorModelStub model; model.makeTaskAvailable(); editor.setModel(&model); auto doneButton = editor.findChild<QAbstractButton*>(QStringLiteral("doneButton")); // WHEN doneButton->setChecked(true); // THEN QCOMPARE(model.property("done").toBool(), true); } void shouldReactToStartDateChanges() { // GIVEN Widgets::EditorView editor; EditorModelStub model; model.makeTaskAvailable(); model.setProperty("title", "My title"); model.setProperty("text", "\nMy text"); model.setProperty("startDate", QDate::currentDate()); model.setProperty("dueDate", QDate::currentDate().addDays(2)); model.setProperty("done", false); editor.setModel(&model); auto startDateEdit = editor.findChild<KPIM::KDateEdit*>(QStringLiteral("startDateEdit")); // WHEN model.setPropertyAndSignal("startDate", QDate::currentDate().addDays(-2)); // THEN QCOMPARE(startDateEdit->date(), model.property("startDate").toDate()); } void shouldApplyStartDateEditChanges() { // GIVEN Widgets::EditorView editor; EditorModelStub model; model.makeTaskAvailable(); editor.setModel(&model); auto startDateEdit = editor.findChild<KPIM::KDateEdit*>(QStringLiteral("startDateEdit")); auto today = QDate::currentDate(); // WHEN startDateEdit->setEditText(today.toString(QStringLiteral("dd/MM/yyyy"))); QTest::keyClick(startDateEdit, Qt::Key_Enter); // THEN const QDate newStartDate = model.property("startDate").toDate(); QCOMPARE(newStartDate, today); } void shouldReactToDueDateChanges() { // GIVEN Widgets::EditorView editor; EditorModelStub model; model.setProperty("title", "My title"); model.setProperty("text", "\nMy text"); model.setProperty("startDate", QDate::currentDate()); model.setProperty("dueDate", QDate::currentDate().addDays(2)); model.setProperty("done", false); editor.setModel(&model); auto dueDateEdit = editor.findChild<KPIM::KDateEdit*>(QStringLiteral("dueDateEdit")); // WHEN model.setPropertyAndSignal("dueDate", QDate::currentDate().addDays(-2)); // THEN QCOMPARE(dueDateEdit->date(), model.property("dueDate").toDate()); } void shouldApplyDueDateEditChanges() { // GIVEN Widgets::EditorView editor; EditorModelStub model; model.makeTaskAvailable(); editor.setModel(&model); auto dueDateEdit = editor.findChild<KPIM::KDateEdit*>(QStringLiteral("dueDateEdit")); auto today = QDate::currentDate(); // WHEN QVERIFY(dueDateEdit->isEnabled()); dueDateEdit->setEditText(today.toString(QStringLiteral("dd/MM/yyyy"))); QTest::keyClick(dueDateEdit, Qt::Key_Enter); // THEN QCOMPARE(model.property("dueDate").toDate(), today); } void shouldApplyStartTodayChanges() { // GIVEN Widgets::EditorView editor; EditorModelStub model; model.makeTaskAvailable(); editor.setModel(&model); QAbstractButton *startTodayButton = editor.findChild<QAbstractButton *>(QStringLiteral("startTodayButton")); QVERIFY(startTodayButton); auto startDateEdit = editor.findChild<KPIM::KDateEdit*>(QStringLiteral("startDateEdit")); auto today = QDate::currentDate(); // WHEN QVERIFY(startTodayButton->isEnabled()); startTodayButton->click(); // THEN QCOMPARE(startDateEdit->currentText(), today.toString(QStringLiteral("dd/MM/yyyy"))); QCOMPARE(model.property("startDate").toDate(), today); } void shouldReactToRecurrenceChanges() { // GIVEN Widgets::EditorView editor; EditorModelStub model; model.makeTaskAvailable(); model.setProperty("title", "My title"); model.setProperty("text", "\nMy text"); model.setProperty("startDate", QDate::currentDate()); model.setProperty("dueDate", QDate::currentDate().addDays(2)); model.setProperty("recurrence", QVariant::fromValue(Domain::Task::RecursWeekly)); model.setProperty("done", false); editor.setModel(&model); auto recurrenceCombo = editor.findChild<QComboBox*>(QStringLiteral("recurrenceCombo")); // WHEN model.setPropertyAndSignal("recurrence", Domain::Task::RecursMonthly); // THEN QCOMPARE(recurrenceCombo->currentData().value<Domain::Task::Recurrence>(), model.property("recurrence").value<Domain::Task::Recurrence>()); } void shouldApplyRecurrenceComboChanges() { // GIVEN Widgets::EditorView editor; EditorModelStub model; model.makeTaskAvailable(); editor.setModel(&model); auto recurrenceCombo = editor.findChild<QComboBox*>(QStringLiteral("recurrenceCombo")); // WHEN recurrenceCombo->setCurrentIndex(2); // Weekly // THEN QCOMPARE(model.property("recurrence").value<Domain::Task::Recurrence>(), Domain::Task::RecursWeekly); } void shouldAddAttachments() { // GIVEN Widgets::EditorView editor; editor.setRequestFileNameFunction([](QWidget*) { return "/tmp/foobar"; }); EditorModelStub model; model.makeTaskAvailable(); editor.setModel(&model); auto addAttachmentButton = editor.findChild<QToolButton*>(QStringLiteral("addAttachmentButton")); // WHEN addAttachmentButton->click(); // THEN QCOMPARE(model.attachmentModel.rowCount(), 1); QCOMPARE(model.attachmentModel.data(model.attachmentModel.index(0, 0)).toString(), QStringLiteral("/tmp/foobar")); } void shouldRemoveAttachments() { // GIVEN Widgets::EditorView editor; EditorModelStub model; model.makeTaskAvailable(); model.addAttachment("/tmp/foo"); model.addAttachment("/tmp/bar"); editor.setModel(&model); auto attachmentList = editor.findChild<QListView*>(QStringLiteral("attachmentList")); auto removeAttachmentButton = editor.findChild<QToolButton*>(QStringLiteral("removeAttachmentButton")); // THEN QVERIFY(!removeAttachmentButton->isEnabled()); // WHEN attachmentList->selectionModel()->select(model.attachmentModel.index(0, 0), QItemSelectionModel::ClearAndSelect); // THEN QVERIFY(removeAttachmentButton->isEnabled()); // WHEN removeAttachmentButton->click(); // THEN QCOMPARE(model.attachmentModel.rowCount(), 1); QCOMPARE(model.attachmentModel.data(model.attachmentModel.index(0, 0)).toString(), QStringLiteral("/tmp/bar")); } }; ZANSHIN_TEST_MAIN(EditorViewTest) #include "editorviewtest.moc"