testactivitylistmodel.cpp 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703
  1. /*
  2. * Copyright (C) by Claudio Cambra <claudio.cambra@nextcloud.com>
  3. *
  4. * This program is free software; you can redistribute it and/or modify
  5. * it under the terms of the GNU General Public License as published by
  6. * the Free Software Foundation; either version 2 of the License, or
  7. * (at your option) any later version.
  8. *
  9. * This program is distributed in the hope that it will be useful, but
  10. * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  11. * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
  12. * for more details.
  13. */
  14. #include "gui/tray/activitylistmodel.h"
  15. #include "account.h"
  16. #include "accountstate.h"
  17. #include "accountmanager.h"
  18. #include "syncenginetestutils.h"
  19. #include "syncresult.h"
  20. #include <QAbstractItemModelTester>
  21. #include <QDesktopServices>
  22. #include <QSignalSpy>
  23. #include <QTest>
  24. namespace {
  25. constexpr auto startingId = 90000;
  26. }
  27. static QByteArray fake404Response = R"(
  28. {"ocs":{"meta":{"status":"failure","statuscode":404,"message":"Invalid query, please check the syntax. API specifications are here: http:\/\/www.freedesktop.org\/wiki\/Specifications\/open-collaboration-services.\n"},"data":[]}}
  29. )";
  30. static QByteArray fake400Response = R"(
  31. {"ocs":{"meta":{"status":"failure","statuscode":400,"message":"Parameter is incorrect.\n"},"data":[]}}
  32. )";
  33. static QByteArray fake500Response = R"(
  34. {"ocs":{"meta":{"status":"failure","statuscode":500,"message":"Internal Server Error.\n"},"data":[]}}
  35. )";
  36. class FakeRemoteActivityStorage
  37. {
  38. FakeRemoteActivityStorage() = default;
  39. public:
  40. static FakeRemoteActivityStorage *instance()
  41. {
  42. if (!_instance) {
  43. _instance = new FakeRemoteActivityStorage();
  44. _instance->init();
  45. }
  46. return _instance;
  47. }
  48. static void destroy()
  49. {
  50. if (_instance) {
  51. delete _instance;
  52. }
  53. _instance = nullptr;
  54. }
  55. void init()
  56. {
  57. if (!_activityData.isEmpty()) {
  58. return;
  59. }
  60. _metaSuccess = {{QStringLiteral("status"), QStringLiteral("ok")}, {QStringLiteral("statuscode"), 200},
  61. {QStringLiteral("message"), QStringLiteral("OK")}};
  62. initActivityData();
  63. }
  64. void initActivityData()
  65. {
  66. // Insert activity data
  67. for (quint32 i = 0; i <= _numItemsToInsert; i++) {
  68. QJsonObject activity;
  69. activity.insert(QStringLiteral("object_type"), "files");
  70. activity.insert(QStringLiteral("activity_id"), _startingId);
  71. activity.insert(QStringLiteral("type"), QStringLiteral("file"));
  72. activity.insert(QStringLiteral("subject"), QStringLiteral("You created %1.txt").arg(i));
  73. activity.insert(QStringLiteral("message"), QStringLiteral(""));
  74. activity.insert(QStringLiteral("object_name"), QStringLiteral("%1.txt").arg(i));
  75. activity.insert(QStringLiteral("datetime"), QDateTime::currentDateTime().toString(Qt::ISODate));
  76. activity.insert(QStringLiteral("icon"), QStringLiteral("http://example.de/apps/files/img/add-color.svg"));
  77. _activityData.push_back(activity);
  78. _startingId++;
  79. }
  80. // Insert notification data
  81. for (quint32 i = 0; i < _numItemsToInsert; i++) {
  82. QJsonObject activity;
  83. activity.insert(QStringLiteral("activity_id"), _startingId);
  84. activity.insert(QStringLiteral("object_type"), "calendar");
  85. activity.insert(QStringLiteral("type"), QStringLiteral("calendar-event"));
  86. activity.insert(
  87. QStringLiteral("subject"), QStringLiteral("You created event %1 in calendar Events").arg(i));
  88. activity.insert(QStringLiteral("message"), QStringLiteral(""));
  89. activity.insert(QStringLiteral("object_name"), QStringLiteral(""));
  90. activity.insert(QStringLiteral("datetime"), QDateTime::currentDateTime().toString(Qt::ISODate));
  91. activity.insert(QStringLiteral("icon"), QStringLiteral("http://example.de/core/img/places/calendar.svg"));
  92. QJsonArray actionsArray;
  93. QJsonObject secondaryAction;
  94. secondaryAction.insert(QStringLiteral("label"), QStringLiteral("Dismiss"));
  95. secondaryAction.insert(QStringLiteral("link"),
  96. QString(QStringLiteral("http://cloud.example.de/remote.php/dav")
  97. + QStringLiteral("ocs/v2.php/apps/notifications/api/v2/notifications") + QString::number(i)));
  98. secondaryAction.insert(QStringLiteral("type"), QStringLiteral("DELETE"));
  99. secondaryAction.insert(QStringLiteral("primary"), false);
  100. actionsArray.push_back(secondaryAction);
  101. _activityData.push_back(activity);
  102. _startingId++;
  103. }
  104. // Insert notification data
  105. for (quint32 i = 0; i < _numItemsToInsert; i++) {
  106. QJsonObject activity;
  107. activity.insert(QStringLiteral("activity_id"), _startingId);
  108. activity.insert(QStringLiteral("object_type"), "chat");
  109. activity.insert(QStringLiteral("type"), QStringLiteral("chat"));
  110. activity.insert(QStringLiteral("subject"), QStringLiteral("You have received %1's message").arg(i));
  111. activity.insert(QStringLiteral("message"), QStringLiteral(""));
  112. activity.insert(QStringLiteral("object_name"), QStringLiteral(""));
  113. activity.insert(QStringLiteral("datetime"), QDateTime::currentDateTime().toString(Qt::ISODate));
  114. activity.insert(QStringLiteral("icon"), QStringLiteral("http://example.de/core/img/places/talk.svg"));
  115. QJsonArray actionsArray;
  116. QJsonObject primaryAction;
  117. primaryAction.insert(QStringLiteral("label"), QStringLiteral("View chat"));
  118. primaryAction.insert(QStringLiteral("link"), QStringLiteral("http://cloud.example.de/call/9p4vjdzd"));
  119. primaryAction.insert(QStringLiteral("type"), QStringLiteral("WEB"));
  120. primaryAction.insert(QStringLiteral("primary"), true);
  121. actionsArray.push_back(primaryAction);
  122. QJsonObject secondaryAction;
  123. secondaryAction.insert(QStringLiteral("label"), QStringLiteral("Dismiss"));
  124. secondaryAction.insert(QStringLiteral("link"),
  125. QString(QStringLiteral("http://cloud.example.de/remote.php/dav")
  126. + QStringLiteral("ocs/v2.php/apps/notifications/api/v2/notifications") + QString::number(i)));
  127. secondaryAction.insert(QStringLiteral("type"), QStringLiteral("DELETE"));
  128. secondaryAction.insert(QStringLiteral("primary"), false);
  129. actionsArray.push_back(secondaryAction);
  130. QJsonObject additionalAction;
  131. additionalAction.insert(QStringLiteral("label"), QStringLiteral("Additional 1"));
  132. additionalAction.insert(QStringLiteral("link"), QStringLiteral("http://cloud.example.de/call/9p4vjdzd"));
  133. additionalAction.insert(QStringLiteral("type"), QStringLiteral("POST"));
  134. additionalAction.insert(QStringLiteral("primary"), false);
  135. actionsArray.push_back(additionalAction);
  136. additionalAction.insert(QStringLiteral("label"), QStringLiteral("Additional 2"));
  137. actionsArray.push_back(additionalAction);
  138. activity.insert(QStringLiteral("actions"), actionsArray);
  139. _activityData.push_back(activity);
  140. _startingId++;
  141. }
  142. // Insert notification data
  143. for (quint32 i = 0; i < _numItemsToInsert; i++) {
  144. QJsonObject activity;
  145. activity.insert(QStringLiteral("activity_id"), _startingId);
  146. activity.insert(QStringLiteral("object_type"), "room");
  147. activity.insert(QStringLiteral("type"), QStringLiteral("room"));
  148. activity.insert(QStringLiteral("subject"), QStringLiteral("You have been invited into room%1").arg(i));
  149. activity.insert(QStringLiteral("message"), QStringLiteral(""));
  150. activity.insert(QStringLiteral("object_name"), QStringLiteral(""));
  151. activity.insert(QStringLiteral("datetime"), QDateTime::currentDateTime().toString(Qt::ISODate));
  152. activity.insert(QStringLiteral("icon"), QStringLiteral("http://example.de/core/img/places/talk.svg"));
  153. QJsonArray actionsArray;
  154. QJsonObject primaryAction;
  155. primaryAction.insert(QStringLiteral("label"), QStringLiteral("View chat"));
  156. primaryAction.insert(QStringLiteral("link"), QStringLiteral("http://cloud.example.de/call/9p4vjdzd"));
  157. primaryAction.insert(QStringLiteral("type"), QStringLiteral("WEB"));
  158. primaryAction.insert(QStringLiteral("primary"), true);
  159. actionsArray.push_back(primaryAction);
  160. QJsonObject secondaryAction;
  161. secondaryAction.insert(QStringLiteral("label"), QStringLiteral("Dismiss"));
  162. secondaryAction.insert(QStringLiteral("link"),
  163. QString(QStringLiteral("http://cloud.example.de/remote.php/dav")
  164. + QStringLiteral("ocs/v2.php/apps/notifications/api/v2/notifications") + QString::number(i)));
  165. secondaryAction.insert(QStringLiteral("type"), QStringLiteral("DELETE"));
  166. secondaryAction.insert(QStringLiteral("primary"), false);
  167. actionsArray.push_back(secondaryAction);
  168. activity.insert(QStringLiteral("actions"), actionsArray);
  169. _activityData.push_back(activity);
  170. _startingId++;
  171. }
  172. // Insert notification data
  173. for (quint32 i = 0; i < _numItemsToInsert; i++) {
  174. QJsonObject activity;
  175. activity.insert(QStringLiteral("activity_id"), _startingId);
  176. activity.insert(QStringLiteral("object_type"), "call");
  177. activity.insert(QStringLiteral("type"), QStringLiteral("call"));
  178. activity.insert(QStringLiteral("subject"), QStringLiteral("You have missed a %1's call").arg(i));
  179. activity.insert(QStringLiteral("message"), QStringLiteral(""));
  180. activity.insert(QStringLiteral("object_name"), QStringLiteral(""));
  181. activity.insert(QStringLiteral("datetime"), QDateTime::currentDateTime().toString(Qt::ISODate));
  182. activity.insert(QStringLiteral("icon"), QStringLiteral("http://example.de/core/img/places/talk.svg"));
  183. QJsonArray actionsArray;
  184. QJsonObject primaryAction;
  185. primaryAction.insert(QStringLiteral("label"), QStringLiteral("Call back"));
  186. primaryAction.insert(QStringLiteral("link"), QStringLiteral("http://cloud.example.de/call/9p4vjdzd"));
  187. primaryAction.insert(QStringLiteral("type"), QStringLiteral("WEB"));
  188. primaryAction.insert(QStringLiteral("primary"), true);
  189. actionsArray.push_back(primaryAction);
  190. QJsonObject secondaryAction;
  191. secondaryAction.insert(QStringLiteral("label"), QStringLiteral("Dismiss"));
  192. secondaryAction.insert(QStringLiteral("link"),
  193. QString(QStringLiteral("http://cloud.example.de/remote.php/dav")
  194. + QStringLiteral("ocs/v2.php/apps/notifications/api/v2/notifications") + QString::number(i)));
  195. secondaryAction.insert(QStringLiteral("type"), QStringLiteral("DELETE"));
  196. secondaryAction.insert(QStringLiteral("primary"), false);
  197. actionsArray.push_back(secondaryAction);
  198. activity.insert(QStringLiteral("actions"), actionsArray);
  199. _activityData.push_back(activity);
  200. _startingId++;
  201. }
  202. _startingId--;
  203. }
  204. const QByteArray activityJsonData(int sinceId, int limit)
  205. {
  206. QJsonArray data;
  207. const auto itFound = std::find_if(
  208. std::cbegin(_activityData), std::cend(_activityData), [&sinceId](const QJsonValue &currentActivityValue) {
  209. const auto currentActivityId =
  210. currentActivityValue.toObject().value(QStringLiteral("activity_id")).toInt();
  211. return currentActivityId == sinceId;
  212. });
  213. const int startIndex = itFound != std::cend(_activityData)
  214. ? static_cast<int>(std::distance(std::cbegin(_activityData), itFound))
  215. : -1;
  216. if (startIndex > 0) {
  217. for (int dataIndex = startIndex, iteration = 0; dataIndex >= 0 && iteration < limit;
  218. --dataIndex, ++iteration) {
  219. if (_activityData[dataIndex].toObject().value(QStringLiteral("activity_id")).toInt()
  220. > sinceId - limit) {
  221. data.append(_activityData[dataIndex]);
  222. }
  223. }
  224. }
  225. QJsonObject root;
  226. QJsonObject ocs;
  227. ocs.insert(QStringLiteral("data"), data);
  228. root.insert(QStringLiteral("ocs"), ocs);
  229. return QJsonDocument(root).toJson();
  230. }
  231. QJsonValue activityById(int id)
  232. {
  233. const auto itFound = std::find_if(
  234. std::cbegin(_activityData), std::cend(_activityData), [&id](const QJsonValue &currentActivityValue) {
  235. const auto currentActivityId =
  236. currentActivityValue.toObject().value(QStringLiteral("activity_id")).toInt();
  237. return currentActivityId == id;
  238. });
  239. if (itFound != std::cend(_activityData)) {
  240. return (*itFound);
  241. }
  242. return {};
  243. }
  244. int startingIdLast() const { return _startingId; }
  245. private:
  246. static FakeRemoteActivityStorage *_instance;
  247. QJsonArray _activityData;
  248. QVariantMap _metaSuccess;
  249. quint32 _numItemsToInsert = 30;
  250. int _startingId = startingId;
  251. };
  252. FakeRemoteActivityStorage *FakeRemoteActivityStorage::_instance = nullptr;
  253. class TestingALM : public OCC::ActivityListModel
  254. {
  255. Q_OBJECT
  256. public:
  257. TestingALM() = default;
  258. void startFetchJob() override
  259. {
  260. auto *job = new OCC::JsonApiJob(
  261. accountState()->account(), QLatin1String("ocs/v2.php/apps/activity/api/v2/activity"), this);
  262. QObject::connect(this, &TestingALM::activityJobStatusCode, this, &TestingALM::slotProcessReceivedActivities);
  263. QObject::connect(job, &OCC::JsonApiJob::jsonReceived, this, &TestingALM::activitiesReceived);
  264. QUrlQuery params;
  265. params.addQueryItem(QLatin1String("since"), QString::number(currentItem()));
  266. params.addQueryItem(QLatin1String("limit"), QString::number(50));
  267. job->addQueryParams(params);
  268. job->start();
  269. }
  270. public slots:
  271. void slotProcessReceivedActivities()
  272. {
  273. if (rowCount() > _numRowsPrev) {
  274. auto finalListCopy = finalList();
  275. for (int i = _numRowsPrev; i < rowCount(); ++i) {
  276. const auto modelIndex = index(i, 0);
  277. auto activity = finalListCopy.at(modelIndex.row());
  278. if (activity._links.isEmpty()) {
  279. const auto activityJsonObject = FakeRemoteActivityStorage::instance()->activityById(activity._id);
  280. if (!activityJsonObject.isNull()) {
  281. // because "_links" are normally populated within the notificationhandler.cpp, which we don't run as part of this unit test, we have to fill them here
  282. // TODO: move the logic to populate "_links" to "activitylistmodel.cpp"
  283. auto actions = activityJsonObject.toObject().value("actions").toArray();
  284. foreach (auto action, actions) {
  285. activity._links.append(OCC::ActivityLink::createFomJsonObject(action.toObject()));
  286. }
  287. finalListCopy[modelIndex.row()] = activity;
  288. }
  289. }
  290. }
  291. setFinalList(finalListCopy);
  292. }
  293. _numRowsPrev = rowCount();
  294. emit activitiesProcessed();
  295. }
  296. signals:
  297. void activitiesProcessed();
  298. private:
  299. int _numRowsPrev = 0;
  300. };
  301. class TestActivityListModel : public QObject
  302. {
  303. Q_OBJECT
  304. public:
  305. TestActivityListModel() = default;
  306. ~TestActivityListModel() override {
  307. OCC::AccountManager::instance()->deleteAccount(accountState.data());
  308. }
  309. QScopedPointer<FakeQNAM> fakeQnam;
  310. OCC::AccountPtr account;
  311. QScopedPointer<OCC::AccountState> accountState;
  312. OCC::Activity testNotificationActivity;
  313. static constexpr int searchResultsReplyDelay = 100;
  314. private slots:
  315. void initTestCase()
  316. {
  317. fakeQnam.reset(new FakeQNAM({}));
  318. account = OCC::Account::create();
  319. account->setCredentials(new FakeCredentials{fakeQnam.data()});
  320. account->setUrl(QUrl(("http://example.de")));
  321. accountState.reset(new OCC::AccountState(account));
  322. fakeQnam->setOverride([this](QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *device) {
  323. Q_UNUSED(device);
  324. QNetworkReply *reply = nullptr;
  325. const auto urlQuery = QUrlQuery(req.url());
  326. const auto format = urlQuery.queryItemValue(QStringLiteral("format"));
  327. const auto since = urlQuery.queryItemValue(QStringLiteral("since")).toInt();
  328. const auto limit = urlQuery.queryItemValue(QStringLiteral("limit")).toInt();
  329. const auto path = req.url().path();
  330. if (!req.url().toString().startsWith(accountState->account()->url().toString())) {
  331. reply = new FakeErrorReply(op, req, this, 404, fake404Response);
  332. }
  333. if (format != QStringLiteral("json")) {
  334. reply = new FakeErrorReply(op, req, this, 400, fake400Response);
  335. }
  336. if (path.startsWith(QStringLiteral("/ocs/v2.php/apps/activity/api/v2/activity"))) {
  337. reply = new FakePayloadReply(op, req, FakeRemoteActivityStorage::instance()->activityJsonData(since, limit), searchResultsReplyDelay, fakeQnam.data());
  338. }
  339. if (!reply) {
  340. return qobject_cast<QNetworkReply*>(new FakeErrorReply(op, req, this, 404, QByteArrayLiteral("{error: \"Not found!\"}")));
  341. }
  342. return reply;
  343. });
  344. OCC::AccountManager::instance()->addAccount(account);
  345. // Activity comparison is done by checking type, id, and accName
  346. // We need an activity with these details, at least
  347. testNotificationActivity._accName = accountState->account()->displayName();
  348. testNotificationActivity._id = 1;
  349. testNotificationActivity._type = OCC::Activity::NotificationType;
  350. testNotificationActivity._dateTime = QDateTime::currentDateTime();
  351. };
  352. // Test receiving activity from server
  353. void testFetchingRemoteActivity() {
  354. TestingALM model;
  355. model.setAccountState(accountState.data());
  356. QAbstractItemModelTester modelTester(&model);
  357. QCOMPARE(model.rowCount(), 0);
  358. model.setCurrentItem(FakeRemoteActivityStorage::instance()->startingIdLast());
  359. model.startFetchJob();
  360. QSignalSpy activitiesJob(&model, &TestingALM::activitiesProcessed);
  361. QVERIFY(activitiesJob.wait(3000));
  362. QCOMPARE(model.rowCount(), 50);
  363. };
  364. // Test receiving activity from local user action
  365. void testLocalSyncFileAction() {
  366. TestingALM model;
  367. model.setAccountState(accountState.data());
  368. QAbstractItemModelTester modelTester(&model);
  369. QCOMPARE(model.rowCount(), 0);
  370. OCC::Activity activity;
  371. model.addSyncFileItemToActivityList(activity);
  372. QCOMPARE(model.rowCount(), 1);
  373. const auto index = model.index(0, 0);
  374. QVERIFY(index.isValid());
  375. };
  376. void testAddNotification() {
  377. TestingALM model;
  378. model.setAccountState(accountState.data());
  379. QAbstractItemModelTester modelTester(&model);
  380. QCOMPARE(model.rowCount(), 0);
  381. model.addNotificationToActivityList(testNotificationActivity);
  382. QCOMPARE(model.rowCount(), 1);
  383. const auto index = model.index(0, 0);
  384. QVERIFY(index.isValid());
  385. };
  386. void testAddError() {
  387. TestingALM model;
  388. model.setAccountState(accountState.data());
  389. QAbstractItemModelTester modelTester(&model);
  390. QCOMPARE(model.rowCount(), 0);
  391. OCC::Activity activity;
  392. model.addErrorToActivityList(activity);
  393. QCOMPARE(model.rowCount(), 1);
  394. const auto index = model.index(0, 0);
  395. QVERIFY(index.isValid());
  396. };
  397. void testAddIgnoredFile() {
  398. TestingALM model;
  399. model.setAccountState(accountState.data());
  400. QAbstractItemModelTester modelTester(&model);
  401. QCOMPARE(model.rowCount(), 0);
  402. OCC::Activity activity;
  403. activity._folder = QStringLiteral("thingy");
  404. activity._file = QStringLiteral("test.txt");
  405. model.addIgnoredFileToList(activity);
  406. // We need to add another activity to the model for the combineActivityLists method to be called
  407. model.addNotificationToActivityList(testNotificationActivity);
  408. QCOMPARE(model.rowCount(), 2);
  409. const auto index = model.index(0, 0);
  410. QVERIFY(index.isValid());
  411. };
  412. // Test removing activity from list
  413. void testRemoveActivityWithRow() {
  414. TestingALM model;
  415. model.setAccountState(accountState.data());
  416. QAbstractItemModelTester modelTester(&model);
  417. QCOMPARE(model.rowCount(), 0);
  418. model.addNotificationToActivityList(testNotificationActivity);
  419. QCOMPARE(model.rowCount(), 1);
  420. model.removeActivityFromActivityList(0);
  421. QCOMPARE(model.rowCount(), 0);
  422. }
  423. void testRemoveActivityWithActivity() {
  424. TestingALM model;
  425. model.setAccountState(accountState.data());
  426. QAbstractItemModelTester modelTester(&model);
  427. QCOMPARE(model.rowCount(), 0);
  428. model.addNotificationToActivityList(testNotificationActivity);
  429. QCOMPARE(model.rowCount(), 1);
  430. model.removeActivityFromActivityList(testNotificationActivity);
  431. QCOMPARE(model.rowCount(), 0);
  432. }
  433. // Test getting the data from the model
  434. void testData() {
  435. TestingALM model;
  436. model.setAccountState(accountState.data());
  437. QAbstractItemModelTester modelTester(&model);
  438. QCOMPARE(model.rowCount(), 0);
  439. model.setCurrentItem(FakeRemoteActivityStorage::instance()->startingIdLast());
  440. model.startFetchJob();
  441. QSignalSpy activitiesJob(&model, &TestingALM::activitiesProcessed);
  442. QVERIFY(activitiesJob.wait(3000));
  443. QCOMPARE(model.rowCount(), 50);
  444. model.addNotificationToActivityList(testNotificationActivity);
  445. QCOMPARE(model.rowCount(), 51);
  446. OCC::Activity syncResultActivity;
  447. syncResultActivity._id = 2;
  448. syncResultActivity._type = OCC::Activity::SyncResultType;
  449. syncResultActivity._status = OCC::SyncResult::Error;
  450. syncResultActivity._dateTime = QDateTime::currentDateTime();
  451. syncResultActivity._subject = QStringLiteral("Sample failed sync text");
  452. syncResultActivity._message = QStringLiteral("/path/to/thingy");
  453. syncResultActivity._link = QStringLiteral("/path/to/thingy");
  454. syncResultActivity._accName = accountState->account()->displayName();
  455. model.addSyncFileItemToActivityList(syncResultActivity);
  456. QCOMPARE(model.rowCount(), 52);
  457. OCC::Activity syncFileItemActivity;
  458. syncFileItemActivity._id = 3;
  459. syncFileItemActivity._type = OCC::Activity::SyncFileItemType; //client activity
  460. syncFileItemActivity._status = OCC::SyncFileItem::Success;
  461. syncFileItemActivity._dateTime = QDateTime::currentDateTime();
  462. syncFileItemActivity._message = QStringLiteral("You created xyz.pdf");
  463. syncFileItemActivity._link = accountState->account()->url();
  464. syncFileItemActivity._accName = accountState->account()->displayName();
  465. syncFileItemActivity._file = QStringLiteral("xyz.pdf");
  466. syncFileItemActivity._fileAction = "";
  467. model.addSyncFileItemToActivityList(syncFileItemActivity);
  468. QCOMPARE(model.rowCount(), 53);
  469. // Test all rows for things in common
  470. for (int i = 0; i < model.rowCount(); i++) {
  471. const auto index = model.index(i, 0);
  472. auto text = index.data(OCC::ActivityListModel::ActionTextRole).toString();
  473. QVERIFY(index.data(OCC::ActivityListModel::ActionRole).canConvert<int>());
  474. const auto type = index.data(OCC::ActivityListModel::ActionRole).toInt();
  475. QVERIFY(type >= OCC::Activity::ActivityType);
  476. QVERIFY(!index.data(OCC::ActivityListModel::AccountRole).toString().isEmpty());
  477. QVERIFY(!index.data(OCC::ActivityListModel::ActionTextColorRole).toString().isEmpty());
  478. QVERIFY(!index.data(OCC::ActivityListModel::ActionIconRole).toString().isEmpty());
  479. QVERIFY(!index.data(OCC::ActivityListModel::PointInTimeRole).toString().isEmpty());
  480. QVERIFY(index.data(OCC::ActivityListModel::ObjectTypeRole).canConvert<int>());
  481. QVERIFY(index.data(OCC::ActivityListModel::ObjectNameRole).canConvert<QString>());
  482. QVERIFY(index.data(OCC::ActivityListModel::ObjectIdRole).canConvert<int>());
  483. QVERIFY(index.data(OCC::ActivityListModel::ActionsLinksRole).canConvert<QList<QVariant>>());
  484. QVERIFY(index.data(OCC::ActivityListModel::ActionTextRole).canConvert<QString>());
  485. QVERIFY(index.data(OCC::ActivityListModel::MessageRole).canConvert<QString>());
  486. QVERIFY(index.data(OCC::ActivityListModel::LinkRole).canConvert<QUrl>());
  487. QVERIFY(index.data(OCC::ActivityListModel::AccountConnectedRole).canConvert<bool>());
  488. QVERIFY(index.data(OCC::ActivityListModel::DisplayActions).canConvert<bool>());
  489. // Unfortunately, trying to check anything relating to filepaths causes a crash
  490. // when the folder manager is invoked by the model to look for the relevant file
  491. }
  492. };
  493. void tesActivityActionstData()
  494. {
  495. TestingALM model;
  496. model.setAccountState(accountState.data());
  497. QAbstractItemModelTester modelTester(&model);
  498. QCOMPARE(model.rowCount(), 0);
  499. model.setCurrentItem(FakeRemoteActivityStorage::instance()->startingIdLast());
  500. int prevModelRowCount = model.rowCount();
  501. do {
  502. prevModelRowCount = model.rowCount();
  503. model.startFetchJob();
  504. QSignalSpy activitiesJob(&model, &TestingALM::activitiesProcessed);
  505. QVERIFY(activitiesJob.wait(3000));
  506. for (int i = prevModelRowCount; i < model.rowCount(); i++) {
  507. const auto index = model.index(i, 0);
  508. const auto actionsLinks = index.data(OCC::ActivityListModel::ActionsLinksRole).toList();
  509. if (!actionsLinks.isEmpty()) {
  510. const auto actionsLinksContextMenu =
  511. index.data(OCC::ActivityListModel::ActionsLinksContextMenuRole).toList();
  512. // context menu must be shorter than total action links
  513. QVERIFY(actionsLinks.isEmpty() || actionsLinksContextMenu.size() < actionsLinks.size());
  514. // context menu must not contain the primary action
  515. QVERIFY(std::find_if(std::begin(actionsLinksContextMenu), std::end(actionsLinksContextMenu),
  516. [](const QVariant &entry) { return entry.value<OCC::ActivityLink>()._primary; })
  517. == std::end(actionsLinksContextMenu));
  518. const auto objectType = index.data(OCC::ActivityListModel::ObjectTypeRole).toString();
  519. if ((objectType == QStringLiteral("chat") || objectType == QStringLiteral("call")
  520. || objectType == QStringLiteral("room"))) {
  521. const auto actionButtonsLinks =
  522. index.data(OCC::ActivityListModel::ActionsLinksForActionButtonsRole).toList();
  523. // both action links and buttons must contain a "WEB" verb element at the beginning
  524. QVERIFY(actionsLinks[0].value<OCC::ActivityLink>()._verb == QStringLiteral("WEB"));
  525. QVERIFY(actionButtonsLinks[0].value<OCC::ActivityLink>()._verb == QStringLiteral("WEB"));
  526. // the first action button for chat must have image set
  527. QVERIFY(!actionButtonsLinks[0].value<OCC::ActivityLink>()._imageSource.isEmpty());
  528. QVERIFY(!actionButtonsLinks[0].value<OCC::ActivityLink>()._imageSourceHovered.isEmpty());
  529. // logic for "chat" and other types of activities with multiple actions
  530. if ((objectType == QStringLiteral("chat")
  531. || (objectType != QStringLiteral("room") && objectType != QStringLiteral("call")))) {
  532. // button's label for "chat" must be renamed to "Reply"
  533. QVERIFY(actionButtonsLinks[0].value<OCC::ActivityLink>()._label == QObject::tr("Reply"));
  534. if (static_cast<quint32>(actionsLinks.size()) > OCC::ActivityListModel::maxActionButtons()) {
  535. // in case total actions is longer than ActivityListModel::maxActionButtons, only one button must be present in a list of action buttons
  536. QVERIFY(actionButtonsLinks.size() == 1);
  537. const auto actionButtonsAndContextMenuEntries = actionButtonsLinks + actionsLinksContextMenu;
  538. // in case total actions is longer than ActivityListModel::maxActionButtons, then a sum of action buttons and action menu entries must be equal to a total of action links
  539. QVERIFY(actionButtonsLinks.size() + actionsLinksContextMenu.size() == actionsLinks.size());
  540. } else {
  541. // in case a total of actions is less or equal to than ActivityListModel::maxActionButtons, then the length of action buttons must be greater than 1 and should contain "Mark as read" button at the end
  542. QVERIFY(actionButtonsLinks.size() > 1);
  543. QVERIFY(actionButtonsLinks[1].value<OCC::ActivityLink>()._label
  544. == QObject::tr("Mark as read"));
  545. }
  546. } else if ((objectType == QStringLiteral("call"))) {
  547. QVERIFY(
  548. actionButtonsLinks[0].value<OCC::ActivityLink>()._label == QStringLiteral("Call back"));
  549. }
  550. } else {
  551. QVERIFY(actionsLinks[0].value<OCC::ActivityLink>()._label == QStringLiteral("Dismiss"));
  552. }
  553. }
  554. }
  555. } while (prevModelRowCount < model.rowCount());
  556. };
  557. };
  558. QTEST_MAIN(TestActivityListModel)
  559. #include "testactivitylistmodel.moc"