diff --git a/src/presentation/contextpagemodel.h b/src/presentation/contextpagemodel.h --- a/src/presentation/contextpagemodel.h +++ b/src/presentation/contextpagemodel.h @@ -45,7 +45,7 @@ Domain::Context::Ptr context() const; public slots: - Domain::Artifact::Ptr addItem(const QString &title) Q_DECL_OVERRIDE; + Domain::Artifact::Ptr addItem(const QString &title, const QModelIndex &parentIndex = QModelIndex()) Q_DECL_OVERRIDE; void removeItem(const QModelIndex &index) Q_DECL_OVERRIDE; void promoteItem(const QModelIndex &index) Q_DECL_OVERRIDE; diff --git a/src/presentation/contextpagemodel.cpp b/src/presentation/contextpagemodel.cpp --- a/src/presentation/contextpagemodel.cpp +++ b/src/presentation/contextpagemodel.cpp @@ -55,11 +55,17 @@ return m_context; } -Domain::Artifact::Ptr ContextPageModel::addItem(const QString &title) +Domain::Artifact::Ptr ContextPageModel::addItem(const QString &title, const QModelIndex &parentIndex) { + const auto parentData = parentIndex.data(QueryTreeModel::ObjectRole); + const auto parentArtifact = parentData.value(); + const auto parentTask = parentArtifact.objectCast(); + auto task = Domain::Task::Ptr::create(); task->setTitle(title); - const auto job = m_taskRepository->createInContext(task, m_context); + + const auto job = parentTask ? m_taskRepository->createChild(task, parentTask) + : m_taskRepository->createInContext(task, m_context); installHandler(job, tr("Cannot add task %1 in context %2").arg(title).arg(m_context->name())); return task; diff --git a/src/presentation/noteinboxpagemodel.h b/src/presentation/noteinboxpagemodel.h --- a/src/presentation/noteinboxpagemodel.h +++ b/src/presentation/noteinboxpagemodel.h @@ -40,7 +40,7 @@ const Domain::NoteRepository::Ptr ¬eRepository, QObject *parent = Q_NULLPTR); - Domain::Artifact::Ptr addItem(const QString &title) Q_DECL_OVERRIDE; + Domain::Artifact::Ptr addItem(const QString &title, const QModelIndex &parentIndex = QModelIndex()) Q_DECL_OVERRIDE; void removeItem(const QModelIndex &index) Q_DECL_OVERRIDE; void promoteItem(const QModelIndex &index) Q_DECL_OVERRIDE; diff --git a/src/presentation/noteinboxpagemodel.cpp b/src/presentation/noteinboxpagemodel.cpp --- a/src/presentation/noteinboxpagemodel.cpp +++ b/src/presentation/noteinboxpagemodel.cpp @@ -39,7 +39,7 @@ { } -Domain::Artifact::Ptr NoteInboxPageModel::addItem(const QString &title) +Domain::Artifact::Ptr NoteInboxPageModel::addItem(const QString &title, const QModelIndex &) { auto note = Domain::Note::Ptr::create(); note->setTitle(title); diff --git a/src/presentation/pagemodel.h b/src/presentation/pagemodel.h --- a/src/presentation/pagemodel.h +++ b/src/presentation/pagemodel.h @@ -27,13 +27,13 @@ #include +#include + #include "domain/artifact.h" #include "presentation/metatypes.h" #include "presentation/errorhandlingmodelbase.h" -class QModelIndex; - namespace Presentation { class PageModel : public QObject, public ErrorHandlingModelBase @@ -46,7 +46,7 @@ QAbstractItemModel *centralListModel(); public slots: - virtual Domain::Artifact::Ptr addItem(const QString &title) = 0; + virtual Domain::Artifact::Ptr addItem(const QString &title, const QModelIndex &parentIndex = QModelIndex()) = 0; virtual void removeItem(const QModelIndex &index) = 0; virtual void promoteItem(const QModelIndex &index) = 0; diff --git a/src/presentation/projectpagemodel.h b/src/presentation/projectpagemodel.h --- a/src/presentation/projectpagemodel.h +++ b/src/presentation/projectpagemodel.h @@ -45,7 +45,7 @@ Domain::Project::Ptr project() const; - Domain::Artifact::Ptr addItem(const QString &title) Q_DECL_OVERRIDE; + Domain::Artifact::Ptr addItem(const QString &title, const QModelIndex &parentIndex = QModelIndex()) Q_DECL_OVERRIDE; void removeItem(const QModelIndex &index) Q_DECL_OVERRIDE; void promoteItem(const QModelIndex &index) Q_DECL_OVERRIDE; diff --git a/src/presentation/projectpagemodel.cpp b/src/presentation/projectpagemodel.cpp --- a/src/presentation/projectpagemodel.cpp +++ b/src/presentation/projectpagemodel.cpp @@ -48,11 +48,17 @@ return m_project; } -Domain::Artifact::Ptr ProjectPageModel::addItem(const QString &title) +Domain::Artifact::Ptr ProjectPageModel::addItem(const QString &title, const QModelIndex &parentIndex) { + const auto parentData = parentIndex.data(QueryTreeModel::ObjectRole); + const auto parentArtifact = parentData.value(); + const auto parentTask = parentArtifact.objectCast(); + auto task = Domain::Task::Ptr::create(); task->setTitle(title); - const auto job = m_taskRepository->createInProject(task, m_project); + + const auto job = parentTask ? m_taskRepository->createChild(task, parentTask) + : m_taskRepository->createInProject(task, m_project); installHandler(job, tr("Cannot add task %1 in project %2").arg(title).arg(m_project->name())); return task; diff --git a/src/presentation/tagpagemodel.h b/src/presentation/tagpagemodel.h --- a/src/presentation/tagpagemodel.h +++ b/src/presentation/tagpagemodel.h @@ -47,7 +47,7 @@ Domain::Tag::Ptr tag() const; - Domain::Artifact::Ptr addItem(const QString &title) Q_DECL_OVERRIDE; + Domain::Artifact::Ptr addItem(const QString &title, const QModelIndex &parentIndex = QModelIndex()) Q_DECL_OVERRIDE; void removeItem(const QModelIndex &index) Q_DECL_OVERRIDE; void promoteItem(const QModelIndex &index) Q_DECL_OVERRIDE; diff --git a/src/presentation/tagpagemodel.cpp b/src/presentation/tagpagemodel.cpp --- a/src/presentation/tagpagemodel.cpp +++ b/src/presentation/tagpagemodel.cpp @@ -56,7 +56,7 @@ return m_tag; } -Domain::Artifact::Ptr TagPageModel::addItem(const QString &title) +Domain::Artifact::Ptr TagPageModel::addItem(const QString &title, const QModelIndex &) { auto note = Domain::Note::Ptr::create(); note->setTitle(title); diff --git a/src/presentation/taskinboxpagemodel.h b/src/presentation/taskinboxpagemodel.h --- a/src/presentation/taskinboxpagemodel.h +++ b/src/presentation/taskinboxpagemodel.h @@ -40,7 +40,7 @@ const Domain::TaskRepository::Ptr &taskRepository, QObject *parent = Q_NULLPTR); - Domain::Artifact::Ptr addItem(const QString &title) Q_DECL_OVERRIDE; + Domain::Artifact::Ptr addItem(const QString &title, const QModelIndex &parentIndex = QModelIndex()) Q_DECL_OVERRIDE; void removeItem(const QModelIndex &index) Q_DECL_OVERRIDE; void promoteItem(const QModelIndex &index) Q_DECL_OVERRIDE; diff --git a/src/presentation/taskinboxpagemodel.cpp b/src/presentation/taskinboxpagemodel.cpp --- a/src/presentation/taskinboxpagemodel.cpp +++ b/src/presentation/taskinboxpagemodel.cpp @@ -39,11 +39,16 @@ { } -Domain::Artifact::Ptr TaskInboxPageModel::addItem(const QString &title) +Domain::Artifact::Ptr TaskInboxPageModel::addItem(const QString &title, const QModelIndex &parentIndex) { + const auto parentData = parentIndex.data(QueryTreeModel::ObjectRole); + const auto parentArtifact = parentData.value(); + const auto parentTask = parentArtifact.objectCast(); + auto task = Domain::Task::Ptr::create(); task->setTitle(title); - const auto job = m_taskRepository->create(task); + const auto job = parentTask ? m_taskRepository->createChild(task, parentTask) + : m_taskRepository->create(task); installHandler(job, tr("Cannot add task %1 in Inbox").arg(title)); return task; diff --git a/src/presentation/workdaypagemodel.h b/src/presentation/workdaypagemodel.h --- a/src/presentation/workdaypagemodel.h +++ b/src/presentation/workdaypagemodel.h @@ -40,7 +40,7 @@ const Domain::TaskRepository::Ptr &taskRepository, QObject *parent = Q_NULLPTR); - Domain::Artifact::Ptr addItem(const QString &title) Q_DECL_OVERRIDE; + Domain::Artifact::Ptr addItem(const QString &title, const QModelIndex &parentIndex = QModelIndex()) Q_DECL_OVERRIDE; void removeItem(const QModelIndex &index) Q_DECL_OVERRIDE; void promoteItem(const QModelIndex &index) Q_DECL_OVERRIDE; diff --git a/src/presentation/workdaypagemodel.cpp b/src/presentation/workdaypagemodel.cpp --- a/src/presentation/workdaypagemodel.cpp +++ b/src/presentation/workdaypagemodel.cpp @@ -45,12 +45,18 @@ { } -Domain::Artifact::Ptr WorkdayPageModel::addItem(const QString &title) +Domain::Artifact::Ptr WorkdayPageModel::addItem(const QString &title, const QModelIndex &parentIndex) { + const auto parentData = parentIndex.data(QueryTreeModel::ObjectRole); + const auto parentArtifact = parentData.value(); + const auto parentTask = parentArtifact.objectCast(); + auto task = Domain::Task::Ptr::create(); task->setTitle(title); - task->setStartDate(Utils::DateTime::currentDateTime()); - const auto job = m_taskRepository->create(task); + if (!parentTask) + task->setStartDate(Utils::DateTime::currentDateTime()); + const auto job = parentTask ? m_taskRepository->createChild(task, parentTask) + : m_taskRepository->create(task); installHandler(job, tr("Cannot add task %1 in Workday").arg(title)); return task; diff --git a/src/widgets/pageview.cpp b/src/widgets/pageview.cpp --- a/src/widgets/pageview.cpp +++ b/src/widgets/pageview.cpp @@ -160,7 +160,13 @@ if (m_quickAddEdit->text().isEmpty()) return; - QMetaObject::invokeMethod(m_model, "addItem", Q_ARG(QString, m_quickAddEdit->text())); + auto parentIndex = QModelIndex(); + if (m_centralView->selectionModel()->selectedIndexes().size() == 1) + parentIndex = m_centralView->selectionModel()->selectedIndexes().first(); + + QMetaObject::invokeMethod(m_model, "addItem", + Q_ARG(QString, m_quickAddEdit->text()), + Q_ARG(QModelIndex, parentIndex)); m_quickAddEdit->clear(); } diff --git a/tests/units/presentation/applicationmodeltest.cpp b/tests/units/presentation/applicationmodeltest.cpp --- a/tests/units/presentation/applicationmodeltest.cpp +++ b/tests/units/presentation/applicationmodeltest.cpp @@ -77,7 +77,7 @@ explicit FakePageModel(QObject *parent = Q_NULLPTR) : Presentation::PageModel(parent) {} - Domain::Artifact::Ptr addItem(const QString &) Q_DECL_OVERRIDE { return {}; } + Domain::Artifact::Ptr addItem(const QString &, const QModelIndex &) Q_DECL_OVERRIDE { return {}; } void removeItem(const QModelIndex &) Q_DECL_OVERRIDE {} void promoteItem(const QModelIndex &) Q_DECL_OVERRIDE {} diff --git a/tests/units/presentation/contextpagemodeltest.cpp b/tests/units/presentation/contextpagemodeltest.cpp --- a/tests/units/presentation/contextpagemodeltest.cpp +++ b/tests/units/presentation/contextpagemodeltest.cpp @@ -202,7 +202,7 @@ QVERIFY(taskRepositoryMock(&Domain::TaskRepository::associate).when(parentTask, childTask5.objectCast()).exactly(0)); } - void shouldAddTasks() + void shouldAddTasksInContext() { // GIVEN @@ -236,6 +236,48 @@ QCOMPARE(task->title(), title); } + void shouldAddChildTask() + { + // GIVEN + + // One Context + auto context = Domain::Context::Ptr::create(); + + // A task + auto task = Domain::Task::Ptr::create(); + + auto taskProvider = Domain::QueryResultProvider::Ptr::create(); + auto taskResult = Domain::QueryResult::create(taskProvider); + taskProvider->append(task); + + Utils::MockObject contextQueriesMock; + contextQueriesMock(&Domain::ContextQueries::findTopLevelTasks).when(context).thenReturn(taskResult); + + Utils::MockObject contextRepositoryMock; + + Utils::MockObject taskRepositoryMock; + taskRepositoryMock(&Domain::TaskRepository::createChild).when(any(), + any()) + .thenReturn(new FakeJob(this)); + + Presentation::ContextPageModel page(context, + contextQueriesMock.getInstance(), + contextRepositoryMock.getInstance(), + taskRepositoryMock.getInstance()); + + // WHEN + const auto title = QString("New task"); + const auto parentIndex = page.centralListModel()->index(0, 0); + const auto createdTask = page.addItem(title, parentIndex).objectCast(); + + // THEN + QVERIFY(taskRepositoryMock(&Domain::TaskRepository::createChild).when(any(), + any()) + .exactly(1)); + QVERIFY(createdTask); + QCOMPARE(createdTask->title(), title); + } + void shouldRemoveItem() { // GIVEN diff --git a/tests/units/presentation/pagemodeltest.cpp b/tests/units/presentation/pagemodeltest.cpp --- a/tests/units/presentation/pagemodeltest.cpp +++ b/tests/units/presentation/pagemodeltest.cpp @@ -38,7 +38,7 @@ { } - Domain::Artifact::Ptr addItem(const QString &) { return Domain::Artifact::Ptr::create(); } + Domain::Artifact::Ptr addItem(const QString &, const QModelIndex &) { return Domain::Artifact::Ptr::create(); } void removeItem(const QModelIndex &) {} void promoteItem(const QModelIndex &) {} diff --git a/tests/units/presentation/projectpagemodeltest.cpp b/tests/units/presentation/projectpagemodeltest.cpp --- a/tests/units/presentation/projectpagemodeltest.cpp +++ b/tests/units/presentation/projectpagemodeltest.cpp @@ -173,7 +173,7 @@ QVERIFY(taskRepositoryMock(&Domain::TaskRepository::associate).when(rootTask, childTask4).exactly(1)); } - void shouldAddTasks() + void shouldAddTasksInProject() { // GIVEN @@ -207,6 +207,51 @@ QCOMPARE(task->title(), title); } + void shouldAddChildTask() + { + // GIVEN + + // One project + auto project = Domain::Project::Ptr::create(); + + // Two tasks + auto task1 = Domain::Task::Ptr::create(); + auto task2 = Domain::Task::Ptr::create(); + auto topLevelProvider = Domain::QueryResultProvider::Ptr::create(); + auto topLevelResult = Domain::QueryResult::create(topLevelProvider); + topLevelProvider->append(task1); + topLevelProvider->append(task2); + + Utils::MockObject projectQueriesMock; + projectQueriesMock(&Domain::ProjectQueries::findTopLevel).when(project).thenReturn(topLevelResult); + + Utils::MockObject taskQueriesMock; + taskQueriesMock(&Domain::TaskQueries::findChildren).when(task1).thenReturn(Domain::QueryResult::Ptr()); + taskQueriesMock(&Domain::TaskQueries::findChildren).when(task2).thenReturn(Domain::QueryResult::Ptr()); + + Utils::MockObject taskRepositoryMock; + taskRepositoryMock(&Domain::TaskRepository::createChild).when(any(), + any()) + .thenReturn(new FakeJob(this)); + + Presentation::ProjectPageModel page(project, + projectQueriesMock.getInstance(), + taskQueriesMock.getInstance(), + taskRepositoryMock.getInstance()); + + // WHEN + const auto title = QString("New task"); + const auto parentIndex = page.centralListModel()->index(0, 0); + const auto createdTask = page.addItem(title, parentIndex).objectCast(); + + // THEN + QVERIFY(taskRepositoryMock(&Domain::TaskRepository::createChild).when(any(), + any()) + .exactly(1)); + QVERIFY(createdTask); + QCOMPARE(createdTask->title(), title); + } + void shouldGetAnErrorMessageWhenAddTaskFailed() { // GIVEN diff --git a/tests/units/presentation/taskinboxpagemodeltest.cpp b/tests/units/presentation/taskinboxpagemodeltest.cpp --- a/tests/units/presentation/taskinboxpagemodeltest.cpp +++ b/tests/units/presentation/taskinboxpagemodeltest.cpp @@ -165,7 +165,7 @@ QVERIFY(taskRepositoryMock(&Domain::TaskRepository::associate).when(rootTask, childTask4).exactly(1)); } - void shouldAddTasks() + void shouldAddTasksInInbox() { // GIVEN @@ -189,6 +189,44 @@ QCOMPARE(task->title(), title); } + 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::findInboxTopLevel).when().thenReturn(taskResult); + taskQueriesMock(&Domain::TaskQueries::findChildren).when(task1).thenReturn(Domain::QueryResult::Ptr()); + taskQueriesMock(&Domain::TaskQueries::findChildren).when(task2).thenReturn(Domain::QueryResult::Ptr()); + + Utils::MockObject taskRepositoryMock; + taskRepositoryMock(&Domain::TaskRepository::createChild).when(any(), + any()) + .thenReturn(new FakeJob(this)); + + Presentation::TaskInboxPageModel inbox(taskQueriesMock.getInstance(), + taskRepositoryMock.getInstance()); + + // WHEN + const auto title = QString("New task"); + const auto parentIndex = inbox.centralListModel()->index(0, 0); + const auto createdTask = inbox.addItem(title, parentIndex).objectCast(); + + // THEN + QVERIFY(taskRepositoryMock(&Domain::TaskRepository::createChild).when(any(), + any()) + .exactly(1)); + QVERIFY(createdTask); + QCOMPARE(createdTask->title(), title); + } + void shouldGetAnErrorMessageWhenAddTaskFailed() { // GIVEN diff --git a/tests/units/presentation/workdaypagemodeltest.cpp b/tests/units/presentation/workdaypagemodeltest.cpp --- a/tests/units/presentation/workdaypagemodeltest.cpp +++ b/tests/units/presentation/workdaypagemodeltest.cpp @@ -192,7 +192,7 @@ QCOMPARE(task3->isDone(), false); } - void shouldAddTasks() + void shouldAddTasksInWorkdayPage() { // GIVEN @@ -219,6 +219,46 @@ QCOMPARE(task->startDate(), today); } + void shouldAddChildTask() + { + // GIVEN + + // Two tasks + auto task1 = Domain::Task::Ptr::create(); + auto task2 = Domain::Task::Ptr::create(); + auto taskProvider = Domain::QueryResultProvider::Ptr::create(); + auto taskResult = Domain::QueryResult::create(taskProvider); + taskProvider->append(task1); + taskProvider->append(task2); + + Utils::MockObject taskQueriesMock; + taskQueriesMock(&Domain::TaskQueries::findWorkdayTopLevel).when().thenReturn(taskResult); + taskQueriesMock(&Domain::TaskQueries::findChildren).when(task1).thenReturn(Domain::QueryResult::Ptr()); + taskQueriesMock(&Domain::TaskQueries::findChildren).when(task2).thenReturn(Domain::QueryResult::Ptr()); + + Utils::MockObject taskRepositoryMock; + taskRepositoryMock(&Domain::TaskRepository::createChild).when(any(), + any()) + .thenReturn(new FakeJob(this)); + + Presentation::WorkdayPageModel workday(taskQueriesMock.getInstance(), + taskRepositoryMock.getInstance()); + + // WHEN + const auto title = QString("New task"); + const auto parentIndex = workday.centralListModel()->index(0, 0); + const auto createdTask = workday.addItem(title, parentIndex).objectCast(); + + // THEN + QVERIFY(taskRepositoryMock(&Domain::TaskRepository::createChild).when(any(), + any()) + .exactly(1)); + + QVERIFY(createdTask); + QCOMPARE(createdTask->title(), title); + QVERIFY(!createdTask->startDate().isValid()); + } + void shouldDeleteItems() { // GIVEN diff --git a/tests/units/widgets/pageviewtest.cpp b/tests/units/widgets/pageviewtest.cpp --- a/tests/units/widgets/pageviewtest.cpp +++ b/tests/units/widgets/pageviewtest.cpp @@ -72,9 +72,10 @@ } public slots: - void addItem(const QString &name) + void addItem(const QString &name, const QModelIndex &parentIndex) { taskNames << name; + parentIndices << parentIndex; } void removeItem(const QModelIndex &index) @@ -89,6 +90,7 @@ public: QStringList taskNames; + QList parentIndices; QList removedIndices; QList promotedIndices; QStandardItemModel itemModel; @@ -229,7 +231,7 @@ QVERIFY(!filterEdit->hasFocus()); } - void shouldCreateTasksWhenHittingReturn() + void shouldCreateTasksWithNoParentWhenHittingReturnWithoutSelectedIndex() { // GIVEN PageModelStub stubPageModel; @@ -248,6 +250,63 @@ // THEN QCOMPARE(stubPageModel.taskNames, QStringList() << "Foo" << "Bar"); + QCOMPARE(stubPageModel.parentIndices.size(), 2); + QCOMPARE(stubPageModel.parentIndices.first(), QPersistentModelIndex()); + QCOMPARE(stubPageModel.parentIndices.last(), QPersistentModelIndex()); + } + + void shouldCreateTasksWithNoParentWhenHittingReturnWithSeveralSelectedIndices() + { + // GIVEN + PageModelStub stubPageModel; + Q_ASSERT(stubPageModel.property("centralListModel").canConvert()); + stubPageModel.addStubItems(QStringList() << "A" << "B" << "C"); + QPersistentModelIndex index0 = stubPageModel.itemModel.index(0, 0); + QPersistentModelIndex index1 = stubPageModel.itemModel.index(1, 0); + + Widgets::PageView page; + page.setModel(&stubPageModel); + + auto centralView = page.findChild("centralView"); + centralView->selectionModel()->select(index0, QItemSelectionModel::ClearAndSelect); + centralView->selectionModel()->select(index1, QItemSelectionModel::Select); + + auto quickAddEdit = page.findChild("quickAddEdit"); + + // WHEN + QTest::keyClicks(quickAddEdit, "Foo"); + QTest::keyClick(quickAddEdit, Qt::Key_Return); + + // THEN + QCOMPARE(stubPageModel.taskNames, QStringList() << "A" << "B" << "C" << "Foo"); + QCOMPARE(stubPageModel.parentIndices.size(), 1); + QCOMPARE(stubPageModel.parentIndices.first(), QPersistentModelIndex()); + } + + void shouldCreateTasksWithParentWhenHittingReturnWithOneSelectedIndex() + { + // GIVEN + PageModelStub stubPageModel; + Q_ASSERT(stubPageModel.property("centralListModel").canConvert()); + stubPageModel.addStubItems(QStringList() << "A" << "B" << "C"); + QPersistentModelIndex index = stubPageModel.itemModel.index(1, 0); + + Widgets::PageView page; + page.setModel(&stubPageModel); + + auto centralView = page.findChild("centralView"); + centralView->selectionModel()->select(index, QItemSelectionModel::ClearAndSelect); + + auto quickAddEdit = page.findChild("quickAddEdit"); + + // WHEN + QTest::keyClicks(quickAddEdit, "Foo"); + QTest::keyClick(quickAddEdit, Qt::Key_Return); + + // THEN + QCOMPARE(stubPageModel.taskNames, QStringList() << "A" << "B" << "C" << "Foo"); + QCOMPARE(stubPageModel.parentIndices.size(), 1); + QCOMPARE(stubPageModel.parentIndices.first(), index); } void shouldDeleteItemWhenHittingTheDeleteKey()