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"