testactivitylistmodel.cpp 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729
  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 replyAction;
  117. replyAction.insert(QStringLiteral("label"), QStringLiteral("Reply"));
  118. replyAction.insert(QStringLiteral("link"), QStringLiteral(""));
  119. replyAction.insert(QStringLiteral("type"), QStringLiteral("REPLY"));
  120. replyAction.insert(QStringLiteral("primary"), true);
  121. actionsArray.push_back(replyAction);
  122. QJsonObject primaryAction;
  123. primaryAction.insert(QStringLiteral("label"), QStringLiteral("View chat"));
  124. primaryAction.insert(QStringLiteral("link"), QStringLiteral("http://cloud.example.de/call/9p4vjdzd"));
  125. primaryAction.insert(QStringLiteral("type"), QStringLiteral("WEB"));
  126. primaryAction.insert(QStringLiteral("primary"), false);
  127. actionsArray.push_back(primaryAction);
  128. QJsonObject secondaryAction;
  129. secondaryAction.insert(QStringLiteral("label"), QStringLiteral("Dismiss"));
  130. secondaryAction.insert(QStringLiteral("link"),
  131. QString(QStringLiteral("http://cloud.example.de/remote.php/dav")
  132. + QStringLiteral("ocs/v2.php/apps/notifications/api/v2/notifications") + QString::number(i)));
  133. secondaryAction.insert(QStringLiteral("type"), QStringLiteral("DELETE"));
  134. secondaryAction.insert(QStringLiteral("primary"), false);
  135. actionsArray.push_back(secondaryAction);
  136. QJsonObject additionalAction;
  137. additionalAction.insert(QStringLiteral("label"), QStringLiteral("Additional 1"));
  138. additionalAction.insert(QStringLiteral("link"), QStringLiteral("http://cloud.example.de/call/9p4vjdzd"));
  139. additionalAction.insert(QStringLiteral("type"), QStringLiteral("POST"));
  140. additionalAction.insert(QStringLiteral("primary"), false);
  141. actionsArray.push_back(additionalAction);
  142. additionalAction.insert(QStringLiteral("label"), QStringLiteral("Additional 2"));
  143. actionsArray.push_back(additionalAction);
  144. activity.insert(QStringLiteral("actions"), actionsArray);
  145. _activityData.push_back(activity);
  146. _startingId++;
  147. }
  148. // Insert notification data
  149. for (quint32 i = 0; i < _numItemsToInsert; i++) {
  150. QJsonObject activity;
  151. activity.insert(QStringLiteral("activity_id"), _startingId);
  152. activity.insert(QStringLiteral("object_type"), "room");
  153. activity.insert(QStringLiteral("type"), QStringLiteral("room"));
  154. activity.insert(QStringLiteral("subject"), QStringLiteral("You have been invited into room%1").arg(i));
  155. activity.insert(QStringLiteral("message"), QStringLiteral(""));
  156. activity.insert(QStringLiteral("object_name"), QStringLiteral(""));
  157. activity.insert(QStringLiteral("datetime"), QDateTime::currentDateTime().toString(Qt::ISODate));
  158. activity.insert(QStringLiteral("icon"), QStringLiteral("http://example.de/core/img/places/talk.svg"));
  159. QJsonArray actionsArray;
  160. QJsonObject replyAction;
  161. replyAction.insert(QStringLiteral("label"), QStringLiteral("Reply"));
  162. replyAction.insert(QStringLiteral("link"), QStringLiteral(""));
  163. replyAction.insert(QStringLiteral("type"), QStringLiteral("REPLY"));
  164. replyAction.insert(QStringLiteral("primary"), true);
  165. actionsArray.push_back(replyAction);
  166. QJsonObject primaryAction;
  167. primaryAction.insert(QStringLiteral("label"), QStringLiteral("View chat"));
  168. primaryAction.insert(QStringLiteral("link"), QStringLiteral("http://cloud.example.de/call/9p4vjdzd"));
  169. primaryAction.insert(QStringLiteral("type"), QStringLiteral("WEB"));
  170. primaryAction.insert(QStringLiteral("primary"), false);
  171. actionsArray.push_back(primaryAction);
  172. QJsonObject secondaryAction;
  173. secondaryAction.insert(QStringLiteral("label"), QStringLiteral("Dismiss"));
  174. secondaryAction.insert(QStringLiteral("link"),
  175. QString(QStringLiteral("http://cloud.example.de/remote.php/dav")
  176. + QStringLiteral("ocs/v2.php/apps/notifications/api/v2/notifications") + QString::number(i)));
  177. secondaryAction.insert(QStringLiteral("type"), QStringLiteral("DELETE"));
  178. secondaryAction.insert(QStringLiteral("primary"), false);
  179. actionsArray.push_back(secondaryAction);
  180. activity.insert(QStringLiteral("actions"), actionsArray);
  181. _activityData.push_back(activity);
  182. _startingId++;
  183. }
  184. // Insert notification data
  185. for (quint32 i = 0; i < _numItemsToInsert; i++) {
  186. QJsonObject activity;
  187. activity.insert(QStringLiteral("activity_id"), _startingId);
  188. activity.insert(QStringLiteral("object_type"), "call");
  189. activity.insert(QStringLiteral("type"), QStringLiteral("call"));
  190. activity.insert(QStringLiteral("subject"), QStringLiteral("You have missed a %1's call").arg(i));
  191. activity.insert(QStringLiteral("message"), QStringLiteral(""));
  192. activity.insert(QStringLiteral("object_name"), QStringLiteral(""));
  193. activity.insert(QStringLiteral("datetime"), QDateTime::currentDateTime().toString(Qt::ISODate));
  194. activity.insert(QStringLiteral("icon"), QStringLiteral("http://example.de/core/img/places/talk.svg"));
  195. QJsonArray actionsArray;
  196. QJsonObject replyAction;
  197. replyAction.insert(QStringLiteral("label"), QStringLiteral("Reply"));
  198. replyAction.insert(QStringLiteral("link"), QStringLiteral(""));
  199. replyAction.insert(QStringLiteral("type"), QStringLiteral("REPLY"));
  200. replyAction.insert(QStringLiteral("primary"), true);
  201. actionsArray.push_back(replyAction);
  202. QJsonObject primaryAction;
  203. primaryAction.insert(QStringLiteral("label"), QStringLiteral("Call back"));
  204. primaryAction.insert(QStringLiteral("link"), QStringLiteral("http://cloud.example.de/call/9p4vjdzd"));
  205. primaryAction.insert(QStringLiteral("type"), QStringLiteral("WEB"));
  206. primaryAction.insert(QStringLiteral("primary"), false);
  207. actionsArray.push_back(primaryAction);
  208. QJsonObject secondaryAction;
  209. secondaryAction.insert(QStringLiteral("label"), QStringLiteral("Dismiss"));
  210. secondaryAction.insert(QStringLiteral("link"),
  211. QString(QStringLiteral("http://cloud.example.de/remote.php/dav")
  212. + QStringLiteral("ocs/v2.php/apps/notifications/api/v2/notifications") + QString::number(i)));
  213. secondaryAction.insert(QStringLiteral("type"), QStringLiteral("DELETE"));
  214. secondaryAction.insert(QStringLiteral("primary"), false);
  215. actionsArray.push_back(secondaryAction);
  216. activity.insert(QStringLiteral("actions"), actionsArray);
  217. _activityData.push_back(activity);
  218. _startingId++;
  219. }
  220. _startingId--;
  221. }
  222. const QByteArray activityJsonData(int sinceId, int limit)
  223. {
  224. QJsonArray data;
  225. const auto itFound = std::find_if(
  226. std::cbegin(_activityData), std::cend(_activityData), [&sinceId](const QJsonValue &currentActivityValue) {
  227. const auto currentActivityId =
  228. currentActivityValue.toObject().value(QStringLiteral("activity_id")).toInt();
  229. return currentActivityId == sinceId;
  230. });
  231. const int startIndex = itFound != std::cend(_activityData)
  232. ? static_cast<int>(std::distance(std::cbegin(_activityData), itFound))
  233. : -1;
  234. if (startIndex > 0) {
  235. for (int dataIndex = startIndex, iteration = 0; dataIndex >= 0 && iteration < limit;
  236. --dataIndex, ++iteration) {
  237. if (_activityData[dataIndex].toObject().value(QStringLiteral("activity_id")).toInt()
  238. > sinceId - limit) {
  239. data.append(_activityData[dataIndex]);
  240. }
  241. }
  242. }
  243. QJsonObject root;
  244. QJsonObject ocs;
  245. ocs.insert(QStringLiteral("data"), data);
  246. root.insert(QStringLiteral("ocs"), ocs);
  247. return QJsonDocument(root).toJson();
  248. }
  249. QJsonValue activityById(int id)
  250. {
  251. const auto itFound = std::find_if(
  252. std::cbegin(_activityData), std::cend(_activityData), [&id](const QJsonValue &currentActivityValue) {
  253. const auto currentActivityId =
  254. currentActivityValue.toObject().value(QStringLiteral("activity_id")).toInt();
  255. return currentActivityId == id;
  256. });
  257. if (itFound != std::cend(_activityData)) {
  258. return (*itFound);
  259. }
  260. return {};
  261. }
  262. int startingIdLast() const { return _startingId; }
  263. private:
  264. static FakeRemoteActivityStorage *_instance;
  265. QJsonArray _activityData;
  266. QVariantMap _metaSuccess;
  267. quint32 _numItemsToInsert = 30;
  268. int _startingId = startingId;
  269. };
  270. FakeRemoteActivityStorage *FakeRemoteActivityStorage::_instance = nullptr;
  271. class TestingALM : public OCC::ActivityListModel
  272. {
  273. Q_OBJECT
  274. public:
  275. TestingALM() = default;
  276. void startFetchJob() override
  277. {
  278. auto *job = new OCC::JsonApiJob(
  279. accountState()->account(), QLatin1String("ocs/v2.php/apps/activity/api/v2/activity"), this);
  280. QObject::connect(this, &TestingALM::activityJobStatusCode, this, &TestingALM::slotProcessReceivedActivities);
  281. QObject::connect(job, &OCC::JsonApiJob::jsonReceived, this, &TestingALM::activitiesReceived);
  282. QUrlQuery params;
  283. params.addQueryItem(QLatin1String("since"), QString::number(currentItem()));
  284. params.addQueryItem(QLatin1String("limit"), QString::number(50));
  285. job->addQueryParams(params);
  286. job->start();
  287. }
  288. public slots:
  289. void slotProcessReceivedActivities()
  290. {
  291. if (rowCount() > _numRowsPrev) {
  292. auto finalListCopy = finalList();
  293. for (int i = _numRowsPrev; i < rowCount(); ++i) {
  294. const auto modelIndex = index(i, 0);
  295. auto activity = finalListCopy.at(modelIndex.row());
  296. if (activity._links.isEmpty()) {
  297. const auto activityJsonObject = FakeRemoteActivityStorage::instance()->activityById(activity._id);
  298. if (!activityJsonObject.isNull()) {
  299. // 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
  300. // TODO: move the logic to populate "_links" to "activitylistmodel.cpp"
  301. auto actions = activityJsonObject.toObject().value("actions").toArray();
  302. foreach (auto action, actions) {
  303. activity._links.append(OCC::ActivityLink::createFomJsonObject(action.toObject()));
  304. }
  305. finalListCopy[modelIndex.row()] = activity;
  306. }
  307. }
  308. }
  309. setFinalList(finalListCopy);
  310. }
  311. _numRowsPrev = rowCount();
  312. emit activitiesProcessed();
  313. }
  314. signals:
  315. void activitiesProcessed();
  316. private:
  317. int _numRowsPrev = 0;
  318. };
  319. class TestActivityListModel : public QObject
  320. {
  321. Q_OBJECT
  322. public:
  323. TestActivityListModel() = default;
  324. ~TestActivityListModel() override {
  325. OCC::AccountManager::instance()->deleteAccount(accountState.data());
  326. }
  327. QScopedPointer<FakeQNAM> fakeQnam;
  328. OCC::AccountPtr account;
  329. QScopedPointer<OCC::AccountState> accountState;
  330. OCC::Activity testNotificationActivity;
  331. static constexpr int searchResultsReplyDelay = 100;
  332. private slots:
  333. void initTestCase()
  334. {
  335. fakeQnam.reset(new FakeQNAM({}));
  336. account = OCC::Account::create();
  337. account->setCredentials(new FakeCredentials{fakeQnam.data()});
  338. account->setUrl(QUrl(("http://example.de")));
  339. accountState.reset(new OCC::AccountState(account));
  340. fakeQnam->setOverride([this](QNetworkAccessManager::Operation op, const QNetworkRequest &req, QIODevice *device) {
  341. Q_UNUSED(device);
  342. QNetworkReply *reply = nullptr;
  343. const auto urlQuery = QUrlQuery(req.url());
  344. const auto format = urlQuery.queryItemValue(QStringLiteral("format"));
  345. const auto since = urlQuery.queryItemValue(QStringLiteral("since")).toInt();
  346. const auto limit = urlQuery.queryItemValue(QStringLiteral("limit")).toInt();
  347. const auto path = req.url().path();
  348. if (!req.url().toString().startsWith(accountState->account()->url().toString())) {
  349. reply = new FakeErrorReply(op, req, this, 404, fake404Response);
  350. }
  351. if (format != QStringLiteral("json")) {
  352. reply = new FakeErrorReply(op, req, this, 400, fake400Response);
  353. }
  354. if (path.startsWith(QStringLiteral("/ocs/v2.php/apps/activity/api/v2/activity"))) {
  355. reply = new FakePayloadReply(op, req, FakeRemoteActivityStorage::instance()->activityJsonData(since, limit), searchResultsReplyDelay, fakeQnam.data());
  356. }
  357. if (!reply) {
  358. return qobject_cast<QNetworkReply*>(new FakeErrorReply(op, req, this, 404, QByteArrayLiteral("{error: \"Not found!\"}")));
  359. }
  360. return reply;
  361. });
  362. OCC::AccountManager::instance()->addAccount(account);
  363. // Activity comparison is done by checking type, id, and accName
  364. // We need an activity with these details, at least
  365. testNotificationActivity._accName = accountState->account()->displayName();
  366. testNotificationActivity._id = 1;
  367. testNotificationActivity._type = OCC::Activity::NotificationType;
  368. testNotificationActivity._dateTime = QDateTime::currentDateTime();
  369. };
  370. // Test receiving activity from server
  371. void testFetchingRemoteActivity() {
  372. TestingALM model;
  373. model.setAccountState(accountState.data());
  374. QAbstractItemModelTester modelTester(&model);
  375. QCOMPARE(model.rowCount(), 0);
  376. model.setCurrentItem(FakeRemoteActivityStorage::instance()->startingIdLast());
  377. model.startFetchJob();
  378. QSignalSpy activitiesJob(&model, &TestingALM::activitiesProcessed);
  379. QVERIFY(activitiesJob.wait(3000));
  380. QCOMPARE(model.rowCount(), 50);
  381. };
  382. // Test receiving activity from local user action
  383. void testLocalSyncFileAction() {
  384. TestingALM model;
  385. model.setAccountState(accountState.data());
  386. QAbstractItemModelTester modelTester(&model);
  387. QCOMPARE(model.rowCount(), 0);
  388. OCC::Activity activity;
  389. model.addSyncFileItemToActivityList(activity);
  390. QCOMPARE(model.rowCount(), 1);
  391. const auto index = model.index(0, 0);
  392. QVERIFY(index.isValid());
  393. };
  394. void testAddNotification() {
  395. TestingALM model;
  396. model.setAccountState(accountState.data());
  397. QAbstractItemModelTester modelTester(&model);
  398. QCOMPARE(model.rowCount(), 0);
  399. model.addNotificationToActivityList(testNotificationActivity);
  400. QCOMPARE(model.rowCount(), 1);
  401. const auto index = model.index(0, 0);
  402. QVERIFY(index.isValid());
  403. };
  404. void testAddError() {
  405. TestingALM model;
  406. model.setAccountState(accountState.data());
  407. QAbstractItemModelTester modelTester(&model);
  408. QCOMPARE(model.rowCount(), 0);
  409. OCC::Activity activity;
  410. model.addErrorToActivityList(activity);
  411. QCOMPARE(model.rowCount(), 1);
  412. const auto index = model.index(0, 0);
  413. QVERIFY(index.isValid());
  414. };
  415. void testAddIgnoredFile() {
  416. TestingALM model;
  417. model.setAccountState(accountState.data());
  418. QAbstractItemModelTester modelTester(&model);
  419. QCOMPARE(model.rowCount(), 0);
  420. OCC::Activity activity;
  421. activity._folder = QStringLiteral("thingy");
  422. activity._file = QStringLiteral("test.txt");
  423. model.addIgnoredFileToList(activity);
  424. // We need to add another activity to the model for the combineActivityLists method to be called
  425. model.addNotificationToActivityList(testNotificationActivity);
  426. QCOMPARE(model.rowCount(), 2);
  427. const auto index = model.index(0, 0);
  428. QVERIFY(index.isValid());
  429. };
  430. // Test removing activity from list
  431. void testRemoveActivityWithRow() {
  432. TestingALM model;
  433. model.setAccountState(accountState.data());
  434. QAbstractItemModelTester modelTester(&model);
  435. QCOMPARE(model.rowCount(), 0);
  436. model.addNotificationToActivityList(testNotificationActivity);
  437. QCOMPARE(model.rowCount(), 1);
  438. model.removeActivityFromActivityList(0);
  439. QCOMPARE(model.rowCount(), 0);
  440. }
  441. void testRemoveActivityWithActivity() {
  442. TestingALM model;
  443. model.setAccountState(accountState.data());
  444. QAbstractItemModelTester modelTester(&model);
  445. QCOMPARE(model.rowCount(), 0);
  446. model.addNotificationToActivityList(testNotificationActivity);
  447. QCOMPARE(model.rowCount(), 1);
  448. model.removeActivityFromActivityList(testNotificationActivity);
  449. QCOMPARE(model.rowCount(), 0);
  450. }
  451. // Test getting the data from the model
  452. void testData() {
  453. TestingALM model;
  454. model.setAccountState(accountState.data());
  455. QAbstractItemModelTester modelTester(&model);
  456. QCOMPARE(model.rowCount(), 0);
  457. model.setCurrentItem(FakeRemoteActivityStorage::instance()->startingIdLast());
  458. model.startFetchJob();
  459. QSignalSpy activitiesJob(&model, &TestingALM::activitiesProcessed);
  460. QVERIFY(activitiesJob.wait(3000));
  461. QCOMPARE(model.rowCount(), 50);
  462. model.addNotificationToActivityList(testNotificationActivity);
  463. QCOMPARE(model.rowCount(), 51);
  464. OCC::Activity syncResultActivity;
  465. syncResultActivity._id = 2;
  466. syncResultActivity._type = OCC::Activity::SyncResultType;
  467. syncResultActivity._status = OCC::SyncResult::Error;
  468. syncResultActivity._dateTime = QDateTime::currentDateTime();
  469. syncResultActivity._subject = QStringLiteral("Sample failed sync text");
  470. syncResultActivity._message = QStringLiteral("/path/to/thingy");
  471. syncResultActivity._link = QStringLiteral("/path/to/thingy");
  472. syncResultActivity._accName = accountState->account()->displayName();
  473. model.addSyncFileItemToActivityList(syncResultActivity);
  474. QCOMPARE(model.rowCount(), 52);
  475. OCC::Activity syncFileItemActivity;
  476. syncFileItemActivity._id = 3;
  477. syncFileItemActivity._type = OCC::Activity::SyncFileItemType; //client activity
  478. syncFileItemActivity._status = OCC::SyncFileItem::Success;
  479. syncFileItemActivity._dateTime = QDateTime::currentDateTime();
  480. syncFileItemActivity._message = QStringLiteral("You created xyz.pdf");
  481. syncFileItemActivity._link = accountState->account()->url();
  482. syncFileItemActivity._accName = accountState->account()->displayName();
  483. syncFileItemActivity._file = QStringLiteral("xyz.pdf");
  484. syncFileItemActivity._fileAction = "";
  485. model.addSyncFileItemToActivityList(syncFileItemActivity);
  486. QCOMPARE(model.rowCount(), 53);
  487. // Test all rows for things in common
  488. for (int i = 0; i < model.rowCount(); i++) {
  489. const auto index = model.index(i, 0);
  490. auto text = index.data(OCC::ActivityListModel::ActionTextRole).toString();
  491. QVERIFY(index.data(OCC::ActivityListModel::ActionRole).canConvert<int>());
  492. const auto type = index.data(OCC::ActivityListModel::ActionRole).toInt();
  493. QVERIFY(type >= OCC::Activity::ActivityType);
  494. QVERIFY(!index.data(OCC::ActivityListModel::AccountRole).toString().isEmpty());
  495. QVERIFY(!index.data(OCC::ActivityListModel::ActionTextColorRole).toString().isEmpty());
  496. QVERIFY(!index.data(OCC::ActivityListModel::DarkIconRole).toString().isEmpty());
  497. QVERIFY(!index.data(OCC::ActivityListModel::LightIconRole).toString().isEmpty());
  498. QVERIFY(!index.data(OCC::ActivityListModel::PointInTimeRole).toString().isEmpty());
  499. QVERIFY(index.data(OCC::ActivityListModel::ObjectTypeRole).canConvert<int>());
  500. QVERIFY(index.data(OCC::ActivityListModel::ObjectNameRole).canConvert<QString>());
  501. QVERIFY(index.data(OCC::ActivityListModel::ObjectIdRole).canConvert<int>());
  502. QVERIFY(index.data(OCC::ActivityListModel::ActionsLinksRole).canConvert<QList<QVariant>>());
  503. QVERIFY(index.data(OCC::ActivityListModel::ActionTextRole).canConvert<QString>());
  504. QVERIFY(index.data(OCC::ActivityListModel::MessageRole).canConvert<QString>());
  505. QVERIFY(index.data(OCC::ActivityListModel::LinkRole).canConvert<QUrl>());
  506. QVERIFY(index.data(OCC::ActivityListModel::AccountConnectedRole).canConvert<bool>());
  507. QVERIFY(index.data(OCC::ActivityListModel::DisplayActions).canConvert<bool>());
  508. QVERIFY(index.data(OCC::ActivityListModel::TalkNotificationConversationTokenRole).canConvert<QString>());
  509. QVERIFY(index.data(OCC::ActivityListModel::TalkNotificationMessageIdRole).canConvert<QString>());
  510. QVERIFY(index.data(OCC::ActivityListModel::TalkNotificationMessageSentRole).canConvert<QString>());
  511. // Unfortunately, trying to check anything relating to filepaths causes a crash
  512. // when the folder manager is invoked by the model to look for the relevant file
  513. }
  514. };
  515. void tesActivityActionstData()
  516. {
  517. TestingALM model;
  518. model.setAccountState(accountState.data());
  519. QAbstractItemModelTester modelTester(&model);
  520. QCOMPARE(model.rowCount(), 0);
  521. model.setCurrentItem(FakeRemoteActivityStorage::instance()->startingIdLast());
  522. int prevModelRowCount = model.rowCount();
  523. do {
  524. prevModelRowCount = model.rowCount();
  525. model.startFetchJob();
  526. QSignalSpy activitiesJob(&model, &TestingALM::activitiesProcessed);
  527. QVERIFY(activitiesJob.wait(3000));
  528. for (int i = prevModelRowCount; i < model.rowCount(); i++) {
  529. const auto index = model.index(i, 0);
  530. const auto actionsLinks = index.data(OCC::ActivityListModel::ActionsLinksRole).toList();
  531. if (!actionsLinks.isEmpty()) {
  532. const auto actionsLinksContextMenu =
  533. index.data(OCC::ActivityListModel::ActionsLinksContextMenuRole).toList();
  534. // context menu must be shorter than total action links
  535. QVERIFY(actionsLinks.isEmpty() || actionsLinksContextMenu.size() < actionsLinks.size());
  536. // context menu must not contain the primary action
  537. QVERIFY(std::find_if(std::begin(actionsLinksContextMenu), std::end(actionsLinksContextMenu),
  538. [](const QVariant &entry) { return entry.value<OCC::ActivityLink>()._primary; })
  539. == std::end(actionsLinksContextMenu));
  540. const auto objectType = index.data(OCC::ActivityListModel::ObjectTypeRole).toString();
  541. if ((objectType == QStringLiteral("chat") || objectType == QStringLiteral("call")
  542. || objectType == QStringLiteral("room"))) {
  543. const auto actionButtonsLinks =
  544. index.data(OCC::ActivityListModel::ActionsLinksForActionButtonsRole).toList();
  545. // both action links and buttons must contain a "REPLY" verb element at the beginning
  546. QVERIFY(actionsLinks[0].value<OCC::ActivityLink>()._verb == QStringLiteral("REPLY"));
  547. QVERIFY(actionButtonsLinks[0].value<OCC::ActivityLink>()._verb == QStringLiteral("REPLY"));
  548. // the first action button for chat must have image set
  549. QVERIFY(!actionButtonsLinks[0].value<OCC::ActivityLink>()._imageSource.isEmpty());
  550. QVERIFY(!actionButtonsLinks[0].value<OCC::ActivityLink>()._imageSourceHovered.isEmpty());
  551. // logic for "chat" and other types of activities with multiple actions
  552. if ((objectType == QStringLiteral("chat")
  553. || (objectType != QStringLiteral("room") && objectType != QStringLiteral("call")))) {
  554. // button's label for "chat" must be renamed to "Reply"
  555. QVERIFY(actionButtonsLinks[0].value<OCC::ActivityLink>()._label == QObject::tr("Reply"));
  556. if (static_cast<quint32>(actionsLinks.size()) > OCC::ActivityListModel::maxActionButtons()) {
  557. // in case total actions is longer than ActivityListModel::maxActionButtons, only one button must be present in a list of action buttons
  558. QVERIFY(actionButtonsLinks.size() == 1);
  559. const auto actionButtonsAndContextMenuEntries = actionButtonsLinks + actionsLinksContextMenu;
  560. // 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
  561. QVERIFY(actionButtonsLinks.size() + actionsLinksContextMenu.size() == actionsLinks.size());
  562. } else {
  563. // 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 "Dismiss" button at the end
  564. QVERIFY(actionButtonsLinks.size() > 1);
  565. QVERIFY(actionButtonsLinks[1].value<OCC::ActivityLink>()._label
  566. == QObject::tr("Dismiss"));
  567. }
  568. } else if ((objectType == QStringLiteral("call"))) {
  569. QVERIFY(
  570. actionButtonsLinks[1].value<OCC::ActivityLink>()._label == QStringLiteral("Call back"));
  571. }
  572. } else {
  573. QVERIFY(actionsLinks[0].value<OCC::ActivityLink>()._label == QStringLiteral("Dismiss"));
  574. }
  575. }
  576. }
  577. } while (prevModelRowCount < model.rowCount());
  578. };
  579. };
  580. QTEST_MAIN(TestActivityListModel)
  581. #include "testactivitylistmodel.moc"