diff --git a/src/akonadi/akonadiserializer.cpp b/src/akonadi/akonadiserializer.cpp index ee4d1ff9..be41b181 100644 --- a/src/akonadi/akonadiserializer.cpp +++ b/src/akonadi/akonadiserializer.cpp @@ -1,714 +1,703 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens Copyright 2014 Rémi Benoit This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "akonadiserializer.h" #include #include #include #include #include #include #include #include #if KCALCORE_VERSION < QT_VERSION_CHECK(5, 6, 80) #include #endif #include #include "utils/mem_fn.h" #include "akonadi/akonadiapplicationselectedattribute.h" #include "akonadi/akonaditimestampattribute.h" using namespace Akonadi; Serializer::Serializer() { } Serializer::~Serializer() { } bool Serializer::representsCollection(SerializerInterface::QObjectPtr object, Collection collection) { return object->property("collectionId").toLongLong() == collection.id(); } bool Serializer::representsItem(QObjectPtr object, Item item) { return object->property("itemId").toLongLong() == item.id(); } bool Serializer::representsAkonadiTag(Domain::Tag::Ptr tag, Tag akonadiTag) const { return tag->property("tagId").toLongLong() == akonadiTag.id(); } QString Serializer::objectUid(SerializerInterface::QObjectPtr object) { return object->property("todoUid").toString(); } QString Serializer::itemUid(const Item &item) { if (isTaskItem(item)) { const auto todo = item.payload(); return todo->uid(); } else { return QString(); } } Domain::DataSource::Ptr Serializer::createDataSourceFromCollection(Collection collection, DataSourceNameScheme naming) { if (!collection.isValid()) return Domain::DataSource::Ptr(); auto dataSource = Domain::DataSource::Ptr::create(); updateDataSourceFromCollection(dataSource, collection, naming); return dataSource; } void Serializer::updateDataSourceFromCollection(Domain::DataSource::Ptr dataSource, Collection collection, DataSourceNameScheme naming) { if (!collection.isValid()) return; QString name = collection.displayName(); if (naming == FullPath) { auto parent = collection.parentCollection(); while (parent.isValid() && parent != Akonadi::Collection::root()) { name = parent.displayName() + " » " + name; parent = parent.parentCollection(); } } dataSource->setName(name); const auto mimeTypes = collection.contentMimeTypes(); auto types = Domain::DataSource::ContentTypes(); if (mimeTypes.contains(NoteUtils::noteMimeType())) types |= Domain::DataSource::Notes; if (mimeTypes.contains(KCalCore::Todo::todoMimeType())) types |= Domain::DataSource::Tasks; dataSource->setContentTypes(types); if (collection.hasAttribute()) { auto iconName = collection.attribute()->iconName(); dataSource->setIconName(iconName); } if (!collection.hasAttribute()) { dataSource->setSelected(true); } else { auto isSelected = collection.attribute()->isSelected(); dataSource->setSelected(isSelected); } dataSource->setProperty("collectionId", collection.id()); } Collection Serializer::createCollectionFromDataSource(Domain::DataSource::Ptr dataSource) { const auto id = dataSource->property("collectionId").value(); auto collection = Collection(id); collection.attribute(Akonadi::Collection::AddIfMissing); auto selectedAttribute = collection.attribute(Akonadi::Collection::AddIfMissing); selectedAttribute->setSelected(dataSource->isSelected()); return collection; } bool Serializer::isSelectedCollection(Collection collection) { if (!isNoteCollection(collection) && !isTaskCollection(collection)) return false; if (!collection.hasAttribute()) return true; return collection.attribute()->isSelected(); } bool Akonadi::Serializer::isNoteCollection(Akonadi::Collection collection) { return collection.contentMimeTypes().contains(NoteUtils::noteMimeType()); } bool Akonadi::Serializer::isTaskCollection(Akonadi::Collection collection) { return collection.contentMimeTypes().contains(KCalCore::Todo::todoMimeType()); } bool Serializer::isTaskItem(Item item) { if (!item.hasPayload()) return false; auto todo = item.payload(); return todo->customProperty("Zanshin", "Project").isEmpty(); } Domain::Task::Ptr Serializer::createTaskFromItem(Item item) { if (!isTaskItem(item)) return Domain::Task::Ptr(); auto task = Domain::Task::Ptr::create(); updateTaskFromItem(task, item); return task; } void Serializer::updateTaskFromItem(Domain::Task::Ptr task, Item item) { if (!isTaskItem(item)) return; auto todo = item.payload(); task->setTitle(todo->summary()); task->setText(todo->description()); task->setDone(todo->isCompleted()); #if KCALCORE_VERSION >= QT_VERSION_CHECK(5, 6, 80) - if (todo->allDay()) { - task->setDoneDate(QDateTime(todo->completed().date(), QTime(), Qt::UTC)); - task->setStartDate(QDateTime(todo->dtStart().date(), QTime(), Qt::UTC)); - task->setDueDate(QDateTime(todo->dtDue().date(), QTime(), Qt::UTC)); - } else { - task->setDoneDate(todo->completed().toUTC()); - task->setStartDate(todo->dtStart().toUTC()); - task->setDueDate(todo->dtDue().toUTC()); - } + task->setDoneDate(todo->completed().toLocalTime().date()); + task->setStartDate(todo->dtStart().toLocalTime().date()); + task->setDueDate(todo->dtDue().toLocalTime().date()); #else - task->setDoneDate(todo->completed().dateTime().toUTC()); - task->setStartDate(todo->dtStart().dateTime().toUTC()); - task->setDueDate(todo->dtDue().dateTime().toUTC()); + task->setDoneDate(todo->completed().dateTime().toLocalTime().date()); + task->setStartDate(todo->dtStart().dateTime().toLocalTime().date()); + task->setDueDate(todo->dtDue().dateTime().toLocalTime().date()); #endif task->setProperty("itemId", item.id()); task->setProperty("parentCollectionId", item.parentCollection().id()); task->setProperty("todoUid", todo->uid()); task->setProperty("relatedUid", todo->relatedTo()); task->setRunning(todo->customProperty("Zanshin", "Running") == QLatin1String("1")); switch (todo->recurrence()->recurrenceType()) { case KCalCore::Recurrence::rDaily: task->setRecurrence(Domain::Task::RecursDaily); break; case KCalCore::Recurrence::rWeekly: task->setRecurrence(Domain::Task::RecursWeekly); break; case KCalCore::Recurrence::rMonthlyDay: task->setRecurrence(Domain::Task::RecursMonthly); break; default: // Other cases are not supported for now and as such just ignored break; } QMimeDatabase mimeDb; const auto attachmentsInput = todo->attachments(); Domain::Task::Attachments attachments; attachments.reserve(attachmentsInput.size()); std::transform(attachmentsInput.cbegin(), attachmentsInput.cend(), std::back_inserter(attachments), [&mimeDb] (const KCalCore::Attachment::Ptr &attach) { Domain::Task::Attachment attachment; if (attach->isUri()) attachment.setUri(QUrl(attach->uri())); else attachment.setData(attach->decodedData()); attachment.setLabel(attach->label()); attachment.setMimeType(attach->mimeType()); attachment.setIconName(mimeDb.mimeTypeForName(attach->mimeType()).iconName()); return attachment; }); task->setAttachments(attachments); if (todo->attendeeCount() > 0) { const auto attendees = todo->attendees(); const auto delegate = std::find_if(attendees.begin(), attendees.end(), [] (const KCalCore::Attendee::Ptr &attendee) { return attendee->status() == KCalCore::Attendee::Accepted; }); if (delegate != attendees.end()) { task->setDelegate(Domain::Task::Delegate((*delegate)->name(), (*delegate)->email())); } } } bool Serializer::isTaskChild(Domain::Task::Ptr task, Akonadi::Item item) { if (!isTaskItem(item)) return false; auto todo = item.payload(); if (todo->relatedTo() == task->property("todoUid")) return true; return false; } #if KCALCORE_VERSION >= QT_VERSION_CHECK(5, 6, 80) #define KDateTime QDateTime #endif Akonadi::Item Serializer::createItemFromTask(Domain::Task::Ptr task) { auto todo = KCalCore::Todo::Ptr::create(); todo->setSummary(task->title()); todo->setDescription(task->text()); // We only support all-day todos, so ignore timezone information and possible effect from timezone on dates // KCalCore reads "DUE;VALUE=DATE:20171130" as QDateTime(QDate(2017, 11, 30), QTime(), Qt::LocalTime), for lack of timezone information - // so we should never call toUtc() on that, it would mess up the date. Instead we force UTC while preserving the date. + // so we should never call toUtc() on that, it would mess up the date. // If one day we want to support time information, we need to add a task->isAllDay()/setAllDay(). -#if KCALCORE_VERSION >= QT_VERSION_CHECK(5, 6, 80) - todo->setDtStart(QDateTime(task->startDate().date(), QTime(), Qt::UTC)); - todo->setDtDue(QDateTime(task->dueDate().date(), QTime(), Qt::UTC)); + todo->setDtStart(QDateTime(task->startDate())); + todo->setDtDue(QDateTime(task->dueDate())); todo->setAllDay(true); -#else - todo->setDtStart(KDateTime(task->startDate(), KDateTime::UTC)); - todo->setDtDue(KDateTime(task->dueDate(), KDateTime::UTC)); -#endif if (task->property("todoUid").isValid()) { todo->setUid(task->property("todoUid").toString()); } if (task->property("relatedUid").isValid()) { todo->setRelatedTo(task->property("relatedUid").toString()); } switch (task->recurrence()) { case Domain::Task::NoRecurrence: break; case Domain::Task::RecursDaily: todo->recurrence()->setDaily(1); break; case Domain::Task::RecursWeekly: todo->recurrence()->setWeekly(1); break; case Domain::Task::RecursMonthly: todo->recurrence()->setMonthly(1); break; } for (const auto &attachment : task->attachments()) { KCalCore::Attachment::Ptr attach(new KCalCore::Attachment(QByteArray())); if (attachment.isUri()) attach->setUri(attachment.uri().toString()); else attach->setDecodedData(attachment.data()); attach->setMimeType(attachment.mimeType()); attach->setLabel(attachment.label()); todo->addAttachment(attach); } if (task->delegate().isValid()) { KCalCore::Attendee::Ptr attendee(new KCalCore::Attendee(task->delegate().name(), task->delegate().email(), true, KCalCore::Attendee::Accepted)); todo->addAttendee(attendee); } if (task->isRunning()) { todo->setCustomProperty("Zanshin", "Running", "1"); } else { todo->removeCustomProperty("Zanshin", "Running"); } // Needs to be done after all other dates are positioned // since this applies the recurrence logic if (task->isDone()) - todo->setCompleted(KDateTime(task->doneDate())); + todo->setCompleted(QDateTime(task->doneDate(), QTime(), Qt::UTC)); else todo->setCompleted(false); Akonadi::Item item; if (task->property("itemId").isValid()) { item.setId(task->property("itemId").value()); } if (task->property("parentCollectionId").isValid()) { auto parentId = task->property("parentCollectionId").value(); item.setParentCollection(Akonadi::Collection(parentId)); } item.setMimeType(KCalCore::Todo::todoMimeType()); item.setPayload(todo); return item; } QString Serializer::relatedUidFromItem(Akonadi::Item item) { if (isTaskItem(item)) { const auto todo = item.payload(); return todo->relatedTo(); } else if (isNoteItem(item)) { const auto message = item.payload(); const auto relatedHeader = message->headerByType("X-Zanshin-RelatedProjectUid"); return relatedHeader ? relatedHeader->asUnicodeString() : QString(); } else { return QString(); } } void Serializer::updateItemParent(Akonadi::Item item, Domain::Task::Ptr parent) { if (!isTaskItem(item)) return; auto todo = item.payload(); todo->setRelatedTo(parent->property("todoUid").toString()); } void Serializer::updateItemProject(Item item, Domain::Project::Ptr project) { if (isTaskItem(item)) { auto todo = item.payload(); todo->setRelatedTo(project->property("todoUid").toString()); } else if (isNoteItem(item)) { auto note = item.payload(); note->removeHeader("X-Zanshin-RelatedProjectUid"); const QByteArray parentUid = project->property("todoUid").toString().toUtf8(); if (!parentUid.isEmpty()) { auto relatedHeader = new KMime::Headers::Generic("X-Zanshin-RelatedProjectUid"); relatedHeader->from7BitString(parentUid); note->appendHeader(relatedHeader); } note->assemble(); } } void Serializer::removeItemParent(Akonadi::Item item) { if (!isTaskItem(item)) return; auto todo = item.payload(); todo->setRelatedTo(QString()); } void Serializer::promoteItemToProject(Akonadi::Item item) { if (!isTaskItem(item)) return; auto todo = item.payload(); todo->setRelatedTo(QString()); todo->setCustomProperty("Zanshin", "Project", QStringLiteral("1")); } void Serializer::clearItem(Akonadi::Item *item) { Q_ASSERT(item); if (!isTaskItem(*item)) return; // NOTE : Currently not working, when akonadistorage test will make it pass, we will use it // item->clearTags(); foreach (const Tag& tag, item->tags()) item->clearTag(tag); } Akonadi::Item::List Serializer::filterDescendantItems(const Akonadi::Item::List &potentialChildren, const Akonadi::Item &ancestorItem) { if (potentialChildren.isEmpty()) return Akonadi::Item::List(); Akonadi::Item::List itemsToProcess = potentialChildren; Q_ASSERT(ancestorItem.isValid() && ancestorItem.hasPayload()); KCalCore::Todo::Ptr todo = ancestorItem.payload(); const auto bound = std::partition(itemsToProcess.begin(), itemsToProcess.end(), [ancestorItem, todo](Akonadi::Item currentItem) { return (!currentItem.hasPayload() || currentItem == ancestorItem || currentItem.payload()->relatedTo() != todo->uid()); }); Akonadi::Item::List itemsRemoved; itemsRemoved.reserve(std::distance(itemsToProcess.begin(), bound)); std::copy(itemsToProcess.begin(), bound, std::back_inserter(itemsRemoved)); itemsToProcess.erase(itemsToProcess.begin(), bound); auto result = std::accumulate(itemsToProcess.begin(), itemsToProcess.end(), Akonadi::Item::List(), [this, itemsRemoved](Akonadi::Item::List result, Akonadi::Item currentItem) { result << currentItem; return result += filterDescendantItems(itemsRemoved, currentItem); }); return result; } bool Serializer::isNoteItem(Item item) { return item.hasPayload(); } Domain::Note::Ptr Serializer::createNoteFromItem(Akonadi::Item item) { if (!isNoteItem(item)) return Domain::Note::Ptr(); Domain::Note::Ptr note = Domain::Note::Ptr::create(); updateNoteFromItem(note, item); return note; } void Serializer::updateNoteFromItem(Domain::Note::Ptr note, Item item) { if (!isNoteItem(item)) return; auto message = item.payload(); note->setTitle(message->subject(true)->asUnicodeString()); note->setText(message->mainBodyPart()->decodedText()); note->setProperty("itemId", item.id()); if (auto relatedHeader = message->headerByType("X-Zanshin-RelatedProjectUid")) { note->setProperty("relatedUid", relatedHeader->asUnicodeString()); } else { note->setProperty("relatedUid", QVariant()); } } Item Serializer::createItemFromNote(Domain::Note::Ptr note) { NoteUtils::NoteMessageWrapper builder; builder.setTitle(note->title()); builder.setText(note->text() + '\n'); // Adding an extra '\n' because KMime always removes it... KMime::Message::Ptr message = builder.message(); if (!note->property("relatedUid").toString().isEmpty()) { auto relatedHeader = new KMime::Headers::Generic("X-Zanshin-RelatedProjectUid"); relatedHeader->from7BitString(note->property("relatedUid").toString().toUtf8()); message->appendHeader(relatedHeader); } Akonadi::Item item; if (note->property("itemId").isValid()) { item.setId(note->property("itemId").value()); } item.setMimeType(Akonadi::NoteUtils::noteMimeType()); item.setPayload(message); return item; } bool Serializer::isProjectItem(Item item) { if (!item.hasPayload()) return false; return !isTaskItem(item); } Domain::Project::Ptr Serializer::createProjectFromItem(Item item) { if (!isProjectItem(item)) return Domain::Project::Ptr(); auto project = Domain::Project::Ptr::create(); updateProjectFromItem(project, item); return project; } void Serializer::updateProjectFromItem(Domain::Project::Ptr project, Item item) { if (!isProjectItem(item)) return; auto todo = item.payload(); project->setName(todo->summary()); project->setProperty("itemId", item.id()); project->setProperty("parentCollectionId", item.parentCollection().id()); project->setProperty("todoUid", todo->uid()); } Item Serializer::createItemFromProject(Domain::Project::Ptr project) { auto todo = KCalCore::Todo::Ptr::create(); todo->setSummary(project->name()); todo->setCustomProperty("Zanshin", "Project", QStringLiteral("1")); if (project->property("todoUid").isValid()) { todo->setUid(project->property("todoUid").toString()); } Akonadi::Item item; if (project->property("itemId").isValid()) { item.setId(project->property("itemId").value()); } if (project->property("parentCollectionId").isValid()) { auto parentId = project->property("parentCollectionId").value(); item.setParentCollection(Akonadi::Collection(parentId)); } item.setMimeType(KCalCore::Todo::todoMimeType()); item.setPayload(todo); return item; } bool Serializer::isProjectChild(Domain::Project::Ptr project, Item item) { const QString todoUid = project->property("todoUid").toString(); const QString relatedUid = relatedUidFromItem(item); return !todoUid.isEmpty() && !relatedUid.isEmpty() && todoUid == relatedUid; } Domain::Context::Ptr Serializer::createContextFromTag(Akonadi::Tag tag) { if (!isContext(tag)) return Domain::Context::Ptr(); auto context = Domain::Context::Ptr::create(); updateContextFromTag(context, tag); return context; } Akonadi::Tag Serializer::createTagFromContext(Domain::Context::Ptr context) { auto tag = Akonadi::Tag(); tag.setName(context->name()); tag.setType(Akonadi::SerializerInterface::contextTagType()); tag.setGid(QByteArray(context->name().toLatin1())); if (context->property("tagId").isValid()) tag.setId(context->property("tagId").value()); return tag; } void Serializer::updateContextFromTag(Domain::Context::Ptr context, Akonadi::Tag tag) { if (!isContext(tag)) return; context->setProperty("tagId", tag.id()); context->setName(tag.name()); } bool Serializer::hasContextTags(Item item) const { using namespace std::placeholders; Tag::List tags = item.tags(); return std::any_of(tags.constBegin(), tags.constEnd(), std::bind(Utils::mem_fn(&Serializer::isContext), this, _1)); } bool Serializer::hasAkonadiTags(Item item) const { using namespace std::placeholders; Tag::List tags = item.tags(); return std::any_of(tags.constBegin(), tags.constEnd(), std::bind(Utils::mem_fn(&Serializer::isAkonadiTag), this, _1)); } bool Serializer::isContext(const Akonadi::Tag &tag) const { return (tag.type() == Akonadi::SerializerInterface::contextTagType()); } bool Serializer::isAkonadiTag(const Tag &tag) const { return tag.type() == Akonadi::Tag::PLAIN; } bool Serializer::isContextTag(const Domain::Context::Ptr &context, const Akonadi::Tag &tag) const { return (context->property("tagId").value() == tag.id()); } bool Serializer::isContextChild(Domain::Context::Ptr context, Item item) const { if (!context->property("tagId").isValid()) return false; auto tagId = context->property("tagId").value(); Akonadi::Tag tag(tagId); return item.hasTag(tag); } Domain::Tag::Ptr Serializer::createTagFromAkonadiTag(Akonadi::Tag akonadiTag) { if (!isAkonadiTag(akonadiTag)) return Domain::Tag::Ptr(); auto tag = Domain::Tag::Ptr::create(); updateTagFromAkonadiTag(tag, akonadiTag); return tag; } void Serializer::updateTagFromAkonadiTag(Domain::Tag::Ptr tag, Akonadi::Tag akonadiTag) { if (!isAkonadiTag(akonadiTag)) return; tag->setProperty("tagId", akonadiTag.id()); tag->setName(akonadiTag.name()); } Akonadi::Tag Serializer::createAkonadiTagFromTag(Domain::Tag::Ptr tag) { auto akonadiTag = Akonadi::Tag(); akonadiTag.setName(tag->name()); akonadiTag.setType(Akonadi::Tag::PLAIN); akonadiTag.setGid(QByteArray(tag->name().toLatin1())); const auto tagProperty = tag->property("tagId"); if (tagProperty.isValid()) akonadiTag.setId(tagProperty.value()); return akonadiTag; } bool Serializer::isTagChild(Domain::Tag::Ptr tag, Akonadi::Item item) { if (!tag->property("tagId").isValid()) return false; auto tagId = tag->property("tagId").value(); Akonadi::Tag akonadiTag(tagId); return item.hasTag(akonadiTag); } diff --git a/src/akonadi/akonaditaskqueries.cpp b/src/akonadi/akonaditaskqueries.cpp index e10494b0..dbb29a58 100644 --- a/src/akonadi/akonaditaskqueries.cpp +++ b/src/akonadi/akonaditaskqueries.cpp @@ -1,176 +1,176 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "akonaditaskqueries.h" #include "utils/datetime.h" #include using namespace Akonadi; TaskQueries::TaskQueries(const StorageInterface::Ptr &storage, const SerializerInterface::Ptr &serializer, const MonitorInterface::Ptr &monitor, const Cache::Ptr &cache) : m_serializer(serializer), m_cache(cache), m_helpers(new LiveQueryHelpers(serializer, storage)), m_integrator(new LiveQueryIntegrator(serializer, monitor)), m_workdayPollTimer(new QTimer(this)) { m_workdayPollTimer->setInterval(30000); connect(m_workdayPollTimer, &QTimer::timeout, this, &TaskQueries::onWorkdayPollTimeout); m_integrator->addRemoveHandler([this] (const Item &item) { m_findChildren.remove(item.id()); }); } int TaskQueries::workdayPollInterval() const { return m_workdayPollTimer->interval(); } void TaskQueries::setWorkdayPollInterval(int interval) { m_workdayPollTimer->setInterval(interval); } TaskQueries::TaskResult::Ptr TaskQueries::findAll() const { auto fetch = m_helpers->fetchItems(StorageInterface::Tasks); auto predicate = [this] (const Akonadi::Item &item) { return m_serializer->isTaskItem(item); }; m_integrator->bind("TaskQueries::findAll", m_findAll, fetch, predicate); return m_findAll->result(); } TaskQueries::TaskResult::Ptr TaskQueries::findChildren(Domain::Task::Ptr task) const { Akonadi::Item item = m_serializer->createItemFromTask(task); auto &query = m_findChildren[item.id()]; auto fetch = m_helpers->fetchSiblings(item); auto predicate = [this, task] (const Akonadi::Item &item) { return m_serializer->isTaskChild(task, item); }; m_integrator->bind("TaskQueries::findChildren", query, fetch, predicate); return query->result(); } TaskQueries::TaskResult::Ptr TaskQueries::findTopLevel() const { auto fetch = m_helpers->fetchItems(StorageInterface::Tasks); auto predicate = [this] (const Akonadi::Item &item) { return m_serializer->relatedUidFromItem(item).isEmpty() && m_serializer->isTaskItem(item); }; m_integrator->bind("TaskQueries::findTopLevel", m_findTopLevel, fetch, predicate); return m_findTopLevel->result(); } TaskQueries::TaskResult::Ptr TaskQueries::findInboxTopLevel() const { auto fetch = m_helpers->fetchItems(StorageInterface::Tasks); auto predicate = [this] (const Akonadi::Item &item) { const bool excluded = !m_serializer->isTaskItem(item) || !m_serializer->relatedUidFromItem(item).isEmpty(); return !excluded; }; m_integrator->bind("TaskQueries::findInboxTopLevel", m_findInboxTopLevel, fetch, predicate); return m_findInboxTopLevel->result(); } TaskQueries::TaskResult::Ptr TaskQueries::findWorkdayTopLevel() const { if (!m_findWorkdayTopLevel) { m_workdayPollTimer->start(); - m_today = Utils::DateTime::currentDateTime().date(); + m_today = Utils::DateTime::currentDate(); } auto fetch = m_helpers->fetchItems(StorageInterface::Tasks); auto isWorkdayItem = [this] (const Akonadi::Item &item) { if (!m_serializer->isTaskItem(item)) return false; const Domain::Task::Ptr task = m_serializer->createTaskFromItem(item); - const QDate doneDate = task->doneDate().date(); - const QDate startDate = task->startDate().date(); - const QDate dueDate = task->dueDate().date(); - const QDate today = Utils::DateTime::currentDateTime().date(); + const QDate doneDate = task->doneDate(); + const QDate startDate = task->startDate(); + const QDate dueDate = task->dueDate(); + const QDate today = Utils::DateTime::currentDate(); const bool pastStartDate = startDate.isValid() && startDate <= today; const bool pastDueDate = dueDate.isValid() && dueDate <= today; const bool todayDoneDate = doneDate == today; if (task->isDone()) return todayDoneDate; else return pastStartDate || pastDueDate; }; auto predicate = [this, isWorkdayItem] (const Akonadi::Item &item) { if (!isWorkdayItem(item)) return false; const auto items = m_cache->items(item.parentCollection()); auto currentItem = item; auto parentUid = m_serializer->relatedUidFromItem(currentItem); while (!parentUid.isEmpty()) { const auto parent = std::find_if(items.cbegin(), items.cend(), [this, parentUid] (const Akonadi::Item &item) { return m_serializer->itemUid(item) == parentUid; }); if (parent == items.cend()) break; if (isWorkdayItem(*parent)) return false; currentItem = *parent; parentUid = m_serializer->relatedUidFromItem(currentItem); } return true; }; m_integrator->bind("TaskQueries::findWorkdayTopLevel", m_findWorkdayTopLevel, fetch, predicate); return m_findWorkdayTopLevel->result(); } TaskQueries::ContextResult::Ptr TaskQueries::findContexts(Domain::Task::Ptr task) const { qFatal("Not implemented yet"); Q_UNUSED(task); return ContextResult::Ptr(); } void TaskQueries::onWorkdayPollTimeout() { - auto newDate = Utils::DateTime::currentDateTime().date(); + auto newDate = Utils::DateTime::currentDate(); if (m_findWorkdayTopLevel && m_today != newDate) { m_today = newDate; m_findWorkdayTopLevel->reset(); } } diff --git a/src/domain/task.cpp b/src/domain/task.cpp index f98579ea..4ba9d6ca 100644 --- a/src/domain/task.cpp +++ b/src/domain/task.cpp @@ -1,333 +1,333 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "task.h" #include "utils/datetime.h" using namespace Domain; Task::Task(QObject *parent) : Artifact(parent), m_running(false), m_done(false), m_recurrence(NoRecurrence) { } Task::~Task() { } bool Task::isRunning() const { return m_running; } bool Task::isDone() const { return m_done; } void Task::setDone(bool done) { if (m_done == done) return; - const QDateTime doneDate = done ? Utils::DateTime::currentDateTime() : QDateTime(); + const QDate doneDate = done ? Utils::DateTime::currentDate() : QDate(); m_done = done; m_doneDate = doneDate; emit doneChanged(done); emit doneDateChanged(doneDate); } -void Task::setDoneDate(const QDateTime &doneDate) +void Task::setDoneDate(const QDate &doneDate) { if (m_doneDate == doneDate) return; m_doneDate = doneDate; emit doneDateChanged(doneDate); } -QDateTime Task::startDate() const +QDate Task::startDate() const { return m_startDate; } -void Task::setStartDate(const QDateTime &startDate) +void Task::setStartDate(const QDate &startDate) { if (m_startDate == startDate) return; m_startDate = startDate; emit startDateChanged(startDate); } -QDateTime Task::dueDate() const +QDate Task::dueDate() const { return m_dueDate; } -QDateTime Task::doneDate() const +QDate Task::doneDate() const { return m_doneDate; } Task::Recurrence Task::recurrence() const { return m_recurrence; } Task::Attachments Task::attachments() const { return m_attachments; } Task::Delegate Task::delegate() const { return m_delegate; } void Task::setRunning(bool running) { if (m_running == running) return; m_running = running; emit runningChanged(running); } -void Task::setDueDate(const QDateTime &dueDate) +void Task::setDueDate(const QDate &dueDate) { if (m_dueDate == dueDate) return; m_dueDate = dueDate; emit dueDateChanged(dueDate); } void Task::setRecurrence(Task::Recurrence recurrence) { if (m_recurrence == recurrence) return; m_recurrence = recurrence; emit recurrenceChanged(recurrence); } void Task::setAttachments(const Task::Attachments &attachments) { if (m_attachments == attachments) return; m_attachments = attachments; emit attachmentsChanged(attachments); } void Task::setDelegate(const Task::Delegate &delegate) { if (m_delegate == delegate) return; m_delegate = delegate; emit delegateChanged(delegate); } Task::Attachment::Attachment() { } Task::Attachment::Attachment(const QByteArray &data) { setData(data); } Task::Attachment::Attachment(const QUrl &uri) { setUri(uri); } Task::Attachment::Attachment(const Task::Attachment &other) : m_uri(other.m_uri), m_data(other.m_data), m_label(other.m_label), m_mimeType(other.m_mimeType), m_iconName(other.m_iconName) { } Task::Attachment::~Attachment() { } Task::Attachment &Task::Attachment::operator=(const Task::Attachment &other) { Attachment copy(other); std::swap(m_uri, copy.m_uri); std::swap(m_data, copy.m_data); std::swap(m_label, copy.m_label); std::swap(m_mimeType, copy.m_mimeType); std::swap(m_iconName, copy.m_iconName); return *this; } bool Task::Attachment::operator==(const Task::Attachment &other) const { return m_uri == other.m_uri && m_data == other.m_data && m_label == other.m_label && m_mimeType == other.m_mimeType && m_iconName == other.m_iconName; } bool Task::Attachment::isValid() const { return m_uri.isValid() || !m_data.isEmpty(); } bool Task::Attachment::isUri() const { return m_uri.isValid(); } QUrl Task::Attachment::uri() const { return m_uri; } void Task::Attachment::setUri(const QUrl &uri) { m_uri = uri; m_data.clear(); } QByteArray Task::Attachment::data() const { return m_data; } void Task::Attachment::setData(const QByteArray &data) { m_data = data; m_uri.clear(); } QString Task::Attachment::label() const { return m_label; } void Task::Attachment::setLabel(const QString &label) { m_label = label; } QString Task::Attachment::mimeType() const { return m_mimeType; } void Task::Attachment::setMimeType(const QString &mimeType) { m_mimeType = mimeType; } QString Task::Attachment::iconName() const { return m_iconName; } void Task::Attachment::setIconName(const QString &iconName) { m_iconName = iconName; } Task::Delegate::Delegate() { } Task::Delegate::Delegate(const QString &name, const QString &email) : m_name(name), m_email(email) { } Task::Delegate::Delegate(const Task::Delegate &other) : m_name(other.m_name), m_email(other.m_email) { } Task::Delegate::~Delegate() { } Task::Delegate &Task::Delegate::operator=(const Task::Delegate &other) { Delegate copy(other); std::swap(m_name, copy.m_name); std::swap(m_email, copy.m_email); return *this; } bool Task::Delegate::operator==(const Task::Delegate &other) const { return m_name == other.m_name && m_email == other.m_email; } bool Task::Delegate::isValid() const { return !m_email.isEmpty(); } QString Task::Delegate::display() const { return !isValid() ? QString() : !m_name.isEmpty() ? m_name : m_email; } QString Task::Delegate::name() const { return m_name; } void Task::Delegate::setName(const QString &name) { m_name = name; } QString Task::Delegate::email() const { return m_email; } void Task::Delegate::setEmail(const QString &email) { m_email = email; } diff --git a/src/domain/task.h b/src/domain/task.h index 04e78008..5f5874de 100644 --- a/src/domain/task.h +++ b/src/domain/task.h @@ -1,172 +1,172 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef DOMAIN_TASK_H #define DOMAIN_TASK_H #include "artifact.h" -#include +#include #include namespace Domain { class Task : public Artifact { Q_OBJECT Q_PROPERTY(bool running READ isRunning WRITE setRunning NOTIFY runningChanged) Q_PROPERTY(bool done READ isDone WRITE setDone NOTIFY doneChanged) - Q_PROPERTY(QDateTime startDate READ startDate WRITE setStartDate NOTIFY startDateChanged) - Q_PROPERTY(QDateTime dueDate READ dueDate WRITE setDueDate NOTIFY dueDateChanged) + Q_PROPERTY(QDate startDate READ startDate WRITE setStartDate NOTIFY startDateChanged) + Q_PROPERTY(QDate dueDate READ dueDate WRITE setDueDate NOTIFY dueDateChanged) Q_PROPERTY(Domain::Task::Recurrence recurrence READ recurrence WRITE setRecurrence NOTIFY recurrenceChanged) Q_PROPERTY(Domain::Task::Delegate delegate READ delegate WRITE setDelegate NOTIFY delegateChanged) Q_PROPERTY(Domain::Task::Attachments attachements READ attachments WRITE setAttachments NOTIFY attachmentsChanged) public: typedef QSharedPointer Ptr; typedef QList List; enum Recurrence { NoRecurrence = 0, RecursDaily, RecursWeekly, RecursMonthly // for now only monthly on the same day (say 11th day of the month) }; Q_ENUM(Recurrence) class Attachment { public: Attachment(); explicit Attachment(const QByteArray &data); explicit Attachment(const QUrl &uri); Attachment(const Attachment &other); ~Attachment(); Attachment &operator=(const Attachment &other); bool operator==(const Attachment &other) const; bool isValid() const; bool isUri() const; QUrl uri() const; void setUri(const QUrl &uri); QByteArray data() const; void setData(const QByteArray &data); QString label() const; void setLabel(const QString &label); QString mimeType() const; void setMimeType(const QString &mimeType); QString iconName() const; void setIconName(const QString &iconName); private: QUrl m_uri; QByteArray m_data; QString m_label; QString m_mimeType; QString m_iconName; }; typedef QList Attachments; class Delegate { public: Delegate(); Delegate(const QString &name, const QString &email); Delegate(const Delegate &other); ~Delegate(); Delegate &operator=(const Delegate &other); bool operator==(const Delegate &other) const; bool isValid() const; QString display() const; QString name() const; void setName(const QString &name); QString email() const; void setEmail(const QString &email); private: QString m_name; QString m_email; }; explicit Task(QObject *parent = Q_NULLPTR); virtual ~Task(); bool isRunning() const; bool isDone() const; - QDateTime startDate() const; - QDateTime dueDate() const; - QDateTime doneDate() const; + QDate startDate() const; + QDate dueDate() const; + QDate doneDate() const; Recurrence recurrence() const; Attachments attachments() const; Delegate delegate() const; public slots: void setRunning(bool running); void setDone(bool done); - void setDoneDate(const QDateTime &doneDate); - void setStartDate(const QDateTime &startDate); - void setDueDate(const QDateTime &dueDate); + void setDoneDate(const QDate &doneDate); + void setStartDate(const QDate &startDate); + void setDueDate(const QDate &dueDate); void setRecurrence(Domain::Task::Recurrence recurrence); void setAttachments(const Domain::Task::Attachments &attachments); void setDelegate(const Domain::Task::Delegate &delegate); signals: void runningChanged(bool isRunning); void doneChanged(bool isDone); - void doneDateChanged(const QDateTime &doneDate); - void startDateChanged(const QDateTime &startDate); - void dueDateChanged(const QDateTime &dueDate); + void doneDateChanged(const QDate &doneDate); + void startDateChanged(const QDate &startDate); + void dueDateChanged(const QDate &dueDate); void recurrenceChanged(Domain::Task::Recurrence recurrence); void attachmentsChanged(const Domain::Task::Attachments &attachments); void delegateChanged(const Domain::Task::Delegate &delegate); private: bool m_running; bool m_done; - QDateTime m_startDate; - QDateTime m_dueDate; - QDateTime m_doneDate; + QDate m_startDate; + QDate m_dueDate; + QDate m_doneDate; Recurrence m_recurrence; Attachments m_attachments; Delegate m_delegate; }; } Q_DECLARE_METATYPE(Domain::Task::Ptr) Q_DECLARE_METATYPE(Domain::Task::List) Q_DECLARE_METATYPE(Domain::Task::Attachment) Q_DECLARE_METATYPE(Domain::Task::Attachments) Q_DECLARE_METATYPE(Domain::Task::Delegate) #endif // DOMAIN_TASK_H diff --git a/src/presentation/artifacteditormodel.cpp b/src/presentation/artifacteditormodel.cpp index 78775f0f..ba6dd283 100644 --- a/src/presentation/artifacteditormodel.cpp +++ b/src/presentation/artifacteditormodel.cpp @@ -1,509 +1,509 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "artifacteditormodel.h" #include #include #include #include #include #include #include #include #include #include "domain/task.h" #include "errorhandler.h" namespace Presentation { class AttachmentModel : public QAbstractListModel { Q_OBJECT public: explicit AttachmentModel(QObject *parent = nullptr) : QAbstractListModel(parent) { } void setTask(const Domain::Task::Ptr &task) { if (m_task == task) return; beginResetModel(); if (m_task) { disconnect(m_task.data(), &Domain::Task::attachmentsChanged, this, &AttachmentModel::triggerReset); } m_task = task; if (m_task) { connect(m_task.data(), &Domain::Task::attachmentsChanged, this, &AttachmentModel::triggerReset); } endResetModel(); } int rowCount(const QModelIndex &parent) const override { if (parent.isValid() || !m_task) return 0; return m_task->attachments().size(); } QVariant data(const QModelIndex &index, int role) const override { if (!index.isValid()) return QVariant(); auto attachment = m_task->attachments().at(index.row()); switch (role) { case Qt::DisplayRole: return attachment.label(); case Qt::DecorationRole: return QVariant::fromValue(QIcon::fromTheme(attachment.iconName())); default: return QVariant(); } } private slots: void triggerReset() { beginResetModel(); endResetModel(); } private: Domain::Task::Ptr m_task; }; } using namespace Presentation; static int s_autoSaveDelay = 500; ArtifactEditorModel::ArtifactEditorModel(QObject *parent) : QObject(parent), m_done(false), m_recurrence(Domain::Task::NoRecurrence), m_attachmentModel(new AttachmentModel(this)), m_saveTimer(new QTimer(this)), m_saveNeeded(false), m_editingInProgress(false) { m_saveTimer->setSingleShot(true); connect(m_saveTimer, &QTimer::timeout, this, &ArtifactEditorModel::save); } ArtifactEditorModel::~ArtifactEditorModel() { save(); } Domain::Artifact::Ptr ArtifactEditorModel::artifact() const { return m_artifact; } void ArtifactEditorModel::setArtifact(const Domain::Artifact::Ptr &artifact) { if (m_artifact == artifact) return; save(); m_text = QString(); m_title = QString(); m_done = false; - m_start = QDateTime(); - m_due = QDateTime(); + m_start = QDate(); + m_due = QDate(); m_recurrence = Domain::Task::NoRecurrence; m_attachmentModel->setTask(Domain::Task::Ptr()); m_delegateText = QString(); if (m_artifact) disconnect(m_artifact.data(), Q_NULLPTR, this, Q_NULLPTR); m_artifact = artifact; if (m_artifact) { m_text = m_artifact->text(); m_title = m_artifact->title(); connect(m_artifact.data(), &Domain::Artifact::textChanged, this, &ArtifactEditorModel::onTextChanged); connect(m_artifact.data(), &Domain::Artifact::titleChanged, this, &ArtifactEditorModel::onTitleChanged); } if (auto task = artifact.objectCast()) { m_done = task->isDone(); m_start = task->startDate(); m_due = task->dueDate(); m_recurrence = task->recurrence(); m_attachmentModel->setTask(task); m_delegateText = task->delegate().display(); connect(task.data(), &Domain::Task::doneChanged, this, &ArtifactEditorModel::onDoneChanged); connect(task.data(), &Domain::Task::startDateChanged, this, &ArtifactEditorModel::onStartDateChanged); connect(task.data(), &Domain::Task::dueDateChanged, this, &ArtifactEditorModel::onDueDateChanged); connect(task.data(), &Domain::Task::recurrenceChanged, this, &ArtifactEditorModel::onRecurrenceChanged); connect(task.data(), &Domain::Task::delegateChanged, this, &ArtifactEditorModel::onDelegateChanged); } emit textChanged(m_text); emit titleChanged(m_title); emit doneChanged(m_done); emit startDateChanged(m_start); emit dueDateChanged(m_due); emit recurrenceChanged(m_recurrence); emit delegateTextChanged(m_delegateText); emit hasTaskPropertiesChanged(hasTaskProperties()); emit artifactChanged(m_artifact); } bool ArtifactEditorModel::hasSaveFunction() const { return bool(m_saveFunction); } void ArtifactEditorModel::setSaveFunction(const SaveFunction &function) { m_saveFunction = function; } bool ArtifactEditorModel::hasDelegateFunction() const { return bool(m_delegateFunction); } void ArtifactEditorModel::setDelegateFunction(const DelegateFunction &function) { m_delegateFunction = function; } bool ArtifactEditorModel::hasTaskProperties() const { return m_artifact.objectCast(); } QString ArtifactEditorModel::text() const { return m_text; } QString ArtifactEditorModel::title() const { return m_title; } bool ArtifactEditorModel::isDone() const { return m_done; } -QDateTime ArtifactEditorModel::startDate() const +QDate ArtifactEditorModel::startDate() const { return m_start; } -QDateTime ArtifactEditorModel::dueDate() const +QDate ArtifactEditorModel::dueDate() const { return m_due; } Domain::Task::Recurrence ArtifactEditorModel::recurrence() const { return m_recurrence; } QAbstractItemModel *ArtifactEditorModel::attachmentModel() const { return m_attachmentModel; } QString ArtifactEditorModel::delegateText() const { return m_delegateText; } int ArtifactEditorModel::autoSaveDelay() { return s_autoSaveDelay; } void ArtifactEditorModel::setAutoSaveDelay(int delay) { s_autoSaveDelay = delay; } bool ArtifactEditorModel::editingInProgress() const { return m_editingInProgress; } void ArtifactEditorModel::setText(const QString &text) { if (m_text == text) return; applyNewText(text); setSaveNeeded(true); } void ArtifactEditorModel::setTitle(const QString &title) { if (m_title == title) return; applyNewTitle(title); setSaveNeeded(true); } void ArtifactEditorModel::setDone(bool done) { if (m_done == done) return; applyNewDone(done); setSaveNeeded(true); } -void ArtifactEditorModel::setStartDate(const QDateTime &start) +void ArtifactEditorModel::setStartDate(const QDate &start) { if (m_start == start) return; applyNewStartDate(start); setSaveNeeded(true); } -void ArtifactEditorModel::setDueDate(const QDateTime &due) +void ArtifactEditorModel::setDueDate(const QDate &due) { if (m_due == due) return; applyNewDueDate(due); setSaveNeeded(true); } void ArtifactEditorModel::setRecurrence(Domain::Task::Recurrence recurrence) { if (m_recurrence == recurrence) return; applyNewRecurrence(recurrence); setSaveNeeded(true); } void ArtifactEditorModel::delegate(const QString &name, const QString &email) { auto task = m_artifact.objectCast(); Q_ASSERT(task); auto delegate = Domain::Task::Delegate(name, email); m_delegateFunction(task, delegate); } void ArtifactEditorModel::addAttachment(const QString &fileName) { auto task = m_artifact.objectCast(); if (!task) return; QMimeDatabase mimeDb; auto mimeType = mimeDb.mimeTypeForFile(fileName); auto attachment = Domain::Task::Attachment(); attachment.setLabel(QFileInfo(fileName).fileName()); attachment.setMimeType(mimeType.name()); attachment.setIconName(mimeType.iconName()); QFile file(fileName); if (!file.open(QFile::ReadOnly)) { // TODO: Might be worth extending error handling // to deal with job-less errors later on qWarning() << "Couldn't open" << fileName; return; } attachment.setData(file.readAll()); file.close(); auto attachments = task->attachments(); attachments.append(attachment); task->setAttachments(attachments); setSaveNeeded(true); } void ArtifactEditorModel::removeAttachment(const QModelIndex &index) { auto task = m_artifact.objectCast(); if (!task) return; auto attachments = task->attachments(); attachments.removeAt(index.row()); task->setAttachments(attachments); setSaveNeeded(true); } void ArtifactEditorModel::openAttachment(const QModelIndex &index) { auto task = m_artifact.objectCast(); Q_ASSERT(task); auto attachment = task->attachments().at(index.row()); auto uri = attachment.uri(); if (!attachment.isUri()) { auto tempFile = new QTemporaryFile(QDir::tempPath() + QStringLiteral("/zanshin_attachment_XXXXXX"), this); tempFile->open(); tempFile->setPermissions(QFile::ReadUser); tempFile->write(attachment.data()); tempFile->close(); uri = QUrl::fromLocalFile(tempFile->fileName()); } QDesktopServices::openUrl(uri); } void ArtifactEditorModel::setEditingInProgress(bool editing) { m_editingInProgress = editing; } void ArtifactEditorModel::onTextChanged(const QString &text) { if (!m_editingInProgress) applyNewText(text); } void ArtifactEditorModel::onTitleChanged(const QString &title) { if (!m_editingInProgress) applyNewTitle(title); } void ArtifactEditorModel::onDoneChanged(bool done) { if (!m_editingInProgress) applyNewDone(done); } -void ArtifactEditorModel::onStartDateChanged(const QDateTime &start) +void ArtifactEditorModel::onStartDateChanged(const QDate &start) { if (!m_editingInProgress) applyNewStartDate(start); } -void ArtifactEditorModel::onDueDateChanged(const QDateTime &due) +void ArtifactEditorModel::onDueDateChanged(const QDate &due) { if (!m_editingInProgress) applyNewDueDate(due); } void ArtifactEditorModel::onRecurrenceChanged(Domain::Task::Recurrence recurrence) { if (!m_editingInProgress) applyNewRecurrence(recurrence); } void ArtifactEditorModel::onDelegateChanged(const Domain::Task::Delegate &delegate) { m_delegateText = delegate.display(); emit delegateTextChanged(m_delegateText); } void ArtifactEditorModel::save() { if (!isSaveNeeded()) return; Q_ASSERT(m_artifact); const auto currentTitle = m_artifact->title(); m_artifact->setTitle(m_title); m_artifact->setText(m_text); if (auto task = m_artifact.objectCast()) { task->setDone(m_done); task->setStartDate(m_start); task->setDueDate(m_due); task->setRecurrence(m_recurrence); } const auto job = m_saveFunction(m_artifact); installHandler(job, i18n("Cannot modify task %1", currentTitle)); setSaveNeeded(false); } void ArtifactEditorModel::setSaveNeeded(bool needed) { if (needed) m_saveTimer->start(autoSaveDelay()); else m_saveTimer->stop(); m_saveNeeded = needed; } bool ArtifactEditorModel::isSaveNeeded() const { return m_saveNeeded; } void ArtifactEditorModel::applyNewText(const QString &text) { m_text = text; emit textChanged(m_text); } void ArtifactEditorModel::applyNewTitle(const QString &title) { m_title = title; emit titleChanged(m_title); } void ArtifactEditorModel::applyNewDone(bool done) { m_done = done; emit doneChanged(m_done); } -void ArtifactEditorModel::applyNewStartDate(const QDateTime &start) +void ArtifactEditorModel::applyNewStartDate(const QDate &start) { m_start = start; emit startDateChanged(m_start); } -void ArtifactEditorModel::applyNewDueDate(const QDateTime &due) +void ArtifactEditorModel::applyNewDueDate(const QDate &due) { m_due = due; emit dueDateChanged(m_due); } void ArtifactEditorModel::applyNewRecurrence(Domain::Task::Recurrence recurrence) { m_recurrence = recurrence; emit recurrenceChanged(m_recurrence); } #include "artifacteditormodel.moc" diff --git a/src/presentation/artifacteditormodel.h b/src/presentation/artifacteditormodel.h index 764f6045..68be52f8 100644 --- a/src/presentation/artifacteditormodel.h +++ b/src/presentation/artifacteditormodel.h @@ -1,158 +1,158 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef PRESENTATION_ARTIFACTEDITORMODEL_H #define PRESENTATION_ARTIFACTEDITORMODEL_H -#include +#include #include #include #include "domain/task.h" #include "presentation/errorhandlingmodelbase.h" class QAbstractItemModel; class QTimer; namespace Presentation { class AttachmentModel; class ArtifactEditorModel : public QObject, public ErrorHandlingModelBase { Q_OBJECT Q_PROPERTY(Domain::Artifact::Ptr artifact READ artifact WRITE setArtifact NOTIFY artifactChanged) Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged) Q_PROPERTY(QString title READ title WRITE setTitle NOTIFY titleChanged) Q_PROPERTY(bool done READ isDone WRITE setDone NOTIFY doneChanged) - Q_PROPERTY(QDateTime startDate READ startDate WRITE setStartDate NOTIFY startDateChanged) - Q_PROPERTY(QDateTime dueDate READ dueDate WRITE setDueDate NOTIFY dueDateChanged) + Q_PROPERTY(QDate startDate READ startDate WRITE setStartDate NOTIFY startDateChanged) + Q_PROPERTY(QDate dueDate READ dueDate WRITE setDueDate NOTIFY dueDateChanged) Q_PROPERTY(Domain::Task::Recurrence recurrence READ recurrence WRITE setRecurrence NOTIFY recurrenceChanged) Q_PROPERTY(QAbstractItemModel* attachmentModel READ attachmentModel CONSTANT) Q_PROPERTY(QString delegateText READ delegateText NOTIFY delegateTextChanged) Q_PROPERTY(bool hasTaskProperties READ hasTaskProperties NOTIFY hasTaskPropertiesChanged) Q_PROPERTY(bool editingInProgress READ editingInProgress WRITE setEditingInProgress) public: typedef std::function SaveFunction; typedef std::function DelegateFunction; explicit ArtifactEditorModel(QObject *parent = Q_NULLPTR); ~ArtifactEditorModel(); Domain::Artifact::Ptr artifact() const; void setArtifact(const Domain::Artifact::Ptr &artifact); bool hasSaveFunction() const; void setSaveFunction(const SaveFunction &function); bool hasDelegateFunction() const; void setDelegateFunction(const DelegateFunction &function); bool hasTaskProperties() const; QString text() const; QString title() const; bool isDone() const; - QDateTime startDate() const; - QDateTime dueDate() const; + QDate startDate() const; + QDate dueDate() const; Domain::Task::Recurrence recurrence() const; QAbstractItemModel *attachmentModel() const; QString delegateText() const; static int autoSaveDelay(); static void setAutoSaveDelay(int delay); bool editingInProgress() const; public slots: void setText(const QString &text); void setTitle(const QString &title); void setDone(bool done); - void setStartDate(const QDateTime &start); - void setDueDate(const QDateTime &due); + void setStartDate(const QDate &start); + void setDueDate(const QDate &due); void setRecurrence(Domain::Task::Recurrence recurrence); void delegate(const QString &name, const QString &email); void addAttachment(const QString &fileName); void removeAttachment(const QModelIndex &index); void openAttachment(const QModelIndex &index); void setEditingInProgress(bool editingInProgress); signals: void artifactChanged(const Domain::Artifact::Ptr &artifact); void hasTaskPropertiesChanged(bool hasTaskProperties); void textChanged(const QString &text); void titleChanged(const QString &title); void doneChanged(bool done); - void startDateChanged(const QDateTime &date); - void dueDateChanged(const QDateTime &due); + void startDateChanged(const QDate &date); + void dueDateChanged(const QDate &due); void recurrenceChanged(Domain::Task::Recurrence recurrence); void delegateTextChanged(const QString &delegateText); private slots: void onTextChanged(const QString &text); void onTitleChanged(const QString &title); void onDoneChanged(bool done); - void onStartDateChanged(const QDateTime &start); - void onDueDateChanged(const QDateTime &due); + void onStartDateChanged(const QDate &start); + void onDueDateChanged(const QDate &due); void onRecurrenceChanged(Domain::Task::Recurrence recurrence); void onDelegateChanged(const Domain::Task::Delegate &delegate); void save(); private: void setSaveNeeded(bool needed); bool isSaveNeeded() const; void applyNewText(const QString &text); void applyNewTitle(const QString &title); void applyNewDone(bool done); - void applyNewStartDate(const QDateTime &start); - void applyNewDueDate(const QDateTime &due); + void applyNewStartDate(const QDate &start); + void applyNewDueDate(const QDate &due); void applyNewRecurrence(Domain::Task::Recurrence recurrence); Domain::Artifact::Ptr m_artifact; SaveFunction m_saveFunction; DelegateFunction m_delegateFunction; QString m_text; QString m_title; bool m_done; - QDateTime m_start; - QDateTime m_due; + QDate m_start; + QDate m_due; Domain::Task::Recurrence m_recurrence; AttachmentModel *m_attachmentModel; QString m_delegateText; QTimer *m_saveTimer; bool m_saveNeeded; bool m_editingInProgress; }; } #endif // PRESENTATION_ARTIFACTEDITORMODEL_H diff --git a/src/presentation/artifactfilterproxymodel.cpp b/src/presentation/artifactfilterproxymodel.cpp index b419e6d7..c6b6a054 100644 --- a/src/presentation/artifactfilterproxymodel.cpp +++ b/src/presentation/artifactfilterproxymodel.cpp @@ -1,137 +1,140 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "artifactfilterproxymodel.h" #include #include "domain/artifact.h" #include "domain/task.h" +#include "utils/datetime.h" #include "presentation/querytreemodelbase.h" using namespace Presentation; ArtifactFilterProxyModel::ArtifactFilterProxyModel(QObject *parent) : QSortFilterProxyModel(parent), m_sortType(TitleSort), m_showFuture(false) { setDynamicSortFilter(true); setSortCaseSensitivity(Qt::CaseInsensitive); setSortOrder(Qt::AscendingOrder); } ArtifactFilterProxyModel::SortType ArtifactFilterProxyModel::sortType() const { return m_sortType; } void ArtifactFilterProxyModel::setSortType(ArtifactFilterProxyModel::SortType type) { m_sortType = type; invalidate(); } void ArtifactFilterProxyModel::setSortOrder(Qt::SortOrder order) { sort(0, order); } bool ArtifactFilterProxyModel::showFutureTasks() const { return m_showFuture; } void ArtifactFilterProxyModel::setShowFutureTasks(bool show) { if (m_showFuture == show) return; m_showFuture = show; invalidate(); } static bool isFutureTask(const Domain::Artifact::Ptr &artifact) { auto task = artifact.objectCast(); if (!task) return false; if (!task->startDate().isValid()) return false; - return task->startDate() > QDateTime::currentDateTime(); + return task->startDate() > Utils::DateTime::currentDate(); } bool ArtifactFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { const QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent); const auto artifact = index.data(QueryTreeModelBase::ObjectRole).value(); if (artifact) { QRegExp regexp = filterRegExp(); regexp.setCaseSensitivity(Qt::CaseInsensitive); if (artifact->title().contains(regexp) || artifact->text().contains(regexp)) { return m_showFuture || !isFutureTask(artifact); } } for (int childRow = 0; childRow < sourceModel()->rowCount(index); childRow++) { if (filterAcceptsRow(childRow, index)) return true; } return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent); } -static QDateTime validDt(const QDateTime &date = QDateTime()) +static QDate validDt(const QDate &date = QDate()) { if (date.isValid()) return date; - return QDateTime::fromTime_t(std::numeric_limits::max() - 1); + return QDate(10000, 12, 31); } bool ArtifactFilterProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { if (m_sortType != DateSort) return QSortFilterProxyModel::lessThan(left, right); const auto leftArtifact = left.data(QueryTreeModelBase::ObjectRole).value(); const auto rightArtifact = right.data(QueryTreeModelBase::ObjectRole).value(); const auto leftTask = leftArtifact.objectCast(); const auto rightTask = rightArtifact.objectCast(); - const QDateTime leftDue = leftTask ? validDt(leftTask->dueDate()) : validDt().addSecs(1); - const QDateTime rightDue = rightTask ? validDt(rightTask->dueDate()) : validDt().addSecs(1); + // The addDays(1) is so that we sort non-tasks (e.g. notes) at the end - const QDateTime leftStart = leftTask ? validDt(leftTask->startDate()) : validDt().addSecs(1); - const QDateTime rightStart = rightTask ? validDt(rightTask->startDate()) : validDt().addSecs(1); + const QDate leftDue = leftTask ? validDt(leftTask->dueDate()) : validDt().addDays(1); + const QDate rightDue = rightTask ? validDt(rightTask->dueDate()) : validDt().addDays(1); + + const QDate leftStart = leftTask ? validDt(leftTask->startDate()) : validDt().addDays(1); + const QDate rightStart = rightTask ? validDt(rightTask->startDate()) : validDt().addDays(1); return leftDue < rightDue || leftStart < rightStart; } diff --git a/src/presentation/availabletaskpagesmodel.cpp b/src/presentation/availabletaskpagesmodel.cpp index 5da3bff7..8f3adb5d 100644 --- a/src/presentation/availabletaskpagesmodel.cpp +++ b/src/presentation/availabletaskpagesmodel.cpp @@ -1,350 +1,350 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "availabletaskpagesmodel.h" #include #include #include #include "domain/contextqueries.h" #include "domain/contextrepository.h" #include "domain/projectqueries.h" #include "domain/projectrepository.h" #include "domain/taskrepository.h" #include "presentation/availablepagessortfilterproxymodel.h" #include "presentation/contextpagemodel.h" #include "presentation/metatypes.h" #include "presentation/projectpagemodel.h" #include "presentation/querytreemodel.h" #include "presentation/taskinboxpagemodel.h" #include "presentation/workdaypagemodel.h" #include "utils/jobhandler.h" #include "utils/datetime.h" using namespace Presentation; AvailableTaskPagesModel::AvailableTaskPagesModel(const Domain::DataSourceQueries::Ptr &dataSourceQueries, const Domain::ProjectQueries::Ptr &projectQueries, const Domain::ProjectRepository::Ptr &projectRepository, const Domain::ContextQueries::Ptr &contextQueries, const Domain::ContextRepository::Ptr &contextRepository, const Domain::TaskQueries::Ptr &taskQueries, const Domain::TaskRepository::Ptr &taskRepository, QObject *parent) : AvailablePagesModelInterface(parent), m_pageListModel(Q_NULLPTR), m_sortProxyModel(Q_NULLPTR), m_dataSourceQueries(dataSourceQueries), m_projectQueries(projectQueries), m_projectRepository(projectRepository), m_contextQueries(contextQueries), m_contextRepository(contextRepository), m_taskQueries(taskQueries), m_taskRepository(taskRepository) { } QAbstractItemModel *AvailableTaskPagesModel::pageListModel() { if (!m_pageListModel) m_pageListModel = createPageListModel(); if (!m_sortProxyModel) { m_sortProxyModel = new AvailablePagesSortFilterProxyModel(this); m_sortProxyModel->setSourceModel(m_pageListModel); } return m_sortProxyModel; } bool AvailableTaskPagesModel::hasProjectPages() const { return true; } bool AvailableTaskPagesModel::hasContextPages() const { return true; } bool AvailableTaskPagesModel::hasTagPages() const { return false; } QObject *AvailableTaskPagesModel::createPageForIndex(const QModelIndex &index) { QObjectPtr object = index.data(QueryTreeModelBase::ObjectRole).value(); if (object == m_inboxObject) { auto inboxPageModel = new TaskInboxPageModel(m_taskQueries, m_taskRepository, this); inboxPageModel->setErrorHandler(errorHandler()); return inboxPageModel; } else if (object == m_workdayObject) { auto workdayPageModel = new WorkdayPageModel(m_taskQueries, m_taskRepository, this); workdayPageModel->setErrorHandler(errorHandler()); return workdayPageModel; } else if (auto project = object.objectCast()) { auto projectPageModel = new ProjectPageModel(project, m_projectQueries, m_projectRepository, m_taskQueries, m_taskRepository, this); projectPageModel->setErrorHandler(errorHandler()); return projectPageModel; } else if (auto context = object.objectCast()) { auto contextPageModel = new ContextPageModel(context, m_contextQueries, m_contextRepository, m_taskQueries, m_taskRepository, this); contextPageModel->setErrorHandler(errorHandler()); return contextPageModel; } return Q_NULLPTR; } void AvailableTaskPagesModel::addProject(const QString &name, const Domain::DataSource::Ptr &source) { auto project = Domain::Project::Ptr::create(); project->setName(name); const auto job = m_projectRepository->create(project, source); installHandler(job, i18n("Cannot add project %1 in dataSource %2", name, source->name())); } void AvailableTaskPagesModel::addContext(const QString &name) { auto context = Domain::Context::Ptr::create(); context->setName(name); const auto job = m_contextRepository->create(context); installHandler(job, i18n("Cannot add context %1", name)); } void AvailableTaskPagesModel::addTag(const QString &) { qFatal("Not supported"); } void AvailableTaskPagesModel::removeItem(const QModelIndex &index) { QObjectPtr object = index.data(QueryTreeModelBase::ObjectRole).value(); if (auto project = object.objectCast()) { const auto job = m_projectRepository->remove(project); installHandler(job, i18n("Cannot remove project %1", project->name())); } else if (auto context = object.objectCast()) { const auto job = m_contextRepository->remove(context); installHandler(job, i18n("Cannot remove context %1", context->name())); } else { Q_ASSERT(false); } } QAbstractItemModel *AvailableTaskPagesModel::createPageListModel() { m_inboxObject = QObjectPtr::create(); m_inboxObject->setProperty("name", i18n("Inbox")); m_workdayObject = QObjectPtr::create(); m_workdayObject->setProperty("name", i18n("Workday")); m_projectsObject = QObjectPtr::create(); m_projectsObject->setProperty("name", i18n("Projects")); m_contextsObject = QObjectPtr::create(); m_contextsObject->setProperty("name", i18n("Contexts")); m_rootsProvider = Domain::QueryResultProvider::Ptr::create(); m_rootsProvider->append(m_inboxObject); m_rootsProvider->append(m_workdayObject); m_rootsProvider->append(m_projectsObject); m_rootsProvider->append(m_contextsObject); auto query = [this](const QObjectPtr &object) -> Domain::QueryResultInterface::Ptr { if (!object) return Domain::QueryResult::create(m_rootsProvider); else if (object == m_projectsObject) return Domain::QueryResult::copy(m_dataSourceQueries->findAllSelected()); else if (object == m_contextsObject) return Domain::QueryResult::copy(m_contextQueries->findAll()); else if (const auto source = object.objectCast()) return Domain::QueryResult::copy(m_dataSourceQueries->findProjects(source)); else return Domain::QueryResult::Ptr(); }; auto flags = [this](const QObjectPtr &object) { const Qt::ItemFlags defaultFlags = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsDropEnabled; const Qt::ItemFlags immutableNodeFlags = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDropEnabled; const Qt::ItemFlags structureNodeFlags = Qt::NoItemFlags; return object.objectCast() ? defaultFlags : object.objectCast() ? defaultFlags : object == m_inboxObject ? immutableNodeFlags : object == m_workdayObject ? immutableNodeFlags : structureNodeFlags; }; auto data = [this](const QObjectPtr &object, int role) -> QVariant { if (role != Qt::DisplayRole && role != Qt::EditRole && role != Qt::DecorationRole && role != QueryTreeModelBase::IconNameRole) { return QVariant(); } if (role == Qt::EditRole && (object == m_inboxObject || object == m_workdayObject || object == m_projectsObject || object == m_contextsObject || object.objectCast())) { return QVariant(); } if (role == Qt::DisplayRole || role == Qt::EditRole) { return object->property("name").toString(); } else if (role == Qt::DecorationRole || role == QueryTreeModelBase::IconNameRole) { const QString iconName = object == m_inboxObject ? QStringLiteral("mail-folder-inbox") : (object == m_workdayObject) ? QStringLiteral("go-jump-today") : (object == m_projectsObject) ? QStringLiteral("folder") : (object == m_contextsObject) ? QStringLiteral("folder") : object.objectCast() ? QStringLiteral("folder") : object.objectCast() ? QStringLiteral("view-pim-notes") : QStringLiteral("view-pim-tasks"); if (role == Qt::DecorationRole) return QVariant::fromValue(QIcon::fromTheme(iconName)); else return iconName; } else { return QVariant(); } }; auto setData = [this](const QObjectPtr &object, const QVariant &value, int role) { if (role != Qt::EditRole) { return false; } if (object == m_inboxObject || object == m_workdayObject || object == m_projectsObject || object == m_contextsObject || object.objectCast()) { return false; } if (auto project = object.objectCast()) { const auto currentName = project->name(); project->setName(value.toString()); const auto job = m_projectRepository->update(project); installHandler(job, i18n("Cannot modify project %1", currentName)); } else if (auto context = object.objectCast()) { const auto currentName = context->name(); context->setName(value.toString()); const auto job = m_contextRepository->update(context); installHandler(job, i18n("Cannot modify context %1", currentName)); } else { Q_ASSERT(false); } return true; }; auto drop = [this](const QMimeData *mimeData, Qt::DropAction, const QObjectPtr &object) { if (!mimeData->hasFormat(QStringLiteral("application/x-zanshin-object"))) return false; auto droppedArtifacts = mimeData->property("objects").value(); if (droppedArtifacts.isEmpty()) return false; if (auto project = object.objectCast()) { foreach (const auto &droppedArtifact, droppedArtifacts) { const auto job = m_projectRepository->associate(project, droppedArtifact); installHandler(job, i18n("Cannot add %1 to project %2", droppedArtifact->title(), project->name())); } return true; } else if (auto context = object.objectCast()) { if (std::any_of(droppedArtifacts.begin(), droppedArtifacts.end(), [](const Domain::Artifact::Ptr &droppedArtifact) { return !droppedArtifact.objectCast(); })) { return false; } foreach (const auto &droppedArtifact, droppedArtifacts) { auto task = droppedArtifact.staticCast(); const auto job = m_contextRepository->associate(context, task); installHandler(job, i18n("Cannot add %1 to context %2", task->title(), context->name())); } return true; } else if (object == m_inboxObject) { foreach (const auto &droppedArtifact, droppedArtifacts) { const auto job = m_projectRepository->dissociate(droppedArtifact); installHandler(job, i18n("Cannot move %1 to Inbox", droppedArtifact->title())); if (auto task = droppedArtifact.objectCast()) { Utils::JobHandler::install(job, [this, task] { const auto dissociateJob = m_taskRepository->dissociateAll(task); installHandler(dissociateJob, i18n("Cannot move task %1 to Inbox", task->title())); }); } } return true; } else if (object == m_workdayObject) { foreach (const auto &droppedArtifact, droppedArtifacts) { if (auto task = droppedArtifact.objectCast()) { - task->setStartDate(Utils::DateTime::currentDateTime()); + task->setStartDate(Utils::DateTime::currentDate()); const auto job = m_taskRepository->update(task); installHandler(job, i18n("Cannot update task %1 to Workday", task->title())); } } return true; } return false; }; auto drag = [](const QObjectPtrList &) -> QMimeData* { return Q_NULLPTR; }; return new QueryTreeModel(query, flags, data, setData, drop, drag, this); } diff --git a/src/presentation/workdaypagemodel.cpp b/src/presentation/workdaypagemodel.cpp index fe564b42..be12515b 100644 --- a/src/presentation/workdaypagemodel.cpp +++ b/src/presentation/workdaypagemodel.cpp @@ -1,189 +1,189 @@ /* This file is part of Zanshin Copyright 2015 Theo Vaucher This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "workdaypagemodel.h" #include #include #include "domain/noterepository.h" #include "domain/taskqueries.h" #include "domain/taskrepository.h" #include "presentation/querytreemodel.h" #include "utils/datetime.h" using namespace Presentation; WorkdayPageModel::WorkdayPageModel(const Domain::TaskQueries::Ptr &taskQueries, const Domain::TaskRepository::Ptr &taskRepository, QObject *parent) : PageModel(parent), m_taskQueries(taskQueries), m_taskRepository(taskRepository) { } Domain::Artifact::Ptr WorkdayPageModel::addItem(const QString &title, const QModelIndex &parentIndex) { const auto parentData = parentIndex.data(QueryTreeModel::ObjectRole); const auto parentArtifact = parentData.value(); const auto parentTask = parentArtifact.objectCast(); auto task = Domain::Task::Ptr::create(); task->setTitle(title); if (!parentTask) - task->setStartDate(Utils::DateTime::currentDateTime()); + task->setStartDate(Utils::DateTime::currentDate()); const auto job = parentTask ? m_taskRepository->createChild(task, parentTask) : m_taskRepository->create(task); installHandler(job, i18n("Cannot add task %1 in Workday", title)); return task; } void WorkdayPageModel::removeItem(const QModelIndex &index) { QVariant data = index.data(QueryTreeModel::ObjectRole); auto artifact = data.value(); auto task = artifact.objectCast(); if (task) { const auto job = m_taskRepository->remove(task); installHandler(job, i18n("Cannot remove task %1 from Workday", task->title())); } } void WorkdayPageModel::promoteItem(const QModelIndex &index) { QVariant data = index.data(QueryTreeModel::ObjectRole); auto artifact = data.value(); auto task = artifact.objectCast(); Q_ASSERT(task); const auto job = m_taskRepository->promoteToProject(task); installHandler(job, i18n("Cannot promote task %1 to be a project", task->title())); } QAbstractItemModel *WorkdayPageModel::createCentralListModel() { auto query = [this](const Domain::Artifact::Ptr &artifact) -> Domain::QueryResultInterface::Ptr { if (!artifact) return Domain::QueryResult::copy(m_taskQueries->findWorkdayTopLevel()); else if (auto task = artifact.dynamicCast()) return Domain::QueryResult::copy(m_taskQueries->findChildren(task)); else return Domain::QueryResult::Ptr(); }; auto flags = [](const Domain::Artifact::Ptr &artifact) { const Qt::ItemFlags defaultFlags = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsDragEnabled; return artifact.dynamicCast() ? (defaultFlags | Qt::ItemIsUserCheckable | Qt::ItemIsDropEnabled) : defaultFlags; }; auto data = [](const Domain::Artifact::Ptr &artifact, int role) -> QVariant { if (role != Qt::DisplayRole && role != Qt::EditRole && role != Qt::CheckStateRole) { return QVariant(); } if (role == Qt::DisplayRole || role == Qt::EditRole) { return artifact->title(); } else if (auto task = artifact.dynamicCast()) { return task->isDone() ? Qt::Checked : Qt::Unchecked; } else { return QVariant(); } }; auto setData = [this](const Domain::Artifact::Ptr &artifact, const QVariant &value, int role) { if (role != Qt::EditRole && role != Qt::CheckStateRole) { return false; } if (auto task = artifact.dynamicCast()) { const auto currentTitle = task->title(); if (role == Qt::EditRole) task->setTitle(value.toString()); else task->setDone(value.toInt() == Qt::Checked); const auto job = m_taskRepository->update(task); installHandler(job, i18n("Cannot modify task %1 in Workday", currentTitle)); return true; } return false; }; auto drop = [this](const QMimeData *mimeData, Qt::DropAction, const Domain::Artifact::Ptr &artifact) { auto parentTask = artifact.objectCast(); if (!mimeData->hasFormat(QStringLiteral("application/x-zanshin-object"))) return false; auto droppedArtifacts = mimeData->property("objects").value(); if (droppedArtifacts.isEmpty()) return false; if (std::any_of(droppedArtifacts.begin(), droppedArtifacts.end(), [](const Domain::Artifact::Ptr &droppedArtifact) { return !droppedArtifact.objectCast(); })) { return false; } foreach(const auto &droppedArtifact, droppedArtifacts) { auto childTask = droppedArtifact.objectCast(); if (parentTask) { const auto job = m_taskRepository->associate(parentTask, childTask); installHandler(job, i18n("Cannot move task %1 as sub-task of %2", childTask->title(), parentTask->title())); } else { - childTask->setStartDate(Utils::DateTime::currentDateTime()); + childTask->setStartDate(Utils::DateTime::currentDate()); auto job = m_taskRepository->dissociate(childTask); installHandler(job, i18n("Cannot deparent task %1 from its parent", childTask->title())); } } return true; }; auto drag = [](const Domain::Artifact::List &artifacts) -> QMimeData* { if (artifacts.isEmpty()) return Q_NULLPTR; auto data = new QMimeData; data->setData(QStringLiteral("application/x-zanshin-object"), "object"); data->setProperty("objects", QVariant::fromValue(artifacts)); return data; }; return new QueryTreeModel(query, flags, data, setData, drop, drag, this); } diff --git a/src/widgets/itemdelegate.cpp b/src/widgets/itemdelegate.cpp index b0fa49bd..d8d38614 100644 --- a/src/widgets/itemdelegate.cpp +++ b/src/widgets/itemdelegate.cpp @@ -1,151 +1,153 @@ /* This file is part of Zanshin Copyright 2014-2016 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "itemdelegate.h" #include #include #include #include #include "domain/note.h" #include "domain/task.h" #include "presentation/querytreemodelbase.h" +#include "utils/datetime.h" using namespace Widgets; ItemDelegate::ItemDelegate(QObject *parent) : QStyledItemDelegate(parent) { } QSize ItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { // Make sure they all get the height needed for a check indicator QStyleOptionViewItem opt = option; initStyleOption(&opt, index); opt.features = QStyleOptionViewItem::HasCheckIndicator; opt.text += ' ' + QLocale().dateFormat(QLocale::ShortFormat).toUpper() + ' '; return QStyledItemDelegate::sizeHint(opt, index); } void ItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { const auto data = index.data(Presentation::QueryTreeModelBase::ObjectRole); auto task = Domain::Task::Ptr(); auto artifact = data.value(); if (artifact) { task = artifact.dynamicCast(); } else { task = data.value(); auto note = data.value(); artifact = task ? task.staticCast() : note.staticCast(); } auto opt = QStyleOptionViewItem(option); initStyleOption(&opt, index); const auto widget = opt.widget; const auto style = widget ? widget->style() : QApplication::style(); const auto isDone = task ? task->isDone() : false; const auto isEnabled = (opt.state & QStyle::State_Enabled); const auto isActive = (opt.state & QStyle::State_Active); const auto isSelected = (opt.state & QStyle::State_Selected); const auto isEditing = (opt.state & QStyle::State_Editing); - const auto startDate = task ? task->startDate() : QDateTime(); - const auto dueDate = task ? task->dueDate() : QDateTime(); + const auto startDate = task ? task->startDate() : QDate(); + const auto dueDate = task ? task->dueDate() : QDate(); - const auto onStartDate = startDate.isValid() && startDate.date() <= QDate::currentDate(); - const auto pastDueDate = dueDate.isValid() && dueDate.date() < QDate::currentDate(); - const auto onDueDate = dueDate.isValid() && dueDate.date() == QDate::currentDate(); + const auto currentDate = Utils::DateTime::currentDate(); + const auto onStartDate = startDate.isValid() && startDate <= currentDate; + const auto pastDueDate = dueDate.isValid() && dueDate < currentDate; + const auto onDueDate = dueDate.isValid() && dueDate == currentDate; const auto taskDelegate = task ? task->delegate() : Domain::Task::Delegate(); const auto baseFont = opt.font; const auto summaryFont = [=] { auto font = baseFont; font.setStrikeOut(isDone); font.setBold(!isDone && (onStartDate || onDueDate || pastDueDate)); font.setItalic(taskDelegate.isValid()); return font; }(); const auto summaryMetrics = QFontMetrics(summaryFont); const auto colorGroup = (isEnabled && !isActive) ? QPalette::Inactive : isEnabled ? QPalette::Normal : QPalette::Disabled; const auto colorRole = (isSelected && !isEditing) ? QPalette::HighlightedText : QPalette::Text; const auto baseColor = opt.palette.color(colorGroup, colorRole); const auto summaryColor = isDone ? baseColor : pastDueDate ? QColor(Qt::red) : onDueDate ? QColor("orange") : baseColor; const auto summaryText = taskDelegate.isValid() ? i18n("(%1) %2", taskDelegate.display(), opt.text) : opt.text; - const auto dueDateText = dueDate.isValid() ? QLocale().toString(dueDate.date(), QLocale::ShortFormat) + const auto dueDateText = dueDate.isValid() ? QLocale().toString(dueDate, QLocale::ShortFormat) : QString(); const auto textMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin, 0, widget) + 1; const auto dueDateWidth = dueDate.isValid() ? (summaryMetrics.width(dueDateText) + 2 * textMargin) : 0; const auto checkRect = style->subElementRect(QStyle::SE_ItemViewItemCheckIndicator, &opt, widget); const auto summaryRect = style->subElementRect(QStyle::SE_ItemViewItemText, &opt, widget) .adjusted(textMargin, 0, -dueDateWidth - textMargin, 0); const auto dueDateRect = opt.rect.adjusted(opt.rect.width() - dueDateWidth, 0, 0, 0); // Draw background style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, widget); // Draw the check box if (task) { auto checkOption = opt; checkOption.rect = checkRect; checkOption.state = option.state & ~QStyle::State_HasFocus; checkOption.state |= isDone ? QStyle::State_On : QStyle::State_Off; style->drawPrimitive(QStyle::PE_IndicatorViewItemCheck, &checkOption, painter, widget); } // Draw the summary if (!summaryText.isEmpty()) { painter->setPen(summaryColor); painter->setFont(summaryFont); painter->drawText(summaryRect, Qt::AlignVCenter, summaryMetrics.elidedText(summaryText, Qt::ElideRight, summaryRect.width())); } // Draw the due date if (!dueDateText.isEmpty()) { painter->drawText(dueDateRect, Qt::AlignCenter, dueDateText); } } diff --git a/src/widgets/pageview.cpp b/src/widgets/pageview.cpp index 5fb3aad8..14f8bb93 100644 --- a/src/widgets/pageview.cpp +++ b/src/widgets/pageview.cpp @@ -1,487 +1,488 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "pageview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "filterwidget.h" #include "itemdelegate.h" #include "messagebox.h" #include #include "presentation/artifactfilterproxymodel.h" #include "presentation/metatypes.h" #include "presentation/querytreemodelbase.h" #include "presentation/runningtaskmodelinterface.h" +#include "utils/datetime.h" namespace Widgets { class PageTreeView : public QTreeView { Q_OBJECT public: using QTreeView::QTreeView; protected: void keyPressEvent(QKeyEvent *event) Q_DECL_OVERRIDE { if (event->key() == Qt::Key_Escape && state() != EditingState) { selectionModel()->clear(); } QTreeView::keyPressEvent(event); } void resizeEvent(QResizeEvent *event) Q_DECL_OVERRIDE { header()->resizeSection(0, event->size().width()); QTreeView::resizeEvent(event); } }; } class PassivePopup : public QFrame { Q_OBJECT public: explicit PassivePopup(QWidget *parent = Q_NULLPTR) : QFrame(parent), m_hideTimer(new QTimer(this)), m_label(new QLabel(this)) { setWindowFlags(Qt::Tool | Qt::X11BypassWindowManagerHint | Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint); setFrameStyle(QFrame::Box | QFrame::Plain); setLineWidth(2); setAttribute(Qt::WA_DeleteOnClose); setLayout(new QVBoxLayout); layout()->addWidget(m_label); connect(m_hideTimer, &QTimer::timeout, this, &QWidget::hide); } void setVisible(bool visible) Q_DECL_OVERRIDE { if (visible) { m_hideTimer->start(2000); } QFrame::setVisible(visible); } void setText(const QString &text) { m_label->setText(text); } private: QTimer *m_hideTimer; QLabel *m_label; }; using namespace Widgets; PageView::PageView(QWidget *parent) : QWidget(parent), m_cancelAction(new QAction(this)), m_model(Q_NULLPTR), m_messageWidget(new KMessageWidget(this)), m_filterWidget(new FilterWidget(this)), m_centralView(new PageTreeView(this)), m_quickAddEdit(new QLineEdit(this)) { m_messageWidget->setObjectName(QStringLiteral("messageWidget")); m_messageWidget->setCloseButtonVisible(true); m_messageWidget->setMessageType(KMessageWidget::Error); m_messageWidget->setWordWrap(true); m_messageWidget->hide(); m_filterWidget->setObjectName(QStringLiteral("filterWidget")); m_filterWidget->hide(); m_centralView->setObjectName(QStringLiteral("centralView")); m_centralView->header()->hide(); m_centralView->setAlternatingRowColors(true); m_centralView->setItemDelegate(new ItemDelegate(this)); m_centralView->setDragDropMode(QTreeView::DragDrop); m_centralView->setSelectionMode(QAbstractItemView::ExtendedSelection); m_centralView->setModel(m_filterWidget->proxyModel()); m_centralView->installEventFilter(this); m_centralView->setItemsExpandable(false); m_centralView->setRootIsDecorated(false); connect(m_centralView->model(), &QAbstractItemModel::rowsInserted, m_centralView, &QTreeView::expandAll); connect(m_centralView->model(), &QAbstractItemModel::layoutChanged, m_centralView, &QTreeView::expandAll); connect(m_centralView->model(), &QAbstractItemModel::modelReset, m_centralView, &QTreeView::expandAll); m_centralView->setStyleSheet(QStringLiteral("QTreeView::branch { border-image: url(none.png); }")); m_quickAddEdit->setObjectName(QStringLiteral("quickAddEdit")); m_quickAddEdit->setPlaceholderText(i18n("Type and press enter to add an item")); connect(m_quickAddEdit, &QLineEdit::returnPressed, this, &PageView::onReturnPressed); auto layout = new QVBoxLayout; layout->setContentsMargins(0, 0, 0, 3); layout->addWidget(m_messageWidget); layout->addWidget(m_filterWidget); layout->addWidget(m_centralView); layout->addWidget(m_quickAddEdit); setLayout(layout); m_messageBoxInterface = MessageBox::Ptr::create(); auto addItemAction = new QAction(this); addItemAction->setObjectName(QStringLiteral("addItemAction")); addItemAction->setText(i18n("New Item")); addItemAction->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); addItemAction->setShortcut(Qt::CTRL | Qt::Key_N); connect(addItemAction, &QAction::triggered, this, &PageView::onAddItemRequested); m_cancelAction->setObjectName(QStringLiteral("cancelAddItemAction")); m_cancelAction->setShortcut(Qt::Key_Escape); addAction(m_cancelAction); connect(m_cancelAction, &QAction::triggered, m_centralView, static_cast(&QWidget::setFocus)); auto removeItemAction = new QAction(this); removeItemAction->setObjectName(QStringLiteral("removeItemAction")); removeItemAction->setText(i18n("Remove Item")); removeItemAction->setIcon(QIcon::fromTheme(QStringLiteral("list-remove"))); removeItemAction->setShortcut(Qt::Key_Delete); connect(removeItemAction, &QAction::triggered, this, &PageView::onRemoveItemRequested); addAction(removeItemAction); auto promoteItemAction = new QAction(this); promoteItemAction->setObjectName(QStringLiteral("promoteItemAction")); promoteItemAction->setText(i18n("Promote Item as Project")); promoteItemAction->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_P); connect(promoteItemAction, &QAction::triggered, this, &PageView::onPromoteItemRequested); auto filterViewAction = new QAction(this); filterViewAction->setObjectName(QStringLiteral("filterViewAction")); filterViewAction->setText(i18n("Filter...")); filterViewAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-find"))); filterViewAction->setShortcut(Qt::CTRL | Qt::Key_F); filterViewAction->setCheckable(true); connect(filterViewAction, &QAction::triggered, this, &PageView::onFilterToggled); auto futureViewAction = new QAction(this); futureViewAction->setObjectName(QStringLiteral("futureViewAction")); futureViewAction->setText(i18n("Show future items")); futureViewAction->setIcon(QIcon::fromTheme(QStringLiteral("view-calendar-whatsnext"))); futureViewAction->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_F); futureViewAction->setCheckable(true); connect(futureViewAction, &QAction::triggered, m_filterWidget, &FilterWidget::setShowFutureTasks); auto configGroup = KConfigGroup(KSharedConfig::openConfig(), "General"); if (configGroup.readEntry("ShowFuture", true)) futureViewAction->trigger(); connect(futureViewAction, &QAction::triggered, futureViewAction, [configGroup] (bool checked) mutable { configGroup.writeEntry("ShowFuture", checked); }); m_runTaskAction = new QAction(this); m_runTaskAction->setObjectName(QStringLiteral("runTaskAction")); m_runTaskAction->setShortcut(Qt::CTRL | Qt::Key_Space); m_runTaskAction->setText(i18n("Start Now")); m_runTaskAction->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-start"))); connect(m_runTaskAction, &QAction::triggered, this, &PageView::onRunTaskTriggered); updateRunTaskAction(); m_actions.insert(QStringLiteral("page_view_add"), addItemAction); m_actions.insert(QStringLiteral("page_view_remove"), removeItemAction); m_actions.insert(QStringLiteral("page_view_promote"), promoteItemAction); m_actions.insert(QStringLiteral("page_view_filter"), filterViewAction); m_actions.insert(QStringLiteral("page_view_future"), futureViewAction); m_actions.insert(QStringLiteral("page_run_task"), m_runTaskAction); } QHash PageView::globalActions() const { return m_actions; } QObject *PageView::model() const { return m_model; } Presentation::RunningTaskModelInterface *PageView::runningTaskModel() const { return m_runningTaskModel; } void PageView::setModel(QObject *model) { if (model == m_model) return; if (m_centralView->selectionModel()) { disconnect(m_centralView->selectionModel(), Q_NULLPTR, this, Q_NULLPTR); } m_filterWidget->proxyModel()->setSourceModel(Q_NULLPTR); m_model = model; setEnabled(m_model); updateRunTaskAction(); if (!m_model) return; QVariant modelProperty = m_model->property("centralListModel"); if (modelProperty.canConvert()) m_filterWidget->proxyModel()->setSourceModel(modelProperty.value()); connect(m_centralView->selectionModel(), &QItemSelectionModel::currentChanged, this, &PageView::onCurrentChanged); } MessageBoxInterface::Ptr PageView::messageBoxInterface() const { return m_messageBoxInterface; } QModelIndexList PageView::selectedIndexes() const { using namespace std::placeholders; const auto selection = m_centralView->selectionModel()->selectedIndexes(); auto sourceIndices = QModelIndexList(); std::transform(selection.constBegin(), selection.constEnd(), std::back_inserter(sourceIndices ), std::bind(&QSortFilterProxyModel::mapToSource, m_filterWidget->proxyModel(), _1)); return sourceIndices; } void PageView::setRunningTaskModel(Presentation::RunningTaskModelInterface *model) { m_runningTaskModel = model; connect(m_runningTaskModel, SIGNAL(runningTaskChanged(Domain::Task::Ptr)), this, SLOT(onRunningTaskChanged(Domain::Task::Ptr))); } void PageView::setMessageBoxInterface(const MessageBoxInterface::Ptr &interface) { m_messageBoxInterface = interface; } void PageView::displayErrorMessage(const QString &message) { m_messageWidget->setText(message); m_messageWidget->animatedShow(); } void PageView::onReturnPressed() { if (m_quickAddEdit->text().isEmpty()) return; auto parentIndex = QModelIndex(); if (m_centralView->selectionModel()->selectedIndexes().size() == 1) parentIndex = m_centralView->selectionModel()->selectedIndexes().first(); QMetaObject::invokeMethod(m_model, "addItem", Q_ARG(QString, m_quickAddEdit->text()), Q_ARG(QModelIndex, parentIndex)); m_quickAddEdit->clear(); } void PageView::onAddItemRequested() { if (m_quickAddEdit->hasFocus()) return; const auto editTopLeft = m_quickAddEdit->geometry().topLeft(); const auto pos = mapToGlobal(editTopLeft); auto popup = new PassivePopup(m_quickAddEdit); popup->setText(i18n("Type and press enter to add an item")); popup->show(); popup->move(pos - QPoint(0, popup->height())); m_quickAddEdit->selectAll(); m_quickAddEdit->setFocus(); } void PageView::onRemoveItemRequested() { const QModelIndexList ¤tIndexes = m_centralView->selectionModel()->selectedIndexes(); if (currentIndexes.isEmpty()) return; QString text; if (currentIndexes.size() > 1) { bool hasDescendants = false; foreach (const QModelIndex ¤tIndex, currentIndexes) { if (!currentIndex.isValid()) continue; if (currentIndex.model()->rowCount(currentIndex) > 0) { hasDescendants = true; break; } } if (hasDescendants) text = i18n("Do you really want to delete the selected items and their children?"); else text = i18n("Do you really want to delete the selected items?"); } else { const QModelIndex ¤tIndex = currentIndexes.first(); if (!currentIndex.isValid()) return; if (currentIndex.model()->rowCount(currentIndex) > 0) text = i18n("Do you really want to delete the selected task and all its children?"); } if (!text.isEmpty()) { QMessageBox::Button button = m_messageBoxInterface->askConfirmation(this, i18n("Delete Tasks"), text); bool canRemove = (button == QMessageBox::Yes); if (!canRemove) return; } foreach (const QModelIndex ¤tIndex, currentIndexes) { if (!currentIndex.isValid()) continue; QMetaObject::invokeMethod(m_model, "removeItem", Q_ARG(QModelIndex, currentIndex)); const auto data = currentIndex.data(Presentation::QueryTreeModelBase::ObjectRole); if (data.isValid()) { auto task = data.value().objectCast(); if (task) m_runningTaskModel->taskDeleted(task); } } } void PageView::onPromoteItemRequested() { QModelIndex currentIndex = m_centralView->currentIndex(); if (!currentIndex.isValid()) return; QMetaObject::invokeMethod(m_model, "promoteItem", Q_ARG(QModelIndex, currentIndex)); } void PageView::onFilterToggled(bool show) { m_filterWidget->setVisible(show); if (show) m_filterWidget->setFocus(); else m_filterWidget->clear(); } void PageView::updateRunTaskAction() { const auto artifact = currentArtifact(); const auto task = artifact.objectCast(); m_runTaskAction->setEnabled(task); } void PageView::onRunTaskTriggered() { auto task = currentArtifact().objectCast(); Q_ASSERT(task); // the action is supposed to be disabled otherwise if (task->startDate().isNull()) - task->setStartDate(QDateTime::currentDateTime()); + task->setStartDate(Utils::DateTime::currentDate()); m_runningTaskModel->setRunningTask(task); } void PageView::onRunningTaskChanged(const Domain::Task::Ptr &task) { if (!task) { QWidget *toplevel = window(); toplevel->raise(); toplevel->activateWindow(); } } void PageView::onCurrentChanged(const QModelIndex ¤t) { updateRunTaskAction(); auto data = current.data(Presentation::QueryTreeModelBase::ObjectRole); if (!data.isValid()) return; auto artifact = currentArtifact(); if (!artifact) return; emit currentArtifactChanged(artifact); } bool PageView::eventFilter(QObject *object, QEvent *event) { Q_ASSERT(object == m_centralView); switch(event->type()) { case QEvent::FocusIn: m_cancelAction->setEnabled(false); break; case QEvent::FocusOut: m_cancelAction->setEnabled(true); break; default: break; } return false; } Domain::Artifact::Ptr PageView::currentArtifact() const { const auto current = m_centralView->selectionModel()->currentIndex(); const auto data = current.data(Presentation::QueryTreeModelBase::ObjectRole); if (!data.isValid()) return Domain::Artifact::Ptr(); return data.value(); } #include "pageview.moc" diff --git a/tests/features/cuke-steps.cpp b/tests/features/cuke-steps.cpp index c9dfaf9e..d5c14c9a 100644 --- a/tests/features/cuke-steps.cpp +++ b/tests/features/cuke-steps.cpp @@ -1,905 +1,905 @@ /* This file is part of Zanshin Copyright 2014-2015 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include #include "presentation/applicationmodel.h" #include "presentation/errorhandler.h" #include "presentation/querytreemodelbase.h" #include "akonadi/akonadiapplicationselectedattribute.h" #include "akonadi/akonadicache.h" #include "akonadi/akonadicachingstorage.h" #include "akonadi/akonadimonitorimpl.h" #include "akonadi/akonadimessaginginterface.h" #include "akonadi/akonaditimestampattribute.h" #include "utils/dependencymanager.h" #include "utils/jobhandler.h" #include "testlib/akonadifakedata.h" #include "testlib/akonadifakedataxmlloader.h" #include "testlib/monitorspy.h" #include "testlib/testsafety.h" static int argc = 1; static char *argv0 = "cuke-steps"; static QApplication app(argc, &argv0); namespace CukeSteps { void initializeAppDependencies(); } namespace cucumber { namespace internal { template<> inline QString fromString(const std::string& s) { return QString::fromUtf8(s.data()); } } } using namespace cucumber; class FakeErrorHandler : public Presentation::ErrorHandler { public: void doDisplayMessage(const QString &) { } }; class ZanshinContext : public QObject { Q_OBJECT public: explicit ZanshinContext(QObject *parent = Q_NULLPTR) : QObject(parent), app(), presentation(Q_NULLPTR), editor(Q_NULLPTR), proxyModel(new QSortFilterProxyModel(this)), m_model(Q_NULLPTR), m_sourceModel(Q_NULLPTR), monitorSpy(Q_NULLPTR) { - qputenv("ZANSHIN_OVERRIDE_DATETIME", "2015-03-10"); + qputenv("ZANSHIN_OVERRIDE_DATE", "2015-03-10"); static bool initializedDependencies = false; if (!initializedDependencies) { CukeSteps::initializeAppDependencies(); MonitorSpy::setExpirationDelay(200); initializedDependencies = true; } Akonadi::AttributeFactory::registerAttribute(); Akonadi::AttributeFactory::registerAttribute(); const auto xmlFile = QString::fromLocal8Bit(qgetenv("ZANSHIN_USER_XMLDATA")); if (xmlFile.isEmpty()) { qDebug() << "FATAL ERROR! ZANSHIN_USER_XMLDATA WAS NOT PROVIDED\n\n"; exit(1); } auto searchCollection = Akonadi::Collection(1); searchCollection.setParentCollection(Akonadi::Collection::root()); searchCollection.setName(QStringLiteral("Search")); m_data.createCollection(searchCollection); auto loader = Testlib::AkonadiFakeDataXmlLoader(&m_data); loader.load(xmlFile); // Swap regular dependencies for the fake data ones auto &deps = Utils::DependencyManager::globalInstance(); deps.add( [this] (Utils::DependencyManager *) { return m_data.createMonitor(); } ); deps.add( [this] (Utils::DependencyManager *deps) { return new Akonadi::CachingStorage(deps->create(), Akonadi::StorageInterface::Ptr(m_data.createStorage())); } ); deps.add( [this] (Utils::DependencyManager *) -> Akonadi::MessagingInterface* { return Q_NULLPTR; } ); using namespace Presentation; proxyModel->setDynamicSortFilter(true); auto appModel = ApplicationModel::Ptr::create(); appModel->setErrorHandler(&m_errorHandler); app = appModel; auto monitor = Utils::DependencyManager::globalInstance().create(); monitorSpy = new MonitorSpy(monitor.data(), this); } ~ZanshinContext() { } // Note that setModel might invalidate the 'index' member variable, due to proxyModel->setSourceModel. void setModel(QAbstractItemModel *model) { if (m_sourceModel == model) return; m_sourceModel = model; if (!qobject_cast(model)) { proxyModel->setObjectName(QStringLiteral("m_proxyModel_in_ZanshinContext")); proxyModel->setSourceModel(model); proxyModel->setSortRole(Qt::DisplayRole); proxyModel->sort(0); m_model = proxyModel; } else { m_model = model; } } QAbstractItemModel *sourceModel() { return m_sourceModel; } QAbstractItemModel *model() { return m_model; } Domain::Artifact::Ptr currentArtifact() const { return index.data(Presentation::QueryTreeModelBase::ObjectRole) .value(); } void waitForEmptyJobQueue() { while (Utils::JobHandler::jobCount() != 0) { QTest::qWait(20); } } void waitForStableState() { waitForEmptyJobQueue(); monitorSpy->waitForStableState(); } QObjectPtr app; QList indices; QPersistentModelIndex index; QObject *presentation; QObject *editor; QList dragIndices; private: Testlib::AkonadiFakeData m_data; QSortFilterProxyModel *proxyModel; QAbstractItemModel *m_model; QAbstractItemModel *m_sourceModel; MonitorSpy *monitorSpy; FakeErrorHandler m_errorHandler; }; namespace Zanshin { QString indexString(const QModelIndex &index, int role = Qt::DisplayRole) { if (role != Qt::DisplayRole) return index.data(role).toString(); QString data = index.data(role).toString(); if (index.parent().isValid()) return indexString(index.parent(), role) + " / " + data; else return data; } QModelIndex findIndex(QAbstractItemModel *model, const QString &string, int role = Qt::DisplayRole, const QModelIndex &root = QModelIndex()) { for (int row = 0; row < model->rowCount(root); row++) { const QModelIndex index = model->index(row, 0, root); if (indexString(index, role) == string) return index; if (model->rowCount(index) > 0) { const QModelIndex found = findIndex(model, string, role, index); if (found.isValid()) return found; } } return QModelIndex(); } static void collectIndicesImpl(ZanshinContext *context, const QModelIndex &root = QModelIndex()) { QAbstractItemModel *model = context->model(); for (int row = 0; row < model->rowCount(root); row++) { const QModelIndex index = model->index(row, 0, root); context->indices << index; if (model->rowCount(index) > 0) collectIndicesImpl(context, index); } } static void collectIndices(ZanshinContext *context) { context->indices.clear(); collectIndicesImpl(context); } void dumpIndices(const QList &indices) { qDebug() << "Dumping list of size:" << indices.size(); for (int row = 0; row < indices.size(); row++) { qDebug() << row << indexString(indices.at(row)); } } inline bool verify(bool statement, const char *str, const char *file, int line) { if (statement) return true; qDebug() << "Statement" << str << "returned FALSE"; qDebug() << "Loc:" << file << line; return false; } template inline bool compare(T const &t1, T const &t2, const char *actual, const char *expected, const char *file, int line) { if (t1 == t2) return true; qDebug() << "Compared values are not the same"; qDebug() << "Actual (" << actual << ") :" << QTest::toString(t1); qDebug() << "Expected (" << expected << ") :" << QTest::toString(t2); qDebug() << "Loc:" << file << line; return false; } } // namespace Zanshin #define COMPARE(actual, expected) \ do {\ if (!Zanshin::compare(actual, expected, #actual, #expected, __FILE__, __LINE__))\ BOOST_REQUIRE(false);\ } while (0) // Note: you should make sure that context->indices is filled in before calling this, // e.g. calling Zanshin::collectIndices(context.get()) if not already done. #define COMPARE_OR_DUMP(actual, expected) \ do {\ if (!Zanshin::compare(actual, expected, #actual, #expected, __FILE__, __LINE__)) {\ Zanshin::dumpIndices(context->indices); \ BOOST_REQUIRE(false);\ }\ } while (0) #define VERIFY(statement) \ do {\ if (!Zanshin::verify((statement), #statement, __FILE__, __LINE__))\ BOOST_REQUIRE(false);\ } while (0) // Note: you should make sure that context->indices is filled in before calling this, // e.g. calling Zanshin::collectIndices(context.get()) if not already done. #define VERIFY_OR_DUMP(statement) \ do {\ if (!Zanshin::verify((statement), #statement, __FILE__, __LINE__)) {\ Zanshin::dumpIndices(context->indices); \ BOOST_REQUIRE(false);\ }\ } while (0) #define VERIFY_OR_DO(statement, whatToDo) \ do {\ if (!Zanshin::verify((statement), #statement, __FILE__, __LINE__)) {\ whatToDo; \ BOOST_REQUIRE(false);\ }\ } while (0) GIVEN("^I display the available data sources$") { ScenarioScope context; auto availableSources = context->app->property("availableSources").value(); VERIFY(availableSources); auto sourceListModel = availableSources->property("sourceListModel").value(); VERIFY(sourceListModel); context->presentation = availableSources; context->setModel(sourceListModel); } GIVEN("^I display the available pages$") { ScenarioScope context; context->presentation = context->app->property("availablePages").value(); context->setModel(context->presentation->property("pageListModel").value()); } GIVEN("^I display the \"(.*)\" page$") { REGEX_PARAM(QString, pageName); ScenarioScope context; auto availablePages = context->app->property("availablePages").value(); VERIFY(availablePages); auto pageListModel = availablePages->property("pageListModel").value(); VERIFY(pageListModel); context->waitForEmptyJobQueue(); QModelIndex pageIndex = Zanshin::findIndex(pageListModel, pageName); VERIFY(pageIndex.isValid()); QObject *page = Q_NULLPTR; QMetaObject::invokeMethod(availablePages, "createPageForIndex", Q_RETURN_ARG(QObject*, page), Q_ARG(QModelIndex, pageIndex)); VERIFY(page); VERIFY(context->app->setProperty("currentPage", QVariant::fromValue(page))); context->presentation = context->app->property("currentPage").value(); } GIVEN("^there is an item named \"(.+)\" in the central list$") { REGEX_PARAM(QString, itemName); ScenarioScope context; auto model = context->presentation->property("centralListModel").value(); context->setModel(model); context->waitForEmptyJobQueue(); Zanshin::collectIndices(context.get()); context->index = Zanshin::findIndex(context->model(), itemName); VERIFY_OR_DUMP(context->index.isValid()); } GIVEN("^there is an item named \"(.+)\" in the available data sources$") { REGEX_PARAM(QString, itemName); ScenarioScope context; auto availableSources = context->app->property("availableSources").value(); VERIFY(availableSources); auto model = availableSources->property("sourceListModel").value(); VERIFY(model); context->waitForEmptyJobQueue(); context->setModel(model); Zanshin::collectIndices(context.get()); context->index = Zanshin::findIndex(context->model(), itemName); VERIFY_OR_DUMP(context->index.isValid()); } GIVEN("^the central list contains items named:") { TABLE_PARAM(tableParam); ScenarioScope context; context->dragIndices.clear(); auto model = context->presentation->property("centralListModel").value(); context->waitForEmptyJobQueue(); context->setModel(model); for (const auto &row : tableParam.hashes()) { for (const auto &it : row) { const QString itemName = QString::fromUtf8(it.second.data()); QModelIndex index = Zanshin::findIndex(context->model(), itemName); VERIFY_OR_DO(index.isValid(), Zanshin::dumpIndices(context->dragIndices)); context->dragIndices << index; } } } WHEN("^I look at the central list$") { ScenarioScope context; auto model = context->presentation->property("centralListModel").value(); context->setModel(model); context->waitForStableState(); } WHEN("^I check the item$") { ScenarioScope context; VERIFY(context->model()->setData(context->index, Qt::Checked, Qt::CheckStateRole)); context->waitForStableState(); } WHEN("^I uncheck the item$") { ScenarioScope context; VERIFY(context->model()->setData(context->index, Qt::Unchecked, Qt::CheckStateRole)); context->waitForStableState(); } WHEN("^I remove the item$") { ScenarioScope context; VERIFY(QMetaObject::invokeMethod(context->presentation, "removeItem", Q_ARG(QModelIndex, context->index))); context->waitForStableState(); } WHEN("^I promote the item$") { ScenarioScope context; VERIFY(QMetaObject::invokeMethod(context->presentation, "promoteItem", Q_ARG(QModelIndex, context->index))); context->waitForStableState(); } WHEN("^I add a project named \"(.*)\" in the source named \"(.*)\"$") { REGEX_PARAM(QString, projectName); REGEX_PARAM(QString, sourceName); ScenarioScope context; auto availableSources = context->app->property("availableSources").value(); VERIFY(availableSources); auto sourceList = availableSources->property("sourceListModel").value(); VERIFY(sourceList); context->waitForStableState(); QModelIndex index = Zanshin::findIndex(sourceList, sourceName); VERIFY(index.isValid()); auto source = index.data(Presentation::QueryTreeModelBase::ObjectRole) .value(); VERIFY(source); VERIFY(QMetaObject::invokeMethod(context->presentation, "addProject", Q_ARG(QString, projectName), Q_ARG(Domain::DataSource::Ptr, source))); context->waitForStableState(); } WHEN("^I rename the page named \"(.*)\" under \"(.*)\" to \"(.*)\"$") { REGEX_PARAM(QString, oldName); REGEX_PARAM(QString, path); REGEX_PARAM(QString, newName); const QString pageNodeName = path + " / "; VERIFY(!pageNodeName.isEmpty()); ScenarioScope context; auto availablePages = context->app->property("availablePages").value(); VERIFY(availablePages); auto pageListModel = availablePages->property("pageListModel").value(); VERIFY(pageListModel); context->waitForStableState(); QModelIndex pageIndex = Zanshin::findIndex(pageListModel, pageNodeName + oldName); VERIFY(pageIndex.isValid()); pageListModel->setData(pageIndex, newName); context->waitForStableState(); } WHEN("^I remove the page named \"(.*)\" under \"(.*)\"$") { REGEX_PARAM(QString, name); REGEX_PARAM(QString, path); const QString pageNodeName = path + " / "; VERIFY(!pageNodeName.isEmpty()); ScenarioScope context; auto availablePages = context->app->property("availablePages").value(); VERIFY(availablePages); auto pageListModel = availablePages->property("pageListModel").value(); VERIFY(pageListModel); context->waitForStableState(); QModelIndex pageIndex = Zanshin::findIndex(pageListModel, pageNodeName + name); VERIFY(pageIndex.isValid()); VERIFY(QMetaObject::invokeMethod(availablePages, "removeItem", Q_ARG(QModelIndex, pageIndex))); context->waitForStableState(); } WHEN("^I add a \"(.*)\" named \"(.+)\"$") { REGEX_PARAM(QString, objectType); REGEX_PARAM(QString, objectName); QByteArray actionName = (objectType == QStringLiteral("context")) ? "addContext" : (objectType == QStringLiteral("note")) ? "addItem" : (objectType == QStringLiteral("task")) ? "addItem" : (objectType == QStringLiteral("tag")) ? "addTag" : QByteArray(); VERIFY(!actionName.isEmpty()); ScenarioScope context; context->waitForStableState(); VERIFY(QMetaObject::invokeMethod(context->presentation, actionName.data(), Q_ARG(QString, objectName))); context->waitForStableState(); } WHEN("^I add a child named \"(.+)\" under the task named \"(.+)\"$") { REGEX_PARAM(QString, childName); REGEX_PARAM(QString, parentName); ScenarioScope context; context->waitForStableState(); auto parentIndex = QModelIndex(); for (int row = 0; row < context->indices.size(); row++) { auto index = context->indices.at(row); if (Zanshin::indexString(index) == parentName) { parentIndex = index; break; } } VERIFY_OR_DUMP(parentIndex.isValid()); VERIFY(QMetaObject::invokeMethod(context->presentation, "addItem", Q_ARG(QString, childName), Q_ARG(QModelIndex, parentIndex))); context->waitForStableState(); } WHEN("^I list the items$") { ScenarioScope context; context->waitForStableState(); Zanshin::collectIndices(context.get()); context->waitForStableState(); } WHEN("^I open the item in the editor$") { ScenarioScope context; auto artifact = context->currentArtifact(); VERIFY(artifact); context->editor = context->app->property("editor").value(); VERIFY(context->editor); VERIFY(context->editor->setProperty("artifact", QVariant::fromValue(artifact))); } WHEN("^I mark it done in the editor$") { ScenarioScope context; VERIFY(context->editor->setProperty("done", true)); } WHEN("^I change the editor (.*) to \"(.*)\"$") { REGEX_PARAM(QString, field); REGEX_PARAM(QString, string); const QVariant value = (field == QStringLiteral("text")) ? string : (field == QStringLiteral("title")) ? string : (field == QStringLiteral("start date")) ? QDateTime::fromString(string, Qt::ISODate) : (field == QStringLiteral("due date")) ? QDateTime::fromString(string, Qt::ISODate) : QVariant(); const QByteArray property = (field == QStringLiteral("text")) ? field.toUtf8() : (field == QStringLiteral("title")) ? field.toUtf8() : (field == QStringLiteral("start date")) ? "startDate" : (field == QStringLiteral("due date")) ? "dueDate" : QByteArray(); VERIFY(value.isValid()); VERIFY(!property.isEmpty()); ScenarioScope context; VERIFY(context->editor->setProperty("editingInProgress", true)); VERIFY(context->editor->setProperty(property, value)); } WHEN("^I rename the item to \"(.+)\"$") { REGEX_PARAM(QString, title); ScenarioScope context; VERIFY(context->editor->setProperty("editingInProgress", false)); VERIFY(context->model()->setData(context->index, title, Qt::EditRole)); context->waitForStableState(); } WHEN("^I open the item in the editor again$") { ScenarioScope context; auto artifact = context->currentArtifact(); VERIFY(artifact); VERIFY(context->editor->setProperty("artifact", QVariant::fromValue(Domain::Artifact::Ptr()))); VERIFY(context->editor->setProperty("artifact", QVariant::fromValue(artifact))); context->waitForStableState(); } WHEN("^I drop the item on \"(.*)\" in the central list") { REGEX_PARAM(QString, itemName); ScenarioScope context; VERIFY(context->index.isValid()); const QMimeData *data = context->model()->mimeData(QModelIndexList() << context->index); QAbstractItemModel *destModel = context->model(); QModelIndex dropIndex = Zanshin::findIndex(destModel, itemName); VERIFY(dropIndex.isValid()); VERIFY(destModel->dropMimeData(data, Qt::MoveAction, -1, -1, dropIndex)); context->waitForStableState(); } WHEN("^I drop the item on the blank area of the central list") { ScenarioScope context; VERIFY(context->index.isValid()); const QMimeData *data = context->model()->mimeData(QModelIndexList() << context->index); QAbstractItemModel *destModel = context->model(); VERIFY(destModel->dropMimeData(data, Qt::MoveAction, -1, -1, QModelIndex())); context->waitForStableState(); } WHEN("^I drop items on \"(.*)\" in the central list") { REGEX_PARAM(QString, itemName); ScenarioScope context; VERIFY(!context->dragIndices.isEmpty()); QModelIndexList indexes; std::transform(context->dragIndices.constBegin(), context->dragIndices.constEnd(), std::back_inserter(indexes), [] (const QPersistentModelIndex &index) { VERIFY(index.isValid()); return index; }); const QMimeData *data = context->model()->mimeData(indexes); QAbstractItemModel *destModel = context->model(); QModelIndex dropIndex = Zanshin::findIndex(destModel, itemName); VERIFY(dropIndex.isValid()); VERIFY(destModel->dropMimeData(data, Qt::MoveAction, -1, -1, dropIndex)); context->waitForStableState(); } WHEN("^I drop the item on \"(.*)\" in the page list") { REGEX_PARAM(QString, itemName); ScenarioScope context; VERIFY(context->index.isValid()); const QMimeData *data = context->model()->mimeData(QModelIndexList() << context->index); auto availablePages = context->app->property("availablePages").value(); VERIFY(availablePages); auto destModel = availablePages->property("pageListModel").value(); VERIFY(destModel); context->waitForStableState(); QModelIndex dropIndex = Zanshin::findIndex(destModel, itemName); VERIFY(dropIndex.isValid()); VERIFY(destModel->dropMimeData(data, Qt::MoveAction, -1, -1, dropIndex)); context->waitForStableState(); } WHEN("^I drop items on \"(.*)\" in the page list") { REGEX_PARAM(QString, itemName); ScenarioScope context; VERIFY(!context->dragIndices.isEmpty()); QModelIndexList indexes; std::transform(context->dragIndices.constBegin(), context->dragIndices.constEnd(), std::back_inserter(indexes), [] (const QPersistentModelIndex &index) { VERIFY(index.isValid()); return index; }); const QMimeData *data = context->model()->mimeData(indexes); auto availablePages = context->app->property("availablePages").value(); VERIFY(availablePages); auto destModel = availablePages->property("pageListModel").value(); VERIFY(destModel); context->waitForStableState(); QModelIndex dropIndex = Zanshin::findIndex(destModel, itemName); VERIFY(dropIndex.isValid()); VERIFY(destModel->dropMimeData(data, Qt::MoveAction, -1, -1, dropIndex)); context->waitForStableState(); } WHEN("^the setting key (\\S+) changes to (\\d+)$") { REGEX_PARAM(QString, keyName); REGEX_PARAM(qint64, id); ScenarioScope context; KConfigGroup config(KSharedConfig::openConfig(), "General"); config.writeEntry(keyName, id); } WHEN("^the user changes the default data source to \"(.*)\"$") { REGEX_PARAM(QString, sourceName); ScenarioScope context; context->waitForStableState(); auto sourceIndex = Zanshin::findIndex(context->model(), sourceName); auto availableSources = context->app->property("availableSources").value(); VERIFY(availableSources); VERIFY(QMetaObject::invokeMethod(availableSources, "setDefaultItem", Q_ARG(QModelIndex, sourceIndex))); context->waitForStableState(); } THEN("^the list is") { TABLE_PARAM(tableParam); ScenarioScope context; auto roleNames = context->model()->roleNames(); QSet usedRoles; QStandardItemModel inputModel; for (const auto &row : tableParam.hashes()) { QStandardItem *item = new QStandardItem; for (const auto &it : row) { const QByteArray roleName = it.first.data(); const QString value = QString::fromUtf8(it.second.data()); const int role = roleNames.key(roleName, -1); VERIFY_OR_DUMP(role != -1); item->setData(value, role); usedRoles.insert(role); } inputModel.appendRow(item); } QSortFilterProxyModel proxy; QAbstractItemModel *referenceModel; if (!qobject_cast(context->sourceModel())) { referenceModel = &proxy; proxy.setSourceModel(&inputModel); proxy.setSortRole(Qt::DisplayRole); proxy.sort(0); proxy.setObjectName(QStringLiteral("the_list_is_proxy")); } else { referenceModel = &inputModel; } for (int row = 0; row < context->indices.size(); row++) { QModelIndex expectedIndex = referenceModel->index(row, 0); QModelIndex resultIndex = context->indices.at(row); foreach (const auto &role, usedRoles) { COMPARE_OR_DUMP(Zanshin::indexString(resultIndex, role), Zanshin::indexString(expectedIndex, role)); } } COMPARE_OR_DUMP(context->indices.size(), referenceModel->rowCount()); } THEN("^the list contains \"(.+)\"$") { REGEX_PARAM(QString, itemName); ScenarioScope context; for (int row = 0; row < context->indices.size(); row++) { if (Zanshin::indexString(context->indices.at(row)) == itemName) return; } VERIFY_OR_DUMP(false); } THEN("^the list does not contain \"(.+)\"$") { REGEX_PARAM(QString, itemName); ScenarioScope context; for (int row = 0; row < context->indices.size(); row++) { VERIFY_OR_DUMP(Zanshin::indexString(context->indices.at(row)) != itemName); } } THEN("^the task corresponding to the item is done$") { ScenarioScope context; auto artifact = context->currentArtifact(); VERIFY(artifact); auto task = artifact.dynamicCast(); VERIFY(task); VERIFY(task->isDone()); } THEN("^the editor shows the task as done$") { ScenarioScope context; VERIFY(context->editor->property("done").toBool()); } THEN("^the editor shows \"(.*)\" as (.*)$") { REGEX_PARAM(QString, string); REGEX_PARAM(QString, field); const QVariant value = (field == QStringLiteral("text")) ? string : (field == QStringLiteral("title")) ? string : (field == QStringLiteral("delegate")) ? string : (field == QStringLiteral("start date")) ? QDateTime::fromString(string, Qt::ISODate) : (field == QStringLiteral("due date")) ? QDateTime::fromString(string, Qt::ISODate) : QVariant(); const QByteArray property = (field == QStringLiteral("text")) ? field.toUtf8() : (field == QStringLiteral("title")) ? field.toUtf8() : (field == QStringLiteral("delegate")) ? "delegateText" : (field == QStringLiteral("start date")) ? "startDate" : (field == QStringLiteral("due date")) ? "dueDate" : QByteArray(); VERIFY(value.isValid()); VERIFY(!property.isEmpty()); ScenarioScope context; COMPARE(context->editor->property(property), value); } THEN("^the default data source is \"(.*)\"$") { REGEX_PARAM(QString, expectedName); ScenarioScope context; context->waitForStableState(); auto expectedIndex = Zanshin::findIndex(context->model(), expectedName); VERIFY(expectedIndex.isValid()); auto defaultRole = context->model()->roleNames().key("default", -1); VERIFY(expectedIndex.data(defaultRole).toBool()); } THEN("^the setting key (\\S+) is (\\d+)$") { REGEX_PARAM(QString, keyName); REGEX_PARAM(qint64, expectedId); KConfigGroup config(KSharedConfig::openConfig(), "General"); const qint64 id = config.readEntry(keyName, -1); COMPARE(id, expectedId); } #include "cuke-steps.moc" diff --git a/tests/testlib/gentodo.cpp b/tests/testlib/gentodo.cpp index 99a236cb..55f32f4b 100644 --- a/tests/testlib/gentodo.cpp +++ b/tests/testlib/gentodo.cpp @@ -1,172 +1,158 @@ /* This file is part of Zanshin Copyright 2015 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "gentodo.h" #include -#include +#include #include -#if KCALCORE_VERSION < QT_VERSION_CHECK(5, 6, 80) -#include -#endif using namespace Testlib; GenTodo::GenTodo(const Akonadi::Item &item) : m_item(item) { m_item.setMimeType(KCalCore::Todo::todoMimeType()); if (!m_item.hasPayload()) m_item.setPayload(KCalCore::Todo::Ptr::create()); } Testlib::GenTodo::operator Akonadi::Item() { return m_item; } GenTodo &GenTodo::withId(Akonadi::Item::Id id) { m_item.setId(id); return *this; } GenTodo &GenTodo::withParent(Akonadi::Collection::Id id) { m_item.setParentCollection(Akonadi::Collection(id)); return *this; } GenTodo &GenTodo::withTags(const QList &ids) { auto tags = Akonadi::Tag::List(); std::transform(ids.constBegin(), ids.constEnd(), std::back_inserter(tags), [] (Akonadi::Tag::Id id) { return Akonadi::Tag(id); }); m_item.setTags(tags); return *this; } GenTodo &GenTodo::asProject(bool value) { auto todo = m_item.payload(); if (value) todo->setCustomProperty("Zanshin", "Project", QStringLiteral("1")); else todo->removeCustomProperty("Zanshin", "Project"); return *this; } GenTodo &GenTodo::withUid(const QString &uid) { m_item.payload()->setUid(uid); return *this; } GenTodo &GenTodo::withParentUid(const QString &uid) { m_item.payload()->setRelatedTo(uid); return *this; } GenTodo &GenTodo::withTitle(const QString &title) { m_item.payload()->setSummary(title); return *this; } GenTodo &GenTodo::withText(const QString &text) { m_item.payload()->setDescription(text); return *this; } GenTodo &GenTodo::done(bool value) { m_item.payload()->setCompleted(value); return *this; } -#if KCALCORE_VERSION >= QT_VERSION_CHECK(5, 6, 80) - -template -static QDateTime KDateTime(const T& input) { - QDateTime dt(input); - dt.setTimeSpec(Qt::UTC); - return dt; -} - -#endif - GenTodo &GenTodo::withDoneDate(const QString &date) { - m_item.payload()->setCompleted(KDateTime(QDate::fromString(date, Qt::ISODate))); + m_item.payload()->setCompleted(QDateTime(QDate::fromString(date, Qt::ISODate))); return *this; } -GenTodo &GenTodo::withDoneDate(const QDateTime &date) +GenTodo &GenTodo::withDoneDate(const QDate &date) { - m_item.payload()->setCompleted(KDateTime(date)); + m_item.payload()->setCompleted(QDateTime(date)); return *this; } GenTodo &GenTodo::withStartDate(const QString &date) { - m_item.payload()->setDtStart(KDateTime(QDate::fromString(date, Qt::ISODate))); + m_item.payload()->setDtStart(QDateTime(QDate::fromString(date, Qt::ISODate))); return *this; } -GenTodo &GenTodo::withStartDate(const QDateTime &date) +GenTodo &GenTodo::withStartDate(const QDate &date) { - m_item.payload()->setDtStart(KDateTime(date)); + m_item.payload()->setDtStart(QDateTime(date)); return *this; } GenTodo &GenTodo::withDueDate(const QString &date) { - m_item.payload()->setDtDue(KDateTime(QDate::fromString(date, Qt::ISODate))); + m_item.payload()->setDtDue(QDateTime(QDate::fromString(date, Qt::ISODate))); return *this; } -GenTodo &GenTodo::withDueDate(const QDateTime &date) +GenTodo &GenTodo::withDueDate(const QDate &date) { - m_item.payload()->setDtDue(KDateTime(date)); + m_item.payload()->setDtDue(QDateTime(date)); return *this; } GenTodo &GenTodo::withDelegate(const QString &name, const QString &email) { withNoDelegate(); KCalCore::Attendee::Ptr attendee(new KCalCore::Attendee(name, email, true, KCalCore::Attendee::Delegated)); m_item.payload()->addAttendee(attendee); return *this; } GenTodo &GenTodo::withNoDelegate() { m_item.payload()->clearAttendees(); return *this; } diff --git a/tests/testlib/gentodo.h b/tests/testlib/gentodo.h index d3e424c7..deb22a9f 100644 --- a/tests/testlib/gentodo.h +++ b/tests/testlib/gentodo.h @@ -1,64 +1,64 @@ /* This file is part of Zanshin Copyright 2015 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef TESTLIB_GENTODO_H #define TESTLIB_GENTODO_H #include #include namespace Testlib { class GenTodo { public: explicit GenTodo(const Akonadi::Item &item = Akonadi::Item()); operator Akonadi::Item(); GenTodo &withId(Akonadi::Item::Id id); GenTodo &withParent(Akonadi::Collection::Id id); GenTodo &withTags(const QList &ids); GenTodo &asProject(bool value = true); GenTodo &withUid(const QString &uid); GenTodo &withParentUid(const QString &uid); GenTodo &withTitle(const QString &title); GenTodo &withText(const QString &text); GenTodo &done(bool value = true); GenTodo &withDoneDate(const QString &date); - GenTodo &withDoneDate(const QDateTime &date); + GenTodo &withDoneDate(const QDate &date); GenTodo &withStartDate(const QString &date); - GenTodo &withStartDate(const QDateTime &date); + GenTodo &withStartDate(const QDate &date); GenTodo &withDueDate(const QString &date); - GenTodo &withDueDate(const QDateTime &date); + GenTodo &withDueDate(const QDate &date); GenTodo &withDelegate(const QString &name, const QString &email); GenTodo &withNoDelegate(); private: Akonadi::Item m_item; }; } #endif // TESTLIB_GENTODO_H diff --git a/tests/units/akonadi/akonadiserializertest.cpp b/tests/units/akonadi/akonadiserializertest.cpp index 3be54503..88170c68 100644 --- a/tests/units/akonadi/akonadiserializertest.cpp +++ b/tests/units/akonadi/akonadiserializertest.cpp @@ -1,2702 +1,2682 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens Copyright 2014 Rémi Benoit This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include "akonadi/akonadiserializer.h" #include "akonadi/akonadiapplicationselectedattribute.h" #include "akonadi/akonaditimestampattribute.h" #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(Akonadi::Item*) #if KCALCORE_VERSION >= QT_VERSION_CHECK(5, 6, 80) #define KDateTime QDateTime #endif -static void setTodoDates(KCalCore::Todo::Ptr todo, const QDateTime &start, const QDateTime &due) { -#if KCALCORE_VERSION >= QT_VERSION_CHECK(5, 6, 80) - todo->setDtStart(start); - todo->setDtDue(due); -#else - todo->setDtStart(KDateTime(start, KDateTime::UTC)); - todo->setDtDue(KDateTime(due, KDateTime::UTC)); -#endif +static void setTodoDates(KCalCore::Todo::Ptr todo, const QDate &start, const QDate &due) { + todo->setDtStart(QDateTime(start)); + todo->setDtDue(QDateTime(due)); } class AkonadiSerializerTest : public QObject { Q_OBJECT private slots: void shouldKnowWhenAnObjectRepresentsACollection() { // GIVEN Akonadi::Serializer serializer; auto object = Akonadi::Serializer::QObjectPtr::create(); Akonadi::Collection collection(42); // WHEN // Nothing yet // THEN QVERIFY(!serializer.representsCollection(object, collection)); // WHEN object->setProperty("collectionId", 42); // THEN QVERIFY(serializer.representsCollection(object, collection)); // WHEN object->setProperty("collectionId", 43); // THEN QVERIFY(!serializer.representsCollection(object, collection)); } void shouldKnowWhenAnObjectRepresentsAnItem() { // GIVEN Akonadi::Serializer serializer; auto object = Akonadi::Serializer::QObjectPtr::create(); Akonadi::Item item(42); // WHEN // Nothing yet // THEN QVERIFY(!serializer.representsItem(object, item)); // WHEN object->setProperty("itemId", 42); // THEN QVERIFY(serializer.representsItem(object, item)); // WHEN object->setProperty("itemId", 43); // THEN QVERIFY(!serializer.representsItem(object, item)); } void shouldKnowWhenAnAkonadiTagRepresentsATag() { // GIVEN Akonadi::Serializer serializer; Akonadi::Tag akondiTag(42); auto tag = Domain::Tag::Ptr::create(); // WHEN // Nothing yet // THEN QVERIFY(!serializer.representsAkonadiTag(tag, akondiTag)); // WHEN tag->setProperty("tagId", 42); // THEN QVERIFY(serializer.representsAkonadiTag(tag, akondiTag)); // WHEN tag->setProperty("tagId", 43); // THEN QVERIFY(!serializer.representsAkonadiTag(tag, akondiTag)); } void shouldKnowObjectUid() { // GIVEN Akonadi::Serializer serializer; auto object = Akonadi::Serializer::QObjectPtr::create(); // WHEN object->setProperty("todoUid", "my-uid"); // THEN QCOMPARE(serializer.objectUid(object), QStringLiteral("my-uid")); } void shouldKnowTaskItemUid_data() { QTest::addColumn("item"); QTest::addColumn("expectedUid"); Akonadi::Item item1; KCalCore::Todo::Ptr todo1(new KCalCore::Todo); todo1->setUid(QString()); item1.setPayload(todo1); Akonadi::Item item2; KCalCore::Todo::Ptr todo2(new KCalCore::Todo); todo2->setUid(QStringLiteral("1")); item2.setPayload(todo2); Akonadi::Item item3; KMime::Message::Ptr message(new KMime::Message); message->subject(true)->fromUnicodeString(QStringLiteral("foo"), "utf-8"); message->mainBodyPart()->fromUnicodeString(QStringLiteral("bar")); item3.setMimeType(Akonadi::NoteUtils::noteMimeType()); item3.setPayload(message); QTest::newRow("task without uid") << item1 << QString(); QTest::newRow("task with uid") << item2 << "1"; QTest::newRow("note") << item3 << QString(); } void shouldKnowTaskItemUid() { // GIVEN QFETCH(Akonadi::Item, item); QFETCH(QString, expectedUid); // WHEN Akonadi::Serializer serializer; QString uid = serializer.itemUid(item); // THEN QCOMPARE(uid, expectedUid); } void shouldCreateDataSourceFromCollection_data() { QTest::addColumn("name"); QTest::addColumn("iconName"); QTest::addColumn("mimeTypes"); QTest::addColumn("hasSelectedAttribute"); QTest::addColumn("isSelected"); const auto noteMimeTypes = QStringList() << QStringLiteral("text/x-vnd.akonadi.note"); const auto taskMimeTypes = QStringList() << QStringLiteral("application/x-vnd.akonadi.calendar.todo"); const auto bogusMimeTypes = QStringList() << QStringLiteral("foo/bar"); const auto allMimeTypes = noteMimeTypes + taskMimeTypes + bogusMimeTypes; QTest::newRow("nominal case") << "name" << "icon" << allMimeTypes << true << false; QTest::newRow("only notes") << "name" << "icon" << noteMimeTypes << true << false; QTest::newRow("only tasks") << "name" << "icon" << taskMimeTypes << true << false; QTest::newRow("only bogus") << "name" << "icon" << bogusMimeTypes << true << false; QTest::newRow("no selected attribute") << "name" << "icon" << allMimeTypes << false << false; QTest::newRow("selected attribute (false)") << "name" << "icon" << allMimeTypes << true << false; QTest::newRow("selected attribute (true)") << "name" << "icon" << allMimeTypes << true << true; QTest::newRow("empty case") << QString() << QString() << QStringList() << false << false; } void shouldCreateDataSourceFromCollection() { // GIVEN // Data... QFETCH(QString, name); QFETCH(QString, iconName); QFETCH(QStringList, mimeTypes); QFETCH(bool, hasSelectedAttribute); QFETCH(bool, isSelected); Domain::DataSource::ContentTypes expectedContentTypes; if (mimeTypes.contains(QStringLiteral("text/x-vnd.akonadi.note"))) { expectedContentTypes |= Domain::DataSource::Notes; } if (mimeTypes.contains(QStringLiteral("application/x-vnd.akonadi.calendar.todo"))) { expectedContentTypes |= Domain::DataSource::Tasks; } // ... stored in a collection Akonadi::Collection collection(42); collection.setContentMimeTypes(mimeTypes); collection.setName(name); auto displayAttribute = new Akonadi::EntityDisplayAttribute; displayAttribute->setIconName(iconName); collection.addAttribute(displayAttribute); if (hasSelectedAttribute) { auto selectedAttribute = new Akonadi::ApplicationSelectedAttribute; selectedAttribute->setSelected(isSelected); collection.addAttribute(selectedAttribute); } // WHEN Akonadi::Serializer serializer; auto dataSource = serializer.createDataSourceFromCollection(collection, Akonadi::SerializerInterface::BaseName); // THEN QCOMPARE(dataSource->name(), name); QCOMPARE(dataSource->iconName(), iconName); QCOMPARE(dataSource->contentTypes(), expectedContentTypes); QCOMPARE(dataSource->isSelected(), !hasSelectedAttribute || isSelected); QCOMPARE(dataSource->property("collectionId").value(), collection.id()); } void shouldCreateNullDataSourceFromInvalidCollection() { // GIVEN Akonadi::Collection collection; // WHEN Akonadi::Serializer serializer; auto dataSource = serializer.createDataSourceFromCollection(collection, Akonadi::SerializerInterface::BaseName); // THEN QVERIFY(dataSource.isNull()); } void shouldUpdateDataSourceFromCollection_data() { QTest::addColumn("updatedName"); QTest::newRow("no change") << "name"; QTest::newRow("changed") << "new name"; } void shouldUpdateDataSourceFromCollection() { // GIVEN // A collection... Akonadi::Collection originalCollection(42); originalCollection.setName(QStringLiteral("name")); // ... deserialized as a data source Akonadi::Serializer serializer; auto dataSource = serializer.createDataSourceFromCollection(originalCollection, Akonadi::SerializerInterface::BaseName); // WHEN // Data... QFETCH(QString, updatedName); // ... in a new collection Akonadi::Collection updatedCollection(42); updatedCollection.setName(updatedName); serializer.updateDataSourceFromCollection(dataSource, updatedCollection, Akonadi::SerializerInterface::BaseName); // THEN QCOMPARE(dataSource->name(), updatedName); } void shouldNotUpdateDataSourceFromInvalidCollection() { // GIVEN // Data... const QString name = QStringLiteral("name"); // ... stored in a collection... Akonadi::Collection originalCollection(42); originalCollection.setName(name); // ... deserialized as a data source Akonadi::Serializer serializer; auto dataSource = serializer.createDataSourceFromCollection(originalCollection, Akonadi::SerializerInterface::BaseName); // WHEN Akonadi::Collection invalidCollection; invalidCollection.setName(QStringLiteral("foo")); serializer.updateDataSourceFromCollection(dataSource, invalidCollection, Akonadi::SerializerInterface::BaseName); // THEN QCOMPARE(dataSource->name(), name); } void shouldNameDataSourceFromCollectionPathIfRequested() { // GIVEN // Data... const QString name = QStringLiteral("name"); const QString parentName = QStringLiteral("parent"); // ... stored in a collection with a parent Akonadi::Collection collection(42); collection.setName(name); Akonadi::Collection parentCollection(41); parentCollection.setName(QStringLiteral("Foo")); auto attribute = new Akonadi::EntityDisplayAttribute; attribute->setDisplayName(parentName); parentCollection.addAttribute(attribute); collection.setParentCollection(parentCollection); // WHEN Akonadi::Serializer serializer; auto dataSource1 = serializer.createDataSourceFromCollection(collection, Akonadi::SerializerInterface::FullPath); auto dataSource2 = serializer.createDataSourceFromCollection(collection, Akonadi::SerializerInterface::BaseName); // Give it another try with the root parentCollection.setParentCollection(Akonadi::Collection::root()); collection.setParentCollection(parentCollection); auto dataSource3 = serializer.createDataSourceFromCollection(collection, Akonadi::SerializerInterface::FullPath); auto dataSource4 = serializer.createDataSourceFromCollection(collection, Akonadi::SerializerInterface::BaseName); // THEN QCOMPARE(dataSource1->name(), QString(parentName + " » " + name)); QCOMPARE(dataSource2->name(), name); QCOMPARE(dataSource3->name(), QString(parentName + " » " + name)); QCOMPARE(dataSource4->name(), name); } void shouldCreateCollectionFromDataSource_data() { QTest::addColumn("name"); QTest::addColumn("iconName"); QTest::addColumn("contentTypes"); QTest::addColumn("isSelected"); const auto noType = Domain::DataSource::ContentTypes(Domain::DataSource::NoContent); const auto taskType = Domain::DataSource::ContentTypes(Domain::DataSource::Tasks); const auto noteType = Domain::DataSource::ContentTypes(Domain::DataSource::Notes); const auto allTypes = taskType | noteType; QTest::newRow("nominal case") << "name" << "icon-name" << allTypes << true; QTest::newRow("only notes") << "name" << "icon-name" << noteType << true; QTest::newRow("only tasks") << "name" << "icon-name" << taskType << true; QTest::newRow("only nothing ;)") << "name" << "icon-name" << noType << true; QTest::newRow("not selected") << "name" << "icon-name" << allTypes << false; QTest::newRow("selected") << "name" << "icon-name" << allTypes << true; QTest::newRow("empty case") << QString() << QString() << noType << true; } void shouldCreateCollectionFromDataSource() { // GIVEN const auto timestamp = QDateTime::currentMSecsSinceEpoch(); // Data... QFETCH(QString, name); QFETCH(QString, iconName); QFETCH(Domain::DataSource::ContentTypes, contentTypes); QFETCH(bool, isSelected); QStringList mimeTypes; if (contentTypes & Domain::DataSource::Tasks) mimeTypes << QStringLiteral("application/x-vnd.akonadi.calendar.todo"); if (contentTypes & Domain::DataSource::Notes) mimeTypes << QStringLiteral("text/x-vnd.akonadi.note"); // ... stored in a data source auto source = Domain::DataSource::Ptr::create(); source->setName(name); source->setIconName(iconName); source->setContentTypes(contentTypes); source->setSelected(isSelected); source->setProperty("collectionId", 42); // WHEN Akonadi::Serializer serializer; auto collection = serializer.createCollectionFromDataSource(source); // THEN QCOMPARE(collection.id(), source->property("collectionId").value()); QVERIFY(collection.hasAttribute()); QCOMPARE(collection.attribute()->isSelected(), isSelected); QVERIFY(collection.hasAttribute()); QVERIFY(collection.attribute()->timestamp() >= timestamp); } void shouldVerifyIfCollectionIsSelected_data() { QTest::addColumn("mimeTypes"); QTest::addColumn("hasSelectedAttribute"); QTest::addColumn("isSelected"); QTest::addColumn("expectedSelected"); const auto noteMimeTypes = QStringList() << QStringLiteral("text/x-vnd.akonadi.note"); const auto taskMimeTypes = QStringList() << QStringLiteral("application/x-vnd.akonadi.calendar.todo"); const auto bogusMimeTypes = QStringList() << QStringLiteral("foo/bar"); const auto allMimeTypes = noteMimeTypes + taskMimeTypes + bogusMimeTypes; QTest::newRow("nominal case") << allMimeTypes << true << false << false; QTest::newRow("only notes") << noteMimeTypes << true << false << false; QTest::newRow("only tasks") << taskMimeTypes << true << false << false; QTest::newRow("only bogus") << bogusMimeTypes << true << false << false; QTest::newRow("selected, only notes") << noteMimeTypes << true << true << true; QTest::newRow("selected, only tasks") << taskMimeTypes << true << true << true; QTest::newRow("selected, only bogus") << bogusMimeTypes << true << true << false; QTest::newRow("no selected attribute") << allMimeTypes << false << false << true; QTest::newRow("selected attribute (false)") << allMimeTypes << true << false << false; QTest::newRow("selected attribute (true)") << allMimeTypes << true << true << true; QTest::newRow("empty case") << QStringList() << false << false << false; } void shouldVerifyIfCollectionIsSelected() { // GIVEN QFETCH(QStringList, mimeTypes); QFETCH(bool, hasSelectedAttribute); QFETCH(bool, isSelected); Domain::DataSource::ContentTypes expectedContentTypes; if (mimeTypes.contains(QStringLiteral("text/x-vnd.akonadi.note"))) { expectedContentTypes |= Domain::DataSource::Notes; } if (mimeTypes.contains(QStringLiteral("application/x-vnd.akonadi.calendar.todo"))) { expectedContentTypes |= Domain::DataSource::Tasks; } // ... stored in a collection Akonadi::Collection collection(42); collection.setContentMimeTypes(mimeTypes); if (hasSelectedAttribute) { auto selectedAttribute = new Akonadi::ApplicationSelectedAttribute; selectedAttribute->setSelected(isSelected); collection.addAttribute(selectedAttribute); } // WHEN Akonadi::Serializer serializer; // THEN QFETCH(bool, expectedSelected); QCOMPARE(serializer.isSelectedCollection(collection), expectedSelected); } void shouldVerifyCollectionContents_data() { QTest::addColumn("mimeType"); QTest::addColumn("expectedNotes"); QTest::addColumn("expectedTasks"); QTest::newRow("task collection") << "application/x-vnd.akonadi.calendar.todo" << false << true; QTest::newRow("note collection") << "text/x-vnd.akonadi.note" << true << false; } void shouldVerifyCollectionContents() { // GIVEN // Data... QFETCH(QString, mimeType); // ... stored in a collection Akonadi::Collection collection(42); collection.setContentMimeTypes(QStringList() << mimeType); // WHEN Akonadi::Serializer serializer; QFETCH(bool, expectedNotes); QFETCH(bool, expectedTasks); // THEN QCOMPARE(serializer.isNoteCollection(collection), expectedNotes); QCOMPARE(serializer.isTaskCollection(collection), expectedTasks); } void shouldCreateTaskFromItem_data() { QTest::addColumn("summary"); QTest::addColumn("content"); QTest::addColumn("isDone"); - QTest::addColumn("doneDate"); - QTest::addColumn("startDate"); - QTest::addColumn("dueDate"); + QTest::addColumn("doneDate"); + QTest::addColumn("startDate"); + QTest::addColumn("dueDate"); QTest::addColumn("delegateName"); QTest::addColumn("delegateEmail"); - QTest::newRow("nominal case") << "summary" << "content" << false << QDateTime() << QDateTime(QDate(2013, 11, 24)) << QDateTime(QDate(2014, 03, 01)) << "John Doe" << "j@d.com"; - QTest::newRow("done case") << "summary" << "content" << true << QDateTime(QDate(2013, 11, 30)) << QDateTime(QDate(2013, 11, 24)) << QDateTime(QDate(2014, 03, 01)) << "John Doe" << "j@d.com"; - QTest::newRow("done without doneDate case") << "summary" << "content" << true << QDateTime() << QDateTime(QDate(2013, 11, 24)) << QDateTime(QDate(2014, 03, 01)) << "John Doe" << "j@d.com"; - QTest::newRow("empty case") << QString() << QString() << false << QDateTime() << QDateTime() << QDateTime() << QString() << QString(); + QTest::newRow("nominal case") << "summary" << "content" << false << QDate() << QDate(2013, 11, 24) << QDate(2014, 03, 01) << "John Doe" << "j@d.com"; + QTest::newRow("done case") << "summary" << "content" << true << QDate(2013, 11, 30) << QDate(2013, 11, 24) << QDate(2014, 03, 01) << "John Doe" << "j@d.com"; + QTest::newRow("done without doneDate case") << "summary" << "content" << true << QDate() << QDate(2013, 11, 24) << QDate(2014, 03, 01) << "John Doe" << "j@d.com"; + QTest::newRow("empty case") << QString() << QString() << false << QDate() << QDate() << QDate() << QString() << QString(); } void shouldCreateTaskFromItem() { // GIVEN // Data... QFETCH(QString, summary); QFETCH(QString, content); QFETCH(bool, isDone); - QFETCH(QDateTime, doneDate); - QFETCH(QDateTime, startDate); - QFETCH(QDateTime, dueDate); + QFETCH(QDate, doneDate); + QFETCH(QDate, startDate); + QFETCH(QDate, dueDate); QFETCH(QString, delegateName); QFETCH(QString, delegateEmail); - // Switch to UTC - doneDate.setTimeSpec(Qt::UTC); - startDate.setTimeSpec(Qt::UTC); - dueDate.setTimeSpec(Qt::UTC); - // ... stored in a todo... KCalCore::Todo::Ptr todo(new KCalCore::Todo); todo->setSummary(summary); todo->setDescription(content); if (isDone) - todo->setCompleted(KDateTime(doneDate)); + todo->setCompleted(QDateTime(doneDate)); else todo->setCompleted(isDone); setTodoDates(todo, startDate, dueDate); todo->setRelatedTo(QStringLiteral("my-uid")); if (!delegateName.isEmpty() || !delegateEmail.isEmpty()) { KCalCore::Attendee::Ptr attendee(new KCalCore::Attendee(delegateName, delegateEmail, true, KCalCore::Attendee::Accepted)); todo->addAttendee(attendee); } // ... as payload of an item Akonadi::Item item; item.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); item.setPayload(todo); // which has a parent collection Akonadi::Collection collection(43); item.setParentCollection(collection); // WHEN Akonadi::Serializer serializer; Domain::Task::Ptr task = serializer.createTaskFromItem(item); auto artifact = serializer.createArtifactFromItem(item).dynamicCast(); // THEN QCOMPARE(task->title(), summary); QCOMPARE(task->text(), content); QCOMPARE(task->isDone(), isDone); QCOMPARE(task->doneDate(), doneDate); QCOMPARE(task->startDate(), startDate); QCOMPARE(task->dueDate(), dueDate); QCOMPARE(task->property("todoUid").toString(), todo->uid()); QCOMPARE(task->property("relatedUid").toString(), todo->relatedTo()); QCOMPARE(task->property("itemId").toLongLong(), item.id()); QCOMPARE(task->property("parentCollectionId").toLongLong(), collection.id()); QCOMPARE(task->delegate().name(), delegateName); QCOMPARE(task->delegate().email(), delegateEmail); QVERIFY(!artifact.isNull()); QCOMPARE(artifact->title(), summary); QCOMPARE(artifact->text(), content); QCOMPARE(artifact->isDone(), isDone); QCOMPARE(artifact->doneDate(), doneDate); QCOMPARE(artifact->startDate(), startDate); QCOMPARE(artifact->dueDate(), dueDate); QCOMPARE(artifact->property("todoUid").toString(), todo->uid()); QCOMPARE(artifact->property("relatedUid").toString(), todo->relatedTo()); QCOMPARE(artifact->property("itemId").toLongLong(), item.id()); QCOMPARE(artifact->property("parentCollectionId").toLongLong(), collection.id()); QCOMPARE(artifact->delegate().name(), delegateName); QCOMPARE(artifact->delegate().email(), delegateEmail); } void shouldCreateNullTaskFromInvalidItem() { // GIVEN Akonadi::Item item; // WHEN Akonadi::Serializer serializer; Domain::Task::Ptr task = serializer.createTaskFromItem(item); auto artifact = serializer.createArtifactFromItem(item); // THEN QVERIFY(task.isNull()); QVERIFY(artifact.isNull()); } void shouldCreateNullTaskFromProjectItem() { // GIVEN // A todo with the project flag KCalCore::Todo::Ptr todo(new KCalCore::Todo); todo->setSummary(QStringLiteral("foo")); todo->setCustomProperty("Zanshin", "Project", QStringLiteral("1")); // ... as payload of an item Akonadi::Item item; item.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); item.setPayload(todo); // WHEN Akonadi::Serializer serializer; Domain::Task::Ptr task = serializer.createTaskFromItem(item); auto artifact = serializer.createArtifactFromItem(item); // THEN QVERIFY(task.isNull()); QVERIFY(artifact.isNull()); } void shouldUpdateTaskFromItem_data() { QTest::addColumn("updatedSummary"); QTest::addColumn("updatedContent"); QTest::addColumn("updatedDone"); - QTest::addColumn("updatedDoneDate"); - QTest::addColumn("updatedStartDate"); - QTest::addColumn("updatedDueDate"); + QTest::addColumn("updatedDoneDate"); + QTest::addColumn("updatedStartDate"); + QTest::addColumn("updatedDueDate"); QTest::addColumn("updatedRelated"); QTest::addColumn("updatedRecurs"); QTest::addColumn("updatedAttachmentData"); QTest::addColumn("updatedAttachmentUris"); QTest::addColumn("updatedAttachmentLabels"); QTest::addColumn("updatedAttachmentMimeTypes"); QTest::addColumn("updatedAttachmentIconNames"); QTest::addColumn("updatedDelegateName"); QTest::addColumn("updatedDelegateEmail"); QTest::addColumn("updatedRunning"); - QTest::newRow("no change") << "summary" << "content" << false << QDateTime() << QDateTime(QDate(2013, 11, 24)) << QDateTime(QDate(2014, 03, 01)) << "my-uid" << false << QByteArrayList() << QStringList() << QStringList() << QStringList() << QStringList() << "John Doe" << "j@d.com" << false; - QTest::newRow("changed") << "new summary" << "new content" << true << QDateTime(QDate(2013, 11, 28)) << QDateTime(QDate(2013, 11, 25)) << QDateTime(QDate(2014, 03, 02)) << "my-new-uid" << 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"}) << "John Smith" << "j@s.com" << false; - QTest::newRow("set_to_running") << "summary" << "content" << false << QDateTime() << QDateTime(QDate(2013, 11, 24)) << QDateTime(QDate(2014, 03, 01)) << "my-uid" << false << QByteArrayList() << QStringList() << QStringList() << QStringList() << QStringList() << "John Doe" << "j@d.com" << true; + QTest::newRow("no change") << "summary" << "content" << false << QDate() << QDate(2013, 11, 24) << QDate(2014, 03, 01) << "my-uid" << false << QByteArrayList() << QStringList() << QStringList() << QStringList() << QStringList() << "John Doe" << "j@d.com" << false; + QTest::newRow("changed") << "new summary" << "new content" << true << QDate(2013, 11, 28) << QDate(2013, 11, 25) << QDate(2014, 03, 02) << "my-new-uid" << 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"}) << "John Smith" << "j@s.com" << false; + QTest::newRow("set_to_running") << "summary" << "content" << false << QDate() << QDate(2013, 11, 24) << QDate(2014, 03, 01) << "my-uid" << false << QByteArrayList() << QStringList() << QStringList() << QStringList() << QStringList() << "John Doe" << "j@d.com" << true; } void shouldUpdateTaskFromItem() { // GIVEN // A todo... KCalCore::Todo::Ptr originalTodo(new KCalCore::Todo); originalTodo->setSummary(QStringLiteral("summary")); originalTodo->setDescription(QStringLiteral("content")); originalTodo->setCompleted(false); - setTodoDates(originalTodo, QDateTime(QDate(2013, 11, 24)), QDateTime(QDate(2014, 03, 01))); + setTodoDates(originalTodo, QDate(2013, 11, 24), QDate(2014, 03, 01)); originalTodo->setRelatedTo(QStringLiteral("my-uid")); KCalCore::Attendee::Ptr originalAttendee(new KCalCore::Attendee(QStringLiteral("John Doe"), QStringLiteral("j@d.com"), true, KCalCore::Attendee::Accepted)); originalTodo->addAttendee(originalAttendee); // ... as payload of an item... Akonadi::Item originalItem; originalItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); originalItem.setPayload(originalTodo); // ... which has a parent collection... Akonadi::Collection originalCollection(43); originalItem.setParentCollection(originalCollection); // ... deserialized as a task Akonadi::Serializer serializer; auto task = serializer.createTaskFromItem(originalItem); auto artifact = serializer.createArtifactFromItem(originalItem); // WHEN // Data... QFETCH(QString, updatedSummary); QFETCH(QString, updatedContent); QFETCH(bool, updatedDone); - QFETCH(QDateTime, updatedDoneDate); - QFETCH(QDateTime, updatedStartDate); - QFETCH(QDateTime, updatedDueDate); + QFETCH(QDate, updatedDoneDate); + QFETCH(QDate, updatedStartDate); + QFETCH(QDate, updatedDueDate); QFETCH(QString, updatedRelated); QFETCH(bool, updatedRecurs); QFETCH(QByteArrayList, updatedAttachmentData); QFETCH(QStringList, updatedAttachmentUris); QFETCH(QStringList, updatedAttachmentLabels); QFETCH(QStringList, updatedAttachmentMimeTypes); QFETCH(QStringList, updatedAttachmentIconNames); QFETCH(QString, updatedDelegateName); QFETCH(QString, updatedDelegateEmail); QFETCH(bool, updatedRunning); - // Switch to UTC - updatedDoneDate.setTimeSpec(Qt::UTC); - updatedStartDate.setTimeSpec(Qt::UTC); - updatedDueDate.setTimeSpec(Qt::UTC); - // ... in a new todo... KCalCore::Todo::Ptr updatedTodo(new KCalCore::Todo); updatedTodo->setSummary(updatedSummary); updatedTodo->setDescription(updatedContent); if (updatedDone) updatedTodo->setCompleted(KDateTime(updatedDoneDate)); else updatedTodo->setCompleted(updatedDone); setTodoDates(updatedTodo, updatedStartDate, updatedDueDate); updatedTodo->setRelatedTo(updatedRelated); if (updatedRecurs) updatedTodo->recurrence()->setDaily(1); for (int i = 0; i < updatedAttachmentData.size(); i++) { KCalCore::Attachment::Ptr attachment(new KCalCore::Attachment(QByteArray())); if (!updatedAttachmentData.at(i).isEmpty()) attachment->setDecodedData(updatedAttachmentData.at(i)); else attachment->setUri(updatedAttachmentUris.at(i)); attachment->setMimeType(updatedAttachmentMimeTypes.at(i)); attachment->setLabel(updatedAttachmentLabels.at(i)); updatedTodo->addAttachment(attachment); } if (!updatedDelegateName.isEmpty() || !updatedDelegateEmail.isEmpty()) { KCalCore::Attendee::Ptr updatedAttendee(new KCalCore::Attendee(updatedDelegateName, updatedDelegateEmail, true, KCalCore::Attendee::Accepted)); updatedTodo->addAttendee(updatedAttendee); } if (updatedRunning) { updatedTodo->setCustomProperty("Zanshin", "Running", "1"); } else { updatedTodo->removeCustomProperty("Zanshin", "Running"); } // ... as payload of a new item Akonadi::Item updatedItem; updatedItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); updatedItem.setPayload(updatedTodo); // ... which has a new parent collection Akonadi::Collection updatedCollection(45); updatedItem.setParentCollection(updatedCollection); serializer.updateTaskFromItem(task, updatedItem); serializer.updateArtifactFromItem(artifact, updatedItem); // THEN QCOMPARE(task->title(), updatedSummary); QCOMPARE(task->text(), updatedContent); QCOMPARE(task->isDone(), updatedDone); - QCOMPARE(task->doneDate(), updatedDoneDate.toUTC()); - QCOMPARE(task->startDate(), updatedStartDate.toUTC()); - QCOMPARE(task->dueDate(), updatedDueDate.toUTC()); + QCOMPARE(task->doneDate(), updatedDoneDate); + QCOMPARE(task->startDate(), updatedStartDate); + QCOMPARE(task->dueDate(), updatedDueDate); QCOMPARE(task->property("todoUid").toString(), updatedTodo->uid()); QCOMPARE(task->property("relatedUid").toString(), updatedTodo->relatedTo()); QCOMPARE(task->property("itemId").toLongLong(), updatedItem.id()); QCOMPARE(task->property("parentCollectionId").toLongLong(), updatedCollection.id()); QCOMPARE(task->recurrence(), (updatedRecurs ? Domain::Task::RecursDaily : Domain::Task::NoRecurrence)); QCOMPARE(task->attachments().size(), updatedAttachmentData.size()); for (int i = 0; i < task->attachments().size(); i++) { const auto attachment = task->attachments().at(i); QCOMPARE(attachment.data(), updatedAttachmentData.at(i)); QCOMPARE(attachment.uri(), QUrl(updatedAttachmentUris.at(i))); QCOMPARE(attachment.label(), updatedAttachmentLabels.at(i)); QCOMPARE(attachment.mimeType(), updatedAttachmentMimeTypes.at(i)); QCOMPARE(attachment.iconName(), updatedAttachmentIconNames.at(i)); } QCOMPARE(task->delegate().name(), updatedDelegateName); QCOMPARE(task->delegate().email(), updatedDelegateEmail); QCOMPARE(task->isRunning(), updatedRunning); task = artifact.dynamicCast(); QCOMPARE(task->title(), updatedSummary); QCOMPARE(task->text(), updatedContent); QCOMPARE(task->isDone(), updatedDone); - QCOMPARE(task->doneDate(), updatedDoneDate.toUTC()); - QCOMPARE(task->startDate(), updatedStartDate.toUTC()); - QCOMPARE(task->dueDate(), updatedDueDate.toUTC()); + QCOMPARE(task->doneDate(), updatedDoneDate); + QCOMPARE(task->startDate(), updatedStartDate); + QCOMPARE(task->dueDate(), updatedDueDate); QCOMPARE(task->property("todoUid").toString(), updatedTodo->uid()); QCOMPARE(task->property("relatedUid").toString(), updatedTodo->relatedTo()); QCOMPARE(task->property("itemId").toLongLong(), updatedItem.id()); QCOMPARE(task->property("parentCollectionId").toLongLong(), updatedCollection.id()); QCOMPARE(task->recurrence(), (updatedRecurs ? Domain::Task::RecursDaily : Domain::Task::NoRecurrence)); QCOMPARE(task->attachments().size(), updatedAttachmentData.size()); for (int i = 0; i < task->attachments().size(); i++) { const auto attachment = task->attachments().at(i); QCOMPARE(attachment.data(), updatedAttachmentData.at(i)); QCOMPARE(attachment.uri(), QUrl(updatedAttachmentUris.at(i))); QCOMPARE(attachment.label(), updatedAttachmentLabels.at(i)); QCOMPARE(attachment.mimeType(), updatedAttachmentMimeTypes.at(i)); QCOMPARE(attachment.iconName(), updatedAttachmentIconNames.at(i)); } QCOMPARE(task->delegate().name(), updatedDelegateName); QCOMPARE(task->delegate().email(), updatedDelegateEmail); QCOMPARE(task->isRunning(), updatedRunning); } void shouldUpdateTaskRecurrenceFromItem_data() { QTest::addColumn("todoRecurrence"); QTest::addColumn("expectedRecurrence"); QTest::newRow("none") << int(KCalCore::Recurrence::rNone) << Domain::Task::NoRecurrence; QTest::newRow("minutely") << int(KCalCore::Recurrence::rMinutely) << Domain::Task::NoRecurrence; QTest::newRow("hourly") << int(KCalCore::Recurrence::rHourly) << Domain::Task::NoRecurrence; QTest::newRow("daily") << int(KCalCore::Recurrence::rDaily) << Domain::Task::RecursDaily; QTest::newRow("weekly") << int(KCalCore::Recurrence::rWeekly) << Domain::Task::RecursWeekly; QTest::newRow("monthly") << int(KCalCore::Recurrence::rMonthlyDay) << Domain::Task::RecursMonthly; } void shouldUpdateTaskRecurrenceFromItem() { // GIVEN // A todo... KCalCore::Todo::Ptr todo(new KCalCore::Todo); todo->setSummary(QStringLiteral("summary")); QFETCH(int, todoRecurrence); switch (todoRecurrence) { case KCalCore::Recurrence::rNone: break; case KCalCore::Recurrence::rMinutely: todo->recurrence()->setMinutely(1); break; case KCalCore::Recurrence::rHourly: todo->recurrence()->setHourly(1); break; case KCalCore::Recurrence::rDaily: todo->recurrence()->setDaily(1); break; case KCalCore::Recurrence::rWeekly: todo->recurrence()->setWeekly(1); break; case KCalCore::Recurrence::rMonthlyDay: todo->recurrence()->setMonthly(1); break; default: qFatal("Shouldn't happen"); } // ... as payload of an item... Akonadi::Item item; item.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); item.setPayload(todo); // ... which has a parent collection... Akonadi::Collection collection(43); item.setParentCollection(collection); // WHEN // ... deserialized as a task Akonadi::Serializer serializer; auto task = serializer.createTaskFromItem(item); // THEN QFETCH(Domain::Task::Recurrence, expectedRecurrence); QCOMPARE(task->recurrence(), expectedRecurrence); } void shouldNotBreakRecurrenceDuringSerialization() { // GIVEN // Data... - const QDateTime today(QDate::currentDate(), QTime(0, 0), Qt::UTC); - const QDateTime doneDate(QDate(2013, 11, 20), QTime(0, 0), Qt::UTC); - const QDateTime startDate(QDate(2013, 11, 10), QTime(0, 0), Qt::UTC); + const QDate today(QDate::currentDate()); + const QDate doneDate(2013, 11, 20); + const QDate startDate(2013, 11, 10); // ... stored in a todo... KCalCore::Todo::Ptr todo(new KCalCore::Todo); todo->setSummary(QStringLiteral("summary")); #if KCALCORE_VERSION >= QT_VERSION_CHECK(5, 6, 80) - todo->setDtStart(startDate); + todo->setDtStart(QDateTime(startDate)); #else todo->setDtStart(KDateTime(startDate, KDateTime::UTC)); #endif todo->recurrence()->setMonthly(1); // ... as payload of an item... Akonadi::Item item; item.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); item.setPayload(todo); // ... deserialized as a task Akonadi::Serializer serializer; auto task = serializer.createTaskFromItem(item); // WHEN // Task is marked done... task->setDoneDate(doneDate); task->setDone(true); // and goes through serialization and back const auto newItem = serializer.createItemFromTask(task); serializer.updateTaskFromItem(task, newItem); // THEN QCOMPARE(task->recurrence(), Domain::Task::RecursMonthly); QVERIFY(!task->isDone()); - const QDateTime lastOccurrence(QDate(today.date().year(), today.date().month(), 10), QTime(0, 0), Qt::UTC); - if (today.date().day() >= 10) + const QDate lastOccurrence(QDate(today.year(), today.month(), 10)); + if (today.day() >= 10) QCOMPARE(task->startDate(), lastOccurrence.addMonths(1)); else QCOMPARE(task->startDate(), lastOccurrence); } void shouldNotUpdateTaskFromInvalidItem() { // GIVEN // Data... const QString summary = QStringLiteral("summary"); const QString content = QStringLiteral("content"); const bool isDone = true; - const QDateTime doneDate(QDate(2013, 11, 30), QTime(0, 0), Qt::UTC); - const QDateTime startDate(QDate(2013, 11, 24), QTime(0, 0), Qt::UTC); - const QDateTime dueDate(QDate(2014, 03, 01), QTime(0, 0), Qt::UTC); + const QDate doneDate(2013, 11, 30); + const QDate startDate(2013, 11, 24); + const QDate dueDate(2014, 03, 01); // ... stored in a todo... KCalCore::Todo::Ptr originalTodo(new KCalCore::Todo); originalTodo->setSummary(summary); originalTodo->setDescription(content); if (originalTodo) originalTodo->setCompleted(KDateTime(doneDate)); else originalTodo->setCompleted(isDone); setTodoDates(originalTodo, startDate, dueDate); // ... as payload of an item... Akonadi::Item originalItem; originalItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); originalItem.setPayload(originalTodo); // ... deserialized as a task Akonadi::Serializer serializer; auto task = serializer.createTaskFromItem(originalItem); auto artifact = serializer.createArtifactFromItem(originalItem); // WHEN Akonadi::Item invalidItem; serializer.updateTaskFromItem(task, invalidItem); serializer.updateArtifactFromItem(artifact, invalidItem); // THEN QCOMPARE(task->title(), summary); QCOMPARE(task->text(), content); QCOMPARE(task->isDone(), isDone); QCOMPARE(task->doneDate(), doneDate); QCOMPARE(task->startDate(), startDate); QCOMPARE(task->dueDate(), dueDate); QCOMPARE(task->property("itemId").toLongLong(), originalItem.id()); task = artifact.dynamicCast(); QCOMPARE(task->title(), summary); QCOMPARE(task->text(), content); QCOMPARE(task->isDone(), isDone); QCOMPARE(task->doneDate(), doneDate); QCOMPARE(task->startDate(), startDate); QCOMPARE(task->dueDate(), dueDate); QCOMPARE(task->property("itemId").toLongLong(), originalItem.id()); } void shouldNotUpdateTaskFromProjectItem() { // GIVEN // Data... const QString summary = QStringLiteral("summary"); const QString content = QStringLiteral("content"); const bool isDone = true; - const QDateTime doneDate(QDate(2013, 11, 30), QTime(0, 0), Qt::UTC); - const QDateTime startDate(QDate(2013, 11, 24), QTime(0, 0), Qt::UTC); - const QDateTime dueDate(QDate(2014, 03, 01), QTime(0, 0), Qt::UTC); + const QDate doneDate(2013, 11, 30); + const QDate startDate(2013, 11, 24); + const QDate dueDate(2014, 03, 01); // ... stored in a todo... KCalCore::Todo::Ptr originalTodo(new KCalCore::Todo); originalTodo->setSummary(summary); originalTodo->setDescription(content); if (originalTodo) originalTodo->setCompleted(KDateTime(doneDate)); else originalTodo->setCompleted(isDone); setTodoDates(originalTodo, startDate, dueDate); // ... as payload of an item... Akonadi::Item originalItem; originalItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); originalItem.setPayload(originalTodo); // ... deserialized as a task Akonadi::Serializer serializer; auto task = serializer.createTaskFromItem(originalItem); auto artifact = serializer.createArtifactFromItem(originalItem); // WHEN // A todo with the project flag KCalCore::Todo::Ptr projectTodo(new KCalCore::Todo); projectTodo->setSummary(QStringLiteral("foo")); projectTodo->setCustomProperty("Zanshin", "Project", QStringLiteral("1")); // ... as payload of an item Akonadi::Item projectItem; projectItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); projectItem.setPayload(projectTodo); serializer.updateTaskFromItem(task, projectItem); serializer.updateArtifactFromItem(artifact, projectItem); // THEN QCOMPARE(task->title(), summary); QCOMPARE(task->text(), content); QCOMPARE(task->isDone(), isDone); QCOMPARE(task->doneDate(), doneDate); QCOMPARE(task->startDate(), startDate); QCOMPARE(task->dueDate(), dueDate); QCOMPARE(task->property("itemId").toLongLong(), originalItem.id()); task = artifact.dynamicCast(); QCOMPARE(task->title(), summary); QCOMPARE(task->text(), content); QCOMPARE(task->isDone(), isDone); QCOMPARE(task->doneDate(), doneDate); QCOMPARE(task->startDate(), startDate); QCOMPARE(task->dueDate(), dueDate); QCOMPARE(task->property("itemId").toLongLong(), originalItem.id()); } void shouldCreateItemFromTask_data() { QTest::addColumn("summary"); QTest::addColumn("content"); QTest::addColumn("isDone"); - QTest::addColumn("doneDate"); - QTest::addColumn("startDate"); - QTest::addColumn("dueDate"); + QTest::addColumn("doneDate"); + QTest::addColumn("startDate"); + QTest::addColumn("dueDate"); QTest::addColumn("itemId"); QTest::addColumn("parentCollectionId"); QTest::addColumn("todoUid"); QTest::addColumn("recurrence"); QTest::addColumn("attachments"); QTest::addColumn("delegate"); QTest::addColumn("running"); Domain::Task::Attachments attachments; Domain::Task::Attachment dataAttachment; dataAttachment.setData("foo"); dataAttachment.setLabel("dataAttachment"); dataAttachment.setMimeType("text/plain"); dataAttachment.setIconName("text-plain"); attachments.append(dataAttachment); Domain::Task::Attachment uriAttachment; uriAttachment.setUri(QUrl("https://www.kde.org")); uriAttachment.setLabel("uriAttachment"); uriAttachment.setMimeType("text/html"); uriAttachment.setIconName("text-html"); attachments.append(uriAttachment); - QTest::newRow("nominal case (no id)") << "summary" << "content" << false << QDateTime() - << QDateTime(QDate(2013, 11, 24)) << QDateTime(QDate(2014, 03, 01)) + QTest::newRow("nominal case (no id)") << "summary" << "content" << false << QDate() + << QDate(2013, 11, 24) << QDate(2014, 03, 01) << qint64(-1) << qint64(-1) << QString() << Domain::Task::NoRecurrence << attachments << Domain::Task::Delegate(QStringLiteral("John Doe"), QStringLiteral("j@d.com")) << false; - QTest::newRow("nominal case (daily)") << "summary" << "content" << false << QDateTime() - << QDateTime(QDate(2013, 11, 24)) << QDateTime(QDate(2014, 03, 01)) + QTest::newRow("nominal case (daily)") << "summary" << "content" << false << QDate() + << QDate(2013, 11, 24) << QDate(2014, 03, 01) << qint64(-1) << qint64(-1) << QString() << Domain::Task::RecursDaily << Domain::Task::Attachments() << Domain::Task::Delegate(QStringLiteral("John Doe"), QStringLiteral("j@d.com")) << false; - QTest::newRow("nominal case (weekly)") << "summary" << "content" << false << QDateTime() - << QDateTime(QDate(2013, 11, 24)) << QDateTime(QDate(2014, 03, 01)) + QTest::newRow("nominal case (weekly)") << "summary" << "content" << false << QDate() + << QDate(2013, 11, 24) << QDate(2014, 03, 01) << qint64(-1) << qint64(-1) << QString() << Domain::Task::RecursWeekly << Domain::Task::Attachments() << Domain::Task::Delegate(QStringLiteral("John Doe"), QStringLiteral("j@d.com")) << false; - QTest::newRow("nominal case (monthly)") << "summary" << "content" << false << QDateTime() - << QDateTime(QDate(2013, 11, 24)) << QDateTime(QDate(2014, 03, 01)) + QTest::newRow("nominal case (monthly)") << "summary" << "content" << false << QDate() + << QDate(2013, 11, 24) << QDate(2014, 03, 01) << qint64(-1) << qint64(-1) << QString() << Domain::Task::RecursMonthly << Domain::Task::Attachments() << Domain::Task::Delegate(QStringLiteral("John Doe"), QStringLiteral("j@d.com")) << false; - QTest::newRow("done case (no id)") << "summary" << "content" << true << QDateTime(QDate(2013, 11, 30)) - << QDateTime(QDate(2013, 11, 24)) << QDateTime(QDate(2014, 03, 01)) + QTest::newRow("done case (no id)") << "summary" << "content" << true << QDate(2013, 11, 30) + << QDate(2013, 11, 24) << QDate(2014, 03, 01) << qint64(-1) << qint64(-1) << QString() << Domain::Task::NoRecurrence << Domain::Task::Attachments() << Domain::Task::Delegate(QStringLiteral("John Doe"), QStringLiteral("j@d.com")) << false; - QTest::newRow("empty case (no id)") << QString() << QString() << false << QDateTime() - << QDateTime() << QDateTime() + QTest::newRow("empty case (no id)") << QString() << QString() << false << QDate() + << QDate() << QDate() << qint64(-1) << qint64(-1) << QString() << Domain::Task::NoRecurrence << Domain::Task::Attachments() << Domain::Task::Delegate() << false; #if 0 // if we ever need time info, then we need a Task::setAllDay(bool) just like KCalCore::Todo has. QTest::newRow("nominal_with_time_info_noid") << "summary" << "content" << true << QDateTime(QDate(2015, 3, 1), QTime(1, 2, 3), Qt::UTC) << QDateTime(QDate(2013, 11, 24), QTime(0, 1, 2), Qt::UTC) << QDateTime(QDate(2016, 3, 1), QTime(4, 5, 6), Qt::UTC) << qint64(-1) << qint64(-1) << QString() << Domain::Task::NoRecurrence << Domain::Task::Attachments() << Domain::Task::Delegate(QStringLiteral("John Doe"), QStringLiteral("j@d.com")) << false; #endif - QTest::newRow("nominal case (with id)") << "summary" << "content" << false << QDateTime() - << QDateTime(QDate(2013, 11, 24)) << QDateTime(QDate(2014, 03, 01)) + QTest::newRow("nominal case (with id)") << "summary" << "content" << false << QDate() + << QDate(2013, 11, 24) << QDate(2014, 03, 01) << qint64(42) << qint64(43) << "my-uid" << Domain::Task::NoRecurrence << Domain::Task::Attachments() << Domain::Task::Delegate(QStringLiteral("John Doe"), QStringLiteral("j@d.com")) << false; - QTest::newRow("done case (with id)") << "summary" << "content" << true << QDateTime(QDate(2013, 11, 30)) - << QDateTime(QDate(2013, 11, 24)) << QDateTime(QDate(2014, 03, 01)) + QTest::newRow("done case (with id)") << "summary" << "content" << true << QDate(2013, 11, 30) + << QDate(2013, 11, 24) << QDate(2014, 03, 01) << qint64(42) << qint64(43) << "my-uid" << Domain::Task::NoRecurrence << Domain::Task::Attachments() << Domain::Task::Delegate(QStringLiteral("John Doe"), QStringLiteral("j@d.com")) << false; - QTest::newRow("empty case (with id)") << QString() << QString() << false << QDateTime() - << QDateTime() << QDateTime() + QTest::newRow("empty case (with id)") << QString() << QString() << false << QDate() + << QDate() << QDate() << qint64(42) << qint64(43) << "my-uid" << Domain::Task::NoRecurrence << Domain::Task::Attachments() << Domain::Task::Delegate() << false; - QTest::newRow("nominal case (running)") << "running" << QString() << false << QDateTime() - << QDateTime(QDate(2013, 11, 24)) << QDateTime(QDate(2014, 03, 01)) + QTest::newRow("nominal case (running)") << "running" << QString() << false << QDate() + << QDate(2013, 11, 24) << QDate(2014, 03, 01) << qint64(-1) << qint64(-1) << QString() << Domain::Task::NoRecurrence << Domain::Task::Attachments() << Domain::Task::Delegate() << true; } void shouldCreateItemFromTask() { // GIVEN // Data... QFETCH(QString, summary); QFETCH(QString, content); QFETCH(bool, isDone); - QFETCH(QDateTime, doneDate); - QFETCH(QDateTime, startDate); - QFETCH(QDateTime, dueDate); + QFETCH(QDate, doneDate); + QFETCH(QDate, startDate); + QFETCH(QDate, dueDate); QFETCH(qint64, itemId); QFETCH(qint64, parentCollectionId); QFETCH(QString, todoUid); QFETCH(Domain::Task::Recurrence, recurrence); QFETCH(Domain::Task::Attachments, attachments); QFETCH(Domain::Task::Delegate, delegate); QFETCH(bool, running); - // Switch to UTC - doneDate.setTimeSpec(Qt::UTC); - startDate.setTimeSpec(Qt::UTC); - dueDate.setTimeSpec(Qt::UTC); - // ... stored in a task auto task = Domain::Task::Ptr::create(); task->setTitle(summary); task->setText(content); task->setDone(isDone); task->setDoneDate(doneDate); task->setStartDate(startDate); task->setDueDate(dueDate); task->setRecurrence(recurrence); task->setAttachments(attachments); task->setDelegate(delegate); task->setRunning(running); if (itemId > 0) task->setProperty("itemId", itemId); if (parentCollectionId > 0) task->setProperty("parentCollectionId", parentCollectionId); if (!todoUid.isEmpty()) task->setProperty("todoUid", todoUid); task->setProperty("relatedUid", "parent-uid"); // WHEN Akonadi::Serializer serializer; auto item = serializer.createItemFromTask(task); // THEN QCOMPARE(item.mimeType(), KCalCore::Todo::todoMimeType()); QCOMPARE(item.isValid(), itemId > 0); if (itemId > 0) { QCOMPARE(item.id(), itemId); } QCOMPARE(item.parentCollection().isValid(), parentCollectionId > 0); if (parentCollectionId > 0) { QCOMPARE(item.parentCollection().id(), parentCollectionId); } auto todo = item.payload(); QCOMPARE(todo->summary(), summary); QCOMPARE(todo->description(), content); QCOMPARE(todo->isCompleted(), isDone); #if KCALCORE_VERSION >= QT_VERSION_CHECK(5, 6, 80) - QCOMPARE(todo->completed().toUTC(), doneDate); - QCOMPARE(todo->dtStart().toUTC(), startDate); - QCOMPARE(todo->dtDue().toUTC(), dueDate); + QCOMPARE(todo->completed().toLocalTime().date(), doneDate); + QCOMPARE(todo->dtStart().toLocalTime().date(), startDate); + QCOMPARE(todo->dtDue().toLocalTime().date(), dueDate); if (todo->dtStart().isValid()) { - QCOMPARE(int(todo->dtStart().timeSpec()), int(Qt::UTC)); + QCOMPARE(int(todo->dtStart().timeSpec()), int(Qt::LocalTime)); } QVERIFY(todo->allDay()); // this is always true currently... #else - QCOMPARE(todo->completed().dateTime().toUTC(), doneDate); - QCOMPARE(todo->dtStart().dateTime().toUTC(), startDate); - QCOMPARE(todo->dtDue().dateTime().toUTC(), dueDate); + QCOMPARE(todo->completed().dateTime().date(), doneDate); + QCOMPARE(todo->dtStart().dateTime().date(), startDate); + QCOMPARE(todo->dtDue().dateTime().date(), dueDate); if (todo->dtStart().isValid()) { - QCOMPARE(int(todo->dtStart().timeType()), int(KDateTime::UTC)); + QCOMPARE(int(todo->dtStart().timeType()), int(KDateTime::LocalTime)); } QCOMPARE(todo->dtStart().isDateOnly(), todo->allDay()); #endif const ushort expectedRecurrence = recurrence == Domain::Task::NoRecurrence ? KCalCore::Recurrence::rNone : recurrence == Domain::Task::RecursDaily ? KCalCore::Recurrence::rDaily : recurrence == Domain::Task::RecursWeekly ? KCalCore::Recurrence::rWeekly : recurrence == Domain::Task::RecursMonthly ? KCalCore::Recurrence::rMonthlyDay : KCalCore::Recurrence::rNone; // Shouldn't happen though QCOMPARE(todo->recurrence()->recurrenceType(), expectedRecurrence); if (recurrence != Domain::Task::NoRecurrence) QCOMPARE(todo->recurrence()->frequency(), 1); QCOMPARE(todo->attachments().size(), attachments.size()); for (int i = 0; i < attachments.size(); i++) { auto attachment = todo->attachments().at(i); QCOMPARE(attachment->isUri(), attachments.at(i).isUri()); QCOMPARE(QUrl(attachment->uri()), attachments.at(i).uri()); QCOMPARE(attachment->decodedData(), attachments.at(i).data()); QCOMPARE(attachment->label(), attachments.at(i).label()); QCOMPARE(attachment->mimeType(), attachments.at(i).mimeType()); } if (delegate.isValid()) { auto attendee = todo->attendeeByMail(delegate.email()); QVERIFY(attendee); QCOMPARE(attendee->name(), delegate.name()); QCOMPARE(attendee->email(), delegate.email()); } if (!todoUid.isEmpty()) { QCOMPARE(todo->uid(), todoUid); } QCOMPARE(todo->relatedTo(), QStringLiteral("parent-uid")); QCOMPARE(todo->customProperty("Zanshin", "Running"), running ? QStringLiteral("1") : QString()); } void shouldVerifyIfAnItemIsATaskChild_data() { QTest::addColumn("task"); QTest::addColumn("item"); QTest::addColumn("isParent"); // Create task const QString summary = QStringLiteral("summary"); const QString content = QStringLiteral("content"); const bool isDone = true; - const QDateTime doneDate(QDate(2013, 11, 30), QTime(0, 0), Qt::UTC); - const QDateTime startDate(QDate(2013, 11, 24), QTime(0, 0), Qt::UTC); - const QDateTime dueDate(QDate(2014, 03, 01), QTime(0, 0), Qt::UTC); + const QDate doneDate(QDate(2013, 11, 30)); + const QDate startDate(QDate(2013, 11, 24)); + const QDate dueDate(QDate(2014, 03, 01)); // ... create a task Domain::Task::Ptr task(new Domain::Task); task->setTitle(summary); task->setText(content); task->setDone(isDone); task->setDoneDate(doneDate); task->setStartDate(startDate); task->setDueDate(dueDate); task->setProperty("todoUid", "1"); // Create Child item KCalCore::Todo::Ptr childTodo(new KCalCore::Todo); childTodo->setSummary(summary); childTodo->setDescription(content); if (isDone) childTodo->setCompleted(KDateTime(doneDate)); else childTodo->setCompleted(isDone); setTodoDates(childTodo, startDate, dueDate); Akonadi::Item childItem; childItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); childItem.setPayload(childTodo); QTest::newRow("without parent") << task << childItem << false; // Create Child Item with parent KCalCore::Todo::Ptr childTodo2(new KCalCore::Todo); childTodo2->setSummary(summary); childTodo2->setDescription(content); if (isDone) childTodo2->setCompleted(KDateTime(doneDate)); else childTodo2->setCompleted(isDone); setTodoDates(childTodo2, startDate, dueDate); childTodo2->setRelatedTo(QStringLiteral("1")); Akonadi::Item childItem2; childItem2.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); childItem2.setPayload(childTodo2); QTest::newRow("with parent") << task << childItem2 << true; Domain::Task::Ptr invalidTask(new Domain::Task); QTest::newRow("with invalid task") << invalidTask << childItem << false; Akonadi::Item invalidItem; QTest::newRow("with invalid item") << task << invalidItem << false; } void shouldVerifyIfAnItemIsATaskChild() { // GIVEN QFETCH(Domain::Task::Ptr, task); QFETCH(Akonadi::Item, item); QFETCH(bool, isParent); // WHEN Akonadi::Serializer serializer; bool value = serializer.isTaskChild(task, item); // THEN QCOMPARE(value, isParent); } void shouldRetrieveRelatedUidFromItem_data() { QTest::addColumn("item"); QTest::addColumn("expectedUid"); Akonadi::Item item1; KCalCore::Todo::Ptr todo1(new KCalCore::Todo); item1.setPayload(todo1); Akonadi::Item item2; KCalCore::Todo::Ptr todo2(new KCalCore::Todo); todo2->setRelatedTo(QStringLiteral("1")); item2.setPayload(todo2); Akonadi::Item item3; KMime::Message::Ptr message1(new KMime::Message); message1->subject(true)->fromUnicodeString(QStringLiteral("foo"), "utf-8"); message1->mainBodyPart()->fromUnicodeString(QStringLiteral("bar")); item3.setMimeType(Akonadi::NoteUtils::noteMimeType()); item3.setPayload(message1); Akonadi::Item item4; KMime::Message::Ptr message2(new KMime::Message); message2->subject(true)->fromUnicodeString(QStringLiteral("foo"), "utf-8"); message2->mainBodyPart()->fromUnicodeString(QStringLiteral("bar")); auto relatedHeader1 = new KMime::Headers::Generic("X-Zanshin-RelatedProjectUid"); relatedHeader1->from7BitString("1"); message2->appendHeader(relatedHeader1); item4.setMimeType(Akonadi::NoteUtils::noteMimeType()); item4.setPayload(message2); Akonadi::Item item5; KMime::Message::Ptr message3(new KMime::Message); message3->subject(true)->fromUnicodeString(QStringLiteral("foo"), "utf-8"); message3->mainBodyPart()->fromUnicodeString(QStringLiteral("bar")); auto relatedHeader2 = new KMime::Headers::Generic("X-Zanshin-RelatedProjectUid"); message3->appendHeader(relatedHeader2); item5.setMimeType(Akonadi::NoteUtils::noteMimeType()); item5.setPayload(message3); QTest::newRow("task without related") << item1 << QString(); QTest::newRow("task with related") << item2 << "1"; QTest::newRow("note without related") << item3 << QString(); QTest::newRow("note with related") << item4 << "1"; QTest::newRow("note with empty related") << item5 << QString(); } void shouldRetrieveRelatedUidFromItem() { // GIVEN QFETCH(Akonadi::Item, item); QFETCH(QString, expectedUid); // WHEN Akonadi::Serializer serializer; QString uid = serializer.relatedUidFromItem(item); // THEN QCOMPARE(uid, expectedUid); } void shouldCreateNoteFromItem_data() { QTest::addColumn("title"); QTest::addColumn("text"); QTest::addColumn("relatedUid"); QTest::newRow("nominal case (no related)") << "A note title" << "A note content.\nWith two lines." << QString(); QTest::newRow("nominal case (with related)") << "A note title" << "A note content.\nWith two lines." << "parent-uid"; QTest::newRow("trailing new lines") << "A note title" << "Empty lines at the end.\n\n\n" << QString(); QTest::newRow("empty case") << QString() << QString() << QString(); } void shouldCreateNoteFromItem() { // GIVEN // Data... QFETCH(QString, title); QFETCH(QString, text); QFETCH(QString, relatedUid); // ... stored in a message... KMime::Message::Ptr message(new KMime::Message); message->subject(true)->fromUnicodeString(title, "utf-8"); message->mainBodyPart()->fromUnicodeString(text); if (!relatedUid.isEmpty()) { auto relatedHeader = new KMime::Headers::Generic("X-Zanshin-RelatedProjectUid"); relatedHeader->from7BitString(relatedUid.toUtf8()); message->appendHeader(relatedHeader); } // ... as payload of an item. Akonadi::Item item; item.setMimeType(Akonadi::NoteUtils::noteMimeType()); item.setPayload(message); // WHEN Akonadi::Serializer serializer; Domain::Note::Ptr note = serializer.createNoteFromItem(item); auto artifact = serializer.createArtifactFromItem(item).dynamicCast(); // THEN const auto expectedText = text.endsWith('\n') ? (text.chop(1), text) : text; QCOMPARE(note->title(), title); QCOMPARE(note->text(), expectedText); QCOMPARE(note->property("itemId").toLongLong(), item.id()); QCOMPARE(note->property("relatedUid").toString(), relatedUid); QVERIFY(!artifact.isNull()); QCOMPARE(artifact->title(), title); QCOMPARE(artifact->text(), expectedText); QCOMPARE(artifact->property("itemId").toLongLong(), item.id()); QCOMPARE(artifact->property("relatedUid").toString(), relatedUid); } void shouldCreateNullNoteFromInvalidItem() { // GIVEN Akonadi::Item item; // WHEN Akonadi::Serializer serializer; Domain::Note::Ptr note = serializer.createNoteFromItem(item); auto artifact = serializer.createArtifactFromItem(item); // THEN QVERIFY(note.isNull()); QVERIFY(artifact.isNull()); } void shouldUpdateNoteFromItem_data() { QTest::addColumn("updatedTitle"); QTest::addColumn("updatedText"); QTest::addColumn("updatedRelatedUid"); QTest::newRow("no change") << "title" << "content" << "parent-uid"; QTest::newRow("data changed (with related)") << "A new title" << "A new content" << "new-parent-uid"; QTest::newRow("data changed (with no related)") << "A new title" << "A new content" << QString(); } void shouldUpdateNoteFromItem() { // GIVEN // A message... KMime::Message::Ptr message(new KMime::Message); message->subject(true)->fromUnicodeString(QStringLiteral("title"), "utf-8"); message->mainBodyPart()->fromUnicodeString(QStringLiteral("text")); auto relatedHeader = new KMime::Headers::Generic("X-Zanshin-RelatedProjectUid"); relatedHeader->from7BitString("parent-uid"); message->appendHeader(relatedHeader); //... as the payload of an item... Akonadi::Item item; item.setMimeType(Akonadi::NoteUtils::noteMimeType()); item.setPayload(message); //... deserialized as a note Akonadi::Serializer serializer; auto note = serializer.createNoteFromItem(item); auto artifact = serializer.createNoteFromItem(item); // WHEN // Data... QFETCH(QString, updatedTitle); QFETCH(QString, updatedText); QFETCH(QString, updatedRelatedUid); //... stored in a new message... KMime::Message::Ptr updatedMessage(new KMime::Message); updatedMessage->subject(true)->fromUnicodeString(updatedTitle, "utf-8"); updatedMessage->mainBodyPart()->fromUnicodeString(updatedText); if (!updatedRelatedUid.isEmpty()) { relatedHeader = new KMime::Headers::Generic("X-Zanshin-RelatedProjectUid"); relatedHeader->from7BitString(updatedRelatedUid.toUtf8()); updatedMessage->appendHeader(relatedHeader); } //... as the payload of a new item... Akonadi::Item updatedItem; updatedItem.setMimeType(Akonadi::NoteUtils::noteMimeType()); updatedItem.setPayload(updatedMessage); serializer.updateNoteFromItem(note, updatedItem); serializer.updateArtifactFromItem(artifact, updatedItem); // THEN QCOMPARE(note->title(), updatedTitle); QCOMPARE(note->text(), updatedText); QCOMPARE(note->property("itemId").toLongLong(), updatedItem.id()); QCOMPARE(note->property("relatedUid").toString(), updatedRelatedUid); note = artifact.dynamicCast(); QCOMPARE(note->title(), updatedTitle); QCOMPARE(note->text(), updatedText); QCOMPARE(note->property("itemId").toLongLong(), updatedItem.id()); QCOMPARE(note->property("relatedUid").toString(), updatedRelatedUid); } void shouldNotUpdateNoteFromInvalidItem() { // GIVEN // Data... QString title = QStringLiteral("A title"); QString text = QStringLiteral("A note content"); // ... stored in a message... KMime::Message::Ptr message(new KMime::Message); message->subject(true)->fromUnicodeString(title, "utf-8"); message->mainBodyPart()->fromUnicodeString(text); //... as the payload of an item... Akonadi::Item item; item.setMimeType(Akonadi::NoteUtils::noteMimeType()); item.setPayload(message); //... deserialized as a note Akonadi::Serializer serializer; auto note = serializer.createNoteFromItem(item); auto artifact = serializer.createArtifactFromItem(item); // WHEN Akonadi::Item invalidItem; serializer.updateNoteFromItem(note, invalidItem); serializer.updateArtifactFromItem(artifact, invalidItem); //THEN QCOMPARE(note->title(), title); QCOMPARE(note->text(), text); QCOMPARE(note->property("itemId").toLongLong(), item.id()); note = artifact.dynamicCast(); QCOMPARE(note->title(), title); QCOMPARE(note->text(), text); QCOMPARE(note->property("itemId").toLongLong(), item.id()); } void shouldCreateItemFromNote_data() { QTest::addColumn("title"); QTest::addColumn("content"); QTest::addColumn("expectedTitle"); QTest::addColumn("expectedContent"); QTest::addColumn("itemId"); QTest::addColumn("relatedUid"); QTest::newRow("nominal case (no id)") << "title" << "content" << "title" << "content" << qint64(-1) << QString(); QTest::newRow("empty case (no id)") << QString() << QString() << "New Note" << QString() << qint64(-1) << QString(); QTest::newRow("nominal case (with id)") << "title" << "content" << "title" << "content" << qint64(42) << "parent-uid"; QTest::newRow("empty case (with id)") << QString() << QString() << "New Note" << QString() << qint64(42) << "parent-uid"; QTest::newRow("empty line at the end") << "title" << "content\n\n\n" << "title" << "content\n\n\n" << qint64(-1) << QString(); } void shouldCreateItemFromNote() { // GIVEN // Data... QFETCH(QString, title); QFETCH(QString, content); QFETCH(qint64, itemId); QFETCH(QString, relatedUid); // ... stored in a note auto note = Domain::Note::Ptr::create(); note->setTitle(title); note->setText(content); if (itemId > 0) note->setProperty("itemId", itemId); if (!relatedUid.isEmpty()) note->setProperty("relatedUid", relatedUid); // WHEN Akonadi::Serializer serializer; auto item = serializer.createItemFromNote(note); // THEN QCOMPARE(item.mimeType(), Akonadi::NoteUtils::noteMimeType()); QCOMPARE(item.isValid(), itemId > 0); if (itemId > 0) { QCOMPARE(item.id(), itemId); } QFETCH(QString, expectedTitle); QFETCH(QString, expectedContent); auto message = item.payload(); QCOMPARE(message->subject(false)->asUnicodeString(), expectedTitle); QCOMPARE(message->mainBodyPart()->decodedText(), expectedContent); if (relatedUid.isEmpty()) { QVERIFY(!message->headerByType("X-Zanshin-RelatedProjectUid")); } else { QVERIFY(message->headerByType("X-Zanshin-RelatedProjectUid")); QCOMPARE(message->headerByType("X-Zanshin-RelatedProjectUid")->asUnicodeString(), relatedUid); } } void shouldCreateProjectFromItem_data() { QTest::addColumn("summary"); QTest::newRow("nominal case") << "summary"; QTest::newRow("empty case") << QString(); } void shouldCreateProjectFromItem() { // GIVEN // Data... QFETCH(QString, summary); // ... stored in a todo... KCalCore::Todo::Ptr todo(new KCalCore::Todo); todo->setSummary(summary); todo->setCustomProperty("Zanshin", "Project", QStringLiteral("1")); QVERIFY(!todo->uid().isEmpty()); // ... as payload of an item Akonadi::Item item(42); item.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); item.setPayload(todo); // which has a prent collection Akonadi::Collection collection(43); item.setParentCollection(collection); // WHEN Akonadi::Serializer serializer; Domain::Project::Ptr project = serializer.createProjectFromItem(item); // THEN QCOMPARE(project->name(), summary); QCOMPARE(project->property("itemId").toLongLong(), item.id()); QCOMPARE(project->property("parentCollectionId").toLongLong(), collection.id()); QCOMPARE(project->property("todoUid").toString(), todo->uid()); } void shouldCreateNullProjectFromInvalidItem() { // GIVEN Akonadi::Item item; // WHEN Akonadi::Serializer serializer; Domain::Project::Ptr project = serializer.createProjectFromItem(item); // THEN QVERIFY(project.isNull()); } void shouldCreateNullProjectFromTaskItem() { // GIVEN // A todo without the project flag KCalCore::Todo::Ptr todo(new KCalCore::Todo); todo->setSummary(QStringLiteral("foo")); // ... as payload of an item Akonadi::Item item; item.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); item.setPayload(todo); // WHEN Akonadi::Serializer serializer; Domain::Project::Ptr project = serializer.createProjectFromItem(item); // THEN QVERIFY(project.isNull()); } void shouldUpdateProjectFromItem_data() { QTest::addColumn("updatedSummary"); QTest::newRow("no change") << "summary"; QTest::newRow("changed") << "new summary"; } void shouldUpdateProjectFromItem() { // GIVEN // A todo... KCalCore::Todo::Ptr originalTodo(new KCalCore::Todo); originalTodo->setSummary(QStringLiteral("summary")); originalTodo->setCustomProperty("Zanshin", "Project", QStringLiteral("1")); // ... as payload of an item... Akonadi::Item originalItem(42); originalItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); originalItem.setPayload(originalTodo); // ... which has a parent collection... Akonadi::Collection originalCollection(43); originalItem.setParentCollection(originalCollection); // ... deserialized as a project Akonadi::Serializer serializer; auto project = serializer.createProjectFromItem(originalItem); // WHEN // Data... QFETCH(QString, updatedSummary); // ... in a new todo... KCalCore::Todo::Ptr updatedTodo(new KCalCore::Todo); updatedTodo->setSummary(updatedSummary); updatedTodo->setCustomProperty("Zanshin", "Project", QStringLiteral("1")); QVERIFY(!updatedTodo->uid().isEmpty()); // ... as payload of a new item Akonadi::Item updatedItem(44); updatedItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); updatedItem.setPayload(updatedTodo); // ... which has a new parent collection Akonadi::Collection updatedCollection(45); updatedItem.setParentCollection(updatedCollection); serializer.updateProjectFromItem(project, updatedItem); // THEN QCOMPARE(project->name(), updatedSummary); QCOMPARE(project->property("itemId").toLongLong(), updatedItem.id()); QCOMPARE(project->property("parentCollectionId").toLongLong(), updatedCollection.id()); QCOMPARE(project->property("todoUid").toString(), updatedTodo->uid()); } void shouldNotUpdateProjectFromInvalidItem() { // GIVEN // Data... const QString summary = QStringLiteral("summary"); // ... stored in a todo... KCalCore::Todo::Ptr originalTodo(new KCalCore::Todo); originalTodo->setSummary(summary); originalTodo->setCustomProperty("Zanshin", "Project", QStringLiteral("1")); // ... as payload of an item... Akonadi::Item originalItem; originalItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); originalItem.setPayload(originalTodo); // ... deserialized as a project Akonadi::Serializer serializer; auto project = serializer.createProjectFromItem(originalItem); // WHEN Akonadi::Item invalidItem; serializer.updateProjectFromItem(project, invalidItem); // THEN QCOMPARE(project->name(), summary); } void shouldNotUpdateProjectFromTaskItem() { // GIVEN // Data... const QString summary = QStringLiteral("summary"); // ... stored in a todo... KCalCore::Todo::Ptr originalTodo(new KCalCore::Todo); originalTodo->setSummary(summary); originalTodo->setCustomProperty("Zanshin", "Project", QStringLiteral("1")); // ... as payload of an item... Akonadi::Item originalItem; originalItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); originalItem.setPayload(originalTodo); // ... deserialized as a project Akonadi::Serializer serializer; auto project = serializer.createProjectFromItem(originalItem); // WHEN // A todo without the project flag KCalCore::Todo::Ptr projectTodo(new KCalCore::Todo); projectTodo->setSummary(QStringLiteral("foo")); // ... as payload of an item Akonadi::Item projectItem; projectItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); projectItem.setPayload(projectTodo); serializer.updateProjectFromItem(project, projectItem); // THEN QCOMPARE(project->name(), summary); } void shouldCreateItemFromProject_data() { QTest::addColumn("summary"); QTest::addColumn("itemId"); QTest::addColumn("parentCollectionId"); QTest::newRow("nominal case (no id)") << "summary" << qint64(-1) << qint64(-1); QTest::newRow("empty case (no id)") << QString() << qint64(-1) << qint64(-1); QTest::newRow("nominal case (with id)") << "summary" << qint64(42) << qint64(43); QTest::newRow("empty case (with id)") << QString() << qint64(42) << qint64(43); } void shouldCreateItemFromProject() { // GIVEN // Data... QFETCH(QString, summary); QFETCH(qint64, itemId); QFETCH(qint64, parentCollectionId); const QString todoUid = QStringLiteral("test-uid"); // ... stored in a project auto project = Domain::Project::Ptr::create(); project->setName(summary); project->setProperty("todoUid", todoUid); if (itemId > 0) project->setProperty("itemId", itemId); if (parentCollectionId > 0) project->setProperty("parentCollectionId", parentCollectionId); // WHEN Akonadi::Serializer serializer; auto item = serializer.createItemFromProject(project); // THEN QCOMPARE(item.mimeType(), KCalCore::Todo::todoMimeType()); QCOMPARE(item.isValid(), itemId > 0); if (itemId > 0) { QCOMPARE(item.id(), itemId); } QCOMPARE(item.parentCollection().isValid(), parentCollectionId > 0); if (parentCollectionId > 0) { QCOMPARE(item.parentCollection().id(), parentCollectionId); } auto todo = item.payload(); QCOMPARE(todo->summary(), summary); QCOMPARE(todo->uid(), todoUid); QVERIFY(!todo->customProperty("Zanshin", "Project").isEmpty()); } void shouldVerifyIfAnItemIsAProjectChild_data() { QTest::addColumn("project"); QTest::addColumn("item"); QTest::addColumn("isParent"); // Create project auto project = Domain::Project::Ptr::create(); project->setName(QStringLiteral("project")); project->setProperty("todoUid", "1"); // Create unrelated todo auto unrelatedTodo = KCalCore::Todo::Ptr::create(); unrelatedTodo->setSummary(QStringLiteral("summary")); Akonadi::Item unrelatedTodoItem; unrelatedTodoItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); unrelatedTodoItem.setPayload(unrelatedTodo); QTest::newRow("unrelated todo") << project << unrelatedTodoItem << false; // Create child todo auto childTodo = KCalCore::Todo::Ptr::create(); childTodo->setSummary(QStringLiteral("summary")); childTodo->setRelatedTo(QStringLiteral("1")); Akonadi::Item childTodoItem; childTodoItem.setMimeType(QStringLiteral("application/x-vnd.akonadi.calendar.todo")); childTodoItem.setPayload(childTodo); QTest::newRow("child todo") << project << childTodoItem << true; // Create unrelated note KMime::Message::Ptr unrelatedNote(new KMime::Message); unrelatedNote->subject(true)->fromUnicodeString(QStringLiteral("subject"), "utf-8"); Akonadi::Item unrelatedNoteItem; unrelatedNoteItem.setMimeType(Akonadi::NoteUtils::noteMimeType()); unrelatedNoteItem.setPayload(unrelatedNote); QTest::newRow("unrelated note") << project << unrelatedNoteItem << false; // Create child note KMime::Message::Ptr childNote(new KMime::Message); childNote->subject(true)->fromUnicodeString(QStringLiteral("subject"), "utf-8"); auto relatedHeader = new KMime::Headers::Generic("X-Zanshin-RelatedProjectUid"); relatedHeader->from7BitString("1"); childNote->appendHeader(relatedHeader); Akonadi::Item childNoteItem; childNoteItem.setMimeType(Akonadi::NoteUtils::noteMimeType()); childNoteItem.setPayload(childNote); QTest::newRow("child todo") << project << childNoteItem << true; auto invalidProject = Domain::Project::Ptr::create(); QTest::newRow("invalid project") << invalidProject << unrelatedNoteItem << false; Akonadi::Item invalidItem; QTest::newRow("invalid item") << project << invalidItem << false; } void shouldVerifyIfAnItemIsAProjectChild() { // GIVEN QFETCH(Domain::Project::Ptr, project); QFETCH(Akonadi::Item, item); QFETCH(bool, isParent); // WHEN Akonadi::Serializer serializer; bool value = serializer.isProjectChild(project, item); // THEN QCOMPARE(value, isParent); } void shouldUpdateItemParent_data() { QTest::addColumn("item"); QTest::addColumn("parent"); QTest::addColumn("expectedRelatedToUid"); Akonadi::Item item1; KCalCore::Todo::Ptr todo1(new KCalCore::Todo); item1.setPayload(todo1); Domain::Task::Ptr parent(new Domain::Task); parent->setProperty("todoUid", "1"); QTest::newRow("nominal case") << item1 << parent << "1"; Akonadi::Item item2; QTest::newRow("update item without payload") << item2 << parent << QString(); Domain::Task::Ptr parent2(new Domain::Task); QTest::newRow("update item with a empty parent uid") << item1 << parent2 << QString(); } void shouldUpdateItemParent() { // GIVEN QFETCH(Akonadi::Item, item); QFETCH(Domain::Task::Ptr, parent); QFETCH(QString, expectedRelatedToUid); // WHEN Akonadi::Serializer serializer; serializer.updateItemParent(item, parent); // THEN if (item.hasPayload()) { auto todo = item.payload(); QString relatedUid = todo->relatedTo(); QCOMPARE(relatedUid, expectedRelatedToUid); } } void shouldUpdateItemProject_data() { QTest::addColumn("item"); QTest::addColumn("parent"); QTest::addColumn("expectedRelatedToUid"); Akonadi::Item todoItem; KCalCore::Todo::Ptr todo(new KCalCore::Todo); todoItem.setPayload(todo); auto parent = Domain::Project::Ptr::create(); parent->setProperty("todoUid", "1"); QTest::newRow("nominal todo case") << todoItem << parent << "1"; auto invalidParent = Domain::Project::Ptr::create(); QTest::newRow("update todo item with a empty parent uid") << todoItem << invalidParent << QString(); Akonadi::Item noteItem; KMime::Message::Ptr note(new KMime::Message); noteItem.setPayload(note); QTest::newRow("nominal note case") << noteItem << parent << "1"; QTest::newRow("update note item with a empty parent uid") << noteItem << invalidParent << QString(); Akonadi::Item invalidItem; QTest::newRow("update item without payload") << invalidItem << parent << QString(); } void shouldUpdateItemProject() { // GIVEN QFETCH(Akonadi::Item, item); QFETCH(Domain::Project::Ptr, parent); QFETCH(QString, expectedRelatedToUid); // WHEN Akonadi::Serializer serializer; serializer.updateItemProject(item, parent); // THEN if (item.hasPayload()) { auto todo = item.payload(); const QString relatedUid = todo->relatedTo(); QCOMPARE(relatedUid, expectedRelatedToUid); } else if (item.hasPayload()) { auto note = item.payload(); const auto relatedHeader = note->headerByType("X-Zanshin-RelatedProjectUid"); const QString relatedUid = relatedHeader ? relatedHeader->asUnicodeString() : QString(); QCOMPARE(relatedUid, expectedRelatedToUid); if (!expectedRelatedToUid.isEmpty()) QVERIFY(note->encodedContent().contains(QStringLiteral("X-Zanshin-RelatedProjectUid: %1").arg(expectedRelatedToUid).toUtf8())); else QVERIFY(!note->encodedContent().contains("X-Zanshin-RelatedProjectUid:")); } } void shouldFilterChildrenItem_data() { QTest::addColumn("item"); QTest::addColumn("items"); QTest::addColumn("size"); Akonadi::Item item(12); KCalCore::Todo::Ptr todo(new KCalCore::Todo); todo->setUid(QStringLiteral("1")); item.setPayload(todo); Akonadi::Item::List items; QTest::newRow("empty list") << item << items << 0; Akonadi::Item item2(13); KCalCore::Todo::Ptr todo2(new KCalCore::Todo); item2.setPayload(todo2); Akonadi::Item::List items2; items2 << item2; QTest::newRow("list without child") << item << items2 << 0; Akonadi::Item item3(14); KCalCore::Todo::Ptr todo3(new KCalCore::Todo); todo3->setUid(QStringLiteral("3")); todo3->setRelatedTo(QStringLiteral("1")); item3.setPayload(todo3); Akonadi::Item::List items3; items3 << item2 << item3; QTest::newRow("list with child") << item << items3 << 1; Akonadi::Item item4(15); KCalCore::Todo::Ptr todo4(new KCalCore::Todo); todo4->setRelatedTo(QStringLiteral("3")); item4.setPayload(todo4); Akonadi::Item::List items4; items4 << item2 << item3 << item4; QTest::newRow("list with child with a child") << item << items4 << 2; Akonadi::Item::List items5; items5 << item << item2 << item3 << item4; QTest::newRow("list with filter in list") << item << items5 << 2; } void shouldFilterChildrenItem() { // GIVEN QFETCH(Akonadi::Item, item); QFETCH(Akonadi::Item::List, items); QFETCH(int, size); // WHEN Akonadi::Serializer serializer; Akonadi::Item::List list = serializer.filterDescendantItems(items, item); // THEN QCOMPARE(list.size(), size); } void shouldRemoveItemParent_data() { QTest::addColumn("item"); Akonadi::Item item(15); KCalCore::Todo::Ptr todo(new KCalCore::Todo); todo->setRelatedTo(QStringLiteral("3")); item.setPayload(todo); QTest::newRow("nominal case") << item; Akonadi::Item item2(16); QTest::newRow("parent invalid") << item2; } void shouldRemoveItemParent() { // GIVEN QFETCH(Akonadi::Item, item); // WHEN Akonadi::Serializer serializer; serializer.removeItemParent(item); // THEN if (item.hasPayload()) QCOMPARE(item.payload()->relatedTo(), QString()); } void shouldPromoteItemToProject_data() { QTest::addColumn("item"); auto item = Akonadi::Item(15); auto todo = KCalCore::Todo::Ptr::create(); todo->setRelatedTo(QStringLiteral("3")); item.setPayload(todo); QTest::newRow("nominal case") << item; QTest::newRow("invalid item") << Akonadi::Item(16); } void shouldPromoteItemToProject() { // GIVEN QFETCH(Akonadi::Item, item); // WHEN Akonadi::Serializer serializer; serializer.promoteItemToProject(item); // THEN if (item.hasPayload()) { auto todo = item.payload(); QCOMPARE(todo->relatedTo(), QString()); QVERIFY(!todo->customProperty("Zanshin", "Project").isEmpty()); } } void shouldClearItem_data() { QTest::addColumn("item"); Akonadi::Item *itemWithContent = new Akonadi::Item(15); KCalCore::Todo::Ptr todo(new KCalCore::Todo); // context Akonadi::Tag context(QStringLiteral("42")); context.setType( Akonadi::SerializerInterface::contextTagType() ); // tag Akonadi::Tag tag(QStringLiteral("43")); tag.setType( Akonadi::Tag::PLAIN ); Akonadi::Tag::List tagsList = Akonadi::Tag::List() << tag << context; itemWithContent->setTags(tagsList); itemWithContent->setPayload(todo); QTest::newRow("nominal case") << itemWithContent; Akonadi::Item *item2 = new Akonadi::Item(16); QTest::newRow("parent invalid") << item2; } void shouldClearItem() { // GIVEN QFETCH(Akonadi::Item*, item); // WHEN Akonadi::Serializer serializer; serializer.clearItem(item); // THEN QCOMPARE(item->tags().size(), 0); delete item; } void shouldCreateContextFromTag_data() { QTest::addColumn("type"); QTest::addColumn("name"); QTest::addColumn("tagId"); const QByteArray rightTagType = Akonadi::Serializer::contextTagType() ; QTest::newRow("nominal case") << rightTagType << "Context42" << Akonadi::Tag::Id(42); QTest::newRow("empty name case") << rightTagType << "" << Akonadi::Tag::Id(43); } void shouldCreateContextFromTag() { // GIVEN // Data... QFETCH(QByteArray, type); QFETCH(QString, name); QFETCH(Akonadi::Tag::Id, tagId); // ... stored as an Akonadi Tag Akonadi::Tag tag(name); tag.setType(type); tag.setId(tagId); // WHEN Akonadi::Serializer serializer; Domain::Context::Ptr context = serializer.createContextFromTag(tag); // THEN QCOMPARE(context->name(), tag.name()); QCOMPARE(context->property("tagId").toLongLong(), tag.id()); } void shouldNotCreateContextFromWrongTagType() { // GIVEN // Data stored as an Akonadi Tag Akonadi::Tag tag(QStringLiteral("context42")); tag.setType(QByteArray("wrongTagType")); // WHEN Akonadi::Serializer serializer; Domain::Context::Ptr context = serializer.createContextFromTag(tag); // THEN QVERIFY(!context); } void shouldUpdateContextFromTag_data() { shouldCreateContextFromTag_data(); } void shouldUpdateContextFromTag() { // GIVEN // Data... QFETCH(QByteArray, type); QFETCH(QString, name); QFETCH(Akonadi::Tag::Id, tagId); // ... stored as an Akonadi Tag Akonadi::Tag tag(name); tag.setType(type); tag.setId(tagId); // WHEN Akonadi::Serializer serializer; Domain::Context::Ptr context(new Domain::Context); serializer.updateContextFromTag(context, tag); // THEN QCOMPARE(context->name(), tag.name()); QCOMPARE(context->property("tagId").toLongLong(), tag.id()); } void shouldNotUpdateContextFromWrongTagType() { // GIVEN Akonadi::Tag originalTag(QStringLiteral("Context42")); originalTag.setType(Akonadi::Serializer::contextTagType()); originalTag.setId(42); Akonadi::Serializer serializer; Domain::Context::Ptr context = serializer.createContextFromTag(originalTag); // WHEN Akonadi::Tag wrongTag(QStringLiteral("WrongContext42")); wrongTag.setType(QByteArray("wrongTypeTag")); serializer.updateContextFromTag(context, wrongTag); // THEN QCOMPARE(context->name(), originalTag.name()); QCOMPARE(context->property("tagId").toLongLong(), originalTag.id()); } void shouldVerifyIfAnItemIsAContextChild_data() { QTest::addColumn("context"); QTest::addColumn("item"); QTest::addColumn("isChild"); // Create a context auto context = Domain::Context::Ptr::create(); context->setProperty("tagId", qint64(43)); Akonadi::Tag tag(Akonadi::Tag::Id(43)); Akonadi::Item unrelatedItem; QTest::newRow("Unrelated item") << context << unrelatedItem << false; Akonadi::Item relatedItem; relatedItem.setTag(tag); QTest::newRow("Related item") << context << relatedItem << true; auto invalidContext = Domain::Context::Ptr::create(); QTest::newRow("Invalid context") << invalidContext << relatedItem << false; Akonadi::Item invalidItem; QTest::newRow("Invalid Item") << context << invalidItem << false; } void shouldVerifyIfAnItemIsAContextChild() { // GIVEN QFETCH(Domain::Context::Ptr, context); QFETCH(Akonadi::Item, item); QFETCH(bool, isChild); // WHEN Akonadi::Serializer serializer; bool value = serializer.isContextChild(context, item); // THEN QCOMPARE(value, isChild); } void shouldCheckIfAnItemHasContextsOrTags_data() { QTest::addColumn("item"); QTest::addColumn("contextsExpected"); QTest::addColumn("tagsExpected"); Akonadi::Tag unrelatedTag(QStringLiteral("Foo")); unrelatedTag.setType("unrelated"); Akonadi::Tag contextTag(QStringLiteral("Bar")); contextTag.setType(Akonadi::Serializer::contextTagType()); Akonadi::Tag akonadiTag(QStringLiteral("Baz")); akonadiTag.setType(Akonadi::Tag::PLAIN); Akonadi::Item item; QTest::newRow("no tags") << item << false << false; item.setTags({ unrelatedTag }); QTest::newRow("unrelated tags") << item << false << false; item.setTags({ unrelatedTag, contextTag }); QTest::newRow("has contexts") << item << true << false; item.setTags({ unrelatedTag, akonadiTag }); QTest::newRow("has tags") << item << false << true; item.setTags({ unrelatedTag, contextTag, akonadiTag }); QTest::newRow("has both") << item << true << true; } void shouldCheckIfAnItemHasContextsOrTags() { // GIVEN QFETCH(Akonadi::Item, item); QFETCH(bool, contextsExpected); QFETCH(bool, tagsExpected); Akonadi::Serializer serializer; // WHEN const bool hasContexts = serializer.hasContextTags(item); const bool hasTags = serializer.hasAkonadiTags(item); // THEN QCOMPARE(hasContexts, contextsExpected); QCOMPARE(hasTags, tagsExpected); } void shouldCreateTagFromContext_data() { QTest::addColumn("name"); QTest::addColumn("tagId"); QTest::addColumn("tagGid"); QString nameInternet = QStringLiteral("Internet"); QTest::newRow("nominal case") << QString(nameInternet) << qint64(42) << nameInternet.toLatin1(); QTest::newRow("null name case") << QString() << qint64(42) << QByteArray(); QTest::newRow("null tagId case") << QString(nameInternet)<< qint64(-1) << nameInternet.toLatin1(); QTest::newRow("totally null context case") << QString() << qint64(-1) << QByteArray(); } void shouldCreateTagFromContext() { // GIVEN QFETCH(QString, name); QFETCH(qint64, tagId); QFETCH(QByteArray, tagGid); // WHEN auto context = Domain::Context::Ptr::create(); context->setProperty("tagId", tagId); context->setName(name); Akonadi::Serializer serializer; Akonadi::Tag tag = serializer.createTagFromContext(context); // THEN QCOMPARE(tag.name(), name); QCOMPARE(tag.isValid(), tagId > 0); if (tagId > 0) { QCOMPARE(tag.id(), tagId); QCOMPARE(tag.gid(), tagGid); QCOMPARE(tag.type(), Akonadi::SerializerInterface::contextTagType()); } } void shouldCreateTagFromAkonadiTag_data() { QTest::addColumn("name"); QTest::addColumn("tagId"); QTest::addColumn("type"); QString tagName = QStringLiteral("Optional"); QByteArray plainType = Akonadi::Tag::PLAIN; QTest::newRow("nominal case") << tagName << qint64(42) << plainType; QTest::newRow("null name case") << QString() << qint64(42) << plainType; QTest::newRow("null tagId case") << tagName << qint64(-1) << plainType; QTest::newRow("totally null tag case") << QString() << qint64(-1) << plainType; } void shouldCreateTagFromAkonadiTag() { // GIVEN QFETCH(QString, name); QFETCH(qint64, tagId); QFETCH(QByteArray, type); auto akonadiTag = Akonadi::Tag(); akonadiTag.setName(name); akonadiTag.setId(tagId); akonadiTag.setType(type); // WHEN Akonadi::Serializer serializer; Domain::Tag::Ptr resultTag = serializer.createTagFromAkonadiTag(akonadiTag); // THEN QCOMPARE(resultTag->name(), akonadiTag.name()); QCOMPARE(resultTag->property("tagId").toLongLong(), akonadiTag.id()); } void shouldUpdateTagFromAkonadiTag_data() { shouldCreateTagFromAkonadiTag_data(); } void shouldUpdateTagFromAkonadiTag() { // GIVEN QFETCH(QString, name); QFETCH(qint64, tagId); QFETCH(QByteArray, type); // ... stored as an Akonadi Tag Akonadi::Tag akonadiTag(name); akonadiTag.setId(tagId); akonadiTag.setType(type); // WHEN Akonadi::Serializer serializer; auto tag = Domain::Tag::Ptr::create(); tag->setName(QStringLiteral("tag42")); serializer.updateTagFromAkonadiTag(tag, akonadiTag); // THEN QCOMPARE(tag->name(), akonadiTag.name()); QCOMPARE(tag->property("tagId").toLongLong(), akonadiTag.id()); } void shouldCreateAkonadiTagFromTag_data() { // GIVEN QTest::addColumn("name"); QTest::addColumn("tagId"); QTest::addColumn("tagGid"); const QByteArray namePhilo = "Philosophy"; QTest::newRow("nominal case") << QString(namePhilo) << qint64(42) << namePhilo; QTest::newRow("null name case") << QString() << qint64(42) << QByteArray(); QTest::newRow("null tagId case") << QString(namePhilo) << qint64(-1) << namePhilo; QTest::newRow("totally null tag case") << QString() << qint64(-1) << QByteArray(); } void shouldCreateAkonadiTagFromTag() { // GIVEN QFETCH(QString, name); QFETCH(qint64, tagId); QFETCH(QByteArray, tagGid); // WHEN auto tag = Domain::Tag::Ptr::create(); tag->setProperty("tagId", tagId); tag->setName(name); Akonadi::Serializer serializer; Akonadi::Tag akonadiTag = serializer.createAkonadiTagFromTag(tag); // THEN QCOMPARE(akonadiTag.name(), name); QCOMPARE(akonadiTag.isValid(), tagId > 0); if (tagId > 0) { QCOMPARE(akonadiTag.id(), tagId); QCOMPARE(akonadiTag.gid(), tagGid); QCOMPARE(akonadiTag.type(), QByteArray(Akonadi::Tag::PLAIN)); } } void shouldVerifyIfAnItemIsATagChild_data() { QTest::addColumn("tag"); QTest::addColumn("item"); QTest::addColumn("isChild"); // Create a Tag auto tag = Domain::Tag::Ptr::create(); tag->setProperty("tagId", qint64(43)); Akonadi::Tag akonadiTag(Akonadi::Tag::Id(43)); Akonadi::Item unrelatedItem; QTest::newRow("Unrelated item") << tag << unrelatedItem << false; Akonadi::Item relatedItem; relatedItem.setTag(akonadiTag); QTest::newRow("Related item") << tag << relatedItem << true; auto invalidTag = Domain::Tag::Ptr::create(); QTest::newRow("Invalid Tag") << invalidTag << relatedItem << false; Akonadi::Item invalidItem; QTest::newRow("Invalid Item") << tag << invalidItem << false; QTest::newRow("both invalid") << invalidTag << invalidItem << false; } void shouldVerifyIfAnItemIsATagChild() { // GIVEN QFETCH(Domain::Tag::Ptr, tag); QFETCH(Akonadi::Item, item); QFETCH(bool, isChild); // WHEN Akonadi::Serializer serializer; bool value = serializer.isTagChild(tag, item); // THEN QCOMPARE(value, isChild); } // Investigation into how to differentiate all-day events from events with time, // using QDateTime only. Doesn't seem to be possible. - void KDateTimeShouldStillBeNeeded() // although I wish it wasn't... + void noWayToHaveQDateTimeWithoutTime() { // GIVEN a QDateTime without time information QDateTime dateOnly(QDate(2016, 6, 12), QTime(-1, -1, -1)); // THEN we can't detect that there was no time information, i.e. all day event QVERIFY(dateOnly.time().isValid()); // I wish this was "!" QVERIFY(!dateOnly.time().isNull()); // got converted to midnight localtime by QDateTime // This doesn't help, QDateTime converts "null time" to midnight. dateOnly.setTime(QTime()); QVERIFY(dateOnly.time().isValid()); // same as above QVERIFY(!dateOnly.time().isNull()); // same as above // GIVEN a QDateTime at midnight QDateTime atMidnight(QDate(2016, 6, 12), QTime(0, 0, 0)); // THEN we can detect that a time information was present QVERIFY(atMidnight.time().isValid()); QVERIFY(!atMidnight.time().isNull()); #if 0 // GIVEN a KDateTime without time information KDateTime kdOnly(QDate(2016, 6, 12)); // THEN we can detect that there was no time information, i.e. all day event QVERIFY(kdOnly.isDateOnly()); #endif } }; ZANSHIN_TEST_MAIN(AkonadiSerializerTest) #include "akonadiserializertest.moc" diff --git a/tests/units/akonadi/akonaditaskqueriestest.cpp b/tests/units/akonadi/akonaditaskqueriestest.cpp index cbfc0c94..a5233720 100644 --- a/tests/units/akonadi/akonaditaskqueriestest.cpp +++ b/tests/units/akonadi/akonaditaskqueriestest.cpp @@ -1,1623 +1,1623 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include "akonadi/akonadicachingstorage.h" #include "akonadi/akonaditaskqueries.h" #include "akonadi/akonadiserializer.h" #include "testlib/akonadifakedata.h" #include "testlib/gencollection.h" #include "testlib/gennote.h" #include "testlib/gentag.h" #include "testlib/gentodo.h" #include "testlib/testhelpers.h" #include "utils/datetime.h" using namespace Testlib; class AkonadiTaskQueriesTest : public QObject { Q_OBJECT private: Akonadi::StorageInterface::Ptr createCachingStorage(AkonadiFakeData &data, const Akonadi::Cache::Ptr &cache) { auto storage = Akonadi::StorageInterface::Ptr(data.createStorage()); return Akonadi::StorageInterface::Ptr(new Akonadi::CachingStorage(cache, storage)); } private slots: void shouldLookInAllReportedForAllTasks() { // GIVEN AkonadiFakeData data; // Two top level collections data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withRootAsParent().withTaskContent()); // One task in the first collection data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42"))); // Two tasks in the second collection data.createItem(GenTodo().withId(43).withParent(43).withTitle(QStringLiteral("43"))); data.createItem(GenTodo().withId(44).withParent(43).withTitle(QStringLiteral("44"))); // WHEN QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto result = queries->findAll(); result->data(); result = queries->findAll(); // Should not cause any problem or wrong data // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 3); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("43")); QCOMPARE(result->data().at(2)->title(), QStringLiteral("44")); } void shouldIgnoreItemsWhichAreNotTasks() { // GIVEN AkonadiFakeData data; // Two top level collections data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Two items in the collection data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42"))); // One of them is not a task auto item = Akonadi::Item(43); item.setPayloadFromData("FooBar"); item.setParentCollection(Akonadi::Collection(42)); data.createItem(item); // WHEN QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto result = queries->findAll(); // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); } void shouldReactToItemAddsForTasksOnly() { // GIVEN AkonadiFakeData data; // One empty collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto result = queries->findAll(); TestHelpers::waitForEmptyJobQueue(); QVERIFY(result->data().isEmpty()); // WHEN data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42"))); auto item = Akonadi::Item(43); item.setPayloadFromData("FooBar"); item.setParentCollection(Akonadi::Collection(42)); data.createItem(item); // THEN QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().first()->title(), QStringLiteral("42")); } void shouldReactToItemRemovesForAllTasks() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three task in the collection data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42"))); data.createItem(GenTodo().withId(43).withParent(42).withTitle(QStringLiteral("43"))); data.createItem(GenTodo().withId(44).withParent(42).withTitle(QStringLiteral("44"))); QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto result = queries->findAll(); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 3); // WHEN data.removeItem(Akonadi::Item(43)); // THEN QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("44")); } void shouldReactToItemChangesForAllTasks() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three task in the collection data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42"))); data.createItem(GenTodo().withId(43).withParent(42).withTitle(QStringLiteral("43"))); data.createItem(GenTodo().withId(44).withParent(42).withTitle(QStringLiteral("44"))); QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto result = queries->findAll(); // Even though the pointer didn't change it's convenient to user if we call // the replace handlers bool replaceHandlerCalled = false; result->addPostReplaceHandler([&replaceHandlerCalled](const Domain::Task::Ptr &, int) { replaceHandlerCalled = true; }); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 3); // WHEN data.modifyItem(GenTodo(data.item(43)).withTitle(QStringLiteral("43bis"))); // THEN QCOMPARE(result->data().size(), 3); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("43bis")); QCOMPARE(result->data().at(2)->title(), QStringLiteral("44")); QVERIFY(replaceHandlerCalled); } void shouldLookInAllChildrenReportedForAllChildrenTask() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three tasks in the collection (two being children of the first one) data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(43).withParent(42) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43")) .withParentUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(44).withParent(42) .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44")) .withParentUid(QStringLiteral("uid-42"))); // WHEN auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto task = serializer->createTaskFromItem(data.item(42)); auto result = queries->findChildren(task); result->data(); result = queries->findChildren(task); // Should not cause any problem or wrong data // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->title(), QStringLiteral("43")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("44")); // Should not change nothing result = queries->findChildren(task); QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->title(), QStringLiteral("43")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("44")); } void shouldNotCrashWhenWeAskAgainTheSameChildrenList() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // One task in the collection data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto task = serializer->createTaskFromItem(data.item(42)); // The bug we're trying to hit here is the following: // - when findChildren is called the first time a provider is created internally // - result is deleted at the end of the loop, no one holds the provider with // a strong reference anymore so it is deleted as well // - when findChildren is called the second time, there's a risk of a dangling // pointer if the recycling of providers is wrongly implemented which can lead // to a crash, if it is properly done no crash will occur for (int i = 0; i < 2; i++) { // WHEN * 2 auto result = queries->findChildren(task); // THEN * 2 QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QVERIFY(result->data().isEmpty()); } } void shouldReactToItemAddsForChildrenTask() { // GIVEN AkonadiFakeData data; // One top level collections data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // One task in the collection data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto task = serializer->createTaskFromItem(data.item(42)); auto result = queries->findChildren(task); TestHelpers::waitForEmptyJobQueue(); QVERIFY(result->data().isEmpty()); // WHEN data.createItem(GenTodo().withId(43).withParent(42) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43")) .withParentUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(44).withParent(42) .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44")) .withParentUid(QStringLiteral("uid-42"))); // THEN QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->title(), QStringLiteral("43")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("44")); } void shouldReactToItemChangesForChildrenTask() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three tasks in the collection (two being children of the first one) data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(43).withParent(42) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43")) .withParentUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(44).withParent(42) .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44")) .withParentUid(QStringLiteral("uid-42"))); auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto task = serializer->createTaskFromItem(data.item(42)); auto result = queries->findChildren(task); bool replaceHandlerCalled = false; result->addPostReplaceHandler([&replaceHandlerCalled](const Domain::Task::Ptr &, int) { replaceHandlerCalled = true; }); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); // WHEN data.modifyItem(GenTodo(data.item(43)).withTitle(QStringLiteral("43bis"))); // THEN QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->title(), QStringLiteral("43bis")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("44")); QVERIFY(replaceHandlerCalled); } void shouldRemoveItemFromCorrespondingResultWhenRelatedItemChangeForChildrenTask() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three tasks in the collection (two being children of the first one) data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(43).withParent(42) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43")) .withParentUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(44).withParent(42) .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44")) .withParentUid(QStringLiteral("uid-42"))); auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto task = serializer->createTaskFromItem(data.item(42)); auto result = queries->findChildren(task); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); // WHEN data.modifyItem(GenTodo(data.item(43)).withParentUid(QLatin1String(""))); // THEN QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("44")); } void shouldAddItemToCorrespondingResultWhenRelatedItemChangeForChildrenTask() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three tasks in the collection (two being top level) data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(43).withParent(42) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43"))); data.createItem(GenTodo().withId(44).withParent(42) .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44")) .withParentUid(QStringLiteral("uid-42"))); auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto task = serializer->createTaskFromItem(data.item(42)); auto result = queries->findChildren(task); bool replaceHandlerCalled = false; result->addPostReplaceHandler([&replaceHandlerCalled](const Domain::Task::Ptr &, int) { replaceHandlerCalled = true; }); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 1); // WHEN data.modifyItem(GenTodo(data.item(43)).withParentUid(QStringLiteral("uid-42"))); // THEN QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->title(), QStringLiteral("44")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("43")); QVERIFY(!replaceHandlerCalled); } void shouldMoveItemToCorrespondingResultWhenRelatedItemChangeForChildTask() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three tasks in the collection (two being top level) data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(43).withParent(42) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43"))); data.createItem(GenTodo().withId(44).withParent(42) .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44")) .withParentUid(QStringLiteral("uid-42"))); auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto task1 = serializer->createTaskFromItem(data.item(42)); auto task2 = serializer->createTaskFromItem(data.item(43)); auto result1 = queries->findChildren(task1); auto result2 = queries->findChildren(task2); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result1->data().size(), 1); QCOMPARE(result1->data().at(0)->title(), QStringLiteral("44")); QCOMPARE(result2->data().size(), 0); // WHEN data.modifyItem(GenTodo(data.item(44)).withParentUid(QStringLiteral("uid-43"))); // THEN QCOMPARE(result1->data().size(), 0); QCOMPARE(result2->data().size(), 1); QCOMPARE(result2->data().at(0)->title(), QStringLiteral("44")); } void shouldReactToItemRemovesForChildrenTask() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three tasks in the collection (two being children of the first one) data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(43).withParent(42) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43")) .withParentUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(44).withParent(42) .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44")) .withParentUid(QStringLiteral("uid-42"))); auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto task = serializer->createTaskFromItem(data.item(42)); auto result = queries->findChildren(task); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); // WHEN data.removeItem(Akonadi::Item(43)); // THEN QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("44")); } void shouldLookInAllReportedForTopLevelTasks() { // GIVEN AkonadiFakeData data; // Two top level collections data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withRootAsParent().withTaskContent()); // One task in the first collection data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42"))); // Two tasks in the second collection data.createItem(GenTodo().withId(43).withParent(43).withTitle(QStringLiteral("43"))); data.createItem(GenTodo().withId(44).withParent(43).withTitle(QStringLiteral("44")).withParentUid(QStringLiteral("2"))); // WHEN QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto result = queries->findTopLevel(); result->data(); result = queries->findTopLevel(); // Should not cause any problem or wrong data // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("43")); } void shouldReactToItemAddsForTopLevelTasks() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto result = queries->findTopLevel(); TestHelpers::waitForEmptyJobQueue(); QVERIFY(result->data().isEmpty()); // WHEN data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42"))); data.createItem(GenTodo().withId(43).withParent(42).withTitle(QStringLiteral("43")).withParentUid(QStringLiteral("2"))); // THEN QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().first()->title(), QStringLiteral("42")); } void shouldReactToItemRemovesForTopLevelTasks() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three tasks in the collection (one being child of the second one) data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(43).withParent(42) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43"))); data.createItem(GenTodo().withId(44).withParent(42) .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44")) .withParentUid(QStringLiteral("uid-43"))); QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto result = queries->findTopLevel(); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); // WHEN data.removeItem(Akonadi::Item(43)); // THEN QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); } void shouldReactToItemChangesForTopLevelTasks() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three tasks in the collection (one being child of the second one) data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(43).withParent(42) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43"))); data.createItem(GenTodo().withId(44).withParent(42) .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44")) .withParentUid(QStringLiteral("uid-43"))); QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto result = queries->findTopLevel(); // Even though the pointer didn't change it's convenient to user if we call // the replace handlers bool replaceHandlerCalled = false; result->addPostReplaceHandler([&replaceHandlerCalled](const Domain::Task::Ptr &, int) { replaceHandlerCalled = true; }); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); // WHEN data.modifyItem(GenTodo(data.item(43)).withTitle(QStringLiteral("43bis"))); // THEN QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("43bis")); QVERIFY(replaceHandlerCalled); } void shouldRemoveItemFromTopLevelResultWhenRelatedItemChangeForTopLevelTask() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three tasks in the collection (one being child of the second one) data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(43).withParent(42) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43"))); data.createItem(GenTodo().withId(44).withParent(42) .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44")) .withParentUid(QStringLiteral("uid-43"))); QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto result = queries->findTopLevel(); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); // WHEN data.modifyItem(GenTodo(data.item(43)).withParentUid(QStringLiteral("uid-42"))); // THEN QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); } void shouldAddItemToTopLevelResultWhenRelatedItemChangeForChildrenTask() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three tasks in the collection (one being child of the second one) data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(43).withParent(42) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43")) .withParentUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(44).withParent(42) .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44")) .withParentUid(QStringLiteral("uid-43"))); QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto result = queries->findTopLevel(); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 1); // WHEN data.modifyItem(GenTodo(data.item(43)).withParentUid(QString())); // THEN QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("43")); } void shouldRemoveParentNodeAndMoveChildrenInTopLevelResult() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three tasks in the collection (one being child of the second one) data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(43).withParent(42) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43"))); data.createItem(GenTodo().withId(44).withParent(42) .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44")) .withParentUid(QStringLiteral("uid-43"))); QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto result = queries->findTopLevel(); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); auto resultChild = queries->findChildren(result->data().at(1)); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(resultChild->data().size(), 1); // WHEN data.removeItem(Akonadi::Item(43)); // THEN QCOMPARE(resultChild->data().size(), 0); QCOMPARE(result->data().size(), 1); // FIXME: Should become 2 once we got a proper cache in place } void shouldNotCrashDuringFindChildrenWhenJobIsKilled() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three tasks in the collection (two being children of the first one) data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(43).withParent(42) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43"))); data.createItem(GenTodo().withId(44).withParent(42) .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44")) .withParentUid(QStringLiteral("uid-42"))); auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); data.storageBehavior().setFetchItemErrorCode(42, KJob::KilledJobError); // WHEN auto task1 = serializer->createTaskFromItem(data.item(42)); auto result = queries->findChildren(task1); // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QVERIFY(result->data().isEmpty()); } void shouldNotCrashDuringFindChildrenWhenItemsJobReceiveResult_data() { QTest::addColumn("errorCode"); QTest::addColumn("fetchBehavior"); QTest::addColumn("deleteQuery"); QTest::newRow("No error with empty list") << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << false; QTest::newRow("Error with empty list") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << true; QTest::newRow("Error with list") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::NormalFetch) << true; } void shouldNotCrashDuringFindChildrenWhenItemsJobReceiveResult() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three tasks in the collection (two being children of the first one) data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(43).withParent(42) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43"))); data.createItem(GenTodo().withId(44).withParent(42) .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44")) .withParentUid(QStringLiteral("uid-42"))); auto storage = Akonadi::StorageInterface::Ptr(data.createStorage()); auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); auto monitor = Akonadi::MonitorInterface::Ptr(data.createMonitor()); QScopedPointer queries(new Akonadi::TaskQueries(storage, serializer, monitor, Akonadi::Cache::Ptr())); QFETCH(int, errorCode); QFETCH(int, fetchBehavior); QFETCH(bool, deleteQuery); data.storageBehavior().setFetchItemsErrorCode(42, errorCode); data.storageBehavior().setFetchItemsBehavior(42, AkonadiFakeStorageBehavior::FetchBehavior(fetchBehavior)); // WHEN auto task1 = serializer->createTaskFromItem(data.item(42)); auto result = queries->findChildren(task1); if (deleteQuery) delete queries.take(); // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QVERIFY(result->data().isEmpty()); } void shouldNotCrashDuringFindAllWhenFetchJobFailedOrEmpty_data() { QTest::addColumn("colErrorCode"); QTest::addColumn("colFetchBehavior"); QTest::addColumn("itemsErrorCode"); QTest::addColumn("itemsFetchBehavior"); QTest::addColumn("deleteQuery"); QTest::newRow("No error with empty collection list") << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::NormalFetch) << false; QTest::newRow("Error with empty collection list") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::NormalFetch) << true; QTest::newRow("Error with collection list") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::NormalFetch) << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::NormalFetch) << true; QTest::newRow("No error with empty item list") << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::NormalFetch) << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << false; QTest::newRow("Error with empty item list") << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::NormalFetch) << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << false; QTest::newRow("Error with item list") << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::NormalFetch) << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::NormalFetch) << false; } void shouldNotCrashDuringFindAllWhenFetchJobFailedOrEmpty() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three tasks in the collection data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(43).withParent(42) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43"))); data.createItem(GenTodo().withId(44).withParent(42) .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44"))); auto storage = Akonadi::StorageInterface::Ptr(data.createStorage()); auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); auto monitor = Akonadi::MonitorInterface::Ptr(data.createMonitor()); QScopedPointer queries(new Akonadi::TaskQueries(storage, serializer, monitor, Akonadi::Cache::Ptr())); QFETCH(int, colErrorCode); QFETCH(int, colFetchBehavior); data.storageBehavior().setFetchCollectionsErrorCode(Akonadi::Collection::root().id(), colErrorCode); data.storageBehavior().setFetchCollectionsBehavior(Akonadi::Collection::root().id(), AkonadiFakeStorageBehavior::FetchBehavior(colFetchBehavior)); QFETCH(int, itemsErrorCode); QFETCH(int, itemsFetchBehavior); data.storageBehavior().setFetchItemsErrorCode(42, itemsErrorCode); data.storageBehavior().setFetchItemsBehavior(42, AkonadiFakeStorageBehavior::FetchBehavior(itemsFetchBehavior)); QFETCH(bool, deleteQuery); // WHEN auto result = queries->findAll(); if (deleteQuery) delete queries.take(); // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QVERIFY(result->data().isEmpty()); } void shouldNotCrashDuringFindTopLevelWhenFetchJobFailedOrEmpty_data() { QTest::addColumn("colErrorCode"); QTest::addColumn("colFetchBehavior"); QTest::addColumn("itemsErrorCode"); QTest::addColumn("itemsFetchBehavior"); QTest::addColumn("deleteQuery"); QTest::newRow("No error with empty collection list") << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::NormalFetch) << false; QTest::newRow("Error with empty collection list") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::NormalFetch) << true; QTest::newRow("Error with collection list") << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::NormalFetch) << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::NormalFetch) << true; QTest::newRow("No error with empty item list") << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::NormalFetch) << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << false; QTest::newRow("Error with empty item list") << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::NormalFetch) << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::EmptyFetch) << false; QTest::newRow("Error with item list") << int(KJob::NoError) << int(AkonadiFakeStorageBehavior::NormalFetch) << int(KJob::KilledJobError) << int(AkonadiFakeStorageBehavior::NormalFetch) << false; } void shouldNotCrashDuringFindTopLevelWhenFetchJobFailedOrEmpty() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three tasks in the collection (third one being child of the first one) data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(43).withParent(42) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43"))); data.createItem(GenTodo().withId(44).withParent(42) .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44")) .withParentUid(QStringLiteral("uid-42"))); auto storage = Akonadi::StorageInterface::Ptr(data.createStorage()); auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); auto monitor = Akonadi::MonitorInterface::Ptr(data.createMonitor()); QScopedPointer queries(new Akonadi::TaskQueries(storage, serializer, monitor, Akonadi::Cache::Ptr())); QFETCH(int, colErrorCode); QFETCH(int, colFetchBehavior); data.storageBehavior().setFetchCollectionsErrorCode(Akonadi::Collection::root().id(), colErrorCode); data.storageBehavior().setFetchCollectionsBehavior(Akonadi::Collection::root().id(), AkonadiFakeStorageBehavior::FetchBehavior(colFetchBehavior)); QFETCH(int, itemsErrorCode); QFETCH(int, itemsFetchBehavior); data.storageBehavior().setFetchItemsErrorCode(42, itemsErrorCode); data.storageBehavior().setFetchItemsBehavior(42, AkonadiFakeStorageBehavior::FetchBehavior(itemsFetchBehavior)); QFETCH(bool, deleteQuery); // WHEN auto result = queries->findTopLevel(); if (deleteQuery) delete queries.take(); // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QVERIFY(result->data().isEmpty()); } void shouldIgnoreProjectsWhenReportingTopLevelTasks() { // GIVEN AkonadiFakeData data; // Two top level collections data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withRootAsParent().withTaskContent()); // One task in the first collection data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); // Two tasks and one project in the second collection data.createItem(GenTodo().withId(43).withParent(43) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43"))); data.createItem(GenTodo().withId(44).withParent(43) .withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44")) .withParentUid(QStringLiteral("uid-43"))); data.createItem(GenTodo().withId(45).withParent(43) .withTitle(QStringLiteral("45")).withUid(QStringLiteral("uid-45")) .asProject()); QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto result = queries->findTopLevel(); result->data(); result = queries->findTopLevel(); // Should not cause any problem or wrong data // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("43")); } void shouldLookInAllSelectedCollectionsForInboxTopLevel() { // GIVEN AkonadiFakeData data; // Three top level collections data.createCollection(GenCollection().withId(42).withRootAsParent().withNoteContent()); data.createCollection(GenCollection().withId(43).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(44).withRootAsParent().withTaskContent().selected(false)); // One note in the first collection data.createItem(GenNote().withId(42).withParent(42).withTitle(QStringLiteral("42"))); // Two tasks in the second collection data.createItem(GenTodo().withId(43).withParent(43).withTitle(QStringLiteral("43"))); data.createItem(GenTodo().withId(44).withParent(43).withTitle(QStringLiteral("44"))); // One task in the third collection data.createItem(GenTodo().withId(45).withParent(44).withTitle(QStringLiteral("45"))); // WHEN QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto result = queries->findInboxTopLevel(); result->data(); result = queries->findInboxTopLevel(); // Should not cause any problem or wrong data // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->title(), QStringLiteral("43")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("44")); } void shouldIgnoreItemsWhichAreNotTasksInInboxTopLevel() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Two items in the collection data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42"))); // One of them is not a task auto item = Akonadi::Item(43); item.setPayloadFromData("FooBar"); item.setParentCollection(Akonadi::Collection(42)); data.createItem(item); // WHEN QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto result = queries->findInboxTopLevel(); // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); } void shouldNotHaveTasksWithParentsInInboxTopLevel() { // TODO: Note that this specification is kind of an over simplification which // assumes that all the underlying data is correct. Ideally it should be checked // that the uid referred to actually points to a todo which exists in a proper // collection. We will need a cache to be able to implement that properly though. // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // Three items in the collection data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(43).withParent(42).withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43")).withParentUid(QStringLiteral("uid-42"))); data.createItem(GenTodo().withId(44).withParent(42).withTitle(QStringLiteral("44")).withUid(QStringLiteral("uid-44")).withParentUid(QStringLiteral("foo"))); // WHEN QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto result = queries->findInboxTopLevel(); // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); } void shouldHaveTasksWithContextsInInboxTopLevel_data() { QTest::addColumn("hasContexts"); QTest::addColumn("isExpectedInInbox"); QTest::newRow("task with no context") << false << true; QTest::newRow("task with contexts") << true << true; } void shouldHaveTasksWithContextsInInboxTopLevel() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // One context tag data.createTag(GenTag().withId(42).withName(QStringLiteral("tag-42")).asContext()); // One item in the collection QFETCH(bool, hasContexts); auto tagIds = QList(); if (hasContexts) tagIds << 42; data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42")).withTags(tagIds)); // WHEN QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto result = queries->findInboxTopLevel(); // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QFETCH(bool, isExpectedInInbox); if (isExpectedInInbox) { QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); } else { QVERIFY(result->data().isEmpty()); } } void shouldReactToItemAddsForInboxTopLevel_data() { QTest::addColumn("reactionExpected"); QTest::addColumn("relatedUid"); QTest::addColumn("hasContexts"); QTest::newRow("task which should be in inbox") << true << QString() << false; QTest::newRow("task with related uid") << false << "foo" << false; QTest::newRow("task with context") << true << QString() << true; } void shouldReactToItemAddsForInboxTopLevel() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // One context tag data.createTag(GenTag().withId(42).withName(QStringLiteral("tag-42")).asContext()); QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto result = queries->findInboxTopLevel(); TestHelpers::waitForEmptyJobQueue(); QVERIFY(result->data().isEmpty()); // WHEN QFETCH(QString, relatedUid); QFETCH(bool, hasContexts); auto tagIds = QList(); if (hasContexts) tagIds << 42; data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42")).withTags(tagIds).withParentUid(relatedUid)); // THEN QFETCH(bool, reactionExpected); if (reactionExpected) { QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); } else { QVERIFY(result->data().isEmpty()); } } void shouldReactToItemRemovesForInboxTopLevel() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // One item in the collection data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42"))); // WHEN QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto result = queries->findInboxTopLevel(); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().first()->title(), QStringLiteral("42")); // WHEN data.removeItem(Akonadi::Item(42)); // THEN QVERIFY(result->data().isEmpty()); } void shouldReactToItemChangesForInboxTopLevel_data() { QTest::addColumn("inListAfterChange"); QTest::addColumn("relatedUidBefore"); QTest::addColumn("relatedUidAfter"); QTest::newRow("task appears in inbox (related uid)") << true << "foo" << QString(); QTest::newRow("task disappears from inbox (related uid)") << false << QString() << "foo"; } void shouldReactToItemChangesForInboxTopLevel() { // GIVEN AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // One context tag data.createTag(GenTag().withId(42).withName(QStringLiteral("tag-42")).asContext()); // Artifact data QFETCH(QString, relatedUidBefore); data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42")).withParentUid(relatedUidBefore)); QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto result = queries->findInboxTopLevel(); TestHelpers::waitForEmptyJobQueue(); QFETCH(bool, inListAfterChange); if (inListAfterChange) { QVERIFY(result->data().isEmpty()); } else { QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); } // WHEN QFETCH(QString, relatedUidAfter); data.modifyItem(GenTodo(data.item(42)).withParentUid(relatedUidAfter)); // THEN if (inListAfterChange) { QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); } else { QVERIFY(result->data().isEmpty()); } } void shouldReactToCollectionSelectionChangesForInboxTopLevel() { // GIVEN AkonadiFakeData data; // Two top level collections data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withRootAsParent().withTaskContent()); // One task in each collection data.createItem(GenTodo().withId(42).withParent(42).withTitle(QStringLiteral("42"))); data.createItem(GenTodo().withId(43).withParent(43).withTitle(QStringLiteral("43"))); QScopedPointer queries(new Akonadi::TaskQueries(Akonadi::StorageInterface::Ptr(data.createStorage()), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), Akonadi::Cache::Ptr())); auto result = queries->findInboxTopLevel(); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("43")); // WHEN data.modifyCollection(GenCollection(data.collection(43)).selected(false)); TestHelpers::waitForEmptyJobQueue(); // THEN QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); } void shouldLookInAllWorkdayReportedForAllTasks_data() { QTest::addColumn("isExpectedInWorkday"); QTest::addColumn("item2"); - const auto today = Utils::DateTime::currentDateTime(); + const auto today = Utils::DateTime::currentDate(); QTest::newRow("todayTask") << true << Akonadi::Item(GenTodo() .withStartDate(today) .withDueDate(today)); QTest::newRow("pastTask") << true << Akonadi::Item(GenTodo() .withStartDate(today.addDays(-42)) .withDueDate(today.addDays(-41))); QTest::newRow("startTodayTask") << true << Akonadi::Item(GenTodo() .withStartDate(today)); QTest::newRow("endTodayTask") << true << Akonadi::Item(GenTodo() .withStartDate(today)); QTest::newRow("futureTask") << false << Akonadi::Item(GenTodo() .withStartDate(today.addDays(41)) .withDueDate(today.addDays(42))); QTest::newRow("pastDoneTask") << false << Akonadi::Item(GenTodo() .withStartDate(today.addDays(-42)) .withDueDate(today.addDays(-41)) .done() .withDoneDate(today.addDays(-30))); QTest::newRow("todayDoneTask") << true << Akonadi::Item(GenTodo() .withStartDate(today) .withDueDate(today) .done() - .withDoneDate(QDateTime(today.date(), QTime(12, 00)))); + .withDoneDate(today)); QTest::newRow("startTodayDoneTask") << true << Akonadi::Item(GenTodo() .withStartDate(today) .done() - .withDoneDate(QDateTime(today.date(), QTime(12, 00)))); + .withDoneDate(today)); QTest::newRow("endTodayDoneTask") << true << Akonadi::Item(GenTodo() .withDueDate(today) .done() - .withDoneDate(QDateTime(today.date(), QTime(12, 00)))); + .withDoneDate(today)); } void shouldLookInAllWorkdayReportedForAllTasks() { // GIVEN - const auto today = Utils::DateTime::currentDateTime(); + const auto today = Utils::DateTime::currentDate(); AkonadiFakeData data; // Two top level collections data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withRootAsParent().withTaskContent()); // One task in the first collection data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42")) - .withStartDate(today.addSecs(300))); + .withStartDate(today)); // One task in the second collection (from data driven set) QFETCH(Akonadi::Item, item2); data.createItem(GenTodo(item2).withId(43).withParent(43) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43"))); // WHEN auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); auto cache = Akonadi::Cache::Ptr::create(serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor())); QScopedPointer queries(new Akonadi::TaskQueries(createCachingStorage(data, cache), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), cache)); auto result = queries->findWorkdayTopLevel(); result->data(); result = queries->findWorkdayTopLevel(); // Should not cause any problem or wrong data // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QFETCH(bool, isExpectedInWorkday); const int sizeExpected = (isExpectedInWorkday) ? 2 : 1; QCOMPARE(result->data().size(), sizeExpected); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); if (isExpectedInWorkday) QCOMPARE(result->data().at(1)->title(), QStringLiteral("43")); } void shouldLookInAllWorkdayReportedForAllTasksWhenOverrideDate() { // GIVEN - qputenv("ZANSHIN_OVERRIDE_DATETIME", "2015-03-10"); - const auto today = Utils::DateTime::currentDateTime(); + qputenv("ZANSHIN_OVERRIDE_DATE", "2015-03-10"); + const auto today = Utils::DateTime::currentDate(); AkonadiFakeData data; // Two top level collections data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withRootAsParent().withTaskContent()); // One task in the first collection data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42")) .withStartDate(today)); // One task in the second collection data.createItem(GenTodo().withId(43).withParent(43) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43")) - .withStartDate(today.addSecs(3600))); + .withStartDate(today)); // WHEN auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); auto cache = Akonadi::Cache::Ptr::create(serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor())); QScopedPointer queries(new Akonadi::TaskQueries(createCachingStorage(data, cache), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), cache)); auto result = queries->findWorkdayTopLevel(); result->data(); result = queries->findWorkdayTopLevel(); // Should not cause any problem or wrong data // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("43")); } void shouldPollForCurrentDayToListWorkday() { // GIVEN - qputenv("ZANSHIN_OVERRIDE_DATETIME", "2015-03-10T23:59:59UTC"); - const auto today = Utils::DateTime::currentDateTime(); + qputenv("ZANSHIN_OVERRIDE_DATE", "2015-03-10"); + const auto today = Utils::DateTime::currentDate(); AkonadiFakeData data; // Two top level collections data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); data.createCollection(GenCollection().withId(43).withRootAsParent().withTaskContent()); // One task in the first collection data.createItem(GenTodo().withId(42).withParent(42) .withTitle(QStringLiteral("42")).withUid(QStringLiteral("uid-42")) .withStartDate(today)); // One task in the second collection data.createItem(GenTodo().withId(43).withParent(43) .withTitle(QStringLiteral("43")).withUid(QStringLiteral("uid-43")) .withStartDate(today.addDays(1))); QScopedPointer queries; { auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); auto cache = Akonadi::Cache::Ptr::create(serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor())); auto akqueries = new Akonadi::TaskQueries(createCachingStorage(data, cache), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), cache); QCOMPARE(akqueries->workdayPollInterval(), 30000); akqueries->setWorkdayPollInterval(500); queries.reset(akqueries); } auto result = queries->findWorkdayTopLevel(); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); // WHEN - qputenv("ZANSHIN_OVERRIDE_DATETIME", "2015-03-11T00:01:00UTC"); + qputenv("ZANSHIN_OVERRIDE_DATE", "2015-03-11"); QTest::qWait(1000); TestHelpers::waitForEmptyJobQueue(); // THEN QCOMPARE(result->data().size(), 2); QCOMPARE(result->data().at(0)->title(), QStringLiteral("42")); QCOMPARE(result->data().at(1)->title(), QStringLiteral("43")); } void shouldNotListWorkdayTasksTwiceIfTheyHaveAParentInWorkday() { // GIVEN - const auto today = Utils::DateTime::currentDateTime(); + const auto today = Utils::DateTime::currentDate(); AkonadiFakeData data; // One top level collection data.createCollection(GenCollection().withId(42).withRootAsParent().withTaskContent()); // One context tag data.createTag(GenTag().withId(42).withName(QStringLiteral("42")).asContext()); // Five tasks in the collection, two start today, three not, all forming an ancestry line data.createItem(GenTodo().withParent(42).withId(42).withTitle(QStringLiteral("42")).withUid("42")); - data.createItem(GenTodo().withParent(42).withId(43).withTitle(QStringLiteral("43")).withUid("43").withParentUid("42").withStartDate(today.addSecs(300))); + data.createItem(GenTodo().withParent(42).withId(43).withTitle(QStringLiteral("43")).withUid("43").withParentUid("42").withStartDate(today)); data.createItem(GenTodo().withParent(42).withId(44).withTitle(QStringLiteral("44")).withUid("44").withParentUid("43")); - data.createItem(GenTodo().withParent(42).withId(45).withTitle(QStringLiteral("45")).withUid("45").withParentUid("44").withStartDate(today.addSecs(300))); + data.createItem(GenTodo().withParent(42).withId(45).withTitle(QStringLiteral("45")).withUid("45").withParentUid("44").withStartDate(today)); data.createItem(GenTodo().withParent(42).withId(46).withTitle(QStringLiteral("46")).withUid("46").withParentUid("45")); // WHEN auto serializer = Akonadi::Serializer::Ptr(new Akonadi::Serializer); auto cache = Akonadi::Cache::Ptr::create(serializer, Akonadi::MonitorInterface::Ptr(data.createMonitor())); QScopedPointer queries(new Akonadi::TaskQueries(createCachingStorage(data, cache), Akonadi::Serializer::Ptr(new Akonadi::Serializer), Akonadi::MonitorInterface::Ptr(data.createMonitor()), cache)); auto result = queries->findWorkdayTopLevel(); result->data(); result = queries->findWorkdayTopLevel(); // Should not cause any problem or wrong data // THEN QVERIFY(result->data().isEmpty()); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("43")); // Should not change anything result = queries->findWorkdayTopLevel(); TestHelpers::waitForEmptyJobQueue(); QCOMPARE(result->data().size(), 1); QCOMPARE(result->data().at(0)->title(), QStringLiteral("43")); } }; ZANSHIN_TEST_MAIN(AkonadiTaskQueriesTest) #include "akonaditaskqueriestest.moc" diff --git a/tests/units/domain/tasktest.cpp b/tests/units/domain/tasktest.cpp index 58380558..0824a298 100644 --- a/tests/units/domain/tasktest.cpp +++ b/tests/units/domain/tasktest.cpp @@ -1,319 +1,319 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include "domain/task.h" #include "utils/datetime.h" using namespace Domain; class TaskTest : public QObject { Q_OBJECT public: explicit TaskTest(QObject *parent = Q_NULLPTR) : QObject(parent) { qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); } private slots: void shouldHaveEmptyPropertiesByDefault() { Task t; QCOMPARE(t.text(), QString()); QCOMPARE(t.title(), QString()); QCOMPARE(t.isDone(), false); - QCOMPARE(t.startDate(), QDateTime()); - QCOMPARE(t.dueDate(), QDateTime()); - QCOMPARE(t.doneDate(), QDateTime()); + QCOMPARE(t.startDate(), QDate()); + QCOMPARE(t.dueDate(), QDate()); + QCOMPARE(t.doneDate(), QDate()); QCOMPARE(t.recurrence(), Domain::Task::NoRecurrence); QVERIFY(t.attachments().isEmpty()); QVERIFY(!t.delegate().isValid()); } void shouldHaveValueBasedAttachment() { Task::Attachment a; QVERIFY(!a.isValid()); QVERIFY(!a.isUri()); QCOMPARE(a.uri(), QUrl()); QCOMPARE(a.data(), QByteArray()); QCOMPARE(a.label(), QString()); QCOMPARE(a.mimeType(), QString()); QCOMPARE(a.iconName(), QString()); a.setUri(QUrl("https://www.kde.org")); QVERIFY(a.isValid()); QVERIFY(a.isUri()); QCOMPARE(a.uri(), QUrl("https://www.kde.org")); QCOMPARE(a.data(), QByteArray()); QCOMPARE(a.label(), QString()); QCOMPARE(a.mimeType(), QString()); QCOMPARE(a.iconName(), QString()); a.setData(QByteArrayLiteral("foobar")); QVERIFY(a.isValid()); QVERIFY(!a.isUri()); QCOMPARE(a.uri(), QUrl()); QCOMPARE(a.data(), QByteArrayLiteral("foobar")); QCOMPARE(a.label(), QString()); QCOMPARE(a.mimeType(), QString()); QCOMPARE(a.iconName(), QString()); a.setLabel(QStringLiteral("baz")); QVERIFY(a.isValid()); QVERIFY(!a.isUri()); QCOMPARE(a.uri(), QUrl()); QCOMPARE(a.data(), QByteArrayLiteral("foobar")); QCOMPARE(a.label(), QStringLiteral("baz")); QCOMPARE(a.mimeType(), QString()); QCOMPARE(a.iconName(), QString()); a.setMimeType(QStringLiteral("text/plain")); QVERIFY(a.isValid()); QVERIFY(!a.isUri()); QCOMPARE(a.uri(), QUrl()); QCOMPARE(a.data(), QByteArrayLiteral("foobar")); QCOMPARE(a.label(), QStringLiteral("baz")); QCOMPARE(a.mimeType(), QStringLiteral("text/plain")); QCOMPARE(a.iconName(), QString()); a.setIconName(QStringLiteral("text")); QVERIFY(a.isValid()); QVERIFY(!a.isUri()); QCOMPARE(a.uri(), QUrl()); QCOMPARE(a.data(), QByteArrayLiteral("foobar")); QCOMPARE(a.label(), QStringLiteral("baz")); QCOMPARE(a.mimeType(), QStringLiteral("text/plain")); QCOMPARE(a.iconName(), QStringLiteral("text")); a.setUri(QUrl("https://www.kde.org")); QVERIFY(a.isValid()); QVERIFY(a.isUri()); QCOMPARE(a.uri(), QUrl("https://www.kde.org")); QCOMPARE(a.data(), QByteArray()); QCOMPARE(a.label(), QStringLiteral("baz")); QCOMPARE(a.mimeType(), QStringLiteral("text/plain")); QCOMPARE(a.iconName(), QStringLiteral("text")); a.setUri(QUrl()); QVERIFY(!a.isValid()); QVERIFY(!a.isUri()); QCOMPARE(a.uri(), QUrl()); QCOMPARE(a.data(), QByteArray()); QCOMPARE(a.label(), QStringLiteral("baz")); QCOMPARE(a.mimeType(), QStringLiteral("text/plain")); QCOMPARE(a.iconName(), QStringLiteral("text")); } void shouldHaveValueBasedDelegate() { Task::Delegate d; QVERIFY(!d.isValid()); QCOMPARE(d.name(), QString()); QCOMPARE(d.email(), QString()); QCOMPARE(d.display(), QString()); d.setName(QStringLiteral("John Doe")); QVERIFY(!d.isValid()); QCOMPARE(d.name(), QStringLiteral("John Doe")); QCOMPARE(d.email(), QString()); QCOMPARE(d.display(), QString()); d.setEmail(QStringLiteral("doe@somewhere.com")); QVERIFY(d.isValid()); QCOMPARE(d.name(), QStringLiteral("John Doe")); QCOMPARE(d.email(), QStringLiteral("doe@somewhere.com")); QCOMPARE(d.display(), QStringLiteral("John Doe")); d.setName(QString()); QVERIFY(d.isValid()); QCOMPARE(d.name(), QString()); QCOMPARE(d.email(), QStringLiteral("doe@somewhere.com")); QCOMPARE(d.display(), QStringLiteral("doe@somewhere.com")); } void shouldNotifyStatusChanges() { Task t; QSignalSpy spy(&t, &Task::doneChanged); t.setDone(true); QCOMPARE(spy.count(), 1); QCOMPARE(spy.first().first().toBool(), true); } void shouldNotNotifyIdenticalStatusChanges() { Task t; t.setDone(true); QSignalSpy spy(&t, &Task::doneChanged); t.setDone(true); QCOMPARE(spy.count(), 0); } void shouldNotifyStartDateChanges() { Task t; QSignalSpy spy(&t, &Task::startDateChanged); - t.setStartDate(QDateTime(QDate(2014, 1, 13))); + t.setStartDate(QDate(2014, 1, 13)); QCOMPARE(spy.count(), 1); QCOMPARE(spy.first().first().toDateTime(), QDateTime(QDate(2014, 1, 13))); } void shouldNotNotifyIdenticalStartDateChanges() { Task t; - t.setStartDate(QDateTime(QDate(2014, 1, 13))); + t.setStartDate(QDate(2014, 1, 13)); QSignalSpy spy(&t, &Task::startDateChanged); - t.setStartDate(QDateTime(QDate(2014, 1, 13))); + t.setStartDate(QDate(2014, 1, 13)); QCOMPARE(spy.count(), 0); } void shouldNotifyDueDateChanges() { Task t; QSignalSpy spy(&t, &Task::dueDateChanged); - t.setDueDate(QDateTime(QDate(2014, 1, 13))); + t.setDueDate(QDate(2014, 1, 13)); QCOMPARE(spy.count(), 1); QCOMPARE(spy.first().first().toDateTime(), QDateTime(QDate(2014, 1, 13))); } void shouldNotNotifyIdenticalDueDateChanges() { Task t; - t.setDueDate(QDateTime(QDate(2014, 1, 13))); + t.setDueDate(QDate(2014, 1, 13)); QSignalSpy spy(&t, &Task::dueDateChanged); - t.setDueDate(QDateTime(QDate(2014, 1, 13))); + t.setDueDate(QDate(2014, 1, 13)); QCOMPARE(spy.count(), 0); } void shouldNotifyRecurrenceChanges() { Task t; QSignalSpy spy(&t, &Task::recurrenceChanged); t.setRecurrence(Task::RecursWeekly); QCOMPARE(spy.count(), 1); QCOMPARE(spy.first().first().value(), Task::RecursWeekly); } void shouldNotNotifyIdenticalRecurrenceChanges() { Task t; t.setRecurrence(Task::RecursWeekly); QSignalSpy spy(&t, &Task::recurrenceChanged); t.setRecurrence(Task::RecursWeekly); QCOMPARE(spy.count(), 0); } void shouldNotifyAttachmentsChanges() { Task::Attachments attachments; attachments.append(Task::Attachment(QByteArrayLiteral("foobar"))); attachments.append(Task::Attachment(QUrl("https://www.kde.org"))); Task t; QSignalSpy spy(&t, &Task::attachmentsChanged); t.setAttachments(attachments); QCOMPARE(spy.count(), 1); QCOMPARE(spy.first().first().value(), attachments); } void shouldNotNotifyIdenticalAttachmentsChanges() { Task::Attachments attachments; attachments.append(Task::Attachment(QByteArrayLiteral("foobar"))); attachments.append(Task::Attachment(QUrl("https://www.kde.org"))); Task t; t.setAttachments(attachments); QSignalSpy spy(&t, &Task::attachmentsChanged); t.setAttachments(attachments); QCOMPARE(spy.count(), 0); } void shouldNotifyDelegateChanges() { Task t; QSignalSpy spy(&t, &Task::delegateChanged); t.setDelegate(Task::Delegate(QStringLiteral("John Doe"), QStringLiteral("doe@somewhere.com"))); QCOMPARE(spy.count(), 1); QCOMPARE(spy.first().first().value(), Task::Delegate(QStringLiteral("John Doe"), QStringLiteral("doe@somewhere.com"))); } void shouldNotNotifyIdenticalDelegateChanges() { Task t; t.setDelegate(Task::Delegate(QStringLiteral("John Doe"), QStringLiteral("doe@somewhere.com"))); QSignalSpy spy(&t, &Task::delegateChanged); t.setDelegate(Task::Delegate(QStringLiteral("John Doe"), QStringLiteral("doe@somewhere.com"))); QCOMPARE(spy.count(), 0); } void shouldNotifyDoneDateChanges() { Task t; QSignalSpy spy(&t, &Task::doneDateChanged); - t.setDoneDate(QDateTime(QDate(2014, 1, 13))); + t.setDoneDate(QDate(2014, 1, 13)); QCOMPARE(spy.count(), 1); - QCOMPARE(spy.first().first().toDateTime(), QDateTime(QDate(2014, 1, 13))); + QCOMPARE(spy.first().first().toDate(), QDate(2014, 1, 13)); } void shouldNotNotifyIdenticalDoneDateChanges() { Task t; - t.setDoneDate(QDateTime(QDate(2014, 1, 13))); + t.setDoneDate(QDate(2014, 1, 13)); QSignalSpy spy(&t, &Task::doneDateChanged); - t.setDoneDate(QDateTime(QDate(2014, 1, 13))); + t.setDoneDate(QDate(2014, 1, 13)); QCOMPARE(spy.count(), 0); } void shouldNotifyDoneDateSet() { Task t; QSignalSpy spy(&t, &Task::doneDateChanged); t.setDone(true); QCOMPARE(spy.count(), 1); - QCOMPARE(spy.takeFirst().at(0).toDateTime().date(), Utils::DateTime::currentDateTime().date()); + QCOMPARE(spy.takeFirst().at(0).toDate(), Utils::DateTime::currentDate()); } void shouldNotifyDoneDateUnset() { Task t; t.setDone(true); QSignalSpy spy(&t, &Task::doneDateChanged); t.setDone(false); QCOMPARE(spy.count(), 1); - QCOMPARE(spy.takeFirst().at(0).toDateTime(), QDateTime()); + QCOMPARE(spy.takeFirst().at(0).toDate(), QDate()); } }; ZANSHIN_TEST_MAIN(TaskTest) #include "tasktest.moc" diff --git a/tests/units/presentation/artifacteditormodeltest.cpp b/tests/units/presentation/artifacteditormodeltest.cpp index c01678c5..0535c856 100644 --- a/tests/units/presentation/artifacteditormodeltest.cpp +++ b/tests/units/presentation/artifacteditormodeltest.cpp @@ -1,645 +1,645 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include "utils/mockobject.h" #include #include #include "testlib/fakejob.h" #include "domain/task.h" #include "domain/note.h" #include "presentation/artifacteditormodel.h" #include "presentation/errorhandler.h" using namespace mockitopp; class FakeErrorHandler : public Presentation::ErrorHandler { public: void doDisplayMessage(const QString &message) override { m_message = message; } QString m_message; }; class ArtifactEditorModelTest : public QObject { Q_OBJECT public: explicit ArtifactEditorModelTest(QObject *parent = Q_NULLPTR) : QObject(parent) { qRegisterMetaType(); Presentation::ArtifactEditorModel::setAutoSaveDelay(50); } private slots: void shouldHaveEmptyDefaultState() { // GIVEN Presentation::ArtifactEditorModel model; // WHEN // Nothing // THEN QVERIFY(model.artifact().isNull()); QVERIFY(!model.hasTaskProperties()); QVERIFY(model.text().isEmpty()); QVERIFY(model.title().isEmpty()); QVERIFY(!model.isDone()); QVERIFY(model.startDate().isNull()); QVERIFY(model.dueDate().isNull()); QCOMPARE(model.recurrence(), Domain::Task::NoRecurrence); QVERIFY(model.attachmentModel() != nullptr); QVERIFY(model.delegateText().isNull()); QVERIFY(!model.hasSaveFunction()); QVERIFY(!model.hasDelegateFunction()); auto am = model.attachmentModel(); QCOMPARE(am->rowCount(), 0); } void shouldHaveTaskProperties() { // GIVEN Presentation::ArtifactEditorModel model; QSignalSpy textSpy(&model, &Presentation::ArtifactEditorModel::textChanged); QSignalSpy titleSpy(&model, &Presentation::ArtifactEditorModel::titleChanged); QSignalSpy doneSpy(&model, &Presentation::ArtifactEditorModel::doneChanged); QSignalSpy startSpy(&model, &Presentation::ArtifactEditorModel::startDateChanged); QSignalSpy dueSpy(&model, &Presentation::ArtifactEditorModel::dueDateChanged); QSignalSpy recurrenceSpy(&model, &Presentation::ArtifactEditorModel::recurrenceChanged); QSignalSpy attachmentSpy(model.attachmentModel(), &QAbstractItemModel::modelReset); QSignalSpy delegateSpy(&model, &Presentation::ArtifactEditorModel::delegateTextChanged); Domain::Task::Attachments attachments; Domain::Task::Attachment dataAttachment; dataAttachment.setData("foo"); dataAttachment.setLabel("dataAttachment"); dataAttachment.setMimeType("text/plain"); dataAttachment.setIconName("text-plain"); attachments.append(dataAttachment); Domain::Task::Attachment uriAttachment; uriAttachment.setUri(QUrl("https://www.kde.org")); uriAttachment.setLabel("uriAttachment"); uriAttachment.setMimeType("text/html"); uriAttachment.setIconName("text-html"); attachments.append(uriAttachment); auto task = Domain::Task::Ptr::create(); task->setText(QStringLiteral("description")); task->setTitle(QStringLiteral("title")); task->setDone(true); - task->setStartDate(QDateTime::currentDateTime()); - task->setDueDate(QDateTime::currentDateTime().addDays(2)); + task->setStartDate(QDate::currentDate()); + task->setDueDate(QDate::currentDate().addDays(2)); task->setRecurrence(Domain::Task::RecursDaily); task->setAttachments(attachments); task->setDelegate(Domain::Task::Delegate(QStringLiteral("John Doe"), QStringLiteral("john@doe.com"))); // WHEN model.setArtifact(task); // To make sure we don't signal too much model.setText(task->text()); model.setTitle(task->title()); model.setDone(task->isDone()); model.setStartDate(task->startDate()); model.setDueDate(task->dueDate()); model.setRecurrence(task->recurrence()); // THEN QVERIFY(model.hasTaskProperties()); QCOMPARE(textSpy.size(), 1); QCOMPARE(textSpy.takeFirst().at(0).toString(), task->text()); QCOMPARE(model.property("text").toString(), task->text()); QCOMPARE(titleSpy.size(), 1); QCOMPARE(titleSpy.takeFirst().at(0).toString(), task->title()); QCOMPARE(model.property("title").toString(), task->title()); QCOMPARE(doneSpy.size(), 1); QCOMPARE(doneSpy.takeFirst().at(0).toBool(), task->isDone()); QCOMPARE(model.property("done").toBool(), task->isDone()); QCOMPARE(startSpy.size(), 1); - QCOMPARE(startSpy.takeFirst().at(0).toDateTime(), task->startDate()); - QCOMPARE(model.property("startDate").toDateTime(), task->startDate()); + QCOMPARE(startSpy.takeFirst().at(0).toDate(), task->startDate()); + QCOMPARE(model.property("startDate").toDate(), task->startDate()); QCOMPARE(dueSpy.size(), 1); - QCOMPARE(dueSpy.takeFirst().at(0).toDateTime(), task->dueDate()); - QCOMPARE(model.property("dueDate").toDateTime(), task->dueDate()); + QCOMPARE(dueSpy.takeFirst().at(0).toDate(), task->dueDate()); + QCOMPARE(model.property("dueDate").toDate(), task->dueDate()); QCOMPARE(recurrenceSpy.size(), 1); QCOMPARE(recurrenceSpy.takeFirst().at(0).value(), task->recurrence()); QCOMPARE(model.property("recurrence").value(), task->recurrence()); QCOMPARE(delegateSpy.size(), 1); QCOMPARE(delegateSpy.takeFirst().at(0).toString(), task->delegate().display()); QCOMPARE(model.property("delegateText").toString(), task->delegate().display()); QCOMPARE(attachmentSpy.size(), 1); auto am = model.attachmentModel(); QCOMPARE(am->rowCount(), 2); QCOMPARE(am->data(am->index(0, 0), Qt::DisplayRole).toString(), QStringLiteral("dataAttachment")); QCOMPARE(am->data(am->index(0, 0), Qt::DecorationRole).value(), QIcon::fromTheme("text-plain")); QCOMPARE(am->data(am->index(1, 0), Qt::DisplayRole).toString(), QStringLiteral("uriAttachment")); QCOMPARE(am->data(am->index(1, 0), Qt::DecorationRole).value(), QIcon::fromTheme("text-html")); } void shouldHaveNoteProperties() { // GIVEN Presentation::ArtifactEditorModel model; QSignalSpy textSpy(&model, &Presentation::ArtifactEditorModel::textChanged); QSignalSpy titleSpy(&model, &Presentation::ArtifactEditorModel::titleChanged); QSignalSpy doneSpy(&model, &Presentation::ArtifactEditorModel::doneChanged); QSignalSpy startSpy(&model, &Presentation::ArtifactEditorModel::startDateChanged); QSignalSpy dueSpy(&model, &Presentation::ArtifactEditorModel::dueDateChanged); QSignalSpy delegateSpy(&model, &Presentation::ArtifactEditorModel::delegateTextChanged); auto note = Domain::Note::Ptr::create(); note->setText(QStringLiteral("description")); note->setTitle(QStringLiteral("title")); // WHEN model.setArtifact(note); // To make sure we don't signal too much model.setText(note->text()); model.setTitle(note->title()); // THEN QVERIFY(!model.hasTaskProperties()); QCOMPARE(textSpy.size(), 1); QCOMPARE(textSpy.takeFirst().at(0).toString(), note->text()); QCOMPARE(model.property("text").toString(), note->text()); QCOMPARE(titleSpy.size(), 1); QCOMPARE(titleSpy.takeFirst().at(0).toString(), note->title()); QCOMPARE(model.property("title").toString(), note->title()); QCOMPARE(doneSpy.size(), 1); QCOMPARE(doneSpy.takeFirst().at(0).toBool(), false); QCOMPARE(model.property("done").toBool(), false); QCOMPARE(startSpy.size(), 1); - QVERIFY(startSpy.takeFirst().at(0).toDateTime().isNull()); - QVERIFY(model.property("startDate").toDateTime().isNull()); + QVERIFY(startSpy.takeFirst().at(0).toDate().isNull()); + QVERIFY(model.property("startDate").toDate().isNull()); QCOMPARE(dueSpy.size(), 1); - QVERIFY(dueSpy.takeFirst().at(0).toDateTime().isNull()); - QVERIFY(model.property("dueDate").toDateTime().isNull()); + QVERIFY(dueSpy.takeFirst().at(0).toDate().isNull()); + QVERIFY(model.property("dueDate").toDate().isNull()); QCOMPARE(delegateSpy.size(), 1); QVERIFY(delegateSpy.takeFirst().at(0).toString().isEmpty()); QVERIFY(model.property("delegateText").toString().isEmpty()); } void shouldReactToArtifactPropertyChanges_data() { QTest::addColumn("artifact"); QTest::addColumn("propertyName"); QTest::addColumn("propertyValue"); QTest::addColumn("signal"); QTest::newRow("note text") << Domain::Artifact::Ptr(Domain::Note::Ptr::create()) << QByteArray("text") << QVariant("new text") << QByteArray(SIGNAL(textChanged(QString))); QTest::newRow("note title") << Domain::Artifact::Ptr(Domain::Note::Ptr::create()) << QByteArray("title") << QVariant("new title") << QByteArray(SIGNAL(titleChanged(QString))); QTest::newRow("task text") << Domain::Artifact::Ptr(Domain::Task::Ptr::create()) << QByteArray("text") << QVariant("new text") << QByteArray(SIGNAL(textChanged(QString))); QTest::newRow("task title") << Domain::Artifact::Ptr(Domain::Task::Ptr::create()) << QByteArray("title") << QVariant("new title") << QByteArray(SIGNAL(titleChanged(QString))); QTest::newRow("task done") << Domain::Artifact::Ptr(Domain::Task::Ptr::create()) << QByteArray("done") << QVariant(true) << QByteArray(SIGNAL(doneChanged(bool))); QTest::newRow("task start") << Domain::Artifact::Ptr(Domain::Task::Ptr::create()) << QByteArray("startDate") - << QVariant(QDateTime::currentDateTime()) - << QByteArray(SIGNAL(startDateChanged(QDateTime))); + << QVariant(QDate::currentDate()) + << QByteArray(SIGNAL(startDateChanged(QDate))); QTest::newRow("task due") << Domain::Artifact::Ptr(Domain::Task::Ptr::create()) << QByteArray("dueDate") - << QVariant(QDateTime::currentDateTime().addDays(2)) - << QByteArray(SIGNAL(dueDateChanged(QDateTime))); + << QVariant(QDate::currentDate().addDays(2)) + << QByteArray(SIGNAL(dueDateChanged(QDate))); QTest::newRow("task recurrence") << Domain::Artifact::Ptr(Domain::Task::Ptr::create()) << QByteArray("recurrence") << QVariant::fromValue(Domain::Task::RecursDaily) << QByteArray(SIGNAL(recurrenceChanged(Domain::Task::Recurrence))); } void shouldReactToArtifactPropertyChanges() { // GIVEN QFETCH(Domain::Artifact::Ptr, artifact); QFETCH(QByteArray, propertyName); QFETCH(QVariant, propertyValue); QFETCH(QByteArray, signal); Presentation::ArtifactEditorModel model; model.setArtifact(artifact); QSignalSpy spy(&model, signal.constData()); // WHEN artifact->setProperty(propertyName, propertyValue); // THEN QCOMPARE(spy.size(), 1); QCOMPARE(spy.takeFirst().at(0), propertyValue); QCOMPARE(model.property(propertyName), propertyValue); } void shouldNotReactToArtifactPropertyChangesWhenEditing_data() { shouldReactToArtifactPropertyChanges_data(); } void shouldNotReactToArtifactPropertyChangesWhenEditing() { // GIVEN QFETCH(Domain::Artifact::Ptr, artifact); QFETCH(QByteArray, propertyName); QFETCH(QVariant, propertyValue); QFETCH(QByteArray, signal); Presentation::ArtifactEditorModel model; model.setArtifact(artifact); QSignalSpy spy(&model, signal.constData()); // WHEN const auto oldPropertyValue = artifact->property(propertyName); model.setEditingInProgress(true); artifact->setProperty(propertyName, propertyValue); // THEN QVERIFY(spy.isEmpty()); QCOMPARE(model.property(propertyName), oldPropertyValue); } void shouldReactToTaskDelegateChanges() { // GIVEN auto task = Domain::Task::Ptr::create(); Presentation::ArtifactEditorModel model; model.setArtifact(task); QSignalSpy spy(&model, &Presentation::ArtifactEditorModel::delegateTextChanged); // WHEN task->setDelegate(Domain::Task::Delegate(QStringLiteral("John Doe"), QStringLiteral("john@doe.com"))); // THEN QCOMPARE(spy.size(), 1); QCOMPARE(spy.takeFirst().at(0).toString(), task->delegate().display()); QCOMPARE(model.property("delegateText").toString(), task->delegate().display()); } void shouldApplyChangesBackToArtifactAfterADelay_data() { shouldReactToArtifactPropertyChanges_data(); } void shouldApplyChangesBackToArtifactAfterADelay() { // GIVEN QFETCH(Domain::Artifact::Ptr, artifact); QFETCH(QByteArray, propertyName); QFETCH(QVariant, propertyValue); QFETCH(QByteArray, signal); auto savedArtifact = Domain::Artifact::Ptr(); auto save = [this, &savedArtifact] (const Domain::Artifact::Ptr &artifact) { savedArtifact = artifact; return new FakeJob(this); }; Presentation::ArtifactEditorModel model; model.setSaveFunction(save); model.setArtifact(artifact); QSignalSpy spy(&model, signal.constData()); // WHEN model.setProperty(propertyName, propertyValue); // THEN QCOMPARE(spy.size(), 1); QCOMPARE(spy.takeFirst().at(0), propertyValue); QCOMPARE(model.property(propertyName), propertyValue); QVERIFY(artifact->property(propertyName) != propertyValue); QVERIFY(!savedArtifact); // WHEN (apply after delay) QTest::qWait(model.autoSaveDelay() + 50); // THEN QCOMPARE(savedArtifact, artifact); QCOMPARE(artifact->property(propertyName), propertyValue); } void shouldApplyChangesImmediatelyIfANewArtifactIsSet_data() { shouldReactToArtifactPropertyChanges_data(); } void shouldApplyChangesImmediatelyIfANewArtifactIsSet() { // GIVEN QFETCH(Domain::Artifact::Ptr, artifact); QFETCH(QByteArray, propertyName); QFETCH(QVariant, propertyValue); QFETCH(QByteArray, signal); auto savedArtifact = Domain::Artifact::Ptr(); auto save = [this, &savedArtifact] (const Domain::Artifact::Ptr &artifact) { savedArtifact = artifact; return new FakeJob(this); }; Presentation::ArtifactEditorModel model; model.setSaveFunction(save); QVERIFY(model.hasSaveFunction()); model.setArtifact(artifact); QSignalSpy spy(&model, signal.constData()); // WHEN model.setProperty(propertyName, propertyValue); // THEN QCOMPARE(spy.size(), 1); QCOMPARE(spy.takeFirst().at(0), propertyValue); QCOMPARE(model.property(propertyName), propertyValue); QVERIFY(artifact->property(propertyName) != propertyValue); QVERIFY(!savedArtifact); // WHEN (apply immediately) model.setArtifact(Domain::Task::Ptr::create()); // THEN QCOMPARE(savedArtifact, artifact); QCOMPARE(artifact->property(propertyName), propertyValue); savedArtifact.clear(); // WHEN (nothing else happens after a delay) QTest::qWait(model.autoSaveDelay() + 50); // THEN QVERIFY(!savedArtifact); QCOMPARE(artifact->property(propertyName), propertyValue); } void shouldApplyChangesImmediatelyIfDeleted_data() { shouldReactToArtifactPropertyChanges_data(); } void shouldApplyChangesImmediatelyIfDeleted() { // GIVEN QFETCH(Domain::Artifact::Ptr, artifact); QFETCH(QByteArray, propertyName); QFETCH(QVariant, propertyValue); QFETCH(QByteArray, signal); auto savedArtifact = Domain::Artifact::Ptr(); auto save = [this, &savedArtifact] (const Domain::Artifact::Ptr &artifact) { savedArtifact = artifact; return new FakeJob(this); }; auto model = new Presentation::ArtifactEditorModel; model->setSaveFunction(save); QVERIFY(model->hasSaveFunction()); model->setArtifact(artifact); QSignalSpy spy(model, signal.constData()); // WHEN model->setProperty(propertyName, propertyValue); // THEN QCOMPARE(spy.size(), 1); QCOMPARE(spy.takeFirst().at(0), propertyValue); QCOMPARE(model->property(propertyName), propertyValue); QVERIFY(artifact->property(propertyName) != propertyValue); QVERIFY(!savedArtifact); // WHEN (apply immediately) delete model; // THEN QCOMPARE(savedArtifact, artifact); QCOMPARE(artifact->property(propertyName), propertyValue); } void shouldLaunchDelegation() { // GIVEN auto task = Domain::Task::Ptr::create(); auto expectedDelegate = Domain::Task::Delegate(QStringLiteral("John Doe"), QStringLiteral("john@doe.com")); auto delegatedTask = Domain::Task::Ptr(); auto delegate = Domain::Task::Delegate(); auto delegateFunction = [this, &delegatedTask, &delegate] (const Domain::Task::Ptr &task, const Domain::Task::Delegate &d) { delegatedTask = task; delegate = d; return new FakeJob(this); }; Presentation::ArtifactEditorModel model; model.setDelegateFunction(delegateFunction); QVERIFY(model.hasDelegateFunction()); model.setArtifact(task); // WHEN model.delegate(QStringLiteral("John Doe"), QStringLiteral("john@doe.com")); // THEN QCOMPARE(delegatedTask, task); QCOMPARE(delegate, expectedDelegate); QVERIFY(!task->delegate().isValid()); } void shouldGetAnErrorMessageWhenSaveFailed() { // GIVEN auto task = Domain::Task::Ptr::create(); task->setTitle(QStringLiteral("Task 1")); auto savedArtifact = Domain::Artifact::Ptr(); auto save = [this, &savedArtifact] (const Domain::Artifact::Ptr &artifact) { savedArtifact = artifact; auto job = new FakeJob(this); job->setExpectedError(KJob::KilledJobError, QStringLiteral("Foo")); return job; }; auto model = new Presentation::ArtifactEditorModel; model->setSaveFunction(save); QVERIFY(model->hasSaveFunction()); FakeErrorHandler errorHandler; model->setErrorHandler(&errorHandler); model->setArtifact(task); // WHEN model->setProperty("title", "Foo"); delete model; // THEN QTest::qWait(150); QCOMPARE(errorHandler.m_message, QStringLiteral("Cannot modify task Task 1: Foo")); } void shouldDisconnectFromPreviousArtifact_data() { shouldReactToArtifactPropertyChanges_data(); } void shouldDisconnectFromPreviousArtifact() { // GIVEN QFETCH(Domain::Artifact::Ptr, artifact); QFETCH(QByteArray, propertyName); QFETCH(QVariant, propertyValue); QFETCH(QByteArray, signal); Presentation::ArtifactEditorModel model; model.setArtifact(artifact); QSignalSpy spy(&model, signal.constData()); Domain::Artifact::Ptr newArtifact = Domain::Task::Ptr::create(); // WHEN model.setArtifact(newArtifact); // modifying the *old* artifact should have no effect. artifact->setProperty(propertyName, propertyValue); // THEN QCOMPARE(spy.size(), 1); // emitted by setArtifact QVERIFY(model.property(propertyName) != artifact->property(propertyName)); } void shouldAddAttachments() { // GIVEN QTemporaryFile temporaryFile(QDir::tempPath() + "/artifacteditormodeltest_XXXXXX.txt"); temporaryFile.open(); temporaryFile.write("foo bar"); temporaryFile.close(); auto fileName = temporaryFile.fileName().mid(QDir::tempPath().size() + 1); auto task = Domain::Task::Ptr::create(); auto savedArtifact = Domain::Artifact::Ptr(); auto save = [this, &savedArtifact] (const Domain::Artifact::Ptr &artifact) { savedArtifact = artifact; return new FakeJob(this); }; Presentation::ArtifactEditorModel model; model.setSaveFunction(save); model.setArtifact(task); QSignalSpy spy(model.attachmentModel(), &QAbstractItemModel::modelReset); // WHEN model.addAttachment(temporaryFile.fileName()); // THEN QCOMPARE(spy.size(), 1); QCOMPARE(model.attachmentModel()->rowCount(), 1); QVERIFY(!savedArtifact); // WHEN (nothing else happens after a delay) QTest::qWait(model.autoSaveDelay() + 50); // THEN QCOMPARE(savedArtifact.objectCast(), task); QCOMPARE(task->attachments().size(), 1); QCOMPARE(task->attachments().first().label(), fileName); QCOMPARE(task->attachments().first().mimeType(), QStringLiteral("text/plain")); QCOMPARE(task->attachments().first().iconName(), QStringLiteral("text-plain")); QCOMPARE(task->attachments().first().data(), QByteArrayLiteral("foo bar")); } void shouldRemoveAttachments() { // GIVEN auto task = Domain::Task::Ptr::create(); task->setAttachments(Domain::Task::Attachments() << Domain::Task::Attachment("foo") << Domain::Task::Attachment("bar")); auto savedArtifact = Domain::Artifact::Ptr(); auto save = [this, &savedArtifact] (const Domain::Artifact::Ptr &artifact) { savedArtifact = artifact; return new FakeJob(this); }; Presentation::ArtifactEditorModel model; model.setSaveFunction(save); model.setArtifact(task); QSignalSpy spy(model.attachmentModel(), &QAbstractItemModel::modelReset); // WHEN model.removeAttachment(model.attachmentModel()->index(0, 0)); // THEN QCOMPARE(spy.size(), 1); QCOMPARE(model.attachmentModel()->rowCount(), 1); QVERIFY(!savedArtifact); // WHEN (nothing else happens after a delay) QTest::qWait(model.autoSaveDelay() + 50); // THEN QCOMPARE(savedArtifact.objectCast(), task); QCOMPARE(task->attachments().size(), 1); QCOMPARE(task->attachments().first().data(), QByteArrayLiteral("bar")); } }; ZANSHIN_TEST_MAIN(ArtifactEditorModelTest) #include "artifacteditormodeltest.moc" diff --git a/tests/units/presentation/artifactfilterproxymodeltest.cpp b/tests/units/presentation/artifactfilterproxymodeltest.cpp index 2d79a2c3..dc2e569d 100644 --- a/tests/units/presentation/artifactfilterproxymodeltest.cpp +++ b/tests/units/presentation/artifactfilterproxymodeltest.cpp @@ -1,289 +1,296 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include "domain/note.h" #include "domain/task.h" #include "presentation/artifactfilterproxymodel.h" #include "presentation/querytreemodelbase.h" +#include "utils/datetime.h" Q_DECLARE_METATYPE(QList) class ArtifactFilterProxyModelTest : public QObject { Q_OBJECT private: QStandardItem *createTaskItem(const QString &title, const QString &text, const QDate &start = QDate(), const QDate &due = QDate()) const { auto task = Domain::Task::Ptr::create(); task->setTitle(title); task->setText(text); - task->setStartDate(QDateTime(start)); - task->setDueDate(QDateTime(due)); + task->setStartDate(start); + task->setDueDate(due); auto item = new QStandardItem; item->setData(task->title(), Qt::DisplayRole); item->setData(QVariant::fromValue(Domain::Artifact::Ptr(task)), Presentation::QueryTreeModelBase::ObjectRole); return item; } QStandardItem *createNoteItem(const QString &title, const QString &text) const { auto note = Domain::Note::Ptr::create(); note->setTitle(title); note->setText(text); auto item = new QStandardItem; item->setData(note->title(), Qt::DisplayRole); item->setData(QVariant::fromValue(Domain::Artifact::Ptr(note)), Presentation::QueryTreeModelBase::ObjectRole); return item; } private slots: + void initTestCase() + { + qputenv("ZANSHIN_OVERRIDE_DATE", "2015-03-11"); + } + void shouldHaveDefaultState() { Presentation::ArtifactFilterProxyModel proxy; QVERIFY(!proxy.sourceModel()); QCOMPARE(proxy.sortColumn(), 0); QCOMPARE(proxy.sortOrder(), Qt::AscendingOrder); QCOMPARE(proxy.sortType(), Presentation::ArtifactFilterProxyModel::TitleSort); QCOMPARE(proxy.sortCaseSensitivity(), Qt::CaseInsensitive); QVERIFY(!proxy.showFutureTasks()); } void shouldFilterByTextAndTitle() { // GIVEN QStandardItemModel input; input.appendRow(createTaskItem(QStringLiteral("1. foo"), QStringLiteral("find me"))); input.appendRow(createTaskItem(QStringLiteral("2. Find Me"), QStringLiteral("bar"))); input.appendRow(createTaskItem(QStringLiteral("3. baz"), QStringLiteral("baz"))); input.appendRow(createNoteItem(QStringLiteral("4. foo"), QStringLiteral("find me"))); input.appendRow(createNoteItem(QStringLiteral("5. find me"), QStringLiteral("bar"))); input.appendRow(createNoteItem(QStringLiteral("6. baz"), QStringLiteral("baz"))); Presentation::ArtifactFilterProxyModel output; output.setSourceModel(&input); // WHEN output.setFilterFixedString(QStringLiteral("find me")); // THEN QCOMPARE(output.rowCount(), 4); QCOMPARE(output.index(0, 0).data().toString(), QStringLiteral("1. foo")); QCOMPARE(output.index(1, 0).data().toString(), QStringLiteral("2. Find Me")); QCOMPARE(output.index(2, 0).data().toString(), QStringLiteral("4. foo")); QCOMPARE(output.index(3, 0).data().toString(), QStringLiteral("5. find me")); } void shouldFilterByStartDate() { // GIVEN QStandardItemModel input; - input.appendRow(createTaskItem(QStringLiteral("1. past"), QStringLiteral(""), QDate::currentDate().addDays(-1))); - input.appendRow(createTaskItem(QStringLiteral("2. present"), QStringLiteral(""), QDate::currentDate())); - input.appendRow(createTaskItem(QStringLiteral("3. future"), QStringLiteral(""), QDate::currentDate().addDays(1))); + const auto today = Utils::DateTime::currentDate(); + input.appendRow(createTaskItem(QStringLiteral("1. past"), QStringLiteral(""), today.addDays(-1))); + input.appendRow(createTaskItem(QStringLiteral("2. present"), QStringLiteral(""), today)); + input.appendRow(createTaskItem(QStringLiteral("3. future"), QStringLiteral(""), today.addDays(1))); input.appendRow(createTaskItem(QStringLiteral("4. whatever"), QStringLiteral(""))); Presentation::ArtifactFilterProxyModel output; output.setSourceModel(&input); // WHEN output.setShowFutureTasks(true); // THEN QCOMPARE(output.rowCount(), 4); QCOMPARE(output.index(0, 0).data().toString(), QStringLiteral("1. past")); QCOMPARE(output.index(1, 0).data().toString(), QStringLiteral("2. present")); QCOMPARE(output.index(2, 0).data().toString(), QStringLiteral("3. future")); QCOMPARE(output.index(3, 0).data().toString(), QStringLiteral("4. whatever")); // WHEN output.setShowFutureTasks(false); // THEN QCOMPARE(output.rowCount(), 3); QCOMPARE(output.index(0, 0).data().toString(), QStringLiteral("1. past")); QCOMPARE(output.index(1, 0).data().toString(), QStringLiteral("2. present")); QCOMPARE(output.index(2, 0).data().toString(), QStringLiteral("4. whatever")); } void shouldKeepRowIfItHasAcceptableChildren() { // GIVEN QStandardItemModel input; input.appendRow(createTaskItem(QStringLiteral("1. foo"), QStringLiteral("find me"))); QStandardItem *item = createTaskItem(QStringLiteral("2. baz"), QStringLiteral("baz")); item->appendRow(createTaskItem(QStringLiteral("21. bar"), QStringLiteral("bar"))); item->appendRow(createNoteItem(QStringLiteral("22. foo"), QStringLiteral("Find Me"))); item->appendRow(createTaskItem(QStringLiteral("23. find me"), QStringLiteral("foo"))); input.appendRow(item); input.appendRow(createTaskItem(QStringLiteral("3. baz"), QStringLiteral("baz"))); Presentation::ArtifactFilterProxyModel output; output.setSourceModel(&input); // WHEN output.setFilterFixedString(QStringLiteral("find me")); // THEN QCOMPARE(output.rowCount(), 2); QCOMPARE(output.index(0, 0).data().toString(), QStringLiteral("1. foo")); QCOMPARE(output.index(1, 0).data().toString(), QStringLiteral("2. baz")); const QModelIndex parent = output.index(1, 0); QCOMPARE(output.rowCount(parent), 2); QCOMPARE(output.index(0, 0, parent).data().toString(), QStringLiteral("22. foo")); QCOMPARE(output.index(1, 0, parent).data().toString(), QStringLiteral("23. find me")); } void shouldSortFollowingType_data() { QTest::addColumn("sortType"); QTest::addColumn("sortOrder"); QTest::addColumn>("inputItems"); QTest::addColumn("expectedOutputTitles"); QList inputItems; QStringList expectedOutputTitles; inputItems.clear(); expectedOutputTitles.clear(); inputItems << createTaskItem(QStringLiteral("B"), QStringLiteral("foo")) << createNoteItem(QStringLiteral("A"), QStringLiteral("foo")) << createTaskItem(QStringLiteral("C"), QStringLiteral("foo")); expectedOutputTitles << QStringLiteral("A") << QStringLiteral("B") << QStringLiteral("C"); QTest::newRow("title ascending") << int(Presentation::ArtifactFilterProxyModel::TitleSort) << int(Qt::AscendingOrder) << inputItems << expectedOutputTitles; inputItems.clear(); expectedOutputTitles.clear(); inputItems << createTaskItem(QStringLiteral("B"), QStringLiteral("foo")) << createNoteItem(QStringLiteral("A"), QStringLiteral("foo")) << createTaskItem(QStringLiteral("C"), QStringLiteral("foo")); expectedOutputTitles << QStringLiteral("C") << QStringLiteral("B") << QStringLiteral("A"); QTest::newRow("title descending") << int(Presentation::ArtifactFilterProxyModel::TitleSort) << int(Qt::DescendingOrder) << inputItems << expectedOutputTitles; inputItems.clear(); expectedOutputTitles.clear(); inputItems << createTaskItem(QStringLiteral("B"), QStringLiteral("foo"), QDate(2014, 03, 10)) << createNoteItem(QStringLiteral("A"), QStringLiteral("foo")) << createTaskItem(QStringLiteral("C"), QStringLiteral("foo"), QDate(2014, 03, 01)) << createTaskItem(QStringLiteral("D"), QStringLiteral("foo")); expectedOutputTitles << QStringLiteral("C") << QStringLiteral("B") << QStringLiteral("D") << QStringLiteral("A"); QTest::newRow("start date ascending") << int(Presentation::ArtifactFilterProxyModel::DateSort) << int(Qt::AscendingOrder) << inputItems << expectedOutputTitles; inputItems.clear(); expectedOutputTitles.clear(); inputItems << createTaskItem(QStringLiteral("B"), QStringLiteral("foo"), QDate(2014, 03, 10)) << createNoteItem(QStringLiteral("A"), QStringLiteral("foo")) << createTaskItem(QStringLiteral("C"), QStringLiteral("foo"), QDate(2014, 03, 01)) << createTaskItem(QStringLiteral("D"), QStringLiteral("foo")); expectedOutputTitles << QStringLiteral("A") << QStringLiteral("D") << QStringLiteral("B") << QStringLiteral("C"); QTest::newRow("start date descending") << int(Presentation::ArtifactFilterProxyModel::DateSort) << int(Qt::DescendingOrder) << inputItems << expectedOutputTitles; inputItems.clear(); expectedOutputTitles.clear(); inputItems << createTaskItem(QStringLiteral("B"), QStringLiteral("foo"), QDate(), QDate(2014, 03, 10)) << createNoteItem(QStringLiteral("A"), QStringLiteral("foo")) << createTaskItem(QStringLiteral("C"), QStringLiteral("foo"), QDate(), QDate(2014, 03, 01)) << createTaskItem(QStringLiteral("D"), QStringLiteral("foo")); expectedOutputTitles << QStringLiteral("C") << QStringLiteral("B") << QStringLiteral("D") << QStringLiteral("A"); QTest::newRow("due date ascending") << int(Presentation::ArtifactFilterProxyModel::DateSort) << int(Qt::AscendingOrder) << inputItems << expectedOutputTitles; inputItems.clear(); expectedOutputTitles.clear(); inputItems << createTaskItem(QStringLiteral("B"), QStringLiteral("foo"), QDate(), QDate(2014, 03, 10)) << createNoteItem(QStringLiteral("A"), QStringLiteral("foo")) << createTaskItem(QStringLiteral("C"), QStringLiteral("foo"), QDate(), QDate(2014, 03, 01)) << createTaskItem(QStringLiteral("D"), QStringLiteral("foo")); expectedOutputTitles << QStringLiteral("A") << QStringLiteral("D") << QStringLiteral("B") << QStringLiteral("C"); QTest::newRow("due date descending") << int(Presentation::ArtifactFilterProxyModel::DateSort) << int(Qt::DescendingOrder) << inputItems << expectedOutputTitles; inputItems.clear(); expectedOutputTitles.clear(); inputItems << createTaskItem(QStringLiteral("A"), QStringLiteral("foo"), QDate(2014, 03, 01), QDate(2014, 03, 10)) << createTaskItem(QStringLiteral("B"), QStringLiteral("foo"), QDate(2014, 03, 10), QDate(2014, 03, 01)); expectedOutputTitles << QStringLiteral("B") << QStringLiteral("A"); QTest::newRow("due date over start date") << int(Presentation::ArtifactFilterProxyModel::DateSort) << int(Qt::AscendingOrder) << inputItems << expectedOutputTitles; inputItems.clear(); expectedOutputTitles.clear(); inputItems << createTaskItem(QStringLiteral("A"), QStringLiteral("foo"), QDate(), QDate(2014, 03, 10)) << createTaskItem(QStringLiteral("B"), QStringLiteral("foo"), QDate(2014, 03, 01), QDate()); expectedOutputTitles << QStringLiteral("B") << QStringLiteral("A"); QTest::newRow("due date over start date") << int(Presentation::ArtifactFilterProxyModel::DateSort) << int(Qt::AscendingOrder) << inputItems << expectedOutputTitles; } void shouldSortFollowingType() { // GIVEN QFETCH(int, sortType); QFETCH(int, sortOrder); QFETCH(QList, inputItems); QFETCH(QStringList, expectedOutputTitles); QStandardItemModel input; foreach (QStandardItem *item, inputItems) { input.appendRow(item); } // WHEN Presentation::ArtifactFilterProxyModel output; output.setSourceModel(&input); output.setSortType(Presentation::ArtifactFilterProxyModel::SortType(sortType)); output.setSortOrder(Qt::SortOrder(sortOrder)); QStringList outputTitles; outputTitles.reserve(output.rowCount()); for (int row = 0; row < output.rowCount(); row++) { outputTitles << output.index(row, 0).data().toString(); } // THEN QCOMPARE(outputTitles, expectedOutputTitles); } }; ZANSHIN_TEST_MAIN(ArtifactFilterProxyModelTest) #include "artifactfilterproxymodeltest.moc" diff --git a/tests/units/presentation/availabletaskpagesmodeltest.cpp b/tests/units/presentation/availabletaskpagesmodeltest.cpp index 66c48f10..1c7a2451 100644 --- a/tests/units/presentation/availabletaskpagesmodeltest.cpp +++ b/tests/units/presentation/availabletaskpagesmodeltest.cpp @@ -1,1283 +1,1283 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include "utils/mockobject.h" #include "utils/datetime.h" #include "domain/note.h" #include "presentation/availabletaskpagesmodel.h" #include "presentation/contextpagemodel.h" #include "presentation/errorhandler.h" #include "presentation/projectpagemodel.h" #include "presentation/querytreemodelbase.h" #include "presentation/taskinboxpagemodel.h" #include "presentation/workdaypagemodel.h" #include "testlib/fakejob.h" using namespace mockitopp; using namespace mockitopp::matcher; class FakeErrorHandler : public Presentation::ErrorHandler { public: void doDisplayMessage(const QString &message) override { m_message = message; } QString m_message; }; class AvailableTaskPagesModelTest : public QObject { Q_OBJECT private slots: void shouldDeclareOnlyProjectAndContextPages() { // GIVEN Presentation::AvailableTaskPagesModel pages({}, {}, {}, {}, {}, {}, {}); // THEN QVERIFY(pages.hasProjectPages()); QVERIFY(pages.hasContextPages()); QVERIFY(!pages.hasTagPages()); } void shouldListAvailablePages() { // GIVEN // Two selected data sources auto source1 = Domain::DataSource::Ptr::create(); source1->setName("source1"); auto source2 = Domain::DataSource::Ptr::create(); source2->setName("source2"); auto sourceProvider = Domain::QueryResultProvider::Ptr::create(); auto sourceResult = Domain::QueryResult::create(sourceProvider); sourceProvider->append(source1); sourceProvider->append(source2); // Two projects under source 1 auto project11 = Domain::Project::Ptr::create(); project11->setName(QStringLiteral("Project 11")); auto project12 = Domain::Project::Ptr::create(); project12->setName(QStringLiteral("Project 12")); auto project1Provider = Domain::QueryResultProvider::Ptr::create(); auto project1Result = Domain::QueryResult::create(project1Provider); project1Provider->append(project12); project1Provider->append(project11); // note: reversed order, to test sorting // Two projects under source 2 auto project21 = Domain::Project::Ptr::create(); project21->setName(QStringLiteral("Project 21")); auto project22 = Domain::Project::Ptr::create(); project22->setName(QStringLiteral("Project 22")); auto project2Provider = Domain::QueryResultProvider::Ptr::create(); auto project2Result = Domain::QueryResult::create(project2Provider); project2Provider->append(project22); project2Provider->append(project21); // note: reversed order, to test sorting // Two contexts auto context1 = Domain::Context::Ptr::create(); context1->setName(QStringLiteral("context 1")); auto context2 = Domain::Context::Ptr::create(); context2->setName(QStringLiteral("context 2")); auto contextProvider = Domain::QueryResultProvider::Ptr::create(); auto contextResult = Domain::QueryResult::create(contextProvider); contextProvider->append(context1); contextProvider->append(context2); // Two artifacts (used for dropping later on) Domain::Artifact::Ptr taskToDrop(new Domain::Task); Domain::Artifact::Ptr noteToDrop(new Domain::Note); Utils::MockObject dataSourceQueriesMock; dataSourceQueriesMock(&Domain::DataSourceQueries::findAllSelected).when().thenReturn(sourceResult); dataSourceQueriesMock(&Domain::DataSourceQueries::findProjects).when(source1).thenReturn(project1Result); dataSourceQueriesMock(&Domain::DataSourceQueries::findProjects).when(source2).thenReturn(project2Result); Utils::MockObject projectRepositoryMock; Utils::MockObject taskRepositoryMock; Utils::MockObject contextQueriesMock; contextQueriesMock(&Domain::ContextQueries::findAll).when().thenReturn(contextResult); Utils::MockObject contextRepositoryMock; Presentation::AvailableTaskPagesModel pages(dataSourceQueriesMock.getInstance(), Domain::ProjectQueries::Ptr(), projectRepositoryMock.getInstance(), contextQueriesMock.getInstance(), contextRepositoryMock.getInstance(), Domain::TaskQueries::Ptr(), taskRepositoryMock.getInstance()); // WHEN QAbstractItemModel *model = pages.pageListModel(); // THEN const QModelIndex inboxIndex = model->index(0, 0); const QModelIndex workdayIndex = model->index(1, 0); const QModelIndex projectsIndex = model->index(2, 0); const QModelIndex source1Index = model->index(0, 0, projectsIndex); const QModelIndex project11Index = model->index(0, 0, source1Index); const QModelIndex project12Index = model->index(1, 0, source1Index); const QModelIndex source2Index = model->index(1, 0, projectsIndex); const QModelIndex project21Index = model->index(0, 0, source2Index); const QModelIndex project22Index = model->index(1, 0, source2Index); const QModelIndex contextsIndex = model->index(3, 0); const QModelIndex context1Index = model->index(0, 0, contextsIndex); const QModelIndex context2Index = model->index(1, 0, contextsIndex); QCOMPARE(model->rowCount(), 4); QCOMPARE(model->rowCount(inboxIndex), 0); QCOMPARE(model->rowCount(workdayIndex), 0); QCOMPARE(model->rowCount(projectsIndex), 2); QCOMPARE(model->rowCount(source1Index), 2); QCOMPARE(model->rowCount(project11Index), 0); QCOMPARE(model->rowCount(project12Index), 0); QCOMPARE(model->rowCount(source2Index), 2); QCOMPARE(model->rowCount(project21Index), 0); QCOMPARE(model->rowCount(project22Index), 0); QCOMPARE(model->rowCount(contextsIndex), 2); QCOMPARE(model->rowCount(context1Index), 0); QCOMPARE(model->rowCount(context2Index), 0); const Qt::ItemFlags defaultFlags = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable; QCOMPARE(model->flags(inboxIndex), (defaultFlags & ~(Qt::ItemIsEditable)) | Qt::ItemIsDropEnabled); QCOMPARE(model->flags(workdayIndex), (defaultFlags & ~(Qt::ItemIsEditable)) | Qt::ItemIsDropEnabled); QCOMPARE(model->flags(projectsIndex), Qt::NoItemFlags); QCOMPARE(model->flags(source1Index), Qt::NoItemFlags); QCOMPARE(model->flags(project11Index), defaultFlags | Qt::ItemIsDropEnabled); QCOMPARE(model->flags(project12Index), defaultFlags | Qt::ItemIsDropEnabled); QCOMPARE(model->flags(source2Index), Qt::NoItemFlags); QCOMPARE(model->flags(project21Index), defaultFlags | Qt::ItemIsDropEnabled); QCOMPARE(model->flags(project22Index), defaultFlags | Qt::ItemIsDropEnabled); QCOMPARE(model->flags(contextsIndex), Qt::NoItemFlags); QCOMPARE(model->flags(context1Index), defaultFlags | Qt::ItemIsDropEnabled); QCOMPARE(model->flags(context2Index), defaultFlags | Qt::ItemIsDropEnabled); QCOMPARE(model->data(inboxIndex).toString(), i18n("Inbox")); QCOMPARE(model->data(workdayIndex).toString(), i18n("Workday")); QCOMPARE(model->data(projectsIndex).toString(), i18n("Projects")); QCOMPARE(model->data(source1Index).toString(), source1->name()); QCOMPARE(model->data(project11Index).toString(), project11->name()); QCOMPARE(model->data(project12Index).toString(), project12->name()); QCOMPARE(model->data(source2Index).toString(), source2->name()); QCOMPARE(model->data(project21Index).toString(), project21->name()); QCOMPARE(model->data(project22Index).toString(), project22->name()); QCOMPARE(model->data(contextsIndex).toString(), i18n("Contexts")); QCOMPARE(model->data(context1Index).toString(), context1->name()); QCOMPARE(model->data(context2Index).toString(), context2->name()); QVERIFY(!model->data(inboxIndex, Qt::EditRole).isValid()); QVERIFY(!model->data(workdayIndex, Qt::EditRole).isValid()); QVERIFY(!model->data(projectsIndex, Qt::EditRole).isValid()); QVERIFY(!model->data(source1Index, Qt::EditRole).isValid()); QCOMPARE(model->data(project11Index, Qt::EditRole).toString(), project11->name()); QCOMPARE(model->data(project12Index, Qt::EditRole).toString(), project12->name()); QVERIFY(!model->data(source2Index, Qt::EditRole).isValid()); QCOMPARE(model->data(project21Index, Qt::EditRole).toString(), project21->name()); QCOMPARE(model->data(project22Index, Qt::EditRole).toString(), project22->name()); QVERIFY(!model->data(contextsIndex, Qt::EditRole).isValid()); QCOMPARE(model->data(context1Index, Qt::EditRole).toString(), context1->name()); QCOMPARE(model->data(context2Index, Qt::EditRole).toString(), context2->name()); QCOMPARE(model->data(inboxIndex, Presentation::QueryTreeModelBase::IconNameRole).toString(), QStringLiteral("mail-folder-inbox")); QCOMPARE(model->data(workdayIndex, Presentation::QueryTreeModelBase::IconNameRole).toString(), QStringLiteral("go-jump-today")); QCOMPARE(model->data(projectsIndex, Presentation::QueryTreeModelBase::IconNameRole).toString(), QStringLiteral("folder")); QCOMPARE(model->data(source1Index, Presentation::QueryTreeModelBase::IconNameRole).toString(), QStringLiteral("folder")); QCOMPARE(model->data(project11Index, Presentation::QueryTreeModelBase::IconNameRole).toString(), QStringLiteral("view-pim-tasks")); QCOMPARE(model->data(project12Index, Presentation::QueryTreeModelBase::IconNameRole).toString(), QStringLiteral("view-pim-tasks")); QCOMPARE(model->data(source2Index, Presentation::QueryTreeModelBase::IconNameRole).toString(), QStringLiteral("folder")); QCOMPARE(model->data(project21Index, Presentation::QueryTreeModelBase::IconNameRole).toString(), QStringLiteral("view-pim-tasks")); QCOMPARE(model->data(project22Index, Presentation::QueryTreeModelBase::IconNameRole).toString(), QStringLiteral("view-pim-tasks")); QCOMPARE(model->data(contextsIndex, Presentation::QueryTreeModelBase::IconNameRole).toString(), QStringLiteral("folder")); QCOMPARE(model->data(context1Index, Presentation::QueryTreeModelBase::IconNameRole).toString(), QStringLiteral("view-pim-notes")); QCOMPARE(model->data(context2Index, Presentation::QueryTreeModelBase::IconNameRole).toString(), QStringLiteral("view-pim-notes")); QVERIFY(!model->data(inboxIndex, Qt::CheckStateRole).isValid()); QVERIFY(!model->data(workdayIndex, Qt::CheckStateRole).isValid()); QVERIFY(!model->data(projectsIndex, Qt::CheckStateRole).isValid()); QVERIFY(!model->data(source1Index, Qt::CheckStateRole).isValid()); QVERIFY(!model->data(project11Index, Qt::CheckStateRole).isValid()); QVERIFY(!model->data(project12Index, Qt::CheckStateRole).isValid()); QVERIFY(!model->data(source2Index, Qt::CheckStateRole).isValid()); QVERIFY(!model->data(project21Index, Qt::CheckStateRole).isValid()); QVERIFY(!model->data(project22Index, Qt::CheckStateRole).isValid()); QVERIFY(!model->data(contextsIndex, Qt::CheckStateRole).isValid()); QVERIFY(!model->data(context1Index, Qt::CheckStateRole).isValid()); QVERIFY(!model->data(context2Index, Qt::CheckStateRole).isValid()); // WHEN projectRepositoryMock(&Domain::ProjectRepository::update).when(project11).thenReturn(new FakeJob(this)); projectRepositoryMock(&Domain::ProjectRepository::update).when(project12).thenReturn(new FakeJob(this)); projectRepositoryMock(&Domain::ProjectRepository::update).when(project21).thenReturn(new FakeJob(this)); projectRepositoryMock(&Domain::ProjectRepository::update).when(project22).thenReturn(new FakeJob(this)); contextRepositoryMock(&Domain::ContextRepository::update).when(context1).thenReturn(new FakeJob(this)); contextRepositoryMock(&Domain::ContextRepository::update).when(context2).thenReturn(new FakeJob(this)); QVERIFY(!model->setData(inboxIndex, "Foo")); QVERIFY(!model->setData(projectsIndex, "Foo")); QVERIFY(!model->setData(source1Index, "Foo")); QVERIFY(model->setData(project11Index, "New Project 11")); QVERIFY(model->setData(project12Index, "New Project 12")); QVERIFY(!model->setData(source2Index, "Foo")); QVERIFY(model->setData(project21Index, "New Project 21")); QVERIFY(model->setData(project22Index, "New Project 22")); QVERIFY(!model->setData(contextsIndex, "Foo")); QVERIFY(model->setData(context1Index, "New Context 1")); QVERIFY(model->setData(context2Index, "New Context 2")); // THEN QVERIFY(projectRepositoryMock(&Domain::ProjectRepository::update).when(project11).exactly(1)); QVERIFY(projectRepositoryMock(&Domain::ProjectRepository::update).when(project12).exactly(1)); QVERIFY(projectRepositoryMock(&Domain::ProjectRepository::update).when(project21).exactly(1)); QVERIFY(projectRepositoryMock(&Domain::ProjectRepository::update).when(project22).exactly(1)); QVERIFY(contextRepositoryMock(&Domain::ContextRepository::update).when(context1).exactly(1)); QVERIFY(contextRepositoryMock(&Domain::ContextRepository::update).when(context2).exactly(1)); QCOMPARE(project11->name(), QStringLiteral("New Project 11")); QCOMPARE(project12->name(), QStringLiteral("New Project 12")); QCOMPARE(project21->name(), QStringLiteral("New Project 21")); QCOMPARE(project22->name(), QStringLiteral("New Project 22")); QCOMPARE(context1->name(), QStringLiteral("New Context 1")); QCOMPARE(context2->name(), QStringLiteral("New Context 2")); // WHEN projectRepositoryMock(&Domain::ProjectRepository::associate).when(project11, taskToDrop).thenReturn(new FakeJob(this)); auto data = std::make_unique(); data->setData(QStringLiteral("application/x-zanshin-object"), "object"); data->setProperty("objects", QVariant::fromValue(Domain::Artifact::List() << taskToDrop)); model->dropMimeData(data.get(), Qt::MoveAction, -1, -1, project11Index); // THEN QVERIFY(projectRepositoryMock(&Domain::ProjectRepository::associate).when(project11, taskToDrop).exactly(1)); // WHEN a task is dropped on a context contextRepositoryMock(&Domain::ContextRepository::associate).when(context1, taskToDrop.objectCast()).thenReturn(new FakeJob(this)); data.reset(new QMimeData); data->setData(QStringLiteral("application/x-zanshin-object"), "object"); data->setProperty("objects", QVariant::fromValue(Domain::Artifact::List() << taskToDrop)); model->dropMimeData(data.get(), Qt::MoveAction, -1, -1, context1Index); // THEN QVERIFY(contextRepositoryMock(&Domain::ContextRepository::associate).when(context1, taskToDrop.objectCast()).exactly(1)); // WHEN projectRepositoryMock(&Domain::ProjectRepository::dissociate).when(taskToDrop).thenReturn(new FakeJob(this)); taskRepositoryMock(&Domain::TaskRepository::dissociateAll).when(taskToDrop.objectCast()).thenReturn(new FakeJob(this)); data.reset(new QMimeData); data->setData(QStringLiteral("application/x-zanshin-object"), "object"); data->setProperty("objects", QVariant::fromValue(Domain::Artifact::List() << taskToDrop)); model->dropMimeData(data.get(), Qt::MoveAction, -1, -1, inboxIndex); QTest::qWait(150); // THEN QVERIFY(projectRepositoryMock(&Domain::ProjectRepository::dissociate).when(taskToDrop).exactly(1)); QVERIFY(taskRepositoryMock(&Domain::TaskRepository::dissociateAll).when(taskToDrop.objectCast()).exactly(1)); // WHEN projectRepositoryMock(&Domain::ProjectRepository::associate).when(project12, noteToDrop).thenReturn(new FakeJob(this)); data.reset(new QMimeData); data->setData(QStringLiteral("application/x-zanshin-object"), "object"); data->setProperty("objects", QVariant::fromValue(Domain::Artifact::List() << noteToDrop)); model->dropMimeData(data.get(), Qt::MoveAction, -1, -1, project12Index); // THEN QVERIFY(projectRepositoryMock(&Domain::ProjectRepository::associate).when(project12, noteToDrop).exactly(1)); // WHEN Domain::Artifact::Ptr taskToDrop2(new Domain::Task); Domain::Artifact::Ptr noteToDrop2(new Domain::Note); projectRepositoryMock(&Domain::ProjectRepository::associate).when(project11, taskToDrop2).thenReturn(new FakeJob(this)); projectRepositoryMock(&Domain::ProjectRepository::associate).when(project11, noteToDrop2).thenReturn(new FakeJob(this)); data.reset(new QMimeData); data->setData(QStringLiteral("application/x-zanshin-object"), "object"); data->setProperty("objects", QVariant::fromValue(Domain::Artifact::List() << taskToDrop2 << noteToDrop2)); model->dropMimeData(data.get(), Qt::MoveAction, -1, -1, project11Index); // THEN QVERIFY(projectRepositoryMock(&Domain::ProjectRepository::associate).when(project11, taskToDrop2).exactly(1)); QVERIFY(projectRepositoryMock(&Domain::ProjectRepository::associate).when(project11, noteToDrop2).exactly(1)); // WHEN a task and a note are dropped on a context data.reset(new QMimeData); data->setData(QStringLiteral("application/x-zanshin-object"), "object"); data->setProperty("objects", QVariant::fromValue(Domain::Artifact::List() << taskToDrop2 << noteToDrop2)); model->dropMimeData(data.get(), Qt::MoveAction, -1, -1, context1Index); // THEN QVERIFY(contextRepositoryMock(&Domain::ContextRepository::associate).when(context1, taskToDrop2.objectCast()).exactly(0)); // WHEN two tasks are dropped on a context Domain::Task::Ptr taskToDrop3(new Domain::Task); Domain::Task::Ptr taskToDrop4(new Domain::Task); contextRepositoryMock(&Domain::ContextRepository::associate).when(context1, taskToDrop3).thenReturn(new FakeJob(this)); contextRepositoryMock(&Domain::ContextRepository::associate).when(context1, taskToDrop4).thenReturn(new FakeJob(this)); data.reset(new QMimeData); data->setData(QStringLiteral("application/x-zanshin-object"), "object"); data->setProperty("objects", QVariant::fromValue(Domain::Artifact::List() << taskToDrop3 << taskToDrop4)); model->dropMimeData(data.get(), Qt::MoveAction, -1, -1, context1Index); // THEN QVERIFY(contextRepositoryMock(&Domain::ContextRepository::associate).when(context1, taskToDrop3).exactly(1)); QVERIFY(contextRepositoryMock(&Domain::ContextRepository::associate).when(context1, taskToDrop4).exactly(1)); // WHEN a task is drop on the workday Domain::Task::Ptr taskToDrop5(new Domain::Task); taskRepositoryMock(&Domain::TaskRepository::update).when(taskToDrop5).thenReturn(new FakeJob(this)); data.reset(new QMimeData); data->setData(QStringLiteral("application/x-zanshin-object"), "object"); data->setProperty("objects", QVariant::fromValue(Domain::Artifact::List() << taskToDrop5)); model->dropMimeData(data.get(), Qt::MoveAction, -1, -1, workdayIndex); // THEN - QCOMPARE(taskToDrop5->startDate().date(), Utils::DateTime::currentDateTime().date()); + QCOMPARE(taskToDrop5->startDate(), Utils::DateTime::currentDate()); // WHEN two task are drop on the workday Domain::Task::Ptr taskToDrop6(new Domain::Task); Domain::Task::Ptr taskToDrop7(new Domain::Task); taskRepositoryMock(&Domain::TaskRepository::update).when(taskToDrop6).thenReturn(new FakeJob(this)); taskRepositoryMock(&Domain::TaskRepository::update).when(taskToDrop7).thenReturn(new FakeJob(this)); data.reset(new QMimeData); data->setData(QStringLiteral("application/x-zanshin-object"), "object"); data->setProperty("objects", QVariant::fromValue(Domain::Artifact::List() << taskToDrop6 << taskToDrop7)); model->dropMimeData(data.get(), Qt::MoveAction, -1, -1, workdayIndex); // THEN - QCOMPARE(taskToDrop6->startDate().date(), Utils::DateTime::currentDateTime().date()); - QCOMPARE(taskToDrop7->startDate().date(), Utils::DateTime::currentDateTime().date()); + QCOMPARE(taskToDrop6->startDate(), Utils::DateTime::currentDate()); + QCOMPARE(taskToDrop7->startDate(), Utils::DateTime::currentDate()); } void shouldCreateInboxPage() { // GIVEN // Empty sources provider auto sourceProvider = Domain::QueryResultProvider::Ptr::create(); auto sourceResult = Domain::QueryResult::create(sourceProvider); // Empty context provider auto contextProvider = Domain::QueryResultProvider::Ptr::create(); auto contextResult = Domain::QueryResult::create(contextProvider); // context mocking Utils::MockObject contextQueriesMock; contextQueriesMock(&Domain::ContextQueries::findAll).when().thenReturn(contextResult); Utils::MockObject contextRepositoryMock; // sources mocking Utils::MockObject dataSourceQueriesMock; dataSourceQueriesMock(&Domain::DataSourceQueries::findAllSelected).when().thenReturn(sourceResult); Utils::MockObject projectRepositoryMock; Presentation::AvailableTaskPagesModel pages(dataSourceQueriesMock.getInstance(), Domain::ProjectQueries::Ptr(), projectRepositoryMock.getInstance(), contextQueriesMock.getInstance(), contextRepositoryMock.getInstance(), Domain::TaskQueries::Ptr(), Domain::TaskRepository::Ptr()); // WHEN QAbstractItemModel *model = pages.pageListModel(); // THEN const QModelIndex inboxIndex = model->index(0, 0); QObject *inboxPage = pages.createPageForIndex(inboxIndex); QVERIFY(qobject_cast(inboxPage)); } void shouldCreateWorkdayPage() { // GIVEN // Empty sources provider auto sourceProvider = Domain::QueryResultProvider::Ptr::create(); auto sourceResult = Domain::QueryResult::create(sourceProvider); // Empty context provider auto contextProvider = Domain::QueryResultProvider::Ptr::create(); auto contextResult = Domain::QueryResult::create(contextProvider); // context mocking Utils::MockObject contextQueriesMock; contextQueriesMock(&Domain::ContextQueries::findAll).when().thenReturn(contextResult); Utils::MockObject contextRepositoryMock; // sources mocking Utils::MockObject dataSourceQueriesMock; dataSourceQueriesMock(&Domain::DataSourceQueries::findAllSelected).when().thenReturn(sourceResult); Utils::MockObject projectRepositoryMock; Presentation::AvailableTaskPagesModel pages(dataSourceQueriesMock.getInstance(), Domain::ProjectQueries::Ptr(), projectRepositoryMock.getInstance(), contextQueriesMock.getInstance(), contextRepositoryMock.getInstance(), Domain::TaskQueries::Ptr(), Domain::TaskRepository::Ptr()); // WHEN QAbstractItemModel *model = pages.pageListModel(); // THEN const QModelIndex workdayIndex = model->index(1, 0); QObject *workdayPage = pages.createPageForIndex(workdayIndex); QVERIFY(qobject_cast(workdayPage)); } void shouldCreateProjectsPage() { // GIVEN // One selected data source auto source = Domain::DataSource::Ptr::create(); source->setName("source"); auto sourceProvider = Domain::QueryResultProvider::Ptr::create(); auto sourceResult = Domain::QueryResult::create(sourceProvider); sourceProvider->append(source); // Two projects auto project1 = Domain::Project::Ptr::create(); project1->setName(QStringLiteral("Project 11")); auto project2 = Domain::Project::Ptr::create(); project2->setName(QStringLiteral("Project 12")); auto projectProvider = Domain::QueryResultProvider::Ptr::create(); auto projectResult = Domain::QueryResult::create(projectProvider); projectProvider->append(project1); projectProvider->append(project2); // No contexts auto contextProvider = Domain::QueryResultProvider::Ptr::create(); auto contextResult = Domain::QueryResult::create(contextProvider); // data source mocking Utils::MockObject dataSourceQueriesMock; dataSourceQueriesMock(&Domain::DataSourceQueries::findAllSelected).when().thenReturn(sourceResult); dataSourceQueriesMock(&Domain::DataSourceQueries::findProjects).when(source).thenReturn(projectResult); // projects mocking Utils::MockObject projectRepositoryMock; // contexts mocking Utils::MockObject contextQueriesMock; contextQueriesMock(&Domain::ContextQueries::findAll).when().thenReturn(contextResult); Presentation::AvailableTaskPagesModel pages(dataSourceQueriesMock.getInstance(), Domain::ProjectQueries::Ptr(), projectRepositoryMock.getInstance(), contextQueriesMock.getInstance(), Domain::ContextRepository::Ptr(), Domain::TaskQueries::Ptr(), Domain::TaskRepository::Ptr()); // WHEN QAbstractItemModel *model = pages.pageListModel(); // THEN const QModelIndex projectsIndex = model->index(2, 0); const QModelIndex sourceIndex = model->index(0, 0, projectsIndex); const QModelIndex project1Index = model->index(0, 0, sourceIndex); const QModelIndex project2Index = model->index(1, 0, sourceIndex); QObject *projectsPage = pages.createPageForIndex(projectsIndex); QObject *sourcesPage = pages.createPageForIndex(sourceIndex); QObject *project1Page = pages.createPageForIndex(project1Index); QObject *project2Page = pages.createPageForIndex(project2Index); QVERIFY(!projectsPage); QVERIFY(!sourcesPage); QVERIFY(qobject_cast(project1Page)); QCOMPARE(qobject_cast(project1Page)->project(), project1); QVERIFY(qobject_cast(project2Page)); QCOMPARE(qobject_cast(project2Page)->project(), project2); } void shouldCreateContextsPage() { // GIVEN // Two contexts auto context1 = Domain::Context::Ptr::create(); context1->setName(QStringLiteral("context 1")); auto context2 = Domain::Context::Ptr::create(); context2->setName(QStringLiteral("context 2")); auto contextProvider = Domain::QueryResultProvider::Ptr::create(); auto contextResult = Domain::QueryResult::create(contextProvider); contextProvider->append(context1); contextProvider->append(context2); // Empty sources provider auto sourceProvider = Domain::QueryResultProvider::Ptr::create(); auto sourceResult = Domain::QueryResult::create(sourceProvider); // contexts mocking Utils::MockObject contextQueriesMock; contextQueriesMock(&Domain::ContextQueries::findAll).when().thenReturn(contextResult); Utils::MockObject contextRepositoryMock; // sources mocking Utils::MockObject dataSourceQueriesMock; dataSourceQueriesMock(&Domain::DataSourceQueries::findAllSelected).when().thenReturn(sourceResult); // projects mocking Utils::MockObject projectRepositoryMock; Presentation::AvailableTaskPagesModel pages(dataSourceQueriesMock.getInstance(), Domain::ProjectQueries::Ptr(), projectRepositoryMock.getInstance(), contextQueriesMock.getInstance(), contextRepositoryMock.getInstance(), Domain::TaskQueries::Ptr(), Domain::TaskRepository::Ptr()); // WHEN QAbstractItemModel *model = pages.pageListModel(); // THEN const QModelIndex contextsIndex = model->index(3, 0); const QModelIndex context1Index = model->index(0, 0, contextsIndex); const QModelIndex context2Index = model->index(1, 0, contextsIndex); QObject *contextsPage = pages.createPageForIndex(contextsIndex); QObject *context1Page = pages.createPageForIndex(context1Index); QObject *context2Page = pages.createPageForIndex(context2Index); QVERIFY(!contextsPage); QVERIFY(qobject_cast(context1Page)); QCOMPARE(qobject_cast(context1Page)->context(), context1); QVERIFY(qobject_cast(context2Page)); QCOMPARE(qobject_cast(context2Page)->context(), context2); } void shouldAddProjects() { // GIVEN auto source = Domain::DataSource::Ptr::create(); Utils::MockObject projectRepositoryMock; projectRepositoryMock(&Domain::ProjectRepository::create).when(any(), any()) .thenReturn(new FakeJob(this)); Presentation::AvailableTaskPagesModel pages(Domain::DataSourceQueries::Ptr(), Domain::ProjectQueries::Ptr(), projectRepositoryMock.getInstance(), Domain::ContextQueries::Ptr(), Domain::ContextRepository::Ptr(), Domain::TaskQueries::Ptr(), Domain::TaskRepository::Ptr()); // WHEN pages.addProject(QStringLiteral("Foo"), source); // THEN QVERIFY(projectRepositoryMock(&Domain::ProjectRepository::create).when(any(), any()) .exactly(1)); } void shouldGetAnErrorMessageWhenAddProjectFailed() { // GIVEN auto source = Domain::DataSource::Ptr::create(); source->setName(QStringLiteral("Source1")); Utils::MockObject projectRepositoryMock; auto job = new FakeJob(this); job->setExpectedError(KJob::KilledJobError, QStringLiteral("Foo")); projectRepositoryMock(&Domain::ProjectRepository::create).when(any(), any()) .thenReturn(job); Presentation::AvailableTaskPagesModel pages(Domain::DataSourceQueries::Ptr(), Domain::ProjectQueries::Ptr(), projectRepositoryMock.getInstance(), Domain::ContextQueries::Ptr(), Domain::ContextRepository::Ptr(), Domain::TaskQueries::Ptr(), Domain::TaskRepository::Ptr()); FakeErrorHandler errorHandler; pages.setErrorHandler(&errorHandler); // WHEN pages.addProject(QStringLiteral("Foo"), source); // THEN QTest::qWait(150); QCOMPARE(errorHandler.m_message, QStringLiteral("Cannot add project Foo in dataSource Source1: Foo")); } void shouldAddContexts() { // GIVEN Utils::MockObject contextRepositoryMock; contextRepositoryMock(&Domain::ContextRepository::create).when(any()) .thenReturn(new FakeJob(this)); Presentation::AvailableTaskPagesModel pages(Domain::DataSourceQueries::Ptr(), Domain::ProjectQueries::Ptr(), Domain::ProjectRepository::Ptr(), Domain::ContextQueries::Ptr(), contextRepositoryMock.getInstance(), Domain::TaskQueries::Ptr(), Domain::TaskRepository::Ptr()); // WHEN pages.addContext(QStringLiteral("Foo")); // THEN QVERIFY(contextRepositoryMock(&Domain::ContextRepository::create).when(any()) .exactly(1)); } void shouldGetAnErrorMessageWhenAddContextFailed() { // GIVEN Utils::MockObject contextRepositoryMock; auto job = new FakeJob(this); job->setExpectedError(KJob::KilledJobError, QStringLiteral("Foo")); contextRepositoryMock(&Domain::ContextRepository::create).when(any()) .thenReturn(job); Presentation::AvailableTaskPagesModel pages(Domain::DataSourceQueries::Ptr(), Domain::ProjectQueries::Ptr(), Domain::ProjectRepository::Ptr(), Domain::ContextQueries::Ptr(), contextRepositoryMock.getInstance(), Domain::TaskQueries::Ptr(), Domain::TaskRepository::Ptr()); FakeErrorHandler errorHandler; pages.setErrorHandler(&errorHandler); // WHEN pages.addContext(QStringLiteral("Foo")); // THEN QTest::qWait(150); QCOMPARE(errorHandler.m_message, QStringLiteral("Cannot add context Foo: Foo")); } void shouldRemoveProject() { // GIVEN // One selected data source auto source = Domain::DataSource::Ptr::create(); source->setName("source"); auto sourceProvider = Domain::QueryResultProvider::Ptr::create(); auto sourceResult = Domain::QueryResult::create(sourceProvider); sourceProvider->append(source); // Two projects auto project1 = Domain::Project::Ptr::create(); project1->setName(QStringLiteral("Project 1")); auto project2 = Domain::Project::Ptr::create(); project2->setName(QStringLiteral("Project 2")); auto projectProvider = Domain::QueryResultProvider::Ptr::create(); auto projectResult = Domain::QueryResult::create(projectProvider); projectProvider->append(project1); projectProvider->append(project2); // No contexts auto contextProvider = Domain::QueryResultProvider::Ptr::create(); auto contextResult = Domain::QueryResult::create(contextProvider); // data source mocking Utils::MockObject dataSourceQueriesMock; dataSourceQueriesMock(&Domain::DataSourceQueries::findAllSelected).when().thenReturn(sourceResult); dataSourceQueriesMock(&Domain::DataSourceQueries::findProjects).when(source).thenReturn(projectResult); // projects mocking Utils::MockObject projectRepositoryMock; // contexts mocking Utils::MockObject contextQueriesMock; contextQueriesMock(&Domain::ContextQueries::findAll).when().thenReturn(contextResult); Presentation::AvailableTaskPagesModel pages(dataSourceQueriesMock.getInstance(), Domain::ProjectQueries::Ptr(), projectRepositoryMock.getInstance(), contextQueriesMock.getInstance(), Domain::ContextRepository::Ptr(), Domain::TaskQueries::Ptr(), Domain::TaskRepository::Ptr()); QAbstractItemModel *model = pages.pageListModel(); const QModelIndex projectsIndex = model->index(2, 0); const QModelIndex sourceIndex = model->index(0, 0, projectsIndex); const QModelIndex project1Index = model->index(0, 0, sourceIndex); projectRepositoryMock(&Domain::ProjectRepository::remove).when(project1).thenReturn(new FakeJob(this)); // WHEN pages.removeItem(project1Index); // THEN QVERIFY(projectRepositoryMock(&Domain::ProjectRepository::remove).when(project1).exactly(1)); } void shouldGetAnErrorMessageWhenRemoveProjectFailed() { // GIVEN // One selected data source auto source = Domain::DataSource::Ptr::create(); source->setName("source"); auto sourceProvider = Domain::QueryResultProvider::Ptr::create(); auto sourceResult = Domain::QueryResult::create(sourceProvider); sourceProvider->append(source); // Two projects auto project1 = Domain::Project::Ptr::create(); project1->setName(QStringLiteral("Project 1")); auto project2 = Domain::Project::Ptr::create(); project2->setName(QStringLiteral("Project 2")); auto projectProvider = Domain::QueryResultProvider::Ptr::create(); auto projectResult = Domain::QueryResult::create(projectProvider); projectProvider->append(project1); projectProvider->append(project2); // No contexts auto contextProvider = Domain::QueryResultProvider::Ptr::create(); auto contextResult = Domain::QueryResult::create(contextProvider); // data source mocking Utils::MockObject dataSourceQueriesMock; dataSourceQueriesMock(&Domain::DataSourceQueries::findAllSelected).when().thenReturn(sourceResult); dataSourceQueriesMock(&Domain::DataSourceQueries::findProjects).when(source).thenReturn(projectResult); // projects mocking Utils::MockObject projectRepositoryMock; // contexts mocking Utils::MockObject contextQueriesMock; contextQueriesMock(&Domain::ContextQueries::findAll).when().thenReturn(contextResult); Presentation::AvailableTaskPagesModel pages(dataSourceQueriesMock.getInstance(), Domain::ProjectQueries::Ptr(), projectRepositoryMock.getInstance(), contextQueriesMock.getInstance(), Domain::ContextRepository::Ptr(), Domain::TaskQueries::Ptr(), Domain::TaskRepository::Ptr()); FakeErrorHandler errorHandler; pages.setErrorHandler(&errorHandler); QAbstractItemModel *model = pages.pageListModel(); const QModelIndex projectsIndex = model->index(2, 0); const QModelIndex sourceIndex = model->index(0, 0, projectsIndex); const QModelIndex project1Index = model->index(0, 0, sourceIndex); auto job = new FakeJob(this); job->setExpectedError(KJob::KilledJobError, QStringLiteral("Foo")); projectRepositoryMock(&Domain::ProjectRepository::remove).when(project1).thenReturn(job); // WHEN pages.removeItem(project1Index); // THEN QTest::qWait(150); QCOMPARE(errorHandler.m_message, QStringLiteral("Cannot remove project Project 1: Foo")); } void shouldRemoveContext() { // GIVEN // Two contexts auto context1 = Domain::Context::Ptr::create(); context1->setName(QStringLiteral("context 1")); auto contextProvider = Domain::QueryResultProvider::Ptr::create(); auto contextResult = Domain::QueryResult::create(contextProvider); contextProvider->append(context1); // Empty sources provider auto sourceProvider = Domain::QueryResultProvider::Ptr::create(); auto sourceResult = Domain::QueryResult::create(sourceProvider); // contexts mocking Utils::MockObject contextQueriesMock; contextQueriesMock(&Domain::ContextQueries::findAll).when().thenReturn(contextResult); Utils::MockObject contextRepositoryMock; // sources mocking Utils::MockObject dataSourceQueriesMock; dataSourceQueriesMock(&Domain::DataSourceQueries::findAllSelected).when().thenReturn(sourceResult); Presentation::AvailableTaskPagesModel pages(dataSourceQueriesMock.getInstance(), Domain::ProjectQueries::Ptr(), Domain::ProjectRepository::Ptr(), contextQueriesMock.getInstance(), contextRepositoryMock.getInstance(), Domain::TaskQueries::Ptr(), Domain::TaskRepository::Ptr()); QAbstractItemModel *model = pages.pageListModel(); const QModelIndex contextsIndex = model->index(3, 0); const QModelIndex context1Index = model->index(0, 0, contextsIndex); contextRepositoryMock(&Domain::ContextRepository::remove).when(context1).thenReturn(new FakeJob(this)); // WHEN pages.removeItem(context1Index); // THEN QVERIFY(contextRepositoryMock(&Domain::ContextRepository::remove).when(context1).exactly(1)); } void shouldGetAnErrorMessageWhenRemoveContextFailed() { // GIVEN // Two contexts auto context1 = Domain::Context::Ptr::create(); context1->setName(QStringLiteral("context 1")); auto contextProvider = Domain::QueryResultProvider::Ptr::create(); auto contextResult = Domain::QueryResult::create(contextProvider); contextProvider->append(context1); // Empty sources provider auto sourceProvider = Domain::QueryResultProvider::Ptr::create(); auto sourceResult = Domain::QueryResult::create(sourceProvider); // contexts mocking Utils::MockObject contextQueriesMock; contextQueriesMock(&Domain::ContextQueries::findAll).when().thenReturn(contextResult); Utils::MockObject contextRepositoryMock; // sources mocking Utils::MockObject dataSourceQueriesMock; dataSourceQueriesMock(&Domain::DataSourceQueries::findAllSelected).when().thenReturn(sourceResult); Presentation::AvailableTaskPagesModel pages(dataSourceQueriesMock.getInstance(), Domain::ProjectQueries::Ptr(), Domain::ProjectRepository::Ptr(), contextQueriesMock.getInstance(), contextRepositoryMock.getInstance(), Domain::TaskQueries::Ptr(), Domain::TaskRepository::Ptr()); FakeErrorHandler errorHandler; pages.setErrorHandler(&errorHandler); QAbstractItemModel *model = pages.pageListModel(); const QModelIndex contextsIndex = model->index(3, 0); const QModelIndex context1Index = model->index(0, 0, contextsIndex); auto job = new FakeJob(this); job->setExpectedError(KJob::KilledJobError, QStringLiteral("Foo")); contextRepositoryMock(&Domain::ContextRepository::remove).when(context1).thenReturn(job); // WHEN pages.removeItem(context1Index); // THEN QTest::qWait(150); QCOMPARE(errorHandler.m_message, QStringLiteral("Cannot remove context context 1: Foo")); } void shouldGetAnErrorMessageWhenUpdateProjectFailed() { // GIVEN // One selected data source auto source = Domain::DataSource::Ptr::create(); source->setName("source1"); auto sourceProvider = Domain::QueryResultProvider::Ptr::create(); auto sourceResult = Domain::QueryResult::create(sourceProvider); sourceProvider->append(source); // Two projects under the source auto project1 = Domain::Project::Ptr::create(); project1->setName(QStringLiteral("Project 1")); auto project2 = Domain::Project::Ptr::create(); project2->setName(QStringLiteral("Project 2")); auto projectProvider = Domain::QueryResultProvider::Ptr::create(); auto projectResult = Domain::QueryResult::create(projectProvider); projectProvider->append(project1); projectProvider->append(project2); // Two contexts auto context1 = Domain::Context::Ptr::create(); context1->setName(QStringLiteral("context 1")); auto context2 = Domain::Context::Ptr::create(); context2->setName(QStringLiteral("context 2")); auto contextProvider = Domain::QueryResultProvider::Ptr::create(); auto contextResult = Domain::QueryResult::create(contextProvider); contextProvider->append(context1); contextProvider->append(context2); Utils::MockObject dataSourceQueriesMock; dataSourceQueriesMock(&Domain::DataSourceQueries::findAllSelected).when().thenReturn(sourceResult); dataSourceQueriesMock(&Domain::DataSourceQueries::findProjects).when(source).thenReturn(projectResult); Utils::MockObject projectRepositoryMock; Utils::MockObject contextQueriesMock; contextQueriesMock(&Domain::ContextQueries::findAll).when().thenReturn(contextResult); Utils::MockObject contextRepositoryMock; Presentation::AvailableTaskPagesModel pages(dataSourceQueriesMock.getInstance(), Domain::ProjectQueries::Ptr(), projectRepositoryMock.getInstance(), contextQueriesMock.getInstance(), contextRepositoryMock.getInstance(), Domain::TaskQueries::Ptr(), Domain::TaskRepository::Ptr()); FakeErrorHandler errorHandler; pages.setErrorHandler(&errorHandler); QAbstractItemModel *model = pages.pageListModel(); const QModelIndex projectsIndex = model->index(2, 0); const QModelIndex sourceIndex = model->index(0, 0, projectsIndex); const QModelIndex project1Index = model->index(0, 0, sourceIndex); // WHEN auto job = new FakeJob(this); job->setExpectedError(KJob::KilledJobError, QStringLiteral("Foo")); projectRepositoryMock(&Domain::ProjectRepository::update).when(project1).thenReturn(job); QVERIFY(model->setData(project1Index, "New Project 1")); // THEN QTest::qWait(150); QCOMPARE(errorHandler.m_message, QStringLiteral("Cannot modify project Project 1: Foo")); } void shouldGetAnErrorMessageWhenUpdateContextFailed() { // GIVEN // One selected data source auto source = Domain::DataSource::Ptr::create(); source->setName("source1"); auto sourceProvider = Domain::QueryResultProvider::Ptr::create(); auto sourceResult = Domain::QueryResult::create(sourceProvider); sourceProvider->append(source); // Two projects under the source auto project1 = Domain::Project::Ptr::create(); project1->setName(QStringLiteral("Project 1")); auto project2 = Domain::Project::Ptr::create(); project2->setName(QStringLiteral("Project 2")); auto projectProvider = Domain::QueryResultProvider::Ptr::create(); auto projectResult = Domain::QueryResult::create(projectProvider); projectProvider->append(project1); projectProvider->append(project2); // Two contexts auto context1 = Domain::Context::Ptr::create(); context1->setName(QStringLiteral("context 1")); auto context2 = Domain::Context::Ptr::create(); context2->setName(QStringLiteral("context 2")); auto contextProvider = Domain::QueryResultProvider::Ptr::create(); auto contextResult = Domain::QueryResult::create(contextProvider); contextProvider->append(context1); contextProvider->append(context2); Utils::MockObject dataSourceQueriesMock; dataSourceQueriesMock(&Domain::DataSourceQueries::findAllSelected).when().thenReturn(sourceResult); dataSourceQueriesMock(&Domain::DataSourceQueries::findProjects).when(source).thenReturn(projectResult); Utils::MockObject projectRepositoryMock; Utils::MockObject contextQueriesMock; contextQueriesMock(&Domain::ContextQueries::findAll).when().thenReturn(contextResult); Utils::MockObject contextRepositoryMock; Presentation::AvailableTaskPagesModel pages(dataSourceQueriesMock.getInstance(), Domain::ProjectQueries::Ptr(), projectRepositoryMock.getInstance(), contextQueriesMock.getInstance(), contextRepositoryMock.getInstance(), Domain::TaskQueries::Ptr(), Domain::TaskRepository::Ptr()); FakeErrorHandler errorHandler; pages.setErrorHandler(&errorHandler); QAbstractItemModel *model = pages.pageListModel(); const QModelIndex contextsIndex = model->index(3, 0); const QModelIndex context1Index = model->index(0, 0, contextsIndex); // WHEN auto job = new FakeJob(this); job->setExpectedError(KJob::KilledJobError, QStringLiteral("Foo")); contextRepositoryMock(&Domain::ContextRepository::update).when(context1).thenReturn(job); QVERIFY(model->setData(context1Index, "New Context 1")); // THEN QTest::qWait(150); QCOMPARE(errorHandler.m_message, QStringLiteral("Cannot modify context context 1: Foo")); } void shouldGetAnErrorMessageWhenAssociateProjectFailed() { // GIVEN // One selected data source auto source = Domain::DataSource::Ptr::create(); source->setName("source1"); auto sourceProvider = Domain::QueryResultProvider::Ptr::create(); auto sourceResult = Domain::QueryResult::create(sourceProvider); sourceProvider->append(source); // Two projects under the source auto project1 = Domain::Project::Ptr::create(); project1->setName(QStringLiteral("Project 1")); auto project2 = Domain::Project::Ptr::create(); project2->setName(QStringLiteral("Project 2")); auto projectProvider = Domain::QueryResultProvider::Ptr::create(); auto projectResult = Domain::QueryResult::create(projectProvider); projectProvider->append(project1); projectProvider->append(project2); // Two contexts auto context1 = Domain::Context::Ptr::create(); context1->setName(QStringLiteral("context 1")); auto context2 = Domain::Context::Ptr::create(); context2->setName(QStringLiteral("context 2")); auto contextProvider = Domain::QueryResultProvider::Ptr::create(); auto contextResult = Domain::QueryResult::create(contextProvider); contextProvider->append(context1); contextProvider->append(context2); // One task (used for dropping later on) Domain::Artifact::Ptr taskToDrop(new Domain::Task); taskToDrop->setTitle(QStringLiteral("taskDropped")); Utils::MockObject dataSourceQueriesMock; dataSourceQueriesMock(&Domain::DataSourceQueries::findAllSelected).when().thenReturn(sourceResult); dataSourceQueriesMock(&Domain::DataSourceQueries::findProjects).when(source).thenReturn(projectResult); Utils::MockObject projectRepositoryMock; Utils::MockObject contextQueriesMock; contextQueriesMock(&Domain::ContextQueries::findAll).when().thenReturn(contextResult); Utils::MockObject contextRepositoryMock; Utils::MockObject taskRepositoryMock; Presentation::AvailableTaskPagesModel pages(dataSourceQueriesMock.getInstance(), Domain::ProjectQueries::Ptr(), projectRepositoryMock.getInstance(), contextQueriesMock.getInstance(), contextRepositoryMock.getInstance(), Domain::TaskQueries::Ptr(), taskRepositoryMock.getInstance()); FakeErrorHandler errorHandler; pages.setErrorHandler(&errorHandler); QAbstractItemModel *model = pages.pageListModel(); const QModelIndex projectsIndex = model->index(2, 0); const QModelIndex sourceIndex = model->index(0, 0, projectsIndex); const QModelIndex project1Index = model->index(0, 0, sourceIndex); // WHEN auto job = new FakeJob(this); job->setExpectedError(KJob::KilledJobError, QStringLiteral("Foo")); projectRepositoryMock(&Domain::ProjectRepository::associate).when(project1, taskToDrop).thenReturn(job); auto data = std::make_unique(); data->setData(QStringLiteral("application/x-zanshin-object"), "object"); data->setProperty("objects", QVariant::fromValue(Domain::Artifact::List() << taskToDrop)); model->dropMimeData(data.get(), Qt::MoveAction, -1, -1, project1Index); // THEN QTest::qWait(150); QCOMPARE(errorHandler.m_message, QStringLiteral("Cannot add taskDropped to project Project 1: Foo")); } void shouldGetAnErrorMessageWhenAssociateContextFailed() { // GIVEN // One selected data source auto source = Domain::DataSource::Ptr::create(); source->setName("source1"); auto sourceProvider = Domain::QueryResultProvider::Ptr::create(); auto sourceResult = Domain::QueryResult::create(sourceProvider); sourceProvider->append(source); // Two projects under the source auto project1 = Domain::Project::Ptr::create(); project1->setName(QStringLiteral("Project 1")); auto project2 = Domain::Project::Ptr::create(); project2->setName(QStringLiteral("Project 2")); auto projectProvider = Domain::QueryResultProvider::Ptr::create(); auto projectResult = Domain::QueryResult::create(projectProvider); projectProvider->append(project1); projectProvider->append(project2); // Two contexts auto context1 = Domain::Context::Ptr::create(); context1->setName(QStringLiteral("context 1")); auto context2 = Domain::Context::Ptr::create(); context2->setName(QStringLiteral("context 2")); auto contextProvider = Domain::QueryResultProvider::Ptr::create(); auto contextResult = Domain::QueryResult::create(contextProvider); contextProvider->append(context1); contextProvider->append(context2); // One task (used for dropping later on) Domain::Artifact::Ptr taskToDrop(new Domain::Task); taskToDrop->setTitle(QStringLiteral("taskDropped")); Utils::MockObject dataSourceQueriesMock; dataSourceQueriesMock(&Domain::DataSourceQueries::findAllSelected).when().thenReturn(sourceResult); dataSourceQueriesMock(&Domain::DataSourceQueries::findProjects).when(source).thenReturn(projectResult); Utils::MockObject projectRepositoryMock; Utils::MockObject contextQueriesMock; contextQueriesMock(&Domain::ContextQueries::findAll).when().thenReturn(contextResult); Utils::MockObject contextRepositoryMock; Utils::MockObject taskRepositoryMock; Presentation::AvailableTaskPagesModel pages(dataSourceQueriesMock.getInstance(), Domain::ProjectQueries::Ptr(), projectRepositoryMock.getInstance(), contextQueriesMock.getInstance(), contextRepositoryMock.getInstance(), Domain::TaskQueries::Ptr(), taskRepositoryMock.getInstance()); FakeErrorHandler errorHandler; pages.setErrorHandler(&errorHandler); QAbstractItemModel *model = pages.pageListModel(); const QModelIndex contextsIndex = model->index(3, 0); const QModelIndex context1Index = model->index(0, 0, contextsIndex); // WHEN auto job = new FakeJob(this); job->setExpectedError(KJob::KilledJobError, QStringLiteral("Foo")); contextRepositoryMock(&Domain::ContextRepository::associate).when(context1, taskToDrop.objectCast()).thenReturn(job); auto data = std::make_unique(); data->setData(QStringLiteral("application/x-zanshin-object"), "object"); data->setProperty("objects", QVariant::fromValue(Domain::Artifact::List() << taskToDrop)); model->dropMimeData(data.get(), Qt::MoveAction, -1, -1, context1Index); // THEN QTest::qWait(150); QCOMPARE(errorHandler.m_message, QStringLiteral("Cannot add taskDropped to context context 1: Foo")); } void shouldGetAnErrorMessageWhenDissociateFailed() { // GIVEN // Empty source provider auto sourceProvider = Domain::QueryResultProvider::Ptr::create(); auto sourceResult = Domain::QueryResult::create(sourceProvider); // Empty context provider auto contextProvider = Domain::QueryResultProvider::Ptr::create(); auto contextResult = Domain::QueryResult::create(contextProvider); // One task (used for dropping later on) Domain::Artifact::Ptr taskToDrop(new Domain::Task); taskToDrop->setTitle(QStringLiteral("taskDropped")); // context mocking Utils::MockObject contextQueriesMock; contextQueriesMock(&Domain::ContextQueries::findAll).when().thenReturn(contextResult); Utils::MockObject contextRepositoryMock; // sources mocking Utils::MockObject dataSourceQueriesMock; dataSourceQueriesMock(&Domain::DataSourceQueries::findAllSelected).when().thenReturn(sourceResult); Utils::MockObject projectRepositoryMock; Utils::MockObject taskRepositoryMock; Presentation::AvailableTaskPagesModel pages(dataSourceQueriesMock.getInstance(), Domain::ProjectQueries::Ptr(), projectRepositoryMock.getInstance(), contextQueriesMock.getInstance(), contextRepositoryMock.getInstance(), Domain::TaskQueries::Ptr(), taskRepositoryMock.getInstance()); FakeErrorHandler errorHandler; pages.setErrorHandler(&errorHandler); QAbstractItemModel *model = pages.pageListModel(); const QModelIndex inboxIndex = model->index(0, 0); // WHEN auto job = new FakeJob(this); job->setExpectedError(KJob::KilledJobError, QStringLiteral("Foo")); projectRepositoryMock(&Domain::ProjectRepository::dissociate).when(taskToDrop).thenReturn(job); taskRepositoryMock(&Domain::TaskRepository::dissociateAll).when(taskToDrop.objectCast()).thenReturn(new FakeJob(this)); auto data = std::make_unique(); data->setData(QStringLiteral("application/x-zanshin-object"), "object"); data->setProperty("objects", QVariant::fromValue(Domain::Artifact::List() << taskToDrop)); model->dropMimeData(data.get(), Qt::MoveAction, -1, -1, inboxIndex); // THEN QTest::qWait(150); QCOMPARE(errorHandler.m_message, QStringLiteral("Cannot move taskDropped to Inbox: Foo")); } }; ZANSHIN_TEST_MAIN(AvailableTaskPagesModelTest) #include "availabletaskpagesmodeltest.moc" diff --git a/tests/units/presentation/workdaypagemodeltest.cpp b/tests/units/presentation/workdaypagemodeltest.cpp index e7983482..4fbe246b 100644 --- a/tests/units/presentation/workdaypagemodeltest.cpp +++ b/tests/units/presentation/workdaypagemodeltest.cpp @@ -1,379 +1,379 @@ /* This file is part of Zanshin Copyright 2015 Theo Vaucher This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include "utils/datetime.h" #include "utils/mockobject.h" #include "domain/noterepository.h" #include "domain/taskqueries.h" #include "domain/taskrepository.h" #include "presentation/workdaypagemodel.h" using namespace mockitopp; using namespace mockitopp::matcher; class WorkdayPageModelTest : public QObject { Q_OBJECT private slots: void shouldListWorkdayInCentralListModel() { // GIVEN - const auto today = Utils::DateTime::currentDateTime(); + const auto today = Utils::DateTime::currentDate(); // Three tasks auto task1 = Domain::Task::Ptr::create(); task1->setTitle(QStringLiteral("task1")); task1->setStartDate(today.addDays(-10)); task1->setDueDate(today); auto task2 = Domain::Task::Ptr::create(); task2->setTitle(QStringLiteral("task2")); task2->setStartDate(today); task2->setDueDate(today.addDays(10)); auto task3 = Domain::Task::Ptr::create(); task3->setTitle(QStringLiteral("task3")); task3->setStartDate(today.addYears(-4)); task3->setDueDate(today.addYears(-3)); auto taskProvider = Domain::QueryResultProvider::Ptr::create(); auto taskResult = Domain::QueryResult::create(taskProvider); taskProvider->append(task1); taskProvider->append(task2); taskProvider->append(task3); // Two tasks under the task1 auto childTask11 = Domain::Task::Ptr::create(); childTask11->setTitle(QStringLiteral("childTask11")); auto childTask12 = Domain::Task::Ptr::create(); childTask12->setTitle(QStringLiteral("childTask12")); childTask12->setStartDate(today); childTask12->setDueDate(today); auto childTaskProvider = Domain::QueryResultProvider::Ptr::create(); auto childTaskResult = Domain::QueryResult::create(childTaskProvider); taskProvider->append(childTask12); childTaskProvider->append(childTask11); childTaskProvider->append(childTask12); Utils::MockObject taskQueriesMock; taskQueriesMock(&Domain::TaskQueries::findWorkdayTopLevel).when().thenReturn(taskResult); taskQueriesMock(&Domain::TaskQueries::findChildren).when(task1).thenReturn(childTaskResult); taskQueriesMock(&Domain::TaskQueries::findChildren).when(task2).thenReturn(Domain::QueryResult::Ptr()); taskQueriesMock(&Domain::TaskQueries::findChildren).when(task3).thenReturn(Domain::QueryResult::Ptr()); taskQueriesMock(&Domain::TaskQueries::findChildren).when(childTask11).thenReturn(Domain::QueryResult::Ptr()); taskQueriesMock(&Domain::TaskQueries::findChildren).when(childTask12).thenReturn(Domain::QueryResult::Ptr()); Utils::MockObject taskRepositoryMock; Presentation::WorkdayPageModel workday(taskQueriesMock.getInstance(), taskRepositoryMock.getInstance()); // WHEN QAbstractItemModel *model = workday.centralListModel(); // THEN const QModelIndex task1Index = model->index(0, 0); const QModelIndex task2Index = model->index(1, 0); const QModelIndex task3Index = model->index(2, 0); const QModelIndex taskChildTask12Index = model->index(3, 0); const QModelIndex childTask11Index = model->index(0, 0, task1Index); const QModelIndex childTask12Index = model->index(1, 0, task1Index); QCOMPARE(model->rowCount(), 4); QCOMPARE(model->rowCount(task1Index), 2); QCOMPARE(model->rowCount(task2Index), 0); QCOMPARE(model->rowCount(task3Index), 0); QCOMPARE(model->rowCount(taskChildTask12Index), 0); QVERIFY(childTask11Index.isValid()); QVERIFY(childTask12Index.isValid()); QCOMPARE(model->rowCount(childTask11Index), 0); QCOMPARE(model->rowCount(childTask12Index), 0); const Qt::ItemFlags defaultFlags = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsDragEnabled; QCOMPARE(model->flags(task1Index), defaultFlags | Qt::ItemIsUserCheckable | Qt::ItemIsDropEnabled); QCOMPARE(model->flags(childTask11Index), defaultFlags | Qt::ItemIsUserCheckable | Qt::ItemIsDropEnabled); QCOMPARE(model->flags(childTask12Index), defaultFlags | Qt::ItemIsUserCheckable | Qt::ItemIsDropEnabled); QCOMPARE(model->flags(task2Index), defaultFlags | Qt::ItemIsUserCheckable | Qt::ItemIsDropEnabled); QCOMPARE(model->flags(task3Index), defaultFlags | Qt::ItemIsUserCheckable | Qt::ItemIsDropEnabled); QCOMPARE(model->flags(taskChildTask12Index), defaultFlags | Qt::ItemIsUserCheckable | Qt::ItemIsDropEnabled); QCOMPARE(model->data(task1Index).toString(), task1->title()); QCOMPARE(model->data(childTask11Index).toString(), childTask11->title()); QCOMPARE(model->data(childTask12Index).toString(), childTask12->title()); QCOMPARE(model->data(task2Index).toString(), task2->title()); QCOMPARE(model->data(task3Index).toString(), task3->title()); QCOMPARE(model->data(taskChildTask12Index).toString(), childTask12->title()); QCOMPARE(model->data(task1Index, Qt::EditRole).toString(), task1->title()); QCOMPARE(model->data(childTask11Index, Qt::EditRole).toString(), childTask11->title()); QCOMPARE(model->data(childTask12Index, Qt::EditRole).toString(), childTask12->title()); QCOMPARE(model->data(task2Index, Qt::EditRole).toString(), task2->title()); QCOMPARE(model->data(task3Index, Qt::EditRole).toString(), task3->title()); QCOMPARE(model->data(taskChildTask12Index, Qt::EditRole).toString(), childTask12->title()); QVERIFY(model->data(task1Index, Qt::CheckStateRole).isValid()); QVERIFY(model->data(childTask11Index, Qt::CheckStateRole).isValid()); QVERIFY(model->data(childTask12Index, Qt::CheckStateRole).isValid()); QVERIFY(model->data(task2Index, Qt::CheckStateRole).isValid()); QVERIFY(model->data(task3Index, Qt::CheckStateRole).isValid()); QVERIFY(model->data(taskChildTask12Index, Qt::CheckStateRole).isValid()); QCOMPARE(model->data(task1Index, Qt::CheckStateRole).toBool(), task1->isDone()); QCOMPARE(model->data(childTask11Index, Qt::CheckStateRole).toBool(), childTask11->isDone()); QCOMPARE(model->data(childTask12Index, Qt::CheckStateRole).toBool(), childTask12->isDone()); QCOMPARE(model->data(task2Index, Qt::CheckStateRole).toBool(), task2->isDone()); QCOMPARE(model->data(task3Index, Qt::CheckStateRole).toBool(), task3->isDone()); QCOMPARE(model->data(taskChildTask12Index, Qt::CheckStateRole).toBool(), childTask12->isDone()); // WHEN taskRepositoryMock(&Domain::TaskRepository::update).when(task1).thenReturn(new FakeJob(this)); taskRepositoryMock(&Domain::TaskRepository::update).when(childTask11).thenReturn(new FakeJob(this)); taskRepositoryMock(&Domain::TaskRepository::update).when(childTask12).thenReturn(new FakeJob(this)); taskRepositoryMock(&Domain::TaskRepository::update).when(task2).thenReturn(new FakeJob(this)); taskRepositoryMock(&Domain::TaskRepository::update).when(task3).thenReturn(new FakeJob(this)); QVERIFY(model->setData(task1Index, "newTask1")); QVERIFY(model->setData(childTask11Index, "newChildTask11")); QVERIFY(model->setData(task2Index, "newTask2")); QVERIFY(model->setData(task3Index, "newTask3")); QVERIFY(model->setData(taskChildTask12Index, "newChildTask12")); QVERIFY(model->setData(task1Index, Qt::Unchecked, Qt::CheckStateRole)); QVERIFY(model->setData(childTask11Index, Qt::Unchecked, Qt::CheckStateRole)); QVERIFY(model->setData(task2Index, Qt::Checked, Qt::CheckStateRole)); QVERIFY(model->setData(task3Index, Qt::Unchecked, Qt::CheckStateRole)); QVERIFY(model->setData(taskChildTask12Index, Qt::Checked, Qt::CheckStateRole)); // THEN QVERIFY(taskRepositoryMock(&Domain::TaskRepository::update).when(task1).exactly(2)); QVERIFY(taskRepositoryMock(&Domain::TaskRepository::update).when(childTask11).exactly(2)); QVERIFY(taskRepositoryMock(&Domain::TaskRepository::update).when(childTask12).exactly(2)); QVERIFY(taskRepositoryMock(&Domain::TaskRepository::update).when(task2).exactly(2)); QVERIFY(taskRepositoryMock(&Domain::TaskRepository::update).when(task3).exactly(2)); QCOMPARE(task1->title(), QStringLiteral("newTask1")); QCOMPARE(childTask11->title(), QStringLiteral("newChildTask11")); QCOMPARE(childTask12->title(), QStringLiteral("newChildTask12")); QCOMPARE(task2->title(), QStringLiteral("newTask2")); QCOMPARE(task3->title(), QStringLiteral("newTask3")); QCOMPARE(task1->isDone(), false); QCOMPARE(childTask11->isDone(), false); QCOMPARE(childTask12->isDone(), true); QCOMPARE(task2->isDone(), true); QCOMPARE(task3->isDone(), false); // WHEN auto data = std::unique_ptr(model->mimeData(QModelIndexList() << childTask12Index)); // THEN QVERIFY(data->hasFormat(QStringLiteral("application/x-zanshin-object"))); QCOMPARE(data->property("objects").value(), Domain::Artifact::List() << childTask12); // WHEN auto childTask2 = Domain::Task::Ptr::create(); taskRepositoryMock(&Domain::TaskRepository::associate).when(childTask11, childTask2).thenReturn(new FakeJob(this)); data.reset(new QMimeData); data->setData(QStringLiteral("application/x-zanshin-object"), "object"); data->setProperty("objects", QVariant::fromValue(Domain::Artifact::List() << childTask2)); model->dropMimeData(data.get(), Qt::MoveAction, -1, -1, childTask11Index); // THEN QVERIFY(taskRepositoryMock(&Domain::TaskRepository::associate).when(childTask11, childTask2).exactly(1)); // WHEN auto childTask3 = Domain::Task::Ptr::create(); auto childTask4 = Domain::Task::Ptr::create(); taskRepositoryMock(&Domain::TaskRepository::associate).when(childTask12, childTask3).thenReturn(new FakeJob(this)); taskRepositoryMock(&Domain::TaskRepository::associate).when(childTask12, childTask4).thenReturn(new FakeJob(this)); data.reset(new QMimeData); data->setData(QStringLiteral("application/x-zanshin-object"), "object"); data->setProperty("objects", QVariant::fromValue(Domain::Artifact::List() << childTask3 << childTask4)); model->dropMimeData(data.get(), Qt::MoveAction, -1, -1, childTask12Index); // THEN QVERIFY(taskRepositoryMock(&Domain::TaskRepository::associate).when(childTask12, childTask3).exactly(1)); QVERIFY(taskRepositoryMock(&Domain::TaskRepository::associate).when(childTask12, childTask4).exactly(1)); // WHEN auto childTask5 = Domain::Task::Ptr::create(); QVERIFY(!childTask5->startDate().isValid()); taskRepositoryMock(&Domain::TaskRepository::dissociate).when(childTask5).thenReturn(new FakeJob(this)); data.reset(new QMimeData); data->setData(QStringLiteral("application/x-zanshin-object"), "object"); data->setProperty("objects", QVariant::fromValue(Domain::Artifact::List() << childTask5)); model->dropMimeData(data.get(), Qt::MoveAction, -1, -1, QModelIndex()); // THEN QVERIFY(taskRepositoryMock(&Domain::TaskRepository::dissociate).when(childTask5).exactly(1)); QVERIFY(taskRepositoryMock(&Domain::TaskRepository::update).when(childTask5).exactly(0)); - QCOMPARE(childTask5->startDate().date(), today.date()); + QCOMPARE(childTask5->startDate(), today); } void shouldAddTasksInWorkdayPage() { // GIVEN // ... in fact we won't list any model Utils::MockObject taskQueriesMock; // We'll gladly create a task though Utils::MockObject taskRepositoryMock; taskRepositoryMock(&Domain::TaskRepository::create).when(any()).thenReturn(new FakeJob(this)); Presentation::WorkdayPageModel workday(taskQueriesMock.getInstance(), taskRepositoryMock.getInstance()); // WHEN auto title = QStringLiteral("New task"); - auto today = Utils::DateTime::currentDateTime(); + auto today = Utils::DateTime::currentDate(); auto task = workday.addItem(title).objectCast(); // THEN QVERIFY(taskRepositoryMock(&Domain::TaskRepository::create).when(any()).exactly(1)); QVERIFY(task); QCOMPARE(task->title(), title); - QCOMPARE(task->startDate().date(), today.date()); + QCOMPARE(task->startDate(), today); } void shouldAddChildTask() { // GIVEN // Two tasks auto task1 = Domain::Task::Ptr::create(); auto task2 = Domain::Task::Ptr::create(); auto taskProvider = Domain::QueryResultProvider::Ptr::create(); auto taskResult = Domain::QueryResult::create(taskProvider); taskProvider->append(task1); taskProvider->append(task2); Utils::MockObject taskQueriesMock; taskQueriesMock(&Domain::TaskQueries::findWorkdayTopLevel).when().thenReturn(taskResult); taskQueriesMock(&Domain::TaskQueries::findChildren).when(task1).thenReturn(Domain::QueryResult::Ptr()); taskQueriesMock(&Domain::TaskQueries::findChildren).when(task2).thenReturn(Domain::QueryResult::Ptr()); Utils::MockObject taskRepositoryMock; taskRepositoryMock(&Domain::TaskRepository::createChild).when(any(), any()) .thenReturn(new FakeJob(this)); Presentation::WorkdayPageModel workday(taskQueriesMock.getInstance(), taskRepositoryMock.getInstance()); // WHEN const auto title = QStringLiteral("New task"); const auto parentIndex = workday.centralListModel()->index(0, 0); const auto createdTask = workday.addItem(title, parentIndex).objectCast(); // THEN QVERIFY(taskRepositoryMock(&Domain::TaskRepository::createChild).when(any(), any()) .exactly(1)); QVERIFY(createdTask); QCOMPARE(createdTask->title(), title); QVERIFY(!createdTask->startDate().isValid()); } void shouldDeleteItems() { // GIVEN // Two tasks auto task1 = Domain::Task::Ptr::create(); auto task2 = Domain::Task::Ptr::create(); auto taskProvider = Domain::QueryResultProvider::Ptr::create(); auto taskResult = Domain::QueryResult::create(taskProvider); taskProvider->append(task1); taskProvider->append(task2); Utils::MockObject taskQueriesMock; taskQueriesMock(&Domain::TaskQueries::findWorkdayTopLevel).when().thenReturn(taskResult); taskQueriesMock(&Domain::TaskQueries::findChildren).when(task1).thenReturn(Domain::QueryResult::Ptr()); taskQueriesMock(&Domain::TaskQueries::findChildren).when(task2).thenReturn(Domain::QueryResult::Ptr()); Utils::MockObject taskRepositoryMock; taskRepositoryMock(&Domain::TaskRepository::remove).when(task2).thenReturn(new FakeJob(this)); Presentation::WorkdayPageModel workday(taskQueriesMock.getInstance(), taskRepositoryMock.getInstance()); // WHEN const QModelIndex index = workday.centralListModel()->index(1, 0); workday.removeItem(index); // THEN QVERIFY(taskRepositoryMock(&Domain::TaskRepository::remove).when(task2).exactly(1)); } void shouldPromoteItem() { // GIVEN // Two tasks auto task1 = Domain::Task::Ptr::create(); auto task2 = Domain::Task::Ptr::create(); auto taskProvider = Domain::QueryResultProvider::Ptr::create(); auto taskResult = Domain::QueryResult::create(taskProvider); taskProvider->append(task1); taskProvider->append(task2); Utils::MockObject taskQueriesMock; taskQueriesMock(&Domain::TaskQueries::findWorkdayTopLevel).when().thenReturn(taskResult); taskQueriesMock(&Domain::TaskQueries::findChildren).when(task1).thenReturn(Domain::QueryResult::Ptr()); taskQueriesMock(&Domain::TaskQueries::findChildren).when(task2).thenReturn(Domain::QueryResult::Ptr()); Utils::MockObject taskRepositoryMock; taskRepositoryMock(&Domain::TaskRepository::promoteToProject).when(task2).thenReturn(new FakeJob(this)); Presentation::WorkdayPageModel workday(taskQueriesMock.getInstance(), taskRepositoryMock.getInstance()); // WHEN const QModelIndex index = workday.centralListModel()->index(1, 0); workday.promoteItem(index); // THEN QVERIFY(taskRepositoryMock(&Domain::TaskRepository::promoteToProject).when(task2).exactly(1)); } }; ZANSHIN_TEST_MAIN(WorkdayPageModelTest) #include "workdaypagemodeltest.moc" diff --git a/tests/units/testlib/gentodotest.cpp b/tests/units/testlib/gentodotest.cpp index a0f323d5..83df2ab0 100644 --- a/tests/units/testlib/gentodotest.cpp +++ b/tests/units/testlib/gentodotest.cpp @@ -1,233 +1,233 @@ /* This file is part of Zanshin Copyright 2015 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "testlib/gentodo.h" #include #include #include using namespace Testlib; class GenTodoTest : public QObject { Q_OBJECT private slots: void shouldImplicitlyConvertBackToItem() { // GIVEN auto item = Akonadi::Item(42); auto gen = GenTodo(item); // WHEN Akonadi::Item newItem = gen; // THEN QCOMPARE(newItem, item); QCOMPARE(newItem.mimeType(), KCalCore::Todo::todoMimeType()); QVERIFY(newItem.hasPayload()); } void shouldAllowToSetId() { // GIVEN Akonadi::Item item = GenTodo().withId(42); // THEN QCOMPARE(item.id(), 42LL); } void shouldAllowToSetParent() { // GIVEN Akonadi::Item item = GenTodo().withParent(42); // THEN QCOMPARE(item.parentCollection().id(), 42LL); } void shouldAllowToSetTags() { // GIVEN Akonadi::Item item = GenTodo().withTags({42, 43, 44}); // THEN QCOMPARE(item.tags().size(), 3); QCOMPARE(item.tags().at(0).id(), 42LL); QCOMPARE(item.tags().at(1).id(), 43LL); QCOMPARE(item.tags().at(2).id(), 44LL); } void shouldAllowToSetProjectType() { // GIVEN Akonadi::Item item = GenTodo().asProject(); // THEN QVERIFY(!item.payload()->customProperty("Zanshin", "Project").isEmpty()); // WHEN item = GenTodo(item).asProject(false); // THEN QVERIFY(item.payload()->customProperty("Zanshin", "Project").isEmpty()); } void shouldAllowToSetUid() { // GIVEN Akonadi::Item item = GenTodo().withUid(QStringLiteral("42")); // THEN QCOMPARE(item.payload()->uid(), QStringLiteral("42")); } void shouldAllowToSetParentUid() { // GIVEN Akonadi::Item item = GenTodo().withParentUid(QStringLiteral("42")); // THEN QCOMPARE(item.payload()->relatedTo(), QStringLiteral("42")); } void shouldAllowToSetTitle() { // GIVEN Akonadi::Item item = GenTodo().withTitle(QStringLiteral("42")); // THEN QCOMPARE(item.payload()->summary(), QStringLiteral("42")); } void shouldAllowToSetText() { // GIVEN Akonadi::Item item = GenTodo().withText(QStringLiteral("42")); // THEN QCOMPARE(item.payload()->description(), QStringLiteral("42")); } void shouldAllowToSetDoneState() { // GIVEN Akonadi::Item item = GenTodo().done(); // THEN QVERIFY(item.payload()->isCompleted()); // WHEN item = GenTodo(item).done(false); // THEN // KCalCore 5.2.40 fixes this bug #if KCALCORE_VERSION < 0x050228 QEXPECT_FAIL("", "Bug introduced in KCalCore, fixed in upcoming 16.04", Continue); #endif QVERIFY(!item.payload()->isCompleted()); } void shouldAllowToSetDoneDate() { // GIVEN - Akonadi::Item item = GenTodo().withDoneDate(QDateTime(QDate(2015, 4, 12), QTime(12, 00))); + Akonadi::Item item = GenTodo().withDoneDate(QDate(2015, 4, 12)); // THEN - QCOMPARE(item.payload()->completed().date(), QDate(2015, 04, 12)); + QCOMPARE(item.payload()->completed().toLocalTime().date(), QDate(2015, 04, 12)); } void shouldAllowToSetDoneDateString() { // GIVEN Akonadi::Item item = GenTodo().withDoneDate(QStringLiteral("2015-04-12")); // THEN - QCOMPARE(item.payload()->completed().date(), QDate(2015, 04, 12)); + QCOMPARE(item.payload()->completed().toLocalTime().date(), QDate(2015, 04, 12)); } void shouldAllowToSetStartDate() { // GIVEN - Akonadi::Item item = GenTodo().withStartDate(QDateTime(QDate(2015, 4, 12))); + Akonadi::Item item = GenTodo().withStartDate(QDate(2015, 4, 12)); // THEN QCOMPARE(item.payload()->dtStart().date(), QDate(2015, 04, 12)); } void shouldAllowToSetStartDateString() { // GIVEN Akonadi::Item item = GenTodo().withStartDate(QStringLiteral("2015-04-12")); // THEN QCOMPARE(item.payload()->dtStart().date(), QDate(2015, 04, 12)); } void shouldAllowToSetDueDate() { // GIVEN - Akonadi::Item item = GenTodo().withDueDate(QDateTime(QDate(2015, 4, 12))); + Akonadi::Item item = GenTodo().withDueDate(QDate(2015, 4, 12)); // THEN QCOMPARE(item.payload()->dtDue().date(), QDate(2015, 04, 12)); } void shouldAllowToSetDueDateString() { // GIVEN Akonadi::Item item = GenTodo().withDueDate(QStringLiteral("2015-04-12")); // THEN QCOMPARE(item.payload()->dtDue().date(), QDate(2015, 04, 12)); } void shouldAllowToSetDelegate() { // GIVEN Akonadi::Item item = GenTodo().withDelegate(QStringLiteral("John Doe"), QStringLiteral("john@doe.net")); // THEN QCOMPARE(item.payload()->attendeeCount(), 1); const auto attendees = item.payload()->attendees(); const auto delegate = std::find_if(attendees.begin(), attendees.end(), [] (const KCalCore::Attendee::Ptr &attendee) { return attendee->status() == KCalCore::Attendee::Delegated; }); QVERIFY(delegate != attendees.constEnd()); QCOMPARE((*delegate)->name(), QStringLiteral("John Doe")); QCOMPARE((*delegate)->email(), QStringLiteral("john@doe.net")); // WHEN item = GenTodo(item).withNoDelegate(); // THEN QCOMPARE(item.payload()->attendeeCount(), 0); } }; ZANSHIN_TEST_MAIN(GenTodoTest) #include "gentodotest.moc" diff --git a/tests/units/widgets/pageviewtest.cpp b/tests/units/widgets/pageviewtest.cpp index a9108437..c132ba60 100644 --- a/tests/units/widgets/pageviewtest.cpp +++ b/tests/units/widgets/pageviewtest.cpp @@ -1,850 +1,850 @@ /* This file is part of Zanshin Copyright 2014 Kevin Ottens This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "domain/task.h" #include "presentation/artifactfilterproxymodel.h" #include "presentation/metatypes.h" #include "presentation/querytreemodelbase.h" #include "widgets/filterwidget.h" #include "widgets/itemdelegate.h" #include "widgets/pageview.h" #include "messageboxstub.h" class PageModelStub : public QObject { Q_OBJECT Q_PROPERTY(QAbstractItemModel* centralListModel READ centralListModel) public: QAbstractItemModel *centralListModel() { return &itemModel; } QStandardItem *addStubItem(const QString &title, QStandardItem *parentItem = Q_NULLPTR) { QStandardItem *item = new QStandardItem; item->setData(title, Qt::DisplayRole); if (!parentItem) itemModel.appendRow(item); else parentItem->appendRow(item); taskNames << title; return item; } Domain::Task::Ptr addTaskItem(const QString &title, QStandardItem *parentItem = Q_NULLPTR) { auto item = addStubItem(title, parentItem); auto task = Domain::Task::Ptr::create(); task->setTitle(title); item->setData(QVariant::fromValue(task), Presentation::QueryTreeModelBase::ObjectRole); return task; } void addStubItems(const QStringList &list) { foreach (const QString &title, list) { addStubItem(title); } } public slots: void addItem(const QString &name, const QModelIndex &parentIndex) { taskNames << name; parentIndices << parentIndex; } void removeItem(const QModelIndex &index) { removedIndices << index; } void promoteItem(const QModelIndex &index) { promotedIndices << index; } public: QStringList taskNames; QList parentIndices; QList removedIndices; QList promotedIndices; QStandardItemModel itemModel; }; class RunningTaskModelStub : public Presentation::RunningTaskModelInterface { Q_OBJECT public: Domain::Task::Ptr runningTask() const Q_DECL_OVERRIDE { return m_runningTask; } void setRunningTask(const Domain::Task::Ptr &task) Q_DECL_OVERRIDE { m_runningTask = task; } void taskDeleted(const Domain::Task::Ptr &task) Q_DECL_OVERRIDE { m_deletedTask = task; } void stopTask() Q_DECL_OVERRIDE {} void doneTask() Q_DECL_OVERRIDE {} private: Domain::Task::Ptr m_runningTask; Domain::Task::Ptr m_deletedTask; }; class PageViewTest : public QObject { Q_OBJECT private: KConfigGroup configGroup() { return KConfigGroup(KSharedConfig::openConfig(), "General"); } private slots: void shouldHaveDefaultState() { Widgets::PageView page; QCOMPARE(page.contentsMargins(), QMargins(0, 0, 0, 0)); QCOMPARE(page.layout()->contentsMargins(), QMargins(0, 0, 0, 3)); auto messageWidget = page.findChild(QStringLiteral("messageWidget")); QVERIFY(messageWidget); QVERIFY(!messageWidget->isVisibleTo(&page)); QVERIFY(!messageWidget->isCloseButtonVisible()); QVERIFY(messageWidget->wordWrap()); QVERIFY(messageWidget->text().isEmpty()); QVERIFY(messageWidget->icon().isNull()); QCOMPARE(messageWidget->messageType(), KMessageWidget::Error); QVERIFY(!messageWidget->isShowAnimationRunning()); QVERIFY(!messageWidget->isHideAnimationRunning()); auto centralView = page.findChild(QStringLiteral("centralView")); QVERIFY(centralView); QVERIFY(centralView->isVisibleTo(&page)); QVERIFY(!centralView->header()->isVisibleTo(&page)); QVERIFY(qobject_cast(centralView->itemDelegate())); QVERIFY(centralView->alternatingRowColors()); QCOMPARE(centralView->dragDropMode(), QTreeView::DragDrop); auto filter = page.findChild(QStringLiteral("filterWidget")); QVERIFY(filter); QVERIFY(!filter->isVisibleTo(&page)); QVERIFY(filter->proxyModel()); QCOMPARE(filter->proxyModel(), centralView->model()); QLineEdit *quickAddEdit = page.findChild(QStringLiteral("quickAddEdit")); QVERIFY(quickAddEdit); QVERIFY(quickAddEdit->isVisibleTo(&page)); QVERIFY(quickAddEdit->text().isEmpty()); QCOMPARE(quickAddEdit->placeholderText(), i18n("Type and press enter to add an item")); auto addAction = page.findChild(QStringLiteral("addItemAction")); QVERIFY(addAction); auto cancelAddAction = page.findChild(QStringLiteral("cancelAddItemAction")); QVERIFY(cancelAddAction); auto removeAction = page.findChild(QStringLiteral("removeItemAction")); QVERIFY(removeAction); auto promoteAction = page.findChild(QStringLiteral("promoteItemAction")); QVERIFY(promoteAction); auto filterAction = page.findChild(QStringLiteral("filterViewAction")); QVERIFY(filterAction); QVERIFY(filterAction->isCheckable()); QVERIFY(!filterAction->isChecked()); auto futureAction = page.findChild(QStringLiteral("futureViewAction")); QVERIFY(futureAction); QVERIFY(futureAction->isCheckable()); QVERIFY(futureAction->isChecked()); auto runTaskAction = page.findChild(QStringLiteral("runTaskAction")); QVERIFY(runTaskAction); QVERIFY(!runTaskAction->isEnabled()); auto actions = page.globalActions(); QCOMPARE(actions.value(QStringLiteral("page_view_add")), addAction); QCOMPARE(actions.value(QStringLiteral("page_view_remove")), removeAction); QCOMPARE(actions.value(QStringLiteral("page_view_promote")), promoteAction); QCOMPARE(actions.value(QStringLiteral("page_view_filter")), filterAction); QCOMPARE(actions.value(QStringLiteral("page_view_future")), futureAction); QCOMPARE(actions.value(QStringLiteral("page_run_task")), runTaskAction); } void shouldDisplayListFromPageModel() { // GIVEN QStandardItemModel model; QObject stubPageModel; stubPageModel.setProperty("centralListModel", QVariant::fromValue(static_cast(&model))); Widgets::PageView page; auto centralView = page.findChild(QStringLiteral("centralView")); QVERIFY(centralView); auto proxyModel = qobject_cast(centralView->model()); QVERIFY(proxyModel); QVERIFY(!proxyModel->sourceModel()); // WHEN page.setModel(&stubPageModel); // THEN QCOMPARE(page.model(), &stubPageModel); QVERIFY(page.isEnabled()); QCOMPARE(proxyModel->sourceModel(), &model); } void shouldNotCrashWithNullModel() { // GIVEN QStandardItemModel model; QObject stubPageModel; stubPageModel.setProperty("centralListModel", QVariant::fromValue(static_cast(&model))); Widgets::PageView page; page.setModel(&stubPageModel); auto centralView = page.findChild(QStringLiteral("centralView")); QVERIFY(centralView); auto proxyModel = qobject_cast(centralView->model()); QVERIFY(proxyModel); QCOMPARE(proxyModel->sourceModel(), &model); // WHEN page.setModel(Q_NULLPTR); // THEN QVERIFY(!page.model()); QVERIFY(!page.isEnabled()); QVERIFY(!proxyModel->sourceModel()); } void shouldManageFocusThroughActions() { // GIVEN Widgets::PageView page; auto centralView = page.findChild(QStringLiteral("centralView")); auto quickAddEdit = page.findChild(QStringLiteral("quickAddEdit")); auto filter = page.findChild(QStringLiteral("filterWidget")); auto filterEdit = filter->findChild(); QVERIFY(filterEdit); page.show(); QVERIFY(QTest::qWaitForWindowShown(&page)); centralView->setFocus(); QVERIFY(centralView->hasFocus()); QVERIFY(!quickAddEdit->hasFocus()); QVERIFY(!filter->isVisibleTo(&page)); QVERIFY(!filterEdit->hasFocus()); auto addAction = page.findChild(QStringLiteral("addItemAction")); auto cancelAddAction = page.findChild(QStringLiteral("cancelAddItemAction")); auto filterAction = page.findChild(QStringLiteral("filterViewAction")); // WHEN addAction->trigger(); // THEN QVERIFY(!centralView->hasFocus()); QVERIFY(quickAddEdit->hasFocus()); QVERIFY(!filter->isVisibleTo(&page)); QVERIFY(!filterEdit->hasFocus()); // WHEN cancelAddAction->trigger(); // THEN QVERIFY(centralView->hasFocus()); QVERIFY(!quickAddEdit->hasFocus()); QVERIFY(!filter->isVisibleTo(&page)); QVERIFY(!filterEdit->hasFocus()); // WHEN filterAction->trigger(); // THEN QVERIFY(!centralView->hasFocus()); QVERIFY(!quickAddEdit->hasFocus()); QVERIFY(filter->isVisibleTo(&page)); QVERIFY(filterEdit->hasFocus()); // WHEN cancelAddAction->trigger(); // THEN QVERIFY(centralView->hasFocus()); QVERIFY(!quickAddEdit->hasFocus()); QVERIFY(filter->isVisibleTo(&page)); QVERIFY(!filterEdit->hasFocus()); } void shouldManageFilterVisibilityThroughAction() { // GIVEN Widgets::PageView page; auto centralView = page.findChild(QStringLiteral("centralView")); auto filter = page.findChild(QStringLiteral("filterWidget")); auto filterEdit = filter->findChild(); QVERIFY(filterEdit); page.show(); QVERIFY(QTest::qWaitForWindowShown(&page)); centralView->setFocus(); QVERIFY(centralView->hasFocus()); QVERIFY(!filter->isVisibleTo(&page)); QVERIFY(!filterEdit->hasFocus()); auto filterAction = page.findChild(QStringLiteral("filterViewAction")); // WHEN filterAction->trigger(); // THEN QVERIFY(!centralView->hasFocus()); QVERIFY(filter->isVisibleTo(&page)); QVERIFY(filterEdit->hasFocus()); // WHEN filterEdit->setText("Foo"); // THEN QCOMPARE(filterEdit->text(), QString("Foo")); // WHEN filterAction->trigger(); // THEN QVERIFY(centralView->hasFocus()); QVERIFY(!filter->isVisibleTo(&page)); QVERIFY(!filterEdit->hasFocus()); QVERIFY(filterEdit->text().isEmpty()); } void shouldManageFutureTasksVisibilityThroughAction() { // GIVEN Widgets::PageView page; auto filter = page.findChild(QStringLiteral("filterWidget")); auto filterProxy = filter->proxyModel(); QVERIFY(filterProxy); QVERIFY(filterProxy->showFutureTasks()); auto futureAction = page.findChild(QStringLiteral("futureViewAction")); // WHEN futureAction->trigger(); // THEN QVERIFY(!filterProxy->showFutureTasks()); // WHEN futureAction->trigger(); // THEN QVERIFY(filterProxy->showFutureTasks()); } void shouldStoreFutureTasksVisibilityDefaultState() { // GIVEN configGroup().deleteEntry("ShowFuture"); { Widgets::PageView page; auto futureAction = page.findChild(QStringLiteral("futureViewAction")); // THEN QVERIFY(futureAction->isChecked()); } // WHEN configGroup().writeEntry("ShowFuture", false); { Widgets::PageView page; auto futureAction = page.findChild(QStringLiteral("futureViewAction")); // THEN QVERIFY(!futureAction->isChecked()); } // WHEN configGroup().writeEntry("ShowFuture", true); { Widgets::PageView page; auto futureAction = page.findChild(QStringLiteral("futureViewAction")); // THEN QVERIFY(futureAction->isChecked()); } // WHEN configGroup().deleteEntry("ShowFuture"); { Widgets::PageView page; auto futureAction = page.findChild(QStringLiteral("futureViewAction")); // THEN QVERIFY(futureAction->isChecked()); } // WHEN Widgets::PageView page; auto futureAction = page.findChild(QStringLiteral("futureViewAction")); futureAction->trigger(); // THEN QVERIFY(configGroup().hasKey("ShowFuture")); QVERIFY(!configGroup().readEntry("ShowFuture", true)); // WHEN futureAction->trigger(); // THEN QVERIFY(configGroup().hasKey("ShowFuture")); QVERIFY(configGroup().readEntry("ShowFuture", false)); } void shouldCreateTasksWithNoParentWhenHittingReturnWithoutSelectedIndex() { // GIVEN PageModelStub stubPageModel; Widgets::PageView page; page.setModel(&stubPageModel); auto quickAddEdit = page.findChild(QStringLiteral("quickAddEdit")); // WHEN QTest::keyClick(quickAddEdit, Qt::Key_Return); // Does nothing (edit empty) QTest::keyClicks(quickAddEdit, QStringLiteral("Foo")); QTest::keyClick(quickAddEdit, Qt::Key_Return); QTest::keyClick(quickAddEdit, Qt::Key_Return); // Does nothing (edit empty) QTest::keyClicks(quickAddEdit, QStringLiteral("Bar")); QTest::keyClick(quickAddEdit, Qt::Key_Return); QTest::keyClick(quickAddEdit, Qt::Key_Return); // Does nothing (edit empty) // THEN QCOMPARE(stubPageModel.taskNames, QStringList() << QStringLiteral("Foo") << QStringLiteral("Bar")); QCOMPARE(stubPageModel.parentIndices.size(), 2); QCOMPARE(stubPageModel.parentIndices.first(), QPersistentModelIndex()); QCOMPARE(stubPageModel.parentIndices.last(), QPersistentModelIndex()); } void shouldCreateTasksWithNoParentWhenHittingReturnWithSeveralSelectedIndices() { // GIVEN PageModelStub stubPageModel; Q_ASSERT(stubPageModel.property("centralListModel").canConvert()); stubPageModel.addStubItems(QStringList() << QStringLiteral("A") << QStringLiteral("B") << QStringLiteral("C")); QPersistentModelIndex index0 = stubPageModel.itemModel.index(0, 0); QPersistentModelIndex index1 = stubPageModel.itemModel.index(1, 0); Widgets::PageView page; page.setModel(&stubPageModel); auto centralView = page.findChild(QStringLiteral("centralView")); centralView->selectionModel()->select(index0, QItemSelectionModel::ClearAndSelect); centralView->selectionModel()->select(index1, QItemSelectionModel::Select); auto quickAddEdit = page.findChild(QStringLiteral("quickAddEdit")); // WHEN QTest::keyClicks(quickAddEdit, QStringLiteral("Foo")); QTest::keyClick(quickAddEdit, Qt::Key_Return); // THEN QCOMPARE(stubPageModel.taskNames, QStringList() << QStringLiteral("A") << QStringLiteral("B") << QStringLiteral("C") << QStringLiteral("Foo")); QCOMPARE(stubPageModel.parentIndices.size(), 1); QCOMPARE(stubPageModel.parentIndices.first(), QPersistentModelIndex()); } void shouldCreateTasksWithParentWhenHittingReturnWithOneSelectedIndex() { // GIVEN PageModelStub stubPageModel; Q_ASSERT(stubPageModel.property("centralListModel").canConvert()); stubPageModel.addStubItems(QStringList() << QStringLiteral("A") << QStringLiteral("B") << QStringLiteral("C")); QPersistentModelIndex index = stubPageModel.itemModel.index(1, 0); Widgets::PageView page; page.setModel(&stubPageModel); auto centralView = page.findChild(QStringLiteral("centralView")); centralView->selectionModel()->select(index, QItemSelectionModel::ClearAndSelect); auto quickAddEdit = page.findChild(QStringLiteral("quickAddEdit")); // WHEN QTest::keyClicks(quickAddEdit, QStringLiteral("Foo")); QTest::keyClick(quickAddEdit, Qt::Key_Return); // THEN QCOMPARE(stubPageModel.taskNames, QStringList() << QStringLiteral("A") << QStringLiteral("B") << QStringLiteral("C") << QStringLiteral("Foo")); QCOMPARE(stubPageModel.parentIndices.size(), 1); QCOMPARE(stubPageModel.parentIndices.first(), index); } void shouldDeleteItemWhenHittingTheDeleteKey() { // GIVEN PageModelStub stubPageModel; Q_ASSERT(stubPageModel.property("centralListModel").canConvert()); stubPageModel.addStubItems(QStringList() << QStringLiteral("A") << QStringLiteral("B") << QStringLiteral("C")); QPersistentModelIndex index = stubPageModel.itemModel.index(1, 0); Widgets::PageView page; page.setModel(&stubPageModel); QTreeView *centralView = page.findChild(QStringLiteral("centralView")); centralView->selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect); centralView->setFocus(); // Needed for shortcuts to work page.show(); QVERIFY(QTest::qWaitForWindowShown(&page)); QTest::qWait(100); // WHEN QTest::keyPress(centralView, Qt::Key_Delete); // THEN QCOMPARE(stubPageModel.removedIndices.size(), 1); QCOMPARE(stubPageModel.removedIndices.first(), index); } void shouldNoteTryToDeleteIfThereIsNoSelection() { // GIVEN PageModelStub stubPageModel; Q_ASSERT(stubPageModel.property("centralListModel").canConvert()); stubPageModel.addStubItems(QStringList() << QStringLiteral("A") << QStringLiteral("B") << QStringLiteral("C")); Widgets::PageView page; page.setModel(&stubPageModel); QTreeView *centralView = page.findChild(QStringLiteral("centralView")); centralView->clearSelection(); page.findChild(QStringLiteral("quickAddEdit"))->setFocus(); // Needed for shortcuts to work page.show(); QVERIFY(QTest::qWaitForWindowShown(&page)); QTest::qWait(100); // WHEN QTest::keyPress(centralView, Qt::Key_Delete); // THEN QVERIFY(stubPageModel.removedIndices.isEmpty()); } void shouldDisplayNotificationWhenHittingTheDeleteKeyOnAnItemWithChildren() { // GIVEN PageModelStub stubPageModel; Q_ASSERT(stubPageModel.property("centralListModel").canConvert()); stubPageModel.addStubItems(QStringList() << QStringLiteral("A") << QStringLiteral("B")); QStandardItem *parentIndex = stubPageModel.itemModel.item(1, 0); stubPageModel.addStubItem(QStringLiteral("C"), parentIndex); QPersistentModelIndex index = stubPageModel.itemModel.index(1, 0); Widgets::PageView page; page.setModel(&stubPageModel); auto msgbox = MessageBoxStub::Ptr::create(); page.setMessageBoxInterface(msgbox); QTreeView *centralView = page.findChild(QStringLiteral("centralView")); centralView->selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect); QVERIFY(centralView->selectionModel()->currentIndex().isValid()); centralView->setFocus(); // Needed for shortcuts to work page.show(); QVERIFY(QTest::qWaitForWindowShown(&page)); QTest::qWait(100); // WHEN QTest::keyPress(centralView, Qt::Key_Delete); // THEN QVERIFY(msgbox->called()); QCOMPARE(stubPageModel.removedIndices.size(), 1); QCOMPARE(stubPageModel.removedIndices.first(), index); } void shouldDeleteItemsWhenHittingTheDeleteKey() { // GIVEN PageModelStub stubPageModel; Q_ASSERT(stubPageModel.property("centralListModel").canConvert()); stubPageModel.addStubItems(QStringList() << QStringLiteral("A") << QStringLiteral("B") << QStringLiteral("C")); QPersistentModelIndex index = stubPageModel.itemModel.index(1, 0); QPersistentModelIndex index2 = stubPageModel.itemModel.index(2, 0); Widgets::PageView page; page.setModel(&stubPageModel); auto msgbox = MessageBoxStub::Ptr::create(); page.setMessageBoxInterface(msgbox); QTreeView *centralView = page.findChild(QStringLiteral("centralView")); centralView->selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect); centralView->selectionModel()->setCurrentIndex(index2, QItemSelectionModel::Select); centralView->setFocus(); // Needed for shortcuts to work page.show(); QVERIFY(QTest::qWaitForWindowShown(&page)); QTest::qWait(100); // WHEN QTest::keyPress(centralView, Qt::Key_Delete); // THEN QVERIFY(msgbox->called()); QCOMPARE(stubPageModel.removedIndices.size(), 2); QCOMPARE(stubPageModel.removedIndices.first(), index); QCOMPARE(stubPageModel.removedIndices.at(1), index2); } void shouldPromoteItem() { // GIVEN PageModelStub stubPageModel; Q_ASSERT(stubPageModel.property("centralListModel").canConvert()); stubPageModel.addStubItems(QStringList() << QStringLiteral("A") << QStringLiteral("B") << QStringLiteral("C")); QPersistentModelIndex index = stubPageModel.itemModel.index(1, 0); Widgets::PageView page; page.setModel(&stubPageModel); auto promoteAction = page.findChild(QStringLiteral("promoteItemAction")); QVERIFY(promoteAction); QTreeView *centralView = page.findChild(QStringLiteral("centralView")); centralView->selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect); centralView->setFocus(); // WHEN promoteAction->trigger(); // THEN QCOMPARE(stubPageModel.promotedIndices.size(), 1); QCOMPARE(stubPageModel.promotedIndices.first(), index); } void shouldNotTryToPromoteItemIfThereIsNoSelection() { // GIVEN PageModelStub stubPageModel; Q_ASSERT(stubPageModel.property("centralListModel").canConvert()); stubPageModel.addStubItems(QStringList() << QStringLiteral("A") << QStringLiteral("B") << QStringLiteral("C")); Widgets::PageView page; page.setModel(&stubPageModel); auto promoteAction = page.findChild(QStringLiteral("promoteItemAction")); QVERIFY(promoteAction); QTreeView *centralView = page.findChild(QStringLiteral("centralView")); centralView->selectionModel()->clear(); centralView->setFocus(); // WHEN promoteAction->trigger(); // THEN QVERIFY(stubPageModel.promotedIndices.isEmpty()); } void shouldClearCentralViewSelectionOnEscape() { // GIVEN PageModelStub stubPageModel; Q_ASSERT(stubPageModel.property("centralListModel").canConvert()); stubPageModel.addStubItems(QStringList() << QStringLiteral("A") << QStringLiteral("B") << QStringLiteral("C")); QPersistentModelIndex index = stubPageModel.itemModel.index(1, 0); Widgets::PageView page; page.setModel(&stubPageModel); auto centralView = page.findChild(QStringLiteral("centralView")); centralView->selectionModel()->select(index, QItemSelectionModel::ClearAndSelect); QVERIFY(!centralView->selectionModel()->selectedIndexes().isEmpty()); // WHEN QTest::keyClick(centralView, Qt::Key_Escape); // THEN QVERIFY(centralView->selectionModel()->selectedIndexes().isEmpty()); } void shouldReturnSelectedIndexes() { // GIVEN PageModelStub stubPageModel; Q_ASSERT(stubPageModel.property("centralListModel").canConvert()); stubPageModel.addStubItems(QStringList() << QStringLiteral("A") << QStringLiteral("B") << QStringLiteral("C")); auto index = stubPageModel.itemModel.index(1, 0); auto index2 = stubPageModel.itemModel.index(2, 0); Widgets::PageView page; page.setModel(&stubPageModel); auto centralView = page.findChild(QStringLiteral("centralView")); auto filterWidget = page.findChild(QStringLiteral("filterWidget")); auto displayedModel = filterWidget->proxyModel(); auto displayedIndex = displayedModel->index(1, 0); auto displayedIndex2 = displayedModel->index(2, 0); // WHEN centralView->selectionModel()->setCurrentIndex(displayedIndex, QItemSelectionModel::ClearAndSelect); centralView->selectionModel()->setCurrentIndex(displayedIndex2, QItemSelectionModel::Select); // THEN auto selectedIndexes = page.selectedIndexes(); QCOMPARE(selectedIndexes.size(), 2); QCOMPARE(selectedIndexes.at(0), index); QCOMPARE(selectedIndexes.at(1), index2); QCOMPARE(selectedIndexes.at(0).model(), index.model()); QCOMPARE(selectedIndexes.at(1).model(), index2.model()); } void shouldDisplayMessageOnError() { // GIVEN Widgets::PageView page; page.show(); QVERIFY(QTest::qWaitForWindowShown(&page)); QTest::qWait(100); auto messageWidget = page.findChild(QStringLiteral("messageWidget")); QVERIFY(messageWidget); QVERIFY(!messageWidget->isVisibleTo(&page)); QCOMPARE(messageWidget->findChildren().size(), 1); auto closeButton = messageWidget->findChildren().first(); QVERIFY(closeButton); // WHEN page.displayErrorMessage(QStringLiteral("Foo Error")); // THEN QVERIFY(messageWidget->isVisibleTo(&page)); QVERIFY(messageWidget->isCloseButtonVisible()); QCOMPARE(messageWidget->text(), QStringLiteral("Foo Error")); QVERIFY(messageWidget->icon().isNull()); QCOMPARE(messageWidget->messageType(), KMessageWidget::Error); QVERIFY(messageWidget->isShowAnimationRunning()); QVERIFY(!messageWidget->isHideAnimationRunning()); // WHEN QTest::qWait(800); // THEN QVERIFY(!messageWidget->isShowAnimationRunning()); QVERIFY(!messageWidget->isHideAnimationRunning()); // WHEN closeButton->click(); // THEN QVERIFY(!messageWidget->isShowAnimationRunning()); QVERIFY(messageWidget->isHideAnimationRunning()); // WHEN QTest::qWait(800); // THEN QVERIFY(!messageWidget->isShowAnimationRunning()); QVERIFY(!messageWidget->isHideAnimationRunning()); } void shouldRunTask() { // GIVEN PageModelStub stubPageModel; Q_ASSERT(stubPageModel.property("centralListModel").canConvert()); auto task1 = stubPageModel.addTaskItem(QStringLiteral("Task1")); auto task2 = stubPageModel.addTaskItem(QStringLiteral("Task2")); Widgets::PageView page; page.setModel(&stubPageModel); RunningTaskModelStub stubRunningTaskModel; page.setRunningTaskModel(&stubRunningTaskModel); auto centralView = page.findChild(QStringLiteral("centralView")); QModelIndex index = stubPageModel.itemModel.index(0, 0); centralView->selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect); QVERIFY(centralView->selectionModel()->currentIndex().isValid()); auto runTaskAction = page.findChild(QStringLiteral("runTaskAction")); QVERIFY(runTaskAction); QVERIFY(runTaskAction->isEnabled()); // WHEN starting the first task runTaskAction->trigger(); // THEN QCOMPARE(stubRunningTaskModel.property("runningTask").value(), task1); - QCOMPARE(task1->startDate().date(), QDate::currentDate()); + QCOMPARE(task1->startDate(), QDate::currentDate()); // WHEN starting the second task QModelIndex index2 = stubPageModel.itemModel.index(1, 0); centralView->selectionModel()->setCurrentIndex(index2, QItemSelectionModel::ClearAndSelect); runTaskAction->trigger(); // THEN QCOMPARE(stubRunningTaskModel.property("runningTask").value(), task2); - QCOMPARE(task2->startDate().date(), QDate::currentDate()); + QCOMPARE(task2->startDate(), QDate::currentDate()); } }; ZANSHIN_TEST_MAIN(PageViewTest) #include "pageviewtest.moc"