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
60 .channel_dag_entries()
61 .collect::<Vec<_>>()
62 .is_empty())
63 });
64
65 // Invite client B to channel A as client A.
66 client_a
67 .channel_store()
68 .update(cx_a, |store, cx| {
69 assert!(!store.has_pending_channel_invite(channel_a_id, client_b.user_id().unwrap()));
70
71 let invite = store.invite_member(channel_a_id, client_b.user_id().unwrap(), false, cx);
72
73 // Make sure we're synchronously storing the pending invite
74 assert!(store.has_pending_channel_invite(channel_a_id, client_b.user_id().unwrap()));
75 invite
76 })
77 .await
78 .unwrap();
79
80 // Client A sees that B has been invited.
81 deterministic.run_until_parked();
82 assert_channel_invitations(
83 client_b.channel_store(),
84 cx_b,
85 &[ExpectedChannel {
86 id: channel_a_id,
87 name: "channel-a".to_string(),
88 depth: 0,
89 user_is_admin: false,
90 }],
91 );
92
93 let members = client_a
94 .channel_store()
95 .update(cx_a, |store, cx| {
96 assert!(!store.has_pending_channel_invite(channel_a_id, client_b.user_id().unwrap()));
97 store.get_channel_member_details(channel_a_id, cx)
98 })
99 .await
100 .unwrap();
101 assert_members_eq(
102 &members,
103 &[
104 (
105 client_a.user_id().unwrap(),
106 true,
107 proto::channel_member::Kind::Member,
108 ),
109 (
110 client_b.user_id().unwrap(),
111 false,
112 proto::channel_member::Kind::Invitee,
113 ),
114 ],
115 );
116
117 // Client B accepts the invitation.
118 client_b
119 .channel_store()
120 .update(cx_b, |channels, _| {
121 channels.respond_to_channel_invite(channel_a_id, true)
122 })
123 .await
124 .unwrap();
125 deterministic.run_until_parked();
126
127 // Client B now sees that they are a member of channel A and its existing subchannels.
128 assert_channel_invitations(client_b.channel_store(), cx_b, &[]);
129 assert_channels(
130 client_b.channel_store(),
131 cx_b,
132 &[
133 ExpectedChannel {
134 id: channel_a_id,
135 name: "channel-a".to_string(),
136 user_is_admin: false,
137 depth: 0,
138 },
139 ExpectedChannel {
140 id: channel_b_id,
141 name: "channel-b".to_string(),
142 user_is_admin: false,
143 depth: 1,
144 },
145 ],
146 );
147
148 let channel_c_id = client_a
149 .channel_store()
150 .update(cx_a, |channel_store, cx| {
151 channel_store.create_channel("channel-c", Some(channel_b_id), cx)
152 })
153 .await
154 .unwrap();
155
156 deterministic.run_until_parked();
157 assert_channels(
158 client_b.channel_store(),
159 cx_b,
160 &[
161 ExpectedChannel {
162 id: channel_a_id,
163 name: "channel-a".to_string(),
164 user_is_admin: false,
165 depth: 0,
166 },
167 ExpectedChannel {
168 id: channel_b_id,
169 name: "channel-b".to_string(),
170 user_is_admin: false,
171 depth: 1,
172 },
173 ExpectedChannel {
174 id: channel_c_id,
175 name: "channel-c".to_string(),
176 user_is_admin: false,
177 depth: 2,
178 },
179 ],
180 );
181
182 // Update client B's membership to channel A to be an admin.
183 client_a
184 .channel_store()
185 .update(cx_a, |store, cx| {
186 store.set_member_admin(channel_a_id, client_b.user_id().unwrap(), true, cx)
187 })
188 .await
189 .unwrap();
190 deterministic.run_until_parked();
191
192 // Observe that client B is now an admin of channel A, and that
193 // their admin priveleges extend to subchannels of channel A.
194 assert_channel_invitations(client_b.channel_store(), cx_b, &[]);
195 assert_channels(
196 client_b.channel_store(),
197 cx_b,
198 &[
199 ExpectedChannel {
200 id: channel_a_id,
201 name: "channel-a".to_string(),
202 depth: 0,
203 user_is_admin: true,
204 },
205 ExpectedChannel {
206 id: channel_b_id,
207 name: "channel-b".to_string(),
208 depth: 1,
209 user_is_admin: true,
210 },
211 ExpectedChannel {
212 id: channel_c_id,
213 name: "channel-c".to_string(),
214 depth: 2,
215 user_is_admin: true,
216 },
217 ],
218 );
219
220 // Client A deletes the channel, deletion also deletes subchannels.
221 client_a
222 .channel_store()
223 .update(cx_a, |channel_store, _| {
224 channel_store.remove_channel(channel_b_id)
225 })
226 .await
227 .unwrap();
228
229 deterministic.run_until_parked();
230 assert_channels(
231 client_a.channel_store(),
232 cx_a,
233 &[ExpectedChannel {
234 id: channel_a_id,
235 name: "channel-a".to_string(),
236 depth: 0,
237 user_is_admin: true,
238 }],
239 );
240 assert_channels(
241 client_b.channel_store(),
242 cx_b,
243 &[ExpectedChannel {
244 id: channel_a_id,
245 name: "channel-a".to_string(),
246 depth: 0,
247 user_is_admin: true,
248 }],
249 );
250
251 // Remove client B
252 client_a
253 .channel_store()
254 .update(cx_a, |channel_store, cx| {
255 channel_store.remove_member(channel_a_id, client_b.user_id().unwrap(), cx)
256 })
257 .await
258 .unwrap();
259
260 deterministic.run_until_parked();
261
262 // Client A still has their channel
263 assert_channels(
264 client_a.channel_store(),
265 cx_a,
266 &[ExpectedChannel {
267 id: channel_a_id,
268 name: "channel-a".to_string(),
269 depth: 0,
270 user_is_admin: true,
271 }],
272 );
273
274 // Client B no longer has access to the channel
275 assert_channels(client_b.channel_store(), cx_b, &[]);
276
277 // When disconnected, client A sees no channels.
278 server.forbid_connections();
279 server.disconnect_client(client_a.peer_id().unwrap());
280 deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
281 assert_channels(client_a.channel_store(), cx_a, &[]);
282
283 server.allow_connections();
284 deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
285 assert_channels(
286 client_a.channel_store(),
287 cx_a,
288 &[ExpectedChannel {
289 id: channel_a_id,
290 name: "channel-a".to_string(),
291 depth: 0,
292 user_is_admin: true,
293 }],
294 );
295}
296
297#[track_caller]
298fn assert_participants_eq(participants: &[Arc<User>], expected_partitipants: &[u64]) {
299 assert_eq!(
300 participants.iter().map(|p| p.id).collect::<Vec<_>>(),
301 expected_partitipants
302 );
303}
304
305#[track_caller]
306fn assert_members_eq(
307 members: &[ChannelMembership],
308 expected_members: &[(u64, bool, proto::channel_member::Kind)],
309) {
310 assert_eq!(
311 members
312 .iter()
313 .map(|member| (member.user.id, member.admin, member.kind))
314 .collect::<Vec<_>>(),
315 expected_members
316 );
317}
318
319#[gpui::test]
320async fn test_joining_channel_ancestor_member(
321 deterministic: Arc<Deterministic>,
322 cx_a: &mut TestAppContext,
323 cx_b: &mut TestAppContext,
324) {
325 deterministic.forbid_parking();
326 let mut server = TestServer::start(&deterministic).await;
327
328 let client_a = server.create_client(cx_a, "user_a").await;
329 let client_b = server.create_client(cx_b, "user_b").await;
330
331 let parent_id = server
332 .make_channel("parent", None, (&client_a, cx_a), &mut [(&client_b, cx_b)])
333 .await;
334
335 let sub_id = client_a
336 .channel_store()
337 .update(cx_a, |channel_store, cx| {
338 channel_store.create_channel("sub_channel", Some(parent_id), cx)
339 })
340 .await
341 .unwrap();
342
343 let active_call_b = cx_b.read(ActiveCall::global);
344
345 assert!(active_call_b
346 .update(cx_b, |active_call, cx| active_call.join_channel(sub_id, cx))
347 .await
348 .is_ok());
349}
350
351#[gpui::test]
352async fn test_channel_room(
353 deterministic: Arc<Deterministic>,
354 cx_a: &mut TestAppContext,
355 cx_b: &mut TestAppContext,
356 cx_c: &mut TestAppContext,
357) {
358 deterministic.forbid_parking();
359 let mut server = TestServer::start(&deterministic).await;
360 let client_a = server.create_client(cx_a, "user_a").await;
361 let client_b = server.create_client(cx_b, "user_b").await;
362 let client_c = server.create_client(cx_c, "user_c").await;
363
364 let zed_id = server
365 .make_channel(
366 "zed",
367 None,
368 (&client_a, cx_a),
369 &mut [(&client_b, cx_b), (&client_c, cx_c)],
370 )
371 .await;
372
373 let active_call_a = cx_a.read(ActiveCall::global);
374 let active_call_b = cx_b.read(ActiveCall::global);
375
376 active_call_a
377 .update(cx_a, |active_call, cx| active_call.join_channel(zed_id, cx))
378 .await
379 .unwrap();
380
381 // Give everyone a chance to observe user A joining
382 deterministic.run_until_parked();
383
384 client_a.channel_store().read_with(cx_a, |channels, _| {
385 assert_participants_eq(
386 channels.channel_participants(zed_id),
387 &[client_a.user_id().unwrap()],
388 );
389 });
390
391 assert_channels(
392 client_b.channel_store(),
393 cx_b,
394 &[ExpectedChannel {
395 id: zed_id,
396 name: "zed".to_string(),
397 depth: 0,
398 user_is_admin: false,
399 }],
400 );
401 client_b.channel_store().read_with(cx_b, |channels, _| {
402 assert_participants_eq(
403 channels.channel_participants(zed_id),
404 &[client_a.user_id().unwrap()],
405 );
406 });
407
408 client_c.channel_store().read_with(cx_c, |channels, _| {
409 assert_participants_eq(
410 channels.channel_participants(zed_id),
411 &[client_a.user_id().unwrap()],
412 );
413 });
414
415 active_call_b
416 .update(cx_b, |active_call, cx| active_call.join_channel(zed_id, cx))
417 .await
418 .unwrap();
419
420 deterministic.run_until_parked();
421
422 client_a.channel_store().read_with(cx_a, |channels, _| {
423 assert_participants_eq(
424 channels.channel_participants(zed_id),
425 &[client_a.user_id().unwrap(), client_b.user_id().unwrap()],
426 );
427 });
428
429 client_b.channel_store().read_with(cx_b, |channels, _| {
430 assert_participants_eq(
431 channels.channel_participants(zed_id),
432 &[client_a.user_id().unwrap(), client_b.user_id().unwrap()],
433 );
434 });
435
436 client_c.channel_store().read_with(cx_c, |channels, _| {
437 assert_participants_eq(
438 channels.channel_participants(zed_id),
439 &[client_a.user_id().unwrap(), client_b.user_id().unwrap()],
440 );
441 });
442
443 let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
444 room_a.read_with(cx_a, |room, _| assert!(room.is_connected()));
445 assert_eq!(
446 room_participants(&room_a, cx_a),
447 RoomParticipants {
448 remote: vec!["user_b".to_string()],
449 pending: vec![]
450 }
451 );
452
453 let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
454 room_b.read_with(cx_b, |room, _| assert!(room.is_connected()));
455 assert_eq!(
456 room_participants(&room_b, cx_b),
457 RoomParticipants {
458 remote: vec!["user_a".to_string()],
459 pending: vec![]
460 }
461 );
462
463 // Make sure that leaving and rejoining works
464
465 active_call_a
466 .update(cx_a, |active_call, cx| active_call.hang_up(cx))
467 .await
468 .unwrap();
469
470 deterministic.run_until_parked();
471
472 client_a.channel_store().read_with(cx_a, |channels, _| {
473 assert_participants_eq(
474 channels.channel_participants(zed_id),
475 &[client_b.user_id().unwrap()],
476 );
477 });
478
479 client_b.channel_store().read_with(cx_b, |channels, _| {
480 assert_participants_eq(
481 channels.channel_participants(zed_id),
482 &[client_b.user_id().unwrap()],
483 );
484 });
485
486 client_c.channel_store().read_with(cx_c, |channels, _| {
487 assert_participants_eq(
488 channels.channel_participants(zed_id),
489 &[client_b.user_id().unwrap()],
490 );
491 });
492
493 active_call_b
494 .update(cx_b, |active_call, cx| active_call.hang_up(cx))
495 .await
496 .unwrap();
497
498 deterministic.run_until_parked();
499
500 client_a.channel_store().read_with(cx_a, |channels, _| {
501 assert_participants_eq(channels.channel_participants(zed_id), &[]);
502 });
503
504 client_b.channel_store().read_with(cx_b, |channels, _| {
505 assert_participants_eq(channels.channel_participants(zed_id), &[]);
506 });
507
508 client_c.channel_store().read_with(cx_c, |channels, _| {
509 assert_participants_eq(channels.channel_participants(zed_id), &[]);
510 });
511
512 active_call_a
513 .update(cx_a, |active_call, cx| active_call.join_channel(zed_id, cx))
514 .await
515 .unwrap();
516
517 active_call_b
518 .update(cx_b, |active_call, cx| active_call.join_channel(zed_id, cx))
519 .await
520 .unwrap();
521
522 deterministic.run_until_parked();
523
524 let room_a = active_call_a.read_with(cx_a, |call, _| call.room().unwrap().clone());
525 room_a.read_with(cx_a, |room, _| assert!(room.is_connected()));
526 assert_eq!(
527 room_participants(&room_a, cx_a),
528 RoomParticipants {
529 remote: vec!["user_b".to_string()],
530 pending: vec![]
531 }
532 );
533
534 let room_b = active_call_b.read_with(cx_b, |call, _| call.room().unwrap().clone());
535 room_b.read_with(cx_b, |room, _| assert!(room.is_connected()));
536 assert_eq!(
537 room_participants(&room_b, cx_b),
538 RoomParticipants {
539 remote: vec!["user_a".to_string()],
540 pending: vec![]
541 }
542 );
543}
544
545#[gpui::test]
546async fn test_channel_jumping(deterministic: Arc<Deterministic>, cx_a: &mut TestAppContext) {
547 deterministic.forbid_parking();
548 let mut server = TestServer::start(&deterministic).await;
549 let client_a = server.create_client(cx_a, "user_a").await;
550
551 let zed_id = server
552 .make_channel("zed", None, (&client_a, cx_a), &mut [])
553 .await;
554 let rust_id = server
555 .make_channel("rust", None, (&client_a, cx_a), &mut [])
556 .await;
557
558 let active_call_a = cx_a.read(ActiveCall::global);
559
560 active_call_a
561 .update(cx_a, |active_call, cx| active_call.join_channel(zed_id, cx))
562 .await
563 .unwrap();
564
565 // Give everything a chance to observe user A joining
566 deterministic.run_until_parked();
567
568 client_a.channel_store().read_with(cx_a, |channels, _| {
569 assert_participants_eq(
570 channels.channel_participants(zed_id),
571 &[client_a.user_id().unwrap()],
572 );
573 assert_participants_eq(channels.channel_participants(rust_id), &[]);
574 });
575
576 active_call_a
577 .update(cx_a, |active_call, cx| {
578 active_call.join_channel(rust_id, cx)
579 })
580 .await
581 .unwrap();
582
583 deterministic.run_until_parked();
584
585 client_a.channel_store().read_with(cx_a, |channels, _| {
586 assert_participants_eq(channels.channel_participants(zed_id), &[]);
587 assert_participants_eq(
588 channels.channel_participants(rust_id),
589 &[client_a.user_id().unwrap()],
590 );
591 });
592}
593
594#[gpui::test]
595async fn test_permissions_update_while_invited(
596 deterministic: Arc<Deterministic>,
597 cx_a: &mut TestAppContext,
598 cx_b: &mut TestAppContext,
599) {
600 deterministic.forbid_parking();
601 let mut server = TestServer::start(&deterministic).await;
602 let client_a = server.create_client(cx_a, "user_a").await;
603 let client_b = server.create_client(cx_b, "user_b").await;
604
605 let rust_id = server
606 .make_channel("rust", None, (&client_a, cx_a), &mut [])
607 .await;
608
609 client_a
610 .channel_store()
611 .update(cx_a, |channel_store, cx| {
612 channel_store.invite_member(rust_id, client_b.user_id().unwrap(), false, cx)
613 })
614 .await
615 .unwrap();
616
617 deterministic.run_until_parked();
618
619 assert_channel_invitations(
620 client_b.channel_store(),
621 cx_b,
622 &[ExpectedChannel {
623 depth: 0,
624 id: rust_id,
625 name: "rust".to_string(),
626 user_is_admin: false,
627 }],
628 );
629 assert_channels(client_b.channel_store(), cx_b, &[]);
630
631 // Update B's invite before they've accepted it
632 client_a
633 .channel_store()
634 .update(cx_a, |channel_store, cx| {
635 channel_store.set_member_admin(rust_id, client_b.user_id().unwrap(), true, cx)
636 })
637 .await
638 .unwrap();
639
640 deterministic.run_until_parked();
641
642 assert_channel_invitations(
643 client_b.channel_store(),
644 cx_b,
645 &[ExpectedChannel {
646 depth: 0,
647 id: rust_id,
648 name: "rust".to_string(),
649 user_is_admin: false,
650 }],
651 );
652 assert_channels(client_b.channel_store(), cx_b, &[]);
653}
654
655#[gpui::test]
656async fn test_channel_rename(
657 deterministic: Arc<Deterministic>,
658 cx_a: &mut TestAppContext,
659 cx_b: &mut TestAppContext,
660) {
661 deterministic.forbid_parking();
662 let mut server = TestServer::start(&deterministic).await;
663 let client_a = server.create_client(cx_a, "user_a").await;
664 let client_b = server.create_client(cx_b, "user_b").await;
665
666 let rust_id = server
667 .make_channel("rust", None, (&client_a, cx_a), &mut [(&client_b, cx_b)])
668 .await;
669
670 // Rename the channel
671 client_a
672 .channel_store()
673 .update(cx_a, |channel_store, cx| {
674 channel_store.rename(rust_id, "#rust-archive", cx)
675 })
676 .await
677 .unwrap();
678
679 deterministic.run_until_parked();
680
681 // Client A sees the channel with its new name.
682 assert_channels(
683 client_a.channel_store(),
684 cx_a,
685 &[ExpectedChannel {
686 depth: 0,
687 id: rust_id,
688 name: "rust-archive".to_string(),
689 user_is_admin: true,
690 }],
691 );
692
693 // Client B sees the channel with its new name.
694 assert_channels(
695 client_b.channel_store(),
696 cx_b,
697 &[ExpectedChannel {
698 depth: 0,
699 id: rust_id,
700 name: "rust-archive".to_string(),
701 user_is_admin: false,
702 }],
703 );
704}
705
706#[gpui::test]
707async fn test_call_from_channel(
708 deterministic: Arc<Deterministic>,
709 cx_a: &mut TestAppContext,
710 cx_b: &mut TestAppContext,
711 cx_c: &mut TestAppContext,
712) {
713 deterministic.forbid_parking();
714 let mut server = TestServer::start(&deterministic).await;
715 let client_a = server.create_client(cx_a, "user_a").await;
716 let client_b = server.create_client(cx_b, "user_b").await;
717 let client_c = server.create_client(cx_c, "user_c").await;
718 server
719 .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b)])
720 .await;
721
722 let channel_id = server
723 .make_channel(
724 "x",
725 None,
726 (&client_a, cx_a),
727 &mut [(&client_b, cx_b), (&client_c, cx_c)],
728 )
729 .await;
730
731 let active_call_a = cx_a.read(ActiveCall::global);
732 let active_call_b = cx_b.read(ActiveCall::global);
733
734 active_call_a
735 .update(cx_a, |call, cx| call.join_channel(channel_id, cx))
736 .await
737 .unwrap();
738
739 // Client A calls client B while in the channel.
740 active_call_a
741 .update(cx_a, |call, cx| {
742 call.invite(client_b.user_id().unwrap(), None, cx)
743 })
744 .await
745 .unwrap();
746
747 // Client B accepts the call.
748 deterministic.run_until_parked();
749 active_call_b
750 .update(cx_b, |call, cx| call.accept_incoming(cx))
751 .await
752 .unwrap();
753
754 // Client B sees that they are now in the channel
755 deterministic.run_until_parked();
756 active_call_b.read_with(cx_b, |call, cx| {
757 assert_eq!(call.channel_id(cx), Some(channel_id));
758 });
759 client_b.channel_store().read_with(cx_b, |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
766 // Clients A and C also see that client B is in the channel.
767 client_a.channel_store().read_with(cx_a, |channels, _| {
768 assert_participants_eq(
769 channels.channel_participants(channel_id),
770 &[client_a.user_id().unwrap(), client_b.user_id().unwrap()],
771 );
772 });
773 client_c.channel_store().read_with(cx_c, |channels, _| {
774 assert_participants_eq(
775 channels.channel_participants(channel_id),
776 &[client_a.user_id().unwrap(), client_b.user_id().unwrap()],
777 );
778 });
779}
780
781#[gpui::test]
782async fn test_lost_channel_creation(
783 deterministic: Arc<Deterministic>,
784 cx_a: &mut TestAppContext,
785 cx_b: &mut TestAppContext,
786) {
787 deterministic.forbid_parking();
788 let mut server = TestServer::start(&deterministic).await;
789 let client_a = server.create_client(cx_a, "user_a").await;
790 let client_b = server.create_client(cx_b, "user_b").await;
791
792 server
793 .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b)])
794 .await;
795
796 let channel_id = server
797 .make_channel("x", None, (&client_a, cx_a), &mut [])
798 .await;
799
800 // Invite a member
801 client_a
802 .channel_store()
803 .update(cx_a, |channel_store, cx| {
804 channel_store.invite_member(channel_id, client_b.user_id().unwrap(), false, cx)
805 })
806 .await
807 .unwrap();
808
809 deterministic.run_until_parked();
810
811 // Sanity check, B has the invitation
812 assert_channel_invitations(
813 client_b.channel_store(),
814 cx_b,
815 &[ExpectedChannel {
816 depth: 0,
817 id: channel_id,
818 name: "x".to_string(),
819 user_is_admin: false,
820 }],
821 );
822
823 // A creates a subchannel while the invite is still pending.
824 let subchannel_id = client_a
825 .channel_store()
826 .update(cx_a, |channel_store, cx| {
827 channel_store.create_channel("subchannel", Some(channel_id), cx)
828 })
829 .await
830 .unwrap();
831
832 deterministic.run_until_parked();
833
834 // Make sure A sees their new channel
835 assert_channels(
836 client_a.channel_store(),
837 cx_a,
838 &[
839 ExpectedChannel {
840 depth: 0,
841 id: channel_id,
842 name: "x".to_string(),
843 user_is_admin: true,
844 },
845 ExpectedChannel {
846 depth: 1,
847 id: subchannel_id,
848 name: "subchannel".to_string(),
849 user_is_admin: true,
850 },
851 ],
852 );
853
854 // Client B accepts the invite
855 client_b
856 .channel_store()
857 .update(cx_b, |channel_store, _| {
858 channel_store.respond_to_channel_invite(channel_id, true)
859 })
860 .await
861 .unwrap();
862
863 deterministic.run_until_parked();
864
865 // Client B should now see the channel
866 assert_channels(
867 client_b.channel_store(),
868 cx_b,
869 &[
870 ExpectedChannel {
871 depth: 0,
872 id: channel_id,
873 name: "x".to_string(),
874 user_is_admin: false,
875 },
876 ExpectedChannel {
877 depth: 1,
878 id: subchannel_id,
879 name: "subchannel".to_string(),
880 user_is_admin: false,
881 },
882 ],
883 );
884}
885
886#[gpui::test]
887async fn test_channel_moving(
888 deterministic: Arc<Deterministic>,
889 cx_a: &mut TestAppContext,
890 cx_b: &mut TestAppContext,
891 cx_c: &mut TestAppContext,
892) {
893 deterministic.forbid_parking();
894 let mut server = TestServer::start(&deterministic).await;
895 let client_a = server.create_client(cx_a, "user_a").await;
896 let client_b = server.create_client(cx_b, "user_b").await;
897 let client_c = server.create_client(cx_c, "user_c").await;
898
899 let channels = server
900 .make_channel_tree(
901 &[
902 ("channel-a", None),
903 ("channel-b", Some("channel-a")),
904 ("channel-c", Some("channel-b")),
905 ("channel-d", Some("channel-c")),
906 ],
907 (&client_a, cx_a),
908 )
909 .await;
910 let channel_a_id = channels[0];
911 let channel_b_id = channels[1];
912 let channel_c_id = channels[2];
913 let channel_d_id = channels[3];
914
915 // Current shape:
916 // a - b - c - d
917 assert_channels_list_shape(
918 client_a.channel_store(),
919 cx_a,
920 &[
921 (channel_a_id, 0),
922 (channel_b_id, 1),
923 (channel_c_id, 2),
924 (channel_d_id, 3),
925 ],
926 );
927
928 client_a
929 .channel_store()
930 .update(cx_a, |channel_store, cx| {
931 channel_store.move_channel(channel_d_id, channel_c_id, channel_b_id, cx)
932 })
933 .await
934 .unwrap();
935
936 // Current shape:
937 // /- d
938 // a - b -- c
939 assert_channels_list_shape(
940 client_a.channel_store(),
941 cx_a,
942 &[
943 (channel_a_id, 0),
944 (channel_b_id, 1),
945 (channel_c_id, 2),
946 (channel_d_id, 2),
947 ],
948 );
949
950 client_a
951 .channel_store()
952 .update(cx_a, |channel_store, cx| {
953 channel_store.link_channel(channel_d_id, channel_c_id, cx)
954 })
955 .await
956 .unwrap();
957
958 // Current shape for A:
959 // /------\
960 // a - b -- c -- d
961 assert_channels_list_shape(
962 client_a.channel_store(),
963 cx_a,
964 &[
965 (channel_a_id, 0),
966 (channel_b_id, 1),
967 (channel_c_id, 2),
968 (channel_d_id, 3),
969 (channel_d_id, 2),
970 ],
971 );
972
973 let b_channels = server
974 .make_channel_tree(
975 &[
976 ("channel-mu", None),
977 ("channel-gamma", Some("channel-mu")),
978 ("channel-epsilon", Some("channel-mu")),
979 ],
980 (&client_b, cx_b),
981 )
982 .await;
983 let channel_mu_id = b_channels[0];
984 let channel_ga_id = b_channels[1];
985 let channel_ep_id = b_channels[2];
986
987 // Current shape for B:
988 // /- ep
989 // mu -- ga
990 assert_channels_list_shape(
991 client_b.channel_store(),
992 cx_b,
993 &[(channel_mu_id, 0), (channel_ep_id, 1), (channel_ga_id, 1)],
994 );
995
996 client_a
997 .add_admin_to_channel((&client_b, cx_b), channel_b_id, cx_a)
998 .await;
999
1000 // Current shape for B:
1001 // /- ep
1002 // mu -- ga
1003 // /---------\
1004 // b -- c -- d
1005 assert_channels_list_shape(
1006 client_b.channel_store(),
1007 cx_b,
1008 &[
1009 // New channels from a
1010 (channel_b_id, 0),
1011 (channel_c_id, 1),
1012 (channel_d_id, 2),
1013 (channel_d_id, 1),
1014 // B's old channels
1015 (channel_mu_id, 0),
1016 (channel_ep_id, 1),
1017 (channel_ga_id, 1),
1018 ],
1019 );
1020
1021 client_b
1022 .add_admin_to_channel((&client_c, cx_c), channel_ep_id, cx_b)
1023 .await;
1024
1025 // Current shape for C:
1026 // - ep
1027 assert_channels_list_shape(client_c.channel_store(), cx_c, &[(channel_ep_id, 0)]);
1028
1029 client_b
1030 .channel_store()
1031 .update(cx_b, |channel_store, cx| {
1032 channel_store.link_channel(channel_b_id, channel_ep_id, cx)
1033 })
1034 .await
1035 .unwrap();
1036
1037 // Current shape for B:
1038 // /---------\
1039 // /- ep -- b -- c -- d
1040 // mu -- ga
1041 assert_channels_list_shape(
1042 client_b.channel_store(),
1043 cx_b,
1044 &[
1045 (channel_mu_id, 0),
1046 (channel_ep_id, 1),
1047 (channel_b_id, 2),
1048 (channel_c_id, 3),
1049 (channel_d_id, 4),
1050 (channel_d_id, 3),
1051 (channel_ga_id, 1),
1052 ],
1053 );
1054
1055 // Current shape for C:
1056 // /---------\
1057 // ep -- b -- c -- d
1058 assert_channels_list_shape(
1059 client_c.channel_store(),
1060 cx_c,
1061 &[
1062 (channel_ep_id, 0),
1063 (channel_b_id, 1),
1064 (channel_c_id, 2),
1065 (channel_d_id, 3),
1066 (channel_d_id, 2),
1067 ],
1068 );
1069
1070 client_b
1071 .channel_store()
1072 .update(cx_b, |channel_store, cx| {
1073 channel_store.link_channel(channel_ga_id, channel_b_id, cx)
1074 })
1075 .await
1076 .unwrap();
1077
1078 // Current shape for B:
1079 // /---------\
1080 // /- ep -- b -- c -- d
1081 // / \
1082 // mu ---------- ga
1083 assert_channels_list_shape(
1084 client_b.channel_store(),
1085 cx_b,
1086 &[
1087 (channel_mu_id, 0),
1088 (channel_ep_id, 1),
1089 (channel_b_id, 2),
1090 (channel_c_id, 3),
1091 (channel_d_id, 4),
1092 (channel_d_id, 3),
1093 (channel_ga_id, 3),
1094 (channel_ga_id, 1),
1095 ],
1096 );
1097
1098 // Current shape for A:
1099 // /------\
1100 // a - b -- c -- d
1101 // \-- ga
1102 assert_channels_list_shape(
1103 client_a.channel_store(),
1104 cx_a,
1105 &[
1106 (channel_a_id, 0),
1107 (channel_b_id, 1),
1108 (channel_c_id, 2),
1109 (channel_d_id, 3),
1110 (channel_d_id, 2),
1111 (channel_ga_id, 2),
1112 ],
1113 );
1114
1115 // Current shape for C:
1116 // /-------\
1117 // ep -- b -- c -- d
1118 // \-- ga
1119 assert_channels_list_shape(
1120 client_c.channel_store(),
1121 cx_c,
1122 &[
1123 (channel_ep_id, 0),
1124 (channel_b_id, 1),
1125 (channel_c_id, 2),
1126 (channel_d_id, 3),
1127 (channel_d_id, 2),
1128 (channel_ga_id, 2),
1129 ],
1130 );
1131}
1132
1133#[derive(Debug, PartialEq)]
1134struct ExpectedChannel {
1135 depth: usize,
1136 id: ChannelId,
1137 name: String,
1138 user_is_admin: bool,
1139}
1140
1141#[track_caller]
1142fn assert_channel_invitations(
1143 channel_store: &ModelHandle<ChannelStore>,
1144 cx: &TestAppContext,
1145 expected_channels: &[ExpectedChannel],
1146) {
1147 let actual = channel_store.read_with(cx, |store, _| {
1148 store
1149 .channel_invitations()
1150 .iter()
1151 .map(|channel| ExpectedChannel {
1152 depth: 0,
1153 name: channel.name.clone(),
1154 id: channel.id,
1155 user_is_admin: store.is_user_admin(channel.id),
1156 })
1157 .collect::<Vec<_>>()
1158 });
1159 assert_eq!(actual, expected_channels);
1160}
1161
1162#[track_caller]
1163fn assert_channels(
1164 channel_store: &ModelHandle<ChannelStore>,
1165 cx: &TestAppContext,
1166 expected_channels: &[ExpectedChannel],
1167) {
1168 let actual = channel_store.read_with(cx, |store, _| {
1169 store
1170 .channel_dag_entries()
1171 .map(|(depth, channel)| ExpectedChannel {
1172 depth,
1173 name: channel.name.clone(),
1174 id: channel.id,
1175 user_is_admin: store.is_user_admin(channel.id),
1176 })
1177 .collect::<Vec<_>>()
1178 });
1179 pretty_assertions::assert_eq!(actual, expected_channels);
1180}
1181
1182#[track_caller]
1183fn assert_channels_list_shape(
1184 channel_store: &ModelHandle<ChannelStore>,
1185 cx: &TestAppContext,
1186 expected_channels: &[(u64, usize)],
1187) {
1188 cx.foreground().run_until_parked();
1189
1190 let actual = channel_store.read_with(cx, |store, _| {
1191 store
1192 .channel_dag_entries()
1193 .map(|(depth, channel)| (channel.id, depth))
1194 .collect::<Vec<_>>()
1195 });
1196 pretty_assertions::assert_eq!(actual, expected_channels);
1197}