Redesign and simplify Activity Center app db (#15216)
Fixes #15215. Redesign the app db state for the Activity Center. Please, see the issue being fixed for more details about the problems being solved. TL;DR: There's a lot less state to keep track and reconcile and way less nesting in the app db because we're only managing the state of the *current tab*, not all tabs. Additionally: - [x] While updating unit tests, found a bug on the sorting notifications' logic. - [x] While updating unit tests, found a bug where notifications that are not of the type *contact request* were being removed from the app db. - [x] Fixed regression where pressing on a notification would not open the chat. - [x] Hardened unit tests. #### Platforms - Android - iOS ##### Non-functional - Less memory consumption. - Faster reconciliation of notification coming from signals and from synchronous responses from RPC calls.
This commit is contained in:
parent
25d44b11f1
commit
52b87bab3e
|
@ -1,13 +1,18 @@
|
|||
(ns status-im.data-store.activities
|
||||
(:require [clojure.set :as set]
|
||||
[status-im2.constants :as constants]
|
||||
[status-im.data-store.messages :as messages]
|
||||
[status-im2.constants :as constants]
|
||||
[status-im2.contexts.activity-center.notification-types :as notification-types]))
|
||||
|
||||
(defn mark-notifications-as-read
|
||||
[notifications]
|
||||
(map #(assoc % :read true) notifications))
|
||||
|
||||
(defn pending-contact-request?
|
||||
[contact-id {:keys [type author]}]
|
||||
(and (= type notification-types/contact-request)
|
||||
(= contact-id author)))
|
||||
|
||||
(defn- rpc->type
|
||||
[{:keys [type name] :as chat}]
|
||||
(case type
|
||||
|
|
|
@ -97,3 +97,13 @@
|
|||
(assoc :type notification-types/one-to-one-chat)
|
||||
store/<-rpc
|
||||
(select-keys [:name :chat-type :chat-name :public? :group-chat]))))))
|
||||
|
||||
(deftest remove-pending-contact-request-test
|
||||
(is (true? (store/pending-contact-request?
|
||||
"contact-id"
|
||||
{:type notification-types/contact-request
|
||||
:author "contact-id"})))
|
||||
(is (false? (store/pending-contact-request?
|
||||
"contact-id"
|
||||
{:type notification-types/contact-request
|
||||
:author "contactzzzz"}))))
|
||||
|
|
|
@ -409,7 +409,7 @@
|
|||
(get-node-config)
|
||||
(communities/fetch)
|
||||
(logging/set-log-level (:log-level multiaccount))
|
||||
(activity-center/notifications-fetch-unread-contact-requests)
|
||||
(activity-center/notifications-fetch-pending-contact-requests)
|
||||
(activity-center/notifications-fetch-unread-count))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
(ns status-im2.contexts.activity-center.events
|
||||
(:require [status-im.data-store.activities :as data-store.activities]
|
||||
(:require [quo2.foundations.colors :as colors]
|
||||
[status-im.data-store.activities :as activities]
|
||||
[status-im.data-store.chats :as data-store.chats]
|
||||
[status-im2.contexts.activity-center.notification-types :as types]
|
||||
[status-im2.contexts.chat.events :as chat.events]
|
||||
[status-im2.common.toasts.events :as toasts]
|
||||
[status-im2.constants :as constants]
|
||||
[status-im2.contexts.activity-center.notification-types :as types]
|
||||
status-im2.contexts.activity-center.notification.contact-requests.events
|
||||
[status-im2.contexts.chat.events :as chat.events]
|
||||
[taoensso.timbre :as log]
|
||||
[utils.re-frame :as rf]
|
||||
[utils.collection :as collection]
|
||||
[utils.i18n :as i18n]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[status-im2.constants :as constants]))
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(def defaults
|
||||
{:filter-status :unread
|
||||
|
@ -48,127 +49,58 @@
|
|||
|
||||
;;;; Notification reconciliation
|
||||
|
||||
(defn- notification-type->filter-type
|
||||
[type]
|
||||
(if (some types/membership [type])
|
||||
types/membership
|
||||
type))
|
||||
|
||||
(defn- update-notifications
|
||||
"Insert `new-notifications` in `db-notifications`.
|
||||
|
||||
Although correct, this is a naive implementation for reconciling notifications
|
||||
because for every notification in `new-notifications`, linear scans will be
|
||||
performed to remove it and sorting will be performed for every new insertion.
|
||||
If the number of existing notifications cached in the app db becomes
|
||||
~excessively~ big, this implementation will probably need to be revisited."
|
||||
[db-notifications new-notifications]
|
||||
(reduce (fn [acc {:keys [id read] :as notification}]
|
||||
(let [filter-type (notification-type->filter-type (:type notification))
|
||||
remove-notification (fn [data]
|
||||
(remove #(= id (:id %)) data))
|
||||
insert-and-sort (fn [data]
|
||||
(->> notification
|
||||
(conj data)
|
||||
(sort-by (juxt :timestamp :id))
|
||||
reverse))]
|
||||
(as-> acc $
|
||||
(update-in $ [filter-type :all :data] remove-notification)
|
||||
(update-in $ [types/no-type :all :data] remove-notification)
|
||||
(update-in $ [filter-type :unread :data] remove-notification)
|
||||
(update-in $ [types/no-type :unread :data] remove-notification)
|
||||
(if (:deleted notification)
|
||||
$
|
||||
(cond-> (-> $
|
||||
(update-in [filter-type :all :data] insert-and-sort)
|
||||
(update-in [types/no-type :all :data] insert-and-sort))
|
||||
(not read) (update-in [filter-type :unread :data] insert-and-sort)
|
||||
(not read) (update-in [types/no-type :unread :data] insert-and-sort))))))
|
||||
db-notifications
|
||||
new-notifications))
|
||||
[db-notifications new-notifications {filter-type :type filter-status :status}]
|
||||
(->> new-notifications
|
||||
(reduce (fn [acc {:keys [id type deleted read] :as notification}]
|
||||
(if (or deleted
|
||||
(and (= :unread filter-status) read)
|
||||
(and (set? filter-type)
|
||||
(not (contains? filter-type type)))
|
||||
(and (not (set? filter-type))
|
||||
(not= filter-type types/no-type)
|
||||
(not= filter-type type)))
|
||||
(dissoc acc id)
|
||||
(assoc acc id notification)))
|
||||
(collection/index-by :id db-notifications))
|
||||
(vals)
|
||||
(sort-by (juxt :timestamp :id)
|
||||
#(compare %2 %1))))
|
||||
|
||||
(rf/defn notifications-reconcile
|
||||
{:events [:activity-center.notifications/reconcile]}
|
||||
[{:keys [db]} new-notifications]
|
||||
(when (seq new-notifications)
|
||||
{:db (update-in db
|
||||
[:activity-center :notifications]
|
||||
update-notifications
|
||||
new-notifications)
|
||||
:dispatch [:activity-center.notifications/fetch-unread-count]}))
|
||||
|
||||
(rf/defn show-toasts
|
||||
{:events [:activity-center.notifications/show-toasts]}
|
||||
[{:keys [db]} new-notifications]
|
||||
(let [my-public-key (get-in db [:multiaccount :public-key])]
|
||||
(reduce (fn [cofx {:keys [author type accepted dismissed message name] :as x}]
|
||||
(cond
|
||||
(and (not= author my-public-key)
|
||||
(= type types/contact-request)
|
||||
(not accepted)
|
||||
(not dismissed))
|
||||
(toasts/upsert cofx
|
||||
{:icon :placeholder
|
||||
:icon-color colors/primary-50-opa-40
|
||||
:title (i18n/label :t/contact-request-sent-toast
|
||||
{:name name})
|
||||
:text (get-in message [:content :text])})
|
||||
|
||||
(and (= author my-public-key) ;; we show it for user who sent the request
|
||||
(= type types/contact-request)
|
||||
accepted
|
||||
(not dismissed))
|
||||
(toasts/upsert cofx
|
||||
{:icon :placeholder
|
||||
:icon-color colors/primary-50-opa-40
|
||||
:title (i18n/label :t/contact-request-accepted-toast
|
||||
{:name (:alias message)})})
|
||||
|
||||
:else
|
||||
cofx))
|
||||
{:db db}
|
||||
new-notifications)))
|
||||
{:db (update-in db
|
||||
[:activity-center :notifications]
|
||||
update-notifications
|
||||
new-notifications
|
||||
(get-in db [:activity-center :filter]))
|
||||
:dispatch-n [[:activity-center.notifications/fetch-unread-count]
|
||||
[:activity-center.notifications/fetch-pending-contact-requests]]}))
|
||||
|
||||
(rf/defn notifications-reconcile-from-response
|
||||
{:events [:activity-center/reconcile-notifications-from-response]}
|
||||
[cofx response]
|
||||
(->> response
|
||||
:activityCenterNotifications
|
||||
(map data-store.activities/<-rpc)
|
||||
(map activities/<-rpc)
|
||||
(notifications-reconcile cofx)))
|
||||
|
||||
(defn- remove-pending-contact-request
|
||||
[notifications contact-id]
|
||||
(remove #(= contact-id (:author %))
|
||||
notifications))
|
||||
|
||||
(rf/defn notifications-remove-pending-contact-request
|
||||
{:events [:activity-center/remove-pending-contact-request]}
|
||||
[{:keys [db]} contact-id]
|
||||
{:db (-> db
|
||||
(update-in [:activity-center :notifications types/no-type :all :data]
|
||||
remove-pending-contact-request
|
||||
contact-id)
|
||||
(update-in [:activity-center :notifications types/no-type :unread :data]
|
||||
remove-pending-contact-request
|
||||
contact-id)
|
||||
(update-in [:activity-center :notifications types/contact-request :all :data]
|
||||
remove-pending-contact-request
|
||||
contact-id)
|
||||
(update-in [:activity-center :notifications types/contact-request :unread :data]
|
||||
remove-pending-contact-request
|
||||
contact-id))})
|
||||
{:db (update-in db
|
||||
[:activity-center :notifications]
|
||||
(fn [notifications]
|
||||
(remove #(activities/pending-contact-request? contact-id %)
|
||||
notifications)))})
|
||||
|
||||
;;;; Status changes (read/dismissed/deleted)
|
||||
|
||||
(defn- get-notification
|
||||
[db notification-id]
|
||||
(->> (get-in db
|
||||
[:activity-center
|
||||
:notifications
|
||||
(get-in db [:activity-center :filter :type])
|
||||
(get-in db [:activity-center :filter :status])
|
||||
:data])
|
||||
(->> (get-in db [:activity-center :notifications])
|
||||
(filter #(= notification-id (:id %)))
|
||||
first))
|
||||
|
||||
|
@ -237,20 +169,23 @@
|
|||
{:db (-> db
|
||||
(update-in [:activity-center :notifications]
|
||||
update-notifications
|
||||
notifications)
|
||||
notifications
|
||||
(get-in db [:activity-center :filter]))
|
||||
(update :activity-center dissoc :mark-all-as-read-undoable-till))})
|
||||
|
||||
(rf/defn mark-all-as-read-locally
|
||||
{:events [:activity-center.notifications/mark-all-as-read-locally]}
|
||||
[{:keys [db now]} get-toast-ui-props]
|
||||
(let [unread-notifications (get-in db [:activity-center :notifications types/no-type :unread :data])
|
||||
(let [unread-notifications (filter #(not (:read %))
|
||||
(get-in db [:activity-center :notifications]))
|
||||
undo-time-limit-ms constants/activity-center-mark-all-as-read-undo-time-limit-ms
|
||||
undoable-till (+ now undo-time-limit-ms)]
|
||||
{:db (-> db
|
||||
(update-in [:activity-center :notifications]
|
||||
update-notifications
|
||||
(data-store.activities/mark-notifications-as-read
|
||||
unread-notifications))
|
||||
(activities/mark-notifications-as-read
|
||||
unread-notifications)
|
||||
(get-in db [:activity-center :filter]))
|
||||
(assoc-in [:activity-center :mark-all-as-read-undoable-till]
|
||||
undoable-till))
|
||||
:dispatch [:toasts/upsert
|
||||
|
@ -379,7 +314,7 @@
|
|||
(def start-or-end-cursor
|
||||
"")
|
||||
|
||||
(defn- valid-cursor?
|
||||
(defn- fetch-more?
|
||||
[cursor]
|
||||
(and (some? cursor)
|
||||
(not= cursor start-or-end-cursor)))
|
||||
|
@ -411,12 +346,10 @@
|
|||
|
||||
(rf/defn notifications-fetch
|
||||
[{:keys [db]} {:keys [cursor per-page filter-type filter-status reset-data?]}]
|
||||
(when-not (get-in db [:activity-center :notifications filter-type filter-status :loading?])
|
||||
(when-not (get-in db [:activity-center :loading?])
|
||||
(let [per-page (or per-page (defaults :notifications-per-page))
|
||||
accepted? true]
|
||||
{:db (assoc-in db
|
||||
[:activity-center :notifications filter-type filter-status :loading?]
|
||||
true)
|
||||
{:db (assoc-in db [:activity-center :loading?] true)
|
||||
:json-rpc/call [{:method "wakuext_activityCenterNotificationsBy"
|
||||
:params [cursor
|
||||
per-page
|
||||
|
@ -424,7 +357,7 @@
|
|||
(status filter-status)
|
||||
accepted?]
|
||||
:on-success #(rf/dispatch [:activity-center.notifications/fetch-success
|
||||
filter-type filter-status reset-data? %])
|
||||
reset-data? %])
|
||||
:on-error #(rf/dispatch [:activity-center.notifications/fetch-error
|
||||
filter-type filter-status %])}]})))
|
||||
|
||||
|
@ -452,8 +385,8 @@
|
|||
{:events [:activity-center.notifications/fetch-next-page]}
|
||||
[{:keys [db] :as cofx}]
|
||||
(let [{:keys [type status]} (get-in db [:activity-center :filter])
|
||||
{:keys [cursor]} (get-in db [:activity-center :notifications type status])]
|
||||
(when (valid-cursor? cursor)
|
||||
cursor (get-in db [:activity-center :cursor])]
|
||||
(when (fetch-more? cursor)
|
||||
(notifications-fetch cofx
|
||||
{:cursor cursor
|
||||
:filter-type type
|
||||
|
@ -462,36 +395,59 @@
|
|||
|
||||
(rf/defn notifications-fetch-success
|
||||
{:events [:activity-center.notifications/fetch-success]}
|
||||
[{:keys [db]}
|
||||
filter-type
|
||||
filter-status
|
||||
reset-data?
|
||||
{:keys [cursor notifications]}]
|
||||
(let [processed (map data-store.activities/<-rpc notifications)]
|
||||
[{:keys [db]} reset-data? {:keys [cursor notifications]}]
|
||||
(let [processed (map activities/<-rpc notifications)]
|
||||
{:db (-> db
|
||||
(assoc-in [:activity-center :notifications filter-type filter-status :cursor] cursor)
|
||||
(update-in [:activity-center :notifications filter-type filter-status] dissoc :loading?)
|
||||
(update-in [:activity-center :notifications filter-type filter-status :data]
|
||||
(assoc-in [:activity-center :cursor] cursor)
|
||||
(update :activity-center dissoc :loading?)
|
||||
(update-in [:activity-center :notifications]
|
||||
(if reset-data?
|
||||
(constantly processed)
|
||||
#(concat %1 processed))))}))
|
||||
#(concat % processed))))}))
|
||||
|
||||
(rf/defn notifications-fetch-unread-contact-requests
|
||||
(rf/defn notifications-fetch-pending-contact-requests
|
||||
"Unread contact requests are, in practical terms, the same as pending contact
|
||||
requests in the new Activity Center, because pending contact requests are
|
||||
always marked as unread, and once the user declines/accepts the request, they
|
||||
are marked as read.
|
||||
requests in the Activity Center, because pending contact requests are always
|
||||
marked as unread in status-go, and once the user declines/accepts the request,
|
||||
they are marked as read.
|
||||
|
||||
If this relationship ever changes, we will probably need to change the backend
|
||||
to explicitly support fetching notifications for 'pending' contact requests."
|
||||
{:events [:activity-center.notifications/fetch-unread-contact-requests]}
|
||||
[cofx]
|
||||
(notifications-fetch cofx
|
||||
{:cursor start-or-end-cursor
|
||||
:filter-status :unread
|
||||
:filter-type types/contact-request
|
||||
:per-page 20
|
||||
:reset-data? true}))
|
||||
{:events [:activity-center.notifications/fetch-pending-contact-requests]}
|
||||
[{:keys [db]}]
|
||||
(let [accepted? true]
|
||||
{:db (assoc-in db [:activity-center :loading?] true)
|
||||
:json-rpc/call
|
||||
[{:method "wakuext_activityCenterNotificationsBy"
|
||||
:params [start-or-end-cursor
|
||||
20
|
||||
[types/contact-request]
|
||||
(status :unread)
|
||||
accepted?]
|
||||
:on-success #(rf/dispatch [:activity-center.notifications/fetch-pending-contact-requests-success
|
||||
%])
|
||||
:on-error #(rf/dispatch [:activity-center.notifications/fetch-error
|
||||
types/contact-request :unread %])}]}))
|
||||
|
||||
(rf/defn notifications-fetch-pending-contact-requests-success
|
||||
{:events [:activity-center.notifications/fetch-pending-contact-requests-success]}
|
||||
[{:keys [db]} {:keys [notifications]}]
|
||||
{:db (-> db
|
||||
(update :activity-center dissoc :loading?)
|
||||
(assoc-in [:activity-center :contact-requests]
|
||||
(->> notifications
|
||||
(map activities/<-rpc)
|
||||
(filter (fn [notification]
|
||||
(= constants/contact-request-message-state-pending
|
||||
(get-in notification [:message :contact-request-state])))))))})
|
||||
|
||||
(rf/defn notifications-fetch-error
|
||||
{:events [:activity-center.notifications/fetch-error]}
|
||||
[{:keys [db]} error]
|
||||
(log/warn "Failed to load Activity Center notifications" error)
|
||||
{:db (update db :activity-center dissoc :loading?)})
|
||||
|
||||
;;;; Unread counters
|
||||
|
||||
(rf/defn notifications-fetch-unread-count
|
||||
{:events [:activity-center.notifications/fetch-unread-count]}
|
||||
|
@ -521,8 +477,36 @@
|
|||
(log/error "Failed to fetch count of notifications" {:error error})
|
||||
nil)
|
||||
|
||||
(rf/defn notifications-fetch-error
|
||||
{:events [:activity-center.notifications/fetch-error]}
|
||||
[{:keys [db]} filter-type filter-status error]
|
||||
(log/warn "Failed to load Activity Center notifications" error)
|
||||
{:db (update-in db [:activity-center :notifications filter-type filter-status] dissoc :loading?)})
|
||||
;;;; Toasts
|
||||
|
||||
(rf/defn show-toasts
|
||||
{:events [:activity-center.notifications/show-toasts]}
|
||||
[{:keys [db]} new-notifications]
|
||||
(let [my-public-key (get-in db [:multiaccount :public-key])]
|
||||
(reduce (fn [cofx {:keys [author type accepted dismissed message name] :as x}]
|
||||
(cond
|
||||
(and (not= author my-public-key)
|
||||
(= type types/contact-request)
|
||||
(not accepted)
|
||||
(not dismissed))
|
||||
(toasts/upsert cofx
|
||||
{:icon :placeholder
|
||||
:icon-color colors/primary-50-opa-40
|
||||
:title (i18n/label :t/contact-request-sent-toast
|
||||
{:name name})
|
||||
:text (get-in message [:content :text])})
|
||||
|
||||
(and (= author my-public-key) ;; we show it for user who sent the request
|
||||
(= type types/contact-request)
|
||||
accepted
|
||||
(not dismissed))
|
||||
(toasts/upsert cofx
|
||||
{:icon :placeholder
|
||||
:icon-color colors/primary-50-opa-40
|
||||
:title (i18n/label :t/contact-request-accepted-toast
|
||||
{:name (:alias message)})})
|
||||
|
||||
:else
|
||||
cofx))
|
||||
{:db db}
|
||||
new-notifications)))
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
[status-im2.constants :as constants]
|
||||
status-im.events
|
||||
[test-helpers.unit :as h]
|
||||
[status-im2.contexts.activity-center.events :as activity-center]
|
||||
[status-im2.contexts.activity-center.notification-types :as types]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
|
@ -53,15 +52,13 @@
|
|||
|
||||
(testing "opens the activity center without custom filters"
|
||||
(h/run-test-sync
|
||||
(let [spy-queue (atom [])
|
||||
existing-filters {:status :all}]
|
||||
(let [spy-queue (atom [])]
|
||||
(setup)
|
||||
(h/spy-fx spy-queue :show-popover)
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :filter] existing-filters])
|
||||
|
||||
(rf/dispatch [:activity-center/open])
|
||||
|
||||
(is (= existing-filters
|
||||
(is (= {:status :unread :type types/no-type}
|
||||
(get-in (h/db) [:activity-center :filter])))
|
||||
(is (= [{:id :show-popover :args nil}]
|
||||
@spy-queue))))))
|
||||
|
@ -72,16 +69,10 @@
|
|||
(setup)
|
||||
(let [spy-queue (atom [])]
|
||||
(h/spy-fx spy-queue :json-rpc/call)
|
||||
(let [notifications {types/one-to-one-chat
|
||||
{:all {:cursor ""
|
||||
:data [{:id notification-id
|
||||
:read false
|
||||
:type types/one-to-one-chat}]}
|
||||
:unread {:cursor "" :data []}}}]
|
||||
(rf/dispatch [:test/assoc-in [:activity-center]
|
||||
{:notifications notifications
|
||||
:filter {:type types/one-to-one-chat
|
||||
:status :all}}])
|
||||
(let [notifications [{:id notification-id
|
||||
:read false
|
||||
:type types/one-to-one-chat}]]
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :notifications] notifications])
|
||||
|
||||
(rf/dispatch [:activity-center.notifications/mark-as-read "0x666"])
|
||||
|
||||
|
@ -98,30 +89,15 @@
|
|||
new-notif-2 (assoc notif-2 :read true)]
|
||||
(h/stub-fx-with-callbacks :json-rpc/call :on-success (constantly nil))
|
||||
(rf/dispatch [:test/assoc-in [:activity-center]
|
||||
{:notifications {types/one-to-one-chat
|
||||
{:all {:cursor "" :data [notif-3 notif-2 notif-1]}
|
||||
:unread {:cursor "" :data [notif-3 notif-2]}}}
|
||||
:filter {:type types/one-to-one-chat
|
||||
:status :unread}}])
|
||||
{:filter {:status :all :type types/no-type}
|
||||
:notifications [notif-3 notif-2 notif-1]}])
|
||||
|
||||
(rf/dispatch [:activity-center.notifications/mark-as-read (:id notif-2)])
|
||||
(is (= {types/one-to-one-chat
|
||||
{:all {:cursor "" :data [notif-3 new-notif-2 notif-1]}
|
||||
:unread {:cursor "" :data [notif-3]}}
|
||||
|
||||
types/no-type
|
||||
{:all {:data [new-notif-2]}
|
||||
:unread {:data []}}}
|
||||
(is (= [notif-3 new-notif-2 notif-1]
|
||||
(get-in (h/db) [:activity-center :notifications])))
|
||||
|
||||
(rf/dispatch [:activity-center.notifications/mark-as-read (:id notif-3)])
|
||||
(is (= {types/one-to-one-chat
|
||||
{:all {:cursor "" :data [new-notif-3 new-notif-2 notif-1]}
|
||||
:unread {:cursor "" :data []}}
|
||||
|
||||
types/no-type
|
||||
{:all {:data [new-notif-3 new-notif-2]}
|
||||
:unread {:data []}}}
|
||||
(is (= [new-notif-3 new-notif-2 notif-1]
|
||||
(get-in (h/db) [:activity-center :notifications]))))))
|
||||
|
||||
(testing "logs on failure"
|
||||
|
@ -131,15 +107,10 @@
|
|||
:action :notification/mark-as-read
|
||||
:before-test (fn []
|
||||
(rf/dispatch
|
||||
[:test/assoc-in [:activity-center]
|
||||
{:notifications {types/one-to-one-chat
|
||||
{:all {:cursor ""
|
||||
:data [{:id notification-id
|
||||
:read false
|
||||
:type types/one-to-one-chat}]}
|
||||
:unread {:cursor "" :data []}}}
|
||||
:filter {:type types/one-to-one-chat
|
||||
:status :all}}]))})))
|
||||
[:test/assoc-in [:activity-center :notifications]
|
||||
[{:id notification-id
|
||||
:read false
|
||||
:type types/one-to-one-chat}]]))})))
|
||||
|
||||
;;;; Acceptance/dismissal
|
||||
|
||||
|
@ -147,27 +118,25 @@
|
|||
(testing "marks notification as accepted and read, then reconciles"
|
||||
(h/run-test-sync
|
||||
(setup)
|
||||
(let [notif-1 {:id "0x1" :type types/private-group-chat}
|
||||
notif-2 {:id "0x2" :type types/private-group-chat}
|
||||
notif-3 {:id "0x3" :type types/admin}
|
||||
notif-2-new (assoc notif-2 :accepted true :read true)]
|
||||
(let [notif-1 {:id "0x1" :type types/private-group-chat}
|
||||
notif-2 {:id "0x2" :type types/private-group-chat}
|
||||
notif-2-accepted (assoc notif-2 :accepted true :read true)]
|
||||
(h/stub-fx-with-callbacks :json-rpc/call :on-success (constantly notif-2))
|
||||
(rf/dispatch [:test/assoc-in [:activity-center]
|
||||
{:notifications {types/membership
|
||||
{:unread {:cursor "" :data [notif-2 notif-1]}}
|
||||
|
||||
types/admin
|
||||
{:all {:cursor "" :data [notif-3]}}}
|
||||
:filter {:type types/membership
|
||||
:status :unread}}])
|
||||
{:filter {:type types/no-type :status :all}
|
||||
:notifications [notif-2 notif-1]}])
|
||||
|
||||
(rf/dispatch [:activity-center.notifications/accept (:id notif-2)])
|
||||
|
||||
(is (= {types/no-type {:all {:data [notif-2-new]}
|
||||
:unread {:data []}}
|
||||
types/membership {:all {:data [notif-2-new]}
|
||||
:unread {:cursor "" :data [notif-1]}}
|
||||
types/admin {:all {:cursor "" :data [notif-3]}}}
|
||||
(is (= [notif-2-accepted notif-1]
|
||||
(get-in (h/db) [:activity-center :notifications])))
|
||||
|
||||
;; Ignores accepted notification if the Unread filter is enabled because
|
||||
;; accepted notifications are also marked as read in status-go.
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :filter]
|
||||
{:filter {:type types/no-type :status :unread}}])
|
||||
(rf/dispatch [:activity-center.notifications/accept (:id notif-2)])
|
||||
(is (= [notif-1]
|
||||
(get-in (h/db) [:activity-center :notifications]))))))
|
||||
|
||||
(testing "logs on failure"
|
||||
|
@ -184,24 +153,12 @@
|
|||
notif-2 {:id "0x2" :type types/admin}
|
||||
dismissed-notif-1 (assoc notif-1 :dismissed true)]
|
||||
(h/stub-fx-with-callbacks :json-rpc/call :on-success (constantly notif-2))
|
||||
(rf/dispatch [:test/assoc-in [:activity-center]
|
||||
{:notifications {types/no-type
|
||||
{:all {:cursor "" :data [notif-2 notif-1]}}
|
||||
|
||||
types/membership
|
||||
{:unread {:cursor "" :data [notif-1]}}}
|
||||
:filter {:type types/membership
|
||||
:status :unread}}])
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :notifications]
|
||||
[notif-2 notif-1]])
|
||||
|
||||
(rf/dispatch [:activity-center.notifications/dismiss (:id notif-1)])
|
||||
|
||||
(is (= {types/no-type
|
||||
{:all {:cursor "" :data [notif-2 dismissed-notif-1]}
|
||||
:unread {:data [dismissed-notif-1]}}
|
||||
|
||||
types/membership
|
||||
{:all {:data [dismissed-notif-1]}
|
||||
:unread {:cursor "" :data [dismissed-notif-1]}}}
|
||||
(is (= [notif-2 dismissed-notif-1]
|
||||
(get-in (h/db) [:activity-center :notifications]))))))
|
||||
|
||||
(testing "logs on failure"
|
||||
|
@ -254,36 +211,23 @@
|
|||
:timestamp 1666647286000
|
||||
:type types/contact-verification})
|
||||
|
||||
(defn test-contact-verification-event
|
||||
[{:keys [event expected-rpc-call]}]
|
||||
(h/run-test-sync
|
||||
(setup)
|
||||
(let [spy-queue (atom [])]
|
||||
(h/stub-fx-with-callbacks :json-rpc/call
|
||||
:on-success
|
||||
(constantly contact-verification-rpc-response))
|
||||
(h/spy-fx spy-queue :json-rpc/call)
|
||||
(rf/dispatch event)
|
||||
|
||||
(is (= {types/no-type
|
||||
{:all {:data [contact-verification-expected-notification]}
|
||||
:unread {:data []}}
|
||||
types/contact-verification
|
||||
{:all {:data [contact-verification-expected-notification]}
|
||||
:unread {:data []}}}
|
||||
(get-in (h/db) [:activity-center :notifications])))
|
||||
|
||||
(is (= expected-rpc-call
|
||||
(-> @spy-queue
|
||||
(get-in [0 :args 0])
|
||||
(select-keys [:method :params])))))))
|
||||
|
||||
(deftest contact-verification-decline-test
|
||||
(testing "declines notification and reconciles"
|
||||
(test-contact-verification-event
|
||||
{:event [:activity-center.contact-verification/decline notification-id]
|
||||
:expected-rpc-call {:method "wakuext_declineContactVerificationRequest"
|
||||
:params [notification-id]}}))
|
||||
(h/run-test-sync
|
||||
(setup)
|
||||
(let [spy-queue (atom [])]
|
||||
(h/stub-fx-with-callbacks :json-rpc/call
|
||||
:on-success
|
||||
(constantly contact-verification-rpc-response))
|
||||
(h/spy-fx spy-queue :json-rpc/call)
|
||||
(rf/dispatch [:test/assoc-in [:activity-center]
|
||||
{:filter {:type types/contact-verification :status :all}}])
|
||||
|
||||
(rf/dispatch [:activity-center.contact-verification/decline notification-id])
|
||||
|
||||
(is (= [contact-verification-expected-notification]
|
||||
(get-in (h/db) [:activity-center :notifications]))))))
|
||||
|
||||
(testing "logs on failure"
|
||||
(test-log-on-failure
|
||||
{:notification-id notification-id
|
||||
|
@ -293,10 +237,21 @@
|
|||
(deftest contact-verification-reply-test
|
||||
(testing "sends reply and reconciles"
|
||||
(let [reply "any answer"]
|
||||
(test-contact-verification-event
|
||||
{:event [:activity-center.contact-verification/reply notification-id reply]
|
||||
:expected-rpc-call {:method "wakuext_acceptContactVerificationRequest"
|
||||
:params [notification-id reply]}})))
|
||||
(h/run-test-sync
|
||||
(setup)
|
||||
(let [spy-queue (atom [])]
|
||||
(h/stub-fx-with-callbacks :json-rpc/call
|
||||
:on-success
|
||||
(constantly contact-verification-rpc-response))
|
||||
(h/spy-fx spy-queue :json-rpc/call)
|
||||
(rf/dispatch [:test/assoc-in [:activity-center]
|
||||
{:filter {:type types/contact-verification :status :all}}])
|
||||
|
||||
(rf/dispatch [:activity-center.contact-verification/reply notification-id reply])
|
||||
|
||||
(is (= [contact-verification-expected-notification]
|
||||
(get-in (h/db) [:activity-center :notifications])))))))
|
||||
|
||||
(testing "logs on failure"
|
||||
(test-log-on-failure
|
||||
{:notification-id notification-id
|
||||
|
@ -304,11 +259,25 @@
|
|||
:action :contact-verification/reply})))
|
||||
|
||||
(deftest contact-verification-mark-as-trusted-test
|
||||
(testing "marks notification as trusted and reconciles"
|
||||
(test-contact-verification-event
|
||||
{:event [:activity-center.contact-verification/mark-as-trusted notification-id]
|
||||
:expected-rpc-call {:method "wakuext_verifiedTrusted"
|
||||
:params [{:id notification-id}]}}))
|
||||
(testing "app db reconciliation"
|
||||
(h/run-test-sync
|
||||
(setup)
|
||||
(h/stub-fx-with-callbacks :json-rpc/call
|
||||
:on-success
|
||||
(constantly contact-verification-rpc-response))
|
||||
|
||||
;; With "Unread" filter disabled
|
||||
(rf/dispatch [:test/assoc-in [:activity-center]
|
||||
{:filter {:type types/no-type :status :all}}])
|
||||
(rf/dispatch [:activity-center.contact-verification/mark-as-trusted notification-id])
|
||||
(is (= [contact-verification-expected-notification]
|
||||
(get-in (h/db) [:activity-center :notifications])))
|
||||
|
||||
;; With "Unread" filter enabled
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :filter :status] :unread])
|
||||
(rf/dispatch [:activity-center.contact-verification/mark-as-trusted notification-id])
|
||||
(is (= [] (get-in (h/db) [:activity-center :notifications])))))
|
||||
|
||||
(testing "logs on failure"
|
||||
(test-log-on-failure
|
||||
{:notification-id notification-id
|
||||
|
@ -316,11 +285,26 @@
|
|||
:action :contact-verification/mark-as-trusted})))
|
||||
|
||||
(deftest contact-verification-mark-as-untrustworthy-test
|
||||
(testing "marks notification as untrustworthy and reconciles"
|
||||
(test-contact-verification-event
|
||||
{:event [:activity-center.contact-verification/mark-as-untrustworthy notification-id]
|
||||
:expected-rpc-call {:method "wakuext_verifiedUntrustworthy"
|
||||
:params [{:id notification-id}]}}))
|
||||
(testing "app db reconciliation"
|
||||
(h/run-test-sync
|
||||
(setup)
|
||||
(h/stub-fx-with-callbacks
|
||||
:json-rpc/call
|
||||
:on-success
|
||||
(constantly contact-verification-rpc-response))
|
||||
|
||||
;; With "Unread" filter disabled
|
||||
(rf/dispatch [:test/assoc-in [:activity-center]
|
||||
{:filter {:type types/no-type :status :all}}])
|
||||
(rf/dispatch [:activity-center.contact-verification/mark-as-untrustworthy notification-id])
|
||||
(is (= [contact-verification-expected-notification]
|
||||
(get-in (h/db) [:activity-center :notifications])))
|
||||
|
||||
;; With "Unread" filter enabled
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :filter :status] :unread])
|
||||
(rf/dispatch [:activity-center.contact-verification/mark-as-untrustworthy notification-id])
|
||||
(is (= [] (get-in (h/db) [:activity-center :notifications])))))
|
||||
|
||||
(testing "logs on failure"
|
||||
(test-log-on-failure
|
||||
{:notification-id notification-id
|
||||
|
@ -330,132 +314,100 @@
|
|||
;;;; Notification reconciliation
|
||||
|
||||
(deftest notifications-reconcile-test
|
||||
(testing "does nothing when there are no new notifications"
|
||||
(h/run-test-sync
|
||||
(setup)
|
||||
(let [notifications {types/one-to-one-chat
|
||||
{:all {:cursor ""
|
||||
:data [{:id "0x1"
|
||||
:read true
|
||||
:type types/one-to-one-chat}
|
||||
{:id "0x2"
|
||||
:read false
|
||||
:type types/one-to-one-chat}]}
|
||||
:unread {:cursor ""
|
||||
:data [{:id "0x3"
|
||||
:read false
|
||||
:type types/one-to-one-chat}]}}
|
||||
types/private-group-chat
|
||||
{:unread {:cursor ""
|
||||
:data [{:id "0x4"
|
||||
:read false
|
||||
:type types/private-group-chat}]}}}]
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :notifications] notifications])
|
||||
|
||||
(rf/dispatch [:activity-center.notifications/reconcile nil])
|
||||
|
||||
(is (= notifications (get-in (h/db) [:activity-center :notifications]))))))
|
||||
|
||||
(testing "removes deleted notifications"
|
||||
(h/run-test-sync
|
||||
(setup)
|
||||
(let [notif-1 {:id "0x1" :read true :type types/one-to-one-chat}
|
||||
notif-2 {:id "0x2" :read false :type types/one-to-one-chat}
|
||||
notif-3 {:id "0x3" :read false :type types/system :dismissed true}
|
||||
notif-4 {:id "0x4" :read true :type types/system}
|
||||
notif-5 {:id "0x5" :read false :type types/system :accepted true}]
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :notifications]
|
||||
{types/one-to-one-chat
|
||||
{:all {:cursor "" :data [notif-1 notif-2]}
|
||||
:unread {:cursor "" :data [notif-2]}}
|
||||
types/system
|
||||
{:all {:cursor "" :data [notif-4]}
|
||||
:unread {:cursor "" :data [notif-3 notif-5]}}}])
|
||||
|
||||
(rf/dispatch [:activity-center.notifications/reconcile
|
||||
[(assoc notif-1 :deleted true)
|
||||
(assoc notif-4 :deleted true)
|
||||
notif-5]])
|
||||
|
||||
(is (= {types/no-type
|
||||
{:all {:data [notif-5]}
|
||||
:unread {:data [notif-5]}}
|
||||
types/one-to-one-chat
|
||||
{:all {:cursor "" :data [notif-2]}
|
||||
:unread {:cursor "" :data [notif-2]}}
|
||||
types/system
|
||||
{:all {:cursor "" :data [notif-5]}
|
||||
:unread {:cursor "" :data [notif-5 notif-3]}}}
|
||||
(get-in (h/db) [:activity-center :notifications]))))))
|
||||
|
||||
(testing "replaces old notifications with newly arrived ones"
|
||||
(testing "All tab + All filter"
|
||||
(h/run-test-sync
|
||||
(setup)
|
||||
(let [notif-1 {:id "0x1" :read true :type types/one-to-one-chat}
|
||||
notif-4 {:id "0x4" :read false :type types/system}
|
||||
notif-6 {:id "0x6" :read false :type types/system}
|
||||
new-notif-1 (assoc notif-1 :last-message {})
|
||||
new-notif-4 (assoc notif-4 :author "0xabc")]
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :notifications]
|
||||
{types/no-type
|
||||
{:all {:cursor "" :data [notif-1]}
|
||||
:unread {:cursor "" :data [notif-4 notif-6]}}
|
||||
types/one-to-one-chat
|
||||
{:all {:cursor "" :data [notif-1]}}
|
||||
types/system
|
||||
{:unread {:cursor "" :data [notif-4 notif-6]}}}])
|
||||
notif-2 {:id "0x2" :read false :type types/system}
|
||||
new-notif-3 {:id "0x3" :read false :type types/system}
|
||||
new-notif-4 {:id "0x4" :read true :type types/system}
|
||||
new-notif-2 (assoc notif-2 :read true)]
|
||||
(rf/dispatch [:test/assoc-in [:activity-center]
|
||||
{:filter {:type types/no-type :status :all}
|
||||
:notifications [notif-2 notif-1]}])
|
||||
|
||||
(rf/dispatch [:activity-center.notifications/reconcile [new-notif-1 new-notif-4 notif-6]])
|
||||
(rf/dispatch
|
||||
[:activity-center.notifications/reconcile
|
||||
[(assoc notif-1 :deleted true) ; will be removed
|
||||
new-notif-2
|
||||
new-notif-3
|
||||
new-notif-4]])
|
||||
|
||||
(is (= {types/no-type
|
||||
{:all {:cursor "" :data [notif-6 new-notif-4 new-notif-1]}
|
||||
:unread {:cursor "" :data [notif-6 new-notif-4]}}
|
||||
types/one-to-one-chat
|
||||
{:all {:cursor "" :data [new-notif-1]}
|
||||
:unread {:data []}}
|
||||
types/system
|
||||
{:all {:data [notif-6 new-notif-4]}
|
||||
:unread {:cursor "" :data [notif-6 new-notif-4]}}}
|
||||
(is (= [new-notif-4 new-notif-3 new-notif-2]
|
||||
(get-in (h/db) [:activity-center :notifications]))))))
|
||||
|
||||
(testing "reconciles notifications that switched their read/unread status"
|
||||
(testing "All tab + Unread filter"
|
||||
(h/run-test-sync
|
||||
(setup)
|
||||
(let [notif-1 {:id "0x1" :read true :type types/one-to-one-chat}
|
||||
new-notif-1 (assoc notif-1 :read false)]
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :notifications]
|
||||
{types/one-to-one-chat
|
||||
{:all {:cursor "" :data [notif-1]}}}])
|
||||
(let [notif-1 {:id "0x1" :read false :type types/one-to-one-chat}
|
||||
notif-2 {:id "0x2" :read false :type types/system}
|
||||
new-notif-2 (assoc notif-2 :read true)
|
||||
new-notif-3 {:id "0x3" :read false :type types/system}
|
||||
new-notif-4 {:id "0x4" :read true :type types/system}
|
||||
notif-5 {:id "0x5" :type types/system}]
|
||||
(rf/dispatch [:test/assoc-in [:activity-center]
|
||||
{:filter {:type types/no-type :status :unread}
|
||||
:notifications [notif-5 notif-2 notif-1]}])
|
||||
|
||||
(rf/dispatch [:activity-center.notifications/reconcile [new-notif-1]])
|
||||
(rf/dispatch
|
||||
[:activity-center.notifications/reconcile
|
||||
[new-notif-2 ; will be removed because it's read
|
||||
new-notif-3 ; will be inserted
|
||||
new-notif-4 ; will be ignored because it's read
|
||||
(assoc notif-5 :deleted true) ; will be removed
|
||||
]])
|
||||
|
||||
(is (= {types/no-type
|
||||
{:all {:data [new-notif-1]}
|
||||
:unread {:data [new-notif-1]}}
|
||||
|
||||
types/one-to-one-chat
|
||||
{:all {:cursor "" :data [new-notif-1]}
|
||||
:unread {:data [new-notif-1]}}}
|
||||
(is (= [new-notif-3 notif-1]
|
||||
(get-in (h/db) [:activity-center :notifications]))))))
|
||||
|
||||
(testing "membership notifications"
|
||||
(testing "Contact request tab + All filter"
|
||||
(h/run-test-sync
|
||||
(setup)
|
||||
(let [notif {:read false
|
||||
:dismissed false
|
||||
:accepted false
|
||||
:type types/private-group-chat
|
||||
:id "0x7"
|
||||
:timestamp 1673445663000}]
|
||||
(rf/dispatch [:activity-center.notifications/reconcile [notif]])
|
||||
(let [notif-1 {:id "0x1" :read true :type types/contact-request}
|
||||
notif-2 {:id "0x2" :read false :type types/contact-request}
|
||||
new-notif-2 (assoc notif-2 :read true)
|
||||
new-notif-3 {:id "0x3" :read false :type types/contact-request}
|
||||
new-notif-4 {:id "0x4" :read true :type types/system}
|
||||
notif-5 {:id "0x5" :read false :type types/contact-request}]
|
||||
(rf/dispatch [:test/assoc-in [:activity-center]
|
||||
{:filter {:type types/contact-request :status :all}
|
||||
:notifications [notif-5 notif-2 notif-1]}])
|
||||
|
||||
(is (= {types/no-type
|
||||
{:all {:data [notif]}
|
||||
:unread {:data [notif]}}
|
||||
(rf/dispatch
|
||||
[:activity-center.notifications/reconcile
|
||||
[new-notif-2 ; will be updated
|
||||
new-notif-3 ; will be inserted
|
||||
new-notif-4 ; will be ignored because it's not a contact request
|
||||
(assoc notif-5 :deleted true) ; will be removed
|
||||
]])
|
||||
|
||||
types/membership
|
||||
{:all {:data [notif]}
|
||||
:unread {:data [notif]}}}
|
||||
(is (= [new-notif-3 new-notif-2 notif-1]
|
||||
(get-in (h/db) [:activity-center :notifications]))))))
|
||||
|
||||
(testing "Contact request tab + Unread filter"
|
||||
(h/run-test-sync
|
||||
(setup)
|
||||
(let [notif-1 {:id "0x1" :read false :type types/contact-request}
|
||||
notif-2 {:id "0x2" :read false :type types/contact-request}
|
||||
new-notif-2 (assoc notif-2 :read true)
|
||||
new-notif-3 {:id "0x3" :read false :type types/contact-request}
|
||||
new-notif-4 {:id "0x4" :read true :type types/contact-request}
|
||||
new-notif-5 {:id "0x5" :read true :type types/system}
|
||||
notif-6 {:id "0x6" :read false :type types/contact-request}]
|
||||
(rf/dispatch [:test/assoc-in [:activity-center]
|
||||
{:filter {:type types/contact-request :status :unread}
|
||||
:notifications [notif-6 notif-2 notif-1]}])
|
||||
|
||||
(rf/dispatch
|
||||
[:activity-center.notifications/reconcile
|
||||
[new-notif-2 ; will be removed because it's read
|
||||
new-notif-3 ; will be inserted
|
||||
new-notif-4 ; will be ignored because it's read
|
||||
new-notif-5 ; will be ignored because it's not a contact request
|
||||
(assoc notif-6 :deleted true) ; will be removed
|
||||
]])
|
||||
|
||||
(is (= [new-notif-3 notif-1]
|
||||
(get-in (h/db) [:activity-center :notifications]))))))
|
||||
|
||||
;; Sorting by timestamp and ID is compatible with what the backend does when
|
||||
|
@ -463,56 +415,38 @@
|
|||
(testing "sorts notifications by timestamp and id in descending order"
|
||||
(h/run-test-sync
|
||||
(setup)
|
||||
(let [notif-1 {:id "0x1" :read true :type types/one-to-one-chat :timestamp 1}
|
||||
notif-2 {:id "0x2" :read true :type types/one-to-one-chat :timestamp 1}
|
||||
notif-3 {:id "0x3" :read false :type types/one-to-one-chat :timestamp 50}
|
||||
notif-4 {:id "0x4" :read false :type types/one-to-one-chat :timestamp 100}
|
||||
notif-5 {:id "0x5" :read false :type types/one-to-one-chat :timestamp 100}
|
||||
(let [notif-1 {:id "0x1" :timestamp 1}
|
||||
notif-2 {:id "0x2" :timestamp 1}
|
||||
notif-3 {:id "0x3" :timestamp 50}
|
||||
notif-4 {:id "0x4" :timestamp 100}
|
||||
notif-5 {:id "0x5" :timestamp 100}
|
||||
new-notif-1 (assoc notif-1 :last-message {})
|
||||
new-notif-4 (assoc notif-4 :last-message {})]
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :notifications]
|
||||
{types/one-to-one-chat
|
||||
{:all {:cursor "" :data [notif-1 notif-2]}
|
||||
:unread {:cursor "" :data [notif-3 notif-4 notif-5]}}}])
|
||||
[notif-1 notif-3 notif-4 notif-2 notif-5]])
|
||||
|
||||
(rf/dispatch [:activity-center.notifications/reconcile [new-notif-1 new-notif-4]])
|
||||
|
||||
(is (= {types/no-type
|
||||
{:all {:data [new-notif-4 new-notif-1]}
|
||||
:unread {:data [new-notif-4]}}
|
||||
types/one-to-one-chat
|
||||
{:all {:cursor "" :data [new-notif-4 notif-2 new-notif-1]}
|
||||
:unread {:cursor "" :data [notif-5 new-notif-4 notif-3]}}}
|
||||
(is (= [notif-5 new-notif-4 notif-3 notif-2 new-notif-1]
|
||||
(get-in (h/db) [:activity-center :notifications])))))))
|
||||
|
||||
(deftest remove-pending-contact-request-test
|
||||
(testing "removes notification from all related filter types and status"
|
||||
(h/run-test-sync
|
||||
(setup)
|
||||
(let [contact-pub-key "0x99"
|
||||
notif-1 {:id "0x1" :read true :type types/contact-request}
|
||||
notif-2 {:id "0x2" :read false :type types/contact-request :author contact-pub-key}
|
||||
notif-3 {:id "0x3" :read false :type types/private-group-chat}
|
||||
notifications {types/contact-request
|
||||
{:all {:cursor "" :data [notif-2 notif-1]}
|
||||
:unread {:cursor "" :data [notif-2]}}
|
||||
types/private-group-chat
|
||||
{:unread {:cursor "" :data [notif-3]}}
|
||||
types/no-type
|
||||
{:all {:cursor "" :data [notif-3 notif-2 notif-1]}
|
||||
:unread {:cursor "" :data [notif-2 notif-3]}}}]
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :notifications] notifications])
|
||||
(let [author "0x99"
|
||||
notif-1 {:id "0x1" :read true :type types/contact-request}
|
||||
notif-2 {:id "0x2" :read false :type types/contact-request :author author}
|
||||
notif-3 {:id "0x3" :read false :type types/private-group-chat :author author}]
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :notifications]
|
||||
[notif-3 ; will be ignored because it's not a contact request
|
||||
notif-2 ; will be removed
|
||||
notif-1 ; will be ignored because it's not from the same author
|
||||
]])
|
||||
|
||||
(rf/dispatch [:activity-center/remove-pending-contact-request contact-pub-key])
|
||||
(rf/dispatch [:activity-center/remove-pending-contact-request author])
|
||||
|
||||
(is (= {types/contact-request
|
||||
{:all {:cursor "" :data [notif-1]}
|
||||
:unread {:cursor "" :data []}}
|
||||
types/private-group-chat
|
||||
{:unread {:cursor "" :data [notif-3]}}
|
||||
types/no-type
|
||||
{:all {:cursor "" :data [notif-3 notif-1]}
|
||||
:unread {:cursor "" :data [notif-3]}}}
|
||||
(is (= [notif-3 notif-1]
|
||||
(get-in (h/db) [:activity-center :notifications])))))))
|
||||
|
||||
;;;; Notifications fetching and pagination
|
||||
|
@ -538,19 +472,18 @@
|
|||
(is (= :unread (get-in (h/db) [:activity-center :filter :status])))
|
||||
(is (= "" (get-in @spy-queue [0 :args 0 :params 0]))
|
||||
"Should be called with empty cursor when fetching first page")
|
||||
(is (= {types/one-to-one-chat
|
||||
{:unread {:cursor "10"
|
||||
:data [{:chat-id "0x9"
|
||||
:chat-name nil
|
||||
:chat-type types/one-to-one-chat
|
||||
:group-chat false
|
||||
:id "0x1"
|
||||
:public? false
|
||||
:last-message nil
|
||||
:message nil
|
||||
:read false
|
||||
:reply-message nil
|
||||
:type types/one-to-one-chat}]}}}
|
||||
(is (= "10" (get-in (h/db) [:activity-center :cursor])))
|
||||
(is (= [{:chat-id "0x9"
|
||||
:chat-name nil
|
||||
:chat-type types/one-to-one-chat
|
||||
:group-chat false
|
||||
:id "0x1"
|
||||
:public? false
|
||||
:last-message nil
|
||||
:message nil
|
||||
:read false
|
||||
:reply-message nil
|
||||
:type types/one-to-one-chat}]
|
||||
(get-in (h/db) [:activity-center :notifications]))))))
|
||||
|
||||
(testing "does not fetch next page when pagination cursor reached the end"
|
||||
|
@ -558,36 +491,9 @@
|
|||
(setup)
|
||||
(let [spy-queue (atom [])]
|
||||
(h/spy-fx spy-queue :json-rpc/call)
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :filter :status]
|
||||
:unread])
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :filter :type]
|
||||
types/one-to-one-chat])
|
||||
(rf/dispatch [:test/assoc-in
|
||||
[:activity-center :notifications types/one-to-one-chat :unread :cursor]
|
||||
""])
|
||||
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :cursor] ""])
|
||||
(rf/dispatch [:activity-center.notifications/fetch-next-page])
|
||||
|
||||
(is (= [] @spy-queue)))))
|
||||
|
||||
;; The cursor can be nil sometimes because the reconciliation doesn't care
|
||||
;; about updating the cursor value, but we have to make sure the next page is
|
||||
;; only fetched if the current cursor is valid.
|
||||
(testing "does not fetch next page when cursor is nil"
|
||||
(h/run-test-sync
|
||||
(setup)
|
||||
(let [spy-queue (atom [])]
|
||||
(h/spy-fx spy-queue :json-rpc/call)
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :filter :status]
|
||||
:unread])
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :filter :type]
|
||||
types/one-to-one-chat])
|
||||
(rf/dispatch [:test/assoc-in
|
||||
[:activity-center :notifications types/one-to-one-chat :unread :cursor]
|
||||
nil])
|
||||
|
||||
(rf/dispatch [:activity-center.notifications/fetch-next-page])
|
||||
|
||||
(is (= [] @spy-queue)))))
|
||||
|
||||
(testing "fetches next page when pagination cursor is not empty"
|
||||
|
@ -603,50 +509,27 @@
|
|||
:read false
|
||||
:chatId "0x9"}]}))
|
||||
(h/spy-fx spy-queue :json-rpc/call)
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :filter :status]
|
||||
:unread])
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :filter :type]
|
||||
types/mention])
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :notifications types/mention :unread :cursor]
|
||||
"10"])
|
||||
(rf/dispatch [:test/assoc-in [:activity-center]
|
||||
{:filter {:status :unread :type types/mention}
|
||||
:cursor "10"}])
|
||||
|
||||
(rf/dispatch [:activity-center.notifications/fetch-next-page])
|
||||
|
||||
(is (= "wakuext_activityCenterNotificationsBy" (get-in @spy-queue [0 :args 0 :method])))
|
||||
(is (= "10" (get-in @spy-queue [0 :args 0 :params 0]))
|
||||
"Should be called with current cursor")
|
||||
(is (= {types/mention
|
||||
{:unread {:cursor ""
|
||||
:data [{:chat-id "0x9"
|
||||
:chat-name nil
|
||||
:chat-type 3
|
||||
:id "0x1"
|
||||
:last-message nil
|
||||
:message nil
|
||||
:read false
|
||||
:reply-message nil
|
||||
:type types/mention}]}}}
|
||||
(is (= "" (get-in (h/db) [:activity-center :cursor])))
|
||||
(is (= [{:chat-id "0x9"
|
||||
:chat-name nil
|
||||
:chat-type 3
|
||||
:id "0x1"
|
||||
:last-message nil
|
||||
:message nil
|
||||
:read false
|
||||
:reply-message nil
|
||||
:type types/mention}]
|
||||
(get-in (h/db) [:activity-center :notifications]))))))
|
||||
|
||||
(testing "does not fetch next page while it is still loading"
|
||||
(h/run-test-sync
|
||||
(setup)
|
||||
(let [spy-queue (atom [])]
|
||||
(h/spy-fx spy-queue :json-rpc/call)
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :filter :status]
|
||||
:all])
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :filter :type]
|
||||
types/one-to-one-chat])
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :notifications types/one-to-one-chat :all :cursor]
|
||||
"10"])
|
||||
(rf/dispatch [:test/assoc-in
|
||||
[:activity-center :notifications types/one-to-one-chat :all :loading?]
|
||||
true])
|
||||
|
||||
(rf/dispatch [:activity-center.notifications/fetch-next-page])
|
||||
|
||||
(is (= [] @spy-queue)))))
|
||||
|
||||
(testing "resets loading flag after an error"
|
||||
(h/run-test-sync
|
||||
(setup)
|
||||
|
@ -654,41 +537,19 @@
|
|||
(h/stub-fx-with-callbacks :json-rpc/call :on-error (constantly :fake-error))
|
||||
(h/spy-event-fx spy-queue :activity-center.notifications/fetch-error)
|
||||
(h/spy-fx spy-queue :json-rpc/call)
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :filter :status]
|
||||
:unread])
|
||||
(rf/dispatch [:test/assoc-in [:activity-center :filter :type]
|
||||
types/one-to-one-chat])
|
||||
(rf/dispatch [:test/assoc-in
|
||||
[:activity-center :notifications types/one-to-one-chat :unread :cursor]
|
||||
""])
|
||||
(rf/dispatch [:test/assoc-in [:activity-center]
|
||||
{:filter {:status :unread :type types/one-to-one-chat}
|
||||
:cursor ""}])
|
||||
|
||||
(rf/dispatch [:activity-center.notifications/fetch-first-page])
|
||||
|
||||
(is (nil? (get-in (h/db)
|
||||
[:activity-center :notifications types/one-to-one-chat :unread :loading?])))
|
||||
(is (nil? (get-in (h/db) [:activity-center :loading?])))
|
||||
(is (= [:activity-center.notifications/fetch-error
|
||||
types/one-to-one-chat
|
||||
:unread
|
||||
:fake-error]
|
||||
(:args (last @spy-queue))))))))
|
||||
|
||||
(deftest notifications-fetch-unread-contact-requests-test
|
||||
(testing "fetches latest unread contact requests"
|
||||
(let [actual (activity-center/notifications-fetch-unread-contact-requests {:db {}})
|
||||
per-page 20]
|
||||
(is (= {:activity-center
|
||||
{:notifications
|
||||
{types/contact-request
|
||||
{:unread {:loading? true}}}}}
|
||||
(:db actual)))
|
||||
|
||||
(is (= {:method "wakuext_activityCenterNotificationsBy"
|
||||
:params ["" per-page [types/contact-request] activity-center/status-unread true]}
|
||||
(-> actual
|
||||
:json-rpc/call
|
||||
first
|
||||
(select-keys [:method :params])))))))
|
||||
|
||||
(deftest notifications-fetch-unread-count-test
|
||||
(testing "fetches total notification count and store in db"
|
||||
(h/run-test-sync
|
||||
|
|
|
@ -134,7 +134,7 @@
|
|||
[gesture/touchable-without-feedback
|
||||
{:on-press (fn []
|
||||
(rf/dispatch [:hide-popover])
|
||||
(rf/dispatch [:chat.ui/start-chat {:public-key author}]))}
|
||||
(rf/dispatch [:chat.ui/start-chat author]))}
|
||||
[incoming-contact-request-view notification set-swipeable-height]]
|
||||
|
||||
:else
|
||||
|
|
|
@ -209,7 +209,7 @@
|
|||
(rn/use-effect-once #(rf/dispatch [:activity-center.notifications/fetch-first-page]))
|
||||
[safe-area/consumer
|
||||
(fn [{:keys [top bottom]}]
|
||||
(let [notifications (rf/sub [:activity-center/filtered-notifications])
|
||||
(let [notifications (rf/sub [:activity-center/notifications])
|
||||
window-width (rf/sub [:dimensions/window-width])]
|
||||
[rn/view {:style (style/screen-container window-width top bottom)}
|
||||
[header request-close]
|
||||
|
|
|
@ -51,7 +51,8 @@
|
|||
chats-js (.-chatsForContacts response-js)
|
||||
events (reduce
|
||||
(prepare-events-for-contact db chats-js)
|
||||
[[:activity-center.notifications/fetch-unread-count]]
|
||||
[[:activity-center.notifications/fetch-unread-count]
|
||||
[:activity-center.notifications/fetch-pending-contact-requests]]
|
||||
contacts-cljs)]
|
||||
(js-delete response-js "contacts")
|
||||
(js-delete response-js "chatsForContacts")
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
(ns status-im2.subs.activity-center
|
||||
(:require [re-frame.core :as re-frame]
|
||||
[status-im2.constants :as constants]
|
||||
[status-im2.contexts.activity-center.notification-types :as types]))
|
||||
|
||||
(re-frame/reg-sub
|
||||
|
@ -53,14 +52,6 @@
|
|||
(fn [activity-center]
|
||||
(get-in activity-center [:filter :type] types/no-type)))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:activity-center/filtered-notifications
|
||||
:<- [:activity-center/filter-type]
|
||||
:<- [:activity-center/filter-status]
|
||||
:<- [:activity-center/notifications]
|
||||
(fn [[filter-type filter-status notifications]]
|
||||
(get-in notifications [filter-type filter-status :data])))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:activity-center/filter-status-unread-enabled?
|
||||
:<- [:activity-center/filter-status]
|
||||
|
@ -69,9 +60,6 @@
|
|||
|
||||
(re-frame/reg-sub
|
||||
:activity-center/pending-contact-requests
|
||||
:<- [:activity-center/notifications]
|
||||
(fn [notifications]
|
||||
(filter (fn [{:keys [message]}]
|
||||
(= constants/contact-request-message-state-pending
|
||||
(:contact-request-state message)))
|
||||
(get-in notifications [types/contact-request :unread :data]))))
|
||||
:<- [:activity-center]
|
||||
(fn [activity-center]
|
||||
(:contact-requests activity-center)))
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
(ns status-im2.subs.activity-center-test
|
||||
(:require [cljs.test :refer [is testing]]
|
||||
[re-frame.db :as rf-db]
|
||||
[status-im2.constants :as constants]
|
||||
[status-im2.contexts.activity-center.notification-types :as types]
|
||||
status-im2.subs.activity-center
|
||||
[test-helpers.unit :as h]
|
||||
|
@ -64,27 +63,3 @@
|
|||
types/admin 7})
|
||||
|
||||
(is (= 28 (rf/sub [sub-name]))))
|
||||
|
||||
(h/deftest-sub :activity-center/pending-contact-requests
|
||||
[sub-name]
|
||||
(testing "returns only contact request notifications in the pending state"
|
||||
(let [pending {:id "0x2"
|
||||
:type types/contact-request
|
||||
:message {:contact-request-state
|
||||
constants/contact-request-message-state-pending}}]
|
||||
(swap! rf-db/app-db assoc-in
|
||||
[:activity-center :notifications types/contact-request :unread :data]
|
||||
[{:id "0x1"
|
||||
:type types/contact-request
|
||||
:message {:contact-request-state constants/contact-request-message-state-none}}
|
||||
pending
|
||||
{:id "0x3"
|
||||
:type types/contact-request
|
||||
:message {:contact-request-state constants/contact-request-message-state-accepted}}
|
||||
{:id "0x4"
|
||||
:type types/contact-request
|
||||
:message {:contact-request-state constants/contact-request-message-state-declined}}
|
||||
{:id "0x5"
|
||||
:type types/mention}])
|
||||
|
||||
(is (= [pending] (rf/sub [sub-name]))))))
|
||||
|
|
Loading…
Reference in New Issue