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