diff --git a/src/akonadi/CMakeLists.txt b/src/akonadi/CMakeLists.txt index 52edc8ea..052f4af6 100644 --- a/src/akonadi/CMakeLists.txt +++ b/src/akonadi/CMakeLists.txt @@ -1,43 +1,44 @@ set(akonadi_SRCS akonadiapplicationselectedattribute.cpp akonadicache.cpp + akonadicachingstorage.cpp akonadicollectionfetchjobinterface.cpp akonadiconfigdialog.cpp akonadicontextqueries.cpp akonadicontextrepository.cpp akonadidatasourcequeries.cpp akonadidatasourcerepository.cpp akonadiitemfetchjobinterface.cpp akonadilivequeryhelpers.cpp akonadilivequeryintegrator.cpp akonadimessaging.cpp akonadimessaginginterface.cpp akonadimonitorimpl.cpp akonadimonitorinterface.cpp akonadinotequeries.cpp akonadinoterepository.cpp akonadiprojectqueries.cpp akonadiprojectrepository.cpp akonadiserializer.cpp akonadiserializerinterface.cpp akonadistorage.cpp akonadistorageinterface.cpp akonadistoragesettings.cpp akonaditagfetchjobinterface.cpp akonaditagqueries.cpp akonaditagrepository.cpp akonaditaskqueries.cpp akonaditaskrepository.cpp akonaditimestampattribute.cpp ) add_library(akonadi STATIC ${akonadi_SRCS}) target_link_libraries(akonadi KF5::AkonadiCalendar KF5::AkonadiCore KF5::AkonadiNotes KF5::AkonadiWidgets KF5::Mime KF5::CalendarCore KF5::IdentityManagement ) diff --git a/src/akonadi/akonadicachingstorage.cpp b/src/akonadi/akonadicachingstorage.cpp new file mode 100644 index 00000000..46f426a1 --- /dev/null +++ b/src/akonadi/akonadicachingstorage.cpp @@ -0,0 +1,139 @@ +/* This file is part of Zanshin + + Copyright 2015 Mario Bensi + Copyright 2017 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 "akonadicachingstorage.h" +#include "akonadistorage.h" + +using namespace Akonadi; + +CachingStorage::CachingStorage(const Cache::Ptr &cache, const StorageInterface::Ptr &storage) + : m_cache(cache), + m_storage(storage) +{ +} + +CachingStorage::~CachingStorage() +{ +} + +Collection CachingStorage::defaultTaskCollection() +{ + return m_storage->defaultTaskCollection(); +} + +Collection CachingStorage::defaultNoteCollection() +{ + return m_storage->defaultNoteCollection(); +} + +KJob *CachingStorage::createItem(Item item, Collection collection) +{ + return m_storage->createItem(item, collection); +} + +KJob *CachingStorage::updateItem(Item item, QObject *parent) +{ + return m_storage->updateItem(item, parent); +} + +KJob *CachingStorage::removeItem(Item item) +{ + return m_storage->removeItem(item); +} + +KJob *CachingStorage::removeItems(Item::List items, QObject *parent) +{ + return m_storage->removeItems(items, parent); +} + +KJob *CachingStorage::moveItem(Item item, Collection collection, QObject *parent) +{ + return m_storage->moveItem(item, collection, parent); +} + +KJob *CachingStorage::moveItems(Item::List items, Collection collection, QObject *parent) +{ + return m_storage->moveItems(items, collection, parent); +} + +KJob *CachingStorage::createCollection(Collection collection, QObject *parent) +{ + return m_storage->createCollection(collection, parent); +} + +KJob *CachingStorage::updateCollection(Collection collection, QObject *parent) +{ + return m_storage->updateCollection(collection, parent); +} + +KJob *CachingStorage::removeCollection(Collection collection, QObject *parent) +{ + return m_storage->removeCollection(collection, parent); +} + +KJob *CachingStorage::createTransaction() +{ + return m_storage->createTransaction(); +} + +KJob *CachingStorage::createTag(Tag tag) +{ + return m_storage->createTag(tag); +} + +KJob *CachingStorage::updateTag(Tag tag) +{ + return m_storage->updateTag(tag); +} + +KJob *CachingStorage::removeTag(Tag tag) +{ + return m_storage->removeTag(tag); +} + +CollectionFetchJobInterface *CachingStorage::fetchCollections(Collection collection, StorageInterface::FetchDepth depth, FetchContentTypes types) +{ + return m_storage->fetchCollections(collection, depth, types); +} + +ItemFetchJobInterface *CachingStorage::fetchItems(Collection collection) +{ + return m_storage->fetchItems(collection); +} + +ItemFetchJobInterface *CachingStorage::fetchItem(Akonadi::Item item) +{ + return m_storage->fetchItem(item); +} + +ItemFetchJobInterface *CachingStorage::fetchTagItems(Tag tag) +{ + return m_storage->fetchTagItems(tag); +} + +TagFetchJobInterface *CachingStorage::fetchTags() +{ + return m_storage->fetchTags(); +} diff --git a/src/akonadi/akonadicachingstorage.h b/src/akonadi/akonadicachingstorage.h new file mode 100644 index 00000000..fe68b6f4 --- /dev/null +++ b/src/akonadi/akonadicachingstorage.h @@ -0,0 +1,72 @@ +/* This file is part of Zanshin + + Copyright 2015 Mario Bensi + Copyright 2017 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 AKONADI_CACHING_STORAGE_H +#define AKONADI_CACHING_STORAGE_H + +#include "akonadistorageinterface.h" +#include "akonadicache.h" + +namespace Akonadi { + +class CachingStorage : public StorageInterface +{ +public: + explicit CachingStorage(const Cache::Ptr &cache, const StorageInterface::Ptr &storage); + virtual ~CachingStorage(); + + Akonadi::Collection defaultTaskCollection() Q_DECL_OVERRIDE; + Akonadi::Collection defaultNoteCollection() Q_DECL_OVERRIDE; + + KJob *createItem(Item item, Collection collection) Q_DECL_OVERRIDE; + KJob *updateItem(Item item, QObject *parent = Q_NULLPTR) Q_DECL_OVERRIDE; + KJob *removeItem(Akonadi::Item item) Q_DECL_OVERRIDE; + KJob *removeItems(Item::List items, QObject *parent = Q_NULLPTR) Q_DECL_OVERRIDE; + KJob *moveItem(Item item, Collection collection, QObject *parent = Q_NULLPTR) Q_DECL_OVERRIDE; + KJob *moveItems(Item::List item, Collection collection, QObject *parent = Q_NULLPTR) Q_DECL_OVERRIDE; + + KJob *createCollection(Collection collection, QObject *parent = Q_NULLPTR) Q_DECL_OVERRIDE; + KJob *updateCollection(Collection collection, QObject *parent = Q_NULLPTR) Q_DECL_OVERRIDE; + KJob *removeCollection(Collection collection, QObject *parent = Q_NULLPTR) Q_DECL_OVERRIDE; + + KJob *createTransaction() Q_DECL_OVERRIDE; + + KJob *createTag(Akonadi::Tag tag) Q_DECL_OVERRIDE; + KJob *updateTag(Akonadi::Tag tag) Q_DECL_OVERRIDE; + KJob *removeTag(Akonadi::Tag tag) Q_DECL_OVERRIDE; + + CollectionFetchJobInterface *fetchCollections(Akonadi::Collection collection, FetchDepth depth, FetchContentTypes types) Q_DECL_OVERRIDE; + ItemFetchJobInterface *fetchItems(Akonadi::Collection collection) Q_DECL_OVERRIDE; + ItemFetchJobInterface *fetchItem(Akonadi::Item item) Q_DECL_OVERRIDE; + ItemFetchJobInterface *fetchTagItems(Akonadi::Tag tag) Q_DECL_OVERRIDE; + TagFetchJobInterface *fetchTags() Q_DECL_OVERRIDE; + +private: + Cache::Ptr m_cache; + StorageInterface::Ptr m_storage; +}; + +} + +#endif // AKONADI_CACHING_STORAGE_H diff --git a/src/renku/app/dependencies.cpp b/src/renku/app/dependencies.cpp index 3d348b61..398eb603 100644 --- a/src/renku/app/dependencies.cpp +++ b/src/renku/app/dependencies.cpp @@ -1,101 +1,109 @@ /* This file is part of Renku Notes 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 "dependencies.h" #include "akonadi/akonadidatasourcequeries.h" #include "akonadi/akonadidatasourcerepository.h" #include "akonadi/akonadinotequeries.h" #include "akonadi/akonadinoterepository.h" #include "akonadi/akonaditagqueries.h" #include "akonadi/akonaditagrepository.h" +#include "akonadi/akonadicache.h" +#include "akonadi/akonadicachingstorage.h" #include "akonadi/akonadimonitorimpl.h" #include "akonadi/akonadiserializer.h" #include "akonadi/akonadistorage.h" #include "presentation/artifacteditormodel.h" #include "presentation/availablenotepagesmodel.h" #include "presentation/availablesourcesmodel.h" #include "utils/dependencymanager.h" void App::initializeDependencies() { auto &deps = Utils::DependencyManager::globalInstance(); + deps.add(); deps.add(); deps.add(); - deps.add(); + deps.add([] (Utils::DependencyManager *deps) { + return new Akonadi::CachingStorage(deps->create(), + Akonadi::StorageInterface::Ptr(new Akonadi::Storage)); + }); deps.add([] (Utils::DependencyManager *deps) { return new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Notes, deps->create(), deps->create(), deps->create()); }); deps.add(); deps.add(); deps.add(); deps.add(); deps.add(); deps.add([] (Utils::DependencyManager *deps) { auto model = new Presentation::ArtifactEditorModel; auto repository = deps->create(); model->setSaveFunction([repository] (const Domain::Artifact::Ptr &artifact) { auto note = artifact.objectCast(); Q_ASSERT(note); return repository->update(note); }); return model; }); deps.add(); deps.add(); } diff --git a/src/zanshin/app/dependencies.cpp b/src/zanshin/app/dependencies.cpp index 1ca0aa8c..db22a4b9 100644 --- a/src/zanshin/app/dependencies.cpp +++ b/src/zanshin/app/dependencies.cpp @@ -1,125 +1,132 @@ /* 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 "dependencies.h" #include "akonadi/akonadicontextqueries.h" #include "akonadi/akonadicontextrepository.h" #include "akonadi/akonadidatasourcequeries.h" #include "akonadi/akonadidatasourcerepository.h" #include "akonadi/akonadiprojectqueries.h" #include "akonadi/akonadiprojectrepository.h" #include "akonadi/akonaditaskqueries.h" #include "akonadi/akonaditaskrepository.h" +#include "akonadi/akonadicache.h" +#include "akonadi/akonadicachingstorage.h" #include "akonadi/akonadimessaging.h" #include "akonadi/akonadimonitorimpl.h" #include "akonadi/akonadiserializer.h" #include "akonadi/akonadistorage.h" #include "presentation/artifacteditormodel.h" #include "presentation/availablesourcesmodel.h" #include "presentation/availabletaskpagesmodel.h" #include "presentation/runningtaskmodel.h" #include "utils/dependencymanager.h" void App::initializeDependencies() { auto &deps = Utils::DependencyManager::globalInstance(); + deps.add(); deps.add(); deps.add(); deps.add(); - deps.add(); - + deps.add([] (Utils::DependencyManager *deps) { + return new Akonadi::CachingStorage(deps->create(), + Akonadi::StorageInterface::Ptr(new Akonadi::Storage)); + }); deps.add(); deps.add(); deps.add([] (Utils::DependencyManager *deps) { return new Akonadi::DataSourceQueries(Akonadi::StorageInterface::Tasks, deps->create(), deps->create(), deps->create()); }); deps.add(); deps.add(); deps.add(); deps.add(); deps.add(); deps.add([] (Utils::DependencyManager *deps) { auto model = new Presentation::ArtifactEditorModel; auto repository = deps->create(); model->setSaveFunction([repository] (const Domain::Artifact::Ptr &artifact) { auto task = artifact.objectCast(); Q_ASSERT(task); return repository->update(task); }); model->setDelegateFunction([repository] (const Domain::Task::Ptr &task, const Domain::Task::Delegate &delegate) { return repository->delegate(task, delegate); }); return model; }); deps.add(); deps.add(); deps.add(); } diff --git a/tests/features/cuke-steps.cpp b/tests/features/cuke-steps.cpp index d1a8a35c..83fd52ab 100644 --- a/tests/features/cuke-steps.cpp +++ b/tests/features/cuke-steps.cpp @@ -1,900 +1,903 @@ /* 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 "presentation/applicationmodel.h" #include "presentation/errorhandler.h" #include "presentation/querytreemodelbase.h" +#include "akonadi/akonadicache.h" +#include "akonadi/akonadicachingstorage.h" #include "akonadi/akonadimonitorimpl.h" #include "akonadi/akonadimessaginginterface.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"); static bool initializedDependencies = false; if (!initializedDependencies) { CukeSteps::initializeAppDependencies(); MonitorSpy::setExpirationDelay(200); initializedDependencies = true; } 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 *) { - return m_data.createStorage(); + [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 a \"(.*)\" named \"(.*)\" to \"(.*)\"$") { REGEX_PARAM(QString, objectType); REGEX_PARAM(QString, oldName); REGEX_PARAM(QString, newName); const QString pageNodeName = (objectType == QStringLiteral("project")) ? QStringLiteral("Projects / ") : (objectType == QStringLiteral("context")) ? QStringLiteral("Contexts / ") : QString(); 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 a \"(.*)\" named \"(.*)\"$") { REGEX_PARAM(QString, objectType); REGEX_PARAM(QString, objectName); const QString pageNodeName = (objectType == QStringLiteral("project")) ? QStringLiteral("Projects / ") : (objectType == QStringLiteral("context")) ? QStringLiteral("Contexts / ") : (objectType == QStringLiteral("tag")) ? QStringLiteral("Tags / ") : QString(); 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 + objectName); 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/units/akonadi/CMakeLists.txt b/tests/units/akonadi/CMakeLists.txt index e8b1b1ef..b89b6c5b 100644 --- a/tests/units/akonadi/CMakeLists.txt +++ b/tests/units/akonadi/CMakeLists.txt @@ -1,29 +1,32 @@ include(MacroAkonadiAutoTests) zanshin_auto_tests( akonadiapplicationselectedattributetest akonadicachetest + akonadicachingstoragetest akonadicontextqueriestest akonadicontextrepositorytest akonadidatasourcequeriestest akonadidatasourcerepositorytest akonadilivequeryhelperstest akonadilivequeryintegratortest akonadinotequeriestest akonadinoterepositorytest akonadiprojectqueriestest akonadiprojectrepositorytest akonadiserializertest akonadistoragesettingstest akonaditagqueriestest akonaditagrepositorytest akonaditaskqueriestest akonaditaskrepositorytest akonaditimestampattributetest ) zanshin_akonadi_auto_tests( akonadistoragetest + akonadicachingstorageintegrationtest ) target_link_libraries(tests-units-akonadi-akonadistoragetest akonadi testlib) +target_link_libraries(tests-units-akonadi-akonadicachingstorageintegrationtest akonadi testlib) diff --git a/tests/units/akonadi/akonadicachingstorageintegrationtest.cpp b/tests/units/akonadi/akonadicachingstorageintegrationtest.cpp new file mode 100644 index 00000000..23fa52bf --- /dev/null +++ b/tests/units/akonadi/akonadicachingstorageintegrationtest.cpp @@ -0,0 +1,65 @@ +/* This file is part of Zanshin + + Copyright 2017 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 "akonadi/akonadicachingstorage.h" +#include "akonadi/akonadimonitorimpl.h" +#include "akonadi/akonadiserializer.h" +#include "akonadi/akonadistorage.h" + +class AkonadiCachingStorageIntegrationTest : public Testlib::AkonadiStorageTestBase +{ + Q_OBJECT +public: + explicit AkonadiCachingStorageIntegrationTest(QObject *parent = Q_NULLPTR) + : AkonadiStorageTestBase(parent) + { + } + + Akonadi::StorageInterface::Ptr createStorage() Q_DECL_OVERRIDE + { + auto serializer = Akonadi::SerializerInterface::Ptr(new Akonadi::Serializer); + return Akonadi::StorageInterface::Ptr(new Akonadi::CachingStorage(Akonadi::Cache::Ptr::create(serializer, + createMonitor()), + Akonadi::StorageInterface::Ptr(new Akonadi::Storage))); + } + + Akonadi::MonitorInterface::Ptr createMonitor() Q_DECL_OVERRIDE + { + return Akonadi::MonitorInterface::Ptr(new Akonadi::MonitorImpl); + } + +private slots: + void initTestCase() + { + QVERIFY(TestLib::TestSafety::checkTestIsIsolated()); + } +}; + +ZANSHIN_TEST_MAIN(AkonadiCachingStorageIntegrationTest) + +#include "akonadicachingstorageintegrationtest.moc" diff --git a/tests/units/akonadi/akonadicachingstoragetest.cpp b/tests/units/akonadi/akonadicachingstoragetest.cpp new file mode 100644 index 00000000..6b03accf --- /dev/null +++ b/tests/units/akonadi/akonadicachingstoragetest.cpp @@ -0,0 +1,46 @@ +/* This file is part of Zanshin + + Copyright 2017 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/akonadiserializer.h" + +#include "testlib/akonadifakedata.h" +#include "testlib/gencollection.h" +#include "testlib/gentodo.h" +#include "testlib/gentag.h" +#include "testlib/testhelpers.h" + +using namespace Testlib; + +class AkonadiCachingStorageTest : public QObject +{ + Q_OBJECT + +private slots: +}; + +ZANSHIN_TEST_MAIN(AkonadiCachingStorageTest) + +#include "akonadicachingstoragetest.moc"