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}