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