1use crate::{rpc::RECONNECT_TIMEOUT, tests::TestServer};
2use channel::{ChannelChat, ChannelMessageId, MessageParams};
3use gpui::{BackgroundExecutor, Model, TestAppContext};
4use rpc::Notification;
5
6#[gpui::test]
7async fn test_basic_channel_messages(
8 executor: BackgroundExecutor,
9 mut cx_a: &mut TestAppContext,
10 mut cx_b: &mut TestAppContext,
11 mut cx_c: &mut TestAppContext,
12) {
13 let mut server = TestServer::start(executor.clone()).await;
14 let client_a = server.create_client(cx_a, "user_a").await;
15 let client_b = server.create_client(cx_b, "user_b").await;
16 let client_c = server.create_client(cx_c, "user_c").await;
17
18 let channel_id = server
19 .make_channel(
20 "the-channel",
21 None,
22 (&client_a, cx_a),
23 &mut [(&client_b, cx_b), (&client_c, cx_c)],
24 )
25 .await;
26
27 let channel_chat_a = client_a
28 .channel_store()
29 .update(cx_a, |store, cx| store.open_channel_chat(channel_id, cx))
30 .await
31 .unwrap();
32 let channel_chat_b = client_b
33 .channel_store()
34 .update(cx_b, |store, cx| store.open_channel_chat(channel_id, cx))
35 .await
36 .unwrap();
37
38 let message_id = channel_chat_a
39 .update(cx_a, |c, cx| {
40 c.send_message(
41 MessageParams {
42 text: "hi @user_c!".into(),
43 mentions: vec![(3..10, client_c.id())],
44 },
45 cx,
46 )
47 .unwrap()
48 })
49 .await
50 .unwrap();
51 channel_chat_a
52 .update(cx_a, |c, cx| c.send_message("two".into(), cx).unwrap())
53 .await
54 .unwrap();
55
56 executor.run_until_parked();
57 channel_chat_b
58 .update(cx_b, |c, cx| c.send_message("three".into(), cx).unwrap())
59 .await
60 .unwrap();
61
62 executor.run_until_parked();
63
64 let channel_chat_c = client_c
65 .channel_store()
66 .update(cx_c, |store, cx| store.open_channel_chat(channel_id, cx))
67 .await
68 .unwrap();
69
70 for (chat, cx) in [
71 (&channel_chat_a, &mut cx_a),
72 (&channel_chat_b, &mut cx_b),
73 (&channel_chat_c, &mut cx_c),
74 ] {
75 chat.update(*cx, |c, _| {
76 assert_eq!(
77 c.messages()
78 .iter()
79 .map(|m| (m.body.as_str(), m.mentions.as_slice()))
80 .collect::<Vec<_>>(),
81 vec![
82 ("hi @user_c!", [(3..10, client_c.id())].as_slice()),
83 ("two", &[]),
84 ("three", &[])
85 ],
86 "results for user {}",
87 c.client().id(),
88 );
89 });
90 }
91
92 client_c.notification_store().update(cx_c, |store, _| {
93 assert_eq!(store.notification_count(), 2);
94 assert_eq!(store.unread_notification_count(), 1);
95 assert_eq!(
96 store.notification_at(0).unwrap().notification,
97 Notification::ChannelMessageMention {
98 message_id,
99 sender_id: client_a.id(),
100 channel_id,
101 }
102 );
103 assert_eq!(
104 store.notification_at(1).unwrap().notification,
105 Notification::ChannelInvitation {
106 channel_id,
107 channel_name: "the-channel".to_string(),
108 inviter_id: client_a.id()
109 }
110 );
111 });
112}
113
114#[gpui::test]
115async fn test_rejoin_channel_chat(
116 executor: BackgroundExecutor,
117 cx_a: &mut TestAppContext,
118 cx_b: &mut TestAppContext,
119) {
120 let mut server = TestServer::start(executor.clone()).await;
121 let client_a = server.create_client(cx_a, "user_a").await;
122 let client_b = server.create_client(cx_b, "user_b").await;
123
124 let channel_id = server
125 .make_channel(
126 "the-channel",
127 None,
128 (&client_a, cx_a),
129 &mut [(&client_b, cx_b)],
130 )
131 .await;
132
133 let channel_chat_a = client_a
134 .channel_store()
135 .update(cx_a, |store, cx| store.open_channel_chat(channel_id, cx))
136 .await
137 .unwrap();
138 let channel_chat_b = client_b
139 .channel_store()
140 .update(cx_b, |store, cx| store.open_channel_chat(channel_id, cx))
141 .await
142 .unwrap();
143
144 channel_chat_a
145 .update(cx_a, |c, cx| c.send_message("one".into(), cx).unwrap())
146 .await
147 .unwrap();
148 channel_chat_b
149 .update(cx_b, |c, cx| c.send_message("two".into(), cx).unwrap())
150 .await
151 .unwrap();
152
153 server.forbid_connections();
154 server.disconnect_client(client_a.peer_id().unwrap());
155
156 // While client A is disconnected, clients A and B both send new messages.
157 channel_chat_a
158 .update(cx_a, |c, cx| c.send_message("three".into(), cx).unwrap())
159 .await
160 .unwrap_err();
161 channel_chat_a
162 .update(cx_a, |c, cx| c.send_message("four".into(), cx).unwrap())
163 .await
164 .unwrap_err();
165 channel_chat_b
166 .update(cx_b, |c, cx| c.send_message("five".into(), cx).unwrap())
167 .await
168 .unwrap();
169 channel_chat_b
170 .update(cx_b, |c, cx| c.send_message("six".into(), cx).unwrap())
171 .await
172 .unwrap();
173
174 // Client A reconnects.
175 server.allow_connections();
176 executor.advance_clock(RECONNECT_TIMEOUT);
177
178 // Client A fetches the messages that were sent while they were disconnected
179 // and resends their own messages which failed to send.
180 let expected_messages = &["one", "two", "five", "six", "three", "four"];
181 assert_messages(&channel_chat_a, expected_messages, cx_a);
182 assert_messages(&channel_chat_b, expected_messages, cx_b);
183}
184
185#[gpui::test]
186async fn test_remove_channel_message(
187 executor: BackgroundExecutor,
188 cx_a: &mut TestAppContext,
189 cx_b: &mut TestAppContext,
190 cx_c: &mut TestAppContext,
191) {
192 let mut server = TestServer::start(executor.clone()).await;
193 let client_a = server.create_client(cx_a, "user_a").await;
194 let client_b = server.create_client(cx_b, "user_b").await;
195 let client_c = server.create_client(cx_c, "user_c").await;
196
197 let channel_id = server
198 .make_channel(
199 "the-channel",
200 None,
201 (&client_a, cx_a),
202 &mut [(&client_b, cx_b), (&client_c, cx_c)],
203 )
204 .await;
205
206 let channel_chat_a = client_a
207 .channel_store()
208 .update(cx_a, |store, cx| store.open_channel_chat(channel_id, cx))
209 .await
210 .unwrap();
211 let channel_chat_b = client_b
212 .channel_store()
213 .update(cx_b, |store, cx| store.open_channel_chat(channel_id, cx))
214 .await
215 .unwrap();
216
217 // Client A sends some messages.
218 channel_chat_a
219 .update(cx_a, |c, cx| c.send_message("one".into(), cx).unwrap())
220 .await
221 .unwrap();
222 channel_chat_a
223 .update(cx_a, |c, cx| c.send_message("two".into(), cx).unwrap())
224 .await
225 .unwrap();
226 channel_chat_a
227 .update(cx_a, |c, cx| c.send_message("three".into(), cx).unwrap())
228 .await
229 .unwrap();
230
231 // Clients A and B see all of the messages.
232 executor.run_until_parked();
233 let expected_messages = &["one", "two", "three"];
234 assert_messages(&channel_chat_a, expected_messages, cx_a);
235 assert_messages(&channel_chat_b, expected_messages, cx_b);
236
237 // Client A deletes one of their messages.
238 channel_chat_a
239 .update(cx_a, |c, cx| {
240 let ChannelMessageId::Saved(id) = c.message(1).id else {
241 panic!("message not saved")
242 };
243 c.remove_message(id, cx)
244 })
245 .await
246 .unwrap();
247
248 // Client B sees that the message is gone.
249 executor.run_until_parked();
250 let expected_messages = &["one", "three"];
251 assert_messages(&channel_chat_a, expected_messages, cx_a);
252 assert_messages(&channel_chat_b, expected_messages, cx_b);
253
254 // Client C joins the channel chat, and does not see the deleted message.
255 let channel_chat_c = client_c
256 .channel_store()
257 .update(cx_c, |store, cx| store.open_channel_chat(channel_id, cx))
258 .await
259 .unwrap();
260 assert_messages(&channel_chat_c, expected_messages, cx_c);
261}
262
263#[track_caller]
264fn assert_messages(chat: &Model<ChannelChat>, messages: &[&str], cx: &mut TestAppContext) {
265 assert_eq!(
266 chat.read_with(cx, |chat, _| {
267 chat.messages()
268 .iter()
269 .map(|m| m.body.clone())
270 .collect::<Vec<_>>()
271 }),
272 messages
273 );
274}
275
276//todo!(collab_ui)
277// #[gpui::test]
278// async fn test_channel_message_changes(
279// executor: BackgroundExecutor,
280// cx_a: &mut TestAppContext,
281// cx_b: &mut TestAppContext,
282// ) {
283// let mut server = TestServer::start(&executor).await;
284// let client_a = server.create_client(cx_a, "user_a").await;
285// let client_b = server.create_client(cx_b, "user_b").await;
286
287// let channel_id = server
288// .make_channel(
289// "the-channel",
290// None,
291// (&client_a, cx_a),
292// &mut [(&client_b, cx_b)],
293// )
294// .await;
295
296// // Client A sends a message, client B should see that there is a new message.
297// let channel_chat_a = client_a
298// .channel_store()
299// .update(cx_a, |store, cx| store.open_channel_chat(channel_id, cx))
300// .await
301// .unwrap();
302
303// channel_chat_a
304// .update(cx_a, |c, cx| c.send_message("one".into(), cx).unwrap())
305// .await
306// .unwrap();
307
308// executor.run_until_parked();
309
310// let b_has_messages = cx_b.read_with(|cx| {
311// client_b
312// .channel_store()
313// .read(cx)
314// .has_new_messages(channel_id)
315// .unwrap()
316// });
317
318// assert!(b_has_messages);
319
320// // Opening the chat should clear the changed flag.
321// cx_b.update(|cx| {
322// collab_ui::init(&client_b.app_state, cx);
323// });
324// let project_b = client_b.build_empty_local_project(cx_b);
325// let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
326// let chat_panel_b = workspace_b.update(cx_b, |workspace, cx| ChatPanel::new(workspace, cx));
327// chat_panel_b
328// .update(cx_b, |chat_panel, cx| {
329// chat_panel.set_active(true, cx);
330// chat_panel.select_channel(channel_id, None, cx)
331// })
332// .await
333// .unwrap();
334
335// executor.run_until_parked();
336
337// let b_has_messages = cx_b.read_with(|cx| {
338// client_b
339// .channel_store()
340// .read(cx)
341// .has_new_messages(channel_id)
342// .unwrap()
343// });
344
345// assert!(!b_has_messages);
346
347// // Sending a message while the chat is open should not change the flag.
348// channel_chat_a
349// .update(cx_a, |c, cx| c.send_message("two".into(), cx).unwrap())
350// .await
351// .unwrap();
352
353// executor.run_until_parked();
354
355// let b_has_messages = cx_b.read_with(|cx| {
356// client_b
357// .channel_store()
358// .read(cx)
359// .has_new_messages(channel_id)
360// .unwrap()
361// });
362
363// assert!(!b_has_messages);
364
365// // Sending a message while the chat is closed should change the flag.
366// chat_panel_b.update(cx_b, |chat_panel, cx| {
367// chat_panel.set_active(false, cx);
368// });
369
370// // Sending a message while the chat is open should not change the flag.
371// channel_chat_a
372// .update(cx_a, |c, cx| c.send_message("three".into(), cx).unwrap())
373// .await
374// .unwrap();
375
376// executor.run_until_parked();
377
378// let b_has_messages = cx_b.read_with(|cx| {
379// client_b
380// .channel_store()
381// .read(cx)
382// .has_new_messages(channel_id)
383// .unwrap()
384// });
385
386// assert!(b_has_messages);
387
388// // Closing the chat should re-enable change tracking
389// cx_b.update(|_| drop(chat_panel_b));
390
391// channel_chat_a
392// .update(cx_a, |c, cx| c.send_message("four".into(), cx).unwrap())
393// .await
394// .unwrap();
395
396// executor.run_until_parked();
397
398// let b_has_messages = cx_b.read_with(|cx| {
399// client_b
400// .channel_store()
401// .read(cx)
402// .has_new_messages(channel_id)
403// .unwrap()
404// });
405
406// assert!(b_has_messages);
407// }