File indexing completed on 2025-01-05 05:00:00

0001 /*
0002  * SPDX-FileCopyrightText: 2014 Mario Bensi <mbensi@ipsquad.net>
0003    SPDX-FileCopyrightText: 2014 Kevin Ottens <ervin@kde.org>
0004  * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005  */
0006 
0007 
0008 #include <testlib/qtest_zanshin.h>
0009 
0010 #include <memory>
0011 
0012 #include <QAbstractItemModelTester>
0013 #include <QColor>
0014 #include <QMimeData>
0015 
0016 #include "utils/mockobject.h"
0017 
0018 #include "domain/taskqueries.h"
0019 #include "domain/taskrepository.h"
0020 #include "presentation/querytreemodel.h"
0021 
0022 using namespace mockitopp;
0023 
0024 Q_DECLARE_METATYPE(QModelIndex)
0025 Q_DECLARE_METATYPE(QList<QColor>)
0026 
0027 class QueryTreeModelTest : public QObject
0028 {
0029     Q_OBJECT
0030 public:
0031     explicit QueryTreeModelTest(QObject *parent = nullptr)
0032         : QObject(parent)
0033     {
0034         qRegisterMetaType<QModelIndex>();
0035     }
0036 
0037 private:
0038     Domain::Task::List createTasks() const
0039     {
0040         Domain::Task::List result;
0041 
0042         const QStringList titles = {"first", "second", "third"};
0043         const QList<bool> doneStates = {true, false, false};
0044         Q_ASSERT(titles.size() == doneStates.size());
0045 
0046         result.reserve(titles.size());
0047         for (int i = 0; i < titles.size(); i++) {
0048             auto task = Domain::Task::Ptr::create();
0049             task->setTitle(titles.at(i));
0050             task->setDone(doneStates.at(i));
0051             result << task;
0052         }
0053 
0054         return result;
0055     }
0056 
0057     Domain::Task::List createChildrenTasks() const
0058     {
0059         Domain::Task::List result;
0060 
0061         const QStringList titles = {"childFirst", "childSecond", "childThird"};
0062         const QList<bool> doneStates = {true, false, false};
0063         Q_ASSERT(titles.size() == doneStates.size());
0064 
0065         result.reserve(titles.size());
0066         for (int i = 0; i < titles.size(); i++) {
0067             auto task = Domain::Task::Ptr::create();
0068             task->setTitle(titles.at(i));
0069             task->setDone(doneStates.at(i));
0070             result << task;
0071         }
0072 
0073         return result;
0074     }
0075 
0076     static QVariant standardDataFunction(const Domain::Task::Ptr &task, int role, int)
0077     {
0078         if (role != Qt::DisplayRole && role != Qt::CheckStateRole) {
0079             return QVariant();
0080         }
0081 
0082         if (role == Qt::DisplayRole)
0083             return task->title();
0084         else
0085             return task->isDone() ? Qt::Checked : Qt::Unchecked;
0086     }
0087 
0088 private slots:
0089     void shouldHaveRoleNames()
0090     {
0091         // GIVEN
0092         auto queryGenerator = [](const QColor &) {
0093             return Domain::QueryResult<QColor>::Ptr();
0094         };
0095         auto flagsFunction = [](const QColor &) {
0096             return Qt::NoItemFlags;
0097         };
0098         auto dataFunction = [](const QColor &, int, int) {
0099             return QVariant();
0100         };
0101         auto setDataFunction = [](const QColor &, const QVariant &, int) {
0102             return false;
0103         };
0104         Presentation::QueryTreeModel<QColor> model(queryGenerator, flagsFunction, dataFunction, setDataFunction);
0105 
0106         // WHEN
0107         auto roles = model.roleNames();
0108 
0109         // THEN
0110         QCOMPARE(roles.value(Qt::DisplayRole), QByteArray("display"));
0111         QCOMPARE(roles.value(Presentation::QueryTreeModelBase::ObjectRole), QByteArray("object"));
0112         QCOMPARE(roles.value(Presentation::QueryTreeModelBase::IconNameRole), QByteArray("icon"));
0113         QCOMPARE(roles.value(Presentation::QueryTreeModelBase::IsDefaultRole), QByteArray("default"));
0114     }
0115 
0116     void shouldListTasks()
0117     {
0118         // GIVEN
0119         auto tasks = createTasks();
0120         auto provider = Domain::QueryResultProvider<Domain::Task::Ptr>::Ptr::create();
0121         foreach (const auto &task, tasks)
0122             provider->append(task);
0123 
0124         auto childrenTasks = createChildrenTasks();
0125         auto childrenProvider = Domain::QueryResultProvider<Domain::Task::Ptr>::Ptr::create();
0126         foreach (const auto &task, childrenTasks)
0127             childrenProvider->append(task);
0128 
0129         auto childrenList = Domain::QueryResult<Domain::Task::Ptr>::create(childrenProvider);
0130         auto emptyProvider = Domain::QueryResultProvider<Domain::Task::Ptr>::Ptr::create();
0131         auto emptyList = Domain::QueryResult<Domain::Task::Ptr>::create(emptyProvider);
0132 
0133         Utils::MockObject<Domain::TaskQueries> queryMock;
0134         queryMock(&Domain::TaskQueries::findChildren).when(tasks.at(0)).thenReturn(childrenList);
0135         queryMock(&Domain::TaskQueries::findChildren).when(tasks.at(1)).thenReturn(emptyList);
0136         queryMock(&Domain::TaskQueries::findChildren).when(tasks.at(2)).thenReturn(emptyList);
0137         queryMock(&Domain::TaskQueries::findChildren).when(childrenTasks.at(0)).thenReturn(emptyList);
0138         queryMock(&Domain::TaskQueries::findChildren).when(childrenTasks.at(1)).thenReturn(emptyList);
0139         queryMock(&Domain::TaskQueries::findChildren).when(childrenTasks.at(2)).thenReturn(emptyList);
0140 
0141         // WHEN
0142         auto queryGenerator = [&](const Domain::Task::Ptr &task) {
0143             if (!task)
0144                 return Domain::QueryResult<Domain::Task::Ptr>::create(provider);
0145             else
0146                 return queryMock.getInstance()->findChildren(task);
0147         };
0148         auto flagsFunction = [](const Domain::Task::Ptr &) {
0149             return Qt::ItemIsSelectable
0150                  | Qt::ItemIsEnabled
0151                  | Qt::ItemIsEditable
0152                  | Qt::ItemIsUserCheckable;
0153         };
0154         auto setDataFunction = [](const Domain::Task::Ptr &, const QVariant &, int) {
0155             return false;
0156         };
0157         Presentation::QueryTreeModel<Domain::Task::Ptr> model(queryGenerator, flagsFunction, standardDataFunction, setDataFunction, nullptr);
0158         new QAbstractItemModelTester(&model, this);
0159 
0160         // THEN
0161         QCOMPARE(model.rowCount(), 3);
0162         QCOMPARE(model.rowCount(model.index(0, 0)), 3);
0163         QCOMPARE(model.rowCount(model.index(1, 0)), 0);
0164         QCOMPARE(model.rowCount(model.index(2, 0)), 0);
0165         QCOMPARE(model.rowCount(model.index(0, 0, model.index(0, 0))), 0);
0166         QCOMPARE(model.rowCount(model.index(1, 0, model.index(0, 0))), 0);
0167         QCOMPARE(model.rowCount(model.index(2, 0, model.index(0, 0))), 0);
0168         QCOMPARE(model.rowCount(model.index(3, 0, model.index(0, 0))), 3);
0169 
0170         for (int i = 0; i < tasks.size(); i++) {
0171             auto task = tasks.at(i);
0172             auto index = model.index(i, 0);
0173 
0174             QCOMPARE(model.data(index), model.data(index, Qt::DisplayRole));
0175             QCOMPARE(model.data(index).toString(), task->title());
0176             QCOMPARE(model.data(index, Qt::CheckStateRole).toInt() == Qt::Checked, task->isDone());
0177             QCOMPARE(model.data(index, Qt::CheckStateRole).toInt() == Qt::Unchecked, !task->isDone());
0178         }
0179 
0180         for (int i = 0; i < childrenTasks.size(); i++) {
0181             auto task = childrenTasks.at(i);
0182             auto index = model.index(i, 0, model.index(0, 0));
0183 
0184             QCOMPARE(model.data(index), model.data(index, Qt::DisplayRole));
0185             QCOMPARE(model.data(index).toString(), task->title());
0186             QCOMPARE(model.data(index, Qt::CheckStateRole).toInt() == Qt::Checked, task->isDone());
0187             QCOMPARE(model.data(index, Qt::CheckStateRole).toInt() == Qt::Unchecked, !task->isDone());
0188         }
0189     }
0190 
0191     void shouldDealWithNullQueriesProperly()
0192     {
0193         // GIVEN
0194         auto queryGenerator = [](const QString &) {
0195             return Domain::QueryResult<QString>::Ptr();
0196         };
0197         auto flagsFunction = [](const QString &) {
0198             return Qt::NoItemFlags;
0199         };
0200         auto dataFunction = [](const QString &, int, int) {
0201             return QVariant();
0202         };
0203         auto setDataFunction = [](const QString &, const QVariant &, int) {
0204             return false;
0205         };
0206 
0207         // WHEN
0208         Presentation::QueryTreeModel<QString> model(queryGenerator, flagsFunction, dataFunction, setDataFunction, nullptr);
0209         new QAbstractItemModelTester(&model, this);
0210 
0211         // THEN
0212         QCOMPARE(model.rowCount(), 0);
0213     }
0214 
0215     void shouldReactToTaskAdd()
0216     {
0217         // GIVEN
0218         auto tasks = createTasks();
0219         auto  provider = Domain::QueryResultProvider<Domain::Task::Ptr>::Ptr::create();
0220         provider->append(tasks.at(1));
0221         provider->append(tasks.at(2));
0222 
0223         auto childrenTasks = createChildrenTasks();
0224         auto childrenProvider = Domain::QueryResultProvider<Domain::Task::Ptr>::Ptr::create();
0225         foreach (const auto &task, childrenTasks)
0226             childrenProvider->append(task);
0227 
0228         auto childrenList = Domain::QueryResult<Domain::Task::Ptr>::create(childrenProvider);
0229         auto emptyProvider = Domain::QueryResultProvider<Domain::Task::Ptr>::Ptr::create();
0230         auto emptyList = Domain::QueryResult<Domain::Task::Ptr>::create(emptyProvider);
0231 
0232         Utils::MockObject<Domain::TaskQueries> queryMock;
0233         queryMock(&Domain::TaskQueries::findChildren).when(tasks.at(1)).thenReturn(emptyList);
0234         queryMock(&Domain::TaskQueries::findChildren).when(tasks.at(2)).thenReturn(emptyList);
0235 
0236         auto queryGenerator = [&](const Domain::Task::Ptr &task) {
0237             if (!task)
0238                 return Domain::QueryResult<Domain::Task::Ptr>::create(provider);
0239             else
0240                 return queryMock.getInstance()->findChildren(task);
0241         };
0242         auto flagsFunction = [](const Domain::Task::Ptr &) {
0243             return Qt::ItemIsSelectable
0244                  | Qt::ItemIsEnabled
0245                  | Qt::ItemIsEditable
0246                  | Qt::ItemIsUserCheckable;
0247         };
0248 
0249         auto setDataFunction = [](const Domain::Task::Ptr &, const QVariant &, int) {
0250             return false;
0251         };
0252         Presentation::QueryTreeModel<Domain::Task::Ptr> model(queryGenerator, flagsFunction, standardDataFunction, setDataFunction, nullptr);
0253         new QAbstractItemModelTester(&model, this);
0254         QSignalSpy aboutToBeInsertedSpy(&model, &QAbstractItemModel::rowsAboutToBeInserted);
0255         QSignalSpy insertedSpy(&model, &QAbstractItemModel::rowsInserted);
0256 
0257         // WHEN
0258         queryMock(&Domain::TaskQueries::findChildren).when(tasks.at(0)).thenReturn(childrenList);
0259         queryMock(&Domain::TaskQueries::findChildren).when(childrenTasks.at(0)).thenReturn(emptyList);
0260         queryMock(&Domain::TaskQueries::findChildren).when(childrenTasks.at(1)).thenReturn(emptyList);
0261         queryMock(&Domain::TaskQueries::findChildren).when(childrenTasks.at(2)).thenReturn(emptyList);
0262         provider->insert(0, tasks.at(0));
0263 
0264         // THEN
0265         QCOMPARE(aboutToBeInsertedSpy.size(), 1);
0266         QCOMPARE(aboutToBeInsertedSpy.first().at(0).toModelIndex(), QModelIndex());
0267         QCOMPARE(aboutToBeInsertedSpy.first().at(1).toInt(), 0);
0268         QCOMPARE(aboutToBeInsertedSpy.first().at(2).toInt(), 0);
0269         QCOMPARE(insertedSpy.size(), 1);
0270         QCOMPARE(insertedSpy.first().at(0).toModelIndex(), QModelIndex());
0271         QCOMPARE(insertedSpy.first().at(1).toInt(), 0);
0272         QCOMPARE(insertedSpy.first().at(2).toInt(), 0);
0273     }
0274 
0275     void shouldReactToChilrenTaskAdd()
0276     {
0277         // GIVEN
0278         auto tasks = createTasks();
0279         auto provider = Domain::QueryResultProvider<Domain::Task::Ptr>::Ptr::create();
0280         foreach (const auto &task, tasks)
0281             provider->append(task);
0282 
0283         auto childrenTasks = createChildrenTasks();
0284         auto childrenProvider = Domain::QueryResultProvider<Domain::Task::Ptr>::Ptr::create();
0285         childrenProvider->append(childrenTasks.at(0));
0286         childrenProvider->append(childrenTasks.at(1));
0287 
0288         auto childrenList = Domain::QueryResult<Domain::Task::Ptr>::create(childrenProvider);
0289         auto emptyProvider = Domain::QueryResultProvider<Domain::Task::Ptr>::Ptr::create();
0290         auto emptyList = Domain::QueryResult<Domain::Task::Ptr>::create(emptyProvider);
0291 
0292         Utils::MockObject<Domain::TaskQueries> queryMock;
0293         queryMock(&Domain::TaskQueries::findChildren).when(tasks.at(0)).thenReturn(childrenList);
0294         queryMock(&Domain::TaskQueries::findChildren).when(tasks.at(1)).thenReturn(emptyList);
0295         queryMock(&Domain::TaskQueries::findChildren).when(tasks.at(2)).thenReturn(emptyList);
0296         queryMock(&Domain::TaskQueries::findChildren).when(childrenTasks.at(0)).thenReturn(emptyList);
0297         queryMock(&Domain::TaskQueries::findChildren).when(childrenTasks.at(1)).thenReturn(emptyList);
0298 
0299         auto queryGenerator = [&](const Domain::Task::Ptr &task) {
0300             if (!task)
0301                 return Domain::QueryResult<Domain::Task::Ptr>::create(provider);
0302             else
0303                 return queryMock.getInstance()->findChildren(task);
0304         };
0305         auto flagsFunction = [](const Domain::Task::Ptr &) {
0306             return Qt::ItemIsSelectable
0307                  | Qt::ItemIsEnabled
0308                  | Qt::ItemIsEditable
0309                  | Qt::ItemIsUserCheckable;
0310         };
0311         auto setDataFunction = [](const Domain::Task::Ptr &, const QVariant &, int) {
0312             return false;
0313         };
0314         Presentation::QueryTreeModel<Domain::Task::Ptr> model(queryGenerator, flagsFunction, standardDataFunction, setDataFunction, nullptr);
0315         new QAbstractItemModelTester(&model, this);
0316         QSignalSpy aboutToBeInsertedSpy(&model, &QAbstractItemModel::rowsAboutToBeInserted);
0317         QSignalSpy insertedSpy(&model, &QAbstractItemModel::rowsInserted);
0318 
0319         // WHEN
0320         queryMock(&Domain::TaskQueries::findChildren).when(childrenTasks.at(2)).thenReturn(emptyList);
0321         childrenProvider->insert(1, tasks.at(2));
0322 
0323         // THEN
0324         QCOMPARE(aboutToBeInsertedSpy.size(), 1);
0325         QCOMPARE(aboutToBeInsertedSpy.first().at(0).toModelIndex(), model.index(0, 0));
0326         QCOMPARE(aboutToBeInsertedSpy.first().at(1).toInt(), 1);
0327         QCOMPARE(aboutToBeInsertedSpy.first().at(2).toInt(), 1);
0328         QCOMPARE(insertedSpy.size(), 1);
0329         QCOMPARE(insertedSpy.first().at(0).toModelIndex(), model.index(0, 0));
0330         QCOMPARE(insertedSpy.first().at(1).toInt(), 1);
0331         QCOMPARE(insertedSpy.first().at(2).toInt(), 1);
0332     }
0333 
0334     void shouldReactToTaskRemove()
0335     {
0336         // GIVEN
0337         auto tasks = createTasks();
0338         auto provider = Domain::QueryResultProvider<Domain::Task::Ptr>::Ptr::create();
0339         foreach (const auto &task, tasks)
0340             provider->append(task);
0341 
0342         auto childrenTasks = createChildrenTasks();
0343         auto childrenProvider = Domain::QueryResultProvider<Domain::Task::Ptr>::Ptr::create();
0344         foreach (const auto &task, childrenTasks)
0345             childrenProvider->append(task);
0346 
0347         auto childrenList = Domain::QueryResult<Domain::Task::Ptr>::create(childrenProvider);
0348         auto emptyProvider = Domain::QueryResultProvider<Domain::Task::Ptr>::Ptr::create();
0349         auto emptyList = Domain::QueryResult<Domain::Task::Ptr>::create(emptyProvider);
0350 
0351         Utils::MockObject<Domain::TaskQueries> queryMock;
0352         queryMock(&Domain::TaskQueries::findChildren).when(tasks.at(0)).thenReturn(childrenList);
0353         queryMock(&Domain::TaskQueries::findChildren).when(tasks.at(1)).thenReturn(emptyList);
0354         queryMock(&Domain::TaskQueries::findChildren).when(tasks.at(2)).thenReturn(emptyList);
0355         queryMock(&Domain::TaskQueries::findChildren).when(childrenTasks.at(0)).thenReturn(emptyList);
0356         queryMock(&Domain::TaskQueries::findChildren).when(childrenTasks.at(1)).thenReturn(emptyList);
0357         queryMock(&Domain::TaskQueries::findChildren).when(childrenTasks.at(2)).thenReturn(emptyList);
0358 
0359         auto queryGenerator = [&](const Domain::Task::Ptr &task) {
0360             if (!task)
0361                 return Domain::QueryResult<Domain::Task::Ptr>::create(provider);
0362             else
0363                 return queryMock.getInstance()->findChildren(task);
0364         };
0365         auto flagsFunction = [](const Domain::Task::Ptr &) {
0366             return Qt::ItemIsSelectable
0367                  | Qt::ItemIsEnabled
0368                  | Qt::ItemIsEditable
0369                  | Qt::ItemIsUserCheckable;
0370         };
0371         auto setDataFunction = [](const Domain::Task::Ptr &, const QVariant &, int) {
0372             return false;
0373         };
0374         Presentation::QueryTreeModel<Domain::Task::Ptr> model(queryGenerator, flagsFunction, standardDataFunction, setDataFunction, nullptr);
0375         new QAbstractItemModelTester(&model, this);
0376         QSignalSpy aboutToBeRemovedSpy(&model, &QAbstractItemModel::rowsAboutToBeRemoved);
0377         QSignalSpy removedSpy(&model, &QAbstractItemModel::rowsRemoved);
0378         QSignalSpy aboutToBeInsertedSpy(&model, &QAbstractItemModel::rowsAboutToBeInserted);
0379         QSignalSpy insertedSpy(&model, &QAbstractItemModel::rowsInserted);
0380 
0381         QModelIndex removeIndex = model.index(0, 0);
0382 
0383         // WHEN
0384         // Remove children
0385         childrenProvider->removeAt(0);
0386         childrenProvider->removeAt(0);
0387         childrenProvider->removeAt(0);
0388         // Move children to Top Level
0389         provider->append(childrenTasks.at(0));
0390         provider->append(childrenTasks.at(1));
0391         provider->append(childrenTasks.at(2));
0392         // Remove firt element from topLevel
0393         provider->removeAt(0);
0394 
0395         // THEN
0396         QCOMPARE(aboutToBeRemovedSpy.size(), 4);
0397         QCOMPARE(removedSpy.size(), 4);
0398         for (int i = 0; i < aboutToBeRemovedSpy.size(); i++) {
0399             if (i != 3)
0400                 QCOMPARE(aboutToBeRemovedSpy.at(i).at(0).toModelIndex(), removeIndex);
0401             else
0402                 QCOMPARE(aboutToBeRemovedSpy.at(i).at(0).toModelIndex(), QModelIndex());
0403             QCOMPARE(aboutToBeRemovedSpy.at(i).at(1).toInt(), 0);
0404             QCOMPARE(aboutToBeRemovedSpy.at(i).at(2).toInt(), 0);
0405 
0406             if (i != 3)
0407                 QCOMPARE(removedSpy.at(i).at(0).toModelIndex(), removeIndex);
0408             else
0409                 QCOMPARE(removedSpy.at(i).at(0).toModelIndex(), QModelIndex());
0410             QCOMPARE(removedSpy.at(i).at(1).toInt(), 0);
0411             QCOMPARE(removedSpy.at(i).at(2).toInt(), 0);
0412         }
0413 
0414         QCOMPARE(aboutToBeInsertedSpy.size(), 3);
0415         QCOMPARE(insertedSpy.size(), 3);
0416         for (int i = 0; i < aboutToBeInsertedSpy.size(); i++) {
0417             QCOMPARE(aboutToBeInsertedSpy.at(i).at(0).toModelIndex(), QModelIndex());
0418             QCOMPARE(aboutToBeInsertedSpy.at(i).at(1).toInt(), i + 3);
0419             QCOMPARE(aboutToBeInsertedSpy.at(i).at(2).toInt(), i + 3);
0420             QCOMPARE(insertedSpy.at(i).at(0).toModelIndex(), QModelIndex());
0421             QCOMPARE(insertedSpy.at(i).at(1).toInt(), i + 3);
0422             QCOMPARE(insertedSpy.at(i).at(1).toInt(), i + 3);
0423         }
0424     }
0425 
0426     void shouldReactToTaskChange()
0427     {
0428         // GIVEN
0429         // GIVEN
0430         auto tasks = createTasks();
0431         auto provider = Domain::QueryResultProvider<Domain::Task::Ptr>::Ptr::create();
0432         foreach (const auto &task, tasks)
0433             provider->append(task);
0434 
0435         auto childrenTasks = createChildrenTasks();
0436         auto childrenProvider = Domain::QueryResultProvider<Domain::Task::Ptr>::Ptr::create();
0437         foreach (const auto &task, childrenTasks)
0438             childrenProvider->append(task);
0439 
0440         auto childrenList = Domain::QueryResult<Domain::Task::Ptr>::create(childrenProvider);
0441         auto emptyProvider = Domain::QueryResultProvider<Domain::Task::Ptr>::Ptr::create();
0442         auto emptyList = Domain::QueryResult<Domain::Task::Ptr>::create(emptyProvider);
0443 
0444         Utils::MockObject<Domain::TaskQueries> queryMock;
0445         queryMock(&Domain::TaskQueries::findChildren).when(tasks.at(0)).thenReturn(childrenList);
0446         queryMock(&Domain::TaskQueries::findChildren).when(tasks.at(1)).thenReturn(emptyList);
0447         queryMock(&Domain::TaskQueries::findChildren).when(tasks.at(2)).thenReturn(emptyList);
0448         queryMock(&Domain::TaskQueries::findChildren).when(childrenTasks.at(0)).thenReturn(emptyList);
0449         queryMock(&Domain::TaskQueries::findChildren).when(childrenTasks.at(1)).thenReturn(emptyList);
0450         queryMock(&Domain::TaskQueries::findChildren).when(childrenTasks.at(2)).thenReturn(emptyList);
0451 
0452         // WHEN
0453         auto queryGenerator = [&](const Domain::Task::Ptr &task) {
0454             if (!task)
0455                 return Domain::QueryResult<Domain::Task::Ptr>::create(provider);
0456             else
0457                 return queryMock.getInstance()->findChildren(task);
0458         };
0459         auto flagsFunction = [](const Domain::Task::Ptr &) {
0460             return Qt::ItemIsSelectable
0461                  | Qt::ItemIsEnabled
0462                  | Qt::ItemIsEditable
0463                  | Qt::ItemIsUserCheckable;
0464         };
0465         auto setDataFunction = [](const Domain::Task::Ptr &, const QVariant &, int) {
0466             return false;
0467         };
0468         Presentation::QueryTreeModel<Domain::Task::Ptr> model(queryGenerator, flagsFunction, standardDataFunction, setDataFunction, nullptr);
0469         new QAbstractItemModelTester(&model, this);
0470         QSignalSpy dataChangedSpy(&model, &QAbstractItemModel::dataChanged);
0471 
0472         // WHEN
0473         tasks.at(2)->setDone(true);
0474         childrenTasks.at(2)->setDone(true);
0475         provider->replace(2, tasks.at(2));
0476         childrenProvider->replace(2, tasks.at(2));
0477 
0478         // THEN
0479         QCOMPARE(dataChangedSpy.size(), 2);
0480         QCOMPARE(dataChangedSpy.first().at(0).toModelIndex(), model.index(2, 0));
0481         QCOMPARE(dataChangedSpy.first().at(1).toModelIndex(), model.index(2, 0));
0482         QCOMPARE(dataChangedSpy.last().at(0).toModelIndex(), model.index(2, 0, model.index(0, 0)));
0483         QCOMPARE(dataChangedSpy.last().at(1).toModelIndex(), model.index(2, 0, model.index(0, 0)));
0484     }
0485 
0486     void shouldAllowEditsAndChecks()
0487     {
0488         // GIVEN
0489         auto tasks = createTasks();
0490         auto provider = Domain::QueryResultProvider<Domain::Task::Ptr>::Ptr::create();
0491         foreach (const auto &task, tasks)
0492             provider->append(task);
0493 
0494         auto childrenTasks = createChildrenTasks();
0495         auto childrenProvider = Domain::QueryResultProvider<Domain::Task::Ptr>::Ptr::create();
0496         foreach (const auto &task, childrenTasks)
0497             childrenProvider->append(task);
0498 
0499         auto childrenList = Domain::QueryResult<Domain::Task::Ptr>::create(childrenProvider);
0500         auto emptyProvider = Domain::QueryResultProvider<Domain::Task::Ptr>::Ptr::create();
0501         auto emptyList = Domain::QueryResult<Domain::Task::Ptr>::create(emptyProvider);
0502 
0503         Utils::MockObject<Domain::TaskQueries> queryMock;
0504         queryMock(&Domain::TaskQueries::findChildren).when(tasks.at(0)).thenReturn(childrenList);
0505         queryMock(&Domain::TaskQueries::findChildren).when(tasks.at(1)).thenReturn(emptyList);
0506         queryMock(&Domain::TaskQueries::findChildren).when(tasks.at(2)).thenReturn(emptyList);
0507         queryMock(&Domain::TaskQueries::findChildren).when(childrenTasks.at(0)).thenReturn(emptyList);
0508         queryMock(&Domain::TaskQueries::findChildren).when(childrenTasks.at(1)).thenReturn(emptyList);
0509         queryMock(&Domain::TaskQueries::findChildren).when(childrenTasks.at(2)).thenReturn(emptyList);
0510 
0511         // WHEN
0512         auto queryGenerator = [&](const Domain::Task::Ptr &task) {
0513             if (!task)
0514                 return Domain::QueryResult<Domain::Task::Ptr>::create(provider);
0515             else
0516                 return queryMock.getInstance()->findChildren(task);
0517         };
0518         auto flagsFunction = [](const Domain::Task::Ptr &) {
0519             return Qt::ItemIsSelectable
0520                  | Qt::ItemIsEnabled
0521                  | Qt::ItemIsEditable
0522                  | Qt::ItemIsUserCheckable;
0523         };
0524         auto setDataFunction = [](const Domain::Task::Ptr &, const QVariant &, int) {
0525             return false;
0526         };
0527         Presentation::QueryTreeModel<Domain::Task::Ptr> model(queryGenerator, flagsFunction, standardDataFunction, setDataFunction, nullptr);
0528         new QAbstractItemModelTester(&model, this);
0529 
0530         // WHEN
0531         // Nothing particular
0532 
0533         // THEN
0534         for (int row = 0; row < tasks.size(); row++) {
0535             QVERIFY(model.flags(model.index(row, 0)) & Qt::ItemIsEditable);
0536             QVERIFY(model.flags(model.index(row, 0)) & Qt::ItemIsUserCheckable);
0537         }
0538         for (int row = 0; row < childrenTasks.size(); row++) {
0539             QVERIFY(model.flags(model.index(row, 0, model.index(0, 0))) & Qt::ItemIsEditable);
0540             QVERIFY(model.flags(model.index(row, 0, model.index(0, 0))) & Qt::ItemIsUserCheckable);
0541         }
0542     }
0543 
0544     void shouldSaveChanges()
0545     {
0546         // GIVEN
0547         auto tasks = createTasks();
0548         const int taskPos = 1;
0549         const auto task = tasks[taskPos];
0550         auto provider = Domain::QueryResultProvider<Domain::Task::Ptr>::Ptr::create();
0551         foreach (const auto &task, tasks)
0552             provider->append(task);
0553 
0554         auto childrenTasks = createChildrenTasks();
0555         auto childrenProvider = Domain::QueryResultProvider<Domain::Task::Ptr>::Ptr::create();
0556         foreach (const auto &task, childrenTasks)
0557             childrenProvider->append(task);
0558         
0559         auto childrenList = Domain::QueryResult<Domain::Task::Ptr>::create(childrenProvider);
0560         auto emptyProvider = Domain::QueryResultProvider<Domain::Task::Ptr>::Ptr::create();
0561         auto emptyList = Domain::QueryResult<Domain::Task::Ptr>::create(emptyProvider);
0562             
0563         Utils::MockObject<Domain::TaskQueries> queryMock; 
0564         queryMock(&Domain::TaskQueries::findChildren).when(tasks.at(0)).thenReturn(childrenList);
0565         queryMock(&Domain::TaskQueries::findChildren).when(tasks.at(1)).thenReturn(emptyList);
0566         queryMock(&Domain::TaskQueries::findChildren).when(tasks.at(2)).thenReturn(emptyList);
0567         queryMock(&Domain::TaskQueries::findChildren).when(childrenTasks.at(0)).thenReturn(emptyList);
0568         queryMock(&Domain::TaskQueries::findChildren).when(childrenTasks.at(1)).thenReturn(emptyList);
0569         queryMock(&Domain::TaskQueries::findChildren).when(childrenTasks.at(2)).thenReturn(emptyList);
0570 
0571         Utils::MockObject<Domain::TaskRepository> repositoryMock;
0572         repositoryMock(&Domain::TaskRepository::update).when(task).thenReturn(nullptr);
0573 
0574         auto queryGenerator = [&](const Domain::Task::Ptr &task) {
0575             if (!task)
0576                 return Domain::QueryResult<Domain::Task::Ptr>::create(provider);
0577             else
0578                 return queryMock.getInstance()->findChildren(task);
0579         };
0580         auto flagsFunction = [](const Domain::Task::Ptr &) {
0581             return Qt::ItemIsSelectable
0582                  | Qt::ItemIsEnabled
0583                  | Qt::ItemIsEditable
0584                  | Qt::ItemIsUserCheckable;
0585         };
0586         auto setDataFunction = [&](const Domain::Task::Ptr &task, const QVariant &value, int role) {
0587             if (role != Qt::EditRole && role != Qt::CheckStateRole) {
0588                 return false;
0589             }
0590 
0591             if (role == Qt::EditRole) {
0592                 task->setTitle(value.toString());
0593             } else {
0594                 task->setDone(value.toInt() == Qt::Checked);
0595             }
0596 
0597             repositoryMock.getInstance()->update(task);
0598             return true;
0599         };
0600         Presentation::QueryTreeModel<Domain::Task::Ptr> model(queryGenerator, flagsFunction, standardDataFunction, setDataFunction, nullptr);
0601         new QAbstractItemModelTester(&model, this);
0602         QSignalSpy titleChangedSpy(task.data(), &Domain::Task::titleChanged);
0603         QSignalSpy doneChangedSpy(task.data(), &Domain::Task::doneChanged);
0604 
0605         // WHEN
0606         const auto index = model.index(taskPos, 0);
0607         model.setData(index, "alternate second");
0608         model.setData(index, Qt::Checked, Qt::CheckStateRole);
0609 
0610         // THEN
0611         QVERIFY(repositoryMock(&Domain::TaskRepository::update).when(task).exactly(2));
0612 
0613         QCOMPARE(titleChangedSpy.size(), 1);
0614         QCOMPARE(titleChangedSpy.first().first().toString(), QStringLiteral("alternate second"));
0615         QCOMPARE(doneChangedSpy.size(), 1);
0616         QCOMPARE(doneChangedSpy.first().first().toBool(), true);
0617     }
0618 
0619     void shouldProvideUnderlyingObject()
0620     {
0621         // GIVEN
0622         auto provider = Domain::QueryResultProvider<QColor>::Ptr::create();
0623         provider->append(Qt::red);
0624         provider->append(Qt::green);
0625         provider->append(Qt::blue);
0626 
0627         auto queryGenerator = [&](const QColor &color) {
0628             if (!color.isValid())
0629                 return Domain::QueryResult<QColor>::create(provider);
0630             else
0631                 return Domain::QueryResult<QColor>::Ptr();
0632         };
0633         auto flagsFunction = [](const QColor &) {
0634             return Qt::NoItemFlags;
0635         };
0636         auto dataFunction = [](const QColor &, int, int) {
0637             return QVariant();
0638         };
0639         auto setDataFunction = [](const QColor &, const QVariant &, int) {
0640             return false;
0641         };
0642         Presentation::QueryTreeModel<QColor> model(queryGenerator, flagsFunction, dataFunction, setDataFunction);
0643         new QAbstractItemModelTester(&model, this);
0644 
0645         // WHEN
0646         const QModelIndex index = model.index(1, 0);
0647         const QVariant data = index.data(Presentation::QueryTreeModelBase::ObjectRole);
0648 
0649         // THEN
0650         QVERIFY(data.isValid());
0651         QCOMPARE(data.value<QColor>(), provider->data().at(1));
0652     }
0653 
0654     void shouldProvideUnderlyingTask()
0655     {
0656         // GIVEN
0657         auto provider = Domain::QueryResultProvider<Domain::Task::Ptr>::Ptr::create();
0658         foreach (const auto &task, createTasks())
0659             provider->append(task);
0660 
0661         auto queryGenerator = [&](const Domain::Task::Ptr &task) {
0662             if (!task)
0663                 return Domain::QueryResult<Domain::Task::Ptr>::create(provider);
0664             else
0665                 return Domain::QueryResult<Domain::Task::Ptr>::Ptr();
0666         };
0667         auto flagsFunction = [](const Domain::Task::Ptr &) {
0668             return Qt::NoItemFlags;
0669         };
0670         auto dataFunction = [](const Domain::Task::Ptr &, int, int) {
0671             return QVariant();
0672         };
0673         auto setDataFunction = [](const Domain::Task::Ptr &, const QVariant &, int) {
0674             return false;
0675         };
0676         Presentation::QueryTreeModel<Domain::Task::Ptr> model(queryGenerator, flagsFunction, dataFunction, setDataFunction);
0677         new QAbstractItemModelTester(&model, this);
0678 
0679         // WHEN
0680         const QModelIndex index = model.index(1, 0);
0681         const QVariant data = index.data(Presentation::QueryTreeModelBase::ObjectRole);
0682 
0683         // THEN
0684         QVERIFY(data.isValid());
0685         QVERIFY(!data.value<Domain::Task::Ptr>().isNull());
0686         QCOMPARE(data.value<Domain::Task::Ptr>(), provider->data().at(1));
0687     }
0688 
0689     void shouldMoveOnlyDuringDragAndDrop()
0690     {
0691         // GIVEN
0692         auto queryGenerator = [&] (const QColor &) {
0693             return Domain::QueryResult<QColor>::Ptr();
0694         };
0695         auto flagsFunction = [] (const QColor &) {
0696             return Qt::NoItemFlags;
0697         };
0698         auto dataFunction = [] (const QColor &, int, int) {
0699             return QVariant();
0700         };
0701         auto setDataFunction = [] (const QColor &, const QVariant &, int) {
0702             return false;
0703         };
0704         auto dropFunction = [] (const QMimeData *, Qt::DropAction, const QColor &) {
0705             return false;
0706         };
0707         auto dragFunction = [] (const QList<QColor> &) {
0708             return nullptr;
0709         };
0710 
0711         Presentation::QueryTreeModel<QColor> model(queryGenerator, flagsFunction,
0712                                                    dataFunction, setDataFunction,
0713                                                    dropFunction, dragFunction, nullptr);
0714 
0715         // THEN
0716         QCOMPARE(model.supportedDragActions(), Qt::MoveAction);
0717         QCOMPARE(model.supportedDropActions(), Qt::MoveAction);
0718     }
0719 
0720     void shouldCreateMimeData()
0721     {
0722         // GIVEN
0723         auto provider = Domain::QueryResultProvider<QColor>::Ptr::create();
0724         provider->append(Qt::red);
0725         provider->append(Qt::green);
0726         provider->append(Qt::blue);
0727 
0728         auto queryGenerator = [&] (const QColor &color) {
0729             if (!color.isValid())
0730                 return Domain::QueryResult<QColor>::create(provider);
0731             else
0732                 return Domain::QueryResult<QColor>::Ptr();
0733         };
0734         auto flagsFunction = [] (const QColor &) {
0735             return Qt::NoItemFlags;
0736         };
0737         auto dataFunction = [] (const QColor &, int, int) {
0738             return QVariant();
0739         };
0740         auto setDataFunction = [] (const QColor &, const QVariant &, int) {
0741             return false;
0742         };
0743         auto dropFunction = [] (const QMimeData *, Qt::DropAction, const QColor &) {
0744             return false;
0745         };
0746         auto dragFunction = [] (const QList<QColor> &colors) {
0747             auto mimeData = new QMimeData;
0748             mimeData->setColorData(QVariant::fromValue(colors));
0749             return mimeData;
0750         };
0751 
0752         Presentation::QueryTreeModel<QColor> model(queryGenerator, flagsFunction,
0753                                                    dataFunction, setDataFunction,
0754                                                    dropFunction, dragFunction, nullptr);
0755         new QAbstractItemModelTester(&model, this);
0756 
0757         // WHEN
0758         auto data = std::unique_ptr<QMimeData>(model.mimeData(QList<QModelIndex>() << model.index(1, 0) << model.index(2, 0)));
0759 
0760         // THEN
0761         QVERIFY(data.get());
0762         QVERIFY(model.mimeTypes().contains(QStringLiteral("application/x-zanshin-object")));
0763         QList<QColor> colors;
0764         colors << Qt::green << Qt::blue;
0765         QCOMPARE(data->colorData().value<QList<QColor>>(), colors);
0766     }
0767 
0768     void shouldDropMimeData_data()
0769     {
0770         QTest::addColumn<int>("row");
0771         QTest::addColumn<int>("column");
0772         QTest::addColumn<int>("parentRow");
0773         QTest::addColumn<bool>("callExpected");
0774 
0775         QTest::newRow("drop on object") << -1 << -1 << 2 << true;
0776         QTest::newRow("drop between object") << 1 << 0 << -1 << true;
0777         QTest::newRow("drop in empty area") << -1 << -1 << -1 << true;
0778     }
0779 
0780     void shouldDropMimeData()
0781     {
0782         // GIVEN
0783         QFETCH(int, row);
0784         QFETCH(int, column);
0785         QFETCH(int, parentRow);
0786         QFETCH(bool, callExpected);
0787         bool dropCalled = false;
0788         const QMimeData *droppedData = nullptr;
0789         QColor colorSeen;
0790 
0791         auto provider = Domain::QueryResultProvider<QColor>::Ptr::create();
0792         provider->append(Qt::red);
0793         provider->append(Qt::green);
0794         provider->append(Qt::blue);
0795 
0796         auto queryGenerator = [&] (const QColor &color) {
0797             if (!color.isValid())
0798                 return Domain::QueryResult<QColor>::create(provider);
0799             else
0800                 return Domain::QueryResult<QColor>::Ptr();
0801         };
0802         auto flagsFunction = [] (const QColor &) {
0803             return Qt::NoItemFlags;
0804         };
0805         auto dataFunction = [] (const QColor &, int, int) {
0806             return QVariant();
0807         };
0808         auto setDataFunction = [] (const QColor &, const QVariant &, int) {
0809             return false;
0810         };
0811         auto dropFunction = [&] (const QMimeData *data, Qt::DropAction, const QColor &color) {
0812             dropCalled = true;
0813             droppedData = data;
0814             colorSeen = color;
0815             return false;
0816         };
0817         auto dragFunction = [] (const QList<QColor> &) -> QMimeData* {
0818             return nullptr;
0819         };
0820 
0821         Presentation::QueryTreeModel<QColor> model(queryGenerator, flagsFunction,
0822                                                    dataFunction, setDataFunction,
0823                                                    dropFunction, dragFunction, nullptr);
0824         new QAbstractItemModelTester(&model, this);
0825 
0826         // WHEN
0827         auto data = std::make_unique<QMimeData>();
0828         const QModelIndex parent = parentRow >= 0 ? model.index(parentRow, 0) : QModelIndex();
0829         model.dropMimeData(data.get(), Qt::MoveAction, row, column, parent);
0830 
0831         // THEN
0832         QCOMPARE(dropCalled, callExpected);
0833         if (callExpected) {
0834             QCOMPARE(droppedData, data.get());
0835             QCOMPARE(colorSeen, parent.data(Presentation::QueryTreeModelBase::ObjectRole).value<QColor>());
0836         }
0837     }
0838 
0839     void shouldPreventCyclesByDragAndDrop()
0840     {
0841         // GIVEN
0842         bool dropCalled = false;
0843 
0844         auto topProvider = Domain::QueryResultProvider<QString>::Ptr::create();
0845         topProvider->append(QStringLiteral("1"));
0846         topProvider->append(QStringLiteral("2"));
0847         topProvider->append(QStringLiteral("3"));
0848 
0849         auto firstLevelProvider = Domain::QueryResultProvider<QString>::Ptr::create();
0850         firstLevelProvider->append(QStringLiteral("2.1"));
0851         firstLevelProvider->append(QStringLiteral("2.2"));
0852         firstLevelProvider->append(QStringLiteral("2.3"));
0853 
0854         auto secondLevelProvider = Domain::QueryResultProvider<QString>::Ptr::create();
0855         secondLevelProvider->append(QStringLiteral("2.1.1"));
0856         secondLevelProvider->append(QStringLiteral("2.1.2"));
0857         secondLevelProvider->append(QStringLiteral("2.1.3"));
0858 
0859         auto queryGenerator = [&] (const QString &string) {
0860             if (string.isEmpty())
0861                 return Domain::QueryResult<QString>::create(topProvider);
0862             else if (string == QLatin1StringView("2"))
0863                 return Domain::QueryResult<QString>::create(firstLevelProvider);
0864             else if (string == QLatin1StringView("2.1"))
0865                 return Domain::QueryResult<QString>::create(secondLevelProvider);
0866             else
0867                 return Domain::QueryResult<QString>::Ptr();
0868         };
0869         auto flagsFunction = [] (const QString &) {
0870             return Qt::NoItemFlags;
0871         };
0872         auto dataFunction = [] (const QString &, int, int) {
0873             return QVariant();
0874         };
0875         auto setDataFunction = [] (const QString &, const QVariant &, int) {
0876             return false;
0877         };
0878         auto dropFunction = [&] (const QMimeData *, Qt::DropAction, const QString &) {
0879             dropCalled = true;
0880             return false;
0881         };
0882         auto dragFunction = [] (const QStringList &strings) -> QMimeData* {
0883             auto data = new QMimeData;
0884             data->setData(QStringLiteral("application/x-zanshin-object"), "object");
0885             data->setProperty("objects", QVariant::fromValue(strings));
0886             return data;
0887         };
0888 
0889         Presentation::QueryTreeModel<QString> model(queryGenerator, flagsFunction,
0890                                                     dataFunction, setDataFunction,
0891                                                     dropFunction, dragFunction, nullptr);
0892         new QAbstractItemModelTester(&model, this);
0893 
0894         const auto indexes = QModelIndexList() << model.index(0, 0)
0895                                                << model.index(1, 0)
0896                                                << model.index(1, 0, model.index(1, 0));
0897 
0898         // WHEN
0899         auto data = std::unique_ptr<QMimeData>(model.mimeData(indexes));
0900         const auto parent = model.index(1, 0, model.index(0, 0, model.index(1, 0)));
0901         model.dropMimeData(data.get(), Qt::MoveAction, -1, -1, parent);
0902 
0903         // THEN
0904         QVERIFY(!dropCalled);
0905     }
0906 };
0907 
0908 ZANSHIN_TEST_MAIN(QueryTreeModelTest)
0909 
0910 #include "querytreemodeltest.moc"