File indexing completed on 2025-01-05 04:59:54

0001 /*
0002  * SPDX-FileCopyrightText: 2014 Kevin Ottens <ervin@kde.org>
0003    SPDX-FileCopyrightText: 2014 Rémi Benoit <r3m1.benoit@gmail.com>
0004  * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005  */
0006 
0007 
0008 #include <testlib/qtest_zanshin.h>
0009 
0010 #include "akonadi/akonadiserializer.h"
0011 #include "akonadi/akonadiapplicationselectedattribute.h"
0012 #include "akonadi/akonaditimestampattribute.h"
0013 
0014 #include <Akonadi/Collection>
0015 #include <Akonadi/EntityDisplayAttribute>
0016 #include <Akonadi/Item>
0017 #include <KCalendarCore/Todo>
0018 #if __has_include(<kcalcore_version.h>)
0019 #include <kcalcore_version.h>
0020 #else
0021 #include <kcalendarcore_version.h>
0022 #endif
0023 
0024 Q_DECLARE_METATYPE(Akonadi::Item*)
0025 
0026 using Akonadi::Serializer;
0027 
0028 static void setTodoDates(KCalendarCore::Todo::Ptr todo, const QDate &start, const QDate &due) {
0029     todo->setDtStart(start.startOfDay());
0030     todo->setDtDue(due.startOfDay());
0031 }
0032 
0033 class AkonadiSerializerTest : public QObject
0034 {
0035     Q_OBJECT
0036 private slots:
0037     void shouldKnowWhenAnObjectRepresentsACollection()
0038     {
0039         // GIVEN
0040         Akonadi::Serializer serializer;
0041         auto object = Akonadi::Serializer::QObjectPtr::create();
0042         Akonadi::Collection collection(42);
0043 
0044         // WHEN
0045         // Nothing yet
0046 
0047         // THEN
0048         QVERIFY(!serializer.representsCollection(object, collection));
0049 
0050         // WHEN
0051         object->setProperty("collectionId", 42);
0052 
0053         // THEN
0054         QVERIFY(serializer.representsCollection(object, collection));
0055 
0056         // WHEN
0057         object->setProperty("collectionId", 43);
0058 
0059         // THEN
0060         QVERIFY(!serializer.representsCollection(object, collection));
0061     }
0062 
0063     void shouldKnowWhenAnObjectRepresentsAnItem()
0064     {
0065         // GIVEN
0066         Akonadi::Serializer serializer;
0067         auto object = Akonadi::Serializer::QObjectPtr::create();
0068         Akonadi::Item item(42);
0069 
0070         // WHEN
0071         // Nothing yet
0072 
0073         // THEN
0074         QVERIFY(!serializer.representsItem(object, item));
0075 
0076         // WHEN
0077         object->setProperty("itemId", 42);
0078 
0079         // THEN
0080         QVERIFY(serializer.representsItem(object, item));
0081 
0082         // WHEN
0083         object->setProperty("itemId", 43);
0084 
0085         // THEN
0086         QVERIFY(!serializer.representsItem(object, item));
0087     }
0088 
0089     void shouldKnowTaskItemUid_data()
0090     {
0091         QTest::addColumn<Akonadi::Item>("item");
0092         QTest::addColumn<QString>("expectedUid");
0093 
0094         Akonadi::Item item1;
0095         KCalendarCore::Todo::Ptr todo1(new KCalendarCore::Todo);
0096         todo1->setUid(QString());
0097         item1.setPayload<KCalendarCore::Todo::Ptr>(todo1);
0098 
0099         Akonadi::Item item2;
0100         KCalendarCore::Todo::Ptr todo2(new KCalendarCore::Todo);
0101         todo2->setUid(QStringLiteral("1"));
0102         item2.setPayload<KCalendarCore::Todo::Ptr>(todo2);
0103 
0104         QTest::newRow("task without uid") << item1 << QString();
0105         QTest::newRow("task with uid") << item2 << "1";
0106     }
0107 
0108     void shouldKnowTaskItemUid()
0109     {
0110         // GIVEN
0111         QFETCH(Akonadi::Item, item);
0112         QFETCH(QString, expectedUid);
0113 
0114         // WHEN
0115         Akonadi::Serializer serializer;
0116         QString uid = serializer.itemUid(item);
0117 
0118         // THEN
0119         QCOMPARE(uid, expectedUid);
0120     }
0121 
0122     void shouldCreateDataSourceFromCollection_data()
0123     {
0124         QTest::addColumn<QString>("name");
0125         QTest::addColumn<QString>("iconName");
0126         QTest::addColumn<QStringList>("mimeTypes");
0127         QTest::addColumn<bool>("hasSelectedAttribute");
0128         QTest::addColumn<bool>("isSelected");
0129 
0130         const auto noteMimeTypes = QStringList() << QStringLiteral("text/x-vnd.akonadi.note");
0131         const auto taskMimeTypes = QStringList() << QStringLiteral("application/x-vnd.akonadi.calendar.todo");
0132         const auto bogusMimeTypes = QStringList() << QStringLiteral("foo/bar");
0133         const auto allMimeTypes = noteMimeTypes + taskMimeTypes + bogusMimeTypes;
0134 
0135         QTest::newRow("nominal case") << "name" << "icon" << allMimeTypes << true << false;
0136 
0137         QTest::newRow("only notes") << "name" << "icon" << noteMimeTypes << true << false;
0138         QTest::newRow("only tasks") << "name" << "icon" << taskMimeTypes << true << false;
0139         QTest::newRow("only bogus") << "name" << "icon" << bogusMimeTypes << true << false;
0140 
0141         QTest::newRow("no selected attribute") << "name" << "icon" << allMimeTypes << false << false;
0142         QTest::newRow("selected attribute (false)") << "name" << "icon" << allMimeTypes << true << false;
0143         QTest::newRow("selected attribute (true)") << "name" << "icon" << allMimeTypes << true << true;
0144 
0145         QTest::newRow("empty case") << QString() << QString() << QStringList() << false << false;
0146     }
0147 
0148     void shouldCreateDataSourceFromCollection()
0149     {
0150         // GIVEN
0151 
0152         // Data...
0153         QFETCH(QString, name);
0154         QFETCH(QString, iconName);
0155         QFETCH(QStringList, mimeTypes);
0156         QFETCH(bool, hasSelectedAttribute);
0157         QFETCH(bool, isSelected);
0158 
0159         Domain::DataSource::ContentTypes expectedContentTypes;
0160         if (mimeTypes.contains(QStringLiteral("application/x-vnd.akonadi.calendar.todo"))) {
0161             expectedContentTypes |= Domain::DataSource::Tasks;
0162         }
0163 
0164         // ... stored in a collection
0165         Akonadi::Collection collection(42);
0166         collection.setContentMimeTypes(mimeTypes);
0167         collection.setName(name);
0168         auto displayAttribute = new Akonadi::EntityDisplayAttribute;
0169         displayAttribute->setIconName(iconName);
0170         collection.addAttribute(displayAttribute);
0171         if (hasSelectedAttribute) {
0172             auto selectedAttribute = new Akonadi::ApplicationSelectedAttribute;
0173             selectedAttribute->setSelected(isSelected);
0174             collection.addAttribute(selectedAttribute);
0175         }
0176 
0177         // WHEN
0178         Akonadi::Serializer serializer;
0179         auto dataSource = serializer.createDataSourceFromCollection(collection, Akonadi::SerializerInterface::BaseName);
0180 
0181         // THEN
0182         QCOMPARE(dataSource->name(), name);
0183         QCOMPARE(dataSource->iconName(), iconName);
0184         QCOMPARE(dataSource->contentTypes(), expectedContentTypes);
0185         QCOMPARE(dataSource->isSelected(), !hasSelectedAttribute || isSelected);
0186         QCOMPARE(dataSource->property("collectionId").value<Akonadi::Collection::Id>(), collection.id());
0187     }
0188 
0189     void shouldCreateNullDataSourceFromInvalidCollection()
0190     {
0191         // GIVEN
0192         Akonadi::Collection collection;
0193 
0194         // WHEN
0195         Akonadi::Serializer serializer;
0196         auto dataSource = serializer.createDataSourceFromCollection(collection, Akonadi::SerializerInterface::BaseName);
0197 
0198         // THEN
0199         QVERIFY(dataSource.isNull());
0200     }
0201 
0202     void shouldUpdateDataSourceFromCollection_data()
0203     {
0204         QTest::addColumn<QString>("updatedName");
0205 
0206         QTest::newRow("no change") << "name";
0207         QTest::newRow("changed") << "new name";
0208     }
0209 
0210     void shouldUpdateDataSourceFromCollection()
0211     {
0212         // GIVEN
0213 
0214         // A collection...
0215         Akonadi::Collection originalCollection(42);
0216         originalCollection.setName(QStringLiteral("name"));
0217 
0218         // ... deserialized as a data source
0219         Akonadi::Serializer serializer;
0220         auto dataSource = serializer.createDataSourceFromCollection(originalCollection, Akonadi::SerializerInterface::BaseName);
0221 
0222         // WHEN
0223 
0224         // Data...
0225         QFETCH(QString, updatedName);
0226 
0227         // ... in a new collection
0228         Akonadi::Collection updatedCollection(42);
0229         updatedCollection.setName(updatedName);
0230 
0231         serializer.updateDataSourceFromCollection(dataSource, updatedCollection, Akonadi::SerializerInterface::BaseName);
0232 
0233         // THEN
0234         QCOMPARE(dataSource->name(), updatedName);
0235     }
0236 
0237     void shouldNotUpdateDataSourceFromInvalidCollection()
0238     {
0239         // GIVEN
0240 
0241         // Data...
0242         const QString name = QStringLiteral("name");
0243 
0244         // ... stored in a collection...
0245         Akonadi::Collection originalCollection(42);
0246         originalCollection.setName(name);
0247 
0248         // ... deserialized as a data source
0249         Akonadi::Serializer serializer;
0250         auto dataSource = serializer.createDataSourceFromCollection(originalCollection, Akonadi::SerializerInterface::BaseName);
0251 
0252         // WHEN
0253         Akonadi::Collection invalidCollection;
0254         invalidCollection.setName(QStringLiteral("foo"));
0255         serializer.updateDataSourceFromCollection(dataSource, invalidCollection, Akonadi::SerializerInterface::BaseName);
0256 
0257         // THEN
0258         QCOMPARE(dataSource->name(), name);
0259     }
0260 
0261     void shouldNameDataSourceFromCollectionPathIfRequested()
0262     {
0263         // GIVEN
0264 
0265         // Data...
0266         const QString name = QStringLiteral("name");
0267         const QString parentName = QStringLiteral("parent");
0268 
0269         // ... stored in a collection with a parent
0270         Akonadi::Collection collection(42);
0271         collection.setName(name);
0272         Akonadi::Collection parentCollection(41);
0273         parentCollection.setName(QStringLiteral("Foo"));
0274         auto attribute = new Akonadi::EntityDisplayAttribute;
0275         attribute->setDisplayName(parentName);
0276         parentCollection.addAttribute(attribute);
0277         collection.setParentCollection(parentCollection);
0278 
0279         // WHEN
0280         Akonadi::Serializer serializer;
0281         auto dataSource1 = serializer.createDataSourceFromCollection(collection, Akonadi::SerializerInterface::FullPath);
0282         auto dataSource2 = serializer.createDataSourceFromCollection(collection, Akonadi::SerializerInterface::BaseName);
0283 
0284         // Give it another try with the root
0285         parentCollection.setParentCollection(Akonadi::Collection::root());
0286         collection.setParentCollection(parentCollection);
0287         auto dataSource3 = serializer.createDataSourceFromCollection(collection, Akonadi::SerializerInterface::FullPath);
0288         auto dataSource4 = serializer.createDataSourceFromCollection(collection, Akonadi::SerializerInterface::BaseName);
0289 
0290         // THEN
0291         QCOMPARE(dataSource1->name(), QString(parentName + " » " + name));
0292         QCOMPARE(dataSource2->name(), name);
0293         QCOMPARE(dataSource3->name(), QString(parentName + " » " + name));
0294         QCOMPARE(dataSource4->name(), name);
0295     }
0296 
0297     void shouldCreateCollectionFromDataSource_data()
0298     {
0299         QTest::addColumn<QString>("name");
0300         QTest::addColumn<QString>("iconName");
0301         QTest::addColumn<Domain::DataSource::ContentTypes>("contentTypes");
0302         QTest::addColumn<bool>("isSelected");
0303 
0304         const auto noType = Domain::DataSource::ContentTypes(Domain::DataSource::NoContent);
0305         const auto taskType = Domain::DataSource::ContentTypes(Domain::DataSource::Tasks);
0306 
0307         QTest::newRow("only tasks") << "name" << "icon-name" << taskType << true;
0308         QTest::newRow("only nothing ;)") << "name" << "icon-name" << noType << true;
0309 
0310         QTest::newRow("not selected") << "name" << "icon-name" << taskType << false;
0311         QTest::newRow("selected") << "name" << "icon-name" << taskType << true;
0312 
0313         QTest::newRow("empty case") << QString() << QString() << noType << true;
0314     }
0315 
0316     void shouldCreateCollectionFromDataSource()
0317     {
0318         // GIVEN
0319         const auto timestamp = QDateTime::currentMSecsSinceEpoch();
0320 
0321         // Data...
0322         QFETCH(QString, name);
0323         QFETCH(QString, iconName);
0324         QFETCH(Domain::DataSource::ContentTypes, contentTypes);
0325         QFETCH(bool, isSelected);
0326 
0327         QStringList mimeTypes;
0328         if (contentTypes & Domain::DataSource::Tasks)
0329             mimeTypes << QStringLiteral("application/x-vnd.akonadi.calendar.todo");
0330 
0331 
0332         // ... stored in a data source
0333         auto source = Domain::DataSource::Ptr::create();
0334         source->setName(name);
0335         source->setIconName(iconName);
0336         source->setContentTypes(contentTypes);
0337         source->setSelected(isSelected);
0338         source->setProperty("collectionId", 42);
0339 
0340         // WHEN
0341         Akonadi::Serializer serializer;
0342         auto collection = serializer.createCollectionFromDataSource(source);
0343 
0344         // THEN
0345         QCOMPARE(collection.id(), source->property("collectionId").value<Akonadi::Collection::Id>());
0346         QVERIFY(collection.hasAttribute<Akonadi::ApplicationSelectedAttribute>());
0347         QCOMPARE(collection.attribute<Akonadi::ApplicationSelectedAttribute>()->isSelected(), isSelected);
0348         QVERIFY(collection.hasAttribute<Akonadi::TimestampAttribute>());
0349         QVERIFY(collection.attribute<Akonadi::TimestampAttribute>()->timestamp() >= timestamp);
0350     }
0351 
0352     void shouldVerifyIfCollectionIsSelected_data()
0353     {
0354         QTest::addColumn<QStringList>("mimeTypes");
0355         QTest::addColumn<bool>("hasSelectedAttribute");
0356         QTest::addColumn<bool>("isSelected");
0357         QTest::addColumn<bool>("expectedSelected");
0358 
0359         const auto taskMimeTypes = QStringList() << QStringLiteral("application/x-vnd.akonadi.calendar.todo");
0360         const auto bogusMimeTypes = QStringList() << QStringLiteral("foo/bar");
0361         const auto allMimeTypes = taskMimeTypes + bogusMimeTypes;
0362 
0363         QTest::newRow("nominal case") << allMimeTypes << true << false << false;
0364 
0365         QTest::newRow("only tasks") << taskMimeTypes << true << false << false;
0366         QTest::newRow("only bogus") << bogusMimeTypes << true << false << false;
0367 
0368         QTest::newRow("selected, only tasks") << taskMimeTypes << true << true << true;
0369         QTest::newRow("selected, only bogus") << bogusMimeTypes << true << true << false;
0370 
0371         QTest::newRow("no selected attribute") << allMimeTypes << false << false << true;
0372         QTest::newRow("selected attribute (false)") << allMimeTypes << true << false << false;
0373         QTest::newRow("selected attribute (true)") << allMimeTypes << true << true << true;
0374 
0375         QTest::newRow("empty case") << QStringList() << false << false << false;
0376     }
0377 
0378     void shouldVerifyIfCollectionIsSelected()
0379     {
0380         // GIVEN
0381         QFETCH(QStringList, mimeTypes);
0382         QFETCH(bool, hasSelectedAttribute);
0383         QFETCH(bool, isSelected);
0384 
0385         Domain::DataSource::ContentTypes expectedContentTypes;
0386         if (mimeTypes.contains(QStringLiteral("application/x-vnd.akonadi.calendar.todo"))) {
0387             expectedContentTypes |= Domain::DataSource::Tasks;
0388         }
0389 
0390         // ... stored in a collection
0391         Akonadi::Collection collection(42);
0392         collection.setContentMimeTypes(mimeTypes);
0393         if (hasSelectedAttribute) {
0394             auto selectedAttribute = new Akonadi::ApplicationSelectedAttribute;
0395             selectedAttribute->setSelected(isSelected);
0396             collection.addAttribute(selectedAttribute);
0397         }
0398 
0399         // WHEN
0400         Akonadi::Serializer serializer;
0401 
0402         // THEN
0403         QFETCH(bool, expectedSelected);
0404         QCOMPARE(serializer.isSelectedCollection(collection), expectedSelected);
0405     }
0406 
0407     void shouldVerifyCollectionContents_data()
0408     {
0409         QTest::addColumn<QString>("mimeType");
0410         QTest::addColumn<bool>("expectedTasks");
0411 
0412         QTest::newRow("task collection") << "application/x-vnd.akonadi.calendar.todo" << true;
0413         QTest::newRow("note collection") << "text/x-vnd.akonadi.note" << false;
0414     }
0415 
0416     void shouldVerifyCollectionContents()
0417     {
0418         // GIVEN
0419 
0420         // Data...
0421         QFETCH(QString, mimeType);
0422 
0423         // ... stored in a collection
0424         Akonadi::Collection collection(42);
0425         collection.setContentMimeTypes(QStringList() << mimeType);
0426 
0427         // WHEN
0428         Akonadi::Serializer serializer;
0429         QFETCH(bool, expectedTasks);
0430 
0431         // THEN
0432         QCOMPARE(serializer.isTaskCollection(collection), expectedTasks);
0433     }
0434 
0435     void shouldCreateTaskFromItem_data()
0436     {
0437         QTest::addColumn<QString>("summary");
0438         QTest::addColumn<QString>("content");
0439         QTest::addColumn<bool>("isDone");
0440         QTest::addColumn<QDate>("doneDate");
0441         QTest::addColumn<QDate>("startDate");
0442         QTest::addColumn<QDate>("dueDate");
0443         QTest::addColumn<QStringList>("contexts");
0444 
0445         QTest::newRow("nominal case") << "summary" << "content" << false << QDate() << QDate(2013, 11, 24) << QDate(2014, 03, 01) << QStringList();
0446         QTest::newRow("done case") << "summary" << "content" << true << QDate(2013, 11, 30) << QDate(2013, 11, 24) << QDate(2014, 03, 01) << QStringList();
0447         QTest::newRow("done without doneDate case") << "summary" << "content" << true << QDate() << QDate(2013, 11, 24) << QDate(2014, 03, 01) << QStringList();
0448         QTest::newRow("empty case") << QString() << QString() << false << QDate() << QDate() << QDate() << QStringList();
0449         QTest::newRow("one_context") << "summary" << "content" << false << QDate() << QDate(2013, 11, 24) << QDate(2014, 03, 01) << QStringList({"mycontext"});
0450         QTest::newRow("two_contexts") << "summary" << "content" << false << QDate() << QDate(2013, 11, 24) << QDate(2014, 03, 01) << QStringList({"c1", "c2"});
0451     }
0452 
0453     void shouldCreateTaskFromItem()
0454     {
0455         // GIVEN
0456 
0457         // Data...
0458         QFETCH(QString, summary);
0459         QFETCH(QString, content);
0460         QFETCH(bool, isDone);
0461         QFETCH(QDate, doneDate);
0462         QFETCH(QDate, startDate);
0463         QFETCH(QDate, dueDate);
0464         QFETCH(QStringList, contexts);
0465 
0466         // ... stored in a todo...
0467         KCalendarCore::Todo::Ptr todo(new KCalendarCore::Todo);
0468         todo->setSummary(summary);
0469         todo->setDescription(content);
0470 
0471         if (isDone)
0472             todo->setCompleted(doneDate.startOfDay());
0473         else
0474             todo->setCompleted(isDone);
0475 
0476         setTodoDates(todo, startDate, dueDate);
0477         todo->setRelatedTo(QStringLiteral("my-uid"));
0478 
0479         if (!contexts.isEmpty())
0480             todo->setCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyContextList(), contexts.join(','));
0481 
0482         // ... as payload of an item
0483         Akonadi::Item item;
0484         item.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
0485         item.setPayload<KCalendarCore::Todo::Ptr>(todo);
0486 
0487         // which has a parent collection
0488         Akonadi::Collection collection(43);
0489         item.setParentCollection(collection);
0490 
0491         // WHEN
0492         Akonadi::Serializer serializer;
0493         auto task = serializer.createTaskFromItem(item);
0494 
0495         // THEN
0496         QVERIFY(!task.isNull());
0497         QCOMPARE(task->title(), summary);
0498         QCOMPARE(task->text(), content);
0499         QCOMPARE(task->isDone(), isDone);
0500         QCOMPARE(task->doneDate(), doneDate);
0501         QCOMPARE(task->startDate(), startDate);
0502         QCOMPARE(task->dueDate(), dueDate);
0503         QCOMPARE(task->property("todoUid").toString(), todo->uid());
0504         QCOMPARE(task->property("relatedUid").toString(), todo->relatedTo());
0505         QCOMPARE(task->property("contextUids").toStringList(), contexts);
0506         QCOMPARE(task->property("itemId").toLongLong(), item.id());
0507         QCOMPARE(task->property("parentCollectionId").toLongLong(), collection.id());
0508     }
0509 
0510     void shouldCreateNullTaskFromInvalidItem()
0511     {
0512         // GIVEN
0513         Akonadi::Item item;
0514 
0515         // WHEN
0516         Akonadi::Serializer serializer;
0517         auto task = serializer.createTaskFromItem(item);
0518 
0519         // THEN
0520         QVERIFY(task.isNull());
0521     }
0522 
0523     void shouldCreateNullTaskFromProjectItem()
0524     {
0525         // GIVEN
0526 
0527         // A todo with the project flag
0528         KCalendarCore::Todo::Ptr todo(new KCalendarCore::Todo);
0529         todo->setSummary(QStringLiteral("foo"));
0530         todo->setCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyIsProject(), QStringLiteral("1"));
0531 
0532         // ... as payload of an item
0533         Akonadi::Item item;
0534         item.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
0535         item.setPayload<KCalendarCore::Todo::Ptr>(todo);
0536 
0537         // WHEN
0538         Akonadi::Serializer serializer;
0539         auto task = serializer.createTaskFromItem(item);
0540 
0541         // THEN
0542         QVERIFY(task.isNull());
0543     }
0544 
0545     void shouldUpdateTaskFromItem_data()
0546     {
0547         QTest::addColumn<QString>("updatedSummary");
0548         QTest::addColumn<QString>("updatedContent");
0549         QTest::addColumn<bool>("updatedDone");
0550         QTest::addColumn<QDate>("updatedDoneDate");
0551         QTest::addColumn<QDate>("updatedStartDate");
0552         QTest::addColumn<QDate>("updatedDueDate");
0553         QTest::addColumn<QString>("updatedRelated");
0554         QTest::addColumn<QString>("updatedContexts");
0555         QTest::addColumn<bool>("updatedRecurs");
0556         QTest::addColumn<QByteArrayList>("updatedAttachmentData");
0557         QTest::addColumn<QStringList>("updatedAttachmentUris");
0558         QTest::addColumn<QStringList>("updatedAttachmentLabels");
0559         QTest::addColumn<QStringList>("updatedAttachmentMimeTypes");
0560         QTest::addColumn<QStringList>("updatedAttachmentIconNames");
0561         QTest::addColumn<bool>("updatedRunning");
0562 
0563         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;
0564         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;
0565         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;
0566     }
0567 
0568     void shouldUpdateTaskFromItem()
0569     {
0570         // GIVEN
0571 
0572         // A todo...
0573         KCalendarCore::Todo::Ptr originalTodo(new KCalendarCore::Todo);
0574         originalTodo->setSummary(QStringLiteral("summary"));
0575         originalTodo->setDescription(QStringLiteral("content"));
0576         originalTodo->setCompleted(false);
0577         setTodoDates(originalTodo, QDate(2013, 11, 24), QDate(2014, 03, 01));
0578 
0579         originalTodo->setRelatedTo(QStringLiteral("my-uid"));
0580         originalTodo->setCustomProperty(Serializer::customPropertyAppName(),
0581                                         Serializer::customPropertyContextList(),
0582                                         QStringLiteral("context1,context2"));
0583         KCalendarCore::Attendee originalAttendee(QStringLiteral("John Doe"),
0584                                             QStringLiteral("j@d.com"),
0585                                             true,
0586                                             KCalendarCore::Attendee::Accepted);
0587         originalTodo->addAttendee(originalAttendee);
0588 
0589         // ... as payload of an item...
0590         Akonadi::Item originalItem;
0591         originalItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
0592         originalItem.setPayload<KCalendarCore::Todo::Ptr>(originalTodo);
0593 
0594         // ... which has a parent collection...
0595         Akonadi::Collection originalCollection(43);
0596         originalItem.setParentCollection(originalCollection);
0597 
0598         // ... deserialized as a task
0599         Akonadi::Serializer serializer;
0600         auto task = serializer.createTaskFromItem(originalItem);
0601 
0602         // WHEN
0603 
0604         // Data...
0605         QFETCH(QString, updatedSummary);
0606         QFETCH(QString, updatedContent);
0607         QFETCH(bool, updatedDone);
0608         QFETCH(QDate, updatedDoneDate);
0609         QFETCH(QDate, updatedStartDate);
0610         QFETCH(QDate, updatedDueDate);
0611         QFETCH(QString, updatedRelated);
0612         QFETCH(QString, updatedContexts);
0613         QFETCH(bool, updatedRecurs);
0614         QFETCH(QByteArrayList, updatedAttachmentData);
0615         QFETCH(QStringList, updatedAttachmentUris);
0616         QFETCH(QStringList, updatedAttachmentLabels);
0617         QFETCH(QStringList, updatedAttachmentMimeTypes);
0618         QFETCH(QStringList, updatedAttachmentIconNames);
0619         QFETCH(bool, updatedRunning);
0620 
0621         // ... in a new todo...
0622         KCalendarCore::Todo::Ptr updatedTodo(new KCalendarCore::Todo);
0623         updatedTodo->setSummary(updatedSummary);
0624         updatedTodo->setDescription(updatedContent);
0625 
0626         if (updatedDone)
0627             updatedTodo->setCompleted(updatedDoneDate.startOfDay());
0628         else
0629             updatedTodo->setCompleted(updatedDone);
0630 
0631         setTodoDates(updatedTodo, updatedStartDate, updatedDueDate);
0632         updatedTodo->setRelatedTo(updatedRelated);
0633         updatedTodo->setCustomProperty(Serializer::customPropertyAppName(),
0634                                        Serializer::customPropertyContextList(),
0635                                        updatedContexts);
0636 
0637         if (updatedRecurs)
0638             updatedTodo->recurrence()->setDaily(1);
0639 
0640         for (int i = 0; i < updatedAttachmentData.size(); i++) {
0641             KCalendarCore::Attachment attachment(QByteArray{});
0642             if (!updatedAttachmentData.at(i).isEmpty())
0643                 attachment.setDecodedData(updatedAttachmentData.at(i));
0644             else
0645                 attachment.setUri(updatedAttachmentUris.at(i));
0646             attachment.setMimeType(updatedAttachmentMimeTypes.at(i));
0647             attachment.setLabel(updatedAttachmentLabels.at(i));
0648             updatedTodo->addAttachment(attachment);
0649         }
0650 
0651         if (updatedRunning) {
0652             updatedTodo->setCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyIsRunning(), "1");
0653         } else {
0654             updatedTodo->removeCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyIsRunning());
0655         }
0656 
0657         // ... as payload of a new item
0658         Akonadi::Item updatedItem;
0659         updatedItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
0660         updatedItem.setPayload<KCalendarCore::Todo::Ptr>(updatedTodo);
0661 
0662         // ... which has a new parent collection
0663         Akonadi::Collection updatedCollection(45);
0664         updatedItem.setParentCollection(updatedCollection);
0665 
0666         serializer.updateTaskFromItem(task, updatedItem);
0667 
0668         // THEN
0669         QCOMPARE(task->title(), updatedSummary);
0670         QCOMPARE(task->text(), updatedContent);
0671         QCOMPARE(task->isDone(), updatedDone);
0672         QCOMPARE(task->doneDate(), updatedDoneDate);
0673         QCOMPARE(task->startDate(), updatedStartDate);
0674         QCOMPARE(task->dueDate(), updatedDueDate);
0675         QCOMPARE(task->property("todoUid").toString(), updatedTodo->uid());
0676         QCOMPARE(task->property("relatedUid").toString(), updatedTodo->relatedTo());
0677         QCOMPARE(task->property("contextUids").toStringList(), updatedContexts.split(','));
0678         QCOMPARE(task->property("itemId").toLongLong(), updatedItem.id());
0679         QCOMPARE(task->property("parentCollectionId").toLongLong(), updatedCollection.id());
0680         QCOMPARE(task->recurrence(), (updatedRecurs ? Domain::Task::RecursDaily : Domain::Task::NoRecurrence));
0681         QCOMPARE(task->attachments().size(), updatedAttachmentData.size());
0682         for (int i = 0; i < task->attachments().size(); i++) {
0683             const auto attachment = task->attachments().at(i);
0684             QCOMPARE(attachment.data(), updatedAttachmentData.at(i));
0685             QCOMPARE(attachment.uri(), QUrl(updatedAttachmentUris.at(i)));
0686             QCOMPARE(attachment.label(), updatedAttachmentLabels.at(i));
0687             QCOMPARE(attachment.mimeType(), updatedAttachmentMimeTypes.at(i));
0688             QCOMPARE(attachment.iconName(), updatedAttachmentIconNames.at(i));
0689         }
0690         QCOMPARE(task->isRunning(), updatedRunning);
0691     }
0692 
0693     void shouldUpdateTaskRecurrenceFromItem_data()
0694     {
0695         QTest::addColumn<int>("todoRecurrence");
0696         QTest::addColumn<Domain::Task::Recurrence>("expectedRecurrence");
0697 
0698         QTest::newRow("none") << int(KCalendarCore::Recurrence::rNone) << Domain::Task::NoRecurrence;
0699         QTest::newRow("minutely") << int(KCalendarCore::Recurrence::rMinutely) << Domain::Task::NoRecurrence;
0700         QTest::newRow("hourly") << int(KCalendarCore::Recurrence::rHourly) << Domain::Task::NoRecurrence;
0701         QTest::newRow("daily") << int(KCalendarCore::Recurrence::rDaily) << Domain::Task::RecursDaily;
0702         QTest::newRow("weekly") << int(KCalendarCore::Recurrence::rWeekly) << Domain::Task::RecursWeekly;
0703         QTest::newRow("monthly") << int(KCalendarCore::Recurrence::rMonthlyDay) << Domain::Task::RecursMonthly;
0704         QTest::newRow("yearly") << int(KCalendarCore::Recurrence::rYearlyMonth) << Domain::Task::RecursYearly;
0705     }
0706 
0707     void shouldUpdateTaskRecurrenceFromItem()
0708     {
0709         // GIVEN
0710 
0711         // A todo...
0712         KCalendarCore::Todo::Ptr todo(new KCalendarCore::Todo);
0713         todo->setSummary(QStringLiteral("summary"));
0714 
0715         QFETCH(int, todoRecurrence);
0716         switch (todoRecurrence) {
0717         case KCalendarCore::Recurrence::rNone:
0718             break;
0719         case KCalendarCore::Recurrence::rMinutely:
0720             todo->recurrence()->setMinutely(1);
0721             break;
0722         case KCalendarCore::Recurrence::rHourly:
0723             todo->recurrence()->setHourly(1);
0724             break;
0725         case KCalendarCore::Recurrence::rDaily:
0726             todo->recurrence()->setDaily(1);
0727             break;
0728         case KCalendarCore::Recurrence::rWeekly:
0729             todo->recurrence()->setWeekly(1);
0730             break;
0731         case KCalendarCore::Recurrence::rMonthlyDay:
0732             todo->recurrence()->setMonthly(1);
0733             break;
0734         case KCalendarCore::Recurrence::rYearlyMonth:
0735             todo->recurrence()->setYearly(1);
0736             break;
0737         default:
0738             qFatal("Shouldn't happen");
0739         }
0740 
0741         // ... as payload of an item...
0742         Akonadi::Item item;
0743         item.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
0744         item.setPayload<KCalendarCore::Todo::Ptr>(todo);
0745 
0746         // ... which has a parent collection...
0747         Akonadi::Collection collection(43);
0748         item.setParentCollection(collection);
0749 
0750         // WHEN
0751         // ... deserialized as a task
0752         Akonadi::Serializer serializer;
0753         auto task = serializer.createTaskFromItem(item);
0754 
0755         // THEN
0756         QFETCH(Domain::Task::Recurrence, expectedRecurrence);
0757         QCOMPARE(task->recurrence(), expectedRecurrence);
0758     }
0759 
0760     void shouldNotBreakRecurrenceDuringSerialization()
0761     {
0762         // GIVEN
0763 
0764         // Data...
0765         const QDate today(QDate::currentDate());
0766         const QDate doneDate(2013, 11, 20);
0767         const QDate startDate(2013, 11, 10);
0768 
0769         // ... stored in a todo...
0770         KCalendarCore::Todo::Ptr todo(new KCalendarCore::Todo);
0771         todo->setSummary(QStringLiteral("summary"));
0772         todo->setDtStart(startDate.startOfDay());
0773         todo->recurrence()->setMonthly(1);
0774 
0775         // ... as payload of an item...
0776         Akonadi::Item item;
0777         item.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
0778         item.setPayload<KCalendarCore::Todo::Ptr>(todo);
0779 
0780         // ... deserialized as a task
0781         Akonadi::Serializer serializer;
0782         auto task = serializer.createTaskFromItem(item);
0783 
0784         // WHEN
0785         // Task is marked done...
0786         task->setDoneDate(doneDate);
0787         task->setDone(true);
0788 
0789         // and goes through serialization and back
0790         const auto newItem = serializer.createItemFromTask(task);
0791         serializer.updateTaskFromItem(task, newItem);
0792 
0793         // THEN
0794         QCOMPARE(task->recurrence(), Domain::Task::RecursMonthly);
0795         QVERIFY(!task->isDone());
0796         const QDate lastOccurrence(QDate(today.year(), today.month(), 10));
0797         if (today.day() > 10)
0798             QCOMPARE(task->startDate(), lastOccurrence.addMonths(1));
0799         else
0800             QCOMPARE(task->startDate(), lastOccurrence);
0801     }
0802 
0803     void shouldNotUpdateTaskFromInvalidItem()
0804     {
0805         // GIVEN
0806 
0807         // Data...
0808         const QString summary = QStringLiteral("summary");
0809         const QString content = QStringLiteral("content");
0810         const bool isDone = true;
0811         const QDate doneDate(2013, 11, 30);
0812         const QDate startDate(2013, 11, 24);
0813         const QDate dueDate(2014, 03, 01);
0814 
0815         // ... stored in a todo...
0816         KCalendarCore::Todo::Ptr originalTodo(new KCalendarCore::Todo);
0817         originalTodo->setSummary(summary);
0818         originalTodo->setDescription(content);
0819 
0820         if (originalTodo)
0821             originalTodo->setCompleted(doneDate.startOfDay());
0822         else
0823             originalTodo->setCompleted(isDone);
0824         setTodoDates(originalTodo, startDate, dueDate);
0825 
0826         // ... as payload of an item...
0827         Akonadi::Item originalItem;
0828         originalItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
0829         originalItem.setPayload<KCalendarCore::Todo::Ptr>(originalTodo);
0830 
0831         // ... deserialized as a task
0832         Akonadi::Serializer serializer;
0833         auto task = serializer.createTaskFromItem(originalItem);
0834 
0835         // WHEN
0836         Akonadi::Item invalidItem;
0837         serializer.updateTaskFromItem(task, invalidItem);
0838 
0839         // THEN
0840         QCOMPARE(task->title(), summary);
0841         QCOMPARE(task->text(), content);
0842         QCOMPARE(task->isDone(), isDone);
0843         QCOMPARE(task->doneDate(), doneDate);
0844         QCOMPARE(task->startDate(), startDate);
0845         QCOMPARE(task->dueDate(), dueDate);
0846         QCOMPARE(task->property("itemId").toLongLong(), originalItem.id());
0847     }
0848 
0849     void shouldNotUpdateTaskFromProjectItem()
0850     {
0851         // GIVEN
0852 
0853         // Data...
0854         const QString summary = QStringLiteral("summary");
0855         const QString content = QStringLiteral("content");
0856         const bool isDone = true;
0857         const QDate doneDate(2013, 11, 30);
0858         const QDate startDate(2013, 11, 24);
0859         const QDate dueDate(2014, 03, 01);
0860 
0861         // ... stored in a todo...
0862         KCalendarCore::Todo::Ptr originalTodo(new KCalendarCore::Todo);
0863         originalTodo->setSummary(summary);
0864         originalTodo->setDescription(content);
0865 
0866         if (originalTodo)
0867             originalTodo->setCompleted(doneDate.startOfDay());
0868         else
0869             originalTodo->setCompleted(isDone);
0870         setTodoDates(originalTodo, startDate, dueDate);
0871 
0872         // ... as payload of an item...
0873         Akonadi::Item originalItem;
0874         originalItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
0875         originalItem.setPayload<KCalendarCore::Todo::Ptr>(originalTodo);
0876 
0877         // ... deserialized as a task
0878         Akonadi::Serializer serializer;
0879         auto task = serializer.createTaskFromItem(originalItem);
0880 
0881         // WHEN
0882         // A todo with the project flag
0883         KCalendarCore::Todo::Ptr projectTodo(new KCalendarCore::Todo);
0884         projectTodo->setSummary(QStringLiteral("foo"));
0885         projectTodo->setCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyIsProject(), QStringLiteral("1"));
0886 
0887         // ... as payload of an item
0888         Akonadi::Item projectItem;
0889         projectItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
0890         projectItem.setPayload<KCalendarCore::Todo::Ptr>(projectTodo);
0891         serializer.updateTaskFromItem(task, projectItem);
0892 
0893         // THEN
0894         QCOMPARE(task->title(), summary);
0895         QCOMPARE(task->text(), content);
0896         QCOMPARE(task->isDone(), isDone);
0897         QCOMPARE(task->doneDate(), doneDate);
0898         QCOMPARE(task->startDate(), startDate);
0899         QCOMPARE(task->dueDate(), dueDate);
0900         QCOMPARE(task->property("itemId").toLongLong(), originalItem.id());
0901     }
0902 
0903     void shouldCreateItemFromTask_data()
0904     {
0905         QTest::addColumn<QString>("summary");
0906         QTest::addColumn<QString>("content");
0907         QTest::addColumn<bool>("isDone");
0908         QTest::addColumn<QDate>("doneDate");
0909         QTest::addColumn<QDate>("startDate");
0910         QTest::addColumn<QDate>("dueDate");
0911         QTest::addColumn<qint64>("itemId");
0912         QTest::addColumn<qint64>("parentCollectionId");
0913         QTest::addColumn<QString>("todoUid");
0914         QTest::addColumn<Domain::Task::Recurrence>("recurrence");
0915         QTest::addColumn<Domain::Task::Attachments>("attachments");
0916         QTest::addColumn<bool>("running");
0917         QTest::addColumn<QStringList>("contexts");
0918 
0919         Domain::Task::Attachments attachments;
0920 
0921         Domain::Task::Attachment dataAttachment;
0922         dataAttachment.setData("foo");
0923         dataAttachment.setLabel("dataAttachment");
0924         dataAttachment.setMimeType("text/plain");
0925         dataAttachment.setIconName("text-plain");
0926         attachments.append(dataAttachment);
0927 
0928         Domain::Task::Attachment uriAttachment;
0929         uriAttachment.setUri(QUrl("https://www.kde.org"));
0930         uriAttachment.setLabel("uriAttachment");
0931         uriAttachment.setMimeType("text/html");
0932         uriAttachment.setIconName("text-html");
0933         attachments.append(uriAttachment);
0934 
0935         QTest::newRow("nominal case (no id)") << "summary" << "content" << false << QDate()
0936                                               << QDate(2013, 11, 24) << QDate(2014, 03, 01)
0937                                               << qint64(-1) << qint64(-1) << QString()
0938                                               << Domain::Task::NoRecurrence
0939                                               << attachments
0940                                               << false
0941                                               << QStringList();
0942         QTest::newRow("nominal case (daily)") << "summary" << "content" << false << QDate()
0943                                               << QDate(2013, 11, 24) << QDate(2014, 03, 01)
0944                                               << qint64(-1) << qint64(-1) << QString()
0945                                               << Domain::Task::RecursDaily
0946                                               << Domain::Task::Attachments()
0947                                               << false
0948                                               << QStringList();
0949         QTest::newRow("nominal case (weekly)") << "summary" << "content" << false << QDate()
0950                                                << QDate(2013, 11, 24) << QDate(2014, 03, 01)
0951                                                << qint64(-1) << qint64(-1) << QString()
0952                                                << Domain::Task::RecursWeekly
0953                                                << Domain::Task::Attachments()
0954                                                << false
0955                                                << QStringList();
0956         QTest::newRow("nominal case (monthly)") << "summary" << "content" << false << QDate()
0957                                                 << QDate(2013, 11, 24) << QDate(2014, 03, 01)
0958                                                 << qint64(-1) << qint64(-1) << QString()
0959                                                 << Domain::Task::RecursMonthly
0960                                                 << Domain::Task::Attachments()
0961                                                 << false
0962                                                 << QStringList();
0963         QTest::newRow("nominal case (yearly)") << "summary" << "content" << false << QDate()
0964                                                 << QDate(2013, 11, 24) << QDate(2014, 03, 01)
0965                                                 << qint64(-1) << qint64(-1) << QString()
0966                                                 << Domain::Task::RecursYearly
0967                                                 << Domain::Task::Attachments()
0968                                                 << false
0969                                                 << QStringList();
0970         QTest::newRow("done case (no id)") << "summary" << "content" << true << QDate(2013, 11, 30)
0971                                            << QDate(2013, 11, 24) << QDate(2014, 03, 01)
0972                                            << qint64(-1) << qint64(-1) << QString()
0973                                            << Domain::Task::NoRecurrence
0974                                            << Domain::Task::Attachments()
0975                                            << false
0976                                            << QStringList();
0977         QTest::newRow("empty case (no id)") << QString() << QString() << false << QDate()
0978                                             << QDate() << QDate()
0979                                             << qint64(-1) << qint64(-1) << QString()
0980                                             << Domain::Task::NoRecurrence
0981                                             << Domain::Task::Attachments()
0982                                             << false
0983                                             << QStringList();
0984 #if 0 // if we ever need time info, then we need a Task::setAllDay(bool) just like KCalendarCore::Todo has.
0985       QTest::newRow("nominal_with_time_info_noid") << "summary" << "content" << true << QDateTime(QDate(2015, 3, 1), QTime(1, 2, 3), Qt::UTC)
0986                                               << QDateTime(QDate(2013, 11, 24), QTime(0, 1, 2), Qt::UTC) << QDateTime(QDate(2016, 3, 1), QTime(4, 5, 6), Qt::UTC)
0987                                               << qint64(-1) << qint64(-1) << QString()
0988                                               << Domain::Task::NoRecurrence
0989                                               << Domain::Task::Attachments()
0990                                               << false
0991                                               << QStringList();
0992 #endif
0993 
0994         QTest::newRow("nominal case (with id)") << "summary" << "content" << false << QDate()
0995                                                 << QDate(2013, 11, 24) << QDate(2014, 03, 01)
0996                                                 << qint64(42) << qint64(43) << "my-uid"
0997                                                 << Domain::Task::NoRecurrence
0998                                                 << Domain::Task::Attachments()
0999                                                 << false
1000                                                 << QStringList();
1001         QTest::newRow("done case (with id)") << "summary" << "content" << true << QDate(2013, 11, 30)
1002                                              << QDate(2013, 11, 24) << QDate(2014, 03, 01)
1003                                              << qint64(42) << qint64(43) << "my-uid"
1004                                              << Domain::Task::NoRecurrence
1005                                              << Domain::Task::Attachments()
1006                                              << false
1007                                              << QStringList();
1008         QTest::newRow("empty case (with id)") << QString() << QString() << false << QDate()
1009                                               << QDate() << QDate()
1010                                               << qint64(42) << qint64(43) << "my-uid"
1011                                               << Domain::Task::NoRecurrence
1012                                               << Domain::Task::Attachments()
1013                                               << false
1014                                               << QStringList();
1015         QTest::newRow("nominal case (running)") << "running" << QString() << false << QDate()
1016                                               << QDate(2013, 11, 24) << QDate(2014, 03, 01)
1017                                               << qint64(-1) << qint64(-1) << QString()
1018                                               << Domain::Task::NoRecurrence
1019                                               << Domain::Task::Attachments()
1020                                               << true
1021                                               << QStringList();
1022         QTest::newRow("with_context") << "with_context" << QString() << false << QDate()
1023                                               << QDate(2013, 11, 24) << QDate(2014, 03, 01)
1024                                               << qint64(-1) << qint64(-1) << QString()
1025                                               << Domain::Task::NoRecurrence
1026                                               << Domain::Task::Attachments()
1027                                               << true
1028                                               << QStringList({"c1", "c2"});
1029     }
1030 
1031     void shouldCreateItemFromTask()
1032     {
1033         // GIVEN
1034 
1035         // Data...
1036         QFETCH(QString, summary);
1037         QFETCH(QString, content);
1038         QFETCH(bool, isDone);
1039         QFETCH(QDate, doneDate);
1040         QFETCH(QDate, startDate);
1041         QFETCH(QDate, dueDate);
1042         QFETCH(qint64, itemId);
1043         QFETCH(qint64, parentCollectionId);
1044         QFETCH(QString, todoUid);
1045         QFETCH(Domain::Task::Recurrence, recurrence);
1046         QFETCH(Domain::Task::Attachments, attachments);
1047         QFETCH(bool, running);
1048         QFETCH(QStringList, contexts);
1049 
1050         // ... stored in a task
1051         auto task = Domain::Task::Ptr::create();
1052         task->setTitle(summary);
1053         task->setText(content);
1054         task->setDone(isDone);
1055         task->setDoneDate(doneDate);
1056         task->setStartDate(startDate);
1057         task->setDueDate(dueDate);
1058         task->setRecurrence(recurrence);
1059         task->setAttachments(attachments);
1060         task->setRunning(running);
1061 
1062         if (itemId > 0)
1063             task->setProperty("itemId", itemId);
1064 
1065         if (parentCollectionId > 0)
1066             task->setProperty("parentCollectionId", parentCollectionId);
1067 
1068         if (!todoUid.isEmpty())
1069             task->setProperty("todoUid", todoUid);
1070 
1071         task->setProperty("relatedUid", "parent-uid");
1072         task->setProperty("contextUids", contexts);
1073 
1074         // WHEN
1075         Akonadi::Serializer serializer;
1076         auto item = serializer.createItemFromTask(task);
1077 
1078         // THEN
1079         QCOMPARE(item.mimeType(), KCalendarCore::Todo::todoMimeType());
1080 
1081         QCOMPARE(item.isValid(), itemId > 0);
1082         if (itemId > 0) {
1083             QCOMPARE(item.id(), itemId);
1084         }
1085 
1086         QCOMPARE(item.parentCollection().isValid(), parentCollectionId > 0);
1087         if (parentCollectionId > 0) {
1088             QCOMPARE(item.parentCollection().id(), parentCollectionId);
1089         }
1090 
1091         auto todo = item.payload<KCalendarCore::Todo::Ptr>();
1092         QCOMPARE(todo->summary(), summary);
1093         QCOMPARE(todo->description(), content);
1094         QCOMPARE(todo->isCompleted(), isDone);
1095         QCOMPARE(todo->completed().toLocalTime().date(), doneDate);
1096         QCOMPARE(todo->dtStart().toLocalTime().date(), startDate);
1097         QCOMPARE(todo->dtDue().toLocalTime().date(), dueDate);
1098         if (todo->dtStart().isValid()) {
1099             QCOMPARE(int(todo->dtStart().timeSpec()), int(Qt::LocalTime));
1100         }
1101         QVERIFY(todo->allDay()); // this is always true currently...
1102         const ushort expectedRecurrence = recurrence == Domain::Task::NoRecurrence ? KCalendarCore::Recurrence::rNone
1103                                         : recurrence == Domain::Task::RecursDaily ? KCalendarCore::Recurrence::rDaily
1104                                         : recurrence == Domain::Task::RecursWeekly ? KCalendarCore::Recurrence::rWeekly
1105                                         : recurrence == Domain::Task::RecursMonthly ? KCalendarCore::Recurrence::rMonthlyDay
1106                                         : recurrence == Domain::Task::RecursYearly ? KCalendarCore::Recurrence::rYearlyMonth
1107                                         : KCalendarCore::Recurrence::rNone; // Shouldn't happen though
1108         QCOMPARE(todo->recurrence()->recurrenceType(), expectedRecurrence);
1109         if (recurrence != Domain::Task::NoRecurrence)
1110             QCOMPARE(todo->recurrence()->frequency(), 1);
1111 
1112         QCOMPARE(todo->attachments().size(), attachments.size());
1113         for (int i = 0; i < attachments.size(); i++) {
1114             auto attachment = todo->attachments().at(i);
1115             QCOMPARE(attachment.isUri(), attachments.at(i).isUri());
1116             QCOMPARE(QUrl(attachment.uri()), attachments.at(i).uri());
1117             QCOMPARE(attachment.decodedData(), attachments.at(i).data());
1118             QCOMPARE(attachment.label(), attachments.at(i).label());
1119             QCOMPARE(attachment.mimeType(), attachments.at(i).mimeType());
1120         }
1121 
1122         if (!todoUid.isEmpty()) {
1123             QCOMPARE(todo->uid(), todoUid);
1124         }
1125 
1126         QCOMPARE(todo->relatedTo(), QStringLiteral("parent-uid"));
1127         QCOMPARE(todo->customProperty(Serializer::customPropertyAppName(), Serializer::customPropertyIsRunning()), running ? QStringLiteral("1") : QString());
1128         QCOMPARE(todo->customProperty(Serializer::customPropertyAppName(), Serializer::customPropertyContextList()), contexts.join(','));
1129     }
1130 
1131     void shouldVerifyIfAnItemIsATaskChild_data()
1132     {
1133         QTest::addColumn<Domain::Task::Ptr>("task");
1134         QTest::addColumn<Akonadi::Item>("item");
1135         QTest::addColumn<bool>("isParent");
1136 
1137         // Create task
1138         const QString summary = QStringLiteral("summary");
1139         const QString content = QStringLiteral("content");
1140         const bool isDone = true;
1141         const QDate doneDate(QDate(2013, 11, 30));
1142         const QDate startDate(QDate(2013, 11, 24));
1143         const QDate dueDate(QDate(2014, 03, 01));
1144 
1145         // ... create a task
1146         Domain::Task::Ptr task(new Domain::Task);
1147         task->setTitle(summary);
1148         task->setText(content);
1149         task->setDone(isDone);
1150         task->setDoneDate(doneDate);
1151         task->setStartDate(startDate);
1152         task->setDueDate(dueDate);
1153         task->setProperty("todoUid", "1");
1154 
1155         // Create Child item
1156         KCalendarCore::Todo::Ptr childTodo(new KCalendarCore::Todo);
1157         childTodo->setSummary(summary);
1158         childTodo->setDescription(content);
1159 
1160         if (isDone)
1161             childTodo->setCompleted(doneDate.startOfDay());
1162         else
1163             childTodo->setCompleted(isDone);
1164 
1165         setTodoDates(childTodo, startDate, dueDate);
1166 
1167         Akonadi::Item childItem;
1168         childItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
1169         childItem.setPayload<KCalendarCore::Todo::Ptr>(childTodo);
1170 
1171         QTest::newRow("without parent") << task << childItem << false;
1172 
1173         // Create Child Item with parent
1174         KCalendarCore::Todo::Ptr childTodo2(new KCalendarCore::Todo);
1175         childTodo2->setSummary(summary);
1176         childTodo2->setDescription(content);
1177 
1178         if (isDone)
1179             childTodo2->setCompleted(doneDate.startOfDay());
1180         else
1181             childTodo2->setCompleted(isDone);
1182         setTodoDates(childTodo2, startDate, dueDate);
1183         childTodo2->setRelatedTo(QStringLiteral("1"));
1184 
1185         Akonadi::Item childItem2;
1186         childItem2.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
1187         childItem2.setPayload<KCalendarCore::Todo::Ptr>(childTodo2);
1188 
1189         QTest::newRow("with parent") << task << childItem2 << true;
1190 
1191         Domain::Task::Ptr invalidTask(new Domain::Task);
1192         QTest::newRow("with invalid task") << invalidTask << childItem << false;
1193 
1194         Akonadi::Item invalidItem;
1195         QTest::newRow("with invalid item") << task << invalidItem << false;
1196 
1197     }
1198 
1199     void shouldVerifyIfAnItemIsATaskChild()
1200     {
1201         // GIVEN
1202         QFETCH(Domain::Task::Ptr, task);
1203         QFETCH(Akonadi::Item, item);
1204         QFETCH(bool, isParent);
1205 
1206         // WHEN
1207         Akonadi::Serializer serializer;
1208         bool value = serializer.isTaskChild(task, item);
1209 
1210         // THEN
1211         QCOMPARE(value, isParent);
1212     }
1213 
1214     void shouldRetrieveRelatedUidFromItem_data()
1215     {
1216         QTest::addColumn<Akonadi::Item>("item");
1217         QTest::addColumn<QString>("expectedUid");
1218 
1219         Akonadi::Item item1;
1220         KCalendarCore::Todo::Ptr todo1(new KCalendarCore::Todo);
1221         item1.setPayload<KCalendarCore::Todo::Ptr>(todo1);
1222 
1223         Akonadi::Item item2;
1224         KCalendarCore::Todo::Ptr todo2(new KCalendarCore::Todo);
1225         todo2->setRelatedTo(QStringLiteral("1"));
1226         item2.setPayload<KCalendarCore::Todo::Ptr>(todo2);
1227 
1228         QTest::newRow("task without related") << item1 << QString();
1229         QTest::newRow("task with related") << item2 << "1";
1230     }
1231 
1232     void shouldRetrieveRelatedUidFromItem()
1233     {
1234         // GIVEN
1235         QFETCH(Akonadi::Item, item);
1236         QFETCH(QString, expectedUid);
1237 
1238         // WHEN
1239         Akonadi::Serializer serializer;
1240         QString uid = serializer.relatedUidFromItem(item);
1241 
1242         // THEN
1243         QCOMPARE(uid, expectedUid);
1244     }
1245 
1246     void shouldCreateNoteFromItem_data()
1247     {
1248         QTest::addColumn<QString>("title");
1249         QTest::addColumn<QString>("text");
1250         QTest::addColumn<QString>("relatedUid");
1251 
1252         QTest::newRow("nominal case (no related)") << "A note title" << "A note content.\nWith two lines." << QString();
1253         QTest::newRow("nominal case (with related)") << "A note title" << "A note content.\nWith two lines." << "parent-uid";
1254         QTest::newRow("trailing new lines") << "A note title" << "Empty lines at the end.\n\n\n" << QString();
1255         QTest::newRow("empty case") << QString() << QString() << QString();
1256     }
1257 
1258     void shouldCreateProjectFromItem_data()
1259     {
1260         QTest::addColumn<QString>("summary");
1261 
1262         QTest::newRow("nominal case") << "summary";
1263         QTest::newRow("empty case") << QString();
1264     }
1265 
1266     void shouldCreateProjectFromItem()
1267     {
1268         // GIVEN
1269 
1270         // Data...
1271         QFETCH(QString, summary);
1272 
1273         // ... stored in a todo...
1274         KCalendarCore::Todo::Ptr todo(new KCalendarCore::Todo);
1275         todo->setSummary(summary);
1276         todo->setCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyIsProject(), QStringLiteral("1"));
1277         QVERIFY(!todo->uid().isEmpty());
1278 
1279         // ... as payload of an item
1280         Akonadi::Item item(42);
1281         item.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
1282         item.setPayload<KCalendarCore::Todo::Ptr>(todo);
1283 
1284         // which has a parent collection
1285         Akonadi::Collection collection(43);
1286         item.setParentCollection(collection);
1287 
1288         // WHEN
1289         Akonadi::Serializer serializer;
1290         Domain::Project::Ptr project = serializer.createProjectFromItem(item);
1291 
1292         // THEN
1293         QCOMPARE(project->name(), summary);
1294         QCOMPARE(project->property("itemId").toLongLong(), item.id());
1295         QCOMPARE(project->property("parentCollectionId").toLongLong(), collection.id());
1296         QCOMPARE(project->property("todoUid").toString(), todo->uid());
1297     }
1298 
1299     void shouldCreateNullProjectFromInvalidItem()
1300     {
1301         // GIVEN
1302         Akonadi::Item item;
1303 
1304         // WHEN
1305         Akonadi::Serializer serializer;
1306         Domain::Project::Ptr project = serializer.createProjectFromItem(item);
1307 
1308         // THEN
1309         QVERIFY(project.isNull());
1310     }
1311 
1312     void shouldCreateNullProjectFromTaskItem()
1313     {
1314         // GIVEN
1315 
1316         // A todo without the project flag
1317         KCalendarCore::Todo::Ptr todo(new KCalendarCore::Todo);
1318         todo->setSummary(QStringLiteral("foo"));
1319 
1320         // ... as payload of an item
1321         Akonadi::Item item;
1322         item.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
1323         item.setPayload<KCalendarCore::Todo::Ptr>(todo);
1324 
1325         // WHEN
1326         Akonadi::Serializer serializer;
1327         Domain::Project::Ptr project = serializer.createProjectFromItem(item);
1328 
1329         // THEN
1330         QVERIFY(project.isNull());
1331     }
1332 
1333     void shouldUpdateProjectFromItem_data()
1334     {
1335         QTest::addColumn<QString>("updatedSummary");
1336 
1337         QTest::newRow("no change") << "summary";
1338         QTest::newRow("changed") << "new summary";
1339     }
1340 
1341     void shouldUpdateProjectFromItem()
1342     {
1343         // GIVEN
1344 
1345         // A todo...
1346         KCalendarCore::Todo::Ptr originalTodo(new KCalendarCore::Todo);
1347         originalTodo->setSummary(QStringLiteral("summary"));
1348         originalTodo->setCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyIsProject(), QStringLiteral("1"));
1349 
1350         // ... as payload of an item...
1351         Akonadi::Item originalItem(42);
1352         originalItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
1353         originalItem.setPayload<KCalendarCore::Todo::Ptr>(originalTodo);
1354 
1355         // ... which has a parent collection...
1356         Akonadi::Collection originalCollection(43);
1357         originalItem.setParentCollection(originalCollection);
1358 
1359         // ... deserialized as a project
1360         Akonadi::Serializer serializer;
1361         auto project = serializer.createProjectFromItem(originalItem);
1362 
1363         // WHEN
1364 
1365         // Data...
1366         QFETCH(QString, updatedSummary);
1367 
1368         // ... in a new todo...
1369         KCalendarCore::Todo::Ptr updatedTodo(new KCalendarCore::Todo);
1370         updatedTodo->setSummary(updatedSummary);
1371         updatedTodo->setCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyIsProject(), QStringLiteral("1"));
1372         QVERIFY(!updatedTodo->uid().isEmpty());
1373 
1374         // ... as payload of a new item
1375         Akonadi::Item updatedItem(44);
1376         updatedItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
1377         updatedItem.setPayload<KCalendarCore::Todo::Ptr>(updatedTodo);
1378 
1379         // ... which has a new parent collection
1380         Akonadi::Collection updatedCollection(45);
1381         updatedItem.setParentCollection(updatedCollection);
1382 
1383         serializer.updateProjectFromItem(project, updatedItem);
1384 
1385         // THEN
1386         QCOMPARE(project->name(), updatedSummary);
1387         QCOMPARE(project->property("itemId").toLongLong(), updatedItem.id());
1388         QCOMPARE(project->property("parentCollectionId").toLongLong(), updatedCollection.id());
1389         QCOMPARE(project->property("todoUid").toString(), updatedTodo->uid());
1390     }
1391 
1392     void shouldNotUpdateProjectFromInvalidItem()
1393     {
1394         // GIVEN
1395 
1396         // Data...
1397         const QString summary = QStringLiteral("summary");
1398 
1399         // ... stored in a todo...
1400         KCalendarCore::Todo::Ptr originalTodo(new KCalendarCore::Todo);
1401         originalTodo->setSummary(summary);
1402         originalTodo->setCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyIsProject(), QStringLiteral("1"));
1403 
1404         // ... as payload of an item...
1405         Akonadi::Item originalItem;
1406         originalItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
1407         originalItem.setPayload<KCalendarCore::Todo::Ptr>(originalTodo);
1408 
1409         // ... deserialized as a project
1410         Akonadi::Serializer serializer;
1411         auto project = serializer.createProjectFromItem(originalItem);
1412 
1413         // WHEN
1414         Akonadi::Item invalidItem;
1415         serializer.updateProjectFromItem(project, invalidItem);
1416 
1417         // THEN
1418         QCOMPARE(project->name(), summary);
1419     }
1420 
1421     void shouldNotUpdateProjectFromTaskItem()
1422     {
1423         // GIVEN
1424 
1425         // Data...
1426         const QString summary = QStringLiteral("summary");
1427 
1428         // ... stored in a todo...
1429         KCalendarCore::Todo::Ptr originalTodo(new KCalendarCore::Todo);
1430         originalTodo->setSummary(summary);
1431         originalTodo->setCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyIsProject(), QStringLiteral("1"));
1432 
1433         // ... as payload of an item...
1434         Akonadi::Item originalItem;
1435         originalItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
1436         originalItem.setPayload<KCalendarCore::Todo::Ptr>(originalTodo);
1437 
1438         // ... deserialized as a project
1439         Akonadi::Serializer serializer;
1440         auto project = serializer.createProjectFromItem(originalItem);
1441 
1442         // WHEN
1443         // A todo without the project flag
1444         KCalendarCore::Todo::Ptr projectTodo(new KCalendarCore::Todo);
1445         projectTodo->setSummary(QStringLiteral("foo"));
1446 
1447         // ... as payload of an item
1448         Akonadi::Item projectItem;
1449         projectItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
1450         projectItem.setPayload<KCalendarCore::Todo::Ptr>(projectTodo);
1451         serializer.updateProjectFromItem(project, projectItem);
1452 
1453         // THEN
1454         QCOMPARE(project->name(), summary);
1455     }
1456 
1457     void shouldCreateItemFromProject_data()
1458     {
1459         QTest::addColumn<QString>("summary");
1460         QTest::addColumn<qint64>("itemId");
1461         QTest::addColumn<qint64>("parentCollectionId");
1462 
1463         QTest::newRow("nominal case (no id)") << "summary" << qint64(-1) << qint64(-1);
1464         QTest::newRow("empty case (no id)") << QString() << qint64(-1) << qint64(-1);
1465 
1466         QTest::newRow("nominal case (with id)") << "summary" << qint64(42) << qint64(43);
1467         QTest::newRow("empty case (with id)") << QString() << qint64(42) << qint64(43);
1468     }
1469 
1470     void shouldCreateItemFromProject()
1471     {
1472         // GIVEN
1473 
1474         // Data...
1475         QFETCH(QString, summary);
1476         QFETCH(qint64, itemId);
1477         QFETCH(qint64, parentCollectionId);
1478         const QString todoUid = QStringLiteral("test-uid");
1479 
1480         // ... stored in a project
1481         auto project = Domain::Project::Ptr::create();
1482         project->setName(summary);
1483         project->setProperty("todoUid", todoUid);
1484 
1485         if (itemId > 0)
1486             project->setProperty("itemId", itemId);
1487 
1488         if (parentCollectionId > 0)
1489             project->setProperty("parentCollectionId", parentCollectionId);
1490 
1491         // WHEN
1492         Akonadi::Serializer serializer;
1493         auto item = serializer.createItemFromProject(project);
1494 
1495         // THEN
1496         QCOMPARE(item.mimeType(), KCalendarCore::Todo::todoMimeType());
1497 
1498         QCOMPARE(item.isValid(), itemId > 0);
1499         if (itemId > 0) {
1500             QCOMPARE(item.id(), itemId);
1501         }
1502 
1503         QCOMPARE(item.parentCollection().isValid(), parentCollectionId > 0);
1504         if (parentCollectionId > 0) {
1505             QCOMPARE(item.parentCollection().id(), parentCollectionId);
1506         }
1507 
1508         auto todo = item.payload<KCalendarCore::Todo::Ptr>();
1509         QCOMPARE(todo->summary(), summary);
1510         QCOMPARE(todo->uid(), todoUid);
1511         QVERIFY(!todo->customProperty(Serializer::customPropertyAppName(), Serializer::customPropertyIsProject()).isEmpty());
1512     }
1513 
1514     void shouldVerifyIfAnItemIsAProjectChild_data()
1515     {
1516         QTest::addColumn<Domain::Project::Ptr>("project");
1517         QTest::addColumn<Akonadi::Item>("item");
1518         QTest::addColumn<bool>("isParent");
1519 
1520         // Create project
1521         auto project = Domain::Project::Ptr::create();
1522         project->setName(QStringLiteral("project"));
1523         project->setProperty("todoUid", "1");
1524 
1525         // Create unrelated todo
1526         auto unrelatedTodo = KCalendarCore::Todo::Ptr::create();
1527         unrelatedTodo->setSummary(QStringLiteral("summary"));
1528         Akonadi::Item unrelatedTodoItem;
1529         unrelatedTodoItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
1530         unrelatedTodoItem.setPayload<KCalendarCore::Todo::Ptr>(unrelatedTodo);
1531 
1532         QTest::newRow("unrelated todo") << project << unrelatedTodoItem << false;
1533 
1534         // Create child todo
1535         auto childTodo = KCalendarCore::Todo::Ptr::create();
1536         childTodo->setSummary(QStringLiteral("summary"));
1537         childTodo->setRelatedTo(QStringLiteral("1"));
1538         Akonadi::Item childTodoItem;
1539         childTodoItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
1540         childTodoItem.setPayload<KCalendarCore::Todo::Ptr>(childTodo);
1541 
1542         QTest::newRow("child todo") << project << childTodoItem << true;
1543 
1544         auto invalidProject = Domain::Project::Ptr::create();
1545         QTest::newRow("invalid project") << invalidProject << unrelatedTodoItem << false;
1546 
1547         Akonadi::Item invalidItem;
1548         QTest::newRow("invalid item") << project << invalidItem << false;
1549 
1550     }
1551 
1552     void shouldVerifyIfAnItemIsAProjectChild()
1553     {
1554         // GIVEN
1555         QFETCH(Domain::Project::Ptr, project);
1556         QFETCH(Akonadi::Item, item);
1557         QFETCH(bool, isParent);
1558 
1559         // WHEN
1560         Akonadi::Serializer serializer;
1561         bool value = serializer.isProjectChild(project, item);
1562 
1563         // THEN
1564         QCOMPARE(value, isParent);
1565     }
1566 
1567     void shouldUpdateItemParent_data()
1568     {
1569         QTest::addColumn<Akonadi::Item>("item");
1570         QTest::addColumn<Domain::Task::Ptr>("parent");
1571         QTest::addColumn<QString>("expectedRelatedToUid");
1572 
1573         Akonadi::Item item1;
1574         KCalendarCore::Todo::Ptr todo1(new KCalendarCore::Todo);
1575         item1.setPayload<KCalendarCore::Todo::Ptr>(todo1);
1576 
1577         Domain::Task::Ptr parent(new Domain::Task);
1578         parent->setProperty("todoUid", "1");
1579 
1580         QTest::newRow("nominal case") << item1 << parent << "1";
1581 
1582         Akonadi::Item item2;
1583         QTest::newRow("update item without payload") << item2 << parent << QString();
1584 
1585         Domain::Task::Ptr parent2(new Domain::Task);
1586         QTest::newRow("update item with a empty parent uid") << item1 << parent2 << QString();
1587     }
1588 
1589     void shouldUpdateItemParent()
1590     {
1591         // GIVEN
1592         QFETCH(Akonadi::Item, item);
1593         QFETCH(Domain::Task::Ptr, parent);
1594         QFETCH(QString, expectedRelatedToUid);
1595 
1596         // WHEN
1597         Akonadi::Serializer serializer;
1598         serializer.updateItemParent(item, parent);
1599 
1600         // THEN
1601         if (item.hasPayload<KCalendarCore::Todo::Ptr>()) {
1602             auto todo = item.payload<KCalendarCore::Todo::Ptr>();
1603             QString relatedUid = todo->relatedTo();
1604             QCOMPARE(relatedUid, expectedRelatedToUid);
1605         }
1606     }
1607 
1608     void shouldUpdateItemProject_data()
1609     {
1610         QTest::addColumn<Akonadi::Item>("item");
1611         QTest::addColumn<Domain::Project::Ptr>("parent");
1612         QTest::addColumn<QString>("expectedRelatedToUid");
1613 
1614         Akonadi::Item todoItem;
1615         KCalendarCore::Todo::Ptr todo(new KCalendarCore::Todo);
1616         todoItem.setPayload<KCalendarCore::Todo::Ptr>(todo);
1617 
1618         auto parent = Domain::Project::Ptr::create();
1619         parent->setProperty("todoUid", "1");
1620 
1621         QTest::newRow("nominal todo case") << todoItem << parent << "1";
1622 
1623         auto invalidParent = Domain::Project::Ptr::create();
1624         QTest::newRow("update todo item with a empty parent uid") << todoItem << invalidParent << QString();
1625 
1626         Akonadi::Item invalidItem;
1627         QTest::newRow("update item without payload") << invalidItem << parent << QString();
1628     }
1629 
1630     void shouldUpdateItemProject()
1631     {
1632         // GIVEN
1633         QFETCH(Akonadi::Item, item);
1634         QFETCH(Domain::Project::Ptr, parent);
1635         QFETCH(QString, expectedRelatedToUid);
1636 
1637         // WHEN
1638         Akonadi::Serializer serializer;
1639         serializer.updateItemProject(item, parent);
1640 
1641         // THEN
1642         if (item.hasPayload<KCalendarCore::Todo::Ptr>()) {
1643             auto todo = item.payload<KCalendarCore::Todo::Ptr>();
1644             const QString relatedUid = todo->relatedTo();
1645             QCOMPARE(relatedUid, expectedRelatedToUid);
1646         }
1647     }
1648 
1649     void shouldFilterChildrenItem_data()
1650     {
1651         QTest::addColumn<Akonadi::Item>("item");
1652         QTest::addColumn<Akonadi::Item::List>("items");
1653         QTest::addColumn<int>("size");
1654 
1655         Akonadi::Item item(12);
1656         KCalendarCore::Todo::Ptr todo(new KCalendarCore::Todo);
1657         todo->setUid(QStringLiteral("1"));
1658         item.setPayload<KCalendarCore::Todo::Ptr>(todo);
1659         Akonadi::Item::List items;
1660 
1661         QTest::newRow("empty list") << item << items << 0;
1662 
1663         Akonadi::Item item2(13);
1664         KCalendarCore::Todo::Ptr todo2(new KCalendarCore::Todo);
1665         item2.setPayload<KCalendarCore::Todo::Ptr>(todo2);
1666         Akonadi::Item::List items2;
1667         items2 << item2;
1668 
1669         QTest::newRow("list without child") << item << items2 << 0;
1670 
1671         Akonadi::Item item3(14);
1672         KCalendarCore::Todo::Ptr todo3(new KCalendarCore::Todo);
1673         todo3->setUid(QStringLiteral("3"));
1674         todo3->setRelatedTo(QStringLiteral("1"));
1675         item3.setPayload<KCalendarCore::Todo::Ptr>(todo3);
1676         Akonadi::Item::List items3;
1677         items3 << item2 << item3;
1678 
1679         QTest::newRow("list with child") << item << items3 << 1;
1680 
1681         Akonadi::Item item4(15);
1682         KCalendarCore::Todo::Ptr todo4(new KCalendarCore::Todo);
1683         todo4->setRelatedTo(QStringLiteral("3"));
1684         item4.setPayload<KCalendarCore::Todo::Ptr>(todo4);
1685         Akonadi::Item::List items4;
1686         items4 << item2 << item3 << item4;
1687 
1688         QTest::newRow("list with child with a child") << item << items4 << 2;
1689 
1690         Akonadi::Item::List items5;
1691         items5 << item << item2 << item3 << item4;
1692         QTest::newRow("list with filter in list") << item << items5 << 2;
1693     }
1694 
1695     void shouldFilterChildrenItem()
1696     {
1697         // GIVEN
1698         QFETCH(Akonadi::Item, item);
1699         QFETCH(Akonadi::Item::List, items);
1700         QFETCH(int, size);
1701 
1702         // WHEN
1703         Akonadi::Serializer serializer;
1704         Akonadi::Item::List list = serializer.filterDescendantItems(items, item);
1705 
1706         // THEN
1707         QCOMPARE(list.size(), size);
1708     }
1709 
1710     void shouldRemoveItemParent_data()
1711     {
1712         QTest::addColumn<Akonadi::Item>("item");
1713 
1714         Akonadi::Item item(15);
1715         KCalendarCore::Todo::Ptr todo(new KCalendarCore::Todo);
1716         todo->setRelatedTo(QStringLiteral("3"));
1717         item.setPayload<KCalendarCore::Todo::Ptr>(todo);
1718 
1719         QTest::newRow("nominal case") << item;
1720 
1721         Akonadi::Item item2(16);
1722         QTest::newRow("parent invalid") << item2;
1723     }
1724 
1725     void shouldRemoveItemParent()
1726     {
1727         // GIVEN
1728         QFETCH(Akonadi::Item, item);
1729 
1730         // WHEN
1731         Akonadi::Serializer serializer;
1732         serializer.removeItemParent(item);
1733 
1734         // THEN
1735         if (item.hasPayload<KCalendarCore::Todo::Ptr>())
1736             QCOMPARE(item.payload<KCalendarCore::Todo::Ptr>()->relatedTo(), QString());
1737     }
1738 
1739     void shouldPromoteItemToProject_data()
1740     {
1741         QTest::addColumn<Akonadi::Item>("item");
1742 
1743         auto item = Akonadi::Item(15);
1744         auto todo = KCalendarCore::Todo::Ptr::create();
1745         todo->setRelatedTo(QStringLiteral("3"));
1746         item.setPayload(todo);
1747 
1748         QTest::newRow("nominal case") << item;
1749         QTest::newRow("invalid item") << Akonadi::Item(16);
1750     }
1751 
1752     void shouldPromoteItemToProject()
1753     {
1754         // GIVEN
1755         QFETCH(Akonadi::Item, item);
1756 
1757         // WHEN
1758         Akonadi::Serializer serializer;
1759         serializer.promoteItemToProject(item);
1760 
1761         // THEN
1762         if (item.hasPayload<KCalendarCore::Todo::Ptr>()) {
1763             auto todo = item.payload<KCalendarCore::Todo::Ptr>();
1764             QCOMPARE(todo->relatedTo(), QString());
1765             QVERIFY(!todo->customProperty(Serializer::customPropertyAppName(), Serializer::customPropertyIsProject()).isEmpty());
1766         }
1767     }
1768 
1769     void shouldClearItem_data()
1770     {
1771         QTest::addColumn<Akonadi::Item*>("item");
1772 
1773         Akonadi::Item *itemWithContexts = new Akonadi::Item(15);
1774         KCalendarCore::Todo::Ptr todo(new KCalendarCore::Todo);
1775         // we can cheat and not really create contexts...
1776         todo->setCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyContextList(), "one,two");
1777         itemWithContexts->setPayload<KCalendarCore::Todo::Ptr>(todo);
1778         QTest::newRow("with_contexts") << itemWithContexts;
1779 
1780         Akonadi::Item *itemWithNoContext = new Akonadi::Item(15);
1781         itemWithNoContext->setPayload<KCalendarCore::Todo::Ptr>(todo);
1782         QTest::newRow("no_context") << itemWithNoContext;
1783     }
1784 
1785     void shouldClearItem()
1786     {
1787         // GIVEN
1788         QFETCH(Akonadi::Item*, item);
1789 
1790         // WHEN
1791         Akonadi::Serializer serializer;
1792         serializer.clearItem(item);
1793 
1794         // THEN
1795         auto todo = item->payload<KCalendarCore::Todo::Ptr>();
1796         QVERIFY(todo->customProperty(Serializer::customPropertyAppName(), Serializer::customPropertyContextList()).isEmpty());
1797         delete item;
1798     }
1799 
1800     void shouldCreateContextFromItem_data()
1801     {
1802         QTest::addColumn<QString>("name");
1803 
1804         QTest::newRow("nominal case") << "Context42";
1805         QTest::newRow("empty name case") << "";
1806     }
1807 
1808     void shouldCreateContextFromItem()
1809     {
1810         // GIVEN
1811 
1812         // Data...
1813         QFETCH(QString, name);
1814 
1815         // ... stored in a todo...
1816         KCalendarCore::Todo::Ptr todo(new KCalendarCore::Todo);
1817         todo->setSummary(name);
1818         todo->setCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyIsContext(), QStringLiteral("1"));
1819         QVERIFY(!todo->uid().isEmpty());
1820 
1821         // ... as payload of an item
1822         Akonadi::Item item(42);
1823         item.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
1824         item.setPayload<KCalendarCore::Todo::Ptr>(todo);
1825 
1826         // which has a parent collection
1827         Akonadi::Collection collection(43);
1828         item.setParentCollection(collection);
1829 
1830         // WHEN
1831         Akonadi::Serializer serializer;
1832         Domain::Context::Ptr context = serializer.createContextFromItem(item);
1833 
1834         // THEN
1835         QCOMPARE(context->name(), name);
1836         QCOMPARE(context->property("todoUid").toString(), todo->uid());
1837         QCOMPARE(context->property("itemId").toLongLong(), item.id());
1838         QCOMPARE(context->property("parentCollectionId").toLongLong(), collection.id());
1839     }
1840 
1841     void shouldNotCreateContextFromWrongItemType_data()
1842     {
1843         QTest::addColumn<bool>("isProject");
1844 
1845         QTest::newRow("project") << true;
1846         QTest::newRow("task") << false;
1847     }
1848 
1849     void shouldNotCreateContextFromWrongItemType()
1850     {
1851         // GIVEN
1852         QFETCH(bool, isProject);
1853 
1854         // A project todo
1855         KCalendarCore::Todo::Ptr originalTodo(new KCalendarCore::Todo);
1856         originalTodo->setSummary("summary");
1857         if (isProject)
1858             originalTodo->setCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyIsProject(), QStringLiteral("1"));
1859 
1860         // ... as payload of an item...
1861         Akonadi::Item originalItem;
1862         originalItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
1863         originalItem.setPayload<KCalendarCore::Todo::Ptr>(originalTodo);
1864 
1865         // WHEN
1866         Akonadi::Serializer serializer;
1867         Domain::Context::Ptr context = serializer.createContextFromItem(originalItem);
1868 
1869         // THEN
1870         QVERIFY(!context);
1871     }
1872 
1873     void shouldUpdateContextFromItem_data()
1874     {
1875         shouldCreateContextFromItem_data();
1876     }
1877 
1878     void shouldUpdateContextFromItem()
1879     {
1880         // GIVEN
1881 
1882         // Data...
1883         QFETCH(QString, name);
1884 
1885         // ... stored in a todo...
1886         KCalendarCore::Todo::Ptr todo(new KCalendarCore::Todo);
1887         todo->setSummary(name);
1888         todo->setCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyIsContext(), QStringLiteral("1"));
1889         QVERIFY(!todo->uid().isEmpty());
1890 
1891         // ... as payload of an item
1892         Akonadi::Item item(42);
1893         item.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
1894         item.setPayload<KCalendarCore::Todo::Ptr>(todo);
1895 
1896         // WHEN
1897         Akonadi::Serializer serializer;
1898         Domain::Context::Ptr context(new Domain::Context);
1899 
1900         serializer.updateContextFromItem(context, item);
1901 
1902         // THEN
1903         QCOMPARE(context->name(), name);
1904         QCOMPARE(context->property("todoUid").toString(), todo->uid());
1905         QCOMPARE(context->property("itemId").toLongLong(), item.id());
1906     }
1907 
1908     void shouldNotUpdateContextFromWrongItemType_data()
1909     {
1910         QTest::addColumn<bool>("isProject");
1911 
1912         QTest::newRow("project") << true;
1913         QTest::newRow("task") << false;
1914     }
1915 
1916     void shouldNotUpdateContextFromWrongItemType()
1917     {
1918         // GIVEN
1919         QFETCH(bool, isProject);
1920 
1921         // A context
1922         auto context = Domain::Context::Ptr::create();
1923         context->setName("summary");
1924         context->setProperty("todoUid", qint64(43));
1925 
1926         KCalendarCore::Todo::Ptr wrongTodo(new KCalendarCore::Todo);
1927         wrongTodo->setSummary("wrongSummary");
1928         if (isProject)
1929             wrongTodo->setCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyIsProject(), QStringLiteral("1"));
1930 
1931         Akonadi::Item wrongItem;
1932         wrongItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo"));
1933         wrongItem.setPayload<KCalendarCore::Todo::Ptr>(wrongTodo);
1934 
1935         // WHEN
1936         Akonadi::Serializer serializer;
1937         serializer.updateContextFromItem(context, wrongItem);
1938 
1939         // THEN
1940         QCOMPARE(context->name(), "summary");
1941         QCOMPARE(context->property("todoUid").toLongLong(), qint64(43));
1942     }
1943 
1944     void shouldVerifyIfAnItemIsAContextChild_data()
1945     {
1946         QTest::addColumn<Domain::Context::Ptr>("context");
1947         QTest::addColumn<Akonadi::Item>("item");
1948         QTest::addColumn<bool>("isChild");
1949 
1950         // Create a context
1951         auto context = Domain::Context::Ptr::create();
1952         const QString contextUid = QStringLiteral("abc-123");
1953         context->setProperty("todoUid", contextUid);
1954 
1955         Akonadi::Item unrelatedItem;
1956         QTest::newRow("Unrelated item") << context << unrelatedItem << false;
1957 
1958         Akonadi::Item relatedItem;
1959         auto todo = KCalendarCore::Todo::Ptr::create();
1960         todo->setSummary("summary");
1961         todo->setCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyContextList(), contextUid);
1962         relatedItem.setPayload<KCalendarCore::Todo::Ptr>(todo);
1963         QTest::newRow("Related item") << context << relatedItem << true;
1964 
1965         auto invalidContext = Domain::Context::Ptr::create();
1966         QTest::newRow("Invalid context") << invalidContext << relatedItem << false;
1967 
1968         Akonadi::Item invalidItem;
1969         QTest::newRow("Invalid Item") << context << invalidItem << false;
1970     }
1971 
1972     void shouldVerifyIfAnItemIsAContextChild()
1973     {
1974         // GIVEN
1975         QFETCH(Domain::Context::Ptr, context);
1976         QFETCH(Akonadi::Item, item);
1977         QFETCH(bool, isChild);
1978 
1979         // WHEN
1980         Akonadi::Serializer serializer;
1981         bool value = serializer.isContextChild(context, item);
1982 
1983         // THEN
1984         QCOMPARE(value, isChild);
1985     }
1986 
1987     void shouldCreateItemFromContext_data()
1988     {
1989         QTest::addColumn<QString>("name");
1990         QTest::addColumn<qint64>("itemId");
1991         QTest::addColumn<QString>("todoUid");
1992 
1993         const QString nameInternet = QStringLiteral("Internet");
1994         const QString uid = QStringLiteral("uid-123");
1995 
1996         QTest::newRow("nominal_case") << nameInternet << qint64(42) << uid;
1997         QTest::newRow("no_item_id") << nameInternet<< qint64(-1) << uid;
1998         QTest::newRow("null_uid") << nameInternet << qint64(42) << QString();
1999         QTest::newRow("no_name") << QString() << qint64(42) << uid;
2000     }
2001 
2002     void shouldCreateItemFromContext()
2003     {
2004         // GIVEN
2005         QFETCH(QString, name);
2006         QFETCH(qint64, itemId);
2007         QFETCH(QString, todoUid);
2008 
2009         // WHEN
2010         auto context = Domain::Context::Ptr::create();
2011         context->setProperty("todoUid", todoUid);
2012         if (itemId > 0)
2013             context->setProperty("itemId", itemId);
2014 
2015         context->setName(name);
2016 
2017         Akonadi::Serializer serializer;
2018         Akonadi::Item item = serializer.createItemFromContext(context);
2019         auto todo = item.payload<KCalendarCore::Todo::Ptr>();
2020 
2021         // THEN
2022         QCOMPARE(todo->summary(), name);
2023 
2024         if (!todoUid.isEmpty()) {
2025             QCOMPARE(todo->uid(), todoUid);
2026         }
2027         QCOMPARE(item.id(), itemId);
2028     }
2029 
2030     void shouldTestIfItemRepresentsContext_data()
2031     {
2032         QTest::addColumn<QString>("contextUid");
2033         QTest::addColumn<QString>("todoUid");
2034         QTest::addColumn<bool>("expectedResult");
2035 
2036         QTest::newRow("yes") << "context-123" << "context-123" << true;
2037         QTest::newRow("no") << "context-123" << "another-context" << false;
2038     }
2039 
2040     void shouldAddContextToTask_data()
2041     {
2042         QTest::addColumn<Domain::Context::Ptr>("context");
2043         QTest::addColumn<Akonadi::Item>("item");
2044         QTest::addColumn<QString>("expectedContextList");
2045 
2046         // Create a context
2047         auto context = Domain::Context::Ptr::create();
2048         const QString contextUid = QStringLiteral("abc-123");
2049         context->setProperty("todoUid", contextUid);
2050 
2051         Akonadi::Item item;
2052         auto todo = KCalendarCore::Todo::Ptr::create();
2053         todo->setSummary("summary");
2054         item.setPayload<KCalendarCore::Todo::Ptr>(todo);
2055         QTest::newRow("item_with_no_context") << context << item << contextUid;
2056 
2057         Akonadi::Item item2;
2058         auto todo2 = KCalendarCore::Todo::Ptr::create();
2059         todo2->setCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyContextList(), "another");
2060         item2.setPayload<KCalendarCore::Todo::Ptr>(todo2);
2061         const QString bothContexts = QStringLiteral("another,") + contextUid;
2062         QTest::newRow("item_with_another_context") << context << item2 << bothContexts;
2063 
2064         Akonadi::Item item3;
2065         auto todo3 = KCalendarCore::Todo::Ptr::create();
2066         todo3->setCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyContextList(), bothContexts);
2067         item3.setPayload<KCalendarCore::Todo::Ptr>(todo3);
2068         QTest::newRow("item_with_this_context_already") << context << item3 << bothContexts;
2069     }
2070 
2071     void shouldAddContextToTask()
2072     {
2073         // GIVEN
2074         QFETCH(Domain::Context::Ptr, context);
2075         QFETCH(Akonadi::Item, item);
2076         QFETCH(QString, expectedContextList);
2077 
2078         // WHEN
2079         Akonadi::Serializer serializer;
2080         serializer.addContextToTask(context, item);
2081 
2082         // THEN
2083         KCalendarCore::Todo::Ptr todo = item.payload<KCalendarCore::Todo::Ptr>();
2084         QCOMPARE(todo->customProperty(Serializer::customPropertyAppName(), Serializer::customPropertyContextList()), expectedContextList);
2085     }
2086 
2087     void shouldRemoveContextFromTask_data()
2088     {
2089         QTest::addColumn<Domain::Context::Ptr>("context");
2090         QTest::addColumn<Akonadi::Item>("item");
2091         QTest::addColumn<QString>("expectedContextList");
2092 
2093         auto context = Domain::Context::Ptr::create();
2094         const QString contextUid = QStringLiteral("abc-123");
2095         context->setProperty("todoUid", contextUid);
2096 
2097         Akonadi::Item item;
2098         auto todo = KCalendarCore::Todo::Ptr::create();
2099         todo->setSummary("summary");
2100         item.setPayload<KCalendarCore::Todo::Ptr>(todo);
2101         QTest::newRow("item_with_no_context") << context << item << QString();
2102 
2103         Akonadi::Item item2;
2104         auto todo2 = KCalendarCore::Todo::Ptr::create();
2105         todo2->setCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyContextList(), "another");
2106         item2.setPayload<KCalendarCore::Todo::Ptr>(todo2);
2107         QTest::newRow("item_with_another_context") << context << item2 << QString("another");
2108 
2109         Akonadi::Item item3;
2110         auto todo3 = KCalendarCore::Todo::Ptr::create();
2111         todo3->setCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyContextList(), contextUid);
2112         item3.setPayload<KCalendarCore::Todo::Ptr>(todo3);
2113         QTest::newRow("item_with_this_context_already") << context << item3 << QString();
2114 
2115         Akonadi::Item item4;
2116         auto todo4 = KCalendarCore::Todo::Ptr::create();
2117         const QString bothContexts = QStringLiteral("another,") + contextUid;
2118         todo4->setCustomProperty(Serializer::customPropertyAppName(), Serializer::customPropertyContextList(), bothContexts);
2119         item4.setPayload<KCalendarCore::Todo::Ptr>(todo4);
2120         QTest::newRow("item_with_two_contexts") << context << item4 << QString("another");
2121     }
2122 
2123     void shouldRemoveContextFromTask()
2124     {
2125         // GIVEN
2126         QFETCH(Domain::Context::Ptr, context);
2127         QFETCH(Akonadi::Item, item);
2128         QFETCH(QString, expectedContextList);
2129 
2130         // WHEN
2131         Akonadi::Serializer serializer;
2132         serializer.removeContextFromTask(context, item);
2133 
2134         // THEN
2135         KCalendarCore::Todo::Ptr todo = item.payload<KCalendarCore::Todo::Ptr>();
2136         QCOMPARE(todo->customProperty(Serializer::customPropertyAppName(), Serializer::customPropertyContextList()), expectedContextList);
2137     }
2138 
2139     // Investigation into how to differentiate all-day events from events with time,
2140     // using QDateTime only. Doesn't seem to be possible.
2141     void noWayToHaveQDateTimeWithoutTime()
2142     {
2143         // GIVEN a QDateTime without time information
2144         QDateTime dateOnly(QDate(2016, 6, 12), QTime(-1, -1, -1));
2145         // THEN we can't detect that there was no time information, i.e. all day event
2146         QVERIFY(dateOnly.time().isValid()); // I wish this was "!"
2147         QVERIFY(!dateOnly.time().isNull()); // got converted to midnight localtime by QDateTime
2148         // This doesn't help, QDateTime converts "null time" to midnight.
2149         dateOnly.setTime(QTime());
2150         QVERIFY(dateOnly.time().isValid()); // same as above
2151         QVERIFY(!dateOnly.time().isNull()); // same as above
2152 
2153         // GIVEN a QDateTime at midnight
2154         QDateTime atMidnight(QDate(2016, 6, 12), QTime(0, 0, 0));
2155         // THEN we can detect that a time information was present
2156         QVERIFY(atMidnight.time().isValid());
2157         QVERIFY(!atMidnight.time().isNull());
2158 
2159 #if 0
2160         // GIVEN a KDateTime without time information
2161         KDateTime kdOnly(QDate(2016, 6, 12));
2162         // THEN we can detect that there was no time information, i.e. all day event
2163         QVERIFY(kdOnly.isDateOnly());
2164 #endif
2165     }
2166 };
2167 
2168 ZANSHIN_TEST_MAIN(AkonadiSerializerTest)
2169 
2170 #include "akonadiserializertest.moc"