1//todo!(workspace)
2
3// use crate::{rpc::RECONNECT_TIMEOUT, tests::TestServer};
4// use call::ActiveCall;
5// use collab_ui::notifications::project_shared_notification::ProjectSharedNotification;
6// use editor::{Editor, ExcerptRange, MultiBuffer};
7// use gpui::{BackgroundExecutor, TestAppContext, View};
8// use live_kit_client::MacOSDisplay;
9// use rpc::proto::PeerId;
10// use serde_json::json;
11// use std::borrow::Cow;
12// use workspace::{
13// dock::{test::TestPanel, DockPosition},
14// item::{test::TestItem, ItemHandle as _},
15// shared_screen::SharedScreen,
16// SplitDirection, Workspace,
17// };
18
19// #[gpui::test(iterations = 10)]
20// async fn test_basic_following(
21// executor: BackgroundExecutor,
22// cx_a: &mut TestAppContext,
23// cx_b: &mut TestAppContext,
24// cx_c: &mut TestAppContext,
25// cx_d: &mut TestAppContext,
26// ) {
27// let mut server = TestServer::start(&executor).await;
28// let client_a = server.create_client(cx_a, "user_a").await;
29// let client_b = server.create_client(cx_b, "user_b").await;
30// let client_c = server.create_client(cx_c, "user_c").await;
31// let client_d = server.create_client(cx_d, "user_d").await;
32// server
33// .create_room(&mut [
34// (&client_a, cx_a),
35// (&client_b, cx_b),
36// (&client_c, cx_c),
37// (&client_d, cx_d),
38// ])
39// .await;
40// let active_call_a = cx_a.read(ActiveCall::global);
41// let active_call_b = cx_b.read(ActiveCall::global);
42
43// cx_a.update(editor::init);
44// cx_b.update(editor::init);
45
46// client_a
47// .fs()
48// .insert_tree(
49// "/a",
50// json!({
51// "1.txt": "one\none\none",
52// "2.txt": "two\ntwo\ntwo",
53// "3.txt": "three\nthree\nthree",
54// }),
55// )
56// .await;
57// let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
58// active_call_a
59// .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
60// .await
61// .unwrap();
62
63// let project_id = active_call_a
64// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
65// .await
66// .unwrap();
67// let project_b = client_b.build_remote_project(project_id, cx_b).await;
68// active_call_b
69// .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
70// .await
71// .unwrap();
72
73// let window_a = client_a.build_workspace(&project_a, cx_a);
74// let workspace_a = window_a.root(cx_a);
75// let window_b = client_b.build_workspace(&project_b, cx_b);
76// let workspace_b = window_b.root(cx_b);
77
78// // Client A opens some editors.
79// let pane_a = workspace_a.read_with(cx_a, |workspace, _| workspace.active_pane().clone());
80// let editor_a1 = workspace_a
81// .update(cx_a, |workspace, cx| {
82// workspace.open_path((worktree_id, "1.txt"), None, true, cx)
83// })
84// .await
85// .unwrap()
86// .downcast::<Editor>()
87// .unwrap();
88// let editor_a2 = workspace_a
89// .update(cx_a, |workspace, cx| {
90// workspace.open_path((worktree_id, "2.txt"), None, true, cx)
91// })
92// .await
93// .unwrap()
94// .downcast::<Editor>()
95// .unwrap();
96
97// // Client B opens an editor.
98// let editor_b1 = workspace_b
99// .update(cx_b, |workspace, cx| {
100// workspace.open_path((worktree_id, "1.txt"), None, true, cx)
101// })
102// .await
103// .unwrap()
104// .downcast::<Editor>()
105// .unwrap();
106
107// let peer_id_a = client_a.peer_id().unwrap();
108// let peer_id_b = client_b.peer_id().unwrap();
109// let peer_id_c = client_c.peer_id().unwrap();
110// let peer_id_d = client_d.peer_id().unwrap();
111
112// // Client A updates their selections in those editors
113// editor_a1.update(cx_a, |editor, cx| {
114// editor.handle_input("a", cx);
115// editor.handle_input("b", cx);
116// editor.handle_input("c", cx);
117// editor.select_left(&Default::default(), cx);
118// assert_eq!(editor.selections.ranges(cx), vec![3..2]);
119// });
120// editor_a2.update(cx_a, |editor, cx| {
121// editor.handle_input("d", cx);
122// editor.handle_input("e", cx);
123// editor.select_left(&Default::default(), cx);
124// assert_eq!(editor.selections.ranges(cx), vec![2..1]);
125// });
126
127// // When client B starts following client A, all visible view states are replicated to client B.
128// workspace_b
129// .update(cx_b, |workspace, cx| {
130// workspace.follow(peer_id_a, cx).unwrap()
131// })
132// .await
133// .unwrap();
134
135// cx_c.foreground().run_until_parked();
136// let editor_b2 = workspace_b.read_with(cx_b, |workspace, cx| {
137// workspace
138// .active_item(cx)
139// .unwrap()
140// .downcast::<Editor>()
141// .unwrap()
142// });
143// assert_eq!(
144// cx_b.read(|cx| editor_b2.project_path(cx)),
145// Some((worktree_id, "2.txt").into())
146// );
147// assert_eq!(
148// editor_b2.read_with(cx_b, |editor, cx| editor.selections.ranges(cx)),
149// vec![2..1]
150// );
151// assert_eq!(
152// editor_b1.read_with(cx_b, |editor, cx| editor.selections.ranges(cx)),
153// vec![3..2]
154// );
155
156// cx_c.foreground().run_until_parked();
157// let active_call_c = cx_c.read(ActiveCall::global);
158// let project_c = client_c.build_remote_project(project_id, cx_c).await;
159// let window_c = client_c.build_workspace(&project_c, cx_c);
160// let workspace_c = window_c.root(cx_c);
161// active_call_c
162// .update(cx_c, |call, cx| call.set_location(Some(&project_c), cx))
163// .await
164// .unwrap();
165// drop(project_c);
166
167// // Client C also follows client A.
168// workspace_c
169// .update(cx_c, |workspace, cx| {
170// workspace.follow(peer_id_a, cx).unwrap()
171// })
172// .await
173// .unwrap();
174
175// cx_d.foreground().run_until_parked();
176// let active_call_d = cx_d.read(ActiveCall::global);
177// let project_d = client_d.build_remote_project(project_id, cx_d).await;
178// let workspace_d = client_d.build_workspace(&project_d, cx_d).root(cx_d);
179// active_call_d
180// .update(cx_d, |call, cx| call.set_location(Some(&project_d), cx))
181// .await
182// .unwrap();
183// drop(project_d);
184
185// // All clients see that clients B and C are following client A.
186// cx_c.foreground().run_until_parked();
187// for (name, cx) in [("A", &cx_a), ("B", &cx_b), ("C", &cx_c), ("D", &cx_d)] {
188// assert_eq!(
189// followers_by_leader(project_id, cx),
190// &[(peer_id_a, vec![peer_id_b, peer_id_c])],
191// "followers seen by {name}"
192// );
193// }
194
195// // Client C unfollows client A.
196// workspace_c.update(cx_c, |workspace, cx| {
197// workspace.unfollow(&workspace.active_pane().clone(), cx);
198// });
199
200// // All clients see that clients B is following client A.
201// cx_c.foreground().run_until_parked();
202// for (name, cx) in [("A", &cx_a), ("B", &cx_b), ("C", &cx_c), ("D", &cx_d)] {
203// assert_eq!(
204// followers_by_leader(project_id, cx),
205// &[(peer_id_a, vec![peer_id_b])],
206// "followers seen by {name}"
207// );
208// }
209
210// // Client C re-follows client A.
211// workspace_c
212// .update(cx_c, |workspace, cx| {
213// workspace.follow(peer_id_a, cx).unwrap()
214// })
215// .await
216// .unwrap();
217
218// // All clients see that clients B and C are following client A.
219// cx_c.foreground().run_until_parked();
220// for (name, cx) in [("A", &cx_a), ("B", &cx_b), ("C", &cx_c), ("D", &cx_d)] {
221// assert_eq!(
222// followers_by_leader(project_id, cx),
223// &[(peer_id_a, vec![peer_id_b, peer_id_c])],
224// "followers seen by {name}"
225// );
226// }
227
228// // Client D follows client B, then switches to following client C.
229// workspace_d
230// .update(cx_d, |workspace, cx| {
231// workspace.follow(peer_id_b, cx).unwrap()
232// })
233// .await
234// .unwrap();
235// workspace_d
236// .update(cx_d, |workspace, cx| {
237// workspace.follow(peer_id_c, cx).unwrap()
238// })
239// .await
240// .unwrap();
241
242// // All clients see that D is following C
243// cx_d.foreground().run_until_parked();
244// for (name, cx) in [("A", &cx_a), ("B", &cx_b), ("C", &cx_c), ("D", &cx_d)] {
245// assert_eq!(
246// followers_by_leader(project_id, cx),
247// &[
248// (peer_id_a, vec![peer_id_b, peer_id_c]),
249// (peer_id_c, vec![peer_id_d])
250// ],
251// "followers seen by {name}"
252// );
253// }
254
255// // Client C closes the project.
256// window_c.remove(cx_c);
257// cx_c.drop_last(workspace_c);
258
259// // Clients A and B see that client B is following A, and client C is not present in the followers.
260// cx_c.foreground().run_until_parked();
261// for (name, cx) in [("A", &cx_a), ("B", &cx_b), ("C", &cx_c), ("D", &cx_d)] {
262// assert_eq!(
263// followers_by_leader(project_id, cx),
264// &[(peer_id_a, vec![peer_id_b]),],
265// "followers seen by {name}"
266// );
267// }
268
269// // When client A activates a different editor, client B does so as well.
270// workspace_a.update(cx_a, |workspace, cx| {
271// workspace.activate_item(&editor_a1, cx)
272// });
273// executor.run_until_parked();
274// workspace_b.read_with(cx_b, |workspace, cx| {
275// assert_eq!(workspace.active_item(cx).unwrap().id(), editor_b1.id());
276// });
277
278// // When client A opens a multibuffer, client B does so as well.
279// let multibuffer_a = cx_a.add_model(|cx| {
280// let buffer_a1 = project_a.update(cx, |project, cx| {
281// project
282// .get_open_buffer(&(worktree_id, "1.txt").into(), cx)
283// .unwrap()
284// });
285// let buffer_a2 = project_a.update(cx, |project, cx| {
286// project
287// .get_open_buffer(&(worktree_id, "2.txt").into(), cx)
288// .unwrap()
289// });
290// let mut result = MultiBuffer::new(0);
291// result.push_excerpts(
292// buffer_a1,
293// [ExcerptRange {
294// context: 0..3,
295// primary: None,
296// }],
297// cx,
298// );
299// result.push_excerpts(
300// buffer_a2,
301// [ExcerptRange {
302// context: 4..7,
303// primary: None,
304// }],
305// cx,
306// );
307// result
308// });
309// let multibuffer_editor_a = workspace_a.update(cx_a, |workspace, cx| {
310// let editor =
311// cx.add_view(|cx| Editor::for_multibuffer(multibuffer_a, Some(project_a.clone()), cx));
312// workspace.add_item(Box::new(editor.clone()), cx);
313// editor
314// });
315// executor.run_until_parked();
316// let multibuffer_editor_b = workspace_b.read_with(cx_b, |workspace, cx| {
317// workspace
318// .active_item(cx)
319// .unwrap()
320// .downcast::<Editor>()
321// .unwrap()
322// });
323// assert_eq!(
324// multibuffer_editor_a.read_with(cx_a, |editor, cx| editor.text(cx)),
325// multibuffer_editor_b.read_with(cx_b, |editor, cx| editor.text(cx)),
326// );
327
328// // When client A navigates back and forth, client B does so as well.
329// workspace_a
330// .update(cx_a, |workspace, cx| {
331// workspace.go_back(workspace.active_pane().downgrade(), cx)
332// })
333// .await
334// .unwrap();
335// executor.run_until_parked();
336// workspace_b.read_with(cx_b, |workspace, cx| {
337// assert_eq!(workspace.active_item(cx).unwrap().id(), editor_b1.id());
338// });
339
340// workspace_a
341// .update(cx_a, |workspace, cx| {
342// workspace.go_back(workspace.active_pane().downgrade(), cx)
343// })
344// .await
345// .unwrap();
346// executor.run_until_parked();
347// workspace_b.read_with(cx_b, |workspace, cx| {
348// assert_eq!(workspace.active_item(cx).unwrap().id(), editor_b2.id());
349// });
350
351// workspace_a
352// .update(cx_a, |workspace, cx| {
353// workspace.go_forward(workspace.active_pane().downgrade(), cx)
354// })
355// .await
356// .unwrap();
357// executor.run_until_parked();
358// workspace_b.read_with(cx_b, |workspace, cx| {
359// assert_eq!(workspace.active_item(cx).unwrap().id(), editor_b1.id());
360// });
361
362// // Changes to client A's editor are reflected on client B.
363// editor_a1.update(cx_a, |editor, cx| {
364// editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2]));
365// });
366// executor.run_until_parked();
367// editor_b1.read_with(cx_b, |editor, cx| {
368// assert_eq!(editor.selections.ranges(cx), &[1..1, 2..2]);
369// });
370
371// editor_a1.update(cx_a, |editor, cx| editor.set_text("TWO", cx));
372// executor.run_until_parked();
373// editor_b1.read_with(cx_b, |editor, cx| assert_eq!(editor.text(cx), "TWO"));
374
375// editor_a1.update(cx_a, |editor, cx| {
376// editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
377// editor.set_scroll_position(vec2f(0., 100.), cx);
378// });
379// executor.run_until_parked();
380// editor_b1.read_with(cx_b, |editor, cx| {
381// assert_eq!(editor.selections.ranges(cx), &[3..3]);
382// });
383
384// // After unfollowing, client B stops receiving updates from client A.
385// workspace_b.update(cx_b, |workspace, cx| {
386// workspace.unfollow(&workspace.active_pane().clone(), cx)
387// });
388// workspace_a.update(cx_a, |workspace, cx| {
389// workspace.activate_item(&editor_a2, cx)
390// });
391// executor.run_until_parked();
392// assert_eq!(
393// workspace_b.read_with(cx_b, |workspace, cx| workspace
394// .active_item(cx)
395// .unwrap()
396// .id()),
397// editor_b1.id()
398// );
399
400// // Client A starts following client B.
401// workspace_a
402// .update(cx_a, |workspace, cx| {
403// workspace.follow(peer_id_b, cx).unwrap()
404// })
405// .await
406// .unwrap();
407// assert_eq!(
408// workspace_a.read_with(cx_a, |workspace, _| workspace.leader_for_pane(&pane_a)),
409// Some(peer_id_b)
410// );
411// assert_eq!(
412// workspace_a.read_with(cx_a, |workspace, cx| workspace
413// .active_item(cx)
414// .unwrap()
415// .id()),
416// editor_a1.id()
417// );
418
419// // Client B activates an external window, which causes a new screen-sharing item to be added to the pane.
420// let display = MacOSDisplay::new();
421// active_call_b
422// .update(cx_b, |call, cx| call.set_location(None, cx))
423// .await
424// .unwrap();
425// active_call_b
426// .update(cx_b, |call, cx| {
427// call.room().unwrap().update(cx, |room, cx| {
428// room.set_display_sources(vec![display.clone()]);
429// room.share_screen(cx)
430// })
431// })
432// .await
433// .unwrap();
434// executor.run_until_parked();
435// let shared_screen = workspace_a.read_with(cx_a, |workspace, cx| {
436// workspace
437// .active_item(cx)
438// .expect("no active item")
439// .downcast::<SharedScreen>()
440// .expect("active item isn't a shared screen")
441// });
442
443// // Client B activates Zed again, which causes the previous editor to become focused again.
444// active_call_b
445// .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
446// .await
447// .unwrap();
448// executor.run_until_parked();
449// workspace_a.read_with(cx_a, |workspace, cx| {
450// assert_eq!(workspace.active_item(cx).unwrap().id(), editor_a1.id())
451// });
452
453// // Client B activates a multibuffer that was created by following client A. Client A returns to that multibuffer.
454// workspace_b.update(cx_b, |workspace, cx| {
455// workspace.activate_item(&multibuffer_editor_b, cx)
456// });
457// executor.run_until_parked();
458// workspace_a.read_with(cx_a, |workspace, cx| {
459// assert_eq!(
460// workspace.active_item(cx).unwrap().id(),
461// multibuffer_editor_a.id()
462// )
463// });
464
465// // Client B activates a panel, and the previously-opened screen-sharing item gets activated.
466// let panel = window_b.add_view(cx_b, |_| TestPanel::new(DockPosition::Left));
467// workspace_b.update(cx_b, |workspace, cx| {
468// workspace.add_panel(panel, cx);
469// workspace.toggle_panel_focus::<TestPanel>(cx);
470// });
471// executor.run_until_parked();
472// assert_eq!(
473// workspace_a.read_with(cx_a, |workspace, cx| workspace
474// .active_item(cx)
475// .unwrap()
476// .id()),
477// shared_screen.id()
478// );
479
480// // Toggling the focus back to the pane causes client A to return to the multibuffer.
481// workspace_b.update(cx_b, |workspace, cx| {
482// workspace.toggle_panel_focus::<TestPanel>(cx);
483// });
484// executor.run_until_parked();
485// workspace_a.read_with(cx_a, |workspace, cx| {
486// assert_eq!(
487// workspace.active_item(cx).unwrap().id(),
488// multibuffer_editor_a.id()
489// )
490// });
491
492// // Client B activates an item that doesn't implement following,
493// // so the previously-opened screen-sharing item gets activated.
494// let unfollowable_item = window_b.add_view(cx_b, |_| TestItem::new());
495// workspace_b.update(cx_b, |workspace, cx| {
496// workspace.active_pane().update(cx, |pane, cx| {
497// pane.add_item(Box::new(unfollowable_item), true, true, None, cx)
498// })
499// });
500// executor.run_until_parked();
501// assert_eq!(
502// workspace_a.read_with(cx_a, |workspace, cx| workspace
503// .active_item(cx)
504// .unwrap()
505// .id()),
506// shared_screen.id()
507// );
508
509// // Following interrupts when client B disconnects.
510// client_b.disconnect(&cx_b.to_async());
511// executor.advance_clock(RECONNECT_TIMEOUT);
512// assert_eq!(
513// workspace_a.read_with(cx_a, |workspace, _| workspace.leader_for_pane(&pane_a)),
514// None
515// );
516// }
517
518// #[gpui::test]
519// async fn test_following_tab_order(
520// executor: BackgroundExecutor,
521// cx_a: &mut TestAppContext,
522// cx_b: &mut TestAppContext,
523// ) {
524// let mut server = TestServer::start(&executor).await;
525// let client_a = server.create_client(cx_a, "user_a").await;
526// let client_b = server.create_client(cx_b, "user_b").await;
527// server
528// .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
529// .await;
530// let active_call_a = cx_a.read(ActiveCall::global);
531// let active_call_b = cx_b.read(ActiveCall::global);
532
533// cx_a.update(editor::init);
534// cx_b.update(editor::init);
535
536// client_a
537// .fs()
538// .insert_tree(
539// "/a",
540// json!({
541// "1.txt": "one",
542// "2.txt": "two",
543// "3.txt": "three",
544// }),
545// )
546// .await;
547// let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
548// active_call_a
549// .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
550// .await
551// .unwrap();
552
553// let project_id = active_call_a
554// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
555// .await
556// .unwrap();
557// let project_b = client_b.build_remote_project(project_id, cx_b).await;
558// active_call_b
559// .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
560// .await
561// .unwrap();
562
563// let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
564// let pane_a = workspace_a.read_with(cx_a, |workspace, _| workspace.active_pane().clone());
565
566// let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
567// let pane_b = workspace_b.read_with(cx_b, |workspace, _| workspace.active_pane().clone());
568
569// let client_b_id = project_a.read_with(cx_a, |project, _| {
570// project.collaborators().values().next().unwrap().peer_id
571// });
572
573// //Open 1, 3 in that order on client A
574// workspace_a
575// .update(cx_a, |workspace, cx| {
576// workspace.open_path((worktree_id, "1.txt"), None, true, cx)
577// })
578// .await
579// .unwrap();
580// workspace_a
581// .update(cx_a, |workspace, cx| {
582// workspace.open_path((worktree_id, "3.txt"), None, true, cx)
583// })
584// .await
585// .unwrap();
586
587// let pane_paths = |pane: &ViewHandle<workspace::Pane>, cx: &mut TestAppContext| {
588// pane.update(cx, |pane, cx| {
589// pane.items()
590// .map(|item| {
591// item.project_path(cx)
592// .unwrap()
593// .path
594// .to_str()
595// .unwrap()
596// .to_owned()
597// })
598// .collect::<Vec<_>>()
599// })
600// };
601
602// //Verify that the tabs opened in the order we expect
603// assert_eq!(&pane_paths(&pane_a, cx_a), &["1.txt", "3.txt"]);
604
605// //Follow client B as client A
606// workspace_a
607// .update(cx_a, |workspace, cx| {
608// workspace.follow(client_b_id, cx).unwrap()
609// })
610// .await
611// .unwrap();
612
613// //Open just 2 on client B
614// workspace_b
615// .update(cx_b, |workspace, cx| {
616// workspace.open_path((worktree_id, "2.txt"), None, true, cx)
617// })
618// .await
619// .unwrap();
620// executor.run_until_parked();
621
622// // Verify that newly opened followed file is at the end
623// assert_eq!(&pane_paths(&pane_a, cx_a), &["1.txt", "3.txt", "2.txt"]);
624
625// //Open just 1 on client B
626// workspace_b
627// .update(cx_b, |workspace, cx| {
628// workspace.open_path((worktree_id, "1.txt"), None, true, cx)
629// })
630// .await
631// .unwrap();
632// assert_eq!(&pane_paths(&pane_b, cx_b), &["2.txt", "1.txt"]);
633// executor.run_until_parked();
634
635// // Verify that following into 1 did not reorder
636// assert_eq!(&pane_paths(&pane_a, cx_a), &["1.txt", "3.txt", "2.txt"]);
637// }
638
639// #[gpui::test(iterations = 10)]
640// async fn test_peers_following_each_other(
641// executor: BackgroundExecutor,
642// cx_a: &mut TestAppContext,
643// cx_b: &mut TestAppContext,
644// ) {
645// let mut server = TestServer::start(&executor).await;
646// let client_a = server.create_client(cx_a, "user_a").await;
647// let client_b = server.create_client(cx_b, "user_b").await;
648// server
649// .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
650// .await;
651// let active_call_a = cx_a.read(ActiveCall::global);
652// let active_call_b = cx_b.read(ActiveCall::global);
653
654// cx_a.update(editor::init);
655// cx_b.update(editor::init);
656
657// // Client A shares a project.
658// client_a
659// .fs()
660// .insert_tree(
661// "/a",
662// json!({
663// "1.txt": "one",
664// "2.txt": "two",
665// "3.txt": "three",
666// "4.txt": "four",
667// }),
668// )
669// .await;
670// let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
671// active_call_a
672// .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
673// .await
674// .unwrap();
675// let project_id = active_call_a
676// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
677// .await
678// .unwrap();
679
680// // Client B joins the project.
681// let project_b = client_b.build_remote_project(project_id, cx_b).await;
682// active_call_b
683// .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
684// .await
685// .unwrap();
686
687// // Client A opens a file.
688// let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
689// workspace_a
690// .update(cx_a, |workspace, cx| {
691// workspace.open_path((worktree_id, "1.txt"), None, true, cx)
692// })
693// .await
694// .unwrap()
695// .downcast::<Editor>()
696// .unwrap();
697
698// // Client B opens a different file.
699// let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
700// workspace_b
701// .update(cx_b, |workspace, cx| {
702// workspace.open_path((worktree_id, "2.txt"), None, true, cx)
703// })
704// .await
705// .unwrap()
706// .downcast::<Editor>()
707// .unwrap();
708
709// // Clients A and B follow each other in split panes
710// workspace_a.update(cx_a, |workspace, cx| {
711// workspace.split_and_clone(workspace.active_pane().clone(), SplitDirection::Right, cx);
712// });
713// workspace_a
714// .update(cx_a, |workspace, cx| {
715// workspace.follow(client_b.peer_id().unwrap(), cx).unwrap()
716// })
717// .await
718// .unwrap();
719// workspace_b.update(cx_b, |workspace, cx| {
720// workspace.split_and_clone(workspace.active_pane().clone(), SplitDirection::Right, cx);
721// });
722// workspace_b
723// .update(cx_b, |workspace, cx| {
724// workspace.follow(client_a.peer_id().unwrap(), cx).unwrap()
725// })
726// .await
727// .unwrap();
728
729// // Clients A and B return focus to the original files they had open
730// workspace_a.update(cx_a, |workspace, cx| workspace.activate_next_pane(cx));
731// workspace_b.update(cx_b, |workspace, cx| workspace.activate_next_pane(cx));
732// executor.run_until_parked();
733
734// // Both clients see the other client's focused file in their right pane.
735// assert_eq!(
736// pane_summaries(&workspace_a, cx_a),
737// &[
738// PaneSummary {
739// active: true,
740// leader: None,
741// items: vec![(true, "1.txt".into())]
742// },
743// PaneSummary {
744// active: false,
745// leader: client_b.peer_id(),
746// items: vec![(false, "1.txt".into()), (true, "2.txt".into())]
747// },
748// ]
749// );
750// assert_eq!(
751// pane_summaries(&workspace_b, cx_b),
752// &[
753// PaneSummary {
754// active: true,
755// leader: None,
756// items: vec![(true, "2.txt".into())]
757// },
758// PaneSummary {
759// active: false,
760// leader: client_a.peer_id(),
761// items: vec![(false, "2.txt".into()), (true, "1.txt".into())]
762// },
763// ]
764// );
765
766// // Clients A and B each open a new file.
767// workspace_a
768// .update(cx_a, |workspace, cx| {
769// workspace.open_path((worktree_id, "3.txt"), None, true, cx)
770// })
771// .await
772// .unwrap();
773
774// workspace_b
775// .update(cx_b, |workspace, cx| {
776// workspace.open_path((worktree_id, "4.txt"), None, true, cx)
777// })
778// .await
779// .unwrap();
780// executor.run_until_parked();
781
782// // Both client's see the other client open the new file, but keep their
783// // focus on their own active pane.
784// assert_eq!(
785// pane_summaries(&workspace_a, cx_a),
786// &[
787// PaneSummary {
788// active: true,
789// leader: None,
790// items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
791// },
792// PaneSummary {
793// active: false,
794// leader: client_b.peer_id(),
795// items: vec![
796// (false, "1.txt".into()),
797// (false, "2.txt".into()),
798// (true, "4.txt".into())
799// ]
800// },
801// ]
802// );
803// assert_eq!(
804// pane_summaries(&workspace_b, cx_b),
805// &[
806// PaneSummary {
807// active: true,
808// leader: None,
809// items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
810// },
811// PaneSummary {
812// active: false,
813// leader: client_a.peer_id(),
814// items: vec![
815// (false, "2.txt".into()),
816// (false, "1.txt".into()),
817// (true, "3.txt".into())
818// ]
819// },
820// ]
821// );
822
823// // Client A focuses their right pane, in which they're following client B.
824// workspace_a.update(cx_a, |workspace, cx| workspace.activate_next_pane(cx));
825// executor.run_until_parked();
826
827// // Client B sees that client A is now looking at the same file as them.
828// assert_eq!(
829// pane_summaries(&workspace_a, cx_a),
830// &[
831// PaneSummary {
832// active: false,
833// leader: None,
834// items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
835// },
836// PaneSummary {
837// active: true,
838// leader: client_b.peer_id(),
839// items: vec![
840// (false, "1.txt".into()),
841// (false, "2.txt".into()),
842// (true, "4.txt".into())
843// ]
844// },
845// ]
846// );
847// assert_eq!(
848// pane_summaries(&workspace_b, cx_b),
849// &[
850// PaneSummary {
851// active: true,
852// leader: None,
853// items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
854// },
855// PaneSummary {
856// active: false,
857// leader: client_a.peer_id(),
858// items: vec![
859// (false, "2.txt".into()),
860// (false, "1.txt".into()),
861// (false, "3.txt".into()),
862// (true, "4.txt".into())
863// ]
864// },
865// ]
866// );
867
868// // Client B focuses their right pane, in which they're following client A,
869// // who is following them.
870// workspace_b.update(cx_b, |workspace, cx| workspace.activate_next_pane(cx));
871// executor.run_until_parked();
872
873// // Client A sees that client B is now looking at the same file as them.
874// assert_eq!(
875// pane_summaries(&workspace_b, cx_b),
876// &[
877// PaneSummary {
878// active: false,
879// leader: None,
880// items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
881// },
882// PaneSummary {
883// active: true,
884// leader: client_a.peer_id(),
885// items: vec![
886// (false, "2.txt".into()),
887// (false, "1.txt".into()),
888// (false, "3.txt".into()),
889// (true, "4.txt".into())
890// ]
891// },
892// ]
893// );
894// assert_eq!(
895// pane_summaries(&workspace_a, cx_a),
896// &[
897// PaneSummary {
898// active: false,
899// leader: None,
900// items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
901// },
902// PaneSummary {
903// active: true,
904// leader: client_b.peer_id(),
905// items: vec![
906// (false, "1.txt".into()),
907// (false, "2.txt".into()),
908// (true, "4.txt".into())
909// ]
910// },
911// ]
912// );
913
914// // Client B focuses a file that they previously followed A to, breaking
915// // the follow.
916// workspace_b.update(cx_b, |workspace, cx| {
917// workspace.active_pane().update(cx, |pane, cx| {
918// pane.activate_prev_item(true, cx);
919// });
920// });
921// executor.run_until_parked();
922
923// // Both clients see that client B is looking at that previous file.
924// assert_eq!(
925// pane_summaries(&workspace_b, cx_b),
926// &[
927// PaneSummary {
928// active: false,
929// leader: None,
930// items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
931// },
932// PaneSummary {
933// active: true,
934// leader: None,
935// items: vec![
936// (false, "2.txt".into()),
937// (false, "1.txt".into()),
938// (true, "3.txt".into()),
939// (false, "4.txt".into())
940// ]
941// },
942// ]
943// );
944// assert_eq!(
945// pane_summaries(&workspace_a, cx_a),
946// &[
947// PaneSummary {
948// active: false,
949// leader: None,
950// items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
951// },
952// PaneSummary {
953// active: true,
954// leader: client_b.peer_id(),
955// items: vec![
956// (false, "1.txt".into()),
957// (false, "2.txt".into()),
958// (false, "4.txt".into()),
959// (true, "3.txt".into()),
960// ]
961// },
962// ]
963// );
964
965// // Client B closes tabs, some of which were originally opened by client A,
966// // and some of which were originally opened by client B.
967// workspace_b.update(cx_b, |workspace, cx| {
968// workspace.active_pane().update(cx, |pane, cx| {
969// pane.close_inactive_items(&Default::default(), cx)
970// .unwrap()
971// .detach();
972// });
973// });
974
975// executor.run_until_parked();
976
977// // Both clients see that Client B is looking at the previous tab.
978// assert_eq!(
979// pane_summaries(&workspace_b, cx_b),
980// &[
981// PaneSummary {
982// active: false,
983// leader: None,
984// items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
985// },
986// PaneSummary {
987// active: true,
988// leader: None,
989// items: vec![(true, "3.txt".into()),]
990// },
991// ]
992// );
993// assert_eq!(
994// pane_summaries(&workspace_a, cx_a),
995// &[
996// PaneSummary {
997// active: false,
998// leader: None,
999// items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
1000// },
1001// PaneSummary {
1002// active: true,
1003// leader: client_b.peer_id(),
1004// items: vec![
1005// (false, "1.txt".into()),
1006// (false, "2.txt".into()),
1007// (false, "4.txt".into()),
1008// (true, "3.txt".into()),
1009// ]
1010// },
1011// ]
1012// );
1013
1014// // Client B follows client A again.
1015// workspace_b
1016// .update(cx_b, |workspace, cx| {
1017// workspace.follow(client_a.peer_id().unwrap(), cx).unwrap()
1018// })
1019// .await
1020// .unwrap();
1021
1022// // Client A cycles through some tabs.
1023// workspace_a.update(cx_a, |workspace, cx| {
1024// workspace.active_pane().update(cx, |pane, cx| {
1025// pane.activate_prev_item(true, cx);
1026// });
1027// });
1028// executor.run_until_parked();
1029
1030// // Client B follows client A into those tabs.
1031// assert_eq!(
1032// pane_summaries(&workspace_a, cx_a),
1033// &[
1034// PaneSummary {
1035// active: false,
1036// leader: None,
1037// items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
1038// },
1039// PaneSummary {
1040// active: true,
1041// leader: None,
1042// items: vec![
1043// (false, "1.txt".into()),
1044// (false, "2.txt".into()),
1045// (true, "4.txt".into()),
1046// (false, "3.txt".into()),
1047// ]
1048// },
1049// ]
1050// );
1051// assert_eq!(
1052// pane_summaries(&workspace_b, cx_b),
1053// &[
1054// PaneSummary {
1055// active: false,
1056// leader: None,
1057// items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
1058// },
1059// PaneSummary {
1060// active: true,
1061// leader: client_a.peer_id(),
1062// items: vec![(false, "3.txt".into()), (true, "4.txt".into())]
1063// },
1064// ]
1065// );
1066
1067// workspace_a.update(cx_a, |workspace, cx| {
1068// workspace.active_pane().update(cx, |pane, cx| {
1069// pane.activate_prev_item(true, cx);
1070// });
1071// });
1072// executor.run_until_parked();
1073
1074// assert_eq!(
1075// pane_summaries(&workspace_a, cx_a),
1076// &[
1077// PaneSummary {
1078// active: false,
1079// leader: None,
1080// items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
1081// },
1082// PaneSummary {
1083// active: true,
1084// leader: None,
1085// items: vec![
1086// (false, "1.txt".into()),
1087// (true, "2.txt".into()),
1088// (false, "4.txt".into()),
1089// (false, "3.txt".into()),
1090// ]
1091// },
1092// ]
1093// );
1094// assert_eq!(
1095// pane_summaries(&workspace_b, cx_b),
1096// &[
1097// PaneSummary {
1098// active: false,
1099// leader: None,
1100// items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
1101// },
1102// PaneSummary {
1103// active: true,
1104// leader: client_a.peer_id(),
1105// items: vec![
1106// (false, "3.txt".into()),
1107// (false, "4.txt".into()),
1108// (true, "2.txt".into())
1109// ]
1110// },
1111// ]
1112// );
1113
1114// workspace_a.update(cx_a, |workspace, cx| {
1115// workspace.active_pane().update(cx, |pane, cx| {
1116// pane.activate_prev_item(true, cx);
1117// });
1118// });
1119// executor.run_until_parked();
1120
1121// assert_eq!(
1122// pane_summaries(&workspace_a, cx_a),
1123// &[
1124// PaneSummary {
1125// active: false,
1126// leader: None,
1127// items: vec![(false, "1.txt".into()), (true, "3.txt".into())]
1128// },
1129// PaneSummary {
1130// active: true,
1131// leader: None,
1132// items: vec![
1133// (true, "1.txt".into()),
1134// (false, "2.txt".into()),
1135// (false, "4.txt".into()),
1136// (false, "3.txt".into()),
1137// ]
1138// },
1139// ]
1140// );
1141// assert_eq!(
1142// pane_summaries(&workspace_b, cx_b),
1143// &[
1144// PaneSummary {
1145// active: false,
1146// leader: None,
1147// items: vec![(false, "2.txt".into()), (true, "4.txt".into())]
1148// },
1149// PaneSummary {
1150// active: true,
1151// leader: client_a.peer_id(),
1152// items: vec![
1153// (false, "3.txt".into()),
1154// (false, "4.txt".into()),
1155// (false, "2.txt".into()),
1156// (true, "1.txt".into()),
1157// ]
1158// },
1159// ]
1160// );
1161// }
1162
1163// #[gpui::test(iterations = 10)]
1164// async fn test_auto_unfollowing(
1165// executor: BackgroundExecutor,
1166// cx_a: &mut TestAppContext,
1167// cx_b: &mut TestAppContext,
1168// ) {
1169// // 2 clients connect to a server.
1170// let mut server = TestServer::start(&executor).await;
1171// let client_a = server.create_client(cx_a, "user_a").await;
1172// let client_b = server.create_client(cx_b, "user_b").await;
1173// server
1174// .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1175// .await;
1176// let active_call_a = cx_a.read(ActiveCall::global);
1177// let active_call_b = cx_b.read(ActiveCall::global);
1178
1179// cx_a.update(editor::init);
1180// cx_b.update(editor::init);
1181
1182// // Client A shares a project.
1183// client_a
1184// .fs()
1185// .insert_tree(
1186// "/a",
1187// json!({
1188// "1.txt": "one",
1189// "2.txt": "two",
1190// "3.txt": "three",
1191// }),
1192// )
1193// .await;
1194// let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
1195// active_call_a
1196// .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
1197// .await
1198// .unwrap();
1199
1200// let project_id = active_call_a
1201// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1202// .await
1203// .unwrap();
1204// let project_b = client_b.build_remote_project(project_id, cx_b).await;
1205// active_call_b
1206// .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
1207// .await
1208// .unwrap();
1209
1210// // Client A opens some editors.
1211// let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
1212// let _editor_a1 = workspace_a
1213// .update(cx_a, |workspace, cx| {
1214// workspace.open_path((worktree_id, "1.txt"), None, true, cx)
1215// })
1216// .await
1217// .unwrap()
1218// .downcast::<Editor>()
1219// .unwrap();
1220
1221// // Client B starts following client A.
1222// let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
1223// let pane_b = workspace_b.read_with(cx_b, |workspace, _| workspace.active_pane().clone());
1224// let leader_id = project_b.read_with(cx_b, |project, _| {
1225// project.collaborators().values().next().unwrap().peer_id
1226// });
1227// workspace_b
1228// .update(cx_b, |workspace, cx| {
1229// workspace.follow(leader_id, cx).unwrap()
1230// })
1231// .await
1232// .unwrap();
1233// assert_eq!(
1234// workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
1235// Some(leader_id)
1236// );
1237// let editor_b2 = workspace_b.read_with(cx_b, |workspace, cx| {
1238// workspace
1239// .active_item(cx)
1240// .unwrap()
1241// .downcast::<Editor>()
1242// .unwrap()
1243// });
1244
1245// // When client B moves, it automatically stops following client A.
1246// editor_b2.update(cx_b, |editor, cx| editor.move_right(&editor::MoveRight, cx));
1247// assert_eq!(
1248// workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
1249// None
1250// );
1251
1252// workspace_b
1253// .update(cx_b, |workspace, cx| {
1254// workspace.follow(leader_id, cx).unwrap()
1255// })
1256// .await
1257// .unwrap();
1258// assert_eq!(
1259// workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
1260// Some(leader_id)
1261// );
1262
1263// // When client B edits, it automatically stops following client A.
1264// editor_b2.update(cx_b, |editor, cx| editor.insert("X", cx));
1265// assert_eq!(
1266// workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
1267// None
1268// );
1269
1270// workspace_b
1271// .update(cx_b, |workspace, cx| {
1272// workspace.follow(leader_id, cx).unwrap()
1273// })
1274// .await
1275// .unwrap();
1276// assert_eq!(
1277// workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
1278// Some(leader_id)
1279// );
1280
1281// // When client B scrolls, it automatically stops following client A.
1282// editor_b2.update(cx_b, |editor, cx| {
1283// editor.set_scroll_position(vec2f(0., 3.), cx)
1284// });
1285// assert_eq!(
1286// workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
1287// None
1288// );
1289
1290// workspace_b
1291// .update(cx_b, |workspace, cx| {
1292// workspace.follow(leader_id, cx).unwrap()
1293// })
1294// .await
1295// .unwrap();
1296// assert_eq!(
1297// workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
1298// Some(leader_id)
1299// );
1300
1301// // When client B activates a different pane, it continues following client A in the original pane.
1302// workspace_b.update(cx_b, |workspace, cx| {
1303// workspace.split_and_clone(pane_b.clone(), SplitDirection::Right, cx)
1304// });
1305// assert_eq!(
1306// workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
1307// Some(leader_id)
1308// );
1309
1310// workspace_b.update(cx_b, |workspace, cx| workspace.activate_next_pane(cx));
1311// assert_eq!(
1312// workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
1313// Some(leader_id)
1314// );
1315
1316// // When client B activates a different item in the original pane, it automatically stops following client A.
1317// workspace_b
1318// .update(cx_b, |workspace, cx| {
1319// workspace.open_path((worktree_id, "2.txt"), None, true, cx)
1320// })
1321// .await
1322// .unwrap();
1323// assert_eq!(
1324// workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
1325// None
1326// );
1327// }
1328
1329// #[gpui::test(iterations = 10)]
1330// async fn test_peers_simultaneously_following_each_other(
1331// executor: BackgroundExecutor,
1332// cx_a: &mut TestAppContext,
1333// cx_b: &mut TestAppContext,
1334// ) {
1335// let mut server = TestServer::start(&executor).await;
1336// let client_a = server.create_client(cx_a, "user_a").await;
1337// let client_b = server.create_client(cx_b, "user_b").await;
1338// server
1339// .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1340// .await;
1341// let active_call_a = cx_a.read(ActiveCall::global);
1342
1343// cx_a.update(editor::init);
1344// cx_b.update(editor::init);
1345
1346// client_a.fs().insert_tree("/a", json!({})).await;
1347// let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
1348// let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
1349// let project_id = active_call_a
1350// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1351// .await
1352// .unwrap();
1353
1354// let project_b = client_b.build_remote_project(project_id, cx_b).await;
1355// let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
1356
1357// executor.run_until_parked();
1358// let client_a_id = project_b.read_with(cx_b, |project, _| {
1359// project.collaborators().values().next().unwrap().peer_id
1360// });
1361// let client_b_id = project_a.read_with(cx_a, |project, _| {
1362// project.collaborators().values().next().unwrap().peer_id
1363// });
1364
1365// let a_follow_b = workspace_a.update(cx_a, |workspace, cx| {
1366// workspace.follow(client_b_id, cx).unwrap()
1367// });
1368// let b_follow_a = workspace_b.update(cx_b, |workspace, cx| {
1369// workspace.follow(client_a_id, cx).unwrap()
1370// });
1371
1372// futures::try_join!(a_follow_b, b_follow_a).unwrap();
1373// workspace_a.read_with(cx_a, |workspace, _| {
1374// assert_eq!(
1375// workspace.leader_for_pane(workspace.active_pane()),
1376// Some(client_b_id)
1377// );
1378// });
1379// workspace_b.read_with(cx_b, |workspace, _| {
1380// assert_eq!(
1381// workspace.leader_for_pane(workspace.active_pane()),
1382// Some(client_a_id)
1383// );
1384// });
1385// }
1386
1387// #[gpui::test(iterations = 10)]
1388// async fn test_following_across_workspaces(
1389// executor: BackgroundExecutor,
1390// cx_a: &mut TestAppContext,
1391// cx_b: &mut TestAppContext,
1392// ) {
1393// // a and b join a channel/call
1394// // a shares project 1
1395// // b shares project 2
1396// //
1397// // b follows a: causes project 2 to be joined, and b to follow a.
1398// // b opens a different file in project 2, a follows b
1399// // b opens a different file in project 1, a cannot follow b
1400// // b shares the project, a joins the project and follows b
1401// let mut server = TestServer::start(&executor).await;
1402// let client_a = server.create_client(cx_a, "user_a").await;
1403// let client_b = server.create_client(cx_b, "user_b").await;
1404// cx_a.update(editor::init);
1405// cx_b.update(editor::init);
1406
1407// client_a
1408// .fs()
1409// .insert_tree(
1410// "/a",
1411// json!({
1412// "w.rs": "",
1413// "x.rs": "",
1414// }),
1415// )
1416// .await;
1417
1418// client_b
1419// .fs()
1420// .insert_tree(
1421// "/b",
1422// json!({
1423// "y.rs": "",
1424// "z.rs": "",
1425// }),
1426// )
1427// .await;
1428
1429// server
1430// .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1431// .await;
1432// let active_call_a = cx_a.read(ActiveCall::global);
1433// let active_call_b = cx_b.read(ActiveCall::global);
1434
1435// let (project_a, worktree_id_a) = client_a.build_local_project("/a", cx_a).await;
1436// let (project_b, worktree_id_b) = client_b.build_local_project("/b", cx_b).await;
1437
1438// let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
1439// let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
1440
1441// cx_a.update(|cx| collab_ui::init(&client_a.app_state, cx));
1442// cx_b.update(|cx| collab_ui::init(&client_b.app_state, cx));
1443
1444// active_call_a
1445// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1446// .await
1447// .unwrap();
1448
1449// active_call_a
1450// .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
1451// .await
1452// .unwrap();
1453// active_call_b
1454// .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
1455// .await
1456// .unwrap();
1457
1458// workspace_a
1459// .update(cx_a, |workspace, cx| {
1460// workspace.open_path((worktree_id_a, "w.rs"), None, true, cx)
1461// })
1462// .await
1463// .unwrap();
1464
1465// executor.run_until_parked();
1466// assert_eq!(visible_push_notifications(cx_b).len(), 1);
1467
1468// workspace_b.update(cx_b, |workspace, cx| {
1469// workspace
1470// .follow(client_a.peer_id().unwrap(), cx)
1471// .unwrap()
1472// .detach()
1473// });
1474
1475// executor.run_until_parked();
1476// let workspace_b_project_a = cx_b
1477// .windows()
1478// .iter()
1479// .max_by_key(|window| window.id())
1480// .unwrap()
1481// .downcast::<Workspace>()
1482// .unwrap()
1483// .root(cx_b);
1484
1485// // assert that b is following a in project a in w.rs
1486// workspace_b_project_a.update(cx_b, |workspace, cx| {
1487// assert!(workspace.is_being_followed(client_a.peer_id().unwrap()));
1488// assert_eq!(
1489// client_a.peer_id(),
1490// workspace.leader_for_pane(workspace.active_pane())
1491// );
1492// let item = workspace.active_item(cx).unwrap();
1493// assert_eq!(item.tab_description(0, cx).unwrap(), Cow::Borrowed("w.rs"));
1494// });
1495
1496// // TODO: in app code, this would be done by the collab_ui.
1497// active_call_b
1498// .update(cx_b, |call, cx| {
1499// let project = workspace_b_project_a.read(cx).project().clone();
1500// call.set_location(Some(&project), cx)
1501// })
1502// .await
1503// .unwrap();
1504
1505// // assert that there are no share notifications open
1506// assert_eq!(visible_push_notifications(cx_b).len(), 0);
1507
1508// // b moves to x.rs in a's project, and a follows
1509// workspace_b_project_a
1510// .update(cx_b, |workspace, cx| {
1511// workspace.open_path((worktree_id_a, "x.rs"), None, true, cx)
1512// })
1513// .await
1514// .unwrap();
1515
1516// executor.run_until_parked();
1517// workspace_b_project_a.update(cx_b, |workspace, cx| {
1518// let item = workspace.active_item(cx).unwrap();
1519// assert_eq!(item.tab_description(0, cx).unwrap(), Cow::Borrowed("x.rs"));
1520// });
1521
1522// workspace_a.update(cx_a, |workspace, cx| {
1523// workspace
1524// .follow(client_b.peer_id().unwrap(), cx)
1525// .unwrap()
1526// .detach()
1527// });
1528
1529// executor.run_until_parked();
1530// workspace_a.update(cx_a, |workspace, cx| {
1531// assert!(workspace.is_being_followed(client_b.peer_id().unwrap()));
1532// assert_eq!(
1533// client_b.peer_id(),
1534// workspace.leader_for_pane(workspace.active_pane())
1535// );
1536// let item = workspace.active_pane().read(cx).active_item().unwrap();
1537// assert_eq!(item.tab_description(0, cx).unwrap(), Cow::Borrowed("x.rs"));
1538// });
1539
1540// // b moves to y.rs in b's project, a is still following but can't yet see
1541// workspace_b
1542// .update(cx_b, |workspace, cx| {
1543// workspace.open_path((worktree_id_b, "y.rs"), None, true, cx)
1544// })
1545// .await
1546// .unwrap();
1547
1548// // TODO: in app code, this would be done by the collab_ui.
1549// active_call_b
1550// .update(cx_b, |call, cx| {
1551// let project = workspace_b.read(cx).project().clone();
1552// call.set_location(Some(&project), cx)
1553// })
1554// .await
1555// .unwrap();
1556
1557// let project_b_id = active_call_b
1558// .update(cx_b, |call, cx| call.share_project(project_b.clone(), cx))
1559// .await
1560// .unwrap();
1561
1562// executor.run_until_parked();
1563// assert_eq!(visible_push_notifications(cx_a).len(), 1);
1564// cx_a.update(|cx| {
1565// workspace::join_remote_project(
1566// project_b_id,
1567// client_b.user_id().unwrap(),
1568// client_a.app_state.clone(),
1569// cx,
1570// )
1571// })
1572// .await
1573// .unwrap();
1574
1575// executor.run_until_parked();
1576
1577// assert_eq!(visible_push_notifications(cx_a).len(), 0);
1578// let workspace_a_project_b = cx_a
1579// .windows()
1580// .iter()
1581// .max_by_key(|window| window.id())
1582// .unwrap()
1583// .downcast::<Workspace>()
1584// .unwrap()
1585// .root(cx_a);
1586
1587// workspace_a_project_b.update(cx_a, |workspace, cx| {
1588// assert_eq!(workspace.project().read(cx).remote_id(), Some(project_b_id));
1589// assert!(workspace.is_being_followed(client_b.peer_id().unwrap()));
1590// assert_eq!(
1591// client_b.peer_id(),
1592// workspace.leader_for_pane(workspace.active_pane())
1593// );
1594// let item = workspace.active_item(cx).unwrap();
1595// assert_eq!(item.tab_description(0, cx).unwrap(), Cow::Borrowed("y.rs"));
1596// });
1597// }
1598
1599// fn visible_push_notifications(
1600// cx: &mut TestAppContext,
1601// ) -> Vec<gpui::ViewHandle<ProjectSharedNotification>> {
1602// let mut ret = Vec::new();
1603// for window in cx.windows() {
1604// window.read_with(cx, |window| {
1605// if let Some(handle) = window
1606// .root_view()
1607// .clone()
1608// .downcast::<ProjectSharedNotification>()
1609// {
1610// ret.push(handle)
1611// }
1612// });
1613// }
1614// ret
1615// }
1616
1617// #[derive(Debug, PartialEq, Eq)]
1618// struct PaneSummary {
1619// active: bool,
1620// leader: Option<PeerId>,
1621// items: Vec<(bool, String)>,
1622// }
1623
1624// fn followers_by_leader(project_id: u64, cx: &TestAppContext) -> Vec<(PeerId, Vec<PeerId>)> {
1625// cx.read(|cx| {
1626// let active_call = ActiveCall::global(cx).read(cx);
1627// let peer_id = active_call.client().peer_id();
1628// let room = active_call.room().unwrap().read(cx);
1629// let mut result = room
1630// .remote_participants()
1631// .values()
1632// .map(|participant| participant.peer_id)
1633// .chain(peer_id)
1634// .filter_map(|peer_id| {
1635// let followers = room.followers_for(peer_id, project_id);
1636// if followers.is_empty() {
1637// None
1638// } else {
1639// Some((peer_id, followers.to_vec()))
1640// }
1641// })
1642// .collect::<Vec<_>>();
1643// result.sort_by_key(|e| e.0);
1644// result
1645// })
1646// }
1647
1648// fn pane_summaries(workspace: &ViewHandle<Workspace>, cx: &mut TestAppContext) -> Vec<PaneSummary> {
1649// workspace.read_with(cx, |workspace, cx| {
1650// let active_pane = workspace.active_pane();
1651// workspace
1652// .panes()
1653// .iter()
1654// .map(|pane| {
1655// let leader = workspace.leader_for_pane(pane);
1656// let active = pane == active_pane;
1657// let pane = pane.read(cx);
1658// let active_ix = pane.active_item_index();
1659// PaneSummary {
1660// active,
1661// leader,
1662// items: pane
1663// .items()
1664// .enumerate()
1665// .map(|(ix, item)| {
1666// (
1667// ix == active_ix,
1668// item.tab_description(0, cx)
1669// .map_or(String::new(), |s| s.to_string()),
1670// )
1671// })
1672// .collect(),
1673// }
1674// })
1675// .collect()
1676// })
1677// }