remote_editing_collaboration_tests.rs

  1use crate::tests::TestServer;
  2use call::ActiveCall;
  3use fs::{FakeFs, Fs as _};
  4use gpui::{Context as _, TestAppContext};
  5use language::language_settings::all_language_settings;
  6use project::ProjectPath;
  7use remote::SshRemoteClient;
  8use remote_server::HeadlessProject;
  9use serde_json::json;
 10use std::{path::Path, sync::Arc};
 11
 12#[gpui::test(iterations = 10)]
 13async fn test_sharing_an_ssh_remote_project(
 14    cx_a: &mut TestAppContext,
 15    cx_b: &mut TestAppContext,
 16    server_cx: &mut TestAppContext,
 17) {
 18    let executor = cx_a.executor();
 19    let mut server = TestServer::start(executor.clone()).await;
 20    let client_a = server.create_client(cx_a, "user_a").await;
 21    let client_b = server.create_client(cx_b, "user_b").await;
 22    server
 23        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
 24        .await;
 25
 26    // Set up project on remote FS
 27    let (client_ssh, server_ssh) = SshRemoteClient::fake(cx_a, server_cx);
 28    let remote_fs = FakeFs::new(server_cx.executor());
 29    remote_fs
 30        .insert_tree(
 31            "/code",
 32            json!({
 33                "project1": {
 34                    ".zed": {
 35                        "settings.json": r#"{"languages":{"Rust":{"language_servers":["override-rust-analyzer"]}}}"#
 36                    },
 37                    "README.md": "# project 1",
 38                    "src": {
 39                        "lib.rs": "fn one() -> usize { 1 }"
 40                    }
 41                },
 42                "project2": {
 43                    "README.md": "# project 2",
 44                },
 45            }),
 46        )
 47        .await;
 48
 49    // User A connects to the remote project via SSH.
 50    server_cx.update(HeadlessProject::init);
 51    let _headless_project =
 52        server_cx.new_model(|cx| HeadlessProject::new(server_ssh, remote_fs.clone(), cx));
 53
 54    let (project_a, worktree_id) = client_a
 55        .build_ssh_project("/code/project1", client_ssh, cx_a)
 56        .await;
 57
 58    // While the SSH worktree is being scanned, user A shares the remote project.
 59    let active_call_a = cx_a.read(ActiveCall::global);
 60    let project_id = active_call_a
 61        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
 62        .await
 63        .unwrap();
 64
 65    // User B joins the project.
 66    let project_b = client_b.join_remote_project(project_id, cx_b).await;
 67    let worktree_b = project_b
 68        .update(cx_b, |project, cx| project.worktree_for_id(worktree_id, cx))
 69        .unwrap();
 70
 71    let worktree_a = project_a
 72        .update(cx_a, |project, cx| project.worktree_for_id(worktree_id, cx))
 73        .unwrap();
 74
 75    executor.run_until_parked();
 76
 77    worktree_a.update(cx_a, |worktree, _cx| {
 78        assert_eq!(
 79            worktree.paths().map(Arc::as_ref).collect::<Vec<_>>(),
 80            vec![
 81                Path::new(".zed"),
 82                Path::new(".zed/settings.json"),
 83                Path::new("README.md"),
 84                Path::new("src"),
 85                Path::new("src/lib.rs"),
 86            ]
 87        );
 88    });
 89
 90    worktree_b.update(cx_b, |worktree, _cx| {
 91        assert_eq!(
 92            worktree.paths().map(Arc::as_ref).collect::<Vec<_>>(),
 93            vec![
 94                Path::new(".zed"),
 95                Path::new(".zed/settings.json"),
 96                Path::new("README.md"),
 97                Path::new("src"),
 98                Path::new("src/lib.rs"),
 99            ]
100        );
101    });
102
103    // User B can open buffers in the remote project.
104    let buffer_b = project_b
105        .update(cx_b, |project, cx| {
106            project.open_buffer((worktree_id, "src/lib.rs"), cx)
107        })
108        .await
109        .unwrap();
110    buffer_b.update(cx_b, |buffer, cx| {
111        assert_eq!(buffer.text(), "fn one() -> usize { 1 }");
112        let ix = buffer.text().find('1').unwrap();
113        buffer.edit([(ix..ix + 1, "100")], None, cx);
114    });
115
116    executor.run_until_parked();
117
118    cx_b.read(|cx| {
119        let file = buffer_b.read(cx).file();
120        assert_eq!(
121            all_language_settings(file, cx)
122                .language(Some(&("Rust".into())))
123                .language_servers,
124            ["override-rust-analyzer".to_string()]
125        )
126    });
127
128    project_b
129        .update(cx_b, |project, cx| {
130            project.save_buffer_as(
131                buffer_b.clone(),
132                ProjectPath {
133                    worktree_id: worktree_id.to_owned(),
134                    path: Arc::from(Path::new("src/renamed.rs")),
135                },
136                cx,
137            )
138        })
139        .await
140        .unwrap();
141    assert_eq!(
142        remote_fs
143            .load("/code/project1/src/renamed.rs".as_ref())
144            .await
145            .unwrap(),
146        "fn one() -> usize { 100 }"
147    );
148    cx_b.run_until_parked();
149    cx_b.update(|cx| {
150        assert_eq!(
151            buffer_b
152                .read(cx)
153                .file()
154                .unwrap()
155                .path()
156                .to_string_lossy()
157                .to_string(),
158            "src/renamed.rs".to_string()
159        );
160    });
161}