channel_tests.rs

  1use crate::{
  2    rpc::RECONNECT_TIMEOUT,
  3    tests::{room_participants, RoomParticipants, TestServer},
  4};
  5use call::ActiveCall;
  6use channel::{ChannelId, ChannelMembership, ChannelStore};
  7use client::User;
  8use gpui::{executor::Deterministic, ModelHandle, TestAppContext};
  9use rpc::{proto, RECEIVE_TIMEOUT};
 10use std::sync::Arc;
 11
 12#[gpui::test]
 13async fn test_core_channels(
 14    deterministic: Arc<Deterministic>,
 15    cx_a: &mut TestAppContext,
 16    cx_b: &mut TestAppContext,
 17) {
 18    deterministic.forbid_parking();
 19    let mut server = TestServer::start(&deterministic).await;
 20    let client_a = server.create_client(cx_a, "user_a").await;
 21    let client_b = server.create_client(cx_b, "user_b").await;
 22
 23    let channel_a_id = client_a
 24        .channel_store()
 25        .update(cx_a, |channel_store, cx| {
 26            channel_store.create_channel("channel-a", None, cx)
 27        })
 28        .await
 29        .unwrap();
 30    let channel_b_id = client_a
 31        .channel_store()
 32        .update(cx_a, |channel_store, cx| {
 33            channel_store.create_channel("channel-b", Some(channel_a_id), cx)
 34        })
 35        .await
 36        .unwrap();
 37
 38    deterministic.run_until_parked();
 39    assert_channels(
 40        client_a.channel_store(),
 41        cx_a,
 42        &[
 43            ExpectedChannel {
 44                id: channel_a_id,
 45                name: "channel-a".to_string(),
 46                depth: 0,
 47                user_is_admin: true,
 48            },
 49            ExpectedChannel {
 50                id: channel_b_id,
 51                name: "channel-b".to_string(),
 52                depth: 1,
 53                user_is_admin: true,
 54            },
 55        ],
 56    );
 57
 58    client_b.channel_store().read_with(cx_b, |channels, _| {
 59        assert!(channels.channels().collect::<Vec<_>>().is_empty())
 60    });
 61
 62    // Invite client B to channel A as client A.
 63    client_a
 64        .channel_store()
 65        .update(cx_a, |store, cx| {
 66            assert!(!store.has_pending_channel_invite(channel_a_id, client_b.user_id().unwrap()));
 67
 68            let invite = store.invite_member(channel_a_id, client_b.user_id().unwrap(), false, cx);
 69
 70            // Make sure we're synchronously storing the pending invite
 71            assert!(store.has_pending_channel_invite(channel_a_id, client_b.user_id().unwrap()));
 72            invite
 73        })
 74        .await
 75        .unwrap();
 76
 77    // Client A sees that B has been invited.
 78    deterministic.run_until_parked();
 79    assert_channel_invitations(
 80        client_b.channel_store(),
 81        cx_b,
 82        &[ExpectedChannel {
 83            id: channel_a_id,
 84            name: "channel-a".to_string(),
 85            depth: 0,
 86            user_is_admin: false,
 87        }],
 88    );
 89
 90    let members = client_a
 91        .channel_store()
 92        .update(cx_a, |store, cx| {
 93            assert!(!store.has_pending_channel_invite(channel_a_id, client_b.user_id().unwrap()));
 94            store.get_channel_member_details(channel_a_id, cx)
 95        })
 96        .await
 97        .unwrap();
 98    assert_members_eq(
 99        &members,
100        &[
101            (
102                client_a.user_id().unwrap(),
103                true,
104                proto::channel_member::Kind::Member,
105            ),
106            (
107                client_b.user_id().unwrap(),
108                false,
109                proto::channel_member::Kind::Invitee,
110            ),
111        ],
112    );
113
114    // Client B accepts the invitation.
115    client_b
116        .channel_store()
117        .update(cx_b, |channels, _| {
118            channels.respond_to_channel_invite(channel_a_id, true)
119        })
120        .await
121        .unwrap();
122    deterministic.run_until_parked();
123
124    // Client B now sees that they are a member of channel A and its existing subchannels.
125    assert_channel_invitations(client_b.channel_store(), cx_b, &[]);
126    assert_channels(
127        client_b.channel_store(),
128        cx_b,
129        &[
130            ExpectedChannel {
131                id: channel_a_id,
132                name: "channel-a".to_string(),
133                user_is_admin: false,
134                depth: 0,
135            },
136            ExpectedChannel {
137                id: channel_b_id,
138                name: "channel-b".to_string(),
139                user_is_admin: false,
140                depth: 1,
141            },
142        ],
143    );
144
145    let channel_c_id = client_a
146        .channel_store()
147        .update(cx_a, |channel_store, cx| {
148            channel_store.create_channel("channel-c", Some(channel_b_id), cx)
149        })
150        .await
151        .unwrap();
152
153    deterministic.run_until_parked();
154    assert_channels(
155        client_b.channel_store(),
156        cx_b,
157        &[
158            ExpectedChannel {
159                id: channel_a_id,
160                name: "channel-a".to_string(),
161                user_is_admin: false,
162                depth: 0,
163            },
164            ExpectedChannel {
165                id: channel_b_id,
166                name: "channel-b".to_string(),
167                user_is_admin: false,
168                depth: 1,
169            },
170            ExpectedChannel {
171                id: channel_c_id,
172                name: "channel-c".to_string(),
173                user_is_admin: false,
174                depth: 2,
175            },
176        ],
177    );
178
179    // Update client B's membership to channel A to be an admin.
180    client_a
181        .channel_store()
182        .update(cx_a, |store, cx| {
183            store.set_member_admin(channel_a_id, client_b.user_id().unwrap(), true, cx)
184        })
185        .await
186        .unwrap();
187    deterministic.run_until_parked();
188
189    // Observe that client B is now an admin of channel A, and that
190    // their admin priveleges extend to subchannels of channel A.
191    assert_channel_invitations(client_b.channel_store(), cx_b, &[]);
192    assert_channels(
193        client_b.channel_store(),
194        cx_b,
195        &[
196            ExpectedChannel {
197                id: channel_a_id,
198                name: "channel-a".to_string(),
199                depth: 0,
200                user_is_admin: true,
201            },
202            ExpectedChannel {
203                id: channel_b_id,
204                name: "channel-b".to_string(),
205                depth: 1,
206                user_is_admin: true,
207            },
208            ExpectedChannel {
209                id: channel_c_id,
210                name: "channel-c".to_string(),
211                depth: 2,
212                user_is_admin: true,
213            },
214        ],
215    );
216
217    // Client A deletes the channel, deletion also deletes subchannels.
218    client_a
219        .channel_store()
220        .update(cx_a, |channel_store, _| {
221            channel_store.remove_channel(channel_b_id)
222        })
223        .await
224        .unwrap();
225
226    deterministic.run_until_parked();
227    assert_channels(
228        client_a.channel_store(),
229        cx_a,
230        &[ExpectedChannel {
231            id: channel_a_id,
232            name: "channel-a".to_string(),
233            depth: 0,
234            user_is_admin: true,
235        }],
236    );
237    assert_channels(
238        client_b.channel_store(),
239        cx_b,
240        &[ExpectedChannel {
241            id: channel_a_id,
242            name: "channel-a".to_string(),
243            depth: 0,
244            user_is_admin: true,
245        }],
246    );
247
248    // Remove client B
249    client_a
250        .channel_store()
251        .update(cx_a, |channel_store, cx| {
252            channel_store.remove_member(channel_a_id, client_b.user_id().unwrap(), cx)
253        })
254        .await
255        .unwrap();
256
257    deterministic.run_until_parked();
258
259    // Client A still has their channel
260    assert_channels(
261        client_a.channel_store(),
262        cx_a,
263        &[ExpectedChannel {
264            id: channel_a_id,
265            name: "channel-a".to_string(),
266            depth: 0,
267            user_is_admin: true,
268        }],
269    );
270
271    // Client B no longer has access to the channel
272    assert_channels(client_b.channel_store(), cx_b, &[]);
273
274    // When disconnected, client A sees no channels.
275    server.forbid_connections();
276    server.disconnect_client(client_a.peer_id().unwrap());
277    deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
278    assert_channels(client_a.channel_store(), cx_a, &[]);
279
280    server.allow_connections();
281    deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
282    assert_channels(
283        client_a.channel_store(),
284        cx_a,
285        &[ExpectedChannel {
286            id: channel_a_id,
287            name: "channel-a".to_string(),
288            depth: 0,
289            user_is_admin: true,
290        }],
291    );
292}
293
294#[track_caller]
295fn assert_participants_eq(participants: &[Arc<User>], expected_partitipants: &[u64]) {
296    assert_eq!(
297        participants.iter().map(|p| p.id).collect::<Vec<_>>(),
298        expected_partitipants
299    );
300}
301
302#[track_caller]
303fn assert_members_eq(
304    members: &[ChannelMembership],
305    expected_members: &[(u64, bool, proto::channel_member::Kind)],
306) {
307    assert_eq!(
308        members
309            .iter()
310            .map(|member| (member.user.id, member.admin, member.kind))
311            .collect::<Vec<_>>(),
312        expected_members
313    );
314}
315
316#[gpui::test]
317async fn test_joining_channel_ancestor_member(
318    deterministic: Arc<Deterministic>,
319    cx_a: &mut TestAppContext,
320    cx_b: &mut TestAppContext,
321) {
322    deterministic.forbid_parking();
323    let mut server = TestServer::start(&deterministic).await;
324
325    let client_a = server.create_client(cx_a, "user_a").await;
326    let client_b = server.create_client(cx_b, "user_b").await;
327
328    let parent_id = server
329        .make_channel("parent", (&client_a, cx_a), &mut [(&client_b, cx_b)])
330        .await;
331
332    let sub_id = client_a
333        .channel_store()
334        .update(cx_a, |channel_store, cx| {
335            channel_store.create_channel("sub_channel", Some(parent_id), cx)
336        })
337        .await
338        .unwrap();
339
340    let active_call_b = cx_b.read(ActiveCall::global);
341
342    assert!(active_call_b
343        .update(cx_b, |active_call, cx| active_call.join_channel(sub_id, cx))
344        .await
345        .is_ok());
346}
347
348#[gpui::test]
349async fn test_channel_room(
350    deterministic: Arc<Deterministic>,
351    cx_a: &mut TestAppContext,
352    cx_b: &mut TestAppContext,
353    cx_c: &mut TestAppContext,
354) {
355    deterministic.forbid_parking();
356    let mut server = TestServer::start(&deterministic).await;
357    let client_a = server.create_client(cx_a, "user_a").await;
358    let client_b = server.create_client(cx_b, "user_b").await;
359    let client_c = server.create_client(cx_c, "user_c").await;
360
361    let zed_id = server
362        .make_channel(
363            "zed",
364            (&client_a, cx_a),
365            &mut [(&client_b, cx_b), (&client_c, cx_c)],
366        )
367        .await;
368
369    let active_call_a = cx_a.read(ActiveCall::global);
370    let active_call_b = cx_b.read(ActiveCall::global);
371
372    active_call_a
373        .update(cx_a, |active_call, cx| active_call.join_channel(zed_id, cx))
374        .await
375        .unwrap();
376
377    // Give everyone a chance to observe user A joining
378    deterministic.run_until_parked();
379
380    client_a.channel_store().read_with(cx_a, |channels, _| {
381        assert_participants_eq(
382            channels.channel_participants(zed_id),
383            &[client_a.user_id().unwrap()],
384        );
385    });
386
387    assert_channels(
388        client_b.channel_store(),
389        cx_b,
390        &[ExpectedChannel {
391            id: zed_id,
392            name: "zed".to_string(),
393            depth: 0,
394            user_is_admin: false,
395        }],
396    );
397    client_b.channel_store().read_with(cx_b, |channels, _| {
398        assert_participants_eq(
399            channels.channel_participants(zed_id),
400            &[client_a.user_id().unwrap()],
401        );
402    });
403
404    client_c.channel_store().read_with(cx_c, |channels, _| {
405        assert_participants_eq(
406            channels.channel_participants(zed_id),
407            &[client_a.user_id().unwrap()],
408        );
409    });
410
411    active_call_b
412        .update(cx_b, |active_call, cx| active_call.join_channel(zed_id, cx))
413        .await
414        .unwrap();
415
416    deterministic.run_until_parked();
417
418    client_a.channel_store().read_with(cx_a, |channels, _| {
419        assert_participants_eq(
420            channels.channel_participants(zed_id),
421            &[client_a.user_id().unwrap(), client_b.user_id().unwrap()],
422        );
423    });
424
425    client_b.channel_store().read_with(cx_b, |channels, _| {
426        assert_participants_eq(
427            channels.channel_participants(zed_id),
428            &[client_a.user_id().unwrap(), client_b.user_id().unwrap()],
429        );
430    });
431
432    client_c.channel_store().read_with(cx_c, |channels, _| {
433        assert_participants_eq(
434            channels.channel_participants(zed_id),
435            &[client_a.user_id().unwrap(), client_b.user_id().unwrap()],
436        );
437    });
438
439    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
440    room_a.read_with(cx_a, |room, _| assert!(room.is_connected()));
441    assert_eq!(
442        room_participants(&room_a, cx_a),
443        RoomParticipants {
444            remote: vec!["user_b".to_string()],
445            pending: vec![]
446        }
447    );
448
449    let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
450    room_b.read_with(cx_b, |room, _| assert!(room.is_connected()));
451    assert_eq!(
452        room_participants(&room_b, cx_b),
453        RoomParticipants {
454            remote: vec!["user_a".to_string()],
455            pending: vec![]
456        }
457    );
458
459    // Make sure that leaving and rejoining works
460
461    active_call_a
462        .update(cx_a, |active_call, cx| active_call.hang_up(cx))
463        .await
464        .unwrap();
465
466    deterministic.run_until_parked();
467
468    client_a.channel_store().read_with(cx_a, |channels, _| {
469        assert_participants_eq(
470            channels.channel_participants(zed_id),
471            &[client_b.user_id().unwrap()],
472        );
473    });
474
475    client_b.channel_store().read_with(cx_b, |channels, _| {
476        assert_participants_eq(
477            channels.channel_participants(zed_id),
478            &[client_b.user_id().unwrap()],
479        );
480    });
481
482    client_c.channel_store().read_with(cx_c, |channels, _| {
483        assert_participants_eq(
484            channels.channel_participants(zed_id),
485            &[client_b.user_id().unwrap()],
486        );
487    });
488
489    active_call_b
490        .update(cx_b, |active_call, cx| active_call.hang_up(cx))
491        .await
492        .unwrap();
493
494    deterministic.run_until_parked();
495
496    client_a.channel_store().read_with(cx_a, |channels, _| {
497        assert_participants_eq(channels.channel_participants(zed_id), &[]);
498    });
499
500    client_b.channel_store().read_with(cx_b, |channels, _| {
501        assert_participants_eq(channels.channel_participants(zed_id), &[]);
502    });
503
504    client_c.channel_store().read_with(cx_c, |channels, _| {
505        assert_participants_eq(channels.channel_participants(zed_id), &[]);
506    });
507
508    active_call_a
509        .update(cx_a, |active_call, cx| active_call.join_channel(zed_id, cx))
510        .await
511        .unwrap();
512
513    active_call_b
514        .update(cx_b, |active_call, cx| active_call.join_channel(zed_id, cx))
515        .await
516        .unwrap();
517
518    deterministic.run_until_parked();
519
520    let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
521    room_a.read_with(cx_a, |room, _| assert!(room.is_connected()));
522    assert_eq!(
523        room_participants(&room_a, cx_a),
524        RoomParticipants {
525            remote: vec!["user_b".to_string()],
526            pending: vec![]
527        }
528    );
529
530    let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
531    room_b.read_with(cx_b, |room, _| assert!(room.is_connected()));
532    assert_eq!(
533        room_participants(&room_b, cx_b),
534        RoomParticipants {
535            remote: vec!["user_a".to_string()],
536            pending: vec![]
537        }
538    );
539}
540
541#[gpui::test]
542async fn test_channel_jumping(deterministic: Arc<Deterministic>, cx_a: &mut TestAppContext) {
543    deterministic.forbid_parking();
544    let mut server = TestServer::start(&deterministic).await;
545    let client_a = server.create_client(cx_a, "user_a").await;
546
547    let zed_id = server.make_channel("zed", (&client_a, cx_a), &mut []).await;
548    let rust_id = server
549        .make_channel("rust", (&client_a, cx_a), &mut [])
550        .await;
551
552    let active_call_a = cx_a.read(ActiveCall::global);
553
554    active_call_a
555        .update(cx_a, |active_call, cx| active_call.join_channel(zed_id, cx))
556        .await
557        .unwrap();
558
559    // Give everything a chance to observe user A joining
560    deterministic.run_until_parked();
561
562    client_a.channel_store().read_with(cx_a, |channels, _| {
563        assert_participants_eq(
564            channels.channel_participants(zed_id),
565            &[client_a.user_id().unwrap()],
566        );
567        assert_participants_eq(channels.channel_participants(rust_id), &[]);
568    });
569
570    active_call_a
571        .update(cx_a, |active_call, cx| {
572            active_call.join_channel(rust_id, cx)
573        })
574        .await
575        .unwrap();
576
577    deterministic.run_until_parked();
578
579    client_a.channel_store().read_with(cx_a, |channels, _| {
580        assert_participants_eq(channels.channel_participants(zed_id), &[]);
581        assert_participants_eq(
582            channels.channel_participants(rust_id),
583            &[client_a.user_id().unwrap()],
584        );
585    });
586}
587
588#[gpui::test]
589async fn test_permissions_update_while_invited(
590    deterministic: Arc<Deterministic>,
591    cx_a: &mut TestAppContext,
592    cx_b: &mut TestAppContext,
593) {
594    deterministic.forbid_parking();
595    let mut server = TestServer::start(&deterministic).await;
596    let client_a = server.create_client(cx_a, "user_a").await;
597    let client_b = server.create_client(cx_b, "user_b").await;
598
599    let rust_id = server
600        .make_channel("rust", (&client_a, cx_a), &mut [])
601        .await;
602
603    client_a
604        .channel_store()
605        .update(cx_a, |channel_store, cx| {
606            channel_store.invite_member(rust_id, client_b.user_id().unwrap(), false, cx)
607        })
608        .await
609        .unwrap();
610
611    deterministic.run_until_parked();
612
613    assert_channel_invitations(
614        client_b.channel_store(),
615        cx_b,
616        &[ExpectedChannel {
617            depth: 0,
618            id: rust_id,
619            name: "rust".to_string(),
620            user_is_admin: false,
621        }],
622    );
623    assert_channels(client_b.channel_store(), cx_b, &[]);
624
625    // Update B's invite before they've accepted it
626    client_a
627        .channel_store()
628        .update(cx_a, |channel_store, cx| {
629            channel_store.set_member_admin(rust_id, client_b.user_id().unwrap(), true, cx)
630        })
631        .await
632        .unwrap();
633
634    deterministic.run_until_parked();
635
636    assert_channel_invitations(
637        client_b.channel_store(),
638        cx_b,
639        &[ExpectedChannel {
640            depth: 0,
641            id: rust_id,
642            name: "rust".to_string(),
643            user_is_admin: false,
644        }],
645    );
646    assert_channels(client_b.channel_store(), cx_b, &[]);
647}
648
649#[gpui::test]
650async fn test_channel_rename(
651    deterministic: Arc<Deterministic>,
652    cx_a: &mut TestAppContext,
653    cx_b: &mut TestAppContext,
654) {
655    deterministic.forbid_parking();
656    let mut server = TestServer::start(&deterministic).await;
657    let client_a = server.create_client(cx_a, "user_a").await;
658    let client_b = server.create_client(cx_b, "user_b").await;
659
660    let rust_id = server
661        .make_channel("rust", (&client_a, cx_a), &mut [(&client_b, cx_b)])
662        .await;
663
664    // Rename the channel
665    client_a
666        .channel_store()
667        .update(cx_a, |channel_store, cx| {
668            channel_store.rename(rust_id, "#rust-archive", cx)
669        })
670        .await
671        .unwrap();
672
673    deterministic.run_until_parked();
674
675    // Client A sees the channel with its new name.
676    assert_channels(
677        client_a.channel_store(),
678        cx_a,
679        &[ExpectedChannel {
680            depth: 0,
681            id: rust_id,
682            name: "rust-archive".to_string(),
683            user_is_admin: true,
684        }],
685    );
686
687    // Client B sees the channel with its new name.
688    assert_channels(
689        client_b.channel_store(),
690        cx_b,
691        &[ExpectedChannel {
692            depth: 0,
693            id: rust_id,
694            name: "rust-archive".to_string(),
695            user_is_admin: false,
696        }],
697    );
698}
699
700#[gpui::test]
701async fn test_call_from_channel(
702    deterministic: Arc<Deterministic>,
703    cx_a: &mut TestAppContext,
704    cx_b: &mut TestAppContext,
705    cx_c: &mut TestAppContext,
706) {
707    deterministic.forbid_parking();
708    let mut server = TestServer::start(&deterministic).await;
709    let client_a = server.create_client(cx_a, "user_a").await;
710    let client_b = server.create_client(cx_b, "user_b").await;
711    let client_c = server.create_client(cx_c, "user_c").await;
712    server
713        .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b)])
714        .await;
715
716    let channel_id = server
717        .make_channel(
718            "x",
719            (&client_a, cx_a),
720            &mut [(&client_b, cx_b), (&client_c, cx_c)],
721        )
722        .await;
723
724    let active_call_a = cx_a.read(ActiveCall::global);
725    let active_call_b = cx_b.read(ActiveCall::global);
726
727    active_call_a
728        .update(cx_a, |call, cx| call.join_channel(channel_id, cx))
729        .await
730        .unwrap();
731
732    // Client A calls client B while in the channel.
733    active_call_a
734        .update(cx_a, |call, cx| {
735            call.invite(client_b.user_id().unwrap(), None, cx)
736        })
737        .await
738        .unwrap();
739
740    // Client B accepts the call.
741    deterministic.run_until_parked();
742    active_call_b
743        .update(cx_b, |call, cx| call.accept_incoming(cx))
744        .await
745        .unwrap();
746
747    // Client B sees that they are now in the channel
748    deterministic.run_until_parked();
749    active_call_b.read_with(cx_b, |call, cx| {
750        assert_eq!(call.channel_id(cx), Some(channel_id));
751    });
752    client_b.channel_store().read_with(cx_b, |channels, _| {
753        assert_participants_eq(
754            channels.channel_participants(channel_id),
755            &[client_a.user_id().unwrap(), client_b.user_id().unwrap()],
756        );
757    });
758
759    // Clients A and C also see that client B is in the channel.
760    client_a.channel_store().read_with(cx_a, |channels, _| {
761        assert_participants_eq(
762            channels.channel_participants(channel_id),
763            &[client_a.user_id().unwrap(), client_b.user_id().unwrap()],
764        );
765    });
766    client_c.channel_store().read_with(cx_c, |channels, _| {
767        assert_participants_eq(
768            channels.channel_participants(channel_id),
769            &[client_a.user_id().unwrap(), client_b.user_id().unwrap()],
770        );
771    });
772}
773
774#[gpui::test]
775async fn test_lost_channel_creation(
776    deterministic: Arc<Deterministic>,
777    cx_a: &mut TestAppContext,
778    cx_b: &mut TestAppContext,
779) {
780    deterministic.forbid_parking();
781    let mut server = TestServer::start(&deterministic).await;
782    let client_a = server.create_client(cx_a, "user_a").await;
783    let client_b = server.create_client(cx_b, "user_b").await;
784
785    server
786        .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b)])
787        .await;
788
789    let channel_id = server.make_channel("x", (&client_a, cx_a), &mut []).await;
790
791    // Invite a member
792    client_a
793        .channel_store()
794        .update(cx_a, |channel_store, cx| {
795            channel_store.invite_member(channel_id, client_b.user_id().unwrap(), false, cx)
796        })
797        .await
798        .unwrap();
799
800    deterministic.run_until_parked();
801
802    // Sanity check, B has the invitation
803    assert_channel_invitations(
804        client_b.channel_store(),
805        cx_b,
806        &[ExpectedChannel {
807            depth: 0,
808            id: channel_id,
809            name: "x".to_string(),
810            user_is_admin: false,
811        }],
812    );
813
814    // A creates a subchannel while the invite is still pending.
815    let subchannel_id = client_a
816        .channel_store()
817        .update(cx_a, |channel_store, cx| {
818            channel_store.create_channel("subchannel", Some(channel_id), cx)
819        })
820        .await
821        .unwrap();
822
823    deterministic.run_until_parked();
824
825    // Make sure A sees their new channel
826    assert_channels(
827        client_a.channel_store(),
828        cx_a,
829        &[
830            ExpectedChannel {
831                depth: 0,
832                id: channel_id,
833                name: "x".to_string(),
834                user_is_admin: true,
835            },
836            ExpectedChannel {
837                depth: 1,
838                id: subchannel_id,
839                name: "subchannel".to_string(),
840                user_is_admin: true,
841            },
842        ],
843    );
844
845    // Client B accepts the invite
846    client_b
847        .channel_store()
848        .update(cx_b, |channel_store, _| {
849            channel_store.respond_to_channel_invite(channel_id, true)
850        })
851        .await
852        .unwrap();
853
854    deterministic.run_until_parked();
855
856    // Client B should now see the channel
857    assert_channels(
858        client_b.channel_store(),
859        cx_b,
860        &[
861            ExpectedChannel {
862                depth: 0,
863                id: channel_id,
864                name: "x".to_string(),
865                user_is_admin: false,
866            },
867            ExpectedChannel {
868                depth: 1,
869                id: subchannel_id,
870                name: "subchannel".to_string(),
871                user_is_admin: false,
872            },
873        ],
874    );
875}
876
877#[derive(Debug, PartialEq)]
878struct ExpectedChannel {
879    depth: usize,
880    id: ChannelId,
881    name: String,
882    user_is_admin: bool,
883}
884
885#[track_caller]
886fn assert_channel_invitations(
887    channel_store: &ModelHandle<ChannelStore>,
888    cx: &TestAppContext,
889    expected_channels: &[ExpectedChannel],
890) {
891    let actual = channel_store.read_with(cx, |store, _| {
892        store
893            .channel_invitations()
894            .iter()
895            .map(|channel| ExpectedChannel {
896                depth: 0,
897                name: channel.name.clone(),
898                id: channel.id,
899                user_is_admin: store.is_user_admin(channel.id),
900            })
901            .collect::<Vec<_>>()
902    });
903    assert_eq!(actual, expected_channels);
904}
905
906#[track_caller]
907fn assert_channels(
908    channel_store: &ModelHandle<ChannelStore>,
909    cx: &TestAppContext,
910    expected_channels: &[ExpectedChannel],
911) {
912    let actual = channel_store.read_with(cx, |store, _| {
913        store
914            .channels()
915            .map(|(depth, channel)| ExpectedChannel {
916                depth,
917                name: channel.name.clone(),
918                id: channel.id,
919                user_is_admin: store.is_user_admin(channel.id),
920            })
921            .collect::<Vec<_>>()
922    });
923    assert_eq!(actual, expected_channels);
924}