Commit 291edc93 authored by Daniel Vrátil's avatar Daniel Vrátil 🤖

Refactor plugin to make it testable without Akonadi and add tests

parent a860a7ed
......@@ -4,6 +4,7 @@ endif()
set(pimeventsplugin_SRCS
pimeventsplugin.cpp
akonadipimdatasource.cpp
eventdatavisitor.cpp
settingschangenotifier.cpp
)
......
/*
* Copyright (C) 2016 Daniel Vrátil <dvratil@kde.org>
*
* 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) any later version.
*
* 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 "akonadipimdatasource.h"
#include "settingschangenotifier.h"
#include <AkonadiCore/ChangeRecorder>
#include <AkonadiCore/ItemFetchScope>
#include <AkonadiCore/EntityDisplayAttribute>
#include <Akonadi/Calendar/ETMCalendar>
#include <KSharedConfig>
#include <KConfigGroup>
AkonadiPimDataSource::AkonadiPimDataSource(QObject *parent)
: QObject(parent)
{
connect(SettingsChangeNotifier::self(), &SettingsChangeNotifier::settingsChanged,
this, &AkonadiPimDataSource::onSettingsChanged);
mMonitor = new Akonadi::ChangeRecorder(this);
mMonitor->setChangeRecordingEnabled(false);
mMonitor->itemFetchScope().fetchFullPayload(true);
mMonitor->itemFetchScope().fetchAttribute<Akonadi::EntityDisplayAttribute>();
onSettingsChanged();
mCalendar = new Akonadi::ETMCalendar(mMonitor, this);
// TOOD: Only retrieve PLD:HEAD once it's supported
mCalendar->setCollectionFilteringEnabled(false);
}
AkonadiPimDataSource::~AkonadiPimDataSource()
{
}
qint64 AkonadiPimDataSource::akonadiIdForIncidence(const KCalCore::Incidence::Ptr &incidence) const
{
return mCalendar->item(incidence).id();
}
KCalCore::Calendar *AkonadiPimDataSource::calendar() const
{
return mCalendar;
}
void AkonadiPimDataSource::onSettingsChanged()
{
QSet<Akonadi::Collection> currentCols;
Q_FOREACH (const Akonadi::Collection &col, mMonitor->collectionsMonitored()) {
currentCols.insert(col);
}
auto config = KSharedConfig::openConfig();
auto group = config->group("PIMEventsPlugin");
const QList<qint64> calendars = group.readEntry(QStringLiteral("calendars"), QList<qint64>());
QSet<Akonadi::Collection> configuredCols;
Q_FOREACH (qint64 colId, calendars) {
configuredCols.insert(Akonadi::Collection(colId));
}
Q_FOREACH (const Akonadi::Collection &col, (currentCols - configuredCols)) {
mMonitor->setCollectionMonitored(col, false);
}
Q_FOREACH (const Akonadi::Collection &col, (configuredCols - currentCols)) {
mMonitor->setCollectionMonitored(col, true);
}
const bool hasSelectedCols = mMonitor->collectionsMonitored().isEmpty();
mMonitor->setMimeTypeMonitored(KCalCore::Event::eventMimeType(), hasSelectedCols);
mMonitor->setMimeTypeMonitored(KCalCore::Todo::todoMimeType(), hasSelectedCols);
}
/*
* Copyright (C) 2016 Daniel Vrátil <dvratil@kde.org>
*
* 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) any later version.
*
* 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 AKONADIPIMDATASOURCE_H
#define AKONADIPIMDATASOURCE_H
#include "pimdatasource.h"
#include <QObject>
namespace Akonadi {
class ChangeRecorder;
class ETMCalendar;
}
class AkonadiPimDataSource : public QObject,
public PimDataSource
{
Q_OBJECT
public:
explicit AkonadiPimDataSource(QObject *parent = Q_NULLPTR);
~AkonadiPimDataSource();
qint64 akonadiIdForIncidence(const KCalCore::Incidence::Ptr &incidence) const Q_DECL_OVERRIDE;
KCalCore::Calendar *calendar() const Q_DECL_OVERRIDE;
private Q_SLOTS:
void onSettingsChanged();
private:
Akonadi::ChangeRecorder *mMonitor;
Akonadi::ETMCalendar *mCalendar;
};
#endif
macro(add_plasma_pimeventsplugin_test _source _additional)
set(_test ${_source} ${_additional} testdataparser.cpp)
set(_test ${_source}
${_additional}
testdataparser.cpp
fakepimdatasource.cpp
${CMAKE_CURRENT_BINARY_DIR}/../pimeventsplugin_debug.cpp
)
get_filename_component(_name ${_source} NAME_WE)
add_executable(${_name} ${_test})
add_test(${_name} ${_name})
......@@ -11,4 +16,4 @@ macro(add_plasma_pimeventsplugin_test _source _additional)
endmacro()
add_plasma_pimeventsplugin_test(eventdatavisitortest.cpp ../eventdatavisitor.cpp)
#add_plasma_pimeventsplugin_test(pimeventsplugintest.cpp "../eventdatavisitor.cpp;../pimeventsplugin.cpp")
add_plasma_pimeventsplugin_test(pimeventsplugintest.cpp "../eventdatavisitor.cpp;../pimeventsplugin.cpp;../akonadipimdatasource.cpp;../settingschangenotifier.cpp")
......@@ -19,6 +19,8 @@
#include "eventdatavisitortest.h"
#include "testdataparser.h"
#include "fakepimdatasource.h"
#include "testutils.h"
#include "../eventdatavisitor.h"
#include <QtTest/QTest>
......@@ -36,16 +38,11 @@ template<typename Visitor>
class TestableVisitor : public Visitor
{
public:
TestableVisitor(const QDate &start = QDate(), const QDate &end = QDate())
: Visitor(Q_NULLPTR, start, end)
TestableVisitor(PimDataSource *source, const QDate &start = QDate(), const QDate &end = QDate())
: Visitor(source, start, end)
{
}
void setAkonadiIdForIncidence(const KCalCore::Incidence::Ptr &incidence, qint64 id)
{
mIdForIncidence.insert(incidence, id);
}
QString callGenerateUid(const KCalCore::Incidence::Ptr &incidence,
const KDateTime &recurrenceId) const
{
......@@ -63,15 +60,6 @@ public:
{
return Visitor::explodeIncidenceOccurences(baseEd, incidence, ok);
}
protected:
qint64 itemIdForIncidence(const KCalCore::Incidence::Ptr &incidence) const Q_DECL_OVERRIDE
{
return mIdForIncidence.value(incidence, -1);
}
private:
QHash<KCalCore::Incidence::Ptr, qint64> mIdForIncidence;
};
using TestableEventDataVisitor = TestableVisitor<EventDataVisitor>;
......@@ -79,35 +67,6 @@ using TestableEventDataIdVisitor = TestableVisitor<EventDataIdVisitor>;
using DateTimeRange = QPair<QDateTime, QDateTime>;
bool EventDataVisitorTest::compareResults(const CalendarEvents::EventData &actual,
const CalendarEvents::EventData &expected)
{
#define COMPARE(_actual, _expected) \
{ \
bool ok = false; \
[actual, expected, &ok]() { \
QCOMPARE(_actual, _expected); \
ok = true; \
}(); \
if (!ok) { \
return false; \
} \
}
COMPARE(actual.title(), expected.title());
COMPARE(actual.description(), expected.description());
COMPARE(actual.isAllDay(), expected.isAllDay());
COMPARE(actual.isMinor(), expected.isMinor());
COMPARE(actual.type(), expected.type());
COMPARE(actual.eventColor(), expected.eventColor());
COMPARE(actual.uid(), expected.uid());
COMPARE(actual.startDateTime(), expected.startDateTime());
COMPARE(actual.endDateTime(), expected.endDateTime());
return true;
}
void EventDataVisitorTest::testGenerateUID_data()
{
QTest::addColumn<KCalCore::Incidence::Ptr>("incidence");
......@@ -136,8 +95,9 @@ void EventDataVisitorTest::testGenerateUID()
QFETCH(qint64, itemId);
QFETCH(QString, expectedUID);
TestableEventDataVisitor visitor;
visitor.setAkonadiIdForIncidence(incidence, itemId);
FakePimDataSource source;
source.setAkonadiIdForIncidence(incidence, itemId);
TestableEventDataVisitor visitor(&source);
const QString result = visitor.callGenerateUid(incidence, recurrenceId);
QCOMPARE(result, expectedUID);
......@@ -199,7 +159,8 @@ void EventDataVisitorTest::testIsInRange()
QFETCH(QDate, eventEnd);
QFETCH(bool, expectedResult);
TestableEventDataVisitor visitor(rangeStart, rangeEnd);
FakePimDataSource source;
TestableEventDataVisitor visitor(&source, rangeStart, rangeEnd);
const bool result = visitor.callIsInRange(eventStart, eventEnd);
QCOMPARE(result, expectedResult);
}
......@@ -241,15 +202,16 @@ void EventDataVisitorTest::testExplodeIncidenceOccurences()
QFETCH(qint64, akonadiItemId);
QFETCH(QVector<CalendarEvents::EventData>, expectedEventData);
TestableEventDataVisitor visitor(rangeStart, rangeEnd);
visitor.setAkonadiIdForIncidence(incidence, akonadiItemId);
FakePimDataSource source;
source.setAkonadiIdForIncidence(incidence, akonadiItemId);
TestableEventDataVisitor visitor(&source, rangeStart, rangeEnd);
bool ok = false;
const auto results = visitor.callExplodeIncidenceOccurences(baseEventData, incidence, ok);
QVERIFY(ok);
QCOMPARE(results.size(), expectedEventData.size());
for (int i = 0; i < results.size(); ++i) {
QVERIFY(compareResults(results[i], expectedEventData[i]));
QVERIFY(TestUtils::compareEventData(results[i], expectedEventData[i]));
}
}
......@@ -283,23 +245,21 @@ void EventDataVisitorTest::testEventDataVisitor()
QFETCH(qint64, akonadiItemId);
QFETCH(QVector<CalendarEvents::EventData>, expectedResults);
TestableEventDataVisitor visitor(rangeStart, rangeEnd);
visitor.setAkonadiIdForIncidence(incidence, akonadiItemId);
FakePimDataSource source;
source.setAkonadiIdForIncidence(incidence, akonadiItemId);
TestableEventDataVisitor visitor(&source, rangeStart, rangeEnd);
QVERIFY(visitor.act(incidence));
const auto &results = visitor.results();
QCOMPARE(results.size(), expectedResults.size());
auto resultValues = results.values();
std::sort(resultValues.begin(), resultValues.end(),
[](const CalendarEvents::EventData &lhs, const CalendarEvents::EventData &rhs) -> bool {
return lhs.startDateTime() < rhs.startDateTime();
});
std::sort(resultValues.begin(), resultValues.end(), std::less<CalendarEvents::EventData>());
for (int i = 0; i < resultValues.size(); ++i) {
const auto &result = resultValues[i];
const auto &expectedResult = expectedResults[i];
QVERIFY(compareResults(result, expectedResult));
QVERIFY(TestUtils::compareEventData(result, expectedResult));
}
}
......@@ -336,8 +296,9 @@ void EventDataVisitorTest::testEventDataIdVisitor()
QFETCH(qint64, akonadiItemId);
QFETCH(QStringList, expectedUids);
TestableEventDataIdVisitor visitor(rangeStart, rangeEnd);
visitor.setAkonadiIdForIncidence(incidence, akonadiItemId);
FakePimDataSource source;
source.setAkonadiIdForIncidence(incidence, akonadiItemId);
TestableEventDataIdVisitor visitor(&source, rangeStart, rangeEnd);
QVERIFY(visitor.act(incidence));
auto results = visitor.results();
......
......@@ -47,10 +47,6 @@ private Q_SLOTS:
void testEventDataIdVisitor_data();
void testEventDataIdVisitor();
private:
bool compareResults(const CalendarEvents::EventData &actual,
const CalendarEvents::EventData &expected);
};
#endif
/*
* Copyright (C) 2016 Daniel Vrátil <dvratil@kde.org>
*
* 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) any later version.
*
* 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 "fakepimdatasource.h"
#include <KCalCore/MemoryCalendar>
FakePimDataSource::FakePimDataSource()
: PimDataSource()
, mCalendar(new KCalCore::MemoryCalendar(KDateTime::UTC))
{
}
FakePimDataSource::~FakePimDataSource()
{
delete mCalendar;
}
void FakePimDataSource::setAkonadiIdForIncidence(const KCalCore::Incidence::Ptr &incidence, qint64 akonadiId)
{
mAkonadiIdMap.insert(incidence, akonadiId);
}
qint64 FakePimDataSource::akonadiIdForIncidence(const KCalCore::Incidence::Ptr &incidence) const
{
return mAkonadiIdMap.value(incidence, -1);
}
KCalCore::Calendar *FakePimDataSource::calendar() const
{
return mCalendar;
}
/*
* Copyright (C) 2016 Daniel Vrátil <dvratil@kde.org>
*
* 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) any later version.
*
* 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 FAKEPIMDATASOURCE_H
#define FAKEPIMDATASOURCE_H
#include "pimdatasource.h"
class FakePimDataSource : public PimDataSource
{
public:
FakePimDataSource();
~FakePimDataSource();
void setAkonadiIdForIncidence(const KCalCore::Incidence::Ptr &incidence, qint64 akonadiId);
qint64 akonadiIdForIncidence(const KCalCore::Incidence::Ptr &incidence) const Q_DECL_OVERRIDE;
KCalCore::Calendar *calendar() const Q_DECL_OVERRIDE;
private:
QMap<KCalCore::Incidence::Ptr, qint64> mAkonadiIdMap;
KCalCore::Calendar *mCalendar;
};
#endif
/*
* Copyright (C) 2016 Daniel Vrátil <dvratil@kde.org>
*
* 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) any later version.
*
* 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 "pimeventsplugintest.h"
#include "fakepimdatasource.h"
#include "testdataparser.h"
#include "../pimeventsplugin.h"
#include "testutils.h"
#include <QTest>
#include <QSignalSpy>
Q_DECLARE_METATYPE(DateEventDataHash)
Q_DECLARE_METATYPE(CalendarEvents::EventData);
void PimEventsPluginTest::initTestCase()
{
qRegisterMetaType<DateEventDataHash>("QMultiHash<QDate, CalendarEvents::EventData>");
qRegisterMetaType<CalendarEvents::EventData>("CalendarEvents::EventData");
}
bool PimEventsPluginTest::compareEventDataHashes(const DateEventDataHash &actual,
const DateEventDataHash &expected)
{
COMPARE(actual.size(), expected.size());
Q_FOREACH (const QDate &resultKey, actual.uniqueKeys()) {
VERIFY(expected.contains(resultKey));
auto resultValues = actual.values(resultKey);
auto expectedValues = expected.values(resultKey);
COMPARE(resultValues.size(), expectedValues.size());
std::sort(resultValues.begin(), resultValues.end(), std::less<CalendarEvents::EventData>());
std::sort(expectedValues.begin(), expectedValues.end(), std::less<CalendarEvents::EventData>());
COMPARE(resultValues, expectedValues);
}
return true;
}
DateEventDataHash PimEventsPluginTest::populateCalendar(FakePimDataSource *source)
{
const QStringList allData = TestDataParser::allTestData();
DateEventDataHash expectedData;
Q_FOREACH (const QString &data, allData) {
TestDataParser parser(data);
if (parser.rangeEnd() < QDate(2016, 5, 1) || parser.rangeStart() > QDate(2016, 5, 31)) {
continue;
}
const KCalCore::Event::Ptr event = parser.incidence().dynamicCast<KCalCore::Event>();
if (event) {
source->setAkonadiIdForIncidence(event, parser.akonadiId());
source->calendar()->addEvent(event);
Q_FOREACH (const CalendarEvents::EventData &dt, parser.eventData()) {
expectedData.insert(dt.startDateTime().date(), dt);
}
}
}
return expectedData;
}
QVector<CalendarEvents::EventData> PimEventsPluginTest::findEventData(const KCalCore::Event::Ptr &event,
const DateEventDataHash &allData)
{
QVector<CalendarEvents::EventData> data;
for (auto it = allData.cbegin(), end = allData.cend(); it != end; ++it) {
// This is a very naive check
if (it->title() == event->summary()
&& it->description() == event->description()
&& it->isAllDay() == event->allDay()) {
data.push_back((*it));
}
}
return data;
}
void PimEventsPluginTest::testLoadEventsForDataRange()
{
FakePimDataSource source;
const DateEventDataHash expectedData = populateCalendar(&source);
PimEventsPlugin plugin(&source);
QSignalSpy dataReadySpy(&plugin, &PimEventsPlugin::dataReady);
QVERIFY(dataReadySpy.isValid());
plugin.loadEventsForDateRange(QDate(2016, 5, 1), QDate(2016, 5, 31));
QCOMPARE(dataReadySpy.size(), 1);
const auto results = dataReadySpy.takeFirst().first().value<DateEventDataHash>();
QVERIFY(compareEventDataHashes(results, expectedData));
plugin.loadEventsForDateRange(QDate(2016, 1, 1), QDate(2016, 1, 30));
QCOMPARE(dataReadySpy.size(), 0);
}
void PimEventsPluginTest::testEventAdded()
{
const QStringList allData = TestDataParser::allTestData();
FakePimDataSource source;
PimEventsPlugin plugin(&source);
QSignalSpy dataReadySpy(&plugin, &PimEventsPlugin::dataReady);
QVERIFY(dataReadySpy.isValid());
plugin.loadEventsForDateRange(QDate(2016, 5, 1), QDate(2016, 5, 31));
QCOMPARE(dataReadySpy.size(), 0);
Q_FOREACH (const QString &data, allData) {
TestDataParser parser(data);
if (parser.rangeEnd() < QDate(2016, 5, 1) || parser.rangeStart() > QDate(2016, 5, 31)) {
continue;
}
const KCalCore::Event::Ptr event = parser.incidence().dynamicCast<KCalCore::Event>();
if (event) {
source.setAkonadiIdForIncidence(event, parser.akonadiId());
source.calendar()->addEvent(event);
DateEventDataHash expectedData;
Q_FOREACH (const CalendarEvents::EventData &dt, parser.eventData()) {
expectedData.insert(dt.startDateTime().date(), dt);
}
QCOMPARE(dataReadySpy.size(), 1);
const auto results = dataReadySpy.takeFirst().first().value<DateEventDataHash>();
QVERIFY(compareEventDataHashes(results, expectedData));
}
}
}
void PimEventsPluginTest::testEventModified()
{
FakePimDataSource source;
PimEventsPlugin plugin(&source);
QSignalSpy eventModifiedSpy(&plugin, &PimEventsPlugin::eventModified);
QVERIFY(eventModifiedSpy.isValid());