diff --git a/src/widgets/editorview.cpp b/src/widgets/editorview.cpp index cb07bf96..39090f8c 100644 --- a/src/widgets/editorview.cpp +++ b/src/widgets/editorview.cpp @@ -1,370 +1,370 @@ /* 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 "editorview.h" #include #include #include #include #include #include #include #include "kdateedit.h" #include "addressline/addresseelineedit.h" #include "domain/artifact.h" #include "ui_editorview.h" using namespace Widgets; EditorView::EditorView(QWidget *parent) : QWidget(parent), m_model(Q_NULLPTR), ui(new Ui::EditorView), m_delegateEdit(Q_NULLPTR) { m_requestFileNameFunction = [](QWidget *parent) { return QFileDialog::getOpenFileName(parent, i18n("Add Attachment")); }; ui->setupUi(this); // To avoid having unit tests talking to akonadi // while we don't need the completion for them if (qEnvironmentVariableIsEmpty("ZANSHIN_UNIT_TEST_RUN")) m_delegateEdit = new KPIM::AddresseeLineEdit(ui->delegateEditPlaceHolder); else m_delegateEdit = new KLineEdit(ui->delegateEditPlaceHolder); // placing our special DelegateEdit into the placeholder we prepared m_delegateEdit->setObjectName("delegateEdit"); ui->delegateToLabel->setBuddy(m_delegateEdit); ui->delegateEditPlaceHolder->layout()->addWidget(m_delegateEdit); ui->startDateEdit->setMinimumContentsLength(10); ui->dueDateEdit->setMinimumContentsLength(10); ui->recurrenceCombo->addItem(i18n("Never"), QVariant::fromValue(Domain::Task::NoRecurrence)); ui->recurrenceCombo->addItem(i18n("Daily"), QVariant::fromValue(Domain::Task::RecursDaily)); ui->recurrenceCombo->addItem(i18n("Weekly"), QVariant::fromValue(Domain::Task::RecursWeekly)); ui->recurrenceCombo->addItem(i18n("Monthly"), QVariant::fromValue(Domain::Task::RecursMonthly)); // Make sure our minimum width is always the one with // the task group visible ui->layout->activate(); setMinimumWidth(minimumSizeHint().width()); ui->delegateLabel->setVisible(false); ui->taskGroup->setVisible(false); ui->textEdit->installEventFilter(this); ui->startDateEdit->installEventFilter(this); ui->dueDateEdit->installEventFilter(this); ui->doneButton->installEventFilter(this); ui->recurrenceCombo->installEventFilter(this); m_delegateEdit->installEventFilter(this); connect(ui->textEdit, &QPlainTextEdit::textChanged, this, &EditorView::onTextEditChanged); connect(ui->startDateEdit, &KPIM::KDateEdit::dateEntered, this, &EditorView::onStartEditEntered); connect(ui->dueDateEdit, &KPIM::KDateEdit::dateEntered, this, &EditorView::onDueEditEntered); connect(ui->doneButton, &QAbstractButton::toggled, this, &EditorView::onDoneButtonChanged); connect(ui->startTodayButton, &QAbstractButton::clicked, this, &EditorView::onStartTodayClicked); connect(ui->recurrenceCombo, static_cast(&QComboBox::currentIndexChanged), this, &EditorView::onRecurrenceComboChanged); connect(ui->attachmentList, &QAbstractItemView::doubleClicked, this, &EditorView::onAttachmentDoubleClicked); connect(ui->addAttachmentButton, &QToolButton::clicked, this, &EditorView::onAddAttachmentClicked); connect(ui->removeAttachmentButton, &QToolButton::clicked, this, &EditorView::onRemoveAttachmentClicked); connect(m_delegateEdit, &KLineEdit::returnPressed, this, &EditorView::onDelegateEntered); setEnabled(false); } EditorView::~EditorView() { delete ui; } QObject *EditorView::model() const { return m_model; } EditorView::RequestFileNameFunction EditorView::requestFileNameFunction() const { return m_requestFileNameFunction; } void EditorView::setModel(QObject *model) { if (model == m_model) return; if (m_model) { disconnect(ui->attachmentList->selectionModel(), &QItemSelectionModel::selectionChanged, this, &EditorView::onAttachmentSelectionChanged); ui->attachmentList->setModel(Q_NULLPTR); disconnect(m_model, Q_NULLPTR, this, Q_NULLPTR); disconnect(this, Q_NULLPTR, m_model, Q_NULLPTR); } m_model = model; setEnabled(m_model); if (!m_model) { ui->taskGroup->setVisible(false); ui->textEdit->clear(); return; } auto attachments = m_model->property("attachmentModel").value(); ui->attachmentList->setModel(attachments); connect(ui->attachmentList->selectionModel(), &QItemSelectionModel::selectionChanged, this, &EditorView::onAttachmentSelectionChanged); onArtifactChanged(); onTextOrTitleChanged(); onHasTaskPropertiesChanged(); onStartDateChanged(); onDueDateChanged(); onDoneChanged(); onRecurrenceChanged(); onDelegateTextChanged(); onAttachmentSelectionChanged(); connect(m_model, SIGNAL(artifactChanged(Domain::Artifact::Ptr)), this, SLOT(onArtifactChanged())); connect(m_model, SIGNAL(hasTaskPropertiesChanged(bool)), this, SLOT(onHasTaskPropertiesChanged())); connect(m_model, SIGNAL(titleChanged(QString)), this, SLOT(onTextOrTitleChanged())); connect(m_model, SIGNAL(textChanged(QString)), this, SLOT(onTextOrTitleChanged())); - connect(m_model, SIGNAL(startDateChanged(QDateTime)), this, SLOT(onStartDateChanged())); - connect(m_model, SIGNAL(dueDateChanged(QDateTime)), this, SLOT(onDueDateChanged())); + connect(m_model, SIGNAL(startDateChanged(QDate)), this, SLOT(onStartDateChanged())); + connect(m_model, SIGNAL(dueDateChanged(QDate)), this, SLOT(onDueDateChanged())); connect(m_model, SIGNAL(doneChanged(bool)), this, SLOT(onDoneChanged())); connect(m_model, SIGNAL(recurrenceChanged(Domain::Task::Recurrence)), this, SLOT(onRecurrenceChanged())); connect(m_model, SIGNAL(delegateTextChanged(QString)), this, SLOT(onDelegateTextChanged())); connect(this, SIGNAL(titleChanged(QString)), m_model, SLOT(setTitle(QString))); connect(this, SIGNAL(textChanged(QString)), m_model, SLOT(setText(QString))); - connect(this, SIGNAL(startDateChanged(QDateTime)), m_model, SLOT(setStartDate(QDateTime))); - connect(this, SIGNAL(dueDateChanged(QDateTime)), m_model, SLOT(setDueDate(QDateTime))); + connect(this, SIGNAL(startDateChanged(QDate)), m_model, SLOT(setStartDate(QDate))); + connect(this, SIGNAL(dueDateChanged(QDate)), m_model, SLOT(setDueDate(QDate))); connect(this, SIGNAL(doneChanged(bool)), m_model, SLOT(setDone(bool))); connect(this, SIGNAL(recurrenceChanged(Domain::Task::Recurrence)), m_model, SLOT(setRecurrence(Domain::Task::Recurrence))); } void EditorView::setRequestFileNameFunction(const EditorView::RequestFileNameFunction &function) { m_requestFileNameFunction = function; } bool EditorView::eventFilter(QObject *watched, QEvent *event) { Q_UNUSED(watched); switch (event->type()) { case QEvent::FocusIn: // We don't want to replace text being edited by the user with older text // coming from akonadi notifications (async, after some older save job) m_model->setProperty("editingInProgress", true); break; case QEvent::FocusOut: // We do react to notifications, however, when not having the focus, // for instance when changing the title using the central list. m_model->setProperty("editingInProgress", false); break; default: break; } return false; } void EditorView::onArtifactChanged() { auto artifact = m_model->property("artifact").value(); setEnabled(artifact); m_delegateEdit->clear(); } void EditorView::onHasTaskPropertiesChanged() { ui->taskGroup->setVisible(m_model->property("hasTaskProperties").toBool()); } void EditorView::onTextOrTitleChanged() { const auto title = m_model->property("title").toString(); const auto text = m_model->property("text").toString(); const auto fullText = QString(title + '\n' + text); if (ui->textEdit->toPlainText() != fullText) // QPlainTextEdit doesn't do this check ui->textEdit->setPlainText(fullText); } void EditorView::onStartDateChanged() { - ui->startDateEdit->setDate(m_model->property("startDate").toDateTime().date()); + ui->startDateEdit->setDate(m_model->property("startDate").toDate()); } void EditorView::onDueDateChanged() { - ui->dueDateEdit->setDate(m_model->property("dueDate").toDateTime().date()); + ui->dueDateEdit->setDate(m_model->property("dueDate").toDate()); } void EditorView::onDoneChanged() { ui->doneButton->setChecked(m_model->property("done").toBool()); } void EditorView::onRecurrenceChanged() { const auto recurrence = m_model->property("recurrence").value(); for (int index = 0; index < ui->recurrenceCombo->count(); index++) { if (recurrence == ui->recurrenceCombo->itemData(index).value()) { ui->recurrenceCombo->setCurrentIndex(index); return; } } } void EditorView::onDelegateTextChanged() { const auto delegateText = m_model->property("delegateText").toString(); const auto labelText = delegateText.isEmpty() ? QString() : i18n("Delegated to: %1", delegateText); ui->delegateLabel->setVisible(!labelText.isEmpty()); ui->delegateLabel->setText(labelText); } void EditorView::onTextEditChanged() { const QString plainText = ui->textEdit->toPlainText(); const int index = plainText.indexOf('\n'); if (index < 0) { emit titleChanged(plainText); emit textChanged(QString()); } else { const QString title = plainText.left(index); const QString text = plainText.mid(index + 1); emit titleChanged(title); emit textChanged(text); } } void EditorView::onStartEditEntered(const QDate &start) { - emit startDateChanged(QDateTime(start, QTime(), Qt::UTC)); + emit startDateChanged(start); } void EditorView::onDueEditEntered(const QDate &due) { - emit dueDateChanged(QDateTime(due, QTime(), Qt::UTC)); + emit dueDateChanged(due); } void EditorView::onDoneButtonChanged(bool checked) { emit doneChanged(checked); } void EditorView::onStartTodayClicked() { QDate today(QDate::currentDate()); ui->startDateEdit->setDate(today); - emit startDateChanged(QDateTime(today, QTime(), Qt::UTC)); + emit startDateChanged(today); } void EditorView::onRecurrenceComboChanged(int index) { const auto recurrence = ui->recurrenceCombo->itemData(index).value(); emit recurrenceChanged(recurrence); } void EditorView::onDelegateEntered() { const auto input = m_delegateEdit->text(); auto name = QString(); auto email = QString(); auto gotMatch = false; QRegExp fullRx("\\s*(.*) <([\\w\\.]+@[\\w\\.]+)>\\s*"); QRegExp emailOnlyRx("\\s*?\\s*"); if (input.contains(fullRx)) { name = fullRx.cap(1); email = fullRx.cap(2); gotMatch = true; } else if (input.contains(emailOnlyRx)) { email = emailOnlyRx.cap(1); gotMatch = true; } if (gotMatch) { QMetaObject::invokeMethod(m_model, "delegate", Q_ARG(QString, name), Q_ARG(QString, email)); m_delegateEdit->clear(); } } void EditorView::onAttachmentSelectionChanged() { if (!m_model) return; const auto selectionModel = ui->attachmentList->selectionModel(); const auto selectedIndexes = selectionModel->selectedIndexes(); ui->removeAttachmentButton->setEnabled(!selectedIndexes.isEmpty()); } void EditorView::onAddAttachmentClicked() { if (!m_model) return; auto fileName = m_requestFileNameFunction(this); if (!fileName.isEmpty()) QMetaObject::invokeMethod(m_model, "addAttachment", Q_ARG(QString, fileName)); } void EditorView::onRemoveAttachmentClicked() { if (!m_model) return; const auto selectionModel = ui->attachmentList->selectionModel(); const auto selectedIndexes = selectionModel->selectedIndexes(); if (!selectedIndexes.isEmpty()) QMetaObject::invokeMethod(m_model, "removeAttachment", Q_ARG(QModelIndex, selectedIndexes.first())); } void EditorView::onAttachmentDoubleClicked(const QModelIndex &index) { if (!m_model) return; QMetaObject::invokeMethod(m_model, "openAttachment", Q_ARG(QModelIndex, index)); } diff --git a/src/widgets/editorview.h b/src/widgets/editorview.h index 8dec5e47..cf77ae59 100644 --- a/src/widgets/editorview.h +++ b/src/widgets/editorview.h @@ -1,112 +1,112 @@ /* 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 WIDGETS_EDITORVIEW_H #define WIDGETS_EDITORVIEW_H #include -#include +#include #include #include "domain/task.h" class QAbstractButton; class QLabel; class QPlainTextEdit; class KLineEdit; namespace KPIM { class KDateEdit; } namespace Ui { class EditorView; } namespace Widgets { class EditorView : public QWidget { Q_OBJECT public: typedef std::function RequestFileNameFunction; explicit EditorView(QWidget *parent = Q_NULLPTR); ~EditorView(); QObject *model() const; RequestFileNameFunction requestFileNameFunction() const; public slots: void setModel(QObject *model); void setRequestFileNameFunction(const RequestFileNameFunction &function); signals: void textChanged(const QString &text); void titleChanged(const QString &title); - void startDateChanged(const QDateTime &start); - void dueDateChanged(const QDateTime &due); + void startDateChanged(const QDate &start); + void dueDateChanged(const QDate &due); void doneChanged(bool done); void recurrenceChanged(Domain::Task::Recurrence recurrence); protected: bool eventFilter(QObject *watched, QEvent *event) Q_DECL_OVERRIDE; private slots: void onArtifactChanged(); void onHasTaskPropertiesChanged(); void onTextOrTitleChanged(); void onStartDateChanged(); void onDueDateChanged(); void onDoneChanged(); void onRecurrenceChanged(); void onDelegateTextChanged(); void onTextEditChanged(); void onStartEditEntered(const QDate &start); void onDueEditEntered(const QDate &due); void onDoneButtonChanged(bool checked); void onStartTodayClicked(); void onRecurrenceComboChanged(int index); void onDelegateEntered(); void onAttachmentSelectionChanged(); void onAddAttachmentClicked(); void onRemoveAttachmentClicked(); void onAttachmentDoubleClicked(const QModelIndex &index); private: QObject *m_model; RequestFileNameFunction m_requestFileNameFunction; Ui::EditorView *ui; KLineEdit *m_delegateEdit; }; } #endif // WIDGETS_EDITORVIEW_H diff --git a/tests/units/widgets/editorviewtest.cpp b/tests/units/widgets/editorviewtest.cpp index 38e8f85d..2ae8cb6e 100644 --- a/tests/units/widgets/editorviewtest.cpp +++ b/tests/units/widgets/editorviewtest.cpp @@ -1,870 +1,869 @@ /* 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 "domain/note.h" #include "domain/task.h" #include "widgets/editorview.h" #include "addressline/addresseelineedit.h" #include "kdateedit.h" class EditorModelStub : public QObject { Q_OBJECT public: EditorModelStub() { setProperty("editingInProgress", false); setProperty("attachmentModel", QVariant::fromValue(&attachmentModel)); } void setPropertyAndSignal(const QByteArray &name, const QVariant &value) { if (property(name) == value) return; if (property("editingInProgress").toBool()) return; setProperty(name, value); if (name == "artifact") emit artifactChanged(value.value()); else if (name == "text") emit textChanged(value.toString()); else if (name == "title") emit titleChanged(value.toString()); else if (name == "done") emit doneChanged(value.toBool()); else if (name == "startDate") - emit startDateChanged(value.toDateTime()); + emit startDateChanged(value.toDate()); else if (name == "dueDate") - emit dueDateChanged(value.toDateTime()); + emit dueDateChanged(value.toDate()); else if (name == "recurrence") emit recurrenceChanged(value.value()); else if (name == "delegateText") emit delegateTextChanged(value.toString()); else if (name == "hasTaskProperties") emit hasTaskPropertiesChanged(value.toBool()); else qFatal("Unsupported property %s", name.constData()); } public slots: void setArtifact(const Domain::Artifact::Ptr &artifact) { setPropertyAndSignal("artifact", QVariant::fromValue(artifact)); } void setTitle(const QString &title) { setPropertyAndSignal("title", title); } void setText(const QString &text) { setPropertyAndSignal("text", text); } void setDone(bool done) { setPropertyAndSignal("done", done); } - void setStartDate(const QDateTime &start) { setPropertyAndSignal("startDate", start); } - void setDueDate(const QDateTime &due) { setPropertyAndSignal("dueDate", due); } + void setStartDate(const QDate &start) { setPropertyAndSignal("startDate", start); } + void setDueDate(const QDate &due) { setPropertyAndSignal("dueDate", due); } void setRecurrence(Domain::Task::Recurrence recurrence) { setPropertyAndSignal("recurrence", QVariant::fromValue(recurrence)); } void setDelegateText(const QString &text) { setPropertyAndSignal("delegateText", text); } void makeTaskAvailable() { setArtifact(Domain::Artifact::Ptr(new Domain::Task)); } void delegate(const QString &name, const QString &email) { delegateNames << name; delegateEmails << email; } void addAttachment(const QString &fileName) { auto item = new QStandardItem(fileName); attachmentModel.appendRow(QList() << item); } void removeAttachment(const QModelIndex &index) { if (index.isValid()) attachmentModel.removeRows(index.row(), 1, QModelIndex()); } signals: void artifactChanged(const Domain::Artifact::Ptr &artifact); void hasTaskPropertiesChanged(bool hasTaskProperties); void textChanged(const QString &text); void titleChanged(const QString &title); void doneChanged(bool done); - void startDateChanged(const QDateTime &date); - void dueDateChanged(const QDateTime &due); + void startDateChanged(const QDate &date); + void dueDateChanged(const QDate &due); void recurrenceChanged(Domain::Task::Recurrence recurrence); void delegateTextChanged(const QString &delegateText); public: QStringList delegateNames; QStringList delegateEmails; QStandardItemModel attachmentModel; }; class EditorViewTest : public QObject { Q_OBJECT public: explicit EditorViewTest(QObject *parent = Q_NULLPTR) : QObject(parent) { qputenv("ZANSHIN_UNIT_TEST_RUN", "1"); } private slots: void shouldHaveDefaultState() { Widgets::EditorView editor; QVERIFY(!editor.isEnabled()); auto textEdit = editor.findChild(QStringLiteral("textEdit")); QVERIFY(textEdit); QVERIFY(textEdit->isVisibleTo(&editor)); auto startDateEdit = editor.findChild(QStringLiteral("startDateEdit")); QVERIFY(startDateEdit); QVERIFY(!startDateEdit->isVisibleTo(&editor)); auto dueDateEdit = editor.findChild(QStringLiteral("dueDateEdit")); QVERIFY(dueDateEdit); QVERIFY(!dueDateEdit->isVisibleTo(&editor)); auto recurrenceCombo = editor.findChild(QStringLiteral("recurrenceCombo")); QVERIFY(recurrenceCombo); QVERIFY(!recurrenceCombo->isVisibleTo(&editor)); auto doneButton = editor.findChild(QStringLiteral("doneButton")); QVERIFY(doneButton); QVERIFY(!doneButton->isVisibleTo(&editor)); auto attachmentList = editor.findChild(QStringLiteral("attachmentList")); QVERIFY(attachmentList); QVERIFY(!attachmentList->isVisibleTo(&editor)); auto addAttachmentButton = editor.findChild(QStringLiteral("addAttachmentButton")); QVERIFY(addAttachmentButton); QVERIFY(!addAttachmentButton->isVisibleTo(&editor)); auto removeAttachmentButton = editor.findChild(QStringLiteral("removeAttachmentButton")); QVERIFY(removeAttachmentButton); QVERIFY(!removeAttachmentButton->isVisibleTo(&editor)); auto delegateLabel = editor.findChild(QStringLiteral("delegateLabel")); QVERIFY(delegateLabel); QVERIFY(!delegateLabel->isVisibleTo(&editor)); auto delegateEdit = editor.findChild(QStringLiteral("delegateEdit")); QVERIFY(delegateEdit); QVERIFY(!delegateEdit->isVisibleTo(&editor)); } void shouldNotCrashForNullModel() { // GIVEN Widgets::EditorView editor; EditorModelStub model; model.setTitle(QStringLiteral("Foo")); model.setText(QStringLiteral("Bar")); model.setPropertyAndSignal("hasTaskProperties", true); editor.setModel(&model); auto textEdit = editor.findChild(QStringLiteral("textEdit")); QVERIFY(textEdit); auto startDateEdit = editor.findChild(QStringLiteral("startDateEdit")); QVERIFY(startDateEdit); auto dueDateEdit = editor.findChild(QStringLiteral("dueDateEdit")); QVERIFY(dueDateEdit); auto recurrenceCombo = editor.findChild(QStringLiteral("recurrenceCombo")); QVERIFY(recurrenceCombo); auto doneButton = editor.findChild(QStringLiteral("doneButton")); QVERIFY(doneButton); auto attachmentList = editor.findChild(QStringLiteral("attachmentList")); QVERIFY(attachmentList); QCOMPARE(attachmentList->model(), &model.attachmentModel); auto addAttachmentButton = editor.findChild(QStringLiteral("addAttachmentButton")); QVERIFY(addAttachmentButton); auto removeAttachmentButton = editor.findChild(QStringLiteral("removeAttachmentButton")); QVERIFY(removeAttachmentButton); auto delegateLabel = editor.findChild(QStringLiteral("delegateLabel")); QVERIFY(delegateLabel); auto delegateEdit = editor.findChild(QStringLiteral("delegateEdit")); QVERIFY(delegateEdit); // WHEN editor.setModel(Q_NULLPTR); // THEN QVERIFY(!editor.isEnabled()); QVERIFY(textEdit->toPlainText().isEmpty()); QVERIFY(!startDateEdit->isVisibleTo(&editor)); QVERIFY(!dueDateEdit->isVisibleTo(&editor)); QVERIFY(!recurrenceCombo->isVisibleTo(&editor)); QVERIFY(!doneButton->isVisibleTo(&editor)); QVERIFY(!attachmentList->isVisibleTo(&editor)); QVERIFY(attachmentList->model() == nullptr); QVERIFY(!addAttachmentButton->isVisibleTo(&editor)); QVERIFY(!removeAttachmentButton->isVisibleTo(&editor)); QVERIFY(!delegateLabel->isVisibleTo(&editor)); QVERIFY(!delegateEdit->isVisibleTo(&editor)); } void shouldShowTaskPropertiesEditorsOnlyForTasks() { // GIVEN Widgets::EditorView editor; EditorModelStub model; model.setPropertyAndSignal("hasTaskProperties", true); auto startDateEdit = editor.findChild(QStringLiteral("startDateEdit")); QVERIFY(!startDateEdit->isVisibleTo(&editor)); auto dueDateEdit = editor.findChild(QStringLiteral("dueDateEdit")); QVERIFY(!dueDateEdit->isVisibleTo(&editor)); auto recurrenceCombo = editor.findChild(QStringLiteral("recurrenceCombo")); QVERIFY(recurrenceCombo); auto doneButton = editor.findChild(QStringLiteral("doneButton")); QVERIFY(!doneButton->isVisibleTo(&editor)); auto attachmentList = editor.findChild(QStringLiteral("attachmentList")); QVERIFY(attachmentList); auto addAttachmentButton = editor.findChild(QStringLiteral("addAttachmentButton")); QVERIFY(addAttachmentButton); auto removeAttachmentButton = editor.findChild(QStringLiteral("removeAttachmentButton")); QVERIFY(removeAttachmentButton); auto delegateLabel = editor.findChild(QStringLiteral("delegateLabel")); QVERIFY(!delegateLabel->isVisibleTo(&editor)); auto delegateEdit = editor.findChild(QStringLiteral("delegateEdit")); QVERIFY(!delegateEdit->isVisibleTo(&editor)); // WHEN editor.setModel(&model); // THEN QVERIFY(startDateEdit->isVisibleTo(&editor)); QVERIFY(dueDateEdit->isVisibleTo(&editor)); QVERIFY(recurrenceCombo->isVisibleTo(&editor)); QVERIFY(doneButton->isVisibleTo(&editor)); QVERIFY(attachmentList->isVisibleTo(&editor)); QVERIFY(addAttachmentButton->isVisibleTo(&editor)); QVERIFY(removeAttachmentButton->isVisibleTo(&editor)); QVERIFY(!delegateLabel->isVisibleTo(&editor)); QVERIFY(delegateEdit->isVisibleTo(&editor)); } void shouldDisplayDelegateLabelOnlyWhenNeeded() { // GIVEN Widgets::EditorView editor; EditorModelStub model; model.makeTaskAvailable(); model.setDelegateText(QStringLiteral("John Doe")); auto delegateLabel = editor.findChild(QStringLiteral("delegateLabel")); QVERIFY(!delegateLabel->isVisibleTo(&editor)); // WHEN editor.setModel(&model); // THEN auto expectedText = i18n("Delegated to: %1", model.property("delegateText").toString()); QVERIFY(delegateLabel->isVisibleTo(&editor)); QCOMPARE(delegateLabel->text(), expectedText); } void shouldBeEnabledOnlyWhenAnArtifactIsAvailable() { // GIVEN Widgets::EditorView editor; EditorModelStub model; // WHEN editor.setModel(&model); // THEN QVERIFY(!editor.isEnabled()); // WHEN // like model.makeTaskAvailable() does: Domain::Artifact::Ptr artifact(new Domain::Task); model.setPropertyAndSignal("artifact", QVariant::fromValue(artifact)); // THEN QVERIFY(editor.isEnabled()); // WHEN model.setPropertyAndSignal("artifact", QVariant::fromValue(Domain::Artifact::Ptr())); // THEN QVERIFY(!editor.isEnabled()); // GIVEN EditorModelStub model2; model2.setPropertyAndSignal("artifact", QVariant::fromValue(artifact)); // WHEN editor.setModel(&model2); // THEN QVERIFY(editor.isEnabled()); } void shouldReactToHasTaskPropertiesChanged() { // GIVEN Widgets::EditorView editor; EditorModelStub model; model.makeTaskAvailable(); editor.setModel(&model); auto startDateEdit = editor.findChild(QStringLiteral("startDateEdit")); QVERIFY(!startDateEdit->isVisibleTo(&editor)); auto dueDateEdit = editor.findChild(QStringLiteral("dueDateEdit")); QVERIFY(!dueDateEdit->isVisibleTo(&editor)); auto doneButton = editor.findChild(QStringLiteral("doneButton")); QVERIFY(!doneButton->isVisibleTo(&editor)); // WHEN model.setPropertyAndSignal("hasTaskProperties", true); // THEN QVERIFY(startDateEdit->isVisibleTo(&editor)); QVERIFY(dueDateEdit->isVisibleTo(&editor)); QVERIFY(doneButton->isVisibleTo(&editor)); } void shouldDisplayModelProperties() { // GIVEN Widgets::EditorView editor; EditorModelStub model; model.makeTaskAvailable(); model.setProperty("hasTaskProperties", true); model.setProperty("title", "My title"); model.setProperty("text", "\nMy text"); - model.setProperty("startDate", QDateTime::currentDateTime()); - model.setProperty("dueDate", QDateTime::currentDateTime().addDays(2)); + model.setProperty("startDate", QDate::currentDate()); + model.setProperty("dueDate", QDate::currentDate().addDays(2)); model.setProperty("recurrence", QVariant::fromValue(Domain::Task::RecursWeekly)); model.setProperty("done", true); auto textEdit = editor.findChild(QStringLiteral("textEdit")); auto startDateEdit = editor.findChild(QStringLiteral("startDateEdit")); auto dueDateEdit = editor.findChild(QStringLiteral("dueDateEdit")); auto recurrenceCombo = editor.findChild(QStringLiteral("recurrenceCombo")); auto doneButton = editor.findChild(QStringLiteral("doneButton")); // WHEN editor.setModel(&model); // THEN QCOMPARE(textEdit->toPlainText(), QString(model.property("title").toString() + '\n' + model.property("text").toString())); - QCOMPARE(startDateEdit->date(), model.property("startDate").toDateTime().date()); - QCOMPARE(dueDateEdit->date(), model.property("dueDate").toDateTime().date()); + QCOMPARE(startDateEdit->date(), model.property("startDate").toDate()); + QCOMPARE(dueDateEdit->date(), model.property("dueDate").toDate()); QCOMPARE(recurrenceCombo->currentData().value(), model.property("recurrence").value()); QCOMPARE(doneButton->isChecked(), model.property("done").toBool()); } void shouldNotReactToChangesWhileEditing() { // GIVEN Widgets::EditorView editor; EditorModelStub model; model.makeTaskAvailable(); model.setProperty("title", "My title"); model.setProperty("text", "\nMy text"); - model.setProperty("startDate", QDateTime::currentDateTime()); - model.setProperty("dueDate", QDateTime::currentDateTime().addDays(2)); + model.setProperty("startDate", QDate::currentDate()); + model.setProperty("dueDate", QDate::currentDate().addDays(2)); model.setProperty("recurrence", QVariant::fromValue(Domain::Task::RecursWeekly)); model.setProperty("done", true); editor.setModel(&model); auto textEdit = editor.findChild(QStringLiteral("textEdit")); auto startDateEdit = editor.findChild(QStringLiteral("startDateEdit")); auto dueDateEdit = editor.findChild(QStringLiteral("dueDateEdit")); auto recurrenceCombo = editor.findChild(QStringLiteral("recurrenceCombo")); auto doneButton = editor.findChild(QStringLiteral("doneButton")); auto delegateLabel = editor.findChild(QStringLiteral("delegateLabel")); auto delegateEdit = editor.findChild(QStringLiteral("delegateEdit")); model.setDelegateText(QStringLiteral("John Doe")); editor.setModel(&model); // WHEN editor.show(); QVERIFY(QTest::qWaitForWindowShown(&editor)); editor.activateWindow(); textEdit->setFocus(); model.setTitle("New title"); model.setText("New text"); startDateEdit->setFocus(); - model.setStartDate(QDateTime::currentDateTime().addDays(1)); + model.setStartDate(QDate::currentDate().addDays(1)); dueDateEdit->setFocus(); - model.setDueDate(QDateTime::currentDateTime().addDays(3)); + model.setDueDate(QDate::currentDate().addDays(3)); recurrenceCombo->setFocus(); model.setRecurrence(Domain::Task::RecursDaily); doneButton->setFocus(); model.setDone(false); delegateEdit->setFocus(); model.setDelegateText(QStringLiteral("John Smith")); // THEN (nothing changed) QCOMPARE(textEdit->toPlainText(), QStringLiteral("My title\n\nMy text")); QCOMPARE(startDateEdit->date(), QDate::currentDate()); QCOMPARE(dueDateEdit->date(), QDate::currentDate().addDays(2)); QCOMPARE(recurrenceCombo->currentData().value(), Domain::Task::RecursWeekly); QVERIFY(doneButton->isChecked()); auto expectedText = i18n("Delegated to: %1", QStringLiteral("John Doe")); QCOMPARE(delegateLabel->text(), expectedText); } void shouldReactToTitleChanges() { // GIVEN Widgets::EditorView editor; EditorModelStub model; model.makeTaskAvailable(); model.setProperty("title", "My title"); model.setProperty("text", "\nMy text"); - model.setProperty("startDate", QDateTime::currentDateTime()); - model.setProperty("dueDate", QDateTime::currentDateTime().addDays(2)); + model.setProperty("startDate", QDate::currentDate()); + model.setProperty("dueDate", QDate::currentDate().addDays(2)); model.setProperty("done", true); editor.setModel(&model); auto textEdit = editor.findChild(QStringLiteral("textEdit")); // WHEN model.setPropertyAndSignal("title", "New title"); // THEN QCOMPARE(textEdit->toPlainText(), QString(model.property("title").toString() + '\n' + model.property("text").toString())); } void shouldReactToTextChanges() { // GIVEN Widgets::EditorView editor; EditorModelStub model; model.makeTaskAvailable(); model.setProperty("title", "My title"); model.setProperty("text", "\nMy text"); - model.setProperty("startDate", QDateTime::currentDateTime()); - model.setProperty("dueDate", QDateTime::currentDateTime().addDays(2)); + model.setProperty("startDate", QDate::currentDate()); + model.setProperty("dueDate", QDate::currentDate().addDays(2)); model.setProperty("done", true); editor.setModel(&model); auto textEdit = editor.findChild(QStringLiteral("textEdit")); // WHEN model.setPropertyAndSignal("text", "\nNew text"); // THEN QCOMPARE(textEdit->toPlainText(), QString(model.property("title").toString() + '\n' + model.property("text").toString())); } void shouldApplyTextEditChanges_data() { QTest::addColumn("plainText"); QTest::addColumn("expectedTitle"); QTest::addColumn("expectedText"); QTest::newRow("nominal case") << "Title\n\nText" << "Title" << "\nText"; QTest::newRow("single line") << "Title" << "Title" << ""; } void shouldApplyTextEditChanges() { // GIVEN Widgets::EditorView editor; EditorModelStub model; model.makeTaskAvailable(); editor.setModel(&model); auto textEdit = editor.findChild(QStringLiteral("textEdit")); // WHEN QFETCH(QString, plainText); textEdit->setPlainText(plainText); // THEN QFETCH(QString, expectedTitle); QCOMPARE(model.property("title").toString(), expectedTitle); QFETCH(QString, expectedText); QCOMPARE(model.property("text").toString(), expectedText); } void shouldReactToDoneChanges() { // GIVEN Widgets::EditorView editor; EditorModelStub model; model.makeTaskAvailable(); model.setProperty("title", "My title"); model.setProperty("text", "\nMy text"); - model.setProperty("startDate", QDateTime::currentDateTime()); - model.setProperty("dueDate", QDateTime::currentDateTime().addDays(2)); + model.setProperty("startDate", QDate::currentDate()); + model.setProperty("dueDate", QDate::currentDate().addDays(2)); model.setProperty("done", false); editor.setModel(&model); auto doneButton = editor.findChild(QStringLiteral("doneButton")); QVERIFY(!doneButton->isChecked()); // WHEN model.setPropertyAndSignal("done", true); // THEN QVERIFY(doneButton->isChecked()); } void shouldApplyDoneButtonChanges() { // GIVEN Widgets::EditorView editor; EditorModelStub model; model.makeTaskAvailable(); editor.setModel(&model); auto doneButton = editor.findChild(QStringLiteral("doneButton")); // WHEN doneButton->setChecked(true); // THEN QCOMPARE(model.property("done").toBool(), true); } void shouldReactToStartDateChanges() { // GIVEN Widgets::EditorView editor; EditorModelStub model; model.makeTaskAvailable(); model.setProperty("title", "My title"); model.setProperty("text", "\nMy text"); - model.setProperty("startDate", QDateTime::currentDateTime()); - model.setProperty("dueDate", QDateTime::currentDateTime().addDays(2)); + model.setProperty("startDate", QDate::currentDate()); + model.setProperty("dueDate", QDate::currentDate().addDays(2)); model.setProperty("done", false); editor.setModel(&model); auto startDateEdit = editor.findChild(QStringLiteral("startDateEdit")); // WHEN - model.setPropertyAndSignal("startDate", QDateTime::currentDateTime().addDays(-2)); + model.setPropertyAndSignal("startDate", QDate::currentDate().addDays(-2)); // THEN - QCOMPARE(startDateEdit->date(), model.property("startDate").toDateTime().date()); + QCOMPARE(startDateEdit->date(), model.property("startDate").toDate()); } void shouldApplyStartDateEditChanges() { // GIVEN Widgets::EditorView editor; EditorModelStub model; model.makeTaskAvailable(); editor.setModel(&model); auto startDateEdit = editor.findChild(QStringLiteral("startDateEdit")); auto today = QDate::currentDate(); // WHEN startDateEdit->setEditText(today.toString(QStringLiteral("dd/MM/yyyy"))); QTest::keyClick(startDateEdit, Qt::Key_Enter); // THEN - const QDateTime newStartDateTime = model.property("startDate").toDateTime(); - QCOMPARE(newStartDateTime.date(), today); - QCOMPARE(newStartDateTime.timeSpec(), Qt::UTC); + const QDate newStartDate = model.property("startDate").toDate(); + QCOMPARE(newStartDate, today); } void shouldReactToDueDateChanges() { // GIVEN Widgets::EditorView editor; EditorModelStub model; model.setProperty("title", "My title"); model.setProperty("text", "\nMy text"); - model.setProperty("startDate", QDateTime::currentDateTime()); - model.setProperty("dueDate", QDateTime::currentDateTime().addDays(2)); + model.setProperty("startDate", QDate::currentDate()); + model.setProperty("dueDate", QDate::currentDate().addDays(2)); model.setProperty("done", false); editor.setModel(&model); auto dueDateEdit = editor.findChild(QStringLiteral("dueDateEdit")); // WHEN - model.setPropertyAndSignal("dueDate", QDateTime::currentDateTime().addDays(-2)); + model.setPropertyAndSignal("dueDate", QDate::currentDate().addDays(-2)); // THEN - QCOMPARE(dueDateEdit->date(), model.property("dueDate").toDateTime().date()); + QCOMPARE(dueDateEdit->date(), model.property("dueDate").toDate()); } void shouldApplyDueDateEditChanges() { // GIVEN Widgets::EditorView editor; EditorModelStub model; model.makeTaskAvailable(); editor.setModel(&model); auto dueDateEdit = editor.findChild(QStringLiteral("dueDateEdit")); auto today = QDate::currentDate(); // WHEN QVERIFY(dueDateEdit->isEnabled()); dueDateEdit->setEditText(today.toString(QStringLiteral("dd/MM/yyyy"))); QTest::keyClick(dueDateEdit, Qt::Key_Enter); // THEN - QCOMPARE(model.property("dueDate").toDateTime().date(), today); + QCOMPARE(model.property("dueDate").toDate(), today); } void shouldApplyStartTodayChanges() { // GIVEN Widgets::EditorView editor; EditorModelStub model; model.makeTaskAvailable(); editor.setModel(&model); QAbstractButton *startTodayButton = editor.findChild(QStringLiteral("startTodayButton")); QVERIFY(startTodayButton); auto startDateEdit = editor.findChild(QStringLiteral("startDateEdit")); auto today = QDate::currentDate(); // WHEN QVERIFY(startTodayButton->isEnabled()); startTodayButton->click(); // THEN QCOMPARE(startDateEdit->currentText(), today.toString(QStringLiteral("dd/MM/yyyy"))); - QCOMPARE(model.property("startDate").toDateTime().date(), today); + QCOMPARE(model.property("startDate").toDate(), today); } void shouldReactToRecurrenceChanges() { // GIVEN Widgets::EditorView editor; EditorModelStub model; model.makeTaskAvailable(); model.setProperty("title", "My title"); model.setProperty("text", "\nMy text"); - model.setProperty("startDate", QDateTime::currentDateTime()); - model.setProperty("dueDate", QDateTime::currentDateTime().addDays(2)); + model.setProperty("startDate", QDate::currentDate()); + model.setProperty("dueDate", QDate::currentDate().addDays(2)); model.setProperty("recurrence", QVariant::fromValue(Domain::Task::RecursWeekly)); model.setProperty("done", false); editor.setModel(&model); auto recurrenceCombo = editor.findChild(QStringLiteral("recurrenceCombo")); // WHEN model.setPropertyAndSignal("recurrence", Domain::Task::RecursMonthly); // THEN QCOMPARE(recurrenceCombo->currentData().value(), model.property("recurrence").value()); } void shouldApplyRecurrenceComboChanges() { // GIVEN Widgets::EditorView editor; EditorModelStub model; model.makeTaskAvailable(); editor.setModel(&model); auto recurrenceCombo = editor.findChild(QStringLiteral("recurrenceCombo")); // WHEN recurrenceCombo->setCurrentIndex(2); // Weekly // THEN QCOMPARE(model.property("recurrence").value(), Domain::Task::RecursWeekly); } void shouldAddAttachments() { // GIVEN Widgets::EditorView editor; editor.setRequestFileNameFunction([](QWidget*) { return "/tmp/foobar"; }); EditorModelStub model; model.makeTaskAvailable(); editor.setModel(&model); auto addAttachmentButton = editor.findChild(QStringLiteral("addAttachmentButton")); // WHEN addAttachmentButton->click(); // THEN QCOMPARE(model.attachmentModel.rowCount(), 1); QCOMPARE(model.attachmentModel.data(model.attachmentModel.index(0, 0)).toString(), QStringLiteral("/tmp/foobar")); } void shouldRemoveAttachments() { // GIVEN Widgets::EditorView editor; EditorModelStub model; model.makeTaskAvailable(); model.addAttachment("/tmp/foo"); model.addAttachment("/tmp/bar"); editor.setModel(&model); auto attachmentList = editor.findChild(QStringLiteral("attachmentList")); auto removeAttachmentButton = editor.findChild(QStringLiteral("removeAttachmentButton")); // THEN QVERIFY(!removeAttachmentButton->isEnabled()); // WHEN attachmentList->selectionModel()->select(model.attachmentModel.index(0, 0), QItemSelectionModel::ClearAndSelect); // THEN QVERIFY(removeAttachmentButton->isEnabled()); // WHEN removeAttachmentButton->click(); // THEN QCOMPARE(model.attachmentModel.rowCount(), 1); QCOMPARE(model.attachmentModel.data(model.attachmentModel.index(0, 0)).toString(), QStringLiteral("/tmp/bar")); } void shouldReactToDelegateTextChanges() { // GIVEN Widgets::EditorView editor; EditorModelStub model; model.makeTaskAvailable(); model.setDelegateText(QStringLiteral("John Doe")); editor.setModel(&model); auto delegateLabel = editor.findChild(QStringLiteral("delegateLabel")); // WHEN model.setDelegateText(QStringLiteral("John Smith")); // THEN auto expectedText = i18n("Delegated to: %1", model.property("delegateText").toString()); QCOMPARE(delegateLabel->text(), expectedText); } void shouldClearDelegateEditOnArtifactChanges() { // GIVEN Widgets::EditorView editor; EditorModelStub model; model.makeTaskAvailable(); editor.setModel(&model); auto delegateEdit = editor.findChild(QStringLiteral("delegateEdit")); delegateEdit->setText(QStringLiteral("Foo")); // WHEN model.makeTaskAvailable(); // simulates an artifact change // THEN QVERIFY(delegateEdit->text().isEmpty()); } void shouldRequestDelegationOnInput_data() { QTest::addColumn("userInput"); QTest::addColumn("expectedName"); QTest::addColumn("expectedEmail"); QTest::addColumn("expectedCall"); QTest::newRow("nominal case") << "John Doe " << "John Doe" << "john@doe.com" << true; QTest::newRow("nominal case") << "John Doe " << "John Doe" << "j.doe@some.server.com" << true; QTest::newRow("only name") << "John Doe" << QString() << QString() << false; QTest::newRow("only email") << "john@doe.com" << QString() << "john@doe.com" << true; QTest::newRow("only email again") << "" << QString() << "john@doe.com" << true; QTest::newRow("nonsense case") << "bleh" << QString() << QString() << false; } void shouldRequestDelegationOnInput() { // GIVEN QFETCH(QString, userInput); QFETCH(QString, expectedName); QFETCH(QString, expectedEmail); QFETCH(bool, expectedCall); Widgets::EditorView editor; EditorModelStub model; model.makeTaskAvailable(); editor.setModel(&model); auto delegateEdit = editor.findChild(QStringLiteral("delegateEdit")); // WHEN QVERIFY(delegateEdit->isEnabled()); delegateEdit->setText(userInput); QTest::keyClick(delegateEdit, Qt::Key_Enter); // THEN if (expectedCall) { QCOMPARE(model.delegateNames.size(), 1); QCOMPARE(model.delegateNames.first(), expectedName); QCOMPARE(model.delegateEmails.size(), 1); QCOMPARE(model.delegateEmails.first(), expectedEmail); QVERIFY(delegateEdit->text().isEmpty()); } else { QCOMPARE(model.delegateNames.size(), 0); QCOMPARE(model.delegateEmails.size(), 0); } } }; ZANSHIN_TEST_MAIN(EditorViewTest) #include "editorviewtest.moc"