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, Entity, 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: channel_id.0,
104            }
105        );
106        assert_eq!(
107            store.notification_at(1).unwrap().notification,
108            Notification::ChannelInvitation {
109                channel_id: channel_id.0,
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    let msg_id_2 = channel_chat_a
226        .update(cx_a, |c, cx| {
227            c.send_message(
228                MessageParams {
229                    text: "two @user_b".to_string(),
230                    mentions: vec![(4..12, client_b.id())],
231                    reply_to_message_id: None,
232                },
233                cx,
234            )
235            .unwrap()
236        })
237        .await
238        .unwrap();
239    channel_chat_a
240        .update(cx_a, |c, cx| c.send_message("three".into(), cx).unwrap())
241        .await
242        .unwrap();
243
244    // Clients A and B see all of the messages.
245    executor.run_until_parked();
246    let expected_messages = &["one", "two @user_b", "three"];
247    assert_messages(&channel_chat_a, expected_messages, cx_a);
248    assert_messages(&channel_chat_b, expected_messages, cx_b);
249
250    // Ensure that client B received a notification for the mention.
251    client_b.notification_store().read_with(cx_b, |store, _| {
252        assert_eq!(store.notification_count(), 2);
253        let entry = store.notification_at(0).unwrap();
254        assert_eq!(
255            entry.notification,
256            Notification::ChannelMessageMention {
257                message_id: msg_id_2,
258                sender_id: client_a.id(),
259                channel_id: channel_id.0,
260            }
261        );
262    });
263
264    // Client A deletes one of their messages.
265    channel_chat_a
266        .update(cx_a, |c, cx| {
267            let ChannelMessageId::Saved(id) = c.message(1).id else {
268                panic!("message not saved")
269            };
270            c.remove_message(id, cx)
271        })
272        .await
273        .unwrap();
274
275    // Client B sees that the message is gone.
276    executor.run_until_parked();
277    let expected_messages = &["one", "three"];
278    assert_messages(&channel_chat_a, expected_messages, cx_a);
279    assert_messages(&channel_chat_b, expected_messages, cx_b);
280
281    // Client C joins the channel chat, and does not see the deleted message.
282    let channel_chat_c = client_c
283        .channel_store()
284        .update(cx_c, |store, cx| store.open_channel_chat(channel_id, cx))
285        .await
286        .unwrap();
287    assert_messages(&channel_chat_c, expected_messages, cx_c);
288
289    // Ensure we remove the notifications when the message is removed
290    client_b.notification_store().read_with(cx_b, |store, _| {
291        // First notification is the channel invitation, second would be the mention
292        // notification, which should now be removed.
293        assert_eq!(store.notification_count(), 1);
294    });
295}
296
297#[track_caller]
298fn assert_messages(chat: &Entity<ChannelChat>, messages: &[&str], cx: &mut TestAppContext) {
299    assert_eq!(
300        chat.read_with(cx, |chat, _| {
301            chat.messages()
302                .iter()
303                .map(|m| m.body.clone())
304                .collect::<Vec<_>>()
305        }),
306        messages
307    );
308}
309
310#[gpui::test]
311async fn test_channel_message_changes(
312    executor: BackgroundExecutor,
313    cx_a: &mut TestAppContext,
314    cx_b: &mut TestAppContext,
315) {
316    let mut server = TestServer::start(executor.clone()).await;
317    let client_a = server.create_client(cx_a, "user_a").await;
318    let client_b = server.create_client(cx_b, "user_b").await;
319
320    let channel_id = server
321        .make_channel(
322            "the-channel",
323            None,
324            (&client_a, cx_a),
325            &mut [(&client_b, cx_b)],
326        )
327        .await;
328
329    // Client A sends a message, client B should see that there is a new message.
330    let channel_chat_a = client_a
331        .channel_store()
332        .update(cx_a, |store, cx| store.open_channel_chat(channel_id, cx))
333        .await
334        .unwrap();
335
336    channel_chat_a
337        .update(cx_a, |c, cx| c.send_message("one".into(), cx).unwrap())
338        .await
339        .unwrap();
340
341    executor.run_until_parked();
342
343    let b_has_messages = cx_b.update(|cx| {
344        client_b
345            .channel_store()
346            .read(cx)
347            .has_new_messages(channel_id)
348    });
349
350    assert!(b_has_messages);
351
352    // Opening the chat should clear the changed flag.
353    cx_b.update(|cx| {
354        collab_ui::init(&client_b.app_state, cx);
355    });
356    let project_b = client_b.build_empty_local_project(cx_b);
357    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);
358
359    let chat_panel_b = workspace_b.update_in(cx_b, ChatPanel::new);
360    chat_panel_b
361        .update_in(cx_b, |chat_panel, window, cx| {
362            chat_panel.set_active(true, window, cx);
363            chat_panel.select_channel(channel_id, None, cx)
364        })
365        .await
366        .unwrap();
367
368    executor.run_until_parked();
369
370    let b_has_messages = cx_b.update(|_, cx| {
371        client_b
372            .channel_store()
373            .read(cx)
374            .has_new_messages(channel_id)
375    });
376
377    assert!(!b_has_messages);
378
379    // Sending a message while the chat is open should not change the flag.
380    channel_chat_a
381        .update(cx_a, |c, cx| c.send_message("two".into(), cx).unwrap())
382        .await
383        .unwrap();
384
385    executor.run_until_parked();
386
387    let b_has_messages = cx_b.update(|_, cx| {
388        client_b
389            .channel_store()
390            .read(cx)
391            .has_new_messages(channel_id)
392    });
393
394    assert!(!b_has_messages);
395
396    // Sending a message while the chat is closed should change the flag.
397    chat_panel_b.update_in(cx_b, |chat_panel, window, cx| {
398        chat_panel.set_active(false, window, cx);
399    });
400
401    // Sending a message while the chat is open should not change the flag.
402    channel_chat_a
403        .update(cx_a, |c, cx| c.send_message("three".into(), cx).unwrap())
404        .await
405        .unwrap();
406
407    executor.run_until_parked();
408
409    let b_has_messages = cx_b.update(|_, cx| {
410        client_b
411            .channel_store()
412            .read(cx)
413            .has_new_messages(channel_id)
414    });
415
416    assert!(b_has_messages);
417
418    // Closing the chat should re-enable change tracking
419    cx_b.update(|_, _| drop(chat_panel_b));
420
421    channel_chat_a
422        .update(cx_a, |c, cx| c.send_message("four".into(), cx).unwrap())
423        .await
424        .unwrap();
425
426    executor.run_until_parked();
427
428    let b_has_messages = cx_b.update(|_, cx| {
429        client_b
430            .channel_store()
431            .read(cx)
432            .has_new_messages(channel_id)
433    });
434
435    assert!(b_has_messages);
436}
437
438#[gpui::test]
439async fn test_chat_replies(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
440    let mut server = TestServer::start(cx_a.executor()).await;
441    let client_a = server.create_client(cx_a, "user_a").await;
442    let client_b = server.create_client(cx_b, "user_b").await;
443
444    let channel_id = server
445        .make_channel(
446            "the-channel",
447            None,
448            (&client_a, cx_a),
449            &mut [(&client_b, cx_b)],
450        )
451        .await;
452
453    // Client A sends a message, client B should see that there is a new message.
454    let channel_chat_a = client_a
455        .channel_store()
456        .update(cx_a, |store, cx| store.open_channel_chat(channel_id, cx))
457        .await
458        .unwrap();
459
460    let channel_chat_b = client_b
461        .channel_store()
462        .update(cx_b, |store, cx| store.open_channel_chat(channel_id, cx))
463        .await
464        .unwrap();
465
466    let msg_id = channel_chat_a
467        .update(cx_a, |c, cx| c.send_message("one".into(), cx).unwrap())
468        .await
469        .unwrap();
470
471    cx_a.run_until_parked();
472
473    let reply_id = channel_chat_b
474        .update(cx_b, |c, cx| {
475            c.send_message(
476                MessageParams {
477                    text: "reply".into(),
478                    reply_to_message_id: Some(msg_id),
479                    mentions: Vec::new(),
480                },
481                cx,
482            )
483            .unwrap()
484        })
485        .await
486        .unwrap();
487
488    cx_a.run_until_parked();
489
490    channel_chat_a.update(cx_a, |channel_chat, _| {
491        assert_eq!(
492            channel_chat
493                .find_loaded_message(reply_id)
494                .unwrap()
495                .reply_to_message_id,
496            Some(msg_id),
497        )
498    });
499}
500
501#[gpui::test]
502async fn test_chat_editing(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
503    let mut server = TestServer::start(cx_a.executor()).await;
504    let client_a = server.create_client(cx_a, "user_a").await;
505    let client_b = server.create_client(cx_b, "user_b").await;
506
507    let channel_id = server
508        .make_channel(
509            "the-channel",
510            None,
511            (&client_a, cx_a),
512            &mut [(&client_b, cx_b)],
513        )
514        .await;
515
516    // Client A sends a message, client B should see that there is a new message.
517    let channel_chat_a = client_a
518        .channel_store()
519        .update(cx_a, |store, cx| store.open_channel_chat(channel_id, cx))
520        .await
521        .unwrap();
522
523    let channel_chat_b = client_b
524        .channel_store()
525        .update(cx_b, |store, cx| store.open_channel_chat(channel_id, cx))
526        .await
527        .unwrap();
528
529    let msg_id = channel_chat_a
530        .update(cx_a, |c, cx| {
531            c.send_message(
532                MessageParams {
533                    text: "Initial message".into(),
534                    reply_to_message_id: None,
535                    mentions: Vec::new(),
536                },
537                cx,
538            )
539            .unwrap()
540        })
541        .await
542        .unwrap();
543
544    cx_a.run_until_parked();
545
546    channel_chat_a
547        .update(cx_a, |c, cx| {
548            c.update_message(
549                msg_id,
550                MessageParams {
551                    text: "Updated body".into(),
552                    reply_to_message_id: None,
553                    mentions: Vec::new(),
554                },
555                cx,
556            )
557            .unwrap()
558        })
559        .await
560        .unwrap();
561
562    cx_a.run_until_parked();
563    cx_b.run_until_parked();
564
565    channel_chat_a.update(cx_a, |channel_chat, _| {
566        let update_message = channel_chat.find_loaded_message(msg_id).unwrap();
567
568        assert_eq!(update_message.body, "Updated body");
569        assert_eq!(update_message.mentions, Vec::new());
570    });
571    channel_chat_b.update(cx_b, |channel_chat, _| {
572        let update_message = channel_chat.find_loaded_message(msg_id).unwrap();
573
574        assert_eq!(update_message.body, "Updated body");
575        assert_eq!(update_message.mentions, Vec::new());
576    });
577
578    // test mentions are updated correctly
579
580    client_b.notification_store().read_with(cx_b, |store, _| {
581        assert_eq!(store.notification_count(), 1);
582        let entry = store.notification_at(0).unwrap();
583        assert!(matches!(
584            entry.notification,
585            Notification::ChannelInvitation { .. }
586        ),);
587    });
588
589    channel_chat_a
590        .update(cx_a, |c, cx| {
591            c.update_message(
592                msg_id,
593                MessageParams {
594                    text: "Updated body including a mention for @user_b".into(),
595                    reply_to_message_id: None,
596                    mentions: vec![(37..45, client_b.id())],
597                },
598                cx,
599            )
600            .unwrap()
601        })
602        .await
603        .unwrap();
604
605    cx_a.run_until_parked();
606    cx_b.run_until_parked();
607
608    channel_chat_a.update(cx_a, |channel_chat, _| {
609        assert_eq!(
610            channel_chat.find_loaded_message(msg_id).unwrap().body,
611            "Updated body including a mention for @user_b",
612        )
613    });
614    channel_chat_b.update(cx_b, |channel_chat, _| {
615        assert_eq!(
616            channel_chat.find_loaded_message(msg_id).unwrap().body,
617            "Updated body including a mention for @user_b",
618        )
619    });
620    client_b.notification_store().read_with(cx_b, |store, _| {
621        assert_eq!(store.notification_count(), 2);
622        let entry = store.notification_at(0).unwrap();
623        assert_eq!(
624            entry.notification,
625            Notification::ChannelMessageMention {
626                message_id: msg_id,
627                sender_id: client_a.id(),
628                channel_id: channel_id.0,
629            }
630        );
631    });
632
633    // Test update message and keep the mention and check that the body is updated correctly
634
635    channel_chat_a
636        .update(cx_a, |c, cx| {
637            c.update_message(
638                msg_id,
639                MessageParams {
640                    text: "Updated body v2 including a mention for @user_b".into(),
641                    reply_to_message_id: None,
642                    mentions: vec![(37..45, client_b.id())],
643                },
644                cx,
645            )
646            .unwrap()
647        })
648        .await
649        .unwrap();
650
651    cx_a.run_until_parked();
652    cx_b.run_until_parked();
653
654    channel_chat_a.update(cx_a, |channel_chat, _| {
655        assert_eq!(
656            channel_chat.find_loaded_message(msg_id).unwrap().body,
657            "Updated body v2 including a mention for @user_b",
658        )
659    });
660    channel_chat_b.update(cx_b, |channel_chat, _| {
661        assert_eq!(
662            channel_chat.find_loaded_message(msg_id).unwrap().body,
663            "Updated body v2 including a mention for @user_b",
664        )
665    });
666
667    client_b.notification_store().read_with(cx_b, |store, _| {
668        let message = store.channel_message_for_id(msg_id);
669        assert!(message.is_some());
670        assert_eq!(
671            message.unwrap().body,
672            "Updated body v2 including a mention for @user_b"
673        );
674        assert_eq!(store.notification_count(), 2);
675        let entry = store.notification_at(0).unwrap();
676        assert_eq!(
677            entry.notification,
678            Notification::ChannelMessageMention {
679                message_id: msg_id,
680                sender_id: client_a.id(),
681                channel_id: channel_id.0,
682            }
683        );
684    });
685
686    // If we remove a mention from a message the corresponding mention notification
687    // should also be removed.
688
689    channel_chat_a
690        .update(cx_a, |c, cx| {
691            c.update_message(
692                msg_id,
693                MessageParams {
694                    text: "Updated body without a mention".into(),
695                    reply_to_message_id: None,
696                    mentions: vec![],
697                },
698                cx,
699            )
700            .unwrap()
701        })
702        .await
703        .unwrap();
704
705    cx_a.run_until_parked();
706    cx_b.run_until_parked();
707
708    channel_chat_a.update(cx_a, |channel_chat, _| {
709        assert_eq!(
710            channel_chat.find_loaded_message(msg_id).unwrap().body,
711            "Updated body without a mention",
712        )
713    });
714    channel_chat_b.update(cx_b, |channel_chat, _| {
715        assert_eq!(
716            channel_chat.find_loaded_message(msg_id).unwrap().body,
717            "Updated body without a mention",
718        )
719    });
720    client_b.notification_store().read_with(cx_b, |store, _| {
721        // First notification is the channel invitation, second would be the mention
722        // notification, which should now be removed.
723        assert_eq!(store.notification_count(), 1);
724    });
725}