remote_editing_collaboration_tests.rs

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