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}