1use std::sync::Arc;
  2
  3use gpui::{BackgroundExecutor, TestAppContext};
  4use notifications::NotificationEvent;
  5use parking_lot::Mutex;
  6use pretty_assertions::assert_eq;
  7use rpc::{Notification, proto};
  8
  9use crate::tests::TestServer;
 10
 11#[gpui::test]
 12async fn test_notifications(
 13    executor: BackgroundExecutor,
 14    cx_a: &mut TestAppContext,
 15    cx_b: &mut TestAppContext,
 16) {
 17    let mut server = TestServer::start(executor.clone()).await;
 18    let client_a = server.create_client(cx_a, "user_a").await;
 19    let client_b = server.create_client(cx_b, "user_b").await;
 20
 21    // Wait for authentication/connection to Collab to be established.
 22    executor.run_until_parked();
 23
 24    let notification_events_a = Arc::new(Mutex::new(Vec::new()));
 25    let notification_events_b = Arc::new(Mutex::new(Vec::new()));
 26    client_a.notification_store().update(cx_a, |_, cx| {
 27        let events = notification_events_a.clone();
 28        cx.subscribe(&cx.entity(), move |_, _, event, _| {
 29            events.lock().push(event.clone());
 30        })
 31        .detach()
 32    });
 33    client_b.notification_store().update(cx_b, |_, cx| {
 34        let events = notification_events_b.clone();
 35        cx.subscribe(&cx.entity(), move |_, _, event, _| {
 36            events.lock().push(event.clone());
 37        })
 38        .detach()
 39    });
 40
 41    // Client A sends a contact request to client B.
 42    client_a
 43        .user_store()
 44        .update(cx_a, |store, cx| store.request_contact(client_b.id(), cx))
 45        .await
 46        .unwrap();
 47
 48    // Client B receives a contact request notification and responds to the
 49    // request, accepting it.
 50    executor.run_until_parked();
 51    client_b.notification_store().update(cx_b, |store, cx| {
 52        assert_eq!(store.notification_count(), 1);
 53        assert_eq!(store.unread_notification_count(), 1);
 54
 55        let entry = store.notification_at(0).unwrap();
 56        assert_eq!(
 57            entry.notification,
 58            Notification::ContactRequest {
 59                sender_id: client_a.id()
 60            }
 61        );
 62        assert!(!entry.is_read);
 63        assert_eq!(
 64            ¬ification_events_b.lock()[0..],
 65            &[
 66                NotificationEvent::NewNotification {
 67                    entry: entry.clone(),
 68                },
 69                NotificationEvent::NotificationsUpdated {
 70                    old_range: 0..0,
 71                    new_count: 1
 72                }
 73            ]
 74        );
 75
 76        store.respond_to_notification(entry.notification.clone(), true, cx);
 77    });
 78
 79    // Client B sees the notification is now read, and that they responded.
 80    executor.run_until_parked();
 81    client_b.notification_store().read_with(cx_b, |store, _| {
 82        assert_eq!(store.notification_count(), 1);
 83        assert_eq!(store.unread_notification_count(), 0);
 84
 85        let entry = store.notification_at(0).unwrap();
 86        assert!(entry.is_read);
 87        assert_eq!(entry.response, Some(true));
 88        assert_eq!(
 89            ¬ification_events_b.lock()[2..],
 90            &[
 91                NotificationEvent::NotificationRead {
 92                    entry: entry.clone(),
 93                },
 94                NotificationEvent::NotificationsUpdated {
 95                    old_range: 0..1,
 96                    new_count: 1
 97                }
 98            ]
 99        );
100    });
101
102    // Client A receives a notification that client B accepted their request.
103    client_a.notification_store().read_with(cx_a, |store, _| {
104        assert_eq!(store.notification_count(), 1);
105        assert_eq!(store.unread_notification_count(), 1);
106
107        let entry = store.notification_at(0).unwrap();
108        assert_eq!(
109            entry.notification,
110            Notification::ContactRequestAccepted {
111                responder_id: client_b.id()
112            }
113        );
114        assert!(!entry.is_read);
115    });
116
117    // Client A creates a channel and invites client B to be a member.
118    let channel_id = client_a
119        .channel_store()
120        .update(cx_a, |store, cx| {
121            store.create_channel("the-channel", None, cx)
122        })
123        .await
124        .unwrap();
125    client_a
126        .channel_store()
127        .update(cx_a, |store, cx| {
128            store.invite_member(channel_id, client_b.id(), proto::ChannelRole::Member, cx)
129        })
130        .await
131        .unwrap();
132
133    // Client B receives a channel invitation notification and responds to the
134    // invitation, accepting it.
135    executor.run_until_parked();
136    client_b.notification_store().update(cx_b, |store, cx| {
137        assert_eq!(store.notification_count(), 2);
138        assert_eq!(store.unread_notification_count(), 1);
139
140        let entry = store.notification_at(0).unwrap();
141        assert_eq!(
142            entry.notification,
143            Notification::ChannelInvitation {
144                channel_id: channel_id.0,
145                channel_name: "the-channel".to_string(),
146                inviter_id: client_a.id()
147            }
148        );
149        assert!(!entry.is_read);
150
151        store.respond_to_notification(entry.notification.clone(), true, cx);
152    });
153
154    // Client B sees the notification is now read, and that they responded.
155    executor.run_until_parked();
156    client_b.notification_store().read_with(cx_b, |store, _| {
157        assert_eq!(store.notification_count(), 2);
158        assert_eq!(store.unread_notification_count(), 0);
159
160        let entry = store.notification_at(0).unwrap();
161        assert!(entry.is_read);
162        assert_eq!(entry.response, Some(true));
163    });
164}