2216 lines · 73.0 KB · Rust
RawDownload
#![allow(clippy::reversed_empty_ranges)]use crate::tests::TestServer;use call::{ActiveCall, ParticipantLocation};use client::ChannelId;use collab_ui::{    channel_view::ChannelView,    notifications::project_shared_notification::ProjectSharedNotification,};use editor::{Editor, ExcerptRange, MultiBuffer};use gpui::{    point, AppContext as _, BackgroundExecutor, BorrowAppContext, Entity, SharedString,    TestAppContext, VisualTestContext,};use language::Capability;use project::WorktreeSettings;use rpc::proto::PeerId;use serde_json::json;use settings::SettingsStore;use workspace::{item::ItemHandle as _, SplitDirection, Workspace}; use super::TestClient; #[gpui::test(iterations = 10)]async fn test_basic_following(    cx_a: &mut TestAppContext,    cx_b: &mut TestAppContext,    cx_c: &mut TestAppContext,    cx_d: &mut TestAppContext,) {    let executor = cx_a.executor();    let mut server = TestServer::start(executor.clone()).await;    let client_a = server.create_client(cx_a, "user_a").await;    let client_b = server.create_client(cx_b, "user_b").await;    let client_c = server.create_client(cx_c, "user_c").await;    let client_d = server.create_client(cx_d, "user_d").await;    server        .create_room(&mut [            (&client_a, cx_a),            (&client_b, cx_b),            (&client_c, cx_c),            (&client_d, cx_d),        ])        .await;    let active_call_a = cx_a.read(ActiveCall::global);    let active_call_b = cx_b.read(ActiveCall::global);     cx_a.update(editor::init);    cx_b.update(editor::init);     client_a        .fs()        .insert_tree(            "/a",            json!({                "1.txt": "one\none\none",                "2.txt": "two\ntwo\ntwo",                "3.txt": "three\nthree\nthree",            }),        )        .await;    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;    active_call_a        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))        .await        .unwrap();     let project_id = active_call_a        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))        .await        .unwrap();    let project_b = client_b.join_remote_project(project_id, cx_b).await;    active_call_b        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))        .await        .unwrap();     let (workspace_a, cx_a) = client_a.build_workspace(&project_a, cx_a);    let (workspace_b, cx_b) = client_b.build_workspace(&project_b, cx_b);     cx_b.update(|window, _| {        assert!(window.is_window_active());    });     // Client A opens some editors.    let pane_a = workspace_a.update(cx_a, |workspace, _| workspace.active_pane().clone());    let editor_a1 = workspace_a        .update_in(cx_a, |workspace, window, cx| {            workspace.open_path((worktree_id, "1.txt"), None, true, window, cx)        })        .await        .unwrap()        .downcast::<Editor>()        .unwrap();    let editor_a2 = workspace_a        .update_in(cx_a, |workspace, window, cx| {            workspace.open_path((worktree_id, "2.txt"), None, true, window, cx)        })        .await        .unwrap()        .downcast::<Editor>()        .unwrap();     // Client B opens an editor.    let editor_b1 = workspace_b        .update_in(cx_b, |workspace, window, cx| {            workspace.open_path((worktree_id, "1.txt"), None, true, window, cx)        })        .await        .unwrap()        .downcast::<Editor>()        .unwrap();     let peer_id_a = client_a.peer_id().unwrap();    let peer_id_b = client_b.peer_id().unwrap();    let peer_id_c = client_c.peer_id().unwrap();    let peer_id_d = client_d.peer_id().unwrap();     // Client A updates their selections in those editors    editor_a1.update_in(cx_a, |editor, window, cx| {        editor.handle_input("a", window, cx);        editor.handle_input("b", window, cx);        editor.handle_input("c", window, cx);        editor.select_left(&Default::default(), window, cx);        assert_eq!(editor.selections.ranges(cx), vec![3..2]);    });    editor_a2.update_in(cx_a, |editor, window, cx| {        editor.handle_input("d", window, cx);        editor.handle_input("e", window, cx);        editor.select_left(&Default::default(), window, cx);        assert_eq!(editor.selections.ranges(cx), vec![2..1]);    });     // When client B starts following client A, only the active view state is replicated to client B.    workspace_b.update_in(cx_b, |workspace, window, cx| {        workspace.follow(peer_id_a, window, cx)    });     cx_c.executor().run_until_parked();    let editor_b2 = workspace_b.update(cx_b, |workspace, cx| {        workspace            .active_item(cx)            .unwrap()            .downcast::<Editor>()            .unwrap()    });    assert_eq!(        cx_b.read(|cx| editor_b2.project_path(cx)),        Some((worktree_id, "2.txt").into())    );    assert_eq!(        editor_b2.update(cx_b, |editor, cx| editor.selections.ranges(cx)),        vec![2..1]    );    assert_eq!(        editor_b1.update(cx_b, |editor, cx| editor.selections.ranges(cx)),        vec![3..3]    );     executor.run_until_parked();    let active_call_c = cx_c.read(ActiveCall::global);    let project_c = client_c.join_remote_project(project_id, cx_c).await;    let (workspace_c, cx_c) = client_c.build_workspace(&project_c, cx_c);    active_call_c        .update(cx_c, |call, cx| call.set_location(Some(&project_c), cx))        .await        .unwrap();    drop(project_c);     // Client C also follows client A.    workspace_c.update_in(cx_c, |workspace, window, cx| {        workspace.follow(peer_id_a, window, cx)    });     cx_d.executor().run_until_parked();    let active_call_d = cx_d.read(ActiveCall::global);    let project_d = client_d.join_remote_project(project_id, cx_d).await;    let (workspace_d, cx_d) = client_d.build_workspace(&project_d, cx_d);    active_call_d        .update(cx_d, |call, cx| call.set_location(Some(&project_d), cx))        .await        .unwrap();    drop(project_d);     // All clients see that clients B and C are following client A.    cx_c.executor().run_until_parked();    for (name, cx) in [("A", &cx_a), ("B", &cx_b), ("C", &cx_c), ("D", &cx_d)] {        assert_eq!(            followers_by_leader(project_id, cx),            &[(peer_id_a, vec![peer_id_b, peer_id_c])],            "followers seen by {name}"        );    }     // Client C unfollows client A.    workspace_c.update_in(cx_c, |workspace, window, cx| {        workspace.unfollow(peer_id_a, window, cx).unwrap();    });     // All clients see that clients B is following client A.    cx_c.executor().run_until_parked();    for (name, cx) in [("A", &cx_a), ("B", &cx_b), ("C", &cx_c), ("D", &cx_d)] {        assert_eq!(            followers_by_leader(project_id, cx),            &[(peer_id_a, vec![peer_id_b])],            "followers seen by {name}"        );    }     // Client C re-follows client A.    workspace_c.update_in(cx_c, |workspace, window, cx| {        workspace.follow(peer_id_a, window, cx)    });     // All clients see that clients B and C are following client A.    cx_c.executor().run_until_parked();    for (name, cx) in [("A", &cx_a), ("B", &cx_b), ("C", &cx_c), ("D", &cx_d)] {        assert_eq!(            followers_by_leader(project_id, cx),            &[(peer_id_a, vec![peer_id_b, peer_id_c])],            "followers seen by {name}"        );    }     // Client D follows client B, then switches to following client C.    workspace_d.update_in(cx_d, |workspace, window, cx| {        workspace.follow(peer_id_b, window, cx)    });    cx_a.executor().run_until_parked();    workspace_d.update_in(cx_d, |workspace, window, cx| {        workspace.follow(peer_id_c, window, cx)    });     // All clients see that D is following C    cx_a.executor().run_until_parked();    for (name, cx) in [("A", &cx_a), ("B", &cx_b), ("C", &cx_c), ("D", &cx_d)] {        assert_eq!(            followers_by_leader(project_id, cx),            &[                (peer_id_a, vec![peer_id_b, peer_id_c]),                (peer_id_c, vec![peer_id_d])            ],            "followers seen by {name}"        );    }     // Client C closes the project.    let weak_workspace_c = workspace_c.downgrade();    workspace_c.update_in(cx_c, |workspace, window, cx| {        workspace.close_window(&Default::default(), window, cx);    });    executor.run_until_parked();    // are you sure you want to leave the call?    cx_c.simulate_prompt_answer("Close window and hang up");    cx_c.cx.update(|_| {        drop(workspace_c);    });    executor.run_until_parked();    cx_c.cx.update(|_| {});     weak_workspace_c.assert_released();     // Clients A and B see that client B is following A, and client C is not present in the followers.    executor.run_until_parked();    for (name, cx) in [("A", &cx_a), ("B", &cx_b), ("D", &cx_d)] {        assert_eq!(            followers_by_leader(project_id, cx),            &[(peer_id_a, vec![peer_id_b]),],            "followers seen by {name}"        );    }     // When client A activates a different editor, client B does so as well.    workspace_a.update_in(cx_a, |workspace, window, cx| {        workspace.activate_item(&editor_a1, true, true, window, cx)    });    executor.run_until_parked();    workspace_b.update(cx_b, |workspace, cx| {        assert_eq!(            workspace.active_item(cx).unwrap().item_id(),            editor_b1.item_id()        );    });     // When client A opens a multibuffer, client B does so as well.    let multibuffer_a = cx_a.new(|cx| {        let buffer_a1 = project_a.update(cx, |project, cx| {            project                .get_open_buffer(&(worktree_id, "1.txt").into(), cx)                .unwrap()        });        let buffer_a2 = project_a.update(cx, |project, cx| {            project                .get_open_buffer(&(worktree_id, "2.txt").into(), cx)                .unwrap()        });        let mut result = MultiBuffer::new(Capability::ReadWrite);        result.push_excerpts(            buffer_a1,            [ExcerptRange {                context: 0..3,                primary: None,            }],            cx,        );        result.push_excerpts(            buffer_a2,            [ExcerptRange {                context: 4..7,                primary: None,            }],            cx,        );        result    });    let multibuffer_editor_a = workspace_a.update_in(cx_a, |workspace, window, cx| {        let editor = cx.new(|cx| {            Editor::for_multibuffer(multibuffer_a, Some(project_a.clone()), true, window, cx)        });        workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);        editor    });    executor.run_until_parked();    let multibuffer_editor_b = workspace_b.update(cx_b, |workspace, cx| {        workspace            .active_item(cx)            .unwrap()            .downcast::<Editor>()            .unwrap()    });    assert_eq!(        multibuffer_editor_a.update(cx_a, |editor, cx| editor.text(cx)),        multibuffer_editor_b.update(cx_b, |editor, cx| editor.text(cx)),    );     // When client A navigates back and forth, client B does so as well.    workspace_a        .update_in(cx_a, |workspace, window, cx| {            workspace.go_back(workspace.active_pane().downgrade(), window, cx)        })        .await        .unwrap();    executor.run_until_parked();    workspace_b.update(cx_b, |workspace, cx| {        assert_eq!(            workspace.active_item(cx).unwrap().item_id(),            editor_b1.item_id()        );    });     workspace_a        .update_in(cx_a, |workspace, window, cx| {            workspace.go_back(workspace.active_pane().downgrade(), window, cx)        })        .await        .unwrap();    executor.run_until_parked();    workspace_b.update(cx_b, |workspace, cx| {        assert_eq!(            workspace.active_item(cx).unwrap().item_id(),            editor_b2.item_id()        );    });     workspace_a        .update_in(cx_a, |workspace, window, cx| {            workspace.go_forward(workspace.active_pane().downgrade(), window, cx)        })        .await        .unwrap();    executor.run_until_parked();    workspace_b.update(cx_b, |workspace, cx| {        assert_eq!(            workspace.active_item(cx).unwrap().item_id(),            editor_b1.item_id()        );    });     // Changes to client A's editor are reflected on client B.    editor_a1.update_in(cx_a, |editor, window, cx| {        editor.change_selections(None, window, cx, |s| s.select_ranges([1..1, 2..2]));    });    executor.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE);    executor.run_until_parked();    cx_b.background_executor.run_until_parked();     editor_b1.update(cx_b, |editor, cx| {        assert_eq!(editor.selections.ranges(cx), &[1..1, 2..2]);    });     editor_a1.update_in(cx_a, |editor, window, cx| {        editor.set_text("TWO", window, cx)    });    executor.run_until_parked();    editor_b1.update(cx_b, |editor, cx| assert_eq!(editor.text(cx), "TWO"));     editor_a1.update_in(cx_a, |editor, window, cx| {        editor.change_selections(None, window, cx, |s| s.select_ranges([3..3]));        editor.set_scroll_position(point(0., 100.), window, cx);    });    executor.advance_clock(workspace::item::LEADER_UPDATE_THROTTLE);    executor.run_until_parked();    editor_b1.update(cx_b, |editor, cx| {        assert_eq!(editor.selections.ranges(cx), &[3..3]);    });     // After unfollowing, client B stops receiving updates from client A.    workspace_b.update_in(cx_b, |workspace, window, cx| {        workspace.unfollow(peer_id_a, window, cx).unwrap()    });    workspace_a.update_in(cx_a, |workspace, window, cx| {        workspace.activate_item(&editor_a2, true, true, window, cx)    });    executor.run_until_parked();    assert_eq!(        workspace_b.update(cx_b, |workspace, cx| workspace            .active_item(cx)            .unwrap()            .item_id()),        editor_b1.item_id()    );     // Client A starts following client B.    workspace_a.update_in(cx_a, |workspace, window, cx| {        workspace.follow(peer_id_b, window, cx)    });    executor.run_until_parked();    assert_eq!(        workspace_a.update(cx_a, |workspace, _| workspace.leader_for_pane(&pane_a)),        Some(peer_id_b)    );    assert_eq!(        workspace_a.update_in(cx_a, |workspace, _, cx| workspace            .active_item(cx)            .unwrap()            .item_id()),        editor_a1.item_id()    );     // TODO: Re-enable this test once we can replace our swift Livekit SDK with the rust SDK    #[cfg(not(target_os = "macos"))]    {        use crate::rpc::RECONNECT_TIMEOUT;        use gpui::TestScreenCaptureSource;        use workspace::{            dock::{test::TestPanel, DockPosition},            item::test::TestItem,            shared_screen::SharedScreen,        };         // Client B activates an external window, which causes a new screen-sharing item to be added to the pane.        let display = TestScreenCaptureSource::new();        active_call_b            .update(cx_b, |call, cx| call.set_location(None, cx))            .await            .unwrap();        cx_b.set_screen_capture_sources(vec![display]);        active_call_b            .update(cx_b, |call, cx| {                call.room()                    .unwrap()                    .update(cx, |room, cx| room.share_screen(cx))            })            .await            .unwrap(); // This is what breaks        executor.run_until_parked();        let shared_screen = workspace_a.update(cx_a, |workspace, cx| {            workspace                .active_item(cx)                .expect("no active item")                .downcast::<SharedScreen>()                .expect("active item isn't a shared screen")        });         // Client B activates Zed again, which causes the previous editor to become focused again.        active_call_b            .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))            .await            .unwrap();        executor.run_until_parked();        workspace_a.update(cx_a, |workspace, cx| {            assert_eq!(                workspace.active_item(cx).unwrap().item_id(),                editor_a1.item_id()            )        });         // Client B activates a multibuffer that was created by following client A. Client A returns to that multibuffer.        workspace_b.update_in(cx_b, |workspace, window, cx| {            workspace.activate_item(&multibuffer_editor_b, true, true, window, cx)        });        executor.run_until_parked();        workspace_a.update(cx_a, |workspace, cx| {            assert_eq!(                workspace.active_item(cx).unwrap().item_id(),                multibuffer_editor_a.item_id()            )        });         // Client B activates a panel, and the previously-opened screen-sharing item gets activated.        let panel = cx_b.new(|cx| TestPanel::new(DockPosition::Left, cx));

Lines 1–500 of 2216.