File indexing completed on 2025-01-05 04:59:58
0001 /* 0002 * SPDX-FileCopyrightText: 2014 Kevin Ottens <ervin@kde.org> 0003 SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0004 */ 0005 0006 0007 #include <testlib/qtest_zanshin.h> 0008 0009 #include "utils/mockobject.h" 0010 0011 #include <QSignalSpy> 0012 #include <QTemporaryFile> 0013 0014 #include "testlib/fakejob.h" 0015 0016 #include "domain/task.h" 0017 0018 #include "presentation/editormodel.h" 0019 #include "presentation/errorhandler.h" 0020 0021 using namespace mockitopp; 0022 0023 class FakeErrorHandler : public Presentation::ErrorHandler 0024 { 0025 public: 0026 void doDisplayMessage(const QString &message) override 0027 { 0028 m_message = message; 0029 } 0030 0031 QString m_message; 0032 }; 0033 0034 class EditorModelTest : public QObject 0035 { 0036 Q_OBJECT 0037 public: 0038 explicit EditorModelTest(QObject *parent = nullptr) 0039 : QObject(parent) 0040 { 0041 qRegisterMetaType<Domain::Task::Recurrence>(); 0042 Presentation::EditorModel::setAutoSaveDelay(50); 0043 } 0044 0045 private slots: 0046 void shouldHaveEmptyDefaultState() 0047 { 0048 // GIVEN 0049 Presentation::EditorModel model; 0050 0051 // WHEN 0052 // Nothing 0053 0054 // THEN 0055 QVERIFY(model.task().isNull()); 0056 QVERIFY(model.text().isEmpty()); 0057 QVERIFY(model.title().isEmpty()); 0058 QVERIFY(!model.isDone()); 0059 QVERIFY(model.startDate().isNull()); 0060 QVERIFY(model.dueDate().isNull()); 0061 QCOMPARE(model.recurrence(), Domain::Task::NoRecurrence); 0062 QVERIFY(model.attachmentModel() != nullptr); 0063 QVERIFY(!model.hasSaveFunction()); 0064 auto am = model.attachmentModel(); 0065 QCOMPARE(am->rowCount(), 0); 0066 } 0067 0068 void shouldHaveTaskProperties() 0069 { 0070 // GIVEN 0071 Presentation::EditorModel model; 0072 QSignalSpy textSpy(&model, &Presentation::EditorModel::textChanged); 0073 QSignalSpy titleSpy(&model, &Presentation::EditorModel::titleChanged); 0074 QSignalSpy doneSpy(&model, &Presentation::EditorModel::doneChanged); 0075 QSignalSpy startSpy(&model, &Presentation::EditorModel::startDateChanged); 0076 QSignalSpy dueSpy(&model, &Presentation::EditorModel::dueDateChanged); 0077 QSignalSpy recurrenceSpy(&model, &Presentation::EditorModel::recurrenceChanged); 0078 QSignalSpy attachmentSpy(model.attachmentModel(), &QAbstractItemModel::modelReset); 0079 0080 Domain::Task::Attachments attachments; 0081 0082 Domain::Task::Attachment dataAttachment; 0083 dataAttachment.setData("foo"); 0084 dataAttachment.setLabel("dataAttachment"); 0085 dataAttachment.setMimeType("text/plain"); 0086 dataAttachment.setIconName("text-plain"); 0087 attachments.append(dataAttachment); 0088 0089 Domain::Task::Attachment uriAttachment; 0090 uriAttachment.setUri(QUrl("https://www.kde.org")); 0091 uriAttachment.setLabel("uriAttachment"); 0092 uriAttachment.setMimeType("text/html"); 0093 uriAttachment.setIconName("text-html"); 0094 attachments.append(uriAttachment); 0095 0096 auto task = Domain::Task::Ptr::create(); 0097 task->setText(QStringLiteral("description")); 0098 task->setTitle(QStringLiteral("title")); 0099 task->setDone(true); 0100 task->setStartDate(QDate::currentDate()); 0101 task->setDueDate(QDate::currentDate().addDays(2)); 0102 task->setRecurrence(Domain::Task::RecursDaily); 0103 task->setAttachments(attachments); 0104 0105 // WHEN 0106 model.setTask(task); 0107 // To make sure we don't signal too much 0108 model.setText(task->text()); 0109 model.setTitle(task->title()); 0110 model.setDone(task->isDone()); 0111 model.setStartDate(task->startDate()); 0112 model.setDueDate(task->dueDate()); 0113 model.setRecurrence(task->recurrence()); 0114 0115 // THEN 0116 QCOMPARE(textSpy.size(), 1); 0117 QCOMPARE(textSpy.takeFirst().at(0).toString(), task->text()); 0118 QCOMPARE(model.property("text").toString(), task->text()); 0119 0120 QCOMPARE(titleSpy.size(), 1); 0121 QCOMPARE(titleSpy.takeFirst().at(0).toString(), task->title()); 0122 QCOMPARE(model.property("title").toString(), task->title()); 0123 0124 QCOMPARE(doneSpy.size(), 1); 0125 QCOMPARE(doneSpy.takeFirst().at(0).toBool(), task->isDone()); 0126 QCOMPARE(model.property("done").toBool(), task->isDone()); 0127 0128 QCOMPARE(startSpy.size(), 1); 0129 QCOMPARE(startSpy.takeFirst().at(0).toDate(), task->startDate()); 0130 QCOMPARE(model.property("startDate").toDate(), task->startDate()); 0131 0132 QCOMPARE(dueSpy.size(), 1); 0133 QCOMPARE(dueSpy.takeFirst().at(0).toDate(), task->dueDate()); 0134 QCOMPARE(model.property("dueDate").toDate(), task->dueDate()); 0135 0136 QCOMPARE(recurrenceSpy.size(), 1); 0137 QCOMPARE(recurrenceSpy.takeFirst().at(0).value<Domain::Task::Recurrence>(), task->recurrence()); 0138 QCOMPARE(model.property("recurrence").value<Domain::Task::Recurrence>(), task->recurrence()); 0139 0140 QCOMPARE(attachmentSpy.size(), 1); 0141 auto am = model.attachmentModel(); 0142 QCOMPARE(am->rowCount(), 2); 0143 QCOMPARE(am->data(am->index(0, 0), Qt::DisplayRole).toString(), QStringLiteral("dataAttachment")); 0144 QCOMPARE(am->data(am->index(0, 0), Qt::DecorationRole).value<QIcon>(), QIcon::fromTheme("text-plain")); 0145 QCOMPARE(am->data(am->index(1, 0), Qt::DisplayRole).toString(), QStringLiteral("uriAttachment")); 0146 QCOMPARE(am->data(am->index(1, 0), Qt::DecorationRole).value<QIcon>(), QIcon::fromTheme("text-html")); 0147 } 0148 0149 void shouldReactToTaskPropertyChanges_data() 0150 { 0151 QTest::addColumn<Domain::Task::Ptr>("task"); 0152 QTest::addColumn<QByteArray>("propertyName"); 0153 QTest::addColumn<QVariant>("propertyValue"); 0154 QTest::addColumn<QByteArray>("signal"); 0155 0156 QTest::newRow("task text") << Domain::Task::Ptr(Domain::Task::Ptr::create()) 0157 << QByteArray("text") 0158 << QVariant("new text") 0159 << QByteArray(SIGNAL(textChanged(QString))); 0160 0161 QTest::newRow("task title") << Domain::Task::Ptr(Domain::Task::Ptr::create()) 0162 << QByteArray("title") 0163 << QVariant("new title") 0164 << QByteArray(SIGNAL(titleChanged(QString))); 0165 0166 QTest::newRow("task done") << Domain::Task::Ptr(Domain::Task::Ptr::create()) 0167 << QByteArray("done") 0168 << QVariant(true) 0169 << QByteArray(SIGNAL(doneChanged(bool))); 0170 0171 QTest::newRow("task start") << Domain::Task::Ptr(Domain::Task::Ptr::create()) 0172 << QByteArray("startDate") 0173 << QVariant(QDate::currentDate()) 0174 << QByteArray(SIGNAL(startDateChanged(QDate))); 0175 0176 QTest::newRow("task due") << Domain::Task::Ptr(Domain::Task::Ptr::create()) 0177 << QByteArray("dueDate") 0178 << QVariant(QDate::currentDate().addDays(2)) 0179 << QByteArray(SIGNAL(dueDateChanged(QDate))); 0180 0181 QTest::newRow("task recurrence") << Domain::Task::Ptr(Domain::Task::Ptr::create()) 0182 << QByteArray("recurrence") 0183 << QVariant::fromValue(Domain::Task::RecursDaily) 0184 << QByteArray(SIGNAL(recurrenceChanged(Domain::Task::Recurrence))); 0185 } 0186 0187 void shouldReactToTaskPropertyChanges() 0188 { 0189 // GIVEN 0190 QFETCH(Domain::Task::Ptr, task); 0191 QFETCH(QByteArray, propertyName); 0192 QFETCH(QVariant, propertyValue); 0193 QFETCH(QByteArray, signal); 0194 0195 Presentation::EditorModel model; 0196 model.setTask(task); 0197 QSignalSpy spy(&model, signal.constData()); 0198 0199 // WHEN 0200 task->setProperty(propertyName, propertyValue); 0201 0202 // THEN 0203 QCOMPARE(spy.size(), 1); 0204 QCOMPARE(spy.takeFirst().at(0), propertyValue); 0205 QCOMPARE(model.property(propertyName), propertyValue); 0206 } 0207 0208 void shouldNotReactToTaskPropertyChangesWhenEditing_data() 0209 { 0210 shouldReactToTaskPropertyChanges_data(); 0211 } 0212 0213 void shouldNotReactToTaskPropertyChangesWhenEditing() 0214 { 0215 // GIVEN 0216 QFETCH(Domain::Task::Ptr, task); 0217 QFETCH(QByteArray, propertyName); 0218 QFETCH(QVariant, propertyValue); 0219 QFETCH(QByteArray, signal); 0220 0221 Presentation::EditorModel model; 0222 model.setTask(task); 0223 QSignalSpy spy(&model, signal.constData()); 0224 0225 // WHEN 0226 const auto oldPropertyValue = task->property(propertyName); 0227 model.setEditingInProgress(true); 0228 task->setProperty(propertyName, propertyValue); 0229 0230 // THEN 0231 QVERIFY(spy.isEmpty()); 0232 QCOMPARE(model.property(propertyName), oldPropertyValue); 0233 } 0234 0235 void shouldApplyChangesBackToTaskAfterADelay_data() 0236 { 0237 shouldReactToTaskPropertyChanges_data(); 0238 } 0239 0240 void shouldApplyChangesBackToTaskAfterADelay() 0241 { 0242 // GIVEN 0243 QFETCH(Domain::Task::Ptr, task); 0244 QFETCH(QByteArray, propertyName); 0245 QFETCH(QVariant, propertyValue); 0246 QFETCH(QByteArray, signal); 0247 0248 auto savedTask = Domain::Task::Ptr(); 0249 auto save = [this, &savedTask] (const Domain::Task::Ptr &task) { 0250 savedTask = task; 0251 return new FakeJob(this); 0252 }; 0253 0254 Presentation::EditorModel model; 0255 model.setSaveFunction(save); 0256 model.setTask(task); 0257 QSignalSpy spy(&model, signal.constData()); 0258 0259 // WHEN 0260 model.setProperty(propertyName, propertyValue); 0261 0262 // THEN 0263 QCOMPARE(spy.size(), 1); 0264 QCOMPARE(spy.takeFirst().at(0), propertyValue); 0265 QCOMPARE(model.property(propertyName), propertyValue); 0266 QVERIFY(task->property(propertyName) != propertyValue); 0267 QVERIFY(!savedTask); 0268 0269 // WHEN (apply after delay) 0270 QTest::qWait(model.autoSaveDelay() + 50); 0271 0272 // THEN 0273 QCOMPARE(savedTask, task); 0274 QCOMPARE(task->property(propertyName), propertyValue); 0275 } 0276 0277 void shouldApplyChangesImmediatelyIfANewTaskIsSet_data() 0278 { 0279 shouldReactToTaskPropertyChanges_data(); 0280 } 0281 0282 void shouldApplyChangesImmediatelyIfANewTaskIsSet() 0283 { 0284 // GIVEN 0285 QFETCH(Domain::Task::Ptr, task); 0286 QFETCH(QByteArray, propertyName); 0287 QFETCH(QVariant, propertyValue); 0288 QFETCH(QByteArray, signal); 0289 0290 auto savedTask = Domain::Task::Ptr(); 0291 auto save = [this, &savedTask] (const Domain::Task::Ptr &task) { 0292 savedTask = task; 0293 return new FakeJob(this); 0294 }; 0295 0296 Presentation::EditorModel model; 0297 model.setSaveFunction(save); 0298 QVERIFY(model.hasSaveFunction()); 0299 model.setTask(task); 0300 QSignalSpy spy(&model, signal.constData()); 0301 0302 // WHEN 0303 model.setProperty(propertyName, propertyValue); 0304 0305 // THEN 0306 QCOMPARE(spy.size(), 1); 0307 QCOMPARE(spy.takeFirst().at(0), propertyValue); 0308 QCOMPARE(model.property(propertyName), propertyValue); 0309 QVERIFY(task->property(propertyName) != propertyValue); 0310 QVERIFY(!savedTask); 0311 0312 // WHEN (apply immediately) 0313 model.setTask(Domain::Task::Ptr::create()); 0314 0315 // THEN 0316 QCOMPARE(savedTask, task); 0317 QCOMPARE(task->property(propertyName), propertyValue); 0318 savedTask.clear(); 0319 0320 // WHEN (nothing else happens after a delay) 0321 QTest::qWait(model.autoSaveDelay() + 50); 0322 0323 // THEN 0324 QVERIFY(!savedTask); 0325 QCOMPARE(task->property(propertyName), propertyValue); 0326 } 0327 0328 void shouldApplyChangesImmediatelyIfDeleted_data() 0329 { 0330 shouldReactToTaskPropertyChanges_data(); 0331 } 0332 0333 void shouldApplyChangesImmediatelyIfDeleted() 0334 { 0335 // GIVEN 0336 QFETCH(Domain::Task::Ptr, task); 0337 QFETCH(QByteArray, propertyName); 0338 QFETCH(QVariant, propertyValue); 0339 QFETCH(QByteArray, signal); 0340 0341 auto savedTask = Domain::Task::Ptr(); 0342 auto save = [this, &savedTask] (const Domain::Task::Ptr &task) { 0343 savedTask = task; 0344 return new FakeJob(this); 0345 }; 0346 0347 auto model = new Presentation::EditorModel; 0348 model->setSaveFunction(save); 0349 QVERIFY(model->hasSaveFunction()); 0350 model->setTask(task); 0351 QSignalSpy spy(model, signal.constData()); 0352 0353 // WHEN 0354 model->setProperty(propertyName, propertyValue); 0355 0356 // THEN 0357 QCOMPARE(spy.size(), 1); 0358 QCOMPARE(spy.takeFirst().at(0), propertyValue); 0359 QCOMPARE(model->property(propertyName), propertyValue); 0360 QVERIFY(task->property(propertyName) != propertyValue); 0361 QVERIFY(!savedTask); 0362 0363 // WHEN (apply immediately) 0364 delete model; 0365 0366 // THEN 0367 QCOMPARE(savedTask, task); 0368 QCOMPARE(task->property(propertyName), propertyValue); 0369 } 0370 0371 void shouldGetAnErrorMessageWhenSaveFailed() 0372 { 0373 // GIVEN 0374 auto task = Domain::Task::Ptr::create(); 0375 task->setTitle(QStringLiteral("Task 1")); 0376 0377 auto savedTask = Domain::Task::Ptr(); 0378 auto save = [this, &savedTask] (const Domain::Task::Ptr &task) { 0379 savedTask = task; 0380 auto job = new FakeJob(this); 0381 job->setExpectedError(KJob::KilledJobError, QStringLiteral("Foo")); 0382 return job; 0383 }; 0384 0385 auto model = new Presentation::EditorModel; 0386 model->setSaveFunction(save); 0387 QVERIFY(model->hasSaveFunction()); 0388 FakeErrorHandler errorHandler; 0389 model->setErrorHandler(&errorHandler); 0390 model->setTask(task); 0391 0392 // WHEN 0393 model->setProperty("title", "Foo"); 0394 delete model; 0395 0396 // THEN 0397 QTest::qWait(150); 0398 QCOMPARE(errorHandler.m_message, QStringLiteral("Cannot modify task Task 1: Foo")); 0399 } 0400 0401 void shouldDisconnectFromPreviousTask_data() 0402 { 0403 shouldReactToTaskPropertyChanges_data(); 0404 } 0405 0406 void shouldDisconnectFromPreviousTask() 0407 { 0408 // GIVEN 0409 QFETCH(Domain::Task::Ptr, task); 0410 QFETCH(QByteArray, propertyName); 0411 QFETCH(QVariant, propertyValue); 0412 QFETCH(QByteArray, signal); 0413 0414 Presentation::EditorModel model; 0415 model.setTask(task); 0416 QSignalSpy spy(&model, signal.constData()); 0417 0418 Domain::Task::Ptr newTask = Domain::Task::Ptr::create(); 0419 0420 // WHEN 0421 model.setTask(newTask); 0422 // modifying the *old* task should have no effect. 0423 task->setProperty(propertyName, propertyValue); 0424 0425 // THEN 0426 QCOMPARE(spy.size(), 1); // emitted by setTask 0427 QVERIFY(model.property(propertyName) != task->property(propertyName)); 0428 } 0429 0430 void shouldAddAttachments() 0431 { 0432 // GIVEN 0433 QTemporaryFile temporaryFile(QDir::tempPath() + "/taskeditormodeltest_XXXXXX.txt"); 0434 temporaryFile.open(); 0435 temporaryFile.write("foo bar"); 0436 temporaryFile.close(); 0437 auto fileName = temporaryFile.fileName().mid(QDir::tempPath().size() + 1); 0438 0439 auto task = Domain::Task::Ptr::create(); 0440 0441 auto savedTask = Domain::Task::Ptr(); 0442 auto save = [this, &savedTask] (const Domain::Task::Ptr &task) { 0443 savedTask = task; 0444 return new FakeJob(this); 0445 }; 0446 0447 Presentation::EditorModel model; 0448 model.setSaveFunction(save); 0449 model.setTask(task); 0450 0451 QSignalSpy spy(model.attachmentModel(), &QAbstractItemModel::modelReset); 0452 0453 // WHEN 0454 model.addAttachment(temporaryFile.fileName()); 0455 0456 // THEN 0457 QCOMPARE(spy.size(), 1); 0458 QCOMPARE(model.attachmentModel()->rowCount(), 1); 0459 QVERIFY(!savedTask); 0460 0461 // WHEN (nothing else happens after a delay) 0462 QTest::qWait(model.autoSaveDelay() + 50); 0463 0464 // THEN 0465 QCOMPARE(savedTask, task); 0466 QCOMPARE(task->attachments().size(), 1); 0467 QCOMPARE(task->attachments().first().label(), fileName); 0468 QCOMPARE(task->attachments().first().mimeType(), QStringLiteral("text/plain")); 0469 QCOMPARE(task->attachments().first().iconName(), QStringLiteral("text-plain")); 0470 QCOMPARE(task->attachments().first().data(), QByteArrayLiteral("foo bar")); 0471 } 0472 0473 void shouldRemoveAttachments() 0474 { 0475 // GIVEN 0476 auto task = Domain::Task::Ptr::create(); 0477 task->setAttachments(Domain::Task::Attachments() << Domain::Task::Attachment("foo") 0478 << Domain::Task::Attachment("bar")); 0479 0480 auto savedTask = Domain::Task::Ptr(); 0481 auto save = [this, &savedTask] (const Domain::Task::Ptr &task) { 0482 savedTask = task; 0483 return new FakeJob(this); 0484 }; 0485 0486 Presentation::EditorModel model; 0487 model.setSaveFunction(save); 0488 model.setTask(task); 0489 0490 QSignalSpy spy(model.attachmentModel(), &QAbstractItemModel::modelReset); 0491 0492 // WHEN 0493 model.removeAttachment(model.attachmentModel()->index(0, 0)); 0494 0495 // THEN 0496 QCOMPARE(spy.size(), 1); 0497 QCOMPARE(model.attachmentModel()->rowCount(), 1); 0498 QVERIFY(!savedTask); 0499 0500 // WHEN (nothing else happens after a delay) 0501 QTest::qWait(model.autoSaveDelay() + 50); 0502 0503 // THEN 0504 QCOMPARE(savedTask, task); 0505 QCOMPARE(task->attachments().size(), 1); 0506 QCOMPARE(task->attachments().first().data(), QByteArrayLiteral("bar")); 0507 } 0508 }; 0509 0510 ZANSHIN_TEST_MAIN(EditorModelTest) 0511 0512 #include "editormodeltest.moc"