1 /*
2  * SPDX-FileCopyrightText: 2014 Kevin Ottens <ervin@kde.org>
3    SPDX-FileCopyrightText: 2014 Rémi Benoit <r3m1.benoit@gmail.com>
4  * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
5  */
6 
7 
8 #include <testlib/qtest_zanshin.h>
9 
10 #include "akonadi/akonadiserializer.h"
11 #include "akonadi/akonadiapplicationselectedattribute.h"
12 #include "akonadi/akonaditimestampattribute.h"
13 
14 #include <AkonadiCore/Akonadi/Collection>
15 #include <AkonadiCore/Akonadi/EntityDisplayAttribute>
16 #include <AkonadiCore/Akonadi/Item>
17 #include <KCalCore/Todo>
18 #if __has_include(<kcalcore_version.h>)
19 #include <kcalcore_version.h>
20 #else
21 #include <kcalendarcore_version.h>
22 #endif
23 
24 Q_DECLARE_METATYPE(Akonadi::Item*)
25 
26 using Akonadi::Serializer;
27 
setTodoDates(KCalCore::Todo::Ptr todo,const QDate & start,const QDate & due)28 static void setTodoDates(KCalCore::Todo::Ptr todo, const QDate &start, const QDate &due) {
29     todo->setDtStart(QDateTime(start));
30     todo->setDtDue(QDateTime(due));
31 }
32 
33 class AkonadiSerializerTest : public QObject
34 {
35     Q_OBJECT
36 private slots:
shouldKnowWhenAnObjectRepresentsACollection()37     void shouldKnowWhenAnObjectRepresentsACollection()
38     {
39         // GIVEN
40         Akonadi::Serializer serializer;
41         auto object = Akonadi::Serializer::QObjectPtr::create();
42         Akonadi::Collection collection(42);
43 
44         // WHEN
45         // Nothing yet
46 
47         // THEN
48         QVERIFY(!serializer.representsCollection(object, collection));
49 
50         // WHEN
51         object->setProperty("collectionId", 42);
52 
53         // THEN
54         QVERIFY(serializer.representsCollection(object, collection));
55 
56         // WHEN
57         object->setProperty("collectionId", 43);
58 
59         // THEN
60         QVERIFY(!serializer.representsCollection(object, collection));
61     }
62 
shouldKnowWhenAnObjectRepresentsAnItem()63     void shouldKnowWhenAnObjectRepresentsAnItem()
64     {
65         // GIVEN
66         Akonadi::Serializer serializer;
67         auto object = Akonadi::Serializer::QObjectPtr::create();
68         Akonadi::Item item(42);
69 
70         // WHEN
71         // Nothing yet
72 
73         // THEN
74         QVERIFY(!serializer.representsItem(object, item));
75 
76         // WHEN
77         object->setProperty("itemId", 42);
78 
79         // THEN
80         QVERIFY(serializer.representsItem(object, item));
81 
82         // WHEN
83         object->setProperty("itemId", 43);
84 
85         // THEN
86         QVERIFY(!serializer.representsItem(object, item));
87     }
88 
shouldKnowTaskItemUid_data()89     void shouldKnowTaskItemUid_data()
90     {
91         QTest::addColumn<Akonadi::Item>("item");
92         QTest::addColumn<QString>("expectedUid");
93 
94         Akonadi::Item item1;
95         KCalCore::Todo::Ptr todo1(new KCalCore::Todo);
96         todo1->setUid(QString());
97         item1.setPayload<KCalCore::Todo::Ptr>(todo1);
98 
99         Akonadi::Item item2;
100         KCalCore::Todo::Ptr todo2(new KCalCore::Todo);
101         todo2->setUid(QStringLiteral("1"));
102         item2.setPayload<KCalCore::Todo::Ptr>(todo2);
103 
104         QTest::newRow("task without uid") << item1 << QString();
105         QTest::newRow("task with uid") << item2 << "1";
106     }
107 
shouldKnowTaskItemUid()108     void shouldKnowTaskItemUid()
109     {
110         // GIVEN
111         QFETCH(Akonadi::Item, item);
112         QFETCH(QString, expectedUid);
113 
114         // WHEN
115         Akonadi::Serializer serializer;
116         QString uid = serializer.itemUid(item);
117 
118         // THEN
119         QCOMPARE(uid, expectedUid);
120     }
121 
shouldCreateDataSourceFromCollection_data()122     void shouldCreateDataSourceFromCollection_data()
123     {
124         QTest::addColumn<QString>("name");
125         QTest::addColumn<QString>("iconName");
126         QTest::addColumn<QStringList>("mimeTypes");
127         QTest::addColumn<bool>("hasSelectedAttribute");
128         QTest::addColumn<bool>("isSelected");
129 
130         const auto noteMimeTypes = QStringList() << QStringLiteral("text/x-vnd.akonadi.note");
131         const auto taskMimeTypes = QStringList() << QStringLiteral("application/x-vnd.akonadi.calendar.todo");
132         const auto bogusMimeTypes = QStringList() << QStringLiteral("foo/bar");
133         const auto allMimeTypes = noteMimeTypes + taskMimeTypes + bogusMimeTypes;
134 
135         QTest::newRow("nominal case") << "name" << "icon" << allMimeTypes << true << false;
136 
137         QTest::newRow("only notes") << "name" << "icon" << noteMimeTypes << true << false;
138         QTest::newRow("only tasks") << "name" << "icon" << taskMimeTypes << true << false;
139         QTest::newRow("only bogus") << "name" << "icon" << bogusMimeTypes << true << false;
140 
141         QTest::newRow("no selected attribute") << "name" << "icon" << allMimeTypes << false << false;
142         QTest::newRow("selected attribute (false)") << "name" << "icon" << allMimeTypes << true << false;
143         QTest::newRow("selected attribute (true)") << "name" << "icon" << allMimeTypes << true << true;
144 
145         QTest::newRow("empty case") << QString() << QString() << QStringList() << false << false;
146     }
147 
shouldCreateDataSourceFromCollection()148     void shouldCreateDataSourceFromCollection()
149     {
150         // GIVEN
151 
152         // Data...
153         QFETCH(QString, name);
154         QFETCH(QString, iconName);
155         QFETCH(QStringList, mimeTypes);
156         QFETCH(bool, hasSelectedAttribute);
157         QFETCH(bool, isSelected);
158 
159         Domain::DataSource::ContentTypes expectedContentTypes;
160         if (mimeTypes.contains(QStringLiteral("application/x-vnd.akonadi.calendar.todo"))) {
161             expectedContentTypes |= Domain::DataSource::Tasks;
162         }
163 
164         // ... stored in a collection
165         Akonadi::Collection collection(42);
166         collection.setContentMimeTypes(mimeTypes);
167         collection.setName(name);
168         auto displayAttribute = new Akonadi::EntityDisplayAttribute;
169         displayAttribute->setIconName(iconName);
170         collection.addAttribute(displayAttribute);
171         if (hasSelectedAttribute) {
172             auto selectedAttribute = new Akonadi::ApplicationSelectedAttribute;
173             selectedAttribute->setSelected(isSelected);
174             collection.addAttribute(selectedAttribute);
175         }
176 
177         // WHEN
178         Akonadi::Serializer serializer;
179         auto dataSource = serializer.createDataSourceFromCollection(collection, Akonadi::SerializerInterface::BaseName);
180 
181         // THEN
182         QCOMPARE(dataSource->name(), name);
183         QCOMPARE(dataSource->iconName(), iconName);
184         QCOMPARE(dataSource->contentTypes(), expectedContentTypes);
185         QCOMPARE(dataSource->isSelected(), !hasSelectedAttribute || isSelected);
186         QCOMPARE(dataSource->property("collectionId").value<Akonadi::Collection::Id>(), collection.id());
187     }
188 
shouldCreateNullDataSourceFromInvalidCollection()189     void shouldCreateNullDataSourceFromInvalidCollection()
190     {
191         // GIVEN
192         Akonadi::Collection collection;
193 
194         // WHEN
195         Akonadi::Serializer serializer;
196         auto dataSource = serializer.createDataSourceFromCollection(collection, Akonadi::SerializerInterface::BaseName);
197 
198         // THEN
199         QVERIFY(dataSource.isNull());
200     }
201 
shouldUpdateDataSourceFromCollection_data()202     void shouldUpdateDataSourceFromCollection_data()
203     {
204         QTest::addColumn<QString>("updatedName");
205 
206         QTest::newRow("no change") << "name";
207         QTest::newRow("changed") << "new name";
208     }
209 
shouldUpdateDataSourceFromCollection()210     void shouldUpdateDataSourceFromCollection()
211     {
212         // GIVEN
213 
214         // A collection...
215         Akonadi::Collection originalCollection(42);
216         originalCollection.setName(QStringLiteral("name"));
217 
218         // ... deserialized as a data source
219         Akonadi::Serializer serializer;
220         auto dataSource = serializer.createDataSourceFromCollection(originalCollection, Akonadi::SerializerInterface::BaseName);
221 
222         // WHEN
223 
224         // Data...
225         QFETCH(QString, updatedName);
226 
227         // ... in a new collection
228         Akonadi::Collection updatedCollection(42);
229         updatedCollection.setName(updatedName);
230 
231         serializer.updateDataSourceFromCollection(dataSource, updatedCollection, Akonadi::SerializerInterface::BaseName);
232 
233         // THEN
234         QCOMPARE(dataSource->name(), updatedName);
235     }
236 
shouldNotUpdateDataSourceFromInvalidCollection()237     void shouldNotUpdateDataSourceFromInvalidCollection()
238     {
239         // GIVEN
240 
241         // Data...
242         const QString name = QStringLiteral("name");
243 
244         // ... stored in a collection...
245         Akonadi::Collection originalCollection(42);
246         originalCollection.setName(name);
247 
248         // ... deserialized as a data source
249         Akonadi::Serializer serializer;
250         auto dataSource = serializer.createDataSourceFromCollection(originalCollection, Akonadi::SerializerInterface::BaseName);
251 
252         // WHEN
253         Akonadi::Collection invalidCollection;
254         invalidCollection.setName(QStringLiteral("foo"));
255         serializer.updateDataSourceFromCollection(dataSource, invalidCollection, Akonadi::SerializerInterface::BaseName);
256 
257         // THEN
258         QCOMPARE(dataSource->name(), name);
259     }
260 
shouldNameDataSourceFromCollectionPathIfRequested()261     void shouldNameDataSourceFromCollectionPathIfRequested()
262     {
263         // GIVEN
264 
265         // Data...
266         const QString name = QStringLiteral("name");
267         const QString parentName = QStringLiteral("parent");
268 
269         // ... stored in a collection with a parent
270         Akonadi::Collection collection(42);
271         collection.setName(name);
272         Akonadi::Collection parentCollection(41);
273         parentCollection.setName(QStringLiteral("Foo"));
274         auto attribute = new Akonadi::EntityDisplayAttribute;
275         attribute->setDisplayName(parentName);
276         parentCollection.addAttribute(attribute);
277         collection.setParentCollection(parentCollection);
278 
279         // WHEN
280         Akonadi::Serializer serializer;
281         auto dataSource1 = serializer.createDataSourceFromCollection(collection, Akonadi::SerializerInterface::FullPath);
282         auto dataSource2 = serializer.createDataSourceFromCollection(collection, Akonadi::SerializerInterface::BaseName);
283 
284         // Give it another try with the root
285         parentCollection.setParentCollection(Akonadi::Collection::root());
286         collection.setParentCollection(parentCollection);
287         auto dataSource3 = serializer.createDataSourceFromCollection(collection, Akonadi::SerializerInterface::FullPath);
288         auto dataSource4 = serializer.createDataSourceFromCollection(collection, Akonadi::SerializerInterface::BaseName);
289 
290         // THEN
291         QCOMPARE(dataSource1->name(), QString(parentName + " » " + name));
292         QCOMPARE(dataSource2->name(), name);
293         QCOMPARE(dataSource3->name(), QString(parentName + " » " + name));
294         QCOMPARE(dataSource4->name(), name);
295     }
296 
shouldCreateCollectionFromDataSource_data()297     void shouldCreateCollectionFromDataSource_data()
298     {
299         QTest::addColumn<QString>("name");
300         QTest::addColumn<QString>("iconName");
301         QTest::addColumn<Domain::DataSource::ContentTypes>("contentTypes");
302         QTest::addColumn<bool>("isSelected");
303 
304         const auto noType = Domain::DataSource::ContentTypes(Domain::DataSource::NoContent);
305         const auto taskType = Domain::DataSource::ContentTypes(Domain::DataSource::Tasks);
306 
307         QTest::newRow("only tasks") << "name" << "icon-name" << taskType << true;
308         QTest::newRow("only nothing ;)") << "name" << "icon-name" << noType << true;
309 
310         QTest::newRow("not selected") << "name" << "icon-name" << taskType << false;
311         QTest::newRow("selected") << "name" << "icon-name" << taskType << true;
312 
313         QTest::newRow("empty case") << QString() << QString() << noType << true;
314     }
315 
shouldCreateCollectionFromDataSource()316     void shouldCreateCollectionFromDataSource()
317     {
318         // GIVEN
319         const auto timestamp = QDateTime::currentMSecsSinceEpoch();
320 
321         // Data...
322         QFETCH(QString, name);
323         QFETCH(QString, iconName);
324         QFETCH(Domain::DataSource::ContentTypes, contentTypes);
325         QFETCH(bool, isSelected);
326 
327         QStringList mimeTypes;
328         if (contentTypes & Domain::DataSource::Tasks)
329             mimeTypes << QStringLiteral("application/x-vnd.akonadi.calendar.todo");
330 
331 
332         // ... stored in a data source
333         auto source = Domain::DataSource::Ptr::create();
334         source->setName(name);
335         source->setIconName(iconName);
336         source->setContentTypes(contentTypes);
337         source->setSelected(isSelected);
338         source->setProperty("collectionId", 42);
339 
340         // WHEN
341         Akonadi::Serializer serializer;
342         auto collection = serializer.createCollectionFromDataSource(source);
343 
344         // THEN
345         QCOMPARE(collection.id(), source->property("collectionId").value<Akonadi::Collection::Id>());
346         QVERIFY(collection.hasAttribute<Akonadi::ApplicationSelectedAttribute>());
347         QCOMPARE(collection.attribute<Akonadi::ApplicationSelectedAttribute>()->isSelected(), isSelected);
348         QVERIFY(collection.hasAttribute<Akonadi::TimestampAttribute>());
349         QVERIFY(collection.attribute<Akonadi::TimestampAttribute>()->timestamp() >= timestamp);
350     }
351 
shouldVerifyIfCollectionIsSelected_data()352     void shouldVerifyIfCollectionIsSelected_data()
353     {
354         QTest::addColumn<QStringList>("mimeTypes");
355         QTest::addColumn<bool>("hasSelectedAttribute");
356         QTest::addColumn<bool>("isSelected");
357         QTest::addColumn<bool>("expectedSelected");
358 
359         const auto taskMimeTypes = QStringList() << QStringLiteral("application/x-vnd.akonadi.calendar.todo");
360         const auto bogusMimeTypes = QStringList() << QStringLiteral("foo/bar");
361         const auto allMimeTypes = taskMimeTypes + bogusMimeTypes;
362 
363         QTest::newRow("nominal case") << allMimeTypes << true << false << false;
364 
365         QTest::newRow("only tasks") << taskMimeTypes << true << false << false;
366         QTest::newRow("only bogus") << bogusMimeTypes << true << false << false;
367 
368         QTest::newRow("selected, only tasks") << taskMimeTypes << true << true << true;
369         QTest::newRow("selected, only bogus") << bogusMimeTypes << true << true << false;
370 
371         QTest::newRow("no selected attribute") << allMimeTypes << false << false << true;
372         QTest::newRow("selected attribute (false)") << allMimeTypes << true << false << false;
373         QTest::newRow("selected attribute (true)") << allMimeTypes << true << true << true;
374 
375         QTest::newRow("empty case") << QStringList() << false << false << false;
376     }
377 
shouldVerifyIfCollectionIsSelected()378     void shouldVerifyIfCollectionIsSelected()
379     {
380         // GIVEN
381         QFETCH(QStringList, mimeTypes);
382         QFETCH(bool, hasSelectedAttribute);
383         QFETCH(bool, isSelected);
384 
385         Domain::DataSource::ContentTypes expectedContentTypes;
386         if (mimeTypes.contains(QStringLiteral("application/x-vnd.akonadi.calendar.todo"))) {
387             expectedContentTypes |= Domain::DataSource::Tasks;
388         }
389 
390         // ... stored in a collection
391         Akonadi::Collection collection(42);
392         collection.setContentMimeTypes(mimeTypes);
393         if (hasSelectedAttribute) {
394             auto selectedAttribute = new Akonadi::ApplicationSelectedAttribute;
395             selectedAttribute->setSelected(isSelected);
396             collection.addAttribute(selectedAttribute);
397         }
398 
399         // WHEN
400         Akonadi::Serializer serializer;
401 
402         // THEN
403         QFETCH(bool, expectedSelected);
404         QCOMPARE(serializer.isSelectedCollection(collection), expectedSelected);
405     }
406 
shouldVerifyCollectionContents_data()407     void shouldVerifyCollectionContents_data()
408     {
409         QTest::addColumn<QString>("mimeType");
410         QTest::addColumn<bool>("expectedTasks");
411 
412         QTest::newRow("task collection") << "application/x-vnd.akonadi.calendar.todo" << true;
413         QTest::newRow("note collection") << "text/x-vnd.akonadi.note" << false;
414     }
415 
shouldVerifyCollectionContents()416     void shouldVerifyCollectionContents()
417     {
418         // GIVEN
419 
420         // Data...
421         QFETCH(QString, mimeType);
422 
423         // ... stored in a collection
424         Akonadi::Collection collection(42);
425         collection.setContentMimeTypes(QStringList() << mimeType);
426 
427         // WHEN
428         Akonadi::Serializer serializer;
429         QFETCH(bool, expectedTasks);
430 
431         // THEN
432         QCOMPARE(serializer.isTaskCollection(collection), expectedTasks);
433     }
434 
shouldCreateTaskFromItem_data()435     void shouldCreateTaskFromItem_data()
436     {
437         QTest::addColumn<QString>("summary");
438         QTest::addColumn<QString>("content");
439         QTest::addColumn<bool>("isDone");
440         QTest::addColumn<QDate>("doneDate");
441         QTest::addColumn<QDate>("startDate");
442         QTest::addColumn<QDate>("dueDate");
443         QTest::addColumn<QStringList>("contexts");
444 
445         QTest::newRow("nominal case") << "summary" << "content" << false << QDate() << QDate(2013, 11, 24) << QDate(2014, 03, 01) << QStringList();
446         QTest::newRow("done case") << "summary" << "content" << true << QDate(2013, 11, 30) << QDate(2013, 11, 24) << QDate(2014, 03, 01) << QStringList();
447         QTest::newRow("done without doneDate case") << "summary" << "content" << true << QDate() << QDate(2013, 11, 24) << QDate(2014, 03, 01) << QStringList();
448         QTest::newRow("empty case") << QString() << QString() << false << QDate() << QDate() << QDate() << QStringList();
449         QTest::newRow("one_context") << "summary" << "content" << false << QDate() << QDate(2013, 11, 24) << QDate(2014, 03, 01) << QStringList({"mycontext"});
450         QTest::newRow("two_contexts") << "summary" << "content" << false << QDate() << QDate(2013, 11, 24) << QDate(2014, 03, 01) << QStringList({"c1", "c2"});
451     }
452 
shouldCreateTaskFromItem()453     void shouldCreateTaskFromItem()
454     {
455         // GIVEN
456 
457         // Data...
458         QFETCH(QString, summary);
459         QFETCH(QString, content);
460         QFETCH(bool, isDone);
461         QFETCH(QDate, doneDate);
462         QFETCH(QDate, startDate);
463         QFETCH(QDate, dueDate);
464         QFETCH(QStringList, contexts);
465 
466         // ... stored in a todo...
467         KCalCore::Todo::Ptr todo(new KCalCore::Todo);
468         todo->setSummary(summary);
469         todo->setDescription(content);
470 
471         if (isDone)
472             todo->setCompleted(QDateTime(doneDate));
473         else
474             todo->setCompleted(isDone);
475 
476         setTodoDates(todo, startDate, dueDate);
477         todo->setRelatedTo(QStringLiteral("my-uid"));
478 
479         if (!contexts.isEmpty())
480             todo->setCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyContextList(), contexts.join(','));
481 
482         // ... as payload of an item
483         Akonadi::Item item;
484         item.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
485         item.setPayload<KCalCore::Todo::Ptr>(todo);
486 
487         // which has a parent collection
488         Akonadi::Collection collection(43);
489         item.setParentCollection(collection);
490 
491         // WHEN
492         Akonadi::Serializer serializer;
493         auto task = serializer.createTaskFromItem(item);
494 
495         // THEN
496         QVERIFY(!task.isNull());
497         QCOMPARE(task->title(), summary);
498         QCOMPARE(task->text(), content);
499         QCOMPARE(task->isDone(), isDone);
500         QCOMPARE(task->doneDate(), doneDate);
501         QCOMPARE(task->startDate(), startDate);
502         QCOMPARE(task->dueDate(), dueDate);
503         QCOMPARE(task->property("todoUid").toString(), todo->uid());
504         QCOMPARE(task->property("relatedUid").toString(), todo->relatedTo());
505         QCOMPARE(task->property("contextUids").toStringList(), contexts);
506         QCOMPARE(task->property("itemId").toLongLong(), item.id());
507         QCOMPARE(task->property("parentCollectionId").toLongLong(), collection.id());
508     }
509 
shouldCreateNullTaskFromInvalidItem()510     void shouldCreateNullTaskFromInvalidItem()
511     {
512         // GIVEN
513         Akonadi::Item item;
514 
515         // WHEN
516         Akonadi::Serializer serializer;
517         auto task = serializer.createTaskFromItem(item);
518 
519         // THEN
520         QVERIFY(task.isNull());
521     }
522 
shouldCreateNullTaskFromProjectItem()523     void shouldCreateNullTaskFromProjectItem()
524     {
525         // GIVEN
526 
527         // A todo with the project flag
528         KCalCore::Todo::Ptr todo(new KCalCore::Todo);
529         todo->setSummary(QStringLiteral("foo"));
530         todo->setCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyIsProject(), QStringLiteral("1"));
531 
532         // ... as payload of an item
533         Akonadi::Item item;
534         item.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
535         item.setPayload<KCalCore::Todo::Ptr>(todo);
536 
537         // WHEN
538         Akonadi::Serializer serializer;
539         auto task = serializer.createTaskFromItem(item);
540 
541         // THEN
542         QVERIFY(task.isNull());
543     }
544 
shouldUpdateTaskFromItem_data()545     void shouldUpdateTaskFromItem_data()
546     {
547         QTest::addColumn<QString>("updatedSummary");
548         QTest::addColumn<QString>("updatedContent");
549         QTest::addColumn<bool>("updatedDone");
550         QTest::addColumn<QDate>("updatedDoneDate");
551         QTest::addColumn<QDate>("updatedStartDate");
552         QTest::addColumn<QDate>("updatedDueDate");
553         QTest::addColumn<QString>("updatedRelated");
554         QTest::addColumn<QString>("updatedContexts");
555         QTest::addColumn<bool>("updatedRecurs");
556         QTest::addColumn<QByteArrayList>("updatedAttachmentData");
557         QTest::addColumn<QStringList>("updatedAttachmentUris");
558         QTest::addColumn<QStringList>("updatedAttachmentLabels");
559         QTest::addColumn<QStringList>("updatedAttachmentMimeTypes");
560         QTest::addColumn<QStringList>("updatedAttachmentIconNames");
561         QTest::addColumn<bool>("updatedRunning");
562 
563         QTest::newRow("no change") << "summary" << "content" << false << QDate() <<  QDate(2013, 11, 24) << QDate(2014, 03, 01) << "my-uid" << "context1,context2" << false << QByteArrayList() << QStringList() << QStringList() << QStringList() << QStringList() << false;
564         QTest::newRow("changed") << "new summary" << "new content" << true << QDate(2013, 11, 28) << QDate(2013, 11, 25) << QDate(2014, 03, 02) << "my-new-uid" << "context2" << true << QByteArrayList({"foo", "# bar", QByteArray()}) << QStringList({QString(), QString(), "https://www.kde.org"}) << QStringList({"label1", "label2", "label3"}) << QStringList({"text/plain", "text/markdown", "text/html"}) << QStringList({"text-plain", "text-markdown", "text-html"}) << false;
565         QTest::newRow("set_to_running") << "summary" << "content" << false << QDate() <<  QDate(2013, 11, 24) << QDate(2014, 03, 01) << "my-uid" << "context1,context2" << false << QByteArrayList() << QStringList() << QStringList() << QStringList() << QStringList() << true;
566     }
567 
shouldUpdateTaskFromItem()568     void shouldUpdateTaskFromItem()
569     {
570         // GIVEN
571 
572         // A todo...
573         KCalCore::Todo::Ptr originalTodo(new KCalCore::Todo);
574         originalTodo->setSummary(QStringLiteral("summary"));
575         originalTodo->setDescription(QStringLiteral("content"));
576         originalTodo->setCompleted(false);
577         setTodoDates(originalTodo, QDate(2013, 11, 24), QDate(2014, 03, 01));
578 
579         originalTodo->setRelatedTo(QStringLiteral("my-uid"));
580         originalTodo->setCustomProperty(Serializer::customPropertyAppName(),
581                                         Serializer::customPropertyContextList(),
582                                         QStringLiteral("context1,context2"));
583 #if KCALCORE_VERSION >= QT_VERSION_CHECK(5, 11, 80)
584         KCalCore::Attendee originalAttendee(QStringLiteral("John Doe"),
585                                             QStringLiteral("j@d.com"),
586                                             true,
587                                             KCalCore::Attendee::Accepted);
588 #else
589         KCalCore::Attendee::Ptr originalAttendee(new KCalCore::Attendee(QStringLiteral("John Doe"),
590                                                                         QStringLiteral("j@d.com"),
591                                                                         true,
592                                                                         KCalCore::Attendee::Accepted));
593 #endif
594         originalTodo->addAttendee(originalAttendee);
595 
596         // ... as payload of an item...
597         Akonadi::Item originalItem;
598         originalItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
599         originalItem.setPayload<KCalCore::Todo::Ptr>(originalTodo);
600 
601         // ... which has a parent collection...
602         Akonadi::Collection originalCollection(43);
603         originalItem.setParentCollection(originalCollection);
604 
605         // ... deserialized as a task
606         Akonadi::Serializer serializer;
607         auto task = serializer.createTaskFromItem(originalItem);
608 
609         // WHEN
610 
611         // Data...
612         QFETCH(QString, updatedSummary);
613         QFETCH(QString, updatedContent);
614         QFETCH(bool, updatedDone);
615         QFETCH(QDate, updatedDoneDate);
616         QFETCH(QDate, updatedStartDate);
617         QFETCH(QDate, updatedDueDate);
618         QFETCH(QString, updatedRelated);
619         QFETCH(QString, updatedContexts);
620         QFETCH(bool, updatedRecurs);
621         QFETCH(QByteArrayList, updatedAttachmentData);
622         QFETCH(QStringList, updatedAttachmentUris);
623         QFETCH(QStringList, updatedAttachmentLabels);
624         QFETCH(QStringList, updatedAttachmentMimeTypes);
625         QFETCH(QStringList, updatedAttachmentIconNames);
626         QFETCH(bool, updatedRunning);
627 
628         // ... in a new todo...
629         KCalCore::Todo::Ptr updatedTodo(new KCalCore::Todo);
630         updatedTodo->setSummary(updatedSummary);
631         updatedTodo->setDescription(updatedContent);
632 
633         if (updatedDone)
634             updatedTodo->setCompleted(QDateTime(updatedDoneDate));
635         else
636             updatedTodo->setCompleted(updatedDone);
637 
638         setTodoDates(updatedTodo, updatedStartDate, updatedDueDate);
639         updatedTodo->setRelatedTo(updatedRelated);
640         updatedTodo->setCustomProperty(Serializer::customPropertyAppName(),
641                                        Serializer::customPropertyContextList(),
642                                        updatedContexts);
643 
644         if (updatedRecurs)
645             updatedTodo->recurrence()->setDaily(1);
646 
647         for (int i = 0; i < updatedAttachmentData.size(); i++) {
648 #if KCALCORE_VERSION >= QT_VERSION_CHECK(5, 11, 80)
649             KCalCore::Attachment attachment(QByteArray{});
650             if (!updatedAttachmentData.at(i).isEmpty())
651                 attachment.setDecodedData(updatedAttachmentData.at(i));
652             else
653                 attachment.setUri(updatedAttachmentUris.at(i));
654             attachment.setMimeType(updatedAttachmentMimeTypes.at(i));
655             attachment.setLabel(updatedAttachmentLabels.at(i));
656 #else
657             KCalCore::Attachment::Ptr attachment(new KCalCore::Attachment(QByteArray()));
658             if (!updatedAttachmentData.at(i).isEmpty())
659                 attachment->setDecodedData(updatedAttachmentData.at(i));
660             else
661                 attachment->setUri(updatedAttachmentUris.at(i));
662             attachment->setMimeType(updatedAttachmentMimeTypes.at(i));
663             attachment->setLabel(updatedAttachmentLabels.at(i));
664 #endif
665             updatedTodo->addAttachment(attachment);
666         }
667 
668         if (updatedRunning) {
669             updatedTodo->setCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyIsRunning(), "1");
670         } else {
671             updatedTodo->removeCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyIsRunning());
672         }
673 
674         // ... as payload of a new item
675         Akonadi::Item updatedItem;
676         updatedItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
677         updatedItem.setPayload<KCalCore::Todo::Ptr>(updatedTodo);
678 
679         // ... which has a new parent collection
680         Akonadi::Collection updatedCollection(45);
681         updatedItem.setParentCollection(updatedCollection);
682 
683         serializer.updateTaskFromItem(task, updatedItem);
684 
685         // THEN
686         QCOMPARE(task->title(), updatedSummary);
687         QCOMPARE(task->text(), updatedContent);
688         QCOMPARE(task->isDone(), updatedDone);
689         QCOMPARE(task->doneDate(), updatedDoneDate);
690         QCOMPARE(task->startDate(), updatedStartDate);
691         QCOMPARE(task->dueDate(), updatedDueDate);
692         QCOMPARE(task->property("todoUid").toString(), updatedTodo->uid());
693         QCOMPARE(task->property("relatedUid").toString(), updatedTodo->relatedTo());
694         QCOMPARE(task->property("contextUids").toStringList(), updatedContexts.split(','));
695         QCOMPARE(task->property("itemId").toLongLong(), updatedItem.id());
696         QCOMPARE(task->property("parentCollectionId").toLongLong(), updatedCollection.id());
697         QCOMPARE(task->recurrence(), (updatedRecurs ? Domain::Task::RecursDaily : Domain::Task::NoRecurrence));
698         QCOMPARE(task->attachments().size(), updatedAttachmentData.size());
699         for (int i = 0; i < task->attachments().size(); i++) {
700             const auto attachment = task->attachments().at(i);
701             QCOMPARE(attachment.data(), updatedAttachmentData.at(i));
702             QCOMPARE(attachment.uri(), QUrl(updatedAttachmentUris.at(i)));
703             QCOMPARE(attachment.label(), updatedAttachmentLabels.at(i));
704             QCOMPARE(attachment.mimeType(), updatedAttachmentMimeTypes.at(i));
705             QCOMPARE(attachment.iconName(), updatedAttachmentIconNames.at(i));
706         }
707         QCOMPARE(task->isRunning(), updatedRunning);
708     }
709 
shouldUpdateTaskRecurrenceFromItem_data()710     void shouldUpdateTaskRecurrenceFromItem_data()
711     {
712         QTest::addColumn<int>("todoRecurrence");
713         QTest::addColumn<Domain::Task::Recurrence>("expectedRecurrence");
714 
715         QTest::newRow("none") << int(KCalCore::Recurrence::rNone) << Domain::Task::NoRecurrence;
716         QTest::newRow("minutely") << int(KCalCore::Recurrence::rMinutely) << Domain::Task::NoRecurrence;
717         QTest::newRow("hourly") << int(KCalCore::Recurrence::rHourly) << Domain::Task::NoRecurrence;
718         QTest::newRow("daily") << int(KCalCore::Recurrence::rDaily) << Domain::Task::RecursDaily;
719         QTest::newRow("weekly") << int(KCalCore::Recurrence::rWeekly) << Domain::Task::RecursWeekly;
720         QTest::newRow("monthly") << int(KCalCore::Recurrence::rMonthlyDay) << Domain::Task::RecursMonthly;
721         QTest::newRow("yearly") << int(KCalCore::Recurrence::rYearlyMonth) << Domain::Task::RecursYearly;
722     }
723 
shouldUpdateTaskRecurrenceFromItem()724     void shouldUpdateTaskRecurrenceFromItem()
725     {
726         // GIVEN
727 
728         // A todo...
729         KCalCore::Todo::Ptr todo(new KCalCore::Todo);
730         todo->setSummary(QStringLiteral("summary"));
731 
732         QFETCH(int, todoRecurrence);
733         switch (todoRecurrence) {
734         case KCalCore::Recurrence::rNone:
735             break;
736         case KCalCore::Recurrence::rMinutely:
737             todo->recurrence()->setMinutely(1);
738             break;
739         case KCalCore::Recurrence::rHourly:
740             todo->recurrence()->setHourly(1);
741             break;
742         case KCalCore::Recurrence::rDaily:
743             todo->recurrence()->setDaily(1);
744             break;
745         case KCalCore::Recurrence::rWeekly:
746             todo->recurrence()->setWeekly(1);
747             break;
748         case KCalCore::Recurrence::rMonthlyDay:
749             todo->recurrence()->setMonthly(1);
750             break;
751         case KCalCore::Recurrence::rYearlyMonth:
752             todo->recurrence()->setYearly(1);
753             break;
754         default:
755             qFatal("Shouldn't happen");
756         }
757 
758         // ... as payload of an item...
759         Akonadi::Item item;
760         item.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
761         item.setPayload<KCalCore::Todo::Ptr>(todo);
762 
763         // ... which has a parent collection...
764         Akonadi::Collection collection(43);
765         item.setParentCollection(collection);
766 
767         // WHEN
768         // ... deserialized as a task
769         Akonadi::Serializer serializer;
770         auto task = serializer.createTaskFromItem(item);
771 
772         // THEN
773         QFETCH(Domain::Task::Recurrence, expectedRecurrence);
774         QCOMPARE(task->recurrence(), expectedRecurrence);
775     }
776 
shouldNotBreakRecurrenceDuringSerialization()777     void shouldNotBreakRecurrenceDuringSerialization()
778     {
779         // GIVEN
780 
781         // Data...
782         const QDate today(QDate::currentDate());
783         const QDate doneDate(2013, 11, 20);
784         const QDate startDate(2013, 11, 10);
785 
786         // ... stored in a todo...
787         KCalCore::Todo::Ptr todo(new KCalCore::Todo);
788         todo->setSummary(QStringLiteral("summary"));
789         todo->setDtStart(QDateTime(startDate));
790         todo->recurrence()->setMonthly(1);
791 
792         // ... as payload of an item...
793         Akonadi::Item item;
794         item.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
795         item.setPayload<KCalCore::Todo::Ptr>(todo);
796 
797         // ... deserialized as a task
798         Akonadi::Serializer serializer;
799         auto task = serializer.createTaskFromItem(item);
800 
801         // WHEN
802         // Task is marked done...
803         task->setDoneDate(doneDate);
804         task->setDone(true);
805 
806         // and goes through serialization and back
807         const auto newItem = serializer.createItemFromTask(task);
808         serializer.updateTaskFromItem(task, newItem);
809 
810         // THEN
811         QCOMPARE(task->recurrence(), Domain::Task::RecursMonthly);
812         QVERIFY(!task->isDone());
813         const QDate lastOccurrence(QDate(today.year(), today.month(), 10));
814         if (today.day() > 10)
815             QCOMPARE(task->startDate(), lastOccurrence.addMonths(1));
816         else
817             QCOMPARE(task->startDate(), lastOccurrence);
818     }
819 
shouldNotUpdateTaskFromInvalidItem()820     void shouldNotUpdateTaskFromInvalidItem()
821     {
822         // GIVEN
823 
824         // Data...
825         const QString summary = QStringLiteral("summary");
826         const QString content = QStringLiteral("content");
827         const bool isDone = true;
828         const QDate doneDate(2013, 11, 30);
829         const QDate startDate(2013, 11, 24);
830         const QDate dueDate(2014, 03, 01);
831 
832         // ... stored in a todo...
833         KCalCore::Todo::Ptr originalTodo(new KCalCore::Todo);
834         originalTodo->setSummary(summary);
835         originalTodo->setDescription(content);
836 
837         if (originalTodo)
838             originalTodo->setCompleted(QDateTime(doneDate));
839         else
840             originalTodo->setCompleted(isDone);
841         setTodoDates(originalTodo, startDate, dueDate);
842 
843         // ... as payload of an item...
844         Akonadi::Item originalItem;
845         originalItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
846         originalItem.setPayload<KCalCore::Todo::Ptr>(originalTodo);
847 
848         // ... deserialized as a task
849         Akonadi::Serializer serializer;
850         auto task = serializer.createTaskFromItem(originalItem);
851 
852         // WHEN
853         Akonadi::Item invalidItem;
854         serializer.updateTaskFromItem(task, invalidItem);
855 
856         // THEN
857         QCOMPARE(task->title(), summary);
858         QCOMPARE(task->text(), content);
859         QCOMPARE(task->isDone(), isDone);
860         QCOMPARE(task->doneDate(), doneDate);
861         QCOMPARE(task->startDate(), startDate);
862         QCOMPARE(task->dueDate(), dueDate);
863         QCOMPARE(task->property("itemId").toLongLong(), originalItem.id());
864     }
865 
shouldNotUpdateTaskFromProjectItem()866     void shouldNotUpdateTaskFromProjectItem()
867     {
868         // GIVEN
869 
870         // Data...
871         const QString summary = QStringLiteral("summary");
872         const QString content = QStringLiteral("content");
873         const bool isDone = true;
874         const QDate doneDate(2013, 11, 30);
875         const QDate startDate(2013, 11, 24);
876         const QDate dueDate(2014, 03, 01);
877 
878         // ... stored in a todo...
879         KCalCore::Todo::Ptr originalTodo(new KCalCore::Todo);
880         originalTodo->setSummary(summary);
881         originalTodo->setDescription(content);
882 
883         if (originalTodo)
884             originalTodo->setCompleted(QDateTime(doneDate));
885         else
886             originalTodo->setCompleted(isDone);
887         setTodoDates(originalTodo, startDate, dueDate);
888 
889         // ... as payload of an item...
890         Akonadi::Item originalItem;
891         originalItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
892         originalItem.setPayload<KCalCore::Todo::Ptr>(originalTodo);
893 
894         // ... deserialized as a task
895         Akonadi::Serializer serializer;
896         auto task = serializer.createTaskFromItem(originalItem);
897 
898         // WHEN
899         // A todo with the project flag
900         KCalCore::Todo::Ptr projectTodo(new KCalCore::Todo);
901         projectTodo->setSummary(QStringLiteral("foo"));
902         projectTodo->setCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyIsProject(), QStringLiteral("1"));
903 
904         // ... as payload of an item
905         Akonadi::Item projectItem;
906         projectItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
907         projectItem.setPayload<KCalCore::Todo::Ptr>(projectTodo);
908         serializer.updateTaskFromItem(task, projectItem);
909 
910         // THEN
911         QCOMPARE(task->title(), summary);
912         QCOMPARE(task->text(), content);
913         QCOMPARE(task->isDone(), isDone);
914         QCOMPARE(task->doneDate(), doneDate);
915         QCOMPARE(task->startDate(), startDate);
916         QCOMPARE(task->dueDate(), dueDate);
917         QCOMPARE(task->property("itemId").toLongLong(), originalItem.id());
918     }
919 
shouldCreateItemFromTask_data()920     void shouldCreateItemFromTask_data()
921     {
922         QTest::addColumn<QString>("summary");
923         QTest::addColumn<QString>("content");
924         QTest::addColumn<bool>("isDone");
925         QTest::addColumn<QDate>("doneDate");
926         QTest::addColumn<QDate>("startDate");
927         QTest::addColumn<QDate>("dueDate");
928         QTest::addColumn<qint64>("itemId");
929         QTest::addColumn<qint64>("parentCollectionId");
930         QTest::addColumn<QString>("todoUid");
931         QTest::addColumn<Domain::Task::Recurrence>("recurrence");
932         QTest::addColumn<Domain::Task::Attachments>("attachments");
933         QTest::addColumn<bool>("running");
934         QTest::addColumn<QStringList>("contexts");
935 
936         Domain::Task::Attachments attachments;
937 
938         Domain::Task::Attachment dataAttachment;
939         dataAttachment.setData("foo");
940         dataAttachment.setLabel("dataAttachment");
941         dataAttachment.setMimeType("text/plain");
942         dataAttachment.setIconName("text-plain");
943         attachments.append(dataAttachment);
944 
945         Domain::Task::Attachment uriAttachment;
946         uriAttachment.setUri(QUrl("https://www.kde.org"));
947         uriAttachment.setLabel("uriAttachment");
948         uriAttachment.setMimeType("text/html");
949         uriAttachment.setIconName("text-html");
950         attachments.append(uriAttachment);
951 
952         QTest::newRow("nominal case (no id)") << "summary" << "content" << false << QDate()
953                                               << QDate(2013, 11, 24) << QDate(2014, 03, 01)
954                                               << qint64(-1) << qint64(-1) << QString()
955                                               << Domain::Task::NoRecurrence
956                                               << attachments
957                                               << false
958                                               << QStringList();
959         QTest::newRow("nominal case (daily)") << "summary" << "content" << false << QDate()
960                                               << QDate(2013, 11, 24) << QDate(2014, 03, 01)
961                                               << qint64(-1) << qint64(-1) << QString()
962                                               << Domain::Task::RecursDaily
963                                               << Domain::Task::Attachments()
964                                               << false
965                                               << QStringList();
966         QTest::newRow("nominal case (weekly)") << "summary" << "content" << false << QDate()
967                                                << QDate(2013, 11, 24) << QDate(2014, 03, 01)
968                                                << qint64(-1) << qint64(-1) << QString()
969                                                << Domain::Task::RecursWeekly
970                                                << Domain::Task::Attachments()
971                                                << false
972                                                << QStringList();
973         QTest::newRow("nominal case (monthly)") << "summary" << "content" << false << QDate()
974                                                 << QDate(2013, 11, 24) << QDate(2014, 03, 01)
975                                                 << qint64(-1) << qint64(-1) << QString()
976                                                 << Domain::Task::RecursMonthly
977                                                 << Domain::Task::Attachments()
978                                                 << false
979                                                 << QStringList();
980         QTest::newRow("nominal case (yearly)") << "summary" << "content" << false << QDate()
981                                                 << QDate(2013, 11, 24) << QDate(2014, 03, 01)
982                                                 << qint64(-1) << qint64(-1) << QString()
983                                                 << Domain::Task::RecursYearly
984                                                 << Domain::Task::Attachments()
985                                                 << false
986                                                 << QStringList();
987         QTest::newRow("done case (no id)") << "summary" << "content" << true << QDate(2013, 11, 30)
988                                            << QDate(2013, 11, 24) << QDate(2014, 03, 01)
989                                            << qint64(-1) << qint64(-1) << QString()
990                                            << Domain::Task::NoRecurrence
991                                            << Domain::Task::Attachments()
992                                            << false
993                                            << QStringList();
994         QTest::newRow("empty case (no id)") << QString() << QString() << false << QDate()
995                                             << QDate() << QDate()
996                                             << qint64(-1) << qint64(-1) << QString()
997                                             << Domain::Task::NoRecurrence
998                                             << Domain::Task::Attachments()
999                                             << false
1000                                             << QStringList();
1001 #if 0 // if we ever need time info, then we need a Task::setAllDay(bool) just like KCalCore::Todo has.
1002       QTest::newRow("nominal_with_time_info_noid") << "summary" << "content" << true << QDateTime(QDate(2015, 3, 1), QTime(1, 2, 3), Qt::UTC)
1003                                               << QDateTime(QDate(2013, 11, 24), QTime(0, 1, 2), Qt::UTC) << QDateTime(QDate(2016, 3, 1), QTime(4, 5, 6), Qt::UTC)
1004                                               << qint64(-1) << qint64(-1) << QString()
1005                                               << Domain::Task::NoRecurrence
1006                                               << Domain::Task::Attachments()
1007                                               << false
1008                                               << QStringList();
1009 #endif
1010 
1011         QTest::newRow("nominal case (with id)") << "summary" << "content" << false << QDate()
1012                                                 << QDate(2013, 11, 24) << QDate(2014, 03, 01)
1013                                                 << qint64(42) << qint64(43) << "my-uid"
1014                                                 << Domain::Task::NoRecurrence
1015                                                 << Domain::Task::Attachments()
1016                                                 << false
1017                                                 << QStringList();
1018         QTest::newRow("done case (with id)") << "summary" << "content" << true << QDate(2013, 11, 30)
1019                                              << QDate(2013, 11, 24) << QDate(2014, 03, 01)
1020                                              << qint64(42) << qint64(43) << "my-uid"
1021                                              << Domain::Task::NoRecurrence
1022                                              << Domain::Task::Attachments()
1023                                              << false
1024                                              << QStringList();
1025         QTest::newRow("empty case (with id)") << QString() << QString() << false << QDate()
1026                                               << QDate() << QDate()
1027                                               << qint64(42) << qint64(43) << "my-uid"
1028                                               << Domain::Task::NoRecurrence
1029                                               << Domain::Task::Attachments()
1030                                               << false
1031                                               << QStringList();
1032         QTest::newRow("nominal case (running)") << "running" << QString() << false << QDate()
1033                                               << QDate(2013, 11, 24) << QDate(2014, 03, 01)
1034                                               << qint64(-1) << qint64(-1) << QString()
1035                                               << Domain::Task::NoRecurrence
1036                                               << Domain::Task::Attachments()
1037                                               << true
1038                                               << QStringList();
1039         QTest::newRow("with_context") << "with_context" << QString() << false << QDate()
1040                                               << QDate(2013, 11, 24) << QDate(2014, 03, 01)
1041                                               << qint64(-1) << qint64(-1) << QString()
1042                                               << Domain::Task::NoRecurrence
1043                                               << Domain::Task::Attachments()
1044                                               << true
1045                                               << QStringList({"c1", "c2"});
1046     }
1047 
shouldCreateItemFromTask()1048     void shouldCreateItemFromTask()
1049     {
1050         // GIVEN
1051 
1052         // Data...
1053         QFETCH(QString, summary);
1054         QFETCH(QString, content);
1055         QFETCH(bool, isDone);
1056         QFETCH(QDate, doneDate);
1057         QFETCH(QDate, startDate);
1058         QFETCH(QDate, dueDate);
1059         QFETCH(qint64, itemId);
1060         QFETCH(qint64, parentCollectionId);
1061         QFETCH(QString, todoUid);
1062         QFETCH(Domain::Task::Recurrence, recurrence);
1063         QFETCH(Domain::Task::Attachments, attachments);
1064         QFETCH(bool, running);
1065         QFETCH(QStringList, contexts);
1066 
1067         // ... stored in a task
1068         auto task = Domain::Task::Ptr::create();
1069         task->setTitle(summary);
1070         task->setText(content);
1071         task->setDone(isDone);
1072         task->setDoneDate(doneDate);
1073         task->setStartDate(startDate);
1074         task->setDueDate(dueDate);
1075         task->setRecurrence(recurrence);
1076         task->setAttachments(attachments);
1077         task->setRunning(running);
1078 
1079         if (itemId > 0)
1080             task->setProperty("itemId", itemId);
1081 
1082         if (parentCollectionId > 0)
1083             task->setProperty("parentCollectionId", parentCollectionId);
1084 
1085         if (!todoUid.isEmpty())
1086             task->setProperty("todoUid", todoUid);
1087 
1088         task->setProperty("relatedUid", "parent-uid");
1089         task->setProperty("contextUids", contexts);
1090 
1091         // WHEN
1092         Akonadi::Serializer serializer;
1093         auto item = serializer.createItemFromTask(task);
1094 
1095         // THEN
1096         QCOMPARE(item.mimeType(), KCalCore::Todo::todoMimeType());
1097 
1098         QCOMPARE(item.isValid(), itemId > 0);
1099         if (itemId > 0) {
1100             QCOMPARE(item.id(), itemId);
1101         }
1102 
1103         QCOMPARE(item.parentCollection().isValid(), parentCollectionId > 0);
1104         if (parentCollectionId > 0) {
1105             QCOMPARE(item.parentCollection().id(), parentCollectionId);
1106         }
1107 
1108         auto todo = item.payload<KCalCore::Todo::Ptr>();
1109         QCOMPARE(todo->summary(), summary);
1110         QCOMPARE(todo->description(), content);
1111         QCOMPARE(todo->isCompleted(), isDone);
1112         QCOMPARE(todo->completed().toLocalTime().date(), doneDate);
1113         QCOMPARE(todo->dtStart().toLocalTime().date(), startDate);
1114         QCOMPARE(todo->dtDue().toLocalTime().date(), dueDate);
1115         if (todo->dtStart().isValid()) {
1116             QCOMPARE(int(todo->dtStart().timeSpec()), int(Qt::LocalTime));
1117         }
1118         QVERIFY(todo->allDay()); // this is always true currently...
1119         const ushort expectedRecurrence = recurrence == Domain::Task::NoRecurrence ? KCalCore::Recurrence::rNone
1120                                         : recurrence == Domain::Task::RecursDaily ? KCalCore::Recurrence::rDaily
1121                                         : recurrence == Domain::Task::RecursWeekly ? KCalCore::Recurrence::rWeekly
1122                                         : recurrence == Domain::Task::RecursMonthly ? KCalCore::Recurrence::rMonthlyDay
1123                                         : recurrence == Domain::Task::RecursYearly ? KCalCore::Recurrence::rYearlyMonth
1124                                         : KCalCore::Recurrence::rNone; // Shouldn't happen though
1125         QCOMPARE(todo->recurrence()->recurrenceType(), expectedRecurrence);
1126         if (recurrence != Domain::Task::NoRecurrence)
1127             QCOMPARE(todo->recurrence()->frequency(), 1);
1128 
1129         QCOMPARE(todo->attachments().size(), attachments.size());
1130         for (int i = 0; i < attachments.size(); i++) {
1131             auto attachment = todo->attachments().at(i);
1132 #if KCALCORE_VERSION >= QT_VERSION_CHECK(5, 11, 80)
1133             QCOMPARE(attachment.isUri(), attachments.at(i).isUri());
1134             QCOMPARE(QUrl(attachment.uri()), attachments.at(i).uri());
1135             QCOMPARE(attachment.decodedData(), attachments.at(i).data());
1136             QCOMPARE(attachment.label(), attachments.at(i).label());
1137             QCOMPARE(attachment.mimeType(), attachments.at(i).mimeType());
1138 #else
1139             QCOMPARE(attachment->isUri(), attachments.at(i).isUri());
1140             QCOMPARE(QUrl(attachment->uri()), attachments.at(i).uri());
1141             QCOMPARE(attachment->decodedData(), attachments.at(i).data());
1142             QCOMPARE(attachment->label(), attachments.at(i).label());
1143             QCOMPARE(attachment->mimeType(), attachments.at(i).mimeType());
1144 #endif
1145         }
1146 
1147         if (!todoUid.isEmpty()) {
1148             QCOMPARE(todo->uid(), todoUid);
1149         }
1150 
1151         QCOMPARE(todo->relatedTo(), QStringLiteral("parent-uid"));
1152         QCOMPARE(todo->customProperty(Serializer::customPropertyAppName(), Serializer::customPropertyIsRunning()), running ? QStringLiteral("1") : QString());
1153         QCOMPARE(todo->customProperty(Serializer::customPropertyAppName(), Serializer::customPropertyContextList()), contexts.join(','));
1154     }
1155 
shouldVerifyIfAnItemIsATaskChild_data()1156     void shouldVerifyIfAnItemIsATaskChild_data()
1157     {
1158         QTest::addColumn<Domain::Task::Ptr>("task");
1159         QTest::addColumn<Akonadi::Item>("item");
1160         QTest::addColumn<bool>("isParent");
1161 
1162         // Create task
1163         const QString summary = QStringLiteral("summary");
1164         const QString content = QStringLiteral("content");
1165         const bool isDone = true;
1166         const QDate doneDate(QDate(2013, 11, 30));
1167         const QDate startDate(QDate(2013, 11, 24));
1168         const QDate dueDate(QDate(2014, 03, 01));
1169 
1170         // ... create a task
1171         Domain::Task::Ptr task(new Domain::Task);
1172         task->setTitle(summary);
1173         task->setText(content);
1174         task->setDone(isDone);
1175         task->setDoneDate(doneDate);
1176         task->setStartDate(startDate);
1177         task->setDueDate(dueDate);
1178         task->setProperty("todoUid", "1");
1179 
1180         // Create Child item
1181         KCalCore::Todo::Ptr childTodo(new KCalCore::Todo);
1182         childTodo->setSummary(summary);
1183         childTodo->setDescription(content);
1184 
1185         if (isDone)
1186             childTodo->setCompleted(QDateTime(doneDate));
1187         else
1188             childTodo->setCompleted(isDone);
1189 
1190         setTodoDates(childTodo, startDate, dueDate);
1191 
1192         Akonadi::Item childItem;
1193         childItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
1194         childItem.setPayload<KCalCore::Todo::Ptr>(childTodo);
1195 
1196         QTest::newRow("without parent") << task << childItem << false;
1197 
1198         // Create Child Item with parent
1199         KCalCore::Todo::Ptr childTodo2(new KCalCore::Todo);
1200         childTodo2->setSummary(summary);
1201         childTodo2->setDescription(content);
1202 
1203         if (isDone)
1204             childTodo2->setCompleted(QDateTime(doneDate));
1205         else
1206             childTodo2->setCompleted(isDone);
1207         setTodoDates(childTodo2, startDate, dueDate);
1208         childTodo2->setRelatedTo(QStringLiteral("1"));
1209 
1210         Akonadi::Item childItem2;
1211         childItem2.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
1212         childItem2.setPayload<KCalCore::Todo::Ptr>(childTodo2);
1213 
1214         QTest::newRow("with parent") << task << childItem2 << true;
1215 
1216         Domain::Task::Ptr invalidTask(new Domain::Task);
1217         QTest::newRow("with invalid task") << invalidTask << childItem << false;
1218 
1219         Akonadi::Item invalidItem;
1220         QTest::newRow("with invalid item") << task << invalidItem << false;
1221 
1222     }
1223 
shouldVerifyIfAnItemIsATaskChild()1224     void shouldVerifyIfAnItemIsATaskChild()
1225     {
1226         // GIVEN
1227         QFETCH(Domain::Task::Ptr, task);
1228         QFETCH(Akonadi::Item, item);
1229         QFETCH(bool, isParent);
1230 
1231         // WHEN
1232         Akonadi::Serializer serializer;
1233         bool value = serializer.isTaskChild(task, item);
1234 
1235         // THEN
1236         QCOMPARE(value, isParent);
1237     }
1238 
shouldRetrieveRelatedUidFromItem_data()1239     void shouldRetrieveRelatedUidFromItem_data()
1240     {
1241         QTest::addColumn<Akonadi::Item>("item");
1242         QTest::addColumn<QString>("expectedUid");
1243 
1244         Akonadi::Item item1;
1245         KCalCore::Todo::Ptr todo1(new KCalCore::Todo);
1246         item1.setPayload<KCalCore::Todo::Ptr>(todo1);
1247 
1248         Akonadi::Item item2;
1249         KCalCore::Todo::Ptr todo2(new KCalCore::Todo);
1250         todo2->setRelatedTo(QStringLiteral("1"));
1251         item2.setPayload<KCalCore::Todo::Ptr>(todo2);
1252 
1253         QTest::newRow("task without related") << item1 << QString();
1254         QTest::newRow("task with related") << item2 << "1";
1255     }
1256 
shouldRetrieveRelatedUidFromItem()1257     void shouldRetrieveRelatedUidFromItem()
1258     {
1259         // GIVEN
1260         QFETCH(Akonadi::Item, item);
1261         QFETCH(QString, expectedUid);
1262 
1263         // WHEN
1264         Akonadi::Serializer serializer;
1265         QString uid = serializer.relatedUidFromItem(item);
1266 
1267         // THEN
1268         QCOMPARE(uid, expectedUid);
1269     }
1270 
shouldCreateNoteFromItem_data()1271     void shouldCreateNoteFromItem_data()
1272     {
1273         QTest::addColumn<QString>("title");
1274         QTest::addColumn<QString>("text");
1275         QTest::addColumn<QString>("relatedUid");
1276 
1277         QTest::newRow("nominal case (no related)") << "A note title" << "A note content.\nWith two lines." << QString();
1278         QTest::newRow("nominal case (with related)") << "A note title" << "A note content.\nWith two lines." << "parent-uid";
1279         QTest::newRow("trailing new lines") << "A note title" << "Empty lines at the end.\n\n\n" << QString();
1280         QTest::newRow("empty case") << QString() << QString() << QString();
1281     }
1282 
shouldCreateProjectFromItem_data()1283     void shouldCreateProjectFromItem_data()
1284     {
1285         QTest::addColumn<QString>("summary");
1286 
1287         QTest::newRow("nominal case") << "summary";
1288         QTest::newRow("empty case") << QString();
1289     }
1290 
shouldCreateProjectFromItem()1291     void shouldCreateProjectFromItem()
1292     {
1293         // GIVEN
1294 
1295         // Data...
1296         QFETCH(QString, summary);
1297 
1298         // ... stored in a todo...
1299         KCalCore::Todo::Ptr todo(new KCalCore::Todo);
1300         todo->setSummary(summary);
1301         todo->setCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyIsProject(), QStringLiteral("1"));
1302         QVERIFY(!todo->uid().isEmpty());
1303 
1304         // ... as payload of an item
1305         Akonadi::Item item(42);
1306         item.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
1307         item.setPayload<KCalCore::Todo::Ptr>(todo);
1308 
1309         // which has a parent collection
1310         Akonadi::Collection collection(43);
1311         item.setParentCollection(collection);
1312 
1313         // WHEN
1314         Akonadi::Serializer serializer;
1315         Domain::Project::Ptr project = serializer.createProjectFromItem(item);
1316 
1317         // THEN
1318         QCOMPARE(project->name(), summary);
1319         QCOMPARE(project->property("itemId").toLongLong(), item.id());
1320         QCOMPARE(project->property("parentCollectionId").toLongLong(), collection.id());
1321         QCOMPARE(project->property("todoUid").toString(), todo->uid());
1322     }
1323 
shouldCreateNullProjectFromInvalidItem()1324     void shouldCreateNullProjectFromInvalidItem()
1325     {
1326         // GIVEN
1327         Akonadi::Item item;
1328 
1329         // WHEN
1330         Akonadi::Serializer serializer;
1331         Domain::Project::Ptr project = serializer.createProjectFromItem(item);
1332 
1333         // THEN
1334         QVERIFY(project.isNull());
1335     }
1336 
shouldCreateNullProjectFromTaskItem()1337     void shouldCreateNullProjectFromTaskItem()
1338     {
1339         // GIVEN
1340 
1341         // A todo without the project flag
1342         KCalCore::Todo::Ptr todo(new KCalCore::Todo);
1343         todo->setSummary(QStringLiteral("foo"));
1344 
1345         // ... as payload of an item
1346         Akonadi::Item item;
1347         item.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
1348         item.setPayload<KCalCore::Todo::Ptr>(todo);
1349 
1350         // WHEN
1351         Akonadi::Serializer serializer;
1352         Domain::Project::Ptr project = serializer.createProjectFromItem(item);
1353 
1354         // THEN
1355         QVERIFY(project.isNull());
1356     }
1357 
shouldUpdateProjectFromItem_data()1358     void shouldUpdateProjectFromItem_data()
1359     {
1360         QTest::addColumn<QString>("updatedSummary");
1361 
1362         QTest::newRow("no change") << "summary";
1363         QTest::newRow("changed") << "new summary";
1364     }
1365 
shouldUpdateProjectFromItem()1366     void shouldUpdateProjectFromItem()
1367     {
1368         // GIVEN
1369 
1370         // A todo...
1371         KCalCore::Todo::Ptr originalTodo(new KCalCore::Todo);
1372         originalTodo->setSummary(QStringLiteral("summary"));
1373         originalTodo->setCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyIsProject(), QStringLiteral("1"));
1374 
1375         // ... as payload of an item...
1376         Akonadi::Item originalItem(42);
1377         originalItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
1378         originalItem.setPayload<KCalCore::Todo::Ptr>(originalTodo);
1379 
1380         // ... which has a parent collection...
1381         Akonadi::Collection originalCollection(43);
1382         originalItem.setParentCollection(originalCollection);
1383 
1384         // ... deserialized as a project
1385         Akonadi::Serializer serializer;
1386         auto project = serializer.createProjectFromItem(originalItem);
1387 
1388         // WHEN
1389 
1390         // Data...
1391         QFETCH(QString, updatedSummary);
1392 
1393         // ... in a new todo...
1394         KCalCore::Todo::Ptr updatedTodo(new KCalCore::Todo);
1395         updatedTodo->setSummary(updatedSummary);
1396         updatedTodo->setCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyIsProject(), QStringLiteral("1"));
1397         QVERIFY(!updatedTodo->uid().isEmpty());
1398 
1399         // ... as payload of a new item
1400         Akonadi::Item updatedItem(44);
1401         updatedItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
1402         updatedItem.setPayload<KCalCore::Todo::Ptr>(updatedTodo);
1403 
1404         // ... which has a new parent collection
1405         Akonadi::Collection updatedCollection(45);
1406         updatedItem.setParentCollection(updatedCollection);
1407 
1408         serializer.updateProjectFromItem(project, updatedItem);
1409 
1410         // THEN
1411         QCOMPARE(project->name(), updatedSummary);
1412         QCOMPARE(project->property("itemId").toLongLong(), updatedItem.id());
1413         QCOMPARE(project->property("parentCollectionId").toLongLong(), updatedCollection.id());
1414         QCOMPARE(project->property("todoUid").toString(), updatedTodo->uid());
1415     }
1416 
shouldNotUpdateProjectFromInvalidItem()1417     void shouldNotUpdateProjectFromInvalidItem()
1418     {
1419         // GIVEN
1420 
1421         // Data...
1422         const QString summary = QStringLiteral("summary");
1423 
1424         // ... stored in a todo...
1425         KCalCore::Todo::Ptr originalTodo(new KCalCore::Todo);
1426         originalTodo->setSummary(summary);
1427         originalTodo->setCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyIsProject(), QStringLiteral("1"));
1428 
1429         // ... as payload of an item...
1430         Akonadi::Item originalItem;
1431         originalItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
1432         originalItem.setPayload<KCalCore::Todo::Ptr>(originalTodo);
1433 
1434         // ... deserialized as a project
1435         Akonadi::Serializer serializer;
1436         auto project = serializer.createProjectFromItem(originalItem);
1437 
1438         // WHEN
1439         Akonadi::Item invalidItem;
1440         serializer.updateProjectFromItem(project, invalidItem);
1441 
1442         // THEN
1443         QCOMPARE(project->name(), summary);
1444     }
1445 
shouldNotUpdateProjectFromTaskItem()1446     void shouldNotUpdateProjectFromTaskItem()
1447     {
1448         // GIVEN
1449 
1450         // Data...
1451         const QString summary = QStringLiteral("summary");
1452 
1453         // ... stored in a todo...
1454         KCalCore::Todo::Ptr originalTodo(new KCalCore::Todo);
1455         originalTodo->setSummary(summary);
1456         originalTodo->setCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyIsProject(), QStringLiteral("1"));
1457 
1458         // ... as payload of an item...
1459         Akonadi::Item originalItem;
1460         originalItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
1461         originalItem.setPayload<KCalCore::Todo::Ptr>(originalTodo);
1462 
1463         // ... deserialized as a project
1464         Akonadi::Serializer serializer;
1465         auto project = serializer.createProjectFromItem(originalItem);
1466 
1467         // WHEN
1468         // A todo without the project flag
1469         KCalCore::Todo::Ptr projectTodo(new KCalCore::Todo);
1470         projectTodo->setSummary(QStringLiteral("foo"));
1471 
1472         // ... as payload of an item
1473         Akonadi::Item projectItem;
1474         projectItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
1475         projectItem.setPayload<KCalCore::Todo::Ptr>(projectTodo);
1476         serializer.updateProjectFromItem(project, projectItem);
1477 
1478         // THEN
1479         QCOMPARE(project->name(), summary);
1480     }
1481 
shouldCreateItemFromProject_data()1482     void shouldCreateItemFromProject_data()
1483     {
1484         QTest::addColumn<QString>("summary");
1485         QTest::addColumn<qint64>("itemId");
1486         QTest::addColumn<qint64>("parentCollectionId");
1487 
1488         QTest::newRow("nominal case (no id)") << "summary" << qint64(-1) << qint64(-1);
1489         QTest::newRow("empty case (no id)") << QString() << qint64(-1) << qint64(-1);
1490 
1491         QTest::newRow("nominal case (with id)") << "summary" << qint64(42) << qint64(43);
1492         QTest::newRow("empty case (with id)") << QString() << qint64(42) << qint64(43);
1493     }
1494 
shouldCreateItemFromProject()1495     void shouldCreateItemFromProject()
1496     {
1497         // GIVEN
1498 
1499         // Data...
1500         QFETCH(QString, summary);
1501         QFETCH(qint64, itemId);
1502         QFETCH(qint64, parentCollectionId);
1503         const QString todoUid = QStringLiteral("test-uid");
1504 
1505         // ... stored in a project
1506         auto project = Domain::Project::Ptr::create();
1507         project->setName(summary);
1508         project->setProperty("todoUid", todoUid);
1509 
1510         if (itemId > 0)
1511             project->setProperty("itemId", itemId);
1512 
1513         if (parentCollectionId > 0)
1514             project->setProperty("parentCollectionId", parentCollectionId);
1515 
1516         // WHEN
1517         Akonadi::Serializer serializer;
1518         auto item = serializer.createItemFromProject(project);
1519 
1520         // THEN
1521         QCOMPARE(item.mimeType(), KCalCore::Todo::todoMimeType());
1522 
1523         QCOMPARE(item.isValid(), itemId > 0);
1524         if (itemId > 0) {
1525             QCOMPARE(item.id(), itemId);
1526         }
1527 
1528         QCOMPARE(item.parentCollection().isValid(), parentCollectionId > 0);
1529         if (parentCollectionId > 0) {
1530             QCOMPARE(item.parentCollection().id(), parentCollectionId);
1531         }
1532 
1533         auto todo = item.payload<KCalCore::Todo::Ptr>();
1534         QCOMPARE(todo->summary(), summary);
1535         QCOMPARE(todo->uid(), todoUid);
1536         QVERIFY(!todo->customProperty(Serializer::customPropertyAppName(), Serializer::customPropertyIsProject()).isEmpty());
1537     }
1538 
shouldVerifyIfAnItemIsAProjectChild_data()1539     void shouldVerifyIfAnItemIsAProjectChild_data()
1540     {
1541         QTest::addColumn<Domain::Project::Ptr>("project");
1542         QTest::addColumn<Akonadi::Item>("item");
1543         QTest::addColumn<bool>("isParent");
1544 
1545         // Create project
1546         auto project = Domain::Project::Ptr::create();
1547         project->setName(QStringLiteral("project"));
1548         project->setProperty("todoUid", "1");
1549 
1550         // Create unrelated todo
1551         auto unrelatedTodo = KCalCore::Todo::Ptr::create();
1552         unrelatedTodo->setSummary(QStringLiteral("summary"));
1553         Akonadi::Item unrelatedTodoItem;
1554         unrelatedTodoItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
1555         unrelatedTodoItem.setPayload<KCalCore::Todo::Ptr>(unrelatedTodo);
1556 
1557         QTest::newRow("unrelated todo") << project << unrelatedTodoItem << false;
1558 
1559         // Create child todo
1560         auto childTodo = KCalCore::Todo::Ptr::create();
1561         childTodo->setSummary(QStringLiteral("summary"));
1562         childTodo->setRelatedTo(QStringLiteral("1"));
1563         Akonadi::Item childTodoItem;
1564         childTodoItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
1565         childTodoItem.setPayload<KCalCore::Todo::Ptr>(childTodo);
1566 
1567         QTest::newRow("child todo") << project << childTodoItem << true;
1568 
1569         auto invalidProject = Domain::Project::Ptr::create();
1570         QTest::newRow("invalid project") << invalidProject << unrelatedTodoItem << false;
1571 
1572         Akonadi::Item invalidItem;
1573         QTest::newRow("invalid item") << project << invalidItem << false;
1574 
1575     }
1576 
shouldVerifyIfAnItemIsAProjectChild()1577     void shouldVerifyIfAnItemIsAProjectChild()
1578     {
1579         // GIVEN
1580         QFETCH(Domain::Project::Ptr, project);
1581         QFETCH(Akonadi::Item, item);
1582         QFETCH(bool, isParent);
1583 
1584         // WHEN
1585         Akonadi::Serializer serializer;
1586         bool value = serializer.isProjectChild(project, item);
1587 
1588         // THEN
1589         QCOMPARE(value, isParent);
1590     }
1591 
shouldUpdateItemParent_data()1592     void shouldUpdateItemParent_data()
1593     {
1594         QTest::addColumn<Akonadi::Item>("item");
1595         QTest::addColumn<Domain::Task::Ptr>("parent");
1596         QTest::addColumn<QString>("expectedRelatedToUid");
1597 
1598         Akonadi::Item item1;
1599         KCalCore::Todo::Ptr todo1(new KCalCore::Todo);
1600         item1.setPayload<KCalCore::Todo::Ptr>(todo1);
1601 
1602         Domain::Task::Ptr parent(new Domain::Task);
1603         parent->setProperty("todoUid", "1");
1604 
1605         QTest::newRow("nominal case") << item1 << parent << "1";
1606 
1607         Akonadi::Item item2;
1608         QTest::newRow("update item without payload") << item2 << parent << QString();
1609 
1610         Domain::Task::Ptr parent2(new Domain::Task);
1611         QTest::newRow("update item with a empty parent uid") << item1 << parent2 << QString();
1612     }
1613 
shouldUpdateItemParent()1614     void shouldUpdateItemParent()
1615     {
1616         // GIVEN
1617         QFETCH(Akonadi::Item, item);
1618         QFETCH(Domain::Task::Ptr, parent);
1619         QFETCH(QString, expectedRelatedToUid);
1620 
1621         // WHEN
1622         Akonadi::Serializer serializer;
1623         serializer.updateItemParent(item, parent);
1624 
1625         // THEN
1626         if (item.hasPayload<KCalCore::Todo::Ptr>()) {
1627             auto todo = item.payload<KCalCore::Todo::Ptr>();
1628             QString relatedUid = todo->relatedTo();
1629             QCOMPARE(relatedUid, expectedRelatedToUid);
1630         }
1631     }
1632 
shouldUpdateItemProject_data()1633     void shouldUpdateItemProject_data()
1634     {
1635         QTest::addColumn<Akonadi::Item>("item");
1636         QTest::addColumn<Domain::Project::Ptr>("parent");
1637         QTest::addColumn<QString>("expectedRelatedToUid");
1638 
1639         Akonadi::Item todoItem;
1640         KCalCore::Todo::Ptr todo(new KCalCore::Todo);
1641         todoItem.setPayload<KCalCore::Todo::Ptr>(todo);
1642 
1643         auto parent = Domain::Project::Ptr::create();
1644         parent->setProperty("todoUid", "1");
1645 
1646         QTest::newRow("nominal todo case") << todoItem << parent << "1";
1647 
1648         auto invalidParent = Domain::Project::Ptr::create();
1649         QTest::newRow("update todo item with a empty parent uid") << todoItem << invalidParent << QString();
1650 
1651         Akonadi::Item invalidItem;
1652         QTest::newRow("update item without payload") << invalidItem << parent << QString();
1653     }
1654 
shouldUpdateItemProject()1655     void shouldUpdateItemProject()
1656     {
1657         // GIVEN
1658         QFETCH(Akonadi::Item, item);
1659         QFETCH(Domain::Project::Ptr, parent);
1660         QFETCH(QString, expectedRelatedToUid);
1661 
1662         // WHEN
1663         Akonadi::Serializer serializer;
1664         serializer.updateItemProject(item, parent);
1665 
1666         // THEN
1667         if (item.hasPayload<KCalCore::Todo::Ptr>()) {
1668             auto todo = item.payload<KCalCore::Todo::Ptr>();
1669             const QString relatedUid = todo->relatedTo();
1670             QCOMPARE(relatedUid, expectedRelatedToUid);
1671         }
1672     }
1673 
shouldFilterChildrenItem_data()1674     void shouldFilterChildrenItem_data()
1675     {
1676         QTest::addColumn<Akonadi::Item>("item");
1677         QTest::addColumn<Akonadi::Item::List>("items");
1678         QTest::addColumn<int>("size");
1679 
1680         Akonadi::Item item(12);
1681         KCalCore::Todo::Ptr todo(new KCalCore::Todo);
1682         todo->setUid(QStringLiteral("1"));
1683         item.setPayload<KCalCore::Todo::Ptr>(todo);
1684         Akonadi::Item::List items;
1685 
1686         QTest::newRow("empty list") << item << items << 0;
1687 
1688         Akonadi::Item item2(13);
1689         KCalCore::Todo::Ptr todo2(new KCalCore::Todo);
1690         item2.setPayload<KCalCore::Todo::Ptr>(todo2);
1691         Akonadi::Item::List items2;
1692         items2 << item2;
1693 
1694         QTest::newRow("list without child") << item << items2 << 0;
1695 
1696         Akonadi::Item item3(14);
1697         KCalCore::Todo::Ptr todo3(new KCalCore::Todo);
1698         todo3->setUid(QStringLiteral("3"));
1699         todo3->setRelatedTo(QStringLiteral("1"));
1700         item3.setPayload<KCalCore::Todo::Ptr>(todo3);
1701         Akonadi::Item::List items3;
1702         items3 << item2 << item3;
1703 
1704         QTest::newRow("list with child") << item << items3 << 1;
1705 
1706         Akonadi::Item item4(15);
1707         KCalCore::Todo::Ptr todo4(new KCalCore::Todo);
1708         todo4->setRelatedTo(QStringLiteral("3"));
1709         item4.setPayload<KCalCore::Todo::Ptr>(todo4);
1710         Akonadi::Item::List items4;
1711         items4 << item2 << item3 << item4;
1712 
1713         QTest::newRow("list with child with a child") << item << items4 << 2;
1714 
1715         Akonadi::Item::List items5;
1716         items5 << item << item2 << item3 << item4;
1717         QTest::newRow("list with filter in list") << item << items5 << 2;
1718     }
1719 
shouldFilterChildrenItem()1720     void shouldFilterChildrenItem()
1721     {
1722         // GIVEN
1723         QFETCH(Akonadi::Item, item);
1724         QFETCH(Akonadi::Item::List, items);
1725         QFETCH(int, size);
1726 
1727         // WHEN
1728         Akonadi::Serializer serializer;
1729         Akonadi::Item::List list = serializer.filterDescendantItems(items, item);
1730 
1731         // THEN
1732         QCOMPARE(list.size(), size);
1733     }
1734 
shouldRemoveItemParent_data()1735     void shouldRemoveItemParent_data()
1736     {
1737         QTest::addColumn<Akonadi::Item>("item");
1738 
1739         Akonadi::Item item(15);
1740         KCalCore::Todo::Ptr todo(new KCalCore::Todo);
1741         todo->setRelatedTo(QStringLiteral("3"));
1742         item.setPayload<KCalCore::Todo::Ptr>(todo);
1743 
1744         QTest::newRow("nominal case") << item;
1745 
1746         Akonadi::Item item2(16);
1747         QTest::newRow("parent invalid") << item2;
1748     }
1749 
shouldRemoveItemParent()1750     void shouldRemoveItemParent()
1751     {
1752         // GIVEN
1753         QFETCH(Akonadi::Item, item);
1754 
1755         // WHEN
1756         Akonadi::Serializer serializer;
1757         serializer.removeItemParent(item);
1758 
1759         // THEN
1760         if (item.hasPayload<KCalCore::Todo::Ptr>())
1761             QCOMPARE(item.payload<KCalCore::Todo::Ptr>()->relatedTo(), QString());
1762     }
1763 
shouldPromoteItemToProject_data()1764     void shouldPromoteItemToProject_data()
1765     {
1766         QTest::addColumn<Akonadi::Item>("item");
1767 
1768         auto item = Akonadi::Item(15);
1769         auto todo = KCalCore::Todo::Ptr::create();
1770         todo->setRelatedTo(QStringLiteral("3"));
1771         item.setPayload(todo);
1772 
1773         QTest::newRow("nominal case") << item;
1774         QTest::newRow("invalid item") << Akonadi::Item(16);
1775     }
1776 
shouldPromoteItemToProject()1777     void shouldPromoteItemToProject()
1778     {
1779         // GIVEN
1780         QFETCH(Akonadi::Item, item);
1781 
1782         // WHEN
1783         Akonadi::Serializer serializer;
1784         serializer.promoteItemToProject(item);
1785 
1786         // THEN
1787         if (item.hasPayload<KCalCore::Todo::Ptr>()) {
1788             auto todo = item.payload<KCalCore::Todo::Ptr>();
1789             QCOMPARE(todo->relatedTo(), QString());
1790             QVERIFY(!todo->customProperty(Serializer::customPropertyAppName(), Serializer::customPropertyIsProject()).isEmpty());
1791         }
1792     }
1793 
shouldClearItem_data()1794     void shouldClearItem_data()
1795     {
1796         QTest::addColumn<Akonadi::Item*>("item");
1797 
1798         Akonadi::Item *itemWithContexts = new Akonadi::Item(15);
1799         KCalCore::Todo::Ptr todo(new KCalCore::Todo);
1800         // we can cheat and not really create contexts...
1801         todo->setCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyContextList(), "one,two");
1802         itemWithContexts->setPayload<KCalCore::Todo::Ptr>(todo);
1803         QTest::newRow("with_contexts") << itemWithContexts;
1804 
1805         Akonadi::Item *itemWithNoContext = new Akonadi::Item(15);
1806         itemWithNoContext->setPayload<KCalCore::Todo::Ptr>(todo);
1807         QTest::newRow("no_context") << itemWithNoContext;
1808     }
1809 
shouldClearItem()1810     void shouldClearItem()
1811     {
1812         // GIVEN
1813         QFETCH(Akonadi::Item*, item);
1814 
1815         // WHEN
1816         Akonadi::Serializer serializer;
1817         serializer.clearItem(item);
1818 
1819         // THEN
1820         auto todo = item->payload<KCalCore::Todo::Ptr>();
1821         QVERIFY(todo->customProperty(Serializer::customPropertyAppName(), Serializer::customPropertyContextList()).isEmpty());
1822         delete item;
1823     }
1824 
shouldCreateContextFromItem_data()1825     void shouldCreateContextFromItem_data()
1826     {
1827         QTest::addColumn<QString>("name");
1828 
1829         QTest::newRow("nominal case") << "Context42";
1830         QTest::newRow("empty name case") << "";
1831     }
1832 
shouldCreateContextFromItem()1833     void shouldCreateContextFromItem()
1834     {
1835         // GIVEN
1836 
1837         // Data...
1838         QFETCH(QString, name);
1839 
1840         // ... stored in a todo...
1841         KCalCore::Todo::Ptr todo(new KCalCore::Todo);
1842         todo->setSummary(name);
1843         todo->setCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyIsContext(), QStringLiteral("1"));
1844         QVERIFY(!todo->uid().isEmpty());
1845 
1846         // ... as payload of an item
1847         Akonadi::Item item(42);
1848         item.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
1849         item.setPayload<KCalCore::Todo::Ptr>(todo);
1850 
1851         // which has a parent collection
1852         Akonadi::Collection collection(43);
1853         item.setParentCollection(collection);
1854 
1855         // WHEN
1856         Akonadi::Serializer serializer;
1857         Domain::Context::Ptr context = serializer.createContextFromItem(item);
1858 
1859         // THEN
1860         QCOMPARE(context->name(), name);
1861         QCOMPARE(context->property("todoUid").toString(), todo->uid());
1862         QCOMPARE(context->property("itemId").toLongLong(), item.id());
1863         QCOMPARE(context->property("parentCollectionId").toLongLong(), collection.id());
1864     }
1865 
shouldNotCreateContextFromWrongItemType_data()1866     void shouldNotCreateContextFromWrongItemType_data()
1867     {
1868         QTest::addColumn<bool>("isProject");
1869 
1870         QTest::newRow("project") << true;
1871         QTest::newRow("task") << false;
1872     }
1873 
shouldNotCreateContextFromWrongItemType()1874     void shouldNotCreateContextFromWrongItemType()
1875     {
1876         // GIVEN
1877         QFETCH(bool, isProject);
1878 
1879         // A project todo
1880         KCalCore::Todo::Ptr originalTodo(new KCalCore::Todo);
1881         originalTodo->setSummary("summary");
1882         if (isProject)
1883             originalTodo->setCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyIsProject(), QStringLiteral("1"));
1884 
1885         // ... as payload of an item...
1886         Akonadi::Item originalItem;
1887         originalItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
1888         originalItem.setPayload<KCalCore::Todo::Ptr>(originalTodo);
1889 
1890         // WHEN
1891         Akonadi::Serializer serializer;
1892         Domain::Context::Ptr context = serializer.createContextFromItem(originalItem);
1893 
1894         // THEN
1895         QVERIFY(!context);
1896     }
1897 
shouldUpdateContextFromItem_data()1898     void shouldUpdateContextFromItem_data()
1899     {
1900         shouldCreateContextFromItem_data();
1901     }
1902 
shouldUpdateContextFromItem()1903     void shouldUpdateContextFromItem()
1904     {
1905         // GIVEN
1906 
1907         // Data...
1908         QFETCH(QString, name);
1909 
1910         // ... stored in a todo...
1911         KCalCore::Todo::Ptr todo(new KCalCore::Todo);
1912         todo->setSummary(name);
1913         todo->setCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyIsContext(), QStringLiteral("1"));
1914         QVERIFY(!todo->uid().isEmpty());
1915 
1916         // ... as payload of an item
1917         Akonadi::Item item(42);
1918         item.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
1919         item.setPayload<KCalCore::Todo::Ptr>(todo);
1920 
1921         // WHEN
1922         Akonadi::Serializer serializer;
1923         Domain::Context::Ptr context(new Domain::Context);
1924 
1925         serializer.updateContextFromItem(context, item);
1926 
1927         // THEN
1928         QCOMPARE(context->name(), name);
1929         QCOMPARE(context->property("todoUid").toString(), todo->uid());
1930         QCOMPARE(context->property("itemId").toLongLong(), item.id());
1931     }
1932 
shouldNotUpdateContextFromWrongItemType_data()1933     void shouldNotUpdateContextFromWrongItemType_data()
1934     {
1935         QTest::addColumn<bool>("isProject");
1936 
1937         QTest::newRow("project") << true;
1938         QTest::newRow("task") << false;
1939     }
1940 
shouldNotUpdateContextFromWrongItemType()1941     void shouldNotUpdateContextFromWrongItemType()
1942     {
1943         // GIVEN
1944         QFETCH(bool, isProject);
1945 
1946         // A context
1947         auto context = Domain::Context::Ptr::create();
1948         context->setName("summary");
1949         context->setProperty("todoUid", qint64(43));
1950 
1951         KCalCore::Todo::Ptr wrongTodo(new KCalCore::Todo);
1952         wrongTodo->setSummary("wrongSummary");
1953         if (isProject)
1954             wrongTodo->setCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyIsProject(), QStringLiteral("1"));
1955 
1956         Akonadi::Item wrongItem;
1957         wrongItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
1958         wrongItem.setPayload<KCalCore::Todo::Ptr>(wrongTodo);
1959 
1960         // WHEN
1961         Akonadi::Serializer serializer;
1962         serializer.updateContextFromItem(context, wrongItem);
1963 
1964         // THEN
1965         QCOMPARE(context->name(), "summary");
1966         QCOMPARE(context->property("todoUid").toLongLong(), qint64(43));
1967     }
1968 
shouldVerifyIfAnItemIsAContextChild_data()1969     void shouldVerifyIfAnItemIsAContextChild_data()
1970     {
1971         QTest::addColumn<Domain::Context::Ptr>("context");
1972         QTest::addColumn<Akonadi::Item>("item");
1973         QTest::addColumn<bool>("isChild");
1974 
1975         // Create a context
1976         auto context = Domain::Context::Ptr::create();
1977         const QString contextUid = QStringLiteral("abc-123");
1978         context->setProperty("todoUid", contextUid);
1979 
1980         Akonadi::Item unrelatedItem;
1981         QTest::newRow("Unrelated item") << context << unrelatedItem << false;
1982 
1983         Akonadi::Item relatedItem;
1984         auto todo = KCalCore::Todo::Ptr::create();
1985         todo->setSummary("summary");
1986         todo->setCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyContextList(), contextUid);
1987         relatedItem.setPayload<KCalCore::Todo::Ptr>(todo);
1988         QTest::newRow("Related item") << context << relatedItem << true;
1989 
1990         auto invalidContext = Domain::Context::Ptr::create();
1991         QTest::newRow("Invalid context") << invalidContext << relatedItem << false;
1992 
1993         Akonadi::Item invalidItem;
1994         QTest::newRow("Invalid Item") << context << invalidItem << false;
1995     }
1996 
shouldVerifyIfAnItemIsAContextChild()1997     void shouldVerifyIfAnItemIsAContextChild()
1998     {
1999         // GIVEN
2000         QFETCH(Domain::Context::Ptr, context);
2001         QFETCH(Akonadi::Item, item);
2002         QFETCH(bool, isChild);
2003 
2004         // WHEN
2005         Akonadi::Serializer serializer;
2006         bool value = serializer.isContextChild(context, item);
2007 
2008         // THEN
2009         QCOMPARE(value, isChild);
2010     }
2011 
shouldCreateItemFromContext_data()2012     void shouldCreateItemFromContext_data()
2013     {
2014         QTest::addColumn<QString>("name");
2015         QTest::addColumn<qint64>("itemId");
2016         QTest::addColumn<QString>("todoUid");
2017 
2018         const QString nameInternet = QStringLiteral("Internet");
2019         const QString uid = QStringLiteral("uid-123");
2020 
2021         QTest::newRow("nominal_case") << nameInternet << qint64(42) << uid;
2022         QTest::newRow("no_item_id") << nameInternet<< qint64(-1) << uid;
2023         QTest::newRow("null_uid") << nameInternet << qint64(42) << QString();
2024         QTest::newRow("no_name") << QString() << qint64(42) << uid;
2025     }
2026 
shouldCreateItemFromContext()2027     void shouldCreateItemFromContext()
2028     {
2029         // GIVEN
2030         QFETCH(QString, name);
2031         QFETCH(qint64, itemId);
2032         QFETCH(QString, todoUid);
2033 
2034         // WHEN
2035         auto context = Domain::Context::Ptr::create();
2036         context->setProperty("todoUid", todoUid);
2037         if (itemId > 0)
2038             context->setProperty("itemId", itemId);
2039 
2040         context->setName(name);
2041 
2042         Akonadi::Serializer serializer;
2043         Akonadi::Item item = serializer.createItemFromContext(context);
2044         auto todo = item.payload<KCalCore::Todo::Ptr>();
2045 
2046         // THEN
2047         QCOMPARE(todo->summary(), name);
2048 
2049         if (!todoUid.isEmpty()) {
2050             QCOMPARE(todo->uid(), todoUid);
2051         }
2052         QCOMPARE(item.id(), itemId);
2053     }
2054 
shouldTestIfItemRepresentsContext_data()2055     void shouldTestIfItemRepresentsContext_data()
2056     {
2057         QTest::addColumn<QString>("contextUid");
2058         QTest::addColumn<QString>("todoUid");
2059         QTest::addColumn<bool>("expectedResult");
2060 
2061         QTest::newRow("yes") << "context-123" << "context-123" << true;
2062         QTest::newRow("no") << "context-123" << "another-context" << false;
2063     }
2064 
shouldAddContextToTask_data()2065     void shouldAddContextToTask_data()
2066     {
2067         QTest::addColumn<Domain::Context::Ptr>("context");
2068         QTest::addColumn<Akonadi::Item>("item");
2069         QTest::addColumn<QString>("expectedContextList");
2070 
2071         // Create a context
2072         auto context = Domain::Context::Ptr::create();
2073         const QString contextUid = QStringLiteral("abc-123");
2074         context->setProperty("todoUid", contextUid);
2075 
2076         Akonadi::Item item;
2077         auto todo = KCalCore::Todo::Ptr::create();
2078         todo->setSummary("summary");
2079         item.setPayload<KCalCore::Todo::Ptr>(todo);
2080         QTest::newRow("item_with_no_context") << context << item << contextUid;
2081 
2082         Akonadi::Item item2;
2083         auto todo2 = KCalCore::Todo::Ptr::create();
2084         todo2->setCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyContextList(), "another");
2085         item2.setPayload<KCalCore::Todo::Ptr>(todo2);
2086         const QString bothContexts = QStringLiteral("another,") + contextUid;
2087         QTest::newRow("item_with_another_context") << context << item2 << bothContexts;
2088 
2089         Akonadi::Item item3;
2090         auto todo3 = KCalCore::Todo::Ptr::create();
2091         todo3->setCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyContextList(), bothContexts);
2092         item3.setPayload<KCalCore::Todo::Ptr>(todo3);
2093         QTest::newRow("item_with_this_context_already") << context << item3 << bothContexts;
2094     }
2095 
shouldAddContextToTask()2096     void shouldAddContextToTask()
2097     {
2098         // GIVEN
2099         QFETCH(Domain::Context::Ptr, context);
2100         QFETCH(Akonadi::Item, item);
2101         QFETCH(QString, expectedContextList);
2102 
2103         // WHEN
2104         Akonadi::Serializer serializer;
2105         serializer.addContextToTask(context, item);
2106 
2107         // THEN
2108         KCalCore::Todo::Ptr todo = item.payload<KCalCore::Todo::Ptr>();
2109         QCOMPARE(todo->customProperty(Serializer::customPropertyAppName(), Serializer::customPropertyContextList()), expectedContextList);
2110     }
2111 
shouldRemoveContextFromTask_data()2112     void shouldRemoveContextFromTask_data()
2113     {
2114         QTest::addColumn<Domain::Context::Ptr>("context");
2115         QTest::addColumn<Akonadi::Item>("item");
2116         QTest::addColumn<QString>("expectedContextList");
2117 
2118         auto context = Domain::Context::Ptr::create();
2119         const QString contextUid = QStringLiteral("abc-123");
2120         context->setProperty("todoUid", contextUid);
2121 
2122         Akonadi::Item item;
2123         auto todo = KCalCore::Todo::Ptr::create();
2124         todo->setSummary("summary");
2125         item.setPayload<KCalCore::Todo::Ptr>(todo);
2126         QTest::newRow("item_with_no_context") << context << item << QString();
2127 
2128         Akonadi::Item item2;
2129         auto todo2 = KCalCore::Todo::Ptr::create();
2130         todo2->setCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyContextList(), "another");
2131         item2.setPayload<KCalCore::Todo::Ptr>(todo2);
2132         QTest::newRow("item_with_another_context") << context << item2 << QString("another");
2133 
2134         Akonadi::Item item3;
2135         auto todo3 = KCalCore::Todo::Ptr::create();
2136         todo3->setCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyContextList(), contextUid);
2137         item3.setPayload<KCalCore::Todo::Ptr>(todo3);
2138         QTest::newRow("item_with_this_context_already") << context << item3 << QString();
2139 
2140         Akonadi::Item item4;
2141         auto todo4 = KCalCore::Todo::Ptr::create();
2142         const QString bothContexts = QStringLiteral("another,") + contextUid;
2143         todo4->setCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyContextList(), bothContexts);
2144         item4.setPayload<KCalCore::Todo::Ptr>(todo4);
2145         QTest::newRow("item_with_two_contexts") << context << item4 << QString("another");
2146     }
2147 
shouldRemoveContextFromTask()2148     void shouldRemoveContextFromTask()
2149     {
2150         // GIVEN
2151         QFETCH(Domain::Context::Ptr, context);
2152         QFETCH(Akonadi::Item, item);
2153         QFETCH(QString, expectedContextList);
2154 
2155         // WHEN
2156         Akonadi::Serializer serializer;
2157         serializer.removeContextFromTask(context, item);
2158 
2159         // THEN
2160         KCalCore::Todo::Ptr todo = item.payload<KCalCore::Todo::Ptr>();
2161         QCOMPARE(todo->customProperty(Serializer::customPropertyAppName(), Serializer::customPropertyContextList()), expectedContextList);
2162     }
2163 
2164     // Investigation into how to differentiate all-day events from events with time,
2165     // using QDateTime only. Doesn't seem to be possible.
noWayToHaveQDateTimeWithoutTime()2166     void noWayToHaveQDateTimeWithoutTime()
2167     {
2168         // GIVEN a QDateTime without time information
2169         QDateTime dateOnly(QDate(2016, 6, 12), QTime(-1, -1, -1));
2170         // THEN we can't detect that there was no time information, i.e. all day event
2171         QVERIFY(dateOnly.time().isValid()); // I wish this was "!"
2172         QVERIFY(!dateOnly.time().isNull()); // got converted to midnight localtime by QDateTime
2173         // This doesn't help, QDateTime converts "null time" to midnight.
2174         dateOnly.setTime(QTime());
2175         QVERIFY(dateOnly.time().isValid()); // same as above
2176         QVERIFY(!dateOnly.time().isNull()); // same as above
2177 
2178         // GIVEN a QDateTime at midnight
2179         QDateTime atMidnight(QDate(2016, 6, 12), QTime(0, 0, 0));
2180         // THEN we can detect that a time information was present
2181         QVERIFY(atMidnight.time().isValid());
2182         QVERIFY(!atMidnight.time().isNull());
2183 
2184 #if 0
2185         // GIVEN a KDateTime without time information
2186         KDateTime kdOnly(QDate(2016, 6, 12));
2187         // THEN we can detect that there was no time information, i.e. all day event
2188         QVERIFY(kdOnly.isDateOnly());
2189 #endif
2190     }
2191 };
2192 
2193 ZANSHIN_TEST_MAIN(AkonadiSerializerTest)
2194 
2195 #include "akonadiserializertest.moc"
2196