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

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