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

0001 /*
0002  * SPDX-FileCopyrightText: 2014-2019 Kevin Ottens <ervin@kde.org>
0003  * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0004  */
0005 
0006 
0007 #include "zanshincontext.h"
0008 
0009 #include "akonadi/akonadiapplicationselectedattribute.h"
0010 #include "akonadi/akonadicachingstorage.h"
0011 #include "akonadi/akonadistorageinterface.h"
0012 #include "akonadi/akonaditimestampattribute.h"
0013 
0014 #include "presentation/applicationmodel.h"
0015 #include "presentation/querytreemodelbase.h"
0016 
0017 #include "testlib/akonadifakedataxmlloader.h"
0018 #include "testlib/monitorspy.h"
0019 
0020 #include "utils/dependencymanager.h"
0021 #include "utils/jobhandler.h"
0022 #include "integration/dependencies.h"
0023 
0024 #include <Akonadi/AttributeFactory>
0025 
0026 #include <KConfigGroup>
0027 #include <KSharedConfig>
0028 
0029 #include <QSortFilterProxyModel>
0030 #include <QStandardItemModel>
0031 #include <QTest>
0032 
0033 void FakeErrorHandler::doDisplayMessage(const QString &)
0034 {
0035 }
0036 
0037 ZanshinContext::ZanshinContext(QObject *parent)
0038     : QObject(parent),
0039       m_presentation(nullptr),
0040       m_editor(nullptr),
0041       m_proxyModel(new QSortFilterProxyModel(this)),
0042       m_model(nullptr),
0043       m_sourceModel(nullptr),
0044       m_monitorSpy(nullptr)
0045 {
0046     qputenv("ZANSHIN_OVERRIDE_DATE", "2015-03-10");
0047 
0048     static bool initializedDependencies = false;
0049 
0050     if (!initializedDependencies) {
0051         Integration::initializeGlobalAppDependencies();
0052         MonitorSpy::setExpirationDelay(200);
0053         initializedDependencies = true;
0054     }
0055 
0056     Akonadi::AttributeFactory::registerAttribute<Akonadi::ApplicationSelectedAttribute>();
0057     Akonadi::AttributeFactory::registerAttribute<Akonadi::TimestampAttribute>();
0058 
0059     const auto xmlFile = QString::fromLocal8Bit(ZANSHIN_USER_XMLDATA);
0060     if (xmlFile.isEmpty()) {
0061         qDebug() << "FATAL ERROR! ZANSHIN_USER_XMLDATA WAS NOT PROVIDED\n\n";
0062         exit(1);
0063     }
0064 
0065     auto searchCollection = Akonadi::Collection(1);
0066     searchCollection.setParentCollection(Akonadi::Collection::root());
0067     searchCollection.setName(QStringLiteral("Search"));
0068     m_data.createCollection(searchCollection);
0069 
0070     auto loader = Testlib::AkonadiFakeDataXmlLoader(&m_data);
0071     loader.load(xmlFile);
0072 
0073     // Sanity checks
0074     QVERIFY(m_data.collections().size() > 1);
0075     QVERIFY(m_data.items().size() > 1);
0076     QVERIFY(m_data.contexts().size() > 1);
0077 
0078     // Swap regular dependencies for the fake data ones
0079     auto &deps = Utils::DependencyManager::globalInstance();
0080     deps.add<Akonadi::MonitorInterface,
0081             Utils::DependencyManager::UniqueInstance>(
0082                 [this] (Utils::DependencyManager *) {
0083         return m_data.createMonitor();
0084     }
0085     );
0086     deps.add<Akonadi::StorageInterface,
0087             Utils::DependencyManager::UniqueInstance>(
0088                 [this] (Utils::DependencyManager *deps) {
0089         return new Akonadi::CachingStorage(deps->create<Akonadi::Cache>(),
0090                                            Akonadi::StorageInterface::Ptr(m_data.createStorage()));
0091     }
0092     );
0093 
0094     using namespace Presentation;
0095     m_proxyModel->setDynamicSortFilter(true);
0096 
0097     auto appModel = ApplicationModel::Ptr::create();
0098 
0099     appModel->setErrorHandler(&m_errorHandler);
0100 
0101     m_appModel = appModel;
0102 
0103     auto monitor = Utils::DependencyManager::globalInstance().create<Akonadi::MonitorInterface>();
0104     m_monitorSpy = new MonitorSpy(monitor.data(), this);
0105 }
0106 
0107 // Note that setModel might invalidate the 'index' member variable, due to proxyModel->setSourceModel.
0108 void ZanshinContext::setModel(QAbstractItemModel *model)
0109 {
0110     if (m_sourceModel == model)
0111         return;
0112     m_sourceModel = model;
0113     if (!qobject_cast<QSortFilterProxyModel *>(model)) {
0114         m_proxyModel->setObjectName(QLatin1StringView("m_proxyModel_in_ZanshinContext"));
0115         m_proxyModel->setSourceModel(model);
0116         m_proxyModel->setSortRole(Qt::DisplayRole);
0117         m_proxyModel->sort(0);
0118         m_model = m_proxyModel;
0119     } else {
0120         m_model = model;
0121     }
0122 }
0123 
0124 QAbstractItemModel *ZanshinContext::sourceModel() const
0125 {
0126     return m_sourceModel;
0127 }
0128 
0129 QAbstractItemModel *ZanshinContext::model() const
0130 {
0131     return m_model;
0132 }
0133 
0134 Domain::Task::Ptr ZanshinContext::currentTask() const
0135 {
0136     return m_index.data(Presentation::QueryTreeModelBase::ObjectRole)
0137             .value<Domain::Task::Ptr>();
0138 }
0139 
0140 void ZanshinContext::waitForEmptyJobQueue()
0141 {
0142     while (Utils::JobHandler::jobCount() != 0) {
0143         QTest::qWait(20);
0144     }
0145 }
0146 
0147 void ZanshinContext::waitForStableState()
0148 {
0149     waitForEmptyJobQueue();
0150     m_monitorSpy->waitForStableState();
0151 }
0152 
0153 void ZanshinContext::collectIndicesImpl(const QModelIndex &root)
0154 {
0155     QAbstractItemModel *model = m_model;
0156     for (int row = 0; row < model->rowCount(root); row++) {
0157         const QModelIndex index = model->index(row, 0, root);
0158         m_indices << index;
0159         if (model->rowCount(index) > 0)
0160             collectIndicesImpl(index);
0161     }
0162 }
0163 
0164 void ZanshinContext::collectIndices()
0165 {
0166     m_indices.clear();
0167     collectIndicesImpl();
0168 }
0169 
0170 namespace Zanshin {
0171 
0172 QString indexString(const QModelIndex &index, int role = Qt::DisplayRole)
0173 {
0174     if (role != Qt::DisplayRole)
0175         return index.data(role).toString();
0176 
0177     QString data = index.data(role).toString();
0178 
0179     if (index.parent().isValid())
0180         return indexString(index.parent(), role) + " / " + data;
0181     else
0182         return data;
0183 }
0184 
0185 QModelIndex findIndex(QAbstractItemModel *model,
0186                       const QString &string,
0187                       int role = Qt::DisplayRole,
0188                       const QModelIndex &root = QModelIndex())
0189 {
0190     for (int row = 0; row < model->rowCount(root); row++) {
0191         const QModelIndex index = model->index(row, 0, root);
0192         if (indexString(index, role) == string)
0193             return index;
0194 
0195         if (model->rowCount(index) > 0) {
0196             const QModelIndex found = findIndex(model, string, role, index);
0197             if (found.isValid())
0198                 return found;
0199         }
0200     }
0201 
0202     return QModelIndex();
0203 }
0204 
0205 void dumpIndices(const QList<QPersistentModelIndex> &indices)
0206 {
0207     qDebug() << "Dumping list of size:" << indices.size();
0208     for (int row = 0; row < indices.size(); row++) {
0209         qDebug() << row << indexString(indices.at(row));
0210     }
0211 }
0212 
0213 inline bool verify(bool statement, const char *str,
0214                    const char *file, int line)
0215 {
0216     if (statement)
0217         return true;
0218 
0219     qDebug() << "Statement" << str << "returned FALSE";
0220     qDebug() << "Loc:" << file << line;
0221     return false;
0222 }
0223 
0224 template <typename T>
0225 inline bool compare(T const &t1, T const &t2,
0226                     const char *actual, const char *expected,
0227                     const char *file, int line)
0228 {
0229     if (t1 == t2)
0230         return true;
0231 
0232     qDebug() << "Compared values are not the same";
0233     qDebug() << "Actual (" << actual << ") :" << QTest::toString<T>(t1);
0234     qDebug() << "Expected (" << expected << ") :" << QTest::toString<T>(t2);
0235     qDebug() << "Loc:" << file << line;
0236     return false;
0237 }
0238 
0239 } // namespace Zanshin
0240 
0241 #define COMPARE(actual, expected) \
0242 do {\
0243     if (!Zanshin::compare(actual, expected, #actual, #expected, __FILE__, __LINE__))\
0244         return false;\
0245 } while (0)
0246 
0247 // Note: you should make sure that m_indices is filled in before calling this,
0248 // e.g. calling Zanshin::collectIndices(context.get()) if not already done.
0249 #define COMPARE_OR_DUMP(actual, expected) \
0250 do {\
0251     if (!Zanshin::compare(actual, expected, #actual, #expected, __FILE__, __LINE__)) {\
0252         Zanshin::dumpIndices(m_indices); \
0253         return false;\
0254     }\
0255 } while (0)
0256 
0257 #define VERIFY(statement) \
0258 do {\
0259     if (!Zanshin::verify((statement), #statement, __FILE__, __LINE__))\
0260         return false;\
0261 } while (0)
0262 
0263 // Note: you should make sure that m_indices is filled in before calling this,
0264 // e.g. calling Zanshin::collectIndices(context.get()) if not already done.
0265 #define VERIFY_OR_DUMP(statement) \
0266 do {\
0267     if (!Zanshin::verify((statement), #statement, __FILE__, __LINE__)) {\
0268         Zanshin::dumpIndices(m_indices); \
0269         return false;\
0270     }\
0271 } while (0)
0272 
0273 #define VERIFY_OR_DO(statement, whatToDo) \
0274 do {\
0275     if (!Zanshin::verify((statement), #statement, __FILE__, __LINE__)) {\
0276         whatToDo; \
0277         return false;\
0278     }\
0279 } while (0)
0280 
0281 
0282 bool ZanshinContext::I_display_the_available_data_sources()
0283 {
0284     auto availableSources = m_appModel->property("availableSources").value<QObject*>();
0285     VERIFY(availableSources);
0286 
0287     auto sourceListModel = availableSources->property("sourceListModel").value<QAbstractItemModel*>();
0288     VERIFY(sourceListModel);
0289 
0290     m_presentation = availableSources;
0291     setModel(sourceListModel);
0292 
0293     return true;
0294 }
0295 
0296 bool ZanshinContext::I_display_the_available_pages()
0297 {
0298     m_presentation = m_appModel->property("availablePages").value<QObject*>();
0299     setModel(m_presentation->property("pageListModel").value<QAbstractItemModel*>());
0300     return true;
0301 }
0302 
0303 bool ZanshinContext::I_display_the_page(const QString &pageName)
0304 {
0305     if (m_editor) {
0306         // save pending changes
0307         VERIFY(m_editor->setProperty("task", QVariant::fromValue(Domain::Task::Ptr())));
0308     }
0309     auto availablePages = m_appModel->property("availablePages").value<QObject*>();
0310     VERIFY(availablePages);
0311 
0312     auto pageListModel = availablePages->property("pageListModel").value<QAbstractItemModel*>();
0313     VERIFY(pageListModel);
0314     waitForEmptyJobQueue();
0315 
0316     QModelIndex pageIndex = Zanshin::findIndex(pageListModel, pageName);
0317     VERIFY_OR_DUMP(pageIndex.isValid());
0318 
0319     QObject *page = nullptr;
0320     QMetaObject::invokeMethod(availablePages, "createPageForIndex",
0321                               Q_RETURN_ARG(QObject*, page),
0322                               Q_ARG(QModelIndex, pageIndex));
0323     VERIFY(page);
0324 
0325     VERIFY(m_appModel->setProperty("currentPage", QVariant::fromValue(page)));
0326     m_presentation = m_appModel->property("currentPage").value<QObject*>();
0327 
0328     return true;
0329 }
0330 
0331 bool ZanshinContext::there_is_an_item_in_the_central_list(const QString &taskName)
0332 {
0333     auto m = m_presentation->property("centralListModel").value<QAbstractItemModel*>();
0334     setModel(m);
0335     waitForEmptyJobQueue();
0336 
0337     collectIndices();
0338     m_index = Zanshin::findIndex(model(), taskName);
0339     VERIFY_OR_DUMP(m_index.isValid());
0340 
0341     return true;
0342 }
0343 
0344 bool ZanshinContext::there_is_an_item_in_the_available_data_sources(const QString &sourceName)
0345 {
0346     auto availableSources = m_appModel->property("availableSources").value<QObject*>();
0347     VERIFY(availableSources);
0348     auto m = availableSources->property("sourceListModel").value<QAbstractItemModel*>();
0349     VERIFY(m);
0350     waitForEmptyJobQueue();
0351     setModel(m);
0352 
0353     collectIndices();
0354     m_index = Zanshin::findIndex(model(), sourceName);
0355     VERIFY_OR_DUMP(m_index.isValid());
0356 
0357     return true;
0358 }
0359 
0360 bool ZanshinContext::the_central_list_contains_items_named(const QStringList &taskNames)
0361 {
0362     m_dragIndices.clear();
0363 
0364     auto m = m_presentation->property("centralListModel").value<QAbstractItemModel*>();
0365     waitForEmptyJobQueue();
0366     setModel(m);
0367 
0368     for (const auto &taskName : taskNames) {
0369         QModelIndex index = Zanshin::findIndex(model(), taskName);
0370         VERIFY_OR_DO(index.isValid(), Zanshin::dumpIndices(m_dragIndices));
0371         m_dragIndices << index;
0372     }
0373 
0374     return true;
0375 }
0376 
0377 bool ZanshinContext::I_look_at_the_central_list()
0378 {
0379     auto m = m_presentation->property("centralListModel").value<QAbstractItemModel*>();
0380     setModel(m);
0381     waitForStableState();
0382     return true;
0383 }
0384 
0385 bool ZanshinContext::I_check_the_item()
0386 {
0387     VERIFY(model()->setData(m_index, Qt::Checked, Qt::CheckStateRole));
0388     waitForStableState();
0389     return true;
0390 }
0391 
0392 bool ZanshinContext::I_uncheck_the_item()
0393 {
0394     VERIFY(model()->setData(m_index, Qt::Unchecked, Qt::CheckStateRole));
0395     waitForStableState();
0396     return true;
0397 }
0398 
0399 bool ZanshinContext::I_remove_the_item()
0400 {
0401     VERIFY(QMetaObject::invokeMethod(m_presentation, "removeItem", Q_ARG(QModelIndex, m_index)));
0402     waitForStableState();
0403     return true;
0404 }
0405 
0406 bool ZanshinContext::I_promote_the_item()
0407 {
0408     VERIFY(QMetaObject::invokeMethod(m_presentation, "promoteItem", Q_ARG(QModelIndex, m_index)));
0409     waitForStableState();
0410 
0411     return true;
0412 }
0413 
0414 bool ZanshinContext::I_add_a_project(const QString &projectName, const QString &parentSourceName)
0415 {
0416     auto source = dataSourceFromName(parentSourceName);
0417     VERIFY(!source.isNull());
0418 
0419     VERIFY(QMetaObject::invokeMethod(m_presentation, "addProject",
0420                                      Q_ARG(QString, projectName),
0421                                      Q_ARG(Domain::DataSource::Ptr, source)));
0422     waitForStableState();
0423 
0424     return true;
0425 }
0426 
0427 bool ZanshinContext::I_add_a_context(const QString &contextName, const QString &parentSourceName)
0428 {
0429     auto source = dataSourceFromName(parentSourceName);
0430     VERIFY(!source.isNull());
0431 
0432     VERIFY(QMetaObject::invokeMethod(m_presentation,
0433                                      "addContext",
0434                                      Q_ARG(QString, contextName),
0435                                      Q_ARG(Domain::DataSource::Ptr, source)));
0436     waitForStableState();
0437 
0438     return true;
0439 
0440 }
0441 
0442 bool ZanshinContext::I_add_a_task(const QString &taskName)
0443 {
0444     waitForStableState();
0445 
0446     VERIFY(QMetaObject::invokeMethod(m_presentation,
0447                                      "addItem",
0448                                      Q_ARG(QString, taskName)));
0449     waitForStableState();
0450 
0451     return true;
0452 }
0453 
0454 bool ZanshinContext::I_rename_a_page(const QString &path, const QString &oldName, const QString &newName)
0455 {
0456     const QString pageNodeName = path + " / ";
0457 
0458     VERIFY(!pageNodeName.isEmpty());
0459 
0460     auto availablePages = m_appModel->property("availablePages").value<QObject*>();
0461     VERIFY(availablePages);
0462 
0463     auto pageListModel = availablePages->property("pageListModel").value<QAbstractItemModel*>();
0464     VERIFY(pageListModel);
0465     waitForStableState();
0466 
0467     QModelIndex pageIndex = Zanshin::findIndex(pageListModel, pageNodeName + oldName);
0468     VERIFY(pageIndex.isValid());
0469 
0470     pageListModel->setData(pageIndex, newName);
0471     waitForStableState();
0472 
0473     return true;
0474 }
0475 
0476 bool ZanshinContext::I_remove_a_page(const QString &path, const QString &pageName)
0477 {
0478     const QString pageNodeName = path + " / ";
0479 
0480     VERIFY(!pageNodeName.isEmpty());
0481 
0482     auto availablePages = m_appModel->property("availablePages").value<QObject*>();
0483     VERIFY(availablePages);
0484 
0485     auto pageListModel = availablePages->property("pageListModel").value<QAbstractItemModel*>();
0486     VERIFY(pageListModel);
0487     waitForStableState();
0488 
0489     QModelIndex pageIndex = Zanshin::findIndex(pageListModel, pageNodeName + pageName);
0490     VERIFY(pageIndex.isValid());
0491 
0492     VERIFY(QMetaObject::invokeMethod(availablePages, "removeItem",
0493                                      Q_ARG(QModelIndex, pageIndex)));
0494     waitForStableState();
0495 
0496     return true;
0497 }
0498 
0499 bool ZanshinContext::I_add_a_task_child(const QString &childName, const QString &parentName)
0500 {
0501     waitForStableState();
0502 
0503     auto parentIndex = QModelIndex();
0504     for (int row = 0; row < m_indices.size(); row++) {
0505         auto index = m_indices.at(row);
0506         if (Zanshin::indexString(index) == parentName) {
0507             parentIndex = index;
0508             break;
0509         }
0510     }
0511 
0512     VERIFY_OR_DUMP(parentIndex.isValid());
0513 
0514     VERIFY(QMetaObject::invokeMethod(m_presentation,
0515                                      "addItem",
0516                                      Q_ARG(QString, childName),
0517                                      Q_ARG(QModelIndex, parentIndex)));
0518     waitForStableState();
0519 
0520     return true;
0521 }
0522 
0523 bool ZanshinContext::I_list_the_items()
0524 {
0525     waitForStableState();
0526     collectIndices();
0527     waitForStableState();
0528 
0529     return true;
0530 }
0531 
0532 bool ZanshinContext::I_open_the_item_in_the_editor()
0533 {
0534     auto task = currentTask();
0535     VERIFY(!task.isNull());
0536     m_editor = m_appModel->property("editor").value<QObject*>();
0537     VERIFY(m_editor);
0538     VERIFY(m_editor->setProperty("task", QVariant::fromValue(task)));
0539 
0540     return true;
0541 }
0542 
0543 bool ZanshinContext::I_mark_the_item_done_in_the_editor()
0544 {
0545     VERIFY(m_editor->setProperty("done", true));
0546     return true;
0547 }
0548 
0549 bool ZanshinContext::I_change_the_editor_field(const QString &field, const QVariant &value)
0550 {
0551     const QByteArray property = (field == QStringLiteral("text")) ? field.toUtf8()
0552                               : (field == QStringLiteral("title")) ? field.toUtf8()
0553                               : (field == QStringLiteral("start date")) ? "startDate"
0554                               : (field == QStringLiteral("due date")) ? "dueDate"
0555                               : QByteArray();
0556 
0557     VERIFY(value.isValid());
0558     VERIFY(!property.isEmpty());
0559 
0560     VERIFY(m_editor->setProperty("editingInProgress", true));
0561     VERIFY(m_editor->setProperty(property, value));
0562 
0563     return true;
0564 }
0565 
0566 bool ZanshinContext::I_rename_the_item(const QString &taskName)
0567 {
0568     VERIFY(m_editor->setProperty("editingInProgress", false));
0569     VERIFY(model()->setData(m_index, taskName, Qt::EditRole));
0570     waitForStableState();
0571 
0572     return true;
0573 }
0574 
0575 bool ZanshinContext::I_open_the_item_in_the_editor_again()
0576 {
0577     auto task = currentTask();
0578     VERIFY(!task.isNull());
0579     VERIFY(m_editor->setProperty("task", QVariant::fromValue(Domain::Task::Ptr())));
0580     VERIFY(m_editor->setProperty("task", QVariant::fromValue(task)));
0581     waitForStableState();
0582 
0583     return true;
0584 }
0585 
0586 bool ZanshinContext::I_drop_the_item_on_the_central_list(const QString &dropSiteName)
0587 {
0588     VERIFY(m_index.isValid());
0589     const QMimeData *data = model()->mimeData(QModelIndexList() << m_index);
0590 
0591     QAbstractItemModel *destModel = model();
0592     QModelIndex dropIndex = Zanshin::findIndex(destModel, dropSiteName);
0593     VERIFY(dropIndex.isValid());
0594     VERIFY(destModel->dropMimeData(data, Qt::MoveAction, -1, -1, dropIndex));
0595     waitForStableState();
0596 
0597     return true;
0598 }
0599 
0600 bool ZanshinContext::I_drop_the_item_on_the_blank_area_of_the_central_list()
0601 {
0602     VERIFY(m_index.isValid());
0603     const QMimeData *data = model()->mimeData(QModelIndexList() << m_index);
0604 
0605     QAbstractItemModel *destModel = model();
0606     VERIFY(destModel->dropMimeData(data, Qt::MoveAction, -1, -1, QModelIndex()));
0607     waitForStableState();
0608 
0609     return true;
0610 }
0611 
0612 bool ZanshinContext::I_drop_items_on_the_central_list(const QString &dropSiteName)
0613 {
0614     VERIFY(!m_dragIndices.isEmpty());
0615     QModelIndexList indexes;
0616     bool allValid = true;
0617     std::transform(m_dragIndices.constBegin(), m_dragIndices.constEnd(),
0618                    std::back_inserter(indexes),
0619                    [&allValid] (const QPersistentModelIndex &index) {
0620                         allValid &= index.isValid();
0621                         return index;
0622                    });
0623     VERIFY(allValid);
0624 
0625     const QMimeData *data = model()->mimeData(indexes);
0626 
0627     QAbstractItemModel *destModel = model();
0628     QModelIndex dropIndex = Zanshin::findIndex(destModel, dropSiteName);
0629     VERIFY(dropIndex.isValid());
0630     VERIFY(destModel->dropMimeData(data, Qt::MoveAction, -1, -1, dropIndex));
0631     waitForStableState();
0632 
0633     return true;
0634 }
0635 
0636 bool ZanshinContext::I_drop_the_item_on_the_page_list(const QString &pageName)
0637 {
0638     VERIFY(m_index.isValid());
0639     const QMimeData *data = model()->mimeData(QModelIndexList() << m_index);
0640 
0641     auto availablePages = m_appModel->property("availablePages").value<QObject*>();
0642     VERIFY(availablePages);
0643 
0644     auto destModel = availablePages->property("pageListModel").value<QAbstractItemModel*>();
0645     VERIFY(destModel);
0646     waitForStableState();
0647 
0648     QModelIndex dropIndex = Zanshin::findIndex(destModel, pageName);
0649     VERIFY(dropIndex.isValid());
0650     VERIFY(destModel->dropMimeData(data, Qt::MoveAction, -1, -1, dropIndex));
0651     waitForStableState();
0652 
0653     return true;
0654 }
0655 
0656 bool ZanshinContext::I_drop_items_on_the_page_list(const QString &pageName)
0657 {
0658     VERIFY(!m_dragIndices.isEmpty());
0659     QModelIndexList indexes;
0660     bool allValid = true;
0661     std::transform(m_dragIndices.constBegin(), m_dragIndices.constEnd(),
0662                    std::back_inserter(indexes),
0663                    [&allValid] (const QPersistentModelIndex &index) {
0664                         allValid &= index.isValid();
0665                         return index;
0666                    });
0667     VERIFY(allValid);
0668 
0669     const QMimeData *data = model()->mimeData(indexes);
0670 
0671     auto availablePages = m_appModel->property("availablePages").value<QObject*>();
0672     VERIFY(availablePages);
0673 
0674     auto destModel = availablePages->property("pageListModel").value<QAbstractItemModel*>();
0675     VERIFY(destModel);
0676     waitForStableState();
0677 
0678     QModelIndex dropIndex = Zanshin::findIndex(destModel, pageName);
0679     VERIFY(dropIndex.isValid());
0680     VERIFY(destModel->dropMimeData(data, Qt::MoveAction, -1, -1, dropIndex));
0681     waitForStableState();
0682 
0683     return true;
0684 }
0685 
0686 bool ZanshinContext::I_change_the_setting(const QString &key, qint64 id)
0687 {
0688     KConfigGroup config(KSharedConfig::openConfig(), "General");
0689     config.writeEntry(key, id);
0690     return true;
0691 }
0692 
0693 bool ZanshinContext::I_change_the_default_data_source(const QString &sourceName)
0694 {
0695     waitForStableState();
0696     auto sourceIndex = Zanshin::findIndex(model(), sourceName);
0697     auto availableSources = m_appModel->property("availableSources").value<QObject*>();
0698     VERIFY(availableSources);
0699     VERIFY(QMetaObject::invokeMethod(availableSources, "setDefaultItem", Q_ARG(QModelIndex, sourceIndex)));
0700     waitForStableState();
0701 
0702     return true;
0703 }
0704 
0705 bool ZanshinContext::the_list_is(const TableData &data)
0706 {
0707     auto roleNames = model()->roleNames();
0708     QList<int> usedRoles;
0709 
0710     for (const auto &roleName : data.roles) {
0711         const int role = roleNames.key(roleName, -1);
0712         VERIFY_OR_DUMP(role != -1 && !usedRoles.contains(role));
0713         usedRoles << role;
0714     }
0715 
0716     QStandardItemModel inputModel;
0717     for (const auto &row : data.rows) {
0718         VERIFY_OR_DUMP(usedRoles.size() == row.size());
0719 
0720         QStandardItem *item = new QStandardItem;
0721         for (int i = 0; i < row.size(); ++i) {
0722             const auto role = usedRoles.at(i);
0723             const auto value = row.at(i);
0724             item->setData(value, role);
0725         }
0726         inputModel.appendRow(item);
0727     }
0728 
0729     QSortFilterProxyModel proxy;
0730 
0731     QAbstractItemModel *referenceModel;
0732     if (!qobject_cast<QSortFilterProxyModel *>(sourceModel())) {
0733         referenceModel = &proxy;
0734         proxy.setSourceModel(&inputModel);
0735         proxy.setSortRole(Qt::DisplayRole);
0736         proxy.sort(0);
0737         proxy.setObjectName(QLatin1StringView("the_list_is_proxy"));
0738     } else {
0739         referenceModel = &inputModel;
0740     }
0741 
0742     for (int row = 0; row < m_indices.size(); row++) {
0743         QModelIndex expectedIndex = referenceModel->index(row, 0);
0744         QModelIndex resultIndex = m_indices.at(row);
0745 
0746         foreach (const auto &role, usedRoles) {
0747             COMPARE_OR_DUMP(Zanshin::indexString(resultIndex, role),
0748                             Zanshin::indexString(expectedIndex, role));
0749         }
0750     }
0751     COMPARE_OR_DUMP((int)m_indices.size(), referenceModel->rowCount());
0752 
0753     return true;
0754 }
0755 
0756 bool ZanshinContext::the_list_contains(const QString &itemName)
0757 {
0758     for (int row = 0; row < m_indices.size(); row++) {
0759         if (Zanshin::indexString(m_indices.at(row)) == itemName)
0760             return true;
0761     }
0762 
0763     VERIFY_OR_DUMP(false);
0764     return false;
0765 }
0766 
0767 bool ZanshinContext::the_list_does_not_contain(const QString &itemName)
0768 {
0769     for (int row = 0; row < m_indices.size(); row++) {
0770         VERIFY_OR_DUMP(Zanshin::indexString(m_indices.at(row)) != itemName);
0771     }
0772 
0773     return true;
0774 }
0775 
0776 bool ZanshinContext::the_task_corresponding_to_the_item_is_done()
0777 {
0778     auto task = currentTask();
0779     VERIFY(!task.isNull());
0780     VERIFY(task->isDone());
0781 
0782     return true;
0783 }
0784 
0785 bool ZanshinContext::the_editor_shows_the_task_as_done()
0786 {
0787     VERIFY(m_editor->property("done").toBool());
0788     return true;
0789 }
0790 
0791 bool ZanshinContext::the_editor_shows_the_field(const QString &field, const QVariant &expectedValue)
0792 {
0793     const QByteArray property = (field == QStringLiteral("text")) ? field.toUtf8()
0794                               : (field == QStringLiteral("title")) ? field.toUtf8()
0795                               : (field == QStringLiteral("start date")) ? "startDate"
0796                               : (field == QStringLiteral("due date")) ? "dueDate"
0797                               : QByteArray();
0798 
0799     VERIFY(expectedValue.isValid());
0800     VERIFY(!property.isEmpty());
0801 
0802     COMPARE(m_editor->property(property), expectedValue);
0803 
0804     return true;
0805 }
0806 
0807 bool ZanshinContext::the_default_data_source_is(const QString &expectedName)
0808 {
0809     waitForStableState();
0810     auto expectedIndex = Zanshin::findIndex(model(), expectedName);
0811     VERIFY(expectedIndex.isValid());
0812     auto defaultRole = model()->roleNames().key("default", -1);
0813     VERIFY(expectedIndex.data(defaultRole).toBool());
0814 
0815     return true;
0816 }
0817 
0818 bool ZanshinContext::the_setting_is(const QString &key, qint64 expectedId)
0819 {
0820     KConfigGroup config(KSharedConfig::openConfig(), "General");
0821     const qint64 id = config.readEntry(key, -1);
0822     COMPARE(id, expectedId);
0823 
0824     return true;
0825 }
0826 
0827 Domain::DataSource::Ptr ZanshinContext::dataSourceFromName(const QString &sourceName)
0828 {
0829     auto availableSources = m_appModel->property("availableSources").value<QObject*>();
0830     if (!availableSources)
0831         return nullptr;
0832     auto sourceList = availableSources->property("sourceListModel").value<QAbstractItemModel*>();
0833     if (!sourceList)
0834         return nullptr;
0835     waitForStableState();
0836     QModelIndex index = Zanshin::findIndex(sourceList, sourceName);
0837     if (!index.isValid()) {
0838         qWarning() << "source" << sourceName << "not found.";
0839         for (int row = 0; row < sourceList->rowCount(); row++) {
0840             qDebug() << sourceList->index(row, 0).data().toString();
0841         }
0842         return nullptr;
0843     }
0844     return index.data(Presentation::QueryTreeModelBase::ObjectRole)
0845                 .value<Domain::DataSource::Ptr>();
0846 }
0847 
0848 #include "moc_zanshincontext.cpp"