channel_message_tests.rs

  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            .unwrap()
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            .unwrap()
345    });
346
347    assert!(!b_has_messages);
348
349    // Sending a message while the chat is open should not change the flag.
350    channel_chat_a
351        .update(cx_a, |c, cx| c.send_message("two".into(), cx).unwrap())
352        .await
353        .unwrap();
354
355    executor.run_until_parked();
356
357    let b_has_messages = cx_b.update(|cx| {
358        client_b
359            .channel_store()
360            .read(cx)
361            .has_new_messages(channel_id)
362            .unwrap()
363    });
364
365    assert!(!b_has_messages);
366
367    // Sending a message while the chat is closed should change the flag.
368    chat_panel_b.update(cx_b, |chat_panel, cx| {
369        chat_panel.set_active(false, cx);
370    });
371
372    // Sending a message while the chat is open should not change the flag.
373    channel_chat_a
374        .update(cx_a, |c, cx| c.send_message("three".into(), cx).unwrap())
375        .await
376        .unwrap();
377
378    executor.run_until_parked();
379
380    let b_has_messages = cx_b.update(|cx| {
381        client_b
382            .channel_store()
383            .read(cx)
384            .has_new_messages(channel_id)
385            .unwrap()
386    });
387
388    assert!(b_has_messages);
389
390    // Closing the chat should re-enable change tracking
391    cx_b.update(|_| drop(chat_panel_b));
392
393    channel_chat_a
394        .update(cx_a, |c, cx| c.send_message("four".into(), cx).unwrap())
395        .await
396        .unwrap();
397
398    executor.run_until_parked();
399
400    let b_has_messages = cx_b.update(|cx| {
401        client_b
402            .channel_store()
403            .read(cx)
404            .has_new_messages(channel_id)
405            .unwrap()
406    });
407
408    assert!(b_has_messages);
409}