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}