diff --git a/src/presentation/runningtaskmodel.cpp b/src/presentation/runningtaskmodel.cpp index 78854169..3cd6a450 100644 --- a/src/presentation/runningtaskmodel.cpp +++ b/src/presentation/runningtaskmodel.cpp @@ -1,82 +1,88 @@ /* This file is part of Zanshin Copyright 2016-2017 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 "runningtaskmodel.h" using namespace Presentation; RunningTaskModel::RunningTaskModel(const Domain::TaskQueries::Ptr &taskQueries, const Domain::TaskRepository::Ptr &taskRepository, QObject *parent) : RunningTaskModelInterface(parent), m_queries(taskQueries), m_taskRepository(taskRepository) { // List all tasks to find if any one is already set to "running" if (m_queries) { m_taskList = m_queries->findAll(); Q_ASSERT(m_taskList); m_taskList->addPostInsertHandler([this](const Domain::Task::Ptr &task, int) { if (task->isRunning()) { setRunningTask(task); } }); // if there was a addFinishedHandler, we could reset m_queries and m_taskList to nullptr there to free memory. } } RunningTaskModel::~RunningTaskModel() { } Domain::Task::Ptr RunningTaskModel::runningTask() const { return m_runningTask; } void RunningTaskModel::setRunningTask(const Domain::Task::Ptr &runningTask) { if (m_runningTask) { m_runningTask->setRunning(false); KJob *job = m_taskRepository->update(m_runningTask); installHandler(job, tr("Cannot update task %1 to 'not running'").arg(m_runningTask->title())); } m_runningTask = runningTask; if (m_runningTask) { m_runningTask->setRunning(true); KJob *job = m_taskRepository->update(m_runningTask); installHandler(job, tr("Cannot update task %1 to 'running'").arg(m_runningTask->title())); } emit runningTaskChanged(m_runningTask); } +void RunningTaskModel::taskDeleted(const Domain::Task::Ptr &task) +{ + if (m_runningTask == task) + setRunningTask({}); +} + void RunningTaskModel::stopTask() { setRunningTask(Domain::Task::Ptr()); } void RunningTaskModel::doneTask() { m_runningTask->setDone(true); stopTask(); } diff --git a/src/presentation/runningtaskmodel.h b/src/presentation/runningtaskmodel.h index d169bee1..94b05b2a 100644 --- a/src/presentation/runningtaskmodel.h +++ b/src/presentation/runningtaskmodel.h @@ -1,62 +1,64 @@ /* This file is part of Zanshin Copyright 2016-2017 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_RUNNINGTASKMODEL_H #define PRESENTATION_RUNNINGTASKMODEL_H #include "domain/taskqueries.h" #include "domain/taskrepository.h" #include "runningtaskmodelinterface.h" namespace Presentation { class RunningTaskModel : public RunningTaskModelInterface { Q_OBJECT public: typedef QSharedPointer Ptr; explicit RunningTaskModel(const Domain::TaskQueries::Ptr &taskQueries, const Domain::TaskRepository::Ptr &taskRepository, QObject *parent = nullptr); ~RunningTaskModel(); Domain::Task::Ptr runningTask() const Q_DECL_OVERRIDE; void setRunningTask(const Domain::Task::Ptr &runningTask) Q_DECL_OVERRIDE; + void taskDeleted(const Domain::Task::Ptr &task) Q_DECL_OVERRIDE; + public slots: void stopTask() Q_DECL_OVERRIDE; void doneTask() Q_DECL_OVERRIDE; private: Domain::Task::Ptr m_runningTask; Domain::QueryResult::Ptr m_taskList; Domain::TaskQueries::Ptr m_queries; Domain::TaskRepository::Ptr m_taskRepository; }; } #endif // PRESENTATION_RUNNINGTASKMODEL_H diff --git a/src/presentation/runningtaskmodelinterface.h b/src/presentation/runningtaskmodelinterface.h index daf3e8d0..6e25ff08 100644 --- a/src/presentation/runningtaskmodelinterface.h +++ b/src/presentation/runningtaskmodelinterface.h @@ -1,58 +1,60 @@ /* This file is part of Zanshin Copyright 2017 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_RUNNINGTASKMODELINTERFACE_H #define PRESENTATION_RUNNINGTASKMODELINTERFACE_H #include #include "domain/task.h" #include "errorhandlingmodelbase.h" namespace Presentation { class RunningTaskModelInterface : public QObject, public ErrorHandlingModelBase { Q_OBJECT Q_PROPERTY(Domain::Task::Ptr runningTask READ runningTask WRITE setRunningTask NOTIFY runningTaskChanged) public: typedef QSharedPointer Ptr; explicit RunningTaskModelInterface(QObject *parent = nullptr); ~RunningTaskModelInterface(); virtual Domain::Task::Ptr runningTask() const = 0; virtual void setRunningTask(const Domain::Task::Ptr &runningTask) = 0; + virtual void taskDeleted(const Domain::Task::Ptr &task) = 0; + signals: void runningTaskChanged(const Domain::Task::Ptr &task); public slots: virtual void stopTask() = 0; virtual void doneTask() = 0; }; } #endif // PRESENTATION_RUNNINGTASKMODEL_H diff --git a/src/widgets/pageview.cpp b/src/widgets/pageview.cpp index cc6faef0..240af8ff 100644 --- a/src/widgets/pageview.cpp +++ b/src/widgets/pageview.cpp @@ -1,459 +1,465 @@ /* 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 "pageview.h" #include #include #include #include #include #include #include #include #include #include #include "filterwidget.h" #include "itemdelegate.h" #include "messagebox.h" #include #include "presentation/artifactfilterproxymodel.h" #include "presentation/metatypes.h" #include "presentation/querytreemodelbase.h" #include "presentation/runningtaskmodelinterface.h" namespace Widgets { class PageTreeView : public QTreeView { Q_OBJECT public: using QTreeView::QTreeView; protected: void keyPressEvent(QKeyEvent *event) Q_DECL_OVERRIDE { if (event->key() == Qt::Key_Escape && state() != EditingState) { selectionModel()->clear(); } QTreeView::keyPressEvent(event); } void resizeEvent(QResizeEvent *event) Q_DECL_OVERRIDE { header()->resizeSection(0, event->size().width()); QTreeView::resizeEvent(event); } }; } class PassivePopup : public QFrame { Q_OBJECT public: explicit PassivePopup(QWidget *parent = Q_NULLPTR) : QFrame(parent), m_hideTimer(new QTimer(this)), m_label(new QLabel(this)) { setWindowFlags(Qt::Tool | Qt::X11BypassWindowManagerHint | Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint); setFrameStyle(QFrame::Box | QFrame::Plain); setLineWidth(2); setAttribute(Qt::WA_DeleteOnClose); setLayout(new QVBoxLayout); layout()->addWidget(m_label); connect(m_hideTimer, &QTimer::timeout, this, &QWidget::hide); } void setVisible(bool visible) Q_DECL_OVERRIDE { if (visible) { m_hideTimer->start(2000); } QFrame::setVisible(visible); } void setText(const QString &text) { m_label->setText(text); } private: QTimer *m_hideTimer; QLabel *m_label; }; using namespace Widgets; PageView::PageView(QWidget *parent) : QWidget(parent), m_cancelAction(new QAction(this)), m_model(Q_NULLPTR), m_messageWidget(new KMessageWidget(this)), m_filterWidget(new FilterWidget(this)), m_centralView(new PageTreeView(this)), m_quickAddEdit(new QLineEdit(this)) { m_messageWidget->setObjectName(QStringLiteral("messageWidget")); m_messageWidget->setCloseButtonVisible(true); m_messageWidget->setMessageType(KMessageWidget::Error); m_messageWidget->setWordWrap(true); m_messageWidget->hide(); m_filterWidget->setObjectName(QStringLiteral("filterWidget")); m_filterWidget->hide(); m_centralView->setObjectName(QStringLiteral("centralView")); m_centralView->header()->hide(); m_centralView->setAlternatingRowColors(true); m_centralView->setItemDelegate(new ItemDelegate(this)); m_centralView->setDragDropMode(QTreeView::DragDrop); m_centralView->setSelectionMode(QAbstractItemView::ExtendedSelection); m_centralView->setModel(m_filterWidget->proxyModel()); m_centralView->installEventFilter(this); m_centralView->setItemsExpandable(false); m_centralView->setRootIsDecorated(false); connect(m_centralView->model(), &QAbstractItemModel::rowsInserted, m_centralView, &QTreeView::expandAll); connect(m_centralView->model(), &QAbstractItemModel::layoutChanged, m_centralView, &QTreeView::expandAll); connect(m_centralView->model(), &QAbstractItemModel::modelReset, m_centralView, &QTreeView::expandAll); m_centralView->setStyleSheet(QStringLiteral("QTreeView::branch { border-image: url(none.png); }")); m_quickAddEdit->setObjectName(QStringLiteral("quickAddEdit")); m_quickAddEdit->setPlaceholderText(tr("Type and press enter to add an item")); connect(m_quickAddEdit, &QLineEdit::returnPressed, this, &PageView::onReturnPressed); auto layout = new QVBoxLayout; layout->setContentsMargins(0, 0, 0, 3); layout->addWidget(m_messageWidget); layout->addWidget(m_filterWidget); layout->addWidget(m_centralView); layout->addWidget(m_quickAddEdit); setLayout(layout); m_messageBoxInterface = MessageBox::Ptr::create(); auto addItemAction = new QAction(this); addItemAction->setObjectName(QStringLiteral("addItemAction")); addItemAction->setText(tr("New item")); addItemAction->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); addItemAction->setShortcut(Qt::CTRL | Qt::Key_N); connect(addItemAction, &QAction::triggered, this, &PageView::onAddItemRequested); m_cancelAction->setObjectName(QStringLiteral("cancelAddItemAction")); m_cancelAction->setShortcut(Qt::Key_Escape); addAction(m_cancelAction); connect(m_cancelAction, &QAction::triggered, m_centralView, static_cast(&QWidget::setFocus)); auto removeItemAction = new QAction(this); removeItemAction->setObjectName(QStringLiteral("removeItemAction")); removeItemAction->setText(tr("Remove item")); removeItemAction->setIcon(QIcon::fromTheme(QStringLiteral("list-remove"))); removeItemAction->setShortcut(Qt::Key_Delete); connect(removeItemAction, &QAction::triggered, this, &PageView::onRemoveItemRequested); addAction(removeItemAction); auto promoteItemAction = new QAction(this); promoteItemAction->setObjectName(QStringLiteral("promoteItemAction")); promoteItemAction->setText(tr("Promote item as project")); promoteItemAction->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_P); connect(promoteItemAction, &QAction::triggered, this, &PageView::onPromoteItemRequested); auto filterViewAction = new QAction(this); filterViewAction->setObjectName(QStringLiteral("filterViewAction")); filterViewAction->setText(tr("Filter...")); filterViewAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-find"))); filterViewAction->setShortcut(Qt::CTRL | Qt::Key_F); filterViewAction->setCheckable(true); connect(filterViewAction, &QAction::triggered, this, &PageView::onFilterToggled); m_runTaskAction = new QAction(this); m_runTaskAction->setObjectName(QStringLiteral("runTaskAction")); m_runTaskAction->setShortcut(Qt::CTRL | Qt::Key_Space); m_runTaskAction->setText(tr("Start now")); m_runTaskAction->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-start"))); connect(m_runTaskAction, &QAction::triggered, this, &PageView::onRunTaskTriggered); updateRunTaskAction(); m_actions.insert(QStringLiteral("page_view_add"), addItemAction); m_actions.insert(QStringLiteral("page_view_remove"), removeItemAction); m_actions.insert(QStringLiteral("page_view_promote"), promoteItemAction); m_actions.insert(QStringLiteral("page_view_filter"), filterViewAction); m_actions.insert(QStringLiteral("page_run_task"), m_runTaskAction); } QHash PageView::globalActions() const { return m_actions; } QObject *PageView::model() const { return m_model; } Presentation::RunningTaskModelInterface *PageView::runningTaskModel() const { return m_runningTaskModel; } void PageView::setModel(QObject *model) { if (model == m_model) return; if (m_centralView->selectionModel()) { disconnect(m_centralView->selectionModel(), Q_NULLPTR, this, Q_NULLPTR); } m_filterWidget->proxyModel()->setSourceModel(Q_NULLPTR); m_model = model; setEnabled(m_model); updateRunTaskAction(); if (!m_model) return; QVariant modelProperty = m_model->property("centralListModel"); if (modelProperty.canConvert()) m_filterWidget->proxyModel()->setSourceModel(modelProperty.value()); connect(m_centralView->selectionModel(), &QItemSelectionModel::currentChanged, this, &PageView::onCurrentChanged); } MessageBoxInterface::Ptr PageView::messageBoxInterface() const { return m_messageBoxInterface; } QModelIndexList PageView::selectedIndexes() const { using namespace std::placeholders; const auto selection = m_centralView->selectionModel()->selectedIndexes(); auto sourceIndices = QModelIndexList(); std::transform(selection.constBegin(), selection.constEnd(), std::back_inserter(sourceIndices ), std::bind(&QSortFilterProxyModel::mapToSource, m_filterWidget->proxyModel(), _1)); return sourceIndices; } void PageView::setRunningTaskModel(Presentation::RunningTaskModelInterface *model) { m_runningTaskModel = model; connect(m_runningTaskModel, SIGNAL(runningTaskChanged(Domain::Task::Ptr)), this, SLOT(onRunningTaskChanged(Domain::Task::Ptr))); } void PageView::setMessageBoxInterface(const MessageBoxInterface::Ptr &interface) { m_messageBoxInterface = interface; } void PageView::displayErrorMessage(const QString &message) { m_messageWidget->setText(message); m_messageWidget->animatedShow(); } void PageView::onReturnPressed() { if (m_quickAddEdit->text().isEmpty()) return; 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(); } void PageView::onAddItemRequested() { if (m_quickAddEdit->hasFocus()) return; const auto editTopLeft = m_quickAddEdit->geometry().topLeft(); const auto pos = mapToGlobal(editTopLeft); auto popup = new PassivePopup(m_quickAddEdit); popup->setText(tr("Type and press enter to add an item")); popup->show(); popup->move(pos - QPoint(0, popup->height())); m_quickAddEdit->selectAll(); m_quickAddEdit->setFocus(); } void PageView::onRemoveItemRequested() { const QModelIndexList ¤tIndexes = m_centralView->selectionModel()->selectedIndexes(); if (currentIndexes.isEmpty()) return; QString text; if (currentIndexes.size() > 1) { bool hasDescendants = false; foreach (const QModelIndex ¤tIndex, currentIndexes) { if (!currentIndex.isValid()) continue; if (currentIndex.model()->rowCount(currentIndex) > 0) { hasDescendants = true; break; } } if (hasDescendants) text = tr("Do you really want to delete the selected items and their children?"); else text = tr("Do you really want to delete the selected items?"); } else { const QModelIndex ¤tIndex = currentIndexes.first(); if (!currentIndex.isValid()) return; if (currentIndex.model()->rowCount(currentIndex) > 0) text = tr("Do you really want to delete the selected task and all its children?"); } if (!text.isEmpty()) { QMessageBox::Button button = m_messageBoxInterface->askConfirmation(this, tr("Delete Tasks"), text); bool canRemove = (button == QMessageBox::Yes); if (!canRemove) return; } foreach (const QModelIndex ¤tIndex, currentIndexes) { if (!currentIndex.isValid()) continue; QMetaObject::invokeMethod(m_model, "removeItem", Q_ARG(QModelIndex, currentIndex)); + const auto data = currentIndex.data(Presentation::QueryTreeModelBase::ObjectRole); + if (data.isValid()) { + auto task = data.value().objectCast(); + if (task) + m_runningTaskModel->taskDeleted(task); + } } } void PageView::onPromoteItemRequested() { QModelIndex currentIndex = m_centralView->currentIndex(); if (!currentIndex.isValid()) return; QMetaObject::invokeMethod(m_model, "promoteItem", Q_ARG(QModelIndex, currentIndex)); } void PageView::onFilterToggled(bool show) { m_filterWidget->setVisible(show); if (show) m_filterWidget->setFocus(); else m_filterWidget->clear(); } void PageView::updateRunTaskAction() { const auto artifact = currentArtifact(); const auto task = artifact.objectCast(); m_runTaskAction->setEnabled(task); } void PageView::onRunTaskTriggered() { auto task = currentArtifact().objectCast(); Q_ASSERT(task); // the action is supposed to be disabled otherwise if (task->startDate().isNull()) task->setStartDate(QDateTime::currentDateTime()); m_runningTaskModel->setRunningTask(task); } void PageView::onRunningTaskChanged(const Domain::Task::Ptr &task) { if (!task) { QWidget *toplevel = window(); toplevel->raise(); toplevel->activateWindow(); } } void PageView::onCurrentChanged(const QModelIndex ¤t) { updateRunTaskAction(); auto data = current.data(Presentation::QueryTreeModelBase::ObjectRole); if (!data.isValid()) return; auto artifact = currentArtifact(); if (!artifact) return; emit currentArtifactChanged(artifact); } bool PageView::eventFilter(QObject *object, QEvent *event) { Q_ASSERT(object == m_centralView); switch(event->type()) { case QEvent::FocusIn: m_cancelAction->setEnabled(false); break; case QEvent::FocusOut: m_cancelAction->setEnabled(true); break; default: break; } return false; } Domain::Artifact::Ptr PageView::currentArtifact() const { const auto current = m_centralView->selectionModel()->currentIndex(); const auto data = current.data(Presentation::QueryTreeModelBase::ObjectRole); if (!data.isValid()) return Domain::Artifact::Ptr(); return data.value(); } #include "pageview.moc" diff --git a/tests/units/presentation/runningtaskmodeltest.cpp b/tests/units/presentation/runningtaskmodeltest.cpp index 175d47fa..59edf62a 100644 --- a/tests/units/presentation/runningtaskmodeltest.cpp +++ b/tests/units/presentation/runningtaskmodeltest.cpp @@ -1,170 +1,209 @@ /* This file is part of Zanshin Copyright 2016-2017 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 "utils/mockobject.h" #include "utils/jobhandler.h" #include "presentation/runningtaskmodel.h" #include "testhelpers.h" using namespace mockitopp; using namespace mockitopp::matcher; class TestDependencies { public: TestDependencies() { m_taskProvider = Domain::QueryResultProvider::Ptr::create(); auto taskResult = Domain::QueryResult::create(m_taskProvider); m_taskQueriesMock(&Domain::TaskQueries::findAll).when().thenReturn(taskResult); m_taskQueriesMockInstance = m_taskQueriesMock.getInstance(); Utils::MockObject taskRepositoryMock; taskRepositoryMock(&Domain::TaskRepository::update).when(any()) .thenReturn(0); m_taskRepositoryMockInstance = taskRepositoryMock.getInstance(); } Utils::MockObject m_taskQueriesMock; Domain::TaskQueries::Ptr m_taskQueriesMockInstance; Domain::TaskRepository::Ptr m_taskRepositoryMockInstance; Domain::QueryResultProvider::Ptr m_taskProvider; }; class RunningTaskModelTest : public QObject { Q_OBJECT private slots: void shouldDoInitialListing() { // GIVEN TestDependencies deps; // Three tasks, one being marked as running auto firstTask = Domain::Task::Ptr::create(); firstTask->setTitle(QStringLiteral("rootTask")); auto initialTask = Domain::Task::Ptr::create(); initialTask->setTitle(QStringLiteral("initialTask")); initialTask->setRunning(true); auto otherTask = Domain::Task::Ptr::create(); otherTask->setTitle(QStringLiteral("otherTask")); // WHEN Presentation::RunningTaskModel model(deps.m_taskQueriesMockInstance, deps.m_taskRepositoryMockInstance); QVERIFY(!model.runningTask()); deps.m_taskProvider->append(firstTask); deps.m_taskProvider->append(initialTask); deps.m_taskProvider->append(otherTask); // THEN QCOMPARE(model.runningTask(), initialTask); } void shouldStartTask() { // GIVEN TestDependencies deps; Presentation::RunningTaskModel model(deps.m_taskQueriesMockInstance, deps.m_taskRepositoryMockInstance); Domain::Task::Ptr task = Domain::Task::Ptr::create(); QSignalSpy spy(&model, &Presentation::RunningTaskModel::runningTaskChanged); // WHEN model.setRunningTask(task); // THEN QCOMPARE(model.runningTask(), task); QVERIFY(task->isRunning()); QCOMPARE(spy.count(), 1); QCOMPARE(spy.at(0).at(0).value(), task); } void shouldHandleStopTask() { // GIVEN TestDependencies deps; Presentation::RunningTaskModel model(deps.m_taskQueriesMockInstance, deps.m_taskRepositoryMockInstance); Domain::Task::Ptr task = Domain::Task::Ptr::create(); model.setRunningTask(task); QSignalSpy spy(&model, &Presentation::RunningTaskModel::runningTaskChanged); // WHEN model.stopTask(); // THEN QCOMPARE(model.runningTask(), Domain::Task::Ptr()); QVERIFY(!task->isRunning()); QCOMPARE(spy.count(), 1); QCOMPARE(spy.at(0).at(0).value(), Domain::Task::Ptr()); } void shouldHandleDoneTask() { // GIVEN TestDependencies deps; Presentation::RunningTaskModel model(deps.m_taskQueriesMockInstance, deps.m_taskRepositoryMockInstance); Domain::Task::Ptr task = Domain::Task::Ptr::create(); model.setRunningTask(task); QSignalSpy spy(&model, &Presentation::RunningTaskModel::runningTaskChanged); // WHEN model.doneTask(); // THEN QCOMPARE(model.runningTask(), Domain::Task::Ptr()); QVERIFY(!task->isRunning()); QVERIFY(task->isDone()); QCOMPARE(spy.count(), 1); QCOMPARE(spy.at(0).at(0).value(), Domain::Task::Ptr()); } void shouldHandleSwitchingToAnotherTask() { // GIVEN TestDependencies deps; Presentation::RunningTaskModel model(deps.m_taskQueriesMockInstance, deps.m_taskRepositoryMockInstance); Domain::Task::Ptr task = Domain::Task::Ptr::create(); model.setRunningTask(task); Domain::Task::Ptr task2 = Domain::Task::Ptr::create(); QSignalSpy spy(&model, &Presentation::RunningTaskModel::runningTaskChanged); // WHEN model.setRunningTask(task2); // THEN QCOMPARE(model.runningTask(), task2); QVERIFY(!task->isRunning()); QVERIFY(task2->isRunning()); QCOMPARE(spy.count(), 1); QCOMPARE(spy.at(0).at(0).value(), task2); } + void shouldHandleTaskDeletion() + { + // GIVEN + TestDependencies deps; + Presentation::RunningTaskModel model(deps.m_taskQueriesMockInstance, deps.m_taskRepositoryMockInstance); + Domain::Task::Ptr task = Domain::Task::Ptr::create(); + model.setRunningTask(task); + QSignalSpy spy(&model, &Presentation::RunningTaskModel::runningTaskChanged); + + // WHEN + model.taskDeleted(task); + + // THEN + QCOMPARE(model.runningTask(), Domain::Task::Ptr()); + QVERIFY(!task->isRunning()); + QVERIFY(!task->isDone()); + QCOMPARE(spy.count(), 1); + QCOMPARE(spy.at(0).at(0).value(), Domain::Task::Ptr()); + } + + void shouldIgnoreDeletionOfAnotherTask() + { + // GIVEN + TestDependencies deps; + Presentation::RunningTaskModel model(deps.m_taskQueriesMockInstance, deps.m_taskRepositoryMockInstance); + Domain::Task::Ptr task = Domain::Task::Ptr::create(); + model.setRunningTask(task); + QSignalSpy spy(&model, &Presentation::RunningTaskModel::runningTaskChanged); + Domain::Task::Ptr task2 = Domain::Task::Ptr::create(); + + // WHEN + model.taskDeleted(task2); + + // THEN + QCOMPARE(model.runningTask(), task); + QVERIFY(task->isRunning()); + QCOMPARE(spy.count(), 0); + } + private: }; ZANSHIN_TEST_MAIN(RunningTaskModelTest) #include "runningtaskmodeltest.moc" diff --git a/tests/units/widgets/pageviewtest.cpp b/tests/units/widgets/pageviewtest.cpp index 80838e28..1d1a12b9 100644 --- a/tests/units/widgets/pageviewtest.cpp +++ b/tests/units/widgets/pageviewtest.cpp @@ -1,745 +1,747 @@ /* 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 #include #include #include #include #include #include #include #include "domain/task.h" #include "presentation/artifactfilterproxymodel.h" #include "presentation/metatypes.h" #include "presentation/querytreemodelbase.h" #include "widgets/filterwidget.h" #include "widgets/itemdelegate.h" #include "widgets/pageview.h" #include "messageboxstub.h" class PageModelStub : public QObject { Q_OBJECT Q_PROPERTY(QAbstractItemModel* centralListModel READ centralListModel) public: QAbstractItemModel *centralListModel() { return &itemModel; } QStandardItem *addStubItem(const QString &title, QStandardItem *parentItem = Q_NULLPTR) { QStandardItem *item = new QStandardItem; item->setData(title, Qt::DisplayRole); if (!parentItem) itemModel.appendRow(item); else parentItem->appendRow(item); taskNames << title; return item; } Domain::Task::Ptr addTaskItem(const QString &title, QStandardItem *parentItem = Q_NULLPTR) { auto item = addStubItem(title, parentItem); auto task = Domain::Task::Ptr::create(); task->setTitle(title); item->setData(QVariant::fromValue(task), Presentation::QueryTreeModelBase::ObjectRole); return task; } void addStubItems(const QStringList &list) { foreach (const QString &title, list) { addStubItem(title); } } public slots: void addItem(const QString &name, const QModelIndex &parentIndex) { taskNames << name; parentIndices << parentIndex; } void removeItem(const QModelIndex &index) { removedIndices << index; } void promoteItem(const QModelIndex &index) { promotedIndices << index; } public: QStringList taskNames; QList parentIndices; QList removedIndices; QList promotedIndices; QStandardItemModel itemModel; }; class RunningTaskModelStub : public Presentation::RunningTaskModelInterface { Q_OBJECT public: Domain::Task::Ptr runningTask() const Q_DECL_OVERRIDE { return m_runningTask; } void setRunningTask(const Domain::Task::Ptr &task) Q_DECL_OVERRIDE { m_runningTask = task; } + void taskDeleted(const Domain::Task::Ptr &task) { m_deletedTask = task; } void stopTask() Q_DECL_OVERRIDE {} void doneTask() Q_DECL_OVERRIDE {} private: Domain::Task::Ptr m_runningTask; + Domain::Task::Ptr m_deletedTask; }; class PageViewTest : public QObject { Q_OBJECT private slots: void shouldHaveDefaultState() { Widgets::PageView page; QCOMPARE(page.contentsMargins(), QMargins(0, 0, 0, 0)); QCOMPARE(page.layout()->contentsMargins(), QMargins(0, 0, 0, 3)); auto messageWidget = page.findChild(QStringLiteral("messageWidget")); QVERIFY(messageWidget); QVERIFY(!messageWidget->isVisibleTo(&page)); QVERIFY(!messageWidget->isCloseButtonVisible()); QVERIFY(messageWidget->wordWrap()); QVERIFY(messageWidget->text().isEmpty()); QVERIFY(messageWidget->icon().isNull()); QCOMPARE(messageWidget->messageType(), KMessageWidget::Error); QVERIFY(!messageWidget->isShowAnimationRunning()); QVERIFY(!messageWidget->isHideAnimationRunning()); auto centralView = page.findChild(QStringLiteral("centralView")); QVERIFY(centralView); QVERIFY(centralView->isVisibleTo(&page)); QVERIFY(!centralView->header()->isVisibleTo(&page)); QVERIFY(qobject_cast(centralView->itemDelegate())); QVERIFY(centralView->alternatingRowColors()); QCOMPARE(centralView->dragDropMode(), QTreeView::DragDrop); auto filter = page.findChild(QStringLiteral("filterWidget")); QVERIFY(filter); QVERIFY(!filter->isVisibleTo(&page)); QVERIFY(filter->proxyModel()); QCOMPARE(filter->proxyModel(), centralView->model()); QLineEdit *quickAddEdit = page.findChild(QStringLiteral("quickAddEdit")); QVERIFY(quickAddEdit); QVERIFY(quickAddEdit->isVisibleTo(&page)); QVERIFY(quickAddEdit->text().isEmpty()); QCOMPARE(quickAddEdit->placeholderText(), tr("Type and press enter to add an item")); auto addAction = page.findChild(QStringLiteral("addItemAction")); QVERIFY(addAction); auto cancelAddAction = page.findChild(QStringLiteral("cancelAddItemAction")); QVERIFY(cancelAddAction); auto removeAction = page.findChild(QStringLiteral("removeItemAction")); QVERIFY(removeAction); auto promoteAction = page.findChild(QStringLiteral("promoteItemAction")); QVERIFY(promoteAction); auto filterAction = page.findChild(QStringLiteral("filterViewAction")); QVERIFY(filterAction); QVERIFY(filterAction->isCheckable()); QVERIFY(!filterAction->isChecked()); auto runTaskAction = page.findChild(QStringLiteral("runTaskAction")); QVERIFY(runTaskAction); QVERIFY(!runTaskAction->isEnabled()); auto actions = page.globalActions(); QCOMPARE(actions.value(QStringLiteral("page_view_add")), addAction); QCOMPARE(actions.value(QStringLiteral("page_view_remove")), removeAction); QCOMPARE(actions.value(QStringLiteral("page_view_promote")), promoteAction); QCOMPARE(actions.value(QStringLiteral("page_view_filter")), filterAction); QCOMPARE(actions.value(QStringLiteral("page_run_task")), runTaskAction); } void shouldDisplayListFromPageModel() { // GIVEN QStandardItemModel model; QObject stubPageModel; stubPageModel.setProperty("centralListModel", QVariant::fromValue(static_cast(&model))); Widgets::PageView page; auto centralView = page.findChild(QStringLiteral("centralView")); QVERIFY(centralView); auto proxyModel = qobject_cast(centralView->model()); QVERIFY(proxyModel); QVERIFY(!proxyModel->sourceModel()); // WHEN page.setModel(&stubPageModel); // THEN QCOMPARE(page.model(), &stubPageModel); QVERIFY(page.isEnabled()); QCOMPARE(proxyModel->sourceModel(), &model); } void shouldNotCrashWithNullModel() { // GIVEN QStandardItemModel model; QObject stubPageModel; stubPageModel.setProperty("centralListModel", QVariant::fromValue(static_cast(&model))); Widgets::PageView page; page.setModel(&stubPageModel); auto centralView = page.findChild(QStringLiteral("centralView")); QVERIFY(centralView); auto proxyModel = qobject_cast(centralView->model()); QVERIFY(proxyModel); QCOMPARE(proxyModel->sourceModel(), &model); // WHEN page.setModel(Q_NULLPTR); // THEN QVERIFY(!page.model()); QVERIFY(!page.isEnabled()); QVERIFY(!proxyModel->sourceModel()); } void shouldManageFocusThroughActions() { // GIVEN Widgets::PageView page; auto centralView = page.findChild(QStringLiteral("centralView")); auto quickAddEdit = page.findChild(QStringLiteral("quickAddEdit")); auto filter = page.findChild(QStringLiteral("filterWidget")); auto filterEdit = filter->findChild(); QVERIFY(filterEdit); page.show(); QTest::qWaitForWindowShown(&page); centralView->setFocus(); QVERIFY(centralView->hasFocus()); QVERIFY(!quickAddEdit->hasFocus()); QVERIFY(!filter->isVisibleTo(&page)); QVERIFY(!filterEdit->hasFocus()); auto addAction = page.findChild(QStringLiteral("addItemAction")); auto cancelAddAction = page.findChild(QStringLiteral("cancelAddItemAction")); auto filterAction = page.findChild(QStringLiteral("filterViewAction")); // WHEN addAction->trigger(); // THEN QVERIFY(!centralView->hasFocus()); QVERIFY(quickAddEdit->hasFocus()); QVERIFY(!filter->isVisibleTo(&page)); QVERIFY(!filterEdit->hasFocus()); // WHEN cancelAddAction->trigger(); // THEN QVERIFY(centralView->hasFocus()); QVERIFY(!quickAddEdit->hasFocus()); QVERIFY(!filter->isVisibleTo(&page)); QVERIFY(!filterEdit->hasFocus()); // WHEN filterAction->trigger(); // THEN QVERIFY(!centralView->hasFocus()); QVERIFY(!quickAddEdit->hasFocus()); QVERIFY(filter->isVisibleTo(&page)); QVERIFY(filterEdit->hasFocus()); // WHEN cancelAddAction->trigger(); // THEN QVERIFY(centralView->hasFocus()); QVERIFY(!quickAddEdit->hasFocus()); QVERIFY(filter->isVisibleTo(&page)); QVERIFY(!filterEdit->hasFocus()); } void shouldManageFilterVisibilityThroughAction() { // GIVEN Widgets::PageView page; auto centralView = page.findChild(QStringLiteral("centralView")); auto filter = page.findChild(QStringLiteral("filterWidget")); auto filterEdit = filter->findChild(); QVERIFY(filterEdit); page.show(); QTest::qWaitForWindowShown(&page); centralView->setFocus(); QVERIFY(centralView->hasFocus()); QVERIFY(!filter->isVisibleTo(&page)); QVERIFY(!filterEdit->hasFocus()); auto filterAction = page.findChild(QStringLiteral("filterViewAction")); // WHEN filterAction->trigger(); // THEN QVERIFY(!centralView->hasFocus()); QVERIFY(filter->isVisibleTo(&page)); QVERIFY(filterEdit->hasFocus()); // WHEN filterEdit->setText("Foo"); // THEN QCOMPARE(filterEdit->text(), QString("Foo")); // WHEN filterAction->trigger(); // THEN QVERIFY(centralView->hasFocus()); QVERIFY(!filter->isVisibleTo(&page)); QVERIFY(!filterEdit->hasFocus()); QVERIFY(filterEdit->text().isEmpty()); } void shouldCreateTasksWithNoParentWhenHittingReturnWithoutSelectedIndex() { // GIVEN PageModelStub stubPageModel; Widgets::PageView page; page.setModel(&stubPageModel); auto quickAddEdit = page.findChild(QStringLiteral("quickAddEdit")); // WHEN QTest::keyClick(quickAddEdit, Qt::Key_Return); // Does nothing (edit empty) QTest::keyClicks(quickAddEdit, QStringLiteral("Foo")); QTest::keyClick(quickAddEdit, Qt::Key_Return); QTest::keyClick(quickAddEdit, Qt::Key_Return); // Does nothing (edit empty) QTest::keyClicks(quickAddEdit, QStringLiteral("Bar")); QTest::keyClick(quickAddEdit, Qt::Key_Return); QTest::keyClick(quickAddEdit, Qt::Key_Return); // Does nothing (edit empty) // THEN QCOMPARE(stubPageModel.taskNames, QStringList() << QStringLiteral("Foo") << QStringLiteral("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() << QStringLiteral("A") << QStringLiteral("B") << QStringLiteral("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(QStringLiteral("centralView")); centralView->selectionModel()->select(index0, QItemSelectionModel::ClearAndSelect); centralView->selectionModel()->select(index1, QItemSelectionModel::Select); auto quickAddEdit = page.findChild(QStringLiteral("quickAddEdit")); // WHEN QTest::keyClicks(quickAddEdit, QStringLiteral("Foo")); QTest::keyClick(quickAddEdit, Qt::Key_Return); // THEN QCOMPARE(stubPageModel.taskNames, QStringList() << QStringLiteral("A") << QStringLiteral("B") << QStringLiteral("C") << QStringLiteral("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() << QStringLiteral("A") << QStringLiteral("B") << QStringLiteral("C")); QPersistentModelIndex index = stubPageModel.itemModel.index(1, 0); Widgets::PageView page; page.setModel(&stubPageModel); auto centralView = page.findChild(QStringLiteral("centralView")); centralView->selectionModel()->select(index, QItemSelectionModel::ClearAndSelect); auto quickAddEdit = page.findChild(QStringLiteral("quickAddEdit")); // WHEN QTest::keyClicks(quickAddEdit, QStringLiteral("Foo")); QTest::keyClick(quickAddEdit, Qt::Key_Return); // THEN QCOMPARE(stubPageModel.taskNames, QStringList() << QStringLiteral("A") << QStringLiteral("B") << QStringLiteral("C") << QStringLiteral("Foo")); QCOMPARE(stubPageModel.parentIndices.size(), 1); QCOMPARE(stubPageModel.parentIndices.first(), index); } void shouldDeleteItemWhenHittingTheDeleteKey() { // GIVEN PageModelStub stubPageModel; Q_ASSERT(stubPageModel.property("centralListModel").canConvert()); stubPageModel.addStubItems(QStringList() << QStringLiteral("A") << QStringLiteral("B") << QStringLiteral("C")); QPersistentModelIndex index = stubPageModel.itemModel.index(1, 0); Widgets::PageView page; page.setModel(&stubPageModel); QTreeView *centralView = page.findChild(QStringLiteral("centralView")); centralView->selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect); centralView->setFocus(); // Needed for shortcuts to work page.show(); QTest::qWaitForWindowShown(&page); QTest::qWait(100); // WHEN QTest::keyPress(centralView, Qt::Key_Delete); // THEN QCOMPARE(stubPageModel.removedIndices.size(), 1); QCOMPARE(stubPageModel.removedIndices.first(), index); } void shouldNoteTryToDeleteIfThereIsNoSelection() { // GIVEN PageModelStub stubPageModel; Q_ASSERT(stubPageModel.property("centralListModel").canConvert()); stubPageModel.addStubItems(QStringList() << QStringLiteral("A") << QStringLiteral("B") << QStringLiteral("C")); Widgets::PageView page; page.setModel(&stubPageModel); QTreeView *centralView = page.findChild(QStringLiteral("centralView")); centralView->clearSelection(); page.findChild(QStringLiteral("quickAddEdit"))->setFocus(); // Needed for shortcuts to work page.show(); QTest::qWaitForWindowShown(&page); QTest::qWait(100); // WHEN QTest::keyPress(centralView, Qt::Key_Delete); // THEN QVERIFY(stubPageModel.removedIndices.isEmpty()); } void shouldDisplayNotificationWhenHittingTheDeleteKeyOnAnItemWithChildren() { // GIVEN PageModelStub stubPageModel; Q_ASSERT(stubPageModel.property("centralListModel").canConvert()); stubPageModel.addStubItems(QStringList() << QStringLiteral("A") << QStringLiteral("B")); QStandardItem *parentIndex = stubPageModel.itemModel.item(1, 0); stubPageModel.addStubItem(QStringLiteral("C"), parentIndex); QPersistentModelIndex index = stubPageModel.itemModel.index(1, 0); Widgets::PageView page; page.setModel(&stubPageModel); auto msgbox = MessageBoxStub::Ptr::create(); page.setMessageBoxInterface(msgbox); QTreeView *centralView = page.findChild(QStringLiteral("centralView")); centralView->selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect); QVERIFY(centralView->selectionModel()->currentIndex().isValid()); centralView->setFocus(); // Needed for shortcuts to work page.show(); QTest::qWaitForWindowShown(&page); QTest::qWait(100); // WHEN QTest::keyPress(centralView, Qt::Key_Delete); // THEN QVERIFY(msgbox->called()); QCOMPARE(stubPageModel.removedIndices.size(), 1); QCOMPARE(stubPageModel.removedIndices.first(), index); } void shouldDeleteItemsWhenHittingTheDeleteKey() { // GIVEN PageModelStub stubPageModel; Q_ASSERT(stubPageModel.property("centralListModel").canConvert()); stubPageModel.addStubItems(QStringList() << QStringLiteral("A") << QStringLiteral("B") << QStringLiteral("C")); QPersistentModelIndex index = stubPageModel.itemModel.index(1, 0); QPersistentModelIndex index2 = stubPageModel.itemModel.index(2, 0); Widgets::PageView page; page.setModel(&stubPageModel); auto msgbox = MessageBoxStub::Ptr::create(); page.setMessageBoxInterface(msgbox); QTreeView *centralView = page.findChild(QStringLiteral("centralView")); centralView->selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect); centralView->selectionModel()->setCurrentIndex(index2, QItemSelectionModel::Select); centralView->setFocus(); // Needed for shortcuts to work page.show(); QTest::qWaitForWindowShown(&page); QTest::qWait(100); // WHEN QTest::keyPress(centralView, Qt::Key_Delete); // THEN QVERIFY(msgbox->called()); QCOMPARE(stubPageModel.removedIndices.size(), 2); QCOMPARE(stubPageModel.removedIndices.first(), index); QCOMPARE(stubPageModel.removedIndices.at(1), index2); } void shouldPromoteItem() { // GIVEN PageModelStub stubPageModel; Q_ASSERT(stubPageModel.property("centralListModel").canConvert()); stubPageModel.addStubItems(QStringList() << QStringLiteral("A") << QStringLiteral("B") << QStringLiteral("C")); QPersistentModelIndex index = stubPageModel.itemModel.index(1, 0); Widgets::PageView page; page.setModel(&stubPageModel); auto promoteAction = page.findChild(QStringLiteral("promoteItemAction")); QVERIFY(promoteAction); QTreeView *centralView = page.findChild(QStringLiteral("centralView")); centralView->selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect); centralView->setFocus(); // WHEN promoteAction->trigger(); // THEN QCOMPARE(stubPageModel.promotedIndices.size(), 1); QCOMPARE(stubPageModel.promotedIndices.first(), index); } void shouldNotTryToPromoteItemIfThereIsNoSelection() { // GIVEN PageModelStub stubPageModel; Q_ASSERT(stubPageModel.property("centralListModel").canConvert()); stubPageModel.addStubItems(QStringList() << QStringLiteral("A") << QStringLiteral("B") << QStringLiteral("C")); Widgets::PageView page; page.setModel(&stubPageModel); auto promoteAction = page.findChild(QStringLiteral("promoteItemAction")); QVERIFY(promoteAction); QTreeView *centralView = page.findChild(QStringLiteral("centralView")); centralView->selectionModel()->clear(); centralView->setFocus(); // WHEN promoteAction->trigger(); // THEN QVERIFY(stubPageModel.promotedIndices.isEmpty()); } void shouldClearCentralViewSelectionOnEscape() { // GIVEN PageModelStub stubPageModel; Q_ASSERT(stubPageModel.property("centralListModel").canConvert()); stubPageModel.addStubItems(QStringList() << QStringLiteral("A") << QStringLiteral("B") << QStringLiteral("C")); QPersistentModelIndex index = stubPageModel.itemModel.index(1, 0); Widgets::PageView page; page.setModel(&stubPageModel); auto centralView = page.findChild(QStringLiteral("centralView")); centralView->selectionModel()->select(index, QItemSelectionModel::ClearAndSelect); QVERIFY(!centralView->selectionModel()->selectedIndexes().isEmpty()); // WHEN QTest::keyClick(centralView, Qt::Key_Escape); // THEN QVERIFY(centralView->selectionModel()->selectedIndexes().isEmpty()); } void shouldReturnSelectedIndexes() { // GIVEN PageModelStub stubPageModel; Q_ASSERT(stubPageModel.property("centralListModel").canConvert()); stubPageModel.addStubItems(QStringList() << QStringLiteral("A") << QStringLiteral("B") << QStringLiteral("C")); auto index = stubPageModel.itemModel.index(1, 0); auto index2 = stubPageModel.itemModel.index(2, 0); Widgets::PageView page; page.setModel(&stubPageModel); auto centralView = page.findChild(QStringLiteral("centralView")); auto filterWidget = page.findChild(QStringLiteral("filterWidget")); auto displayedModel = filterWidget->proxyModel(); auto displayedIndex = displayedModel->index(1, 0); auto displayedIndex2 = displayedModel->index(2, 0); // WHEN centralView->selectionModel()->setCurrentIndex(displayedIndex, QItemSelectionModel::ClearAndSelect); centralView->selectionModel()->setCurrentIndex(displayedIndex2, QItemSelectionModel::Select); // THEN auto selectedIndexes = page.selectedIndexes(); QCOMPARE(selectedIndexes.size(), 2); QCOMPARE(selectedIndexes.at(0), index); QCOMPARE(selectedIndexes.at(1), index2); QCOMPARE(selectedIndexes.at(0).model(), index.model()); QCOMPARE(selectedIndexes.at(1).model(), index2.model()); } void shouldDisplayMessageOnError() { // GIVEN Widgets::PageView page; page.show(); QTest::qWaitForWindowShown(&page); QTest::qWait(100); auto messageWidget = page.findChild(QStringLiteral("messageWidget")); QVERIFY(messageWidget); QVERIFY(!messageWidget->isVisibleTo(&page)); QCOMPARE(messageWidget->findChildren().size(), 1); auto closeButton = messageWidget->findChildren().first(); QVERIFY(closeButton); // WHEN page.displayErrorMessage(QStringLiteral("Foo Error")); // THEN QVERIFY(messageWidget->isVisibleTo(&page)); QVERIFY(messageWidget->isCloseButtonVisible()); QCOMPARE(messageWidget->text(), QStringLiteral("Foo Error")); QVERIFY(messageWidget->icon().isNull()); QCOMPARE(messageWidget->messageType(), KMessageWidget::Error); QVERIFY(messageWidget->isShowAnimationRunning()); QVERIFY(!messageWidget->isHideAnimationRunning()); // WHEN QTest::qWait(800); // THEN QVERIFY(!messageWidget->isShowAnimationRunning()); QVERIFY(!messageWidget->isHideAnimationRunning()); // WHEN closeButton->click(); // THEN QVERIFY(!messageWidget->isShowAnimationRunning()); QVERIFY(messageWidget->isHideAnimationRunning()); // WHEN QTest::qWait(800); // THEN QVERIFY(!messageWidget->isShowAnimationRunning()); QVERIFY(!messageWidget->isHideAnimationRunning()); } void shouldRunTask() { // GIVEN PageModelStub stubPageModel; Q_ASSERT(stubPageModel.property("centralListModel").canConvert()); auto task1 = stubPageModel.addTaskItem(QStringLiteral("Task1")); auto task2 = stubPageModel.addTaskItem(QStringLiteral("Task2")); Widgets::PageView page; page.setModel(&stubPageModel); RunningTaskModelStub stubRunningTaskModel; page.setRunningTaskModel(&stubRunningTaskModel); auto centralView = page.findChild(QStringLiteral("centralView")); QModelIndex index = stubPageModel.itemModel.index(0, 0); centralView->selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect); QVERIFY(centralView->selectionModel()->currentIndex().isValid()); auto runTaskAction = page.findChild(QStringLiteral("runTaskAction")); QVERIFY(runTaskAction); QVERIFY(runTaskAction->isEnabled()); // WHEN starting the first task runTaskAction->trigger(); // THEN QCOMPARE(stubRunningTaskModel.property("runningTask").value(), task1); QCOMPARE(task1->startDate().date(), QDate::currentDate()); // WHEN starting the second task QModelIndex index2 = stubPageModel.itemModel.index(1, 0); centralView->selectionModel()->setCurrentIndex(index2, QItemSelectionModel::ClearAndSelect); runTaskAction->trigger(); // THEN QCOMPARE(stubRunningTaskModel.property("runningTask").value(), task2); QCOMPARE(task2->startDate().date(), QDate::currentDate()); } }; ZANSHIN_TEST_MAIN(PageViewTest) #include "pageviewtest.moc" diff --git a/tests/units/widgets/runningtaskwidgettest.cpp b/tests/units/widgets/runningtaskwidgettest.cpp index aec1dbab..84a16052 100644 --- a/tests/units/widgets/runningtaskwidgettest.cpp +++ b/tests/units/widgets/runningtaskwidgettest.cpp @@ -1,174 +1,197 @@ /* This file is part of Zanshin Copyright 2016-2017 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 "widgets/runningtaskwidget.h" #include "presentation/runningtaskmodelinterface.h" #include class RunningTaskModelStub : public Presentation::RunningTaskModelInterface { Q_OBJECT public: Domain::Task::Ptr runningTask() const Q_DECL_OVERRIDE { return m_runningTask; } void setRunningTask(const Domain::Task::Ptr &runningTask) Q_DECL_OVERRIDE { m_runningTask = runningTask; emit runningTaskChanged(m_runningTask); } void stopTask() Q_DECL_OVERRIDE { Q_ASSERT(m_runningTask); setRunningTask(Domain::Task::Ptr()); } void doneTask() Q_DECL_OVERRIDE { Q_ASSERT(m_runningTask); m_runningTask->setDone(true); stopTask(); } + void taskDeleted(const Domain::Task::Ptr &task) Q_DECL_OVERRIDE + { + Q_ASSERT(task); + if (m_runningTask == task) + setRunningTask(Domain::Task::Ptr()); + } + private: Domain::Task::Ptr m_runningTask; }; class RunningTaskWidgetTest : public QObject { Q_OBJECT private slots: void shouldHaveDefaultState() { Widgets::RunningTaskWidget widget; QVERIFY(widget.isHidden()); } void shouldShowWhenRunningATask() { // GIVEN Widgets::RunningTaskWidget widget; auto task = Domain::Task::Ptr::create(); RunningTaskModelStub model; widget.setModel(&model); // WHEN model.setRunningTask(task); // THEN QVERIFY(!widget.isHidden()); } void shouldShowWhenRunningADifferentTask() { // GIVEN Widgets::RunningTaskWidget widget; auto task1 = Domain::Task::Ptr::create(); auto task2 = Domain::Task::Ptr::create(); RunningTaskModelStub model; widget.setModel(&model); model.setRunningTask(task1); // WHEN model.setRunningTask(task2); // THEN QVERIFY(!widget.isHidden()); } void shouldStopAndHideOnClickingStop() { // GIVEN Widgets::RunningTaskWidget widget; auto task = Domain::Task::Ptr::create(); RunningTaskModelStub model; widget.setModel(&model); model.setRunningTask(task); auto button = widget.findChild("stopButton"); QVERIFY(button); // WHEN button->click(); // THEN stopTask should have been called QCOMPARE(model.runningTask(), Domain::Task::Ptr()); QVERIFY(widget.isHidden()); } void shouldMarkAsDoneAndHideOnClickingDone() { // GIVEN Widgets::RunningTaskWidget widget; auto task = Domain::Task::Ptr::create(); RunningTaskModelStub model; widget.setModel(&model); model.setRunningTask(task); QVERIFY(!task->isDone()); auto button = widget.findChild("doneButton"); QVERIFY(button); // WHEN button->click(); // THEN doneTask should have been called QVERIFY(task->isDone()); QVERIFY(widget.isHidden()); } void shouldHideOnExternalStop() { // GIVEN Widgets::RunningTaskWidget widget; auto task = Domain::Task::Ptr::create(); RunningTaskModelStub model; widget.setModel(&model); model.setRunningTask(task); // WHEN model.stopTask(); // THEN QVERIFY(widget.isHidden()); } void shouldHideOnExternalDone() { // GIVEN Widgets::RunningTaskWidget widget; auto task = Domain::Task::Ptr::create(); RunningTaskModelStub model; widget.setModel(&model); model.setRunningTask(task); // WHEN model.doneTask(); // THEN QVERIFY(widget.isHidden()); } + + void shouldHideOnDeletion() + { + // GIVEN + Widgets::RunningTaskWidget widget; + auto task = Domain::Task::Ptr::create(); + RunningTaskModelStub model; + widget.setModel(&model); + model.setRunningTask(task); + + // WHEN + model.taskDeleted(task); + + // THEN + QVERIFY(widget.isHidden()); + } }; ZANSHIN_TEST_MAIN(RunningTaskWidgetTest) #include "runningtaskwidgettest.moc" diff --git a/tests/units/widgets/taskapplicationcomponentstest.cpp b/tests/units/widgets/taskapplicationcomponentstest.cpp index 5f45aefd..678e8a9a 100644 --- a/tests/units/widgets/taskapplicationcomponentstest.cpp +++ b/tests/units/widgets/taskapplicationcomponentstest.cpp @@ -1,88 +1,89 @@ /* This file is part of Zanshin Copyright 2017 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 "widgets/pageview.h" #include "domain/task.h" #include "widgets/taskapplicationcomponents.h" #include "widgets/runningtaskwidget.h" #include "presentation/runningtaskmodelinterface.h" class RunningTaskModelStub : public Presentation::RunningTaskModelInterface { Q_OBJECT public: Domain::Task::Ptr runningTask() const Q_DECL_OVERRIDE { return {}; } void setRunningTask(const Domain::Task::Ptr &) Q_DECL_OVERRIDE {} + void taskDeleted(const Domain::Task::Ptr &) Q_DECL_OVERRIDE {} void stopTask() Q_DECL_OVERRIDE {} void doneTask() Q_DECL_OVERRIDE {} }; class TaskApplicationComponentsTest : public QObject { Q_OBJECT public: explicit TaskApplicationComponentsTest(QObject *parent = Q_NULLPTR) : QObject(parent) { } private slots: void shouldApplyRunningTaskModelToPageView() { // GIVEN Widgets::TaskApplicationComponents components; auto appModelStub = QObjectPtr::create(); RunningTaskModelStub runningTaskModelStub; appModelStub->setProperty("runningTaskModel", QVariant::fromValue(&runningTaskModelStub)); // WHEN components.setModel(appModelStub); auto pageView = components.pageView(); // THEN QCOMPARE(pageView->runningTaskModel(), &runningTaskModelStub); } void shouldApplyRunningTaskModelToRunningTaskWidget() { // GIVEN Widgets::TaskApplicationComponents components; auto appModelStub = QObjectPtr::create(); RunningTaskModelStub runningTaskModelStub; appModelStub->setProperty("runningTaskModel", QVariant::fromValue(&runningTaskModelStub)); // WHEN components.setModel(appModelStub); auto runningTaskWidget = components.runningTaskWidget(); // THEN QCOMPARE(runningTaskWidget->model(), &runningTaskModelStub); } }; ZANSHIN_TEST_MAIN(TaskApplicationComponentsTest) #include "taskapplicationcomponentstest.moc"