channel_tests.rs

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