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

0001 /*
0002  * SPDX-FileCopyrightText: 2014 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 <testlib/qtest_zanshin.h>
0008 
0009 #include "domain/livequery.h"
0010 
0011 #include "utils/jobhandler.h"
0012 
0013 #include "testlib/fakejob.h"
0014 
0015 using namespace Domain;
0016 
0017 typedef QSharedPointer<QObject> QObjectPtr;
0018 
0019 class LiveQueryTest : public QObject
0020 {
0021     Q_OBJECT
0022 private:
0023     QObject *createObject(int id, const QString &name)
0024     {
0025         QObject *obj = new QObject(this);
0026         obj->setObjectName(name);
0027         obj->setProperty("objectId", id);
0028         return obj;
0029     }
0030 
0031 private slots:
0032     void shouldHaveInitialFetchFunctionAndPredicate()
0033     {
0034         // GIVEN
0035         Domain::LiveQuery<QObject*, QPair<int, QString>> query;
0036         query.setFetchFunction([this] (const Domain::LiveQueryInput<QObject*>::AddFunction &add) {
0037             Utils::JobHandler::install(new FakeJob, [this, add] {
0038                 add(createObject(0, QStringLiteral("0A")));
0039                 add(createObject(1, QStringLiteral("1A")));
0040                 add(createObject(2, QStringLiteral("2A")));
0041                 add(createObject(3, QStringLiteral("0B")));
0042                 add(createObject(4, QStringLiteral("1B")));
0043                 add(createObject(5, QStringLiteral("2B")));
0044                 add(createObject(6, QStringLiteral("0C")));
0045                 add(createObject(7, QStringLiteral("1C")));
0046                 add(createObject(8, QStringLiteral("2C")));
0047             });
0048         });
0049         query.setConvertFunction([] (QObject *object) {
0050             return QPair<int, QString>(object->property("objectId").toInt(), object->objectName());
0051         });
0052         query.setPredicateFunction([] (QObject *object) {
0053             return object->objectName().startsWith('0');
0054         });
0055 
0056         // WHEN
0057         Domain::QueryResult<QPair<int, QString>>::Ptr result = query.result();
0058         result->data();
0059         result = query.result(); // Should not cause any problem or wrong data
0060         QVERIFY(result->data().isEmpty());
0061         QTest::qWait(150);
0062 
0063         // THEN
0064         QList<QPair<int, QString>> expected;
0065         expected << QPair<int, QString>(0, QStringLiteral("0A"))
0066                  << QPair<int, QString>(3, QStringLiteral("0B"))
0067                  << QPair<int, QString>(6, QStringLiteral("0C"));
0068         QCOMPARE(result->data(), expected);
0069     }
0070 
0071     void shouldFilterOutNullRawPointers()
0072     {
0073         // GIVEN
0074         auto query = Domain::LiveQuery<QString, QObject*>();
0075         query.setFetchFunction([] (const Domain::LiveQueryInput<QString>::AddFunction &add) {
0076             Utils::JobHandler::install(new FakeJob, [add] {
0077                 add(QStringLiteral("0"));
0078                 add(QStringLiteral("1"));
0079                 add(QString());
0080                 add(QStringLiteral("a"));
0081                 add(QStringLiteral("2"));
0082             });
0083         });
0084         query.setConvertFunction([this] (const QString &s) -> QObject* {
0085             bool ok = false;
0086             const int id = s.toInt(&ok);
0087             if (ok) {
0088                 auto object = new QObject(this);
0089                 object->setProperty("id", id);
0090                 return object;
0091             } else {
0092                 return nullptr;
0093             }
0094         });
0095         query.setPredicateFunction([] (const QString &s) {
0096             return !s.isEmpty();
0097         });
0098 
0099         // WHEN
0100         auto result = query.result();
0101         result->data();
0102         result = query.result(); // Should not cause any problem or wrong data
0103         QVERIFY(result->data().isEmpty());
0104         QTest::qWait(150);
0105 
0106         // THEN
0107         QCOMPARE(result->data().size(), 3);
0108         QCOMPARE(result->data().at(0)->property("id").toInt(), 0);
0109         QCOMPARE(result->data().at(1)->property("id").toInt(), 1);
0110         QCOMPARE(result->data().at(2)->property("id").toInt(), 2);
0111     }
0112 
0113     void shouldFilterOutNullSharedPointers()
0114     {
0115         // GIVEN
0116         auto query = Domain::LiveQuery<QString, QObjectPtr>();
0117         query.setFetchFunction([] (const Domain::LiveQueryInput<QString>::AddFunction &add) {
0118             Utils::JobHandler::install(new FakeJob, [add] {
0119                 add(QStringLiteral("0"));
0120                 add(QStringLiteral("1"));
0121                 add(QString());
0122                 add(QStringLiteral("a"));
0123                 add(QStringLiteral("2"));
0124             });
0125         });
0126         query.setConvertFunction([] (const QString &s) {
0127             bool ok = false;
0128             const int id = s.toInt(&ok);
0129             if (ok) {
0130                 auto object = QObjectPtr::create();
0131                 object->setProperty("id", id);
0132                 return object;
0133             } else {
0134                 return QObjectPtr();
0135             }
0136         });
0137         query.setPredicateFunction([] (const QString &s) {
0138             return !s.isEmpty();
0139         });
0140 
0141         // WHEN
0142         auto result = query.result();
0143         result->data();
0144         result = query.result(); // Should not cause any problem or wrong data
0145         QVERIFY(result->data().isEmpty());
0146         QTest::qWait(150);
0147 
0148         // THEN
0149         QCOMPARE(result->data().size(), 3);
0150         QCOMPARE(result->data().at(0)->property("id").toInt(), 0);
0151         QCOMPARE(result->data().at(1)->property("id").toInt(), 1);
0152         QCOMPARE(result->data().at(2)->property("id").toInt(), 2);
0153     }
0154 
0155     void shouldDealWithSeveralFetchesProperly()
0156     {
0157         // GIVEN
0158         Domain::LiveQuery<QObject*, QPair<int, QString>> query;
0159         query.setFetchFunction([this] (const Domain::LiveQuery<QObject*, QString>::AddFunction &add) {
0160             Utils::JobHandler::install(new FakeJob, [this, add] {
0161                 add(createObject(0, QStringLiteral("0A")));
0162                 add(createObject(1, QStringLiteral("1A")));
0163                 add(createObject(2, QStringLiteral("2A")));
0164                 add(createObject(3, QStringLiteral("0B")));
0165                 add(createObject(4, QStringLiteral("1B")));
0166                 add(createObject(5, QStringLiteral("2B")));
0167                 add(createObject(6, QStringLiteral("0C")));
0168                 add(createObject(7, QStringLiteral("1C")));
0169                 add(createObject(8, QStringLiteral("2C")));
0170             });
0171         });
0172         query.setConvertFunction([] (QObject *object) {
0173             return QPair<int, QString>(object->property("objectId").toInt(), object->objectName());
0174         });
0175         query.setPredicateFunction([] (QObject *object) {
0176             return object->objectName().startsWith('0');
0177         });
0178 
0179         for (int i = 0; i < 2; i++) {
0180             // WHEN * 2
0181             Domain::QueryResult<QPair<int, QString>>::Ptr result = query.result();
0182 
0183             // THEN * 2
0184             QVERIFY(result->data().isEmpty());
0185             QTest::qWait(150);
0186             QList<QPair<int, QString>> expected;
0187             expected << QPair<int, QString>(0, QStringLiteral("0A"))
0188                      << QPair<int, QString>(3, QStringLiteral("0B"))
0189                      << QPair<int, QString>(6, QStringLiteral("0C"));
0190             QCOMPARE(result->data(), expected);
0191         }
0192     }
0193 
0194     void shouldClearProviderWhenDeleted()
0195     {
0196         // GIVEN
0197         auto query = new Domain::LiveQuery<QObject*, QPair<int, QString>>;
0198         query->setFetchFunction([this] (const Domain::LiveQuery<QObject*, QString>::AddFunction &add) {
0199             Utils::JobHandler::install(new FakeJob, [this, add] {
0200                 add(createObject(0, QStringLiteral("0A")));
0201                 add(createObject(1, QStringLiteral("1A")));
0202                 add(createObject(2, QStringLiteral("2A")));
0203             });
0204         });
0205         query->setConvertFunction([] (QObject *object) {
0206             return QPair<int, QString>(object->property("objectId").toInt(), object->objectName());
0207         });
0208         query->setPredicateFunction([] (QObject *object) {
0209             return object->objectName().startsWith('0');
0210         });
0211 
0212         Domain::QueryResult<QPair<int, QString>>::Ptr result = query->result();
0213         QTest::qWait(150);
0214         QCOMPARE(result->data().count(), 1);
0215 
0216         // WHEN
0217         delete query;
0218 
0219         // THEN
0220         QVERIFY(result->data().isEmpty());
0221     }
0222 
0223     void shouldReactToAdds()
0224     {
0225         // GIVEN
0226         Domain::LiveQuery<QObject*, QPair<int, QString>> query;
0227         query.setFetchFunction([this] (const Domain::LiveQuery<QObject*, QString>::AddFunction &add) {
0228             Utils::JobHandler::install(new FakeJob, [this, add] {
0229                 add(createObject(0, QStringLiteral("0A")));
0230                 add(createObject(1, QStringLiteral("1A")));
0231                 add(createObject(2, QStringLiteral("2A")));
0232             });
0233         });
0234         query.setConvertFunction([] (QObject *object) {
0235             return QPair<int, QString>(object->property("objectId").toInt(), object->objectName());
0236         });
0237         query.setPredicateFunction([] (QObject *object) {
0238             return object->objectName().startsWith('0');
0239         });
0240 
0241         Domain::QueryResult<QPair<int, QString>>::Ptr result = query.result();
0242         QTest::qWait(150);
0243         QList<QPair<int, QString>> expected;
0244         expected << QPair<int, QString>(0, QStringLiteral("0A"));
0245         QCOMPARE(result->data(), expected);
0246 
0247         // WHEN
0248         query.onAdded(createObject(3, QStringLiteral("0B")));
0249         query.onAdded(createObject(4, QStringLiteral("1B")));
0250         query.onAdded(createObject(5, QStringLiteral("2B")));
0251 
0252         // THEN
0253         expected << QPair<int, QString>(3, QStringLiteral("0B"));
0254         QCOMPARE(result->data(), expected);
0255     }
0256 
0257     void shouldReactToRemoves()
0258     {
0259         // GIVEN
0260         Domain::LiveQuery<QObject*, QPair<int, QString>> query;
0261         query.setFetchFunction([this] (const Domain::LiveQuery<QObject*, QString>::AddFunction &add) {
0262             Utils::JobHandler::install(new FakeJob, [this, add] {
0263                 add(createObject(0, QStringLiteral("0A")));
0264                 add(createObject(1, QStringLiteral("1A")));
0265                 add(createObject(2, QStringLiteral("2A")));
0266                 add(createObject(3, QStringLiteral("0B")));
0267                 add(createObject(4, QStringLiteral("1B")));
0268                 add(createObject(5, QStringLiteral("2B")));
0269                 add(createObject(6, QStringLiteral("0C")));
0270                 add(createObject(7, QStringLiteral("1C")));
0271                 add(createObject(8, QStringLiteral("2C")));
0272             });
0273         });
0274         query.setConvertFunction([] (QObject *object) {
0275             return QPair<int, QString>(object->property("objectId").toInt(), object->objectName());
0276         });
0277         query.setPredicateFunction([] (QObject *object) {
0278             return object->objectName().startsWith('0');
0279         });
0280         query.setRepresentsFunction([] (QObject *object, const QPair<int, QString> &output) {
0281             return object->property("objectId").toInt() == output.first;
0282         });
0283 
0284         Domain::QueryResult<QPair<int, QString>>::Ptr result = query.result();
0285         QTest::qWait(150);
0286         QList<QPair<int, QString>> expected;
0287         expected << QPair<int, QString>(0, QStringLiteral("0A"))
0288                  << QPair<int, QString>(3, QStringLiteral("0B"))
0289                  << QPair<int, QString>(6, QStringLiteral("0C"));
0290         QCOMPARE(result->data(), expected);
0291 
0292         // WHEN
0293         query.onRemoved(createObject(3, QStringLiteral("0B")));
0294         query.onRemoved(createObject(4, QStringLiteral("1B")));
0295         query.onRemoved(createObject(5, QStringLiteral("2B")));
0296 
0297         // THEN
0298         expected.removeAt(1);
0299         QCOMPARE(result->data(), expected);
0300     }
0301 
0302     void shouldReactToChanges()
0303     {
0304         // GIVEN
0305         Domain::LiveQuery<QObject*, QPair<int, QString>> query;
0306         query.setFetchFunction([this] (const Domain::LiveQuery<QObject*, QString>::AddFunction &add) {
0307             Utils::JobHandler::install(new FakeJob, [this, add] {
0308                 add(createObject(0, QStringLiteral("0A")));
0309                 add(createObject(1, QStringLiteral("1A")));
0310                 add(createObject(2, QStringLiteral("2A")));
0311                 add(createObject(3, QStringLiteral("0B")));
0312                 add(createObject(4, QStringLiteral("1B")));
0313                 add(createObject(5, QStringLiteral("2B")));
0314                 add(createObject(6, QStringLiteral("0C")));
0315                 add(createObject(7, QStringLiteral("1C")));
0316                 add(createObject(8, QStringLiteral("2C")));
0317             });
0318         });
0319         query.setConvertFunction([] (QObject *object) {
0320             return QPair<int, QString>(object->property("objectId").toInt(), object->objectName());
0321         });
0322         query.setUpdateFunction([] (QObject *object, QPair<int, QString> &output) {
0323             output.second = object->objectName();
0324         });
0325         query.setPredicateFunction([] (QObject *object) {
0326             return object->objectName().startsWith('0');
0327         });
0328         query.setRepresentsFunction([] (QObject *object, const QPair<int, QString> &output) {
0329             return object->property("objectId").toInt() == output.first;
0330         });
0331 
0332         Domain::QueryResult<QPair<int, QString>>::Ptr result = query.result();
0333         QTest::qWait(150);
0334         QList<QPair<int, QString>> expected;
0335         expected << QPair<int, QString>(0, QStringLiteral("0A"))
0336                  << QPair<int, QString>(3, QStringLiteral("0B"))
0337                  << QPair<int, QString>(6, QStringLiteral("0C"));
0338         QCOMPARE(result->data(), expected);
0339 
0340         bool replaceHandlerCalled = false;
0341         result->addPostReplaceHandler([&replaceHandlerCalled](const QPair<int, QString> &, int) {
0342                                           replaceHandlerCalled = true;
0343                                       });
0344 
0345         // WHEN
0346         query.onChanged(createObject(3, QStringLiteral("0BB")));
0347 
0348         // Then
0349         expected.clear();
0350         expected << QPair<int, QString>(0, QStringLiteral("0A"))
0351                  << QPair<int, QString>(3, QStringLiteral("0BB"))
0352                  << QPair<int, QString>(6, QStringLiteral("0C"));
0353         QCOMPARE(result->data(), expected);
0354         QVERIFY(replaceHandlerCalled);
0355     }
0356 
0357     void shouldRemoveWhenChangesMakeInputUnsuitableForQuery()
0358     {
0359         // GIVEN
0360         Domain::LiveQuery<QObject*, QPair<int, QString>> query;
0361         query.setFetchFunction([this] (const Domain::LiveQuery<QObject*, QString>::AddFunction &add) {
0362             Utils::JobHandler::install(new FakeJob, [this, add] {
0363                 add(createObject(0, QStringLiteral("0A")));
0364                 add(createObject(1, QStringLiteral("1A")));
0365                 add(createObject(2, QStringLiteral("2A")));
0366                 add(createObject(3, QStringLiteral("0B")));
0367                 add(createObject(4, QStringLiteral("1B")));
0368                 add(createObject(5, QStringLiteral("2B")));
0369                 add(createObject(6, QStringLiteral("0C")));
0370                 add(createObject(7, QStringLiteral("1C")));
0371                 add(createObject(8, QStringLiteral("2C")));
0372             });
0373         });
0374         query.setConvertFunction([] (QObject *object) {
0375             return QPair<int, QString>(object->property("objectId").toInt(), object->objectName());
0376         });
0377         query.setPredicateFunction([] (QObject *object) {
0378             return object->objectName().startsWith('0');
0379         });
0380         query.setRepresentsFunction([] (QObject *object, const QPair<int, QString> &output) {
0381             return object->property("objectId").toInt() == output.first;
0382         });
0383 
0384         Domain::QueryResult<QPair<int, QString>>::Ptr result = query.result();
0385         QTest::qWait(150);
0386         QList<QPair<int, QString>> expected;
0387         expected << QPair<int, QString>(0, QStringLiteral("0A"))
0388                  << QPair<int, QString>(3, QStringLiteral("0B"))
0389                  << QPair<int, QString>(6, QStringLiteral("0C"));
0390         QCOMPARE(result->data(), expected);
0391 
0392         bool replaceHandlerCalled = false;
0393         result->addPostReplaceHandler([&replaceHandlerCalled](const QPair<int, QString> &, int) {
0394                                           replaceHandlerCalled = true;
0395                                       });
0396 
0397         // WHEN
0398         query.onChanged(createObject(3, QStringLiteral("1B")));
0399 
0400         // Then
0401         expected.removeAt(1);
0402         QCOMPARE(result->data(), expected);
0403         QVERIFY(!replaceHandlerCalled);
0404     }
0405 
0406     void shouldAddWhenChangesMakeInputSuitableForQuery()
0407     {
0408         // GIVEN
0409         Domain::LiveQuery<QObject*, QPair<int, QString>> query;
0410         query.setFetchFunction([this] (const Domain::LiveQuery<QObject*, QString>::AddFunction &add) {
0411             Utils::JobHandler::install(new FakeJob, [this, add] {
0412                 add(createObject(0, QStringLiteral("0A")));
0413                 add(createObject(1, QStringLiteral("1A")));
0414                 add(createObject(2, QStringLiteral("2A")));
0415                 add(createObject(3, QStringLiteral("0B")));
0416                 add(createObject(4, QStringLiteral("1B")));
0417                 add(createObject(5, QStringLiteral("2B")));
0418                 add(createObject(6, QStringLiteral("0C")));
0419                 add(createObject(7, QStringLiteral("1C")));
0420                 add(createObject(8, QStringLiteral("2C")));
0421             });
0422         });
0423         query.setConvertFunction([] (QObject *object) {
0424             return QPair<int, QString>(object->property("objectId").toInt(), object->objectName());
0425         });
0426         query.setPredicateFunction([] (QObject *object) {
0427             return object->objectName().startsWith('0');
0428         });
0429         query.setRepresentsFunction([] (QObject *object, const QPair<int, QString> &output) {
0430             return object->property("objectId").toInt() == output.first;
0431         });
0432 
0433         Domain::QueryResult<QPair<int, QString>>::Ptr result = query.result();
0434         QTest::qWait(150);
0435         QList<QPair<int, QString>> expected;
0436         expected << QPair<int, QString>(0, QStringLiteral("0A"))
0437                  << QPair<int, QString>(3, QStringLiteral("0B"))
0438                  << QPair<int, QString>(6, QStringLiteral("0C"));
0439         QCOMPARE(result->data(), expected);
0440 
0441         bool replaceHandlerCalled = false;
0442         result->addPostReplaceHandler([&replaceHandlerCalled](const QPair<int, QString> &, int) {
0443                                           replaceHandlerCalled = true;
0444                                       });
0445 
0446         // WHEN
0447         query.onChanged(createObject(4, QStringLiteral("0BB")));
0448 
0449         // Then
0450         expected << QPair<int, QString>(4, QStringLiteral("0BB"));
0451         QCOMPARE(result->data(), expected);
0452         QVERIFY(!replaceHandlerCalled);
0453     }
0454 
0455     void shouldEmptyAndFetchAgainOnReset()
0456     {
0457         // GIVEN
0458         bool afterReset = false;
0459 
0460         Domain::LiveQuery<QObject*, QPair<int, QString>> query;
0461         query.setFetchFunction([this, &afterReset] (const Domain::LiveQuery<QObject*, QString>::AddFunction &add) {
0462             Utils::JobHandler::install(new FakeJob, [this, &afterReset, add] {
0463                 add(createObject(0, QStringLiteral("0A")));
0464                 add(createObject(1, QStringLiteral("1A")));
0465                 add(createObject(2, QStringLiteral("2A")));
0466                 add(createObject(3, QStringLiteral("0B")));
0467                 add(createObject(4, QStringLiteral("1B")));
0468                 add(createObject(5, QStringLiteral("2B")));
0469 
0470                 if (afterReset) {
0471                     add(createObject(6, QStringLiteral("0C")));
0472                     add(createObject(7, QStringLiteral("1C")));
0473                     add(createObject(8, QStringLiteral("2C")));
0474                 }
0475             });
0476         });
0477         query.setConvertFunction([] (QObject *object) {
0478             return QPair<int, QString>(object->property("objectId").toInt(), object->objectName());
0479         });
0480         query.setPredicateFunction([&afterReset] (QObject *object) {
0481             if (afterReset)
0482                 return object->objectName().startsWith('1');
0483             else
0484                 return object->objectName().startsWith('0');
0485         });
0486 
0487         Domain::QueryResult<QPair<int, QString>>::Ptr result = query.result();
0488         int removeHandlerCallCount = 0;
0489         result->addPostRemoveHandler([&removeHandlerCallCount](const QPair<int, QString> &, int) {
0490                                          removeHandlerCallCount++;
0491                                      });
0492 
0493         QTest::qWait(150);
0494         QVERIFY(!result->data().isEmpty());
0495         QCOMPARE(removeHandlerCallCount, 0);
0496 
0497         // WHEN
0498         query.reset();
0499         afterReset = true;
0500         QTest::qWait(150);
0501 
0502         // THEN
0503         QList<QPair<int, QString>> expected;
0504         expected << QPair<int, QString>(1, QStringLiteral("1A"))
0505                  << QPair<int, QString>(4, QStringLiteral("1B"))
0506                  << QPair<int, QString>(7, QStringLiteral("1C"));
0507         QVERIFY(afterReset);
0508         QCOMPARE(result->data(), expected);
0509         QCOMPARE(removeHandlerCallCount, 2);
0510     }
0511 };
0512 
0513 ZANSHIN_TEST_MAIN(LiveQueryTest)
0514 
0515 #include "livequerytest.moc"