diff --git a/src/presentation/CMakeLists.txt b/src/presentation/CMakeLists.txt index 49fb7c0c..79f72cba 100644 --- a/src/presentation/CMakeLists.txt +++ b/src/presentation/CMakeLists.txt @@ -1,22 +1,23 @@ set(presentation_SRCS applicationmodel.cpp editormodel.cpp availablepagesmodel.cpp availablepagessortfilterproxymodel.cpp availablesourcesmodel.cpp contextpagemodel.cpp errorhandler.cpp errorhandlingmodelbase.cpp inboxpagemodel.cpp metatypes.cpp pagemodel.cpp projectpagemodel.cpp + alltaskspagemodel.cpp querytreemodelbase.cpp runningtaskmodelinterface.cpp runningtaskmodel.cpp taskfilterproxymodel.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/workdaypagemodel.cpp b/src/presentation/alltaskspagemodel.cpp similarity index 51% copy from src/presentation/workdaypagemodel.cpp copy to src/presentation/alltaskspagemodel.cpp index 8de32dc5..226296c9 100644 --- a/src/presentation/workdaypagemodel.cpp +++ b/src/presentation/alltaskspagemodel.cpp @@ -1,203 +1,152 @@ /* This file is part of Zanshin - Copyright 2015 Theo Vaucher + Copyright 2019 David Faure 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 "alltaskspagemodel.h" #include #include -#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) +AllTasksPageModel::AllTasksPageModel(const Domain::TaskQueries::Ptr &taskQueries, + const Domain::TaskRepository::Ptr &taskRepository, + QObject *parent) : PageModel(parent), m_taskQueries(taskQueries), m_taskRepository(taskRepository) { } -Domain::Task::Ptr WorkdayPageModel::addItem(const QString &title, const QModelIndex &parentIndex) +Domain::Task::Ptr AllTasksPageModel::addItem(const QString &title, const QModelIndex &parentIndex) { const auto parentData = parentIndex.data(QueryTreeModelBase::ObjectRole); const auto parentTask = parentData.value(); 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)); + installHandler(job, i18n("Cannot add task %1", title)); return task; } -void WorkdayPageModel::removeItem(const QModelIndex &index) +void AllTasksPageModel::removeItem(const QModelIndex &index) { QVariant data = index.data(QueryTreeModelBase::ObjectRole); auto task = data.value(); if (task) { const auto job = m_taskRepository->remove(task); - installHandler(job, i18n("Cannot remove task %1 from Workday", task->title())); + installHandler(job, i18n("Cannot remove task %1", task->title())); } } -void WorkdayPageModel::promoteItem(const QModelIndex &index) +void AllTasksPageModel::promoteItem(const QModelIndex &index) { QVariant data = index.data(QueryTreeModelBase::ObjectRole); auto task = data.value(); 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() +QAbstractItemModel *AllTasksPageModel::createCentralListModel() { auto query = [this](const Domain::Task::Ptr &task) -> Domain::QueryResultInterface::Ptr { if (!task) - return m_taskQueries->findWorkdayTopLevel(); + return m_taskQueries->findTopLevel(); else return m_taskQueries->findChildren(task); }; - auto flags = [](const Domain::Task::Ptr &task) { - const Qt::ItemFlags defaultFlags = Qt::ItemIsSelectable - | Qt::ItemIsEnabled - | Qt::ItemIsEditable - | Qt::ItemIsDragEnabled; - - return task ? (defaultFlags | Qt::ItemIsUserCheckable | Qt::ItemIsDropEnabled) : defaultFlags; + auto flags = [](const Domain::Task::Ptr &) { + return Qt::ItemIsSelectable + | Qt::ItemIsEnabled + | Qt::ItemIsEditable + | Qt::ItemIsDragEnabled + | Qt::ItemIsUserCheckable + | Qt::ItemIsDropEnabled; }; - struct AdditionalData - { - bool childTask = false; - Domain::QueryResult::Ptr projectQueryResult; - // later on we'll want the context query as well - }; - using AdditionalInfo = QSharedPointer; - - auto data = [](const Domain::Task::Ptr &task, int role, const AdditionalInfo &info) -> QVariant { - switch (role) { - case Qt::DisplayRole: - case Qt::EditRole: - return task->title(); - case Qt::CheckStateRole: - return task->isDone() ? Qt::Checked : Qt::Unchecked; - case Presentation::QueryTreeModelBase::AdditionalInfoRole: - if (!info || info->childTask) - return QString(); - if (info->projectQueryResult && !info->projectQueryResult->data().isEmpty()) { - Domain::Project::Ptr project = info->projectQueryResult->data().at(0); - return i18n("Project: %1", project->name()); - } - return i18n("Inbox"); // TODO add source name - default: - break; - } - return QVariant(); + auto data = [](const Domain::Task::Ptr &task, int role, const TaskExtraDataPtr &info) -> QVariant { + return dataForTaskWithProject(task, role, info); }; - auto fetchAdditionalInfo = [this](const QModelIndex &index, const Domain::Task::Ptr &task) -> AdditionalInfo { - AdditionalInfo info = AdditionalInfo::create(); - if (index.parent().isValid()) { // children are in the same collection as their parent, so the same project - info->childTask = true; - return info; - } - - info->projectQueryResult = m_taskQueries->findProject(task); - if (info->projectQueryResult) { - QPersistentModelIndex persistentIndex(index); - info->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 info; + auto fetchAdditionalInfo = [this](const QModelIndex &index, const Domain::Task::Ptr &task) { + return fetchTaskExtraData(m_taskQueries, index, task); }; auto setData = [this](const Domain::Task::Ptr &task, const QVariant &value, int role) { if (role != Qt::EditRole && role != Qt::CheckStateRole) { return false; } 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)); + installHandler(job, i18n("Cannot modify task %1", currentTitle)); return true; }; + auto drag = [](const Domain::Task::List &tasks) -> QMimeData* { + if (tasks.isEmpty()) + return nullptr; + + auto data = new QMimeData; + data->setData(QStringLiteral("application/x-zanshin-object"), "object"); + data->setProperty("objects", QVariant::fromValue(tasks)); + return data; + }; + auto drop = [this](const QMimeData *mimeData, Qt::DropAction, const Domain::Task::Ptr &parentTask) { if (!mimeData->hasFormat(QStringLiteral("application/x-zanshin-object"))) return false; auto droppedTasks = mimeData->property("objects").value(); if (droppedTasks.isEmpty()) return false; foreach(const auto &childTask, droppedTasks) { 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()); - // TODO something like m_taskRepository->update(childTask) is missing here - // It was removed in commit c97a99bf because it led to a LLCONFLICT in akonadi (due to dissociate below). - // The removal broke tests-features-workday-workdaydraganddropfeature (date not changed). - auto job = m_taskRepository->dissociate(childTask); installHandler(job, i18n("Cannot deparent task %1 from its parent", childTask->title())); } } return true; }; - auto drag = [](const Domain::Task::List &tasks) -> QMimeData* { - if (tasks.isEmpty()) - return nullptr; - - auto data = new QMimeData; - data->setData(QStringLiteral("application/x-zanshin-object"), "object"); - data->setProperty("objects", QVariant::fromValue(tasks)); - return data; - }; - return new QueryTreeModel(query, flags, data, setData, drop, drag, fetchAdditionalInfo, this); + return new QueryTreeModel(query, flags, data, setData, drop, drag, fetchAdditionalInfo, this); } diff --git a/src/presentation/pagemodel.h b/src/presentation/alltaskspagemodel.h similarity index 52% copy from src/presentation/pagemodel.h copy to src/presentation/alltaskspagemodel.h index 1eb5eaeb..4cbd6cb2 100644 --- a/src/presentation/pagemodel.h +++ b/src/presentation/alltaskspagemodel.h @@ -1,61 +1,56 @@ /* This file is part of Zanshin - Copyright 2014 Kevin Ottens + Copyright 2019 David Faure 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_PAGEMODEL_H -#define PRESENTATION_PAGEMODEL_H +#ifndef PRESENTATION_ALLTASKSPAGEMODEL_H +#define PRESENTATION_ALLTASKSPAGEMODEL_H -#include +#include "presentation/pagemodel.h" -#include - -#include "domain/task.h" - -#include "presentation/metatypes.h" -#include "presentation/errorhandlingmodelbase.h" +#include "domain/taskqueries.h" +#include "domain/taskrepository.h" namespace Presentation { -class PageModel : public QObject, public ErrorHandlingModelBase +class AllTasksPageModel : public PageModel { Q_OBJECT - Q_PROPERTY(QAbstractItemModel* centralListModel READ centralListModel) public: - explicit PageModel(QObject *parent = nullptr); - - QAbstractItemModel *centralListModel(); + explicit AllTasksPageModel(const Domain::TaskQueries::Ptr &taskQueries, + const Domain::TaskRepository::Ptr &taskRepository, + QObject *parent = nullptr); -public slots: - virtual Domain::Task::Ptr addItem(const QString &title, const QModelIndex &parentIndex = QModelIndex()) = 0; - virtual void removeItem(const QModelIndex &index) = 0; - virtual void promoteItem(const QModelIndex &index) = 0; + Domain::Task::Ptr addItem(const QString &title, const QModelIndex &parentIndex = QModelIndex()) override; + void removeItem(const QModelIndex &index) override; + void promoteItem(const QModelIndex &index) override; private: - virtual QAbstractItemModel *createCentralListModel() = 0; + QAbstractItemModel *createCentralListModel() override; - QAbstractItemModel *m_centralListModel; + Domain::TaskQueries::Ptr m_taskQueries; + Domain::TaskRepository::Ptr m_taskRepository; }; } -#endif // PRESENTATION_PAGEMODEL_H +#endif // PRESENTATION_ALLTASKSPAGEMODEL_H diff --git a/src/presentation/availablepagesmodel.cpp b/src/presentation/availablepagesmodel.cpp index ce612b78..43a1d37a 100644 --- a/src/presentation/availablepagesmodel.cpp +++ b/src/presentation/availablepagesmodel.cpp @@ -1,317 +1,333 @@ /* 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 "availablepagesmodel.h" #include #include #include #include "domain/contextqueries.h" #include "domain/contextrepository.h" #include "domain/projectqueries.h" #include "domain/projectrepository.h" #include "domain/taskrepository.h" +#include "presentation/alltaskspagemodel.h" #include "presentation/availablepagessortfilterproxymodel.h" #include "presentation/contextpagemodel.h" #include "presentation/inboxpagemodel.h" #include "presentation/metatypes.h" #include "presentation/projectpagemodel.h" #include "presentation/querytreemodel.h" #include "presentation/workdaypagemodel.h" #include "utils/jobhandler.h" #include "utils/datetime.h" using namespace Presentation; AvailablePagesModel::AvailablePagesModel(const Domain::DataSourceQueries::Ptr &dataSourceQueries, const Domain::ProjectQueries::Ptr &projectQueries, const Domain::ProjectRepository::Ptr &projectRepository, const Domain::ContextQueries::Ptr &contextQueries, const Domain::ContextRepository::Ptr &contextRepository, const Domain::TaskQueries::Ptr &taskQueries, const Domain::TaskRepository::Ptr &taskRepository, QObject *parent) : QObject(parent), m_pageListModel(nullptr), m_sortProxyModel(nullptr), m_dataSourceQueries(dataSourceQueries), m_projectQueries(projectQueries), m_projectRepository(projectRepository), m_contextQueries(contextQueries), m_contextRepository(contextRepository), m_taskQueries(taskQueries), m_taskRepository(taskRepository) { } QAbstractItemModel *AvailablePagesModel::pageListModel() { if (!m_pageListModel) m_pageListModel = createPageListModel(); if (!m_sortProxyModel) { m_sortProxyModel = new AvailablePagesSortFilterProxyModel(this); m_sortProxyModel->setSourceModel(m_pageListModel); } return m_sortProxyModel; } QObject *AvailablePagesModel::createPageForIndex(const QModelIndex &index) { QObjectPtr object = index.data(QueryTreeModelBase::ObjectRole).value(); if (object == m_inboxObject) { auto inboxPageModel = new InboxPageModel(m_taskQueries, m_taskRepository, this); inboxPageModel->setErrorHandler(errorHandler()); return inboxPageModel; } else if (object == m_workdayObject) { auto workdayPageModel = new WorkdayPageModel(m_taskQueries, m_taskRepository, this); workdayPageModel->setErrorHandler(errorHandler()); return workdayPageModel; } else if (auto project = object.objectCast()) { auto projectPageModel = new ProjectPageModel(project, m_projectQueries, m_projectRepository, m_taskQueries, m_taskRepository, this); projectPageModel->setErrorHandler(errorHandler()); return projectPageModel; } else if (auto context = object.objectCast()) { auto contextPageModel = new ContextPageModel(context, m_contextQueries, m_contextRepository, m_taskQueries, m_taskRepository, this); contextPageModel->setErrorHandler(errorHandler()); return contextPageModel; + } else if (object == m_allTasksObject) { + auto allTasksPageModel = new AllTasksPageModel(m_taskQueries, + m_taskRepository, + this); + allTasksPageModel->setErrorHandler(errorHandler()); + return allTasksPageModel; } return nullptr; } void AvailablePagesModel::addProject(const QString &name, const Domain::DataSource::Ptr &source) { auto project = Domain::Project::Ptr::create(); project->setName(name); const auto job = m_projectRepository->create(project, source); installHandler(job, i18n("Cannot add project %1 in dataSource %2", name, source->name())); } void AvailablePagesModel::addContext(const QString &name, const Domain::DataSource::Ptr &source) { auto context = Domain::Context::Ptr::create(); context->setName(name); const auto job = m_contextRepository->create(context, source); installHandler(job, i18n("Cannot add context %1", name)); } void AvailablePagesModel::removeItem(const QModelIndex &index) { QObjectPtr object = index.data(QueryTreeModelBase::ObjectRole).value(); if (auto project = object.objectCast()) { const auto job = m_projectRepository->remove(project); installHandler(job, i18n("Cannot remove project %1", project->name())); } else if (auto context = object.objectCast()) { const auto job = m_contextRepository->remove(context); installHandler(job, i18n("Cannot remove context %1", context->name())); } else { Q_ASSERT(false); } } QAbstractItemModel *AvailablePagesModel::createPageListModel() { m_inboxObject = QObjectPtr::create(); m_inboxObject->setProperty("name", i18n("Inbox")); m_workdayObject = QObjectPtr::create(); m_workdayObject->setProperty("name", i18n("Workday")); m_projectsObject = QObjectPtr::create(); m_projectsObject->setProperty("name", i18n("Projects")); m_contextsObject = QObjectPtr::create(); m_contextsObject->setProperty("name", i18n("Contexts")); + m_allTasksObject = QObjectPtr::create(); + m_allTasksObject->setProperty("name", i18n("All Tasks")); m_rootsProvider = Domain::QueryResultProvider::Ptr::create(); m_rootsProvider->append(m_inboxObject); m_rootsProvider->append(m_workdayObject); m_rootsProvider->append(m_projectsObject); m_rootsProvider->append(m_contextsObject); + m_rootsProvider->append(m_allTasksObject); auto query = [this](const QObjectPtr &object) -> Domain::QueryResultInterface::Ptr { if (!object) return Domain::QueryResult::create(m_rootsProvider); else if (object == m_projectsObject) return Domain::QueryResult::copy(m_dataSourceQueries->findAllSelected()); else if (object == m_contextsObject) return Domain::QueryResult::copy(m_contextQueries->findAll()); else if (const auto source = object.objectCast()) return Domain::QueryResult::copy(m_dataSourceQueries->findProjects(source)); 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 nonDroppableNodeFlags = Qt::ItemIsSelectable + | Qt::ItemIsEnabled; const Qt::ItemFlags structureNodeFlags = Qt::NoItemFlags; return object.objectCast() ? defaultFlags : object.objectCast() ? defaultFlags : object == m_inboxObject ? immutableNodeFlags : object == m_workdayObject ? immutableNodeFlags + : object == m_allTasksObject ? nonDroppableNodeFlags : 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_workdayObject || object == m_projectsObject || object == m_contextsObject + || object == m_allTasksObject || object.objectCast())) { 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_workdayObject) ? QStringLiteral("go-jump-today") : (object == m_projectsObject) ? QStringLiteral("folder") : (object == m_contextsObject) ? QStringLiteral("folder") + : (object == m_allTasksObject) ? QStringLiteral("view-pim-tasks") : object.objectCast() ? QStringLiteral("folder") : object.objectCast() ? QStringLiteral("view-pim-notes") : 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 &value, int role) { if (role != Qt::EditRole) { return false; } if (object == m_inboxObject || object == m_workdayObject || object == m_projectsObject || object == m_contextsObject + || object == m_allTasksObject || object.objectCast()) { return false; } if (auto project = object.objectCast()) { const auto currentName = project->name(); project->setName(value.toString()); const auto job = m_projectRepository->update(project); installHandler(job, i18n("Cannot modify project %1", currentName)); } else if (auto context = object.objectCast()) { const auto currentName = context->name(); context->setName(value.toString()); const auto job = m_contextRepository->update(context); installHandler(job, i18n("Cannot modify context %1", currentName)); } 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 droppedTasks = mimeData->property("objects").value(); if (droppedTasks.isEmpty()) return false; if (auto project = object.objectCast()) { foreach (const auto &task, droppedTasks) { const auto job = m_projectRepository->associate(project, task); installHandler(job, i18n("Cannot add %1 to project %2", task->title(), project->name())); } return true; } else if (auto context = object.objectCast()) { foreach (const auto &task, droppedTasks) { const auto job = m_contextRepository->associate(context, task); installHandler(job, i18n("Cannot add %1 to context %2", task->title(), context->name())); } return true; } else if (object == m_inboxObject) { foreach (const auto &task, droppedTasks) { const auto job = m_projectRepository->dissociate(task); installHandler(job, i18n("Cannot move %1 to Inbox", task->title())); Utils::JobHandler::install(job, [this, task] { const auto dissociateJob = m_taskRepository->dissociateAll(task); installHandler(dissociateJob, i18n("Cannot move task %1 to Inbox", task->title())); }); } return true; } else if (object == m_workdayObject) { foreach (const auto &task, droppedTasks) { task->setStartDate(Utils::DateTime::currentDate()); const auto job = m_taskRepository->update(task); installHandler(job, i18n("Cannot update task %1 to Workday", task->title())); } return true; } return false; }; auto drag = [](const QObjectPtrList &) -> QMimeData* { return nullptr; }; return new QueryTreeModel(query, flags, data, setData, drop, drag, nullptr, this); } diff --git a/src/presentation/availablepagesmodel.h b/src/presentation/availablepagesmodel.h index 08295daf..b3184f57 100644 --- a/src/presentation/availablepagesmodel.h +++ b/src/presentation/availablepagesmodel.h @@ -1,94 +1,95 @@ /* 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_AVAILABLETASKPAGESMODEL_H #define PRESENTATION_AVAILABLETASKPAGESMODEL_H #include "presentation/errorhandlingmodelbase.h" #include "domain/contextqueries.h" #include "domain/contextrepository.h" #include "domain/datasourcequeries.h" #include "domain/projectqueries.h" #include "domain/projectrepository.h" #include "domain/taskqueries.h" #include "domain/taskrepository.h" #include "presentation/metatypes.h" class QModelIndex; namespace Presentation { class AvailablePagesSortFilterProxyModel; class AvailablePagesModel : public QObject, public ErrorHandlingModelBase { Q_OBJECT Q_PROPERTY(QAbstractItemModel* pageListModel READ pageListModel) public: explicit AvailablePagesModel(const Domain::DataSourceQueries::Ptr &dataSourceQueries, const Domain::ProjectQueries::Ptr &projectQueries, const Domain::ProjectRepository::Ptr &projectRepository, const Domain::ContextQueries::Ptr &contextQueries, const Domain::ContextRepository::Ptr &contextRepository, const Domain::TaskQueries::Ptr &taskQueries, const Domain::TaskRepository::Ptr &taskRepository, QObject *parent = nullptr); QAbstractItemModel *pageListModel(); Q_SCRIPTABLE QObject *createPageForIndex(const QModelIndex &index); public slots: void addProject(const QString &name, const Domain::DataSource::Ptr &source); void addContext(const QString &name, const Domain::DataSource::Ptr &source); void removeItem(const QModelIndex &index); private: QAbstractItemModel *createPageListModel(); QAbstractItemModel *m_pageListModel; Presentation::AvailablePagesSortFilterProxyModel *m_sortProxyModel; Domain::DataSourceQueries::Ptr m_dataSourceQueries; Domain::ProjectQueries::Ptr m_projectQueries; Domain::ProjectRepository::Ptr m_projectRepository; Domain::ContextQueries::Ptr m_contextQueries; Domain::ContextRepository::Ptr m_contextRepository; Domain::TaskQueries::Ptr m_taskQueries; Domain::TaskRepository::Ptr m_taskRepository; Domain::QueryResultProvider::Ptr m_rootsProvider; QObjectPtr m_inboxObject; QObjectPtr m_workdayObject; QObjectPtr m_projectsObject; QObjectPtr m_contextsObject; + QObjectPtr m_allTasksObject; }; } #endif // PRESENTATION_AVAILABLETASKPAGESMODEL_H diff --git a/src/presentation/pagemodel.cpp b/src/presentation/pagemodel.cpp index 1609225a..d765ec58 100644 --- a/src/presentation/pagemodel.cpp +++ b/src/presentation/pagemodel.cpp @@ -1,40 +1,87 @@ /* 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 "pagemodel.h" +#include "presentation/querytreemodel.h" + +#include + using namespace Presentation; PageModel::PageModel(QObject *parent) : QObject(parent), m_centralListModel(nullptr) { } QAbstractItemModel *PageModel::centralListModel() { if (!m_centralListModel) m_centralListModel = createCentralListModel(); return m_centralListModel; } + +PageModel::TaskExtraDataPtr PageModel::fetchTaskExtraData(Domain::TaskQueries::Ptr taskQueries, + const QModelIndex &index, const Domain::Task::Ptr &task) +{ + TaskExtraDataPtr info = TaskExtraDataPtr::create(); + if (index.parent().isValid()) { // children are in the same collection as their parent, so the same project + info->childTask = true; + return info; + } + + info->projectQueryResult = taskQueries->findProject(task); + if (info->projectQueryResult) { + QPersistentModelIndex persistentIndex(index); + info->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 info; +} + +QVariant PageModel::dataForTaskWithProject(const Domain::Task::Ptr &task, int role, const TaskExtraDataPtr &info) +{ + switch (role) { + case Qt::DisplayRole: + case Qt::EditRole: + return task->title(); + case Qt::CheckStateRole: + return task->isDone() ? Qt::Checked : Qt::Unchecked; + case Presentation::QueryTreeModelBase::AdditionalInfoRole: + if (!info || info->childTask) + return QString(); + if (info->projectQueryResult && !info->projectQueryResult->data().isEmpty()) { + Domain::Project::Ptr project = info->projectQueryResult->data().at(0); + return i18n("Project: %1", project->name()); + } + return i18n("Inbox"); // TODO add source name + default: + break; + } + return QVariant(); +} diff --git a/src/presentation/pagemodel.h b/src/presentation/pagemodel.h index 1eb5eaeb..b0bbb9d5 100644 --- a/src/presentation/pagemodel.h +++ b/src/presentation/pagemodel.h @@ -1,61 +1,77 @@ /* 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_PAGEMODEL_H #define PRESENTATION_PAGEMODEL_H #include #include +#include "domain/project.h" #include "domain/task.h" +#include "domain/taskqueries.h" #include "presentation/metatypes.h" #include "presentation/errorhandlingmodelbase.h" namespace Presentation { class PageModel : public QObject, public ErrorHandlingModelBase { Q_OBJECT Q_PROPERTY(QAbstractItemModel* centralListModel READ centralListModel) public: explicit PageModel(QObject *parent = nullptr); QAbstractItemModel *centralListModel(); public slots: virtual Domain::Task::Ptr addItem(const QString &title, const QModelIndex &parentIndex = QModelIndex()) = 0; virtual void removeItem(const QModelIndex &index) = 0; virtual void promoteItem(const QModelIndex &index) = 0; +protected: + struct TaskExtraData + { + bool childTask = false; + Domain::QueryResult::Ptr projectQueryResult; + // later on we'll want the context query as well + }; + using TaskExtraDataPtr = QSharedPointer; + + using ProjectQueryPtr = Domain::QueryResult::Ptr; + static TaskExtraDataPtr fetchTaskExtraData(Domain::TaskQueries::Ptr taskQueries, + const QModelIndex &index, const Domain::Task::Ptr &task); + static QVariant dataForTaskWithProject(const Domain::Task::Ptr &task, int role, const TaskExtraDataPtr &info); + private: virtual QAbstractItemModel *createCentralListModel() = 0; QAbstractItemModel *m_centralListModel; }; } #endif // PRESENTATION_PAGEMODEL_H diff --git a/src/presentation/workdaypagemodel.cpp b/src/presentation/workdaypagemodel.cpp index 8de32dc5..39179934 100644 --- a/src/presentation/workdaypagemodel.cpp +++ b/src/presentation/workdaypagemodel.cpp @@ -1,203 +1,163 @@ /* 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/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::Task::Ptr WorkdayPageModel::addItem(const QString &title, const QModelIndex &parentIndex) { const auto parentData = parentIndex.data(QueryTreeModelBase::ObjectRole); const auto parentTask = parentData.value(); 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 task = data.value(); 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 task = data.value(); 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::Task::Ptr &task) -> Domain::QueryResultInterface::Ptr { if (!task) return m_taskQueries->findWorkdayTopLevel(); else return m_taskQueries->findChildren(task); }; auto flags = [](const Domain::Task::Ptr &task) { const Qt::ItemFlags defaultFlags = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsDragEnabled; return task ? (defaultFlags | Qt::ItemIsUserCheckable | Qt::ItemIsDropEnabled) : defaultFlags; }; - struct AdditionalData - { - bool childTask = false; - Domain::QueryResult::Ptr projectQueryResult; - // later on we'll want the context query as well - }; - using AdditionalInfo = QSharedPointer; - - auto data = [](const Domain::Task::Ptr &task, int role, const AdditionalInfo &info) -> QVariant { - switch (role) { - case Qt::DisplayRole: - case Qt::EditRole: - return task->title(); - case Qt::CheckStateRole: - return task->isDone() ? Qt::Checked : Qt::Unchecked; - case Presentation::QueryTreeModelBase::AdditionalInfoRole: - if (!info || info->childTask) - return QString(); - if (info->projectQueryResult && !info->projectQueryResult->data().isEmpty()) { - Domain::Project::Ptr project = info->projectQueryResult->data().at(0); - return i18n("Project: %1", project->name()); - } - return i18n("Inbox"); // TODO add source name - default: - break; - } - return QVariant(); + auto data = [](const Domain::Task::Ptr &task, int role, const TaskExtraDataPtr &info) { + return dataForTaskWithProject(task, role, info); }; - auto fetchAdditionalInfo = [this](const QModelIndex &index, const Domain::Task::Ptr &task) -> AdditionalInfo { - AdditionalInfo info = AdditionalInfo::create(); - if (index.parent().isValid()) { // children are in the same collection as their parent, so the same project - info->childTask = true; - return info; - } - - info->projectQueryResult = m_taskQueries->findProject(task); - if (info->projectQueryResult) { - QPersistentModelIndex persistentIndex(index); - info->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 info; + auto fetchAdditionalInfo = [this](const QModelIndex &index, const Domain::Task::Ptr &task) { + return fetchTaskExtraData(m_taskQueries, index, task); }; auto setData = [this](const Domain::Task::Ptr &task, const QVariant &value, int role) { if (role != Qt::EditRole && role != Qt::CheckStateRole) { return false; } 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; }; auto drop = [this](const QMimeData *mimeData, Qt::DropAction, const Domain::Task::Ptr &parentTask) { if (!mimeData->hasFormat(QStringLiteral("application/x-zanshin-object"))) return false; auto droppedTasks = mimeData->property("objects").value(); if (droppedTasks.isEmpty()) return false; foreach(const auto &childTask, droppedTasks) { 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()); // TODO something like m_taskRepository->update(childTask) is missing here // It was removed in commit c97a99bf because it led to a LLCONFLICT in akonadi (due to dissociate below). // The removal broke tests-features-workday-workdaydraganddropfeature (date not changed). auto job = m_taskRepository->dissociate(childTask); installHandler(job, i18n("Cannot deparent task %1 from its parent", childTask->title())); } } return true; }; auto drag = [](const Domain::Task::List &tasks) -> QMimeData* { if (tasks.isEmpty()) return nullptr; auto data = new QMimeData; data->setData(QStringLiteral("application/x-zanshin-object"), "object"); data->setProperty("objects", QVariant::fromValue(tasks)); return data; }; - return new QueryTreeModel(query, flags, data, setData, drop, drag, fetchAdditionalInfo, this); + return new QueryTreeModel(query, flags, data, setData, drop, drag, fetchAdditionalInfo, this); } diff --git a/tests/features/contexts/contextaddfeature.cpp b/tests/features/contexts/contextaddfeature.cpp index ae5e863f..706051d7 100644 --- a/tests/features/contexts/contextaddfeature.cpp +++ b/tests/features/contexts/contextaddfeature.cpp @@ -1,66 +1,67 @@ /* This file is part of Zanshin Copyright 2019 Kevin Ottens Copyright 2019 David Faure 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 using namespace Testlib; // Feature: Context creation // As someone using tasks // I can create a context // In order to give them some semantic class ContextAddFeature : public QObject { Q_OBJECT private slots: void New_contexts_appear_in_the_list() { ZanshinContext c; Given(c.I_display_the_available_pages()); When(c.I_add_a_context("Internet", "TestData / Calendar1")); And(c.I_list_the_items()); Then(c.the_list_is({ { "display", "icon" }, { { "Inbox", "mail-folder-inbox" }, { "Workday", "go-jump-today" }, { "Projects", "folder" }, { "Projects / TestData » Calendar1", "folder" }, { "Projects / TestData » Calendar1 / Prepare talk about TDD", "view-pim-tasks" }, { "Projects / TestData » Calendar1 / Read List", "view-pim-tasks" }, { "Projects / TestData » Calendar1 » Calendar2", "folder" }, { "Projects / TestData » Calendar1 » Calendar2 / Backlog", "view-pim-tasks" }, { "Contexts", "folder" }, { "Contexts / Errands", "view-pim-notes" }, { "Contexts / Internet", "view-pim-notes" }, { "Contexts / Online", "view-pim-notes" }, + { "All Tasks", "view-pim-tasks" }, } })); } }; ZANSHIN_TEST_MAIN(ContextAddFeature) #include "contextaddfeature.moc" diff --git a/tests/features/contexts/contexteditfeature.cpp b/tests/features/contexts/contexteditfeature.cpp index dec3d631..cda00d80 100644 --- a/tests/features/contexts/contexteditfeature.cpp +++ b/tests/features/contexts/contexteditfeature.cpp @@ -1,65 +1,66 @@ /* This file is part of Zanshin Copyright 2019 Kevin Ottens Copyright 2019 David Faure 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 using namespace Testlib; // Feature: Context rename // As someone collecting tasks // I can rename a context // In order to refine my tasks organization class ContextEditFeature : public QObject { Q_OBJECT private slots: void Renamed_context_appear_in_the_list() { ZanshinContext c; Given(c.I_display_the_available_pages()); When(c.I_rename_a_page("Contexts", "Errands", "Chores")); And(c.I_list_the_items()); Then(c.the_list_is({ { "display", "icon" }, { { "Inbox", "mail-folder-inbox" }, { "Workday", "go-jump-today" }, { "Projects", "folder" }, { "Projects / TestData » Calendar1", "folder" }, { "Projects / TestData » Calendar1 / Prepare talk about TDD", "view-pim-tasks" }, { "Projects / TestData » Calendar1 / Read List", "view-pim-tasks" }, { "Projects / TestData » Calendar1 » Calendar2", "folder" }, { "Projects / TestData » Calendar1 » Calendar2 / Backlog", "view-pim-tasks" }, { "Contexts", "folder" }, { "Contexts / Chores", "view-pim-notes" }, { "Contexts / Online", "view-pim-notes" }, + { "All Tasks", "view-pim-tasks" }, } })); } }; ZANSHIN_TEST_MAIN(ContextEditFeature) #include "contexteditfeature.moc" diff --git a/tests/features/contexts/contextremovefeature.cpp b/tests/features/contexts/contextremovefeature.cpp index 1a7a4028..70631d6c 100644 --- a/tests/features/contexts/contextremovefeature.cpp +++ b/tests/features/contexts/contextremovefeature.cpp @@ -1,64 +1,65 @@ /* This file is part of Zanshin Copyright 2019 Kevin Ottens Copyright 2019 David Faure 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 using namespace Testlib; // Feature: Context removal // As someone using tasks // I can remove a context // In order to maintain their semantic class ContextRemoveFeature : public QObject { Q_OBJECT private slots: void Removed_context_disappear_from_the_list() { ZanshinContext c; Given(c.I_display_the_available_pages()); When(c.I_remove_a_page("Contexts", "Online")); And(c.I_list_the_items()); Then(c.the_list_is({ { "display", "icon" }, { { "Inbox", "mail-folder-inbox" }, { "Workday", "go-jump-today" }, { "Projects", "folder" }, { "Projects / TestData » Calendar1", "folder" }, { "Projects / TestData » Calendar1 / Prepare talk about TDD", "view-pim-tasks" }, { "Projects / TestData » Calendar1 / Read List", "view-pim-tasks" }, { "Projects / TestData » Calendar1 » Calendar2", "folder" }, { "Projects / TestData » Calendar1 » Calendar2 / Backlog", "view-pim-tasks" }, { "Contexts", "folder" }, { "Contexts / Errands", "view-pim-notes" }, + { "All Tasks", "view-pim-tasks" }, } })); } }; ZANSHIN_TEST_MAIN(ContextRemoveFeature) #include "contextremovefeature.moc" diff --git a/tests/features/datasource/datasourceselectionfeature.cpp b/tests/features/datasource/datasourceselectionfeature.cpp index cebc95a1..08166703 100644 --- a/tests/features/datasource/datasourceselectionfeature.cpp +++ b/tests/features/datasource/datasourceselectionfeature.cpp @@ -1,129 +1,131 @@ /* This file is part of Zanshin Copyright 2019 Kevin Ottens Copyright 2019 David Faure 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 using namespace Testlib; // Feature: Data sources selection // As an advanced user // I can select or deselect sources // In order to see more or less content class DatasourceSelectionFeature : public QObject { Q_OBJECT private slots: void Unchecking_impacts_the_inbox() { ZanshinContext c; Given(c.I_display_the_page("Inbox")); And(c.there_is_an_item_in_the_available_data_sources("TestData / Calendar1")); When(c.I_uncheck_the_item()); And(c.I_look_at_the_central_list()); And(c.I_list_the_items()); Then(c.the_list_is({ { "display" }, { { "Buy apples" }, { "Buy pears" }, { "Errands" }, } })); } void Checking_impacts_the_inbox() { ZanshinContext c; Given(c.I_display_the_page("Inbox")); And(c.there_is_an_item_in_the_available_data_sources("TestData / Calendar1")); When(c.I_check_the_item()); And(c.I_look_at_the_central_list()); And(c.I_list_the_items()); Then(c.the_list_is({ { "display" }, { { "\"Capital in the Twenty-First Century\" by Thomas Piketty" }, { "\"The Pragmatic Programmer\" by Hunt and Thomas" }, { "Buy cheese" }, { "Buy kiwis" }, { "Buy apples" }, { "Buy pears" }, { "Errands" }, { "Buy rutabagas" }, } })); } void Unchecking_impacts_project_list() { ZanshinContext c; Given(c.there_is_an_item_in_the_available_data_sources("TestData / Calendar1")); When(c.I_uncheck_the_item()); And(c.I_display_the_available_pages()); And(c.I_list_the_items()); Then(c.the_list_is({ { "display" }, { { "Inbox" }, { "Workday" }, { "Projects" }, { "Projects / TestData » Calendar1 » Calendar2" }, { "Projects / TestData » Calendar1 » Calendar2 / Backlog" }, { "Contexts" }, { "Contexts / Errands" }, { "Contexts / Online" }, + { "All Tasks" }, } })); } void Checking_impacts_project_list() { ZanshinContext c; Given(c.there_is_an_item_in_the_available_data_sources("TestData / Calendar1")); When(c.I_check_the_item()); And(c.I_display_the_available_pages()); And(c.I_list_the_items()); Then(c.the_list_is({ { "display" }, { { "Inbox" }, { "Workday" }, { "Projects" }, { "Projects / TestData » Calendar1" }, { "Projects / TestData » Calendar1 / Prepare talk about TDD" }, { "Projects / TestData » Calendar1 / Read List" }, { "Projects / TestData » Calendar1 » Calendar2" }, { "Projects / TestData » Calendar1 » Calendar2 / Backlog" }, { "Contexts" }, { "Contexts / Errands" }, { "Contexts / Online" }, + { "All Tasks" }, } })); } }; ZANSHIN_TEST_MAIN(DatasourceSelectionFeature) #include "datasourceselectionfeature.moc" diff --git a/tests/features/pages/pagesdisplayfeature.cpp b/tests/features/pages/pagesdisplayfeature.cpp index c1ac5aee..dde758a5 100644 --- a/tests/features/pages/pagesdisplayfeature.cpp +++ b/tests/features/pages/pagesdisplayfeature.cpp @@ -1,64 +1,65 @@ /* This file is part of Zanshin Copyright 2019 Kevin Ottens Copyright 2019 David Faure 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 using namespace Testlib; // Feature: Available pages content // As someone collecting tasks // I can see all the pages available to me // In order to display them and add tasks to them class PagesDisplayFeature : public QObject { Q_OBJECT private slots: void Inbox_projects_and_contexts_appear_in_the_list() { ZanshinContext c; Given(c.I_display_the_available_pages()); When(c.I_list_the_items()); Then(c.the_list_is({ { "display", "icon" }, { { "Inbox", "mail-folder-inbox" }, { "Workday", "go-jump-today" }, { "Projects", "folder" }, { "Projects / TestData » Calendar1", "folder" }, { "Projects / TestData » Calendar1 / Prepare talk about TDD", "view-pim-tasks" }, { "Projects / TestData » Calendar1 / Read List", "view-pim-tasks" }, { "Projects / TestData » Calendar1 » Calendar2", "folder" }, { "Projects / TestData » Calendar1 » Calendar2 / Backlog", "view-pim-tasks" }, { "Contexts", "folder" }, { "Contexts / Errands", "view-pim-notes" }, { "Contexts / Online", "view-pim-notes" }, + { "All Tasks", "view-pim-tasks" }, } })); } }; ZANSHIN_TEST_MAIN(PagesDisplayFeature) #include "pagesdisplayfeature.moc" diff --git a/tests/features/projects/projectaddfeature.cpp b/tests/features/projects/projectaddfeature.cpp index 56205f7a..36814e7a 100644 --- a/tests/features/projects/projectaddfeature.cpp +++ b/tests/features/projects/projectaddfeature.cpp @@ -1,66 +1,67 @@ /* This file is part of Zanshin Copyright 2019 Kevin Ottens Copyright 2019 David Faure 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 using namespace Testlib; // Feature: Project creation // As someone collecting tasks // I can create a project // In order to organize my tasks class ProjectAddFeature : public QObject { Q_OBJECT private slots: void New_projects_appear_in_the_list() { ZanshinContext c; Given(c.I_display_the_available_pages()); When(c.I_add_a_project("Birthday", "TestData / Calendar1")); And(c.I_list_the_items()); Then(c.the_list_is({ { "display", "icon" }, { { "Inbox", "mail-folder-inbox" }, { "Workday", "go-jump-today" }, { "Projects", "folder" }, { "Projects / TestData » Calendar1", "folder" }, { "Projects / TestData » Calendar1 / Birthday", "view-pim-tasks" }, { "Projects / TestData » Calendar1 / Prepare talk about TDD", "view-pim-tasks" }, { "Projects / TestData » Calendar1 / Read List", "view-pim-tasks" }, { "Projects / TestData » Calendar1 » Calendar2", "folder" }, { "Projects / TestData » Calendar1 » Calendar2 / Backlog", "view-pim-tasks" }, { "Contexts", "folder" }, { "Contexts / Errands", "view-pim-notes" }, { "Contexts / Online", "view-pim-notes" }, + { "All Tasks", "view-pim-tasks" }, } })); } }; ZANSHIN_TEST_MAIN(ProjectAddFeature) #include "projectaddfeature.moc" diff --git a/tests/features/projects/projecteditfeature.cpp b/tests/features/projects/projecteditfeature.cpp index 19502db2..eede48f5 100644 --- a/tests/features/projects/projecteditfeature.cpp +++ b/tests/features/projects/projecteditfeature.cpp @@ -1,65 +1,66 @@ /* This file is part of Zanshin Copyright 2019 Kevin Ottens Copyright 2019 David Faure 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 using namespace Testlib; // Feature: Project rename // As someone collecting tasks // I can rename a project // In order to refine my tasks organization class ProjectEditFeature : public QObject { Q_OBJECT private slots: void Renamed_projects_appear_in_the_list() { ZanshinContext c; Given(c.I_display_the_available_pages()); When(c.I_rename_a_page("Projects / TestData » Calendar1 » Calendar2", "Backlog", "Party")); And(c.I_list_the_items()); Then(c.the_list_is({ { "display", "icon" }, { { "Inbox", "mail-folder-inbox" }, { "Workday", "go-jump-today" }, { "Projects", "folder" }, { "Projects / TestData » Calendar1", "folder" }, { "Projects / TestData » Calendar1 / Prepare talk about TDD", "view-pim-tasks" }, { "Projects / TestData » Calendar1 / Read List", "view-pim-tasks" }, { "Projects / TestData » Calendar1 » Calendar2", "folder" }, { "Projects / TestData » Calendar1 » Calendar2 / Party", "view-pim-tasks" }, { "Contexts", "folder" }, { "Contexts / Errands", "view-pim-notes" }, { "Contexts / Online", "view-pim-notes" }, + { "All Tasks", "view-pim-tasks" }, } })); } }; ZANSHIN_TEST_MAIN(ProjectEditFeature) #include "projecteditfeature.moc" diff --git a/tests/features/projects/projectremovefeature.cpp b/tests/features/projects/projectremovefeature.cpp index 04eff360..8143aae4 100644 --- a/tests/features/projects/projectremovefeature.cpp +++ b/tests/features/projects/projectremovefeature.cpp @@ -1,64 +1,65 @@ /* This file is part of Zanshin Copyright 2019 Kevin Ottens Copyright 2019 David Faure 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 using namespace Testlib; // Feature: Project destruction // As someone collecting tasks // I can delete a project // In order to clean my tasks class ProjectRemoveFeature : public QObject { Q_OBJECT private slots: void Removing_a_simple_project_from_the_list() { ZanshinContext c; Given(c.I_display_the_available_pages()); When(c.I_remove_a_page("Projects / TestData » Calendar1", "Prepare talk about TDD")); And(c.I_list_the_items()); Then(c.the_list_is({ { "display", "icon" }, { { "Inbox", "mail-folder-inbox" }, { "Workday", "go-jump-today" }, { "Projects", "folder" }, { "Projects / TestData » Calendar1", "folder" }, { "Projects / TestData » Calendar1 / Read List", "view-pim-tasks" }, { "Projects / TestData » Calendar1 » Calendar2", "folder" }, { "Projects / TestData » Calendar1 » Calendar2 / Backlog", "view-pim-tasks" }, { "Contexts", "folder" }, { "Contexts / Errands", "view-pim-notes" }, { "Contexts / Online", "view-pim-notes" }, + { "All Tasks", "view-pim-tasks" }, } })); } }; ZANSHIN_TEST_MAIN(ProjectRemoveFeature) #include "projectremovefeature.moc" diff --git a/tests/features/projects/projecttaskpromotefeature.cpp b/tests/features/projects/projecttaskpromotefeature.cpp index 6b54b4d2..1ae98930 100644 --- a/tests/features/projects/projecttaskpromotefeature.cpp +++ b/tests/features/projects/projecttaskpromotefeature.cpp @@ -1,70 +1,71 @@ /* This file is part of Zanshin Copyright 2019 Kevin Ottens Copyright 2019 David Faure 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 using namespace Testlib; // Feature: Task promotion // As someone collecting tasks // I can promote a task into a project // In order to organize my tasks class ProjectTaskPromoteFeature : public QObject { Q_OBJECT private slots: void Task_promoted_into_a_project_appears_in_the_list() { ZanshinContext c; Given(c.I_display_the_page("Projects / TestData » Calendar1 » Calendar2 / Backlog")); And(c.I_add_a_task("Design a present")); And(c.I_look_at_the_central_list()); And(c.there_is_an_item_in_the_central_list("Design a present")); When(c.I_promote_the_item()); And(c.I_display_the_available_pages()); And(c.I_list_the_items()); Then(c.the_list_is({ { "display", "icon" }, { { "Inbox", "mail-folder-inbox" }, { "Workday", "go-jump-today" }, { "Projects", "folder" }, { "Projects / TestData » Calendar1", "folder" }, { "Projects / TestData » Calendar1 / Prepare talk about TDD", "view-pim-tasks" }, { "Projects / TestData » Calendar1 / Read List", "view-pim-tasks" }, { "Projects / TestData » Calendar1 » Calendar2", "folder" }, { "Projects / TestData » Calendar1 » Calendar2 / Backlog", "view-pim-tasks" }, { "Projects / TestData » Calendar1 » Calendar2 / Design a present", "view-pim-tasks" }, { "Contexts", "folder" }, { "Contexts / Errands", "view-pim-notes" }, { "Contexts / Online", "view-pim-notes" }, + { "All Tasks", "view-pim-tasks" }, } })); } }; ZANSHIN_TEST_MAIN(ProjectTaskPromoteFeature) #include "projecttaskpromotefeature.moc" diff --git a/tests/units/presentation/CMakeLists.txt b/tests/units/presentation/CMakeLists.txt index 7b02a2e1..b4360a7f 100644 --- a/tests/units/presentation/CMakeLists.txt +++ b/tests/units/presentation/CMakeLists.txt @@ -1,18 +1,19 @@ zanshin_auto_tests( applicationmodeltest editormodeltest availablepagesmodeltest availablepagessortfilterproxymodeltest availablesourcesmodeltest errorhandlertest errorhandlingmodelbasetest inboxpagemodeltest metatypestest pagemodeltest projectpagemodeltest querytreemodeltest runningtaskmodeltest taskfilterproxymodeltest contextpagemodeltest workdaypagemodeltest + alltaskspagemodeltest ) diff --git a/tests/units/presentation/alltaskspagemodeltest.cpp b/tests/units/presentation/alltaskspagemodeltest.cpp new file mode 100644 index 00000000..adff9619 --- /dev/null +++ b/tests/units/presentation/alltaskspagemodeltest.cpp @@ -0,0 +1,400 @@ +/* This file is part of Zanshin + + Copyright 2019 David Faure + + 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 "utils/datetime.h" +#include "utils/mockobject.h" + +#include "domain/taskqueries.h" +#include "domain/taskrepository.h" +#include "presentation/alltaskspagemodel.h" +#include "presentation/querytreemodelbase.h" + +using namespace mockitopp; +using namespace mockitopp::matcher; + +class AllTasksPageModelTest : public QObject +{ + Q_OBJECT +private slots: + void shouldListAllTasksInCentralListModel() + { + // 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::Ptr::create(); + auto taskResult = Domain::QueryResult::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::Ptr::create(); + auto childTaskResult = Domain::QueryResult::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::Ptr::create(); + auto projectResult = Domain::QueryResult::create(projectProvider); + projectProvider->append(project); + + Utils::MockObject taskQueriesMock; + taskQueriesMock(&Domain::TaskQueries::findTopLevel).when().thenReturn(taskResult); + taskQueriesMock(&Domain::TaskQueries::findChildren).when(task1).thenReturn(childTaskResult); + taskQueriesMock(&Domain::TaskQueries::findChildren).when(task2).thenReturn(Domain::QueryResult::Ptr()); + taskQueriesMock(&Domain::TaskQueries::findChildren).when(task3).thenReturn(Domain::QueryResult::Ptr()); + taskQueriesMock(&Domain::TaskQueries::findChildren).when(childTask11).thenReturn(Domain::QueryResult::Ptr()); + taskQueriesMock(&Domain::TaskQueries::findChildren).when(childTask12).thenReturn(Domain::QueryResult::Ptr()); + + taskQueriesMock(&Domain::TaskQueries::findProject).when(task1).thenReturn(Domain::QueryResult::Ptr()); + taskQueriesMock(&Domain::TaskQueries::findProject).when(task2).thenReturn(projectResult); + taskQueriesMock(&Domain::TaskQueries::findProject).when(task3).thenReturn(Domain::QueryResult::Ptr()); + taskQueriesMock(&Domain::TaskQueries::findProject).when(childTask11).thenReturn(Domain::QueryResult::Ptr()); + taskQueriesMock(&Domain::TaskQueries::findProject).when(childTask12).thenReturn(Domain::QueryResult::Ptr()); + + Utils::MockObject taskRepositoryMock; + + Presentation::AllTasksPageModel pageModel(taskQueriesMock.getInstance(), + taskRepositoryMock.getInstance()); + + // WHEN + QAbstractItemModel *model = pageModel.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")); + QCOMPARE(model->data(childTask11Index, Presentation::QueryTreeModelBase::AdditionalInfoRole).toString(), QString()); + QCOMPARE(model->data(childTask12Index, Presentation::QueryTreeModelBase::AdditionalInfoRole).toString(), QString()); + + // 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(model->mimeData(QModelIndexList() << childTask12Index)); + + // THEN + QVERIFY(data->hasFormat(QStringLiteral("application/x-zanshin-object"))); + QCOMPARE(data->property("objects").value(), + Domain::Task::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::Task::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::Task::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::Task::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(), QDate()); // unlike the workday + } + + void shouldAddTasksInAllTasksPage() + { + // GIVEN + + // ... in fact we won't list any model + Utils::MockObject taskQueriesMock; + + // We'll gladly create a task though + Utils::MockObject taskRepositoryMock; + taskRepositoryMock(&Domain::TaskRepository::create).when(any()).thenReturn(new FakeJob(this)); + + Presentation::AllTasksPageModel pageModel(taskQueriesMock.getInstance(), + taskRepositoryMock.getInstance()); + + // WHEN + auto title = QStringLiteral("New task"); + auto task = pageModel.addItem(title); + + // THEN + QVERIFY(taskRepositoryMock(&Domain::TaskRepository::create).when(any()).exactly(1)); + + QVERIFY(task); + QCOMPARE(task->title(), title); + QCOMPARE(task->startDate(), QDate()); // that's one difference with the workday, which would set it to Utils::DateTime::currentDate() + } + + void shouldAddChildTask() + { + // GIVEN + + // Two tasks + auto task1 = Domain::Task::Ptr::create(); + auto task2 = Domain::Task::Ptr::create(); + auto taskProvider = Domain::QueryResultProvider::Ptr::create(); + auto taskResult = Domain::QueryResult::create(taskProvider); + taskProvider->append(task1); + taskProvider->append(task2); + + Utils::MockObject taskQueriesMock; + taskQueriesMock(&Domain::TaskQueries::findTopLevel).when().thenReturn(taskResult); + taskQueriesMock(&Domain::TaskQueries::findChildren).when(task1).thenReturn(Domain::QueryResult::Ptr()); + taskQueriesMock(&Domain::TaskQueries::findChildren).when(task2).thenReturn(Domain::QueryResult::Ptr()); + taskQueriesMock(&Domain::TaskQueries::findProject).when(any()).thenReturn(Domain::QueryResult::Ptr()); + + Utils::MockObject taskRepositoryMock; + taskRepositoryMock(&Domain::TaskRepository::createChild).when(any(), + any()) + .thenReturn(new FakeJob(this)); + + Presentation::AllTasksPageModel pageModel(taskQueriesMock.getInstance(), + taskRepositoryMock.getInstance()); + + // WHEN + const auto title = QStringLiteral("New task"); + const auto parentIndex = pageModel.centralListModel()->index(0, 0); + const auto createdTask = pageModel.addItem(title, parentIndex); + + // THEN + QVERIFY(taskRepositoryMock(&Domain::TaskRepository::createChild).when(any(), + any()) + .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::Ptr::create(); + auto taskResult = Domain::QueryResult::create(taskProvider); + taskProvider->append(task1); + taskProvider->append(task2); + + Utils::MockObject taskQueriesMock; + taskQueriesMock(&Domain::TaskQueries::findTopLevel).when().thenReturn(taskResult); + taskQueriesMock(&Domain::TaskQueries::findChildren).when(task1).thenReturn(Domain::QueryResult::Ptr()); + taskQueriesMock(&Domain::TaskQueries::findChildren).when(task2).thenReturn(Domain::QueryResult::Ptr()); + taskQueriesMock(&Domain::TaskQueries::findProject).when(any()).thenReturn(Domain::QueryResult::Ptr()); + + Utils::MockObject taskRepositoryMock; + taskRepositoryMock(&Domain::TaskRepository::remove).when(task2).thenReturn(new FakeJob(this)); + + Presentation::AllTasksPageModel pageModel(taskQueriesMock.getInstance(), + taskRepositoryMock.getInstance()); + + // WHEN + const QModelIndex index = pageModel.centralListModel()->index(1, 0); + pageModel.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::Ptr::create(); + auto taskResult = Domain::QueryResult::create(taskProvider); + taskProvider->append(task1); + taskProvider->append(task2); + + Utils::MockObject taskQueriesMock; + taskQueriesMock(&Domain::TaskQueries::findTopLevel).when().thenReturn(taskResult); + taskQueriesMock(&Domain::TaskQueries::findChildren).when(task1).thenReturn(Domain::QueryResult::Ptr()); + taskQueriesMock(&Domain::TaskQueries::findChildren).when(task2).thenReturn(Domain::QueryResult::Ptr()); + taskQueriesMock(&Domain::TaskQueries::findDataSource).when(any()).thenReturn(Domain::QueryResult::Ptr()); + taskQueriesMock(&Domain::TaskQueries::findProject).when(task2).thenReturn(Domain::QueryResult::Ptr()); + + Utils::MockObject taskRepositoryMock; + taskRepositoryMock(&Domain::TaskRepository::promoteToProject).when(task2).thenReturn(new FakeJob(this)); + + Presentation::AllTasksPageModel pageModel(taskQueriesMock.getInstance(), + taskRepositoryMock.getInstance()); + + // WHEN + const QModelIndex index = pageModel.centralListModel()->index(1, 0); + pageModel.promoteItem(index); + + // THEN + QVERIFY(taskRepositoryMock(&Domain::TaskRepository::promoteToProject).when(task2).exactly(1)); + } +}; + +ZANSHIN_TEST_MAIN(AllTasksPageModelTest) + +#include "alltaskspagemodeltest.moc" diff --git a/tests/units/presentation/availablepagesmodeltest.cpp b/tests/units/presentation/availablepagesmodeltest.cpp index 0a5eeae8..d539a85d 100644 --- a/tests/units/presentation/availablepagesmodeltest.cpp +++ b/tests/units/presentation/availablepagesmodeltest.cpp @@ -1,1251 +1,1304 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include "utils/mockobject.h" #include "utils/datetime.h" +#include "presentation/alltaskspagemodel.h" #include "presentation/availablepagesmodel.h" #include "presentation/contextpagemodel.h" #include "presentation/errorhandler.h" #include "presentation/inboxpagemodel.h" #include "presentation/projectpagemodel.h" #include "presentation/querytreemodelbase.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; }; +static const int s_projectsRow = 2; +static const int s_contextRow = 3; +static const int s_allTasksRow = 4; + class AvailablePagesModelTest : public QObject { Q_OBJECT private slots: 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::Ptr::create(); auto sourceResult = Domain::QueryResult::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::Ptr::create(); auto project1Result = Domain::QueryResult::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::Ptr::create(); auto project2Result = Domain::QueryResult::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::Ptr::create(); auto contextResult = Domain::QueryResult::create(contextProvider); contextProvider->append(context1); contextProvider->append(context2); // One task (used for dropping later on) Domain::Task::Ptr taskToDrop(new Domain::Task); Utils::MockObject 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 projectRepositoryMock; Utils::MockObject taskRepositoryMock; Utils::MockObject contextQueriesMock; contextQueriesMock(&Domain::ContextQueries::findAll).when().thenReturn(contextResult); Utils::MockObject contextRepositoryMock; Presentation::AvailablePagesModel 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 projectsIndex = model->index(s_projectsRow, 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 contextsIndex = model->index(s_contextRow, 0); const QModelIndex context1Index = model->index(0, 0, contextsIndex); const QModelIndex context2Index = model->index(1, 0, contextsIndex); + const QModelIndex allTasksIndex = model->index(s_allTasksRow, 0); - QCOMPARE(model->rowCount(), 4); + QCOMPARE(model->rowCount(), 5); 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); + QCOMPARE(model->rowCount(allTasksIndex), 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->flags(allTasksIndex), (defaultFlags & ~(Qt::ItemIsEditable))); 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()); + QCOMPARE(model->data(allTasksIndex).toString(), i18n("All Tasks")); 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()); + QVERIFY(!model->data(allTasksIndex, Qt::EditRole).isValid()); 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")); + QCOMPARE(model->data(allTasksIndex, Presentation::QueryTreeModelBase::IconNameRole).toString(), QStringLiteral("view-pim-tasks")); 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()); + QVERIFY(!model->data(allTasksIndex, 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(); data->setData(QStringLiteral("application/x-zanshin-object"), "object"); data->setProperty("objects", QVariant::fromValue(Domain::Task::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).thenReturn(new FakeJob(this)); data.reset(new QMimeData); data->setData(QStringLiteral("application/x-zanshin-object"), "object"); data->setProperty("objects", QVariant::fromValue(Domain::Task::List() << taskToDrop)); model->dropMimeData(data.get(), Qt::MoveAction, -1, -1, context1Index); // THEN QVERIFY(contextRepositoryMock(&Domain::ContextRepository::associate).when(context1, taskToDrop).exactly(1)); // WHEN projectRepositoryMock(&Domain::ProjectRepository::dissociate).when(taskToDrop).thenReturn(new FakeJob(this)); taskRepositoryMock(&Domain::TaskRepository::dissociateAll).when(taskToDrop).thenReturn(new FakeJob(this)); data.reset(new QMimeData); data->setData(QStringLiteral("application/x-zanshin-object"), "object"); data->setProperty("objects", QVariant::fromValue(Domain::Task::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).exactly(1)); // WHEN Domain::Task::Ptr taskToDrop2(new Domain::Task); projectRepositoryMock(&Domain::ProjectRepository::associate).when(project11, taskToDrop2).thenReturn(new FakeJob(this)); data.reset(new QMimeData); data->setData(QStringLiteral("application/x-zanshin-object"), "object"); data->setProperty("objects", QVariant::fromValue(Domain::Task::List() << taskToDrop2)); model->dropMimeData(data.get(), Qt::MoveAction, -1, -1, project11Index); // THEN QVERIFY(projectRepositoryMock(&Domain::ProjectRepository::associate).when(project11, taskToDrop2).exactly(1)); // 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::Task::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::Task::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::Task::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::Ptr::create(); auto sourceResult = Domain::QueryResult::create(sourceProvider); // Empty context provider auto contextProvider = Domain::QueryResultProvider::Ptr::create(); auto contextResult = Domain::QueryResult::create(contextProvider); // context mocking Utils::MockObject contextQueriesMock; contextQueriesMock(&Domain::ContextQueries::findAll).when().thenReturn(contextResult); Utils::MockObject contextRepositoryMock; // sources mocking Utils::MockObject dataSourceQueriesMock; dataSourceQueriesMock(&Domain::DataSourceQueries::findAllSelected).when().thenReturn(sourceResult); Utils::MockObject projectRepositoryMock; Presentation::AvailablePagesModel 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(inboxPage)); } void shouldCreateWorkdayPage() { // GIVEN // Empty sources provider auto sourceProvider = Domain::QueryResultProvider::Ptr::create(); auto sourceResult = Domain::QueryResult::create(sourceProvider); // Empty context provider auto contextProvider = Domain::QueryResultProvider::Ptr::create(); auto contextResult = Domain::QueryResult::create(contextProvider); // context mocking Utils::MockObject contextQueriesMock; contextQueriesMock(&Domain::ContextQueries::findAll).when().thenReturn(contextResult); Utils::MockObject contextRepositoryMock; // sources mocking Utils::MockObject dataSourceQueriesMock; dataSourceQueriesMock(&Domain::DataSourceQueries::findAllSelected).when().thenReturn(sourceResult); Utils::MockObject projectRepositoryMock; Presentation::AvailablePagesModel 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(workdayPage)); } + void shouldCreateAllTasksPage() + { + // GIVEN + + // Empty sources provider + auto sourceProvider = Domain::QueryResultProvider::Ptr::create(); + auto sourceResult = Domain::QueryResult::create(sourceProvider); + // Empty context provider + auto contextProvider = Domain::QueryResultProvider::Ptr::create(); + auto contextResult = Domain::QueryResult::create(contextProvider); + + // context mocking + Utils::MockObject contextQueriesMock; + contextQueriesMock(&Domain::ContextQueries::findAll).when().thenReturn(contextResult); + + Utils::MockObject contextRepositoryMock; + + // sources mocking + Utils::MockObject dataSourceQueriesMock; + dataSourceQueriesMock(&Domain::DataSourceQueries::findAllSelected).when().thenReturn(sourceResult); + + Utils::MockObject projectRepositoryMock; + + Presentation::AvailablePagesModel 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 allTasksIndex = model->index(s_allTasksRow, 0); + + QObject *workdayPage = pages.createPageForIndex(allTasksIndex); + QVERIFY(qobject_cast(workdayPage)); + } + void shouldCreateProjectsPage() { // GIVEN // One selected data source auto source = Domain::DataSource::Ptr::create(); source->setName("source"); auto sourceProvider = Domain::QueryResultProvider::Ptr::create(); auto sourceResult = Domain::QueryResult::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::Ptr::create(); auto projectResult = Domain::QueryResult::create(projectProvider); projectProvider->append(project1); projectProvider->append(project2); // No contexts auto contextProvider = Domain::QueryResultProvider::Ptr::create(); auto contextResult = Domain::QueryResult::create(contextProvider); // data source mocking Utils::MockObject dataSourceQueriesMock; dataSourceQueriesMock(&Domain::DataSourceQueries::findAllSelected).when().thenReturn(sourceResult); dataSourceQueriesMock(&Domain::DataSourceQueries::findProjects).when(source).thenReturn(projectResult); // projects mocking Utils::MockObject projectRepositoryMock; // contexts mocking Utils::MockObject contextQueriesMock; contextQueriesMock(&Domain::ContextQueries::findAll).when().thenReturn(contextResult); Presentation::AvailablePagesModel 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 projectsIndex = model->index(s_projectsRow, 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(project1Page)); QCOMPARE(qobject_cast(project1Page)->project(), project1); QVERIFY(qobject_cast(project2Page)); QCOMPARE(qobject_cast(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::Ptr::create(); auto contextResult = Domain::QueryResult::create(contextProvider); contextProvider->append(context1); contextProvider->append(context2); // Empty sources provider auto sourceProvider = Domain::QueryResultProvider::Ptr::create(); auto sourceResult = Domain::QueryResult::create(sourceProvider); // contexts mocking Utils::MockObject contextQueriesMock; contextQueriesMock(&Domain::ContextQueries::findAll).when().thenReturn(contextResult); Utils::MockObject contextRepositoryMock; // sources mocking Utils::MockObject dataSourceQueriesMock; dataSourceQueriesMock(&Domain::DataSourceQueries::findAllSelected).when().thenReturn(sourceResult); // projects mocking Utils::MockObject projectRepositoryMock; Presentation::AvailablePagesModel 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 contextsIndex = model->index(s_contextRow, 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(context1Page)); QCOMPARE(qobject_cast(context1Page)->context(), context1); QVERIFY(qobject_cast(context2Page)); QCOMPARE(qobject_cast(context2Page)->context(), context2); } void shouldAddProjects() { // GIVEN auto source = Domain::DataSource::Ptr::create(); Utils::MockObject projectRepositoryMock; projectRepositoryMock(&Domain::ProjectRepository::create).when(any(), any()) .thenReturn(new FakeJob(this)); Presentation::AvailablePagesModel 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(), any()) .exactly(1)); } void shouldGetAnErrorMessageWhenAddProjectFailed() { // GIVEN auto source = Domain::DataSource::Ptr::create(); source->setName(QStringLiteral("Source1")); Utils::MockObject projectRepositoryMock; auto job = new FakeJob(this); job->setExpectedError(KJob::KilledJobError, QStringLiteral("Foo")); projectRepositoryMock(&Domain::ProjectRepository::create).when(any(), any()) .thenReturn(job); Presentation::AvailablePagesModel 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 auto source = Domain::DataSource::Ptr::create(); source->setName(QStringLiteral("Source1")); Utils::MockObject contextRepositoryMock; contextRepositoryMock(&Domain::ContextRepository::create).when(any(), any()) .thenReturn(new FakeJob(this)); Presentation::AvailablePagesModel 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"), source); // THEN QVERIFY(contextRepositoryMock(&Domain::ContextRepository::create).when(any(), any()) .exactly(1)); } void shouldGetAnErrorMessageWhenAddContextFailed() { // GIVEN auto source = Domain::DataSource::Ptr::create(); source->setName(QStringLiteral("Source1")); Utils::MockObject contextRepositoryMock; auto job = new FakeJob(this); job->setExpectedError(KJob::KilledJobError, QStringLiteral("Foo")); contextRepositoryMock(&Domain::ContextRepository::create).when(any(), any()) .thenReturn(job); Presentation::AvailablePagesModel 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"), source); // 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::Ptr::create(); auto sourceResult = Domain::QueryResult::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::Ptr::create(); auto projectResult = Domain::QueryResult::create(projectProvider); projectProvider->append(project1); projectProvider->append(project2); // No contexts auto contextProvider = Domain::QueryResultProvider::Ptr::create(); auto contextResult = Domain::QueryResult::create(contextProvider); // data source mocking Utils::MockObject dataSourceQueriesMock; dataSourceQueriesMock(&Domain::DataSourceQueries::findAllSelected).when().thenReturn(sourceResult); dataSourceQueriesMock(&Domain::DataSourceQueries::findProjects).when(source).thenReturn(projectResult); // projects mocking Utils::MockObject projectRepositoryMock; // contexts mocking Utils::MockObject contextQueriesMock; contextQueriesMock(&Domain::ContextQueries::findAll).when().thenReturn(contextResult); Presentation::AvailablePagesModel 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 projectsIndex = model->index(s_projectsRow, 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::Ptr::create(); auto sourceResult = Domain::QueryResult::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::Ptr::create(); auto projectResult = Domain::QueryResult::create(projectProvider); projectProvider->append(project1); projectProvider->append(project2); // No contexts auto contextProvider = Domain::QueryResultProvider::Ptr::create(); auto contextResult = Domain::QueryResult::create(contextProvider); // data source mocking Utils::MockObject dataSourceQueriesMock; dataSourceQueriesMock(&Domain::DataSourceQueries::findAllSelected).when().thenReturn(sourceResult); dataSourceQueriesMock(&Domain::DataSourceQueries::findProjects).when(source).thenReturn(projectResult); // projects mocking Utils::MockObject projectRepositoryMock; // contexts mocking Utils::MockObject contextQueriesMock; contextQueriesMock(&Domain::ContextQueries::findAll).when().thenReturn(contextResult); Presentation::AvailablePagesModel 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 projectsIndex = model->index(s_projectsRow, 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::Ptr::create(); auto contextResult = Domain::QueryResult::create(contextProvider); contextProvider->append(context1); // Empty sources provider auto sourceProvider = Domain::QueryResultProvider::Ptr::create(); auto sourceResult = Domain::QueryResult::create(sourceProvider); // contexts mocking Utils::MockObject contextQueriesMock; contextQueriesMock(&Domain::ContextQueries::findAll).when().thenReturn(contextResult); Utils::MockObject contextRepositoryMock; // sources mocking Utils::MockObject dataSourceQueriesMock; dataSourceQueriesMock(&Domain::DataSourceQueries::findAllSelected).when().thenReturn(sourceResult); Presentation::AvailablePagesModel 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 contextsIndex = model->index(s_contextRow, 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::Ptr::create(); auto contextResult = Domain::QueryResult::create(contextProvider); contextProvider->append(context1); // Empty sources provider auto sourceProvider = Domain::QueryResultProvider::Ptr::create(); auto sourceResult = Domain::QueryResult::create(sourceProvider); // contexts mocking Utils::MockObject contextQueriesMock; contextQueriesMock(&Domain::ContextQueries::findAll).when().thenReturn(contextResult); Utils::MockObject contextRepositoryMock; // sources mocking Utils::MockObject dataSourceQueriesMock; dataSourceQueriesMock(&Domain::DataSourceQueries::findAllSelected).when().thenReturn(sourceResult); Presentation::AvailablePagesModel 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 contextsIndex = model->index(s_contextRow, 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::Ptr::create(); auto sourceResult = Domain::QueryResult::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::Ptr::create(); auto projectResult = Domain::QueryResult::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::Ptr::create(); auto contextResult = Domain::QueryResult::create(contextProvider); contextProvider->append(context1); contextProvider->append(context2); Utils::MockObject dataSourceQueriesMock; dataSourceQueriesMock(&Domain::DataSourceQueries::findAllSelected).when().thenReturn(sourceResult); dataSourceQueriesMock(&Domain::DataSourceQueries::findProjects).when(source).thenReturn(projectResult); Utils::MockObject projectRepositoryMock; Utils::MockObject contextQueriesMock; contextQueriesMock(&Domain::ContextQueries::findAll).when().thenReturn(contextResult); Utils::MockObject contextRepositoryMock; Presentation::AvailablePagesModel 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 projectsIndex = model->index(s_projectsRow, 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::Ptr::create(); auto sourceResult = Domain::QueryResult::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::Ptr::create(); auto projectResult = Domain::QueryResult::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::Ptr::create(); auto contextResult = Domain::QueryResult::create(contextProvider); contextProvider->append(context1); contextProvider->append(context2); Utils::MockObject dataSourceQueriesMock; dataSourceQueriesMock(&Domain::DataSourceQueries::findAllSelected).when().thenReturn(sourceResult); dataSourceQueriesMock(&Domain::DataSourceQueries::findProjects).when(source).thenReturn(projectResult); Utils::MockObject projectRepositoryMock; Utils::MockObject contextQueriesMock; contextQueriesMock(&Domain::ContextQueries::findAll).when().thenReturn(contextResult); Utils::MockObject contextRepositoryMock; Presentation::AvailablePagesModel 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 contextsIndex = model->index(s_contextRow, 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::Ptr::create(); auto sourceResult = Domain::QueryResult::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::Ptr::create(); auto projectResult = Domain::QueryResult::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::Ptr::create(); auto contextResult = Domain::QueryResult::create(contextProvider); contextProvider->append(context1); contextProvider->append(context2); // One task (used for dropping later on) Domain::Task::Ptr taskToDrop(new Domain::Task); taskToDrop->setTitle(QStringLiteral("taskDropped")); Utils::MockObject dataSourceQueriesMock; dataSourceQueriesMock(&Domain::DataSourceQueries::findAllSelected).when().thenReturn(sourceResult); dataSourceQueriesMock(&Domain::DataSourceQueries::findProjects).when(source).thenReturn(projectResult); Utils::MockObject projectRepositoryMock; Utils::MockObject contextQueriesMock; contextQueriesMock(&Domain::ContextQueries::findAll).when().thenReturn(contextResult); Utils::MockObject contextRepositoryMock; Utils::MockObject taskRepositoryMock; Presentation::AvailablePagesModel 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 projectsIndex = model->index(s_projectsRow, 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(); data->setData(QStringLiteral("application/x-zanshin-object"), "object"); data->setProperty("objects", QVariant::fromValue(Domain::Task::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::Ptr::create(); auto sourceResult = Domain::QueryResult::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::Ptr::create(); auto projectResult = Domain::QueryResult::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::Ptr::create(); auto contextResult = Domain::QueryResult::create(contextProvider); contextProvider->append(context1); contextProvider->append(context2); // One task (used for dropping later on) Domain::Task::Ptr taskToDrop(new Domain::Task); taskToDrop->setTitle(QStringLiteral("taskDropped")); Utils::MockObject dataSourceQueriesMock; dataSourceQueriesMock(&Domain::DataSourceQueries::findAllSelected).when().thenReturn(sourceResult); dataSourceQueriesMock(&Domain::DataSourceQueries::findProjects).when(source).thenReturn(projectResult); Utils::MockObject projectRepositoryMock; Utils::MockObject contextQueriesMock; contextQueriesMock(&Domain::ContextQueries::findAll).when().thenReturn(contextResult); Utils::MockObject contextRepositoryMock; Utils::MockObject taskRepositoryMock; Presentation::AvailablePagesModel 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 contextsIndex = model->index(s_contextRow, 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).thenReturn(job); auto data = std::make_unique(); data->setData(QStringLiteral("application/x-zanshin-object"), "object"); data->setProperty("objects", QVariant::fromValue(Domain::Task::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::Ptr::create(); auto sourceResult = Domain::QueryResult::create(sourceProvider); // Empty context provider auto contextProvider = Domain::QueryResultProvider::Ptr::create(); auto contextResult = Domain::QueryResult::create(contextProvider); // One task (used for dropping later on) Domain::Task::Ptr taskToDrop(new Domain::Task); taskToDrop->setTitle(QStringLiteral("taskDropped")); // context mocking Utils::MockObject contextQueriesMock; contextQueriesMock(&Domain::ContextQueries::findAll).when().thenReturn(contextResult); Utils::MockObject contextRepositoryMock; // sources mocking Utils::MockObject dataSourceQueriesMock; dataSourceQueriesMock(&Domain::DataSourceQueries::findAllSelected).when().thenReturn(sourceResult); Utils::MockObject projectRepositoryMock; Utils::MockObject taskRepositoryMock; Presentation::AvailablePagesModel 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).thenReturn(new FakeJob(this)); auto data = std::make_unique(); data->setData(QStringLiteral("application/x-zanshin-object"), "object"); data->setProperty("objects", QVariant::fromValue(Domain::Task::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(AvailablePagesModelTest) #include "availablepagesmodeltest.moc"