1use crate::{
2 rpc::{CLEANUP_TIMEOUT, RECONNECT_TIMEOUT},
3 tests::TestServer,
4};
5use call::ActiveCall;
6use channel::Channel;
7use client::ParticipantIndex;
8use client::{Collaborator, UserId};
9use collab_ui::channel_view::ChannelView;
10use collections::HashMap;
11use editor::{Anchor, Editor, ToOffset};
12use futures::future;
13use gpui::{executor::Deterministic, ModelHandle, TestAppContext, ViewContext};
14use rpc::{proto::PeerId, RECEIVE_TIMEOUT};
15use serde_json::json;
16use std::{ops::Range, sync::Arc};
17
18#[gpui::test]
19async fn test_core_channel_buffers(
20 deterministic: Arc<Deterministic>,
21 cx_a: &mut TestAppContext,
22 cx_b: &mut TestAppContext,
23) {
24 deterministic.forbid_parking();
25 let mut server = TestServer::start(&deterministic).await;
26 let client_a = server.create_client(cx_a, "user_a").await;
27 let client_b = server.create_client(cx_b, "user_b").await;
28
29 let channel_id = server
30 .make_channel("zed", None, (&client_a, cx_a), &mut [(&client_b, cx_b)])
31 .await;
32
33 // Client A joins the channel buffer
34 let channel_buffer_a = client_a
35 .channel_store()
36 .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx))
37 .await
38 .unwrap();
39
40 // Client A edits the buffer
41 let buffer_a = channel_buffer_a.read_with(cx_a, |buffer, _| buffer.buffer());
42 buffer_a.update(cx_a, |buffer, cx| {
43 buffer.edit([(0..0, "hello world")], None, cx)
44 });
45 buffer_a.update(cx_a, |buffer, cx| {
46 buffer.edit([(5..5, ", cruel")], None, cx)
47 });
48 buffer_a.update(cx_a, |buffer, cx| {
49 buffer.edit([(0..5, "goodbye")], None, cx)
50 });
51 buffer_a.update(cx_a, |buffer, cx| buffer.undo(cx));
52 assert_eq!(buffer_text(&buffer_a, cx_a), "hello, cruel world");
53 deterministic.run_until_parked();
54
55 // Client B joins the channel buffer
56 let channel_buffer_b = client_b
57 .channel_store()
58 .update(cx_b, |store, cx| store.open_channel_buffer(channel_id, cx))
59 .await
60 .unwrap();
61 channel_buffer_b.read_with(cx_b, |buffer, _| {
62 assert_collaborators(
63 buffer.collaborators(),
64 &[client_a.user_id(), client_b.user_id()],
65 );
66 });
67
68 // Client B sees the correct text, and then edits it
69 let buffer_b = channel_buffer_b.read_with(cx_b, |buffer, _| buffer.buffer());
70 assert_eq!(
71 buffer_b.read_with(cx_b, |buffer, _| buffer.remote_id()),
72 buffer_a.read_with(cx_a, |buffer, _| buffer.remote_id())
73 );
74 assert_eq!(buffer_text(&buffer_b, cx_b), "hello, cruel world");
75 buffer_b.update(cx_b, |buffer, cx| {
76 buffer.edit([(7..12, "beautiful")], None, cx)
77 });
78
79 // Both A and B see the new edit
80 deterministic.run_until_parked();
81 assert_eq!(buffer_text(&buffer_a, cx_a), "hello, beautiful world");
82 assert_eq!(buffer_text(&buffer_b, cx_b), "hello, beautiful world");
83
84 // Client A closes the channel buffer.
85 cx_a.update(|_| drop(channel_buffer_a));
86 deterministic.run_until_parked();
87
88 // Client B sees that client A is gone from the channel buffer.
89 channel_buffer_b.read_with(cx_b, |buffer, _| {
90 assert_collaborators(&buffer.collaborators(), &[client_b.user_id()]);
91 });
92
93 // Client A rejoins the channel buffer
94 let _channel_buffer_a = client_a
95 .channel_store()
96 .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx))
97 .await
98 .unwrap();
99 deterministic.run_until_parked();
100
101 // Sanity test, make sure we saw A rejoining
102 channel_buffer_b.read_with(cx_b, |buffer, _| {
103 assert_collaborators(
104 &buffer.collaborators(),
105 &[client_a.user_id(), client_b.user_id()],
106 );
107 });
108
109 // Client A loses connection.
110 server.forbid_connections();
111 server.disconnect_client(client_a.peer_id().unwrap());
112 deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
113
114 // Client B observes A disconnect
115 channel_buffer_b.read_with(cx_b, |buffer, _| {
116 assert_collaborators(&buffer.collaborators(), &[client_b.user_id()]);
117 });
118
119 // TODO:
120 // - Test synchronizing offline updates, what happens to A's channel buffer when A disconnects
121 // - Test interaction with channel deletion while buffer is open
122}
123
124#[gpui::test]
125async fn test_channel_notes_participant_indices(
126 deterministic: Arc<Deterministic>,
127 mut cx_a: &mut TestAppContext,
128 mut cx_b: &mut TestAppContext,
129 cx_c: &mut TestAppContext,
130) {
131 deterministic.forbid_parking();
132 let mut server = TestServer::start(&deterministic).await;
133 let client_a = server.create_client(cx_a, "user_a").await;
134 let client_b = server.create_client(cx_b, "user_b").await;
135 let client_c = server.create_client(cx_c, "user_c").await;
136
137 let active_call_a = cx_a.read(ActiveCall::global);
138 let active_call_b = cx_b.read(ActiveCall::global);
139
140 cx_a.update(editor::init);
141 cx_b.update(editor::init);
142 cx_c.update(editor::init);
143
144 let channel_id = server
145 .make_channel(
146 "the-channel",
147 None,
148 (&client_a, cx_a),
149 &mut [(&client_b, cx_b), (&client_c, cx_c)],
150 )
151 .await;
152
153 client_a
154 .fs()
155 .insert_tree("/root", json!({"file.txt": "123"}))
156 .await;
157 let (project_a, worktree_id_a) = client_a.build_local_project("/root", cx_a).await;
158 let project_b = client_b.build_empty_local_project(cx_b);
159 let project_c = client_c.build_empty_local_project(cx_c);
160 let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
161 let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
162 let workspace_c = client_c.build_workspace(&project_c, cx_c).root(cx_c);
163
164 // Clients A, B, and C open the channel notes
165 let channel_view_a = cx_a
166 .update(|cx| ChannelView::open(channel_id, workspace_a.clone(), cx))
167 .await
168 .unwrap();
169 let channel_view_b = cx_b
170 .update(|cx| ChannelView::open(channel_id, workspace_b.clone(), cx))
171 .await
172 .unwrap();
173 let channel_view_c = cx_c
174 .update(|cx| ChannelView::open(channel_id, workspace_c.clone(), cx))
175 .await
176 .unwrap();
177
178 // Clients A, B, and C all insert and select some text
179 channel_view_a.update(cx_a, |notes, cx| {
180 notes.editor.update(cx, |editor, cx| {
181 editor.insert("a", cx);
182 editor.change_selections(None, cx, |selections| {
183 selections.select_ranges(vec![0..1]);
184 });
185 });
186 });
187 deterministic.run_until_parked();
188 channel_view_b.update(cx_b, |notes, cx| {
189 notes.editor.update(cx, |editor, cx| {
190 editor.move_down(&Default::default(), cx);
191 editor.insert("b", cx);
192 editor.change_selections(None, cx, |selections| {
193 selections.select_ranges(vec![1..2]);
194 });
195 });
196 });
197 deterministic.run_until_parked();
198 channel_view_c.update(cx_c, |notes, cx| {
199 notes.editor.update(cx, |editor, cx| {
200 editor.move_down(&Default::default(), cx);
201 editor.insert("c", cx);
202 editor.change_selections(None, cx, |selections| {
203 selections.select_ranges(vec![2..3]);
204 });
205 });
206 });
207
208 // Client A sees clients B and C without assigned colors, because they aren't
209 // in a call together.
210 deterministic.run_until_parked();
211 channel_view_a.update(cx_a, |notes, cx| {
212 notes.editor.update(cx, |editor, cx| {
213 assert_remote_selections(editor, &[(None, 1..2), (None, 2..3)], cx);
214 });
215 });
216
217 // Clients A and B join the same call.
218 for (call, cx) in [(&active_call_a, &mut cx_a), (&active_call_b, &mut cx_b)] {
219 call.update(*cx, |call, cx| call.join_channel(channel_id, cx))
220 .await
221 .unwrap();
222 }
223
224 // Clients A and B see each other with two different assigned colors. Client C
225 // still doesn't have a color.
226 deterministic.run_until_parked();
227 channel_view_a.update(cx_a, |notes, cx| {
228 notes.editor.update(cx, |editor, cx| {
229 assert_remote_selections(
230 editor,
231 &[(Some(ParticipantIndex(1)), 1..2), (None, 2..3)],
232 cx,
233 );
234 });
235 });
236 channel_view_b.update(cx_b, |notes, cx| {
237 notes.editor.update(cx, |editor, cx| {
238 assert_remote_selections(
239 editor,
240 &[(Some(ParticipantIndex(0)), 0..1), (None, 2..3)],
241 cx,
242 );
243 });
244 });
245
246 // Client A shares a project, and client B joins.
247 let project_id = active_call_a
248 .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
249 .await
250 .unwrap();
251 let project_b = client_b.build_remote_project(project_id, cx_b).await;
252 let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
253
254 // Clients A and B open the same file.
255 let editor_a = workspace_a
256 .update(cx_a, |workspace, cx| {
257 workspace.open_path((worktree_id_a, "file.txt"), None, true, cx)
258 })
259 .await
260 .unwrap()
261 .downcast::<Editor>()
262 .unwrap();
263 let editor_b = workspace_b
264 .update(cx_b, |workspace, cx| {
265 workspace.open_path((worktree_id_a, "file.txt"), None, true, cx)
266 })
267 .await
268 .unwrap()
269 .downcast::<Editor>()
270 .unwrap();
271
272 editor_a.update(cx_a, |editor, cx| {
273 editor.change_selections(None, cx, |selections| {
274 selections.select_ranges(vec![0..1]);
275 });
276 });
277 editor_b.update(cx_b, |editor, cx| {
278 editor.change_selections(None, cx, |selections| {
279 selections.select_ranges(vec![2..3]);
280 });
281 });
282 deterministic.run_until_parked();
283
284 // Clients A and B see each other with the same colors as in the channel notes.
285 editor_a.update(cx_a, |editor, cx| {
286 assert_remote_selections(editor, &[(Some(ParticipantIndex(1)), 2..3)], cx);
287 });
288 editor_b.update(cx_b, |editor, cx| {
289 assert_remote_selections(editor, &[(Some(ParticipantIndex(0)), 0..1)], cx);
290 });
291}
292
293#[track_caller]
294fn assert_remote_selections(
295 editor: &mut Editor,
296 expected_selections: &[(Option<ParticipantIndex>, Range<usize>)],
297 cx: &mut ViewContext<Editor>,
298) {
299 let snapshot = editor.snapshot(cx);
300 let range = Anchor::min()..Anchor::max();
301 let remote_selections = snapshot
302 .remote_selections_in_range(&range, editor.collaboration_hub().unwrap(), cx)
303 .map(|s| {
304 let start = s.selection.start.to_offset(&snapshot.buffer_snapshot);
305 let end = s.selection.end.to_offset(&snapshot.buffer_snapshot);
306 (s.participant_index, start..end)
307 })
308 .collect::<Vec<_>>();
309 assert_eq!(
310 remote_selections, expected_selections,
311 "incorrect remote selections"
312 );
313}
314
315#[gpui::test]
316async fn test_multiple_handles_to_channel_buffer(
317 deterministic: Arc<Deterministic>,
318 cx_a: &mut TestAppContext,
319) {
320 deterministic.forbid_parking();
321 let mut server = TestServer::start(&deterministic).await;
322 let client_a = server.create_client(cx_a, "user_a").await;
323
324 let channel_id = server
325 .make_channel("the-channel", None, (&client_a, cx_a), &mut [])
326 .await;
327
328 let channel_buffer_1 = client_a
329 .channel_store()
330 .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx));
331 let channel_buffer_2 = client_a
332 .channel_store()
333 .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx));
334 let channel_buffer_3 = client_a
335 .channel_store()
336 .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx));
337
338 // All concurrent tasks for opening a channel buffer return the same model handle.
339 let (channel_buffer, channel_buffer_2, channel_buffer_3) =
340 future::try_join3(channel_buffer_1, channel_buffer_2, channel_buffer_3)
341 .await
342 .unwrap();
343 let channel_buffer_model_id = channel_buffer.id();
344 assert_eq!(channel_buffer, channel_buffer_2);
345 assert_eq!(channel_buffer, channel_buffer_3);
346
347 channel_buffer.update(cx_a, |buffer, cx| {
348 buffer.buffer().update(cx, |buffer, cx| {
349 buffer.edit([(0..0, "hello")], None, cx);
350 })
351 });
352 deterministic.run_until_parked();
353
354 cx_a.update(|_| {
355 drop(channel_buffer);
356 drop(channel_buffer_2);
357 drop(channel_buffer_3);
358 });
359 deterministic.run_until_parked();
360
361 // The channel buffer can be reopened after dropping it.
362 let channel_buffer = client_a
363 .channel_store()
364 .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx))
365 .await
366 .unwrap();
367 assert_ne!(channel_buffer.id(), channel_buffer_model_id);
368 channel_buffer.update(cx_a, |buffer, cx| {
369 buffer.buffer().update(cx, |buffer, _| {
370 assert_eq!(buffer.text(), "hello");
371 })
372 });
373}
374
375#[gpui::test]
376async fn test_channel_buffer_disconnect(
377 deterministic: Arc<Deterministic>,
378 cx_a: &mut TestAppContext,
379 cx_b: &mut TestAppContext,
380) {
381 deterministic.forbid_parking();
382 let mut server = TestServer::start(&deterministic).await;
383 let client_a = server.create_client(cx_a, "user_a").await;
384 let client_b = server.create_client(cx_b, "user_b").await;
385
386 let channel_id = server
387 .make_channel(
388 "the-channel",
389 None,
390 (&client_a, cx_a),
391 &mut [(&client_b, cx_b)],
392 )
393 .await;
394
395 let channel_buffer_a = client_a
396 .channel_store()
397 .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx))
398 .await
399 .unwrap();
400 let channel_buffer_b = client_b
401 .channel_store()
402 .update(cx_b, |store, cx| store.open_channel_buffer(channel_id, cx))
403 .await
404 .unwrap();
405
406 server.forbid_connections();
407 server.disconnect_client(client_a.peer_id().unwrap());
408 deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
409
410 channel_buffer_a.update(cx_a, |buffer, _| {
411 assert_eq!(
412 buffer.channel().as_ref(),
413 &Channel {
414 id: channel_id,
415 name: "the-channel".to_string()
416 }
417 );
418 assert!(!buffer.is_connected());
419 });
420
421 deterministic.run_until_parked();
422
423 server.allow_connections();
424 deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
425
426 deterministic.run_until_parked();
427
428 client_a
429 .channel_store()
430 .update(cx_a, |channel_store, _| {
431 channel_store.remove_channel(channel_id)
432 })
433 .await
434 .unwrap();
435 deterministic.run_until_parked();
436
437 // Channel buffer observed the deletion
438 channel_buffer_b.update(cx_b, |buffer, _| {
439 assert_eq!(
440 buffer.channel().as_ref(),
441 &Channel {
442 id: channel_id,
443 name: "the-channel".to_string()
444 }
445 );
446 assert!(!buffer.is_connected());
447 });
448}
449
450#[gpui::test]
451async fn test_rejoin_channel_buffer(
452 deterministic: Arc<Deterministic>,
453 cx_a: &mut TestAppContext,
454 cx_b: &mut TestAppContext,
455) {
456 deterministic.forbid_parking();
457 let mut server = TestServer::start(&deterministic).await;
458 let client_a = server.create_client(cx_a, "user_a").await;
459 let client_b = server.create_client(cx_b, "user_b").await;
460
461 let channel_id = server
462 .make_channel(
463 "the-channel",
464 None,
465 (&client_a, cx_a),
466 &mut [(&client_b, cx_b)],
467 )
468 .await;
469
470 let channel_buffer_a = client_a
471 .channel_store()
472 .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx))
473 .await
474 .unwrap();
475 let channel_buffer_b = client_b
476 .channel_store()
477 .update(cx_b, |store, cx| store.open_channel_buffer(channel_id, cx))
478 .await
479 .unwrap();
480
481 channel_buffer_a.update(cx_a, |buffer, cx| {
482 buffer.buffer().update(cx, |buffer, cx| {
483 buffer.edit([(0..0, "1")], None, cx);
484 })
485 });
486 deterministic.run_until_parked();
487
488 // Client A disconnects.
489 server.forbid_connections();
490 server.disconnect_client(client_a.peer_id().unwrap());
491
492 // Both clients make an edit.
493 channel_buffer_a.update(cx_a, |buffer, cx| {
494 buffer.buffer().update(cx, |buffer, cx| {
495 buffer.edit([(1..1, "2")], None, cx);
496 })
497 });
498 channel_buffer_b.update(cx_b, |buffer, cx| {
499 buffer.buffer().update(cx, |buffer, cx| {
500 buffer.edit([(0..0, "0")], None, cx);
501 })
502 });
503
504 // Both clients see their own edit.
505 deterministic.run_until_parked();
506 channel_buffer_a.read_with(cx_a, |buffer, cx| {
507 assert_eq!(buffer.buffer().read(cx).text(), "12");
508 });
509 channel_buffer_b.read_with(cx_b, |buffer, cx| {
510 assert_eq!(buffer.buffer().read(cx).text(), "01");
511 });
512
513 // Client A reconnects. Both clients see each other's edits, and see
514 // the same collaborators.
515 server.allow_connections();
516 deterministic.advance_clock(RECEIVE_TIMEOUT);
517 channel_buffer_a.read_with(cx_a, |buffer, cx| {
518 assert_eq!(buffer.buffer().read(cx).text(), "012");
519 });
520 channel_buffer_b.read_with(cx_b, |buffer, cx| {
521 assert_eq!(buffer.buffer().read(cx).text(), "012");
522 });
523
524 channel_buffer_a.read_with(cx_a, |buffer_a, _| {
525 channel_buffer_b.read_with(cx_b, |buffer_b, _| {
526 assert_eq!(buffer_a.collaborators(), buffer_b.collaborators());
527 });
528 });
529}
530
531#[gpui::test]
532async fn test_channel_buffers_and_server_restarts(
533 deterministic: Arc<Deterministic>,
534 cx_a: &mut TestAppContext,
535 cx_b: &mut TestAppContext,
536 cx_c: &mut TestAppContext,
537) {
538 deterministic.forbid_parking();
539 let mut server = TestServer::start(&deterministic).await;
540 let client_a = server.create_client(cx_a, "user_a").await;
541 let client_b = server.create_client(cx_b, "user_b").await;
542 let client_c = server.create_client(cx_c, "user_c").await;
543
544 let channel_id = server
545 .make_channel(
546 "the-channel",
547 None,
548 (&client_a, cx_a),
549 &mut [(&client_b, cx_b), (&client_c, cx_c)],
550 )
551 .await;
552
553 let channel_buffer_a = client_a
554 .channel_store()
555 .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx))
556 .await
557 .unwrap();
558 let channel_buffer_b = client_b
559 .channel_store()
560 .update(cx_b, |store, cx| store.open_channel_buffer(channel_id, cx))
561 .await
562 .unwrap();
563 let _channel_buffer_c = client_c
564 .channel_store()
565 .update(cx_c, |store, cx| store.open_channel_buffer(channel_id, cx))
566 .await
567 .unwrap();
568
569 channel_buffer_a.update(cx_a, |buffer, cx| {
570 buffer.buffer().update(cx, |buffer, cx| {
571 buffer.edit([(0..0, "1")], None, cx);
572 })
573 });
574 deterministic.run_until_parked();
575
576 // Client C can't reconnect.
577 client_c.override_establish_connection(|_, cx| cx.spawn(|_| future::pending()));
578
579 // Server stops.
580 server.reset().await;
581 deterministic.advance_clock(RECEIVE_TIMEOUT);
582
583 // While the server is down, both clients make an edit.
584 channel_buffer_a.update(cx_a, |buffer, cx| {
585 buffer.buffer().update(cx, |buffer, cx| {
586 buffer.edit([(1..1, "2")], None, cx);
587 })
588 });
589 channel_buffer_b.update(cx_b, |buffer, cx| {
590 buffer.buffer().update(cx, |buffer, cx| {
591 buffer.edit([(0..0, "0")], None, cx);
592 })
593 });
594
595 // Server restarts.
596 server.start().await.unwrap();
597 deterministic.advance_clock(CLEANUP_TIMEOUT);
598
599 // Clients reconnects. Clients A and B see each other's edits, and see
600 // that client C has disconnected.
601 channel_buffer_a.read_with(cx_a, |buffer, cx| {
602 assert_eq!(buffer.buffer().read(cx).text(), "012");
603 });
604 channel_buffer_b.read_with(cx_b, |buffer, cx| {
605 assert_eq!(buffer.buffer().read(cx).text(), "012");
606 });
607
608 channel_buffer_a.read_with(cx_a, |buffer_a, _| {
609 channel_buffer_b.read_with(cx_b, |buffer_b, _| {
610 assert_collaborators(
611 buffer_a.collaborators(),
612 &[client_a.user_id(), client_b.user_id()],
613 );
614 assert_eq!(buffer_a.collaborators(), buffer_b.collaborators());
615 });
616 });
617}
618
619#[gpui::test(iterations = 10)]
620async fn test_following_to_channel_notes_without_a_shared_project(
621 deterministic: Arc<Deterministic>,
622 mut cx_a: &mut TestAppContext,
623 mut cx_b: &mut TestAppContext,
624 mut cx_c: &mut TestAppContext,
625) {
626 deterministic.forbid_parking();
627 let mut server = TestServer::start(&deterministic).await;
628 let client_a = server.create_client(cx_a, "user_a").await;
629 let client_b = server.create_client(cx_b, "user_b").await;
630 let client_c = server.create_client(cx_c, "user_c").await;
631
632 cx_a.update(editor::init);
633 cx_b.update(editor::init);
634 cx_c.update(editor::init);
635 cx_a.update(collab_ui::channel_view::init);
636 cx_b.update(collab_ui::channel_view::init);
637 cx_c.update(collab_ui::channel_view::init);
638
639 let channel_1_id = server
640 .make_channel(
641 "channel-1",
642 None,
643 (&client_a, cx_a),
644 &mut [(&client_b, cx_b), (&client_c, cx_c)],
645 )
646 .await;
647 let channel_2_id = server
648 .make_channel(
649 "channel-2",
650 None,
651 (&client_a, cx_a),
652 &mut [(&client_b, cx_b), (&client_c, cx_c)],
653 )
654 .await;
655
656 // Clients A, B, and C join a channel.
657 let active_call_a = cx_a.read(ActiveCall::global);
658 let active_call_b = cx_b.read(ActiveCall::global);
659 let active_call_c = cx_c.read(ActiveCall::global);
660 for (call, cx) in [
661 (&active_call_a, &mut cx_a),
662 (&active_call_b, &mut cx_b),
663 (&active_call_c, &mut cx_c),
664 ] {
665 call.update(*cx, |call, cx| call.join_channel(channel_1_id, cx))
666 .await
667 .unwrap();
668 }
669 deterministic.run_until_parked();
670
671 // Clients A, B, and C all open their own unshared projects.
672 client_a.fs().insert_tree("/a", json!({})).await;
673 client_b.fs().insert_tree("/b", json!({})).await;
674 client_c.fs().insert_tree("/c", json!({})).await;
675 let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
676 let (project_b, _) = client_b.build_local_project("/b", cx_b).await;
677 let (project_c, _) = client_b.build_local_project("/c", cx_c).await;
678 let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
679 let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
680 let _workspace_c = client_c.build_workspace(&project_c, cx_c).root(cx_c);
681
682 active_call_a
683 .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
684 .await
685 .unwrap();
686
687 // Client A opens the notes for channel 1.
688 let channel_view_1_a = cx_a
689 .update(|cx| ChannelView::open(channel_1_id, workspace_a.clone(), cx))
690 .await
691 .unwrap();
692 channel_view_1_a.update(cx_a, |notes, cx| {
693 assert_eq!(notes.channel(cx).name, "channel-1");
694 notes.editor.update(cx, |editor, cx| {
695 editor.insert("Hello from A.", cx);
696 editor.change_selections(None, cx, |selections| {
697 selections.select_ranges(vec![3..4]);
698 });
699 });
700 });
701
702 // Client B follows client A.
703 workspace_b
704 .update(cx_b, |workspace, cx| {
705 workspace.follow(client_a.peer_id().unwrap(), cx).unwrap()
706 })
707 .await
708 .unwrap();
709
710 // Client B is taken to the notes for channel 1, with the same
711 // text selected as client A.
712 deterministic.run_until_parked();
713 let channel_view_1_b = workspace_b.read_with(cx_b, |workspace, cx| {
714 assert_eq!(
715 workspace.leader_for_pane(workspace.active_pane()),
716 Some(client_a.peer_id().unwrap())
717 );
718 workspace
719 .active_item(cx)
720 .expect("no active item")
721 .downcast::<ChannelView>()
722 .expect("active item is not a channel view")
723 });
724 channel_view_1_b.read_with(cx_b, |notes, cx| {
725 assert_eq!(notes.channel(cx).name, "channel-1");
726 let editor = notes.editor.read(cx);
727 assert_eq!(editor.text(cx), "Hello from A.");
728 assert_eq!(editor.selections.ranges::<usize>(cx), &[3..4]);
729 });
730
731 // Client A opens the notes for channel 2.
732 let channel_view_2_a = cx_a
733 .update(|cx| ChannelView::open(channel_2_id, workspace_a.clone(), cx))
734 .await
735 .unwrap();
736 channel_view_2_a.read_with(cx_a, |notes, cx| {
737 assert_eq!(notes.channel(cx).name, "channel-2");
738 });
739
740 // Client B is taken to the notes for channel 2.
741 deterministic.run_until_parked();
742 let channel_view_2_b = workspace_b.read_with(cx_b, |workspace, cx| {
743 assert_eq!(
744 workspace.leader_for_pane(workspace.active_pane()),
745 Some(client_a.peer_id().unwrap())
746 );
747 workspace
748 .active_item(cx)
749 .expect("no active item")
750 .downcast::<ChannelView>()
751 .expect("active item is not a channel view")
752 });
753 channel_view_2_b.read_with(cx_b, |notes, cx| {
754 assert_eq!(notes.channel(cx).name, "channel-2");
755 });
756}
757
758#[track_caller]
759fn assert_collaborators(collaborators: &HashMap<PeerId, Collaborator>, ids: &[Option<UserId>]) {
760 let mut user_ids = collaborators
761 .values()
762 .map(|collaborator| collaborator.user_id)
763 .collect::<Vec<_>>();
764 user_ids.sort();
765 assert_eq!(
766 user_ids,
767 ids.into_iter().map(|id| id.unwrap()).collect::<Vec<_>>()
768 );
769}
770
771fn buffer_text(channel_buffer: &ModelHandle<language::Buffer>, cx: &mut TestAppContext) -> String {
772 channel_buffer.read_with(cx, |buffer, _| buffer.text())
773}