remote_editing_tests.rs

  1use crate::headless_project::HeadlessProject;
  2use client::{Client, UserStore};
  3use clock::FakeSystemClock;
  4use fs::{FakeFs, Fs as _};
  5use gpui::{Context, Model, TestAppContext};
  6use http_client::FakeHttpClient;
  7use language::LanguageRegistry;
  8use node_runtime::FakeNodeRuntime;
  9use project::Project;
 10use remote::SshSession;
 11use serde_json::json;
 12use settings::SettingsStore;
 13use std::{path::Path, sync::Arc};
 14
 15fn init_logger() {
 16    if std::env::var("RUST_LOG").is_ok() {
 17        env_logger::try_init().ok();
 18    }
 19}
 20
 21#[gpui::test]
 22async fn test_remote_editing(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
 23    let (client_ssh, server_ssh) = SshSession::fake(cx, server_cx);
 24    init_logger();
 25
 26    let fs = FakeFs::new(server_cx.executor());
 27    fs.insert_tree(
 28        "/code",
 29        json!({
 30            "project1": {
 31                ".git": {},
 32                "README.md": "# project 1",
 33                "src": {
 34                    "lib.rs": "fn one() -> usize { 1 }"
 35                }
 36            },
 37            "project2": {
 38                "README.md": "# project 2",
 39            },
 40        }),
 41    )
 42    .await;
 43    fs.set_index_for_repo(
 44        Path::new("/code/project1/.git"),
 45        &[(Path::new("src/lib.rs"), "fn one() -> usize { 0 }".into())],
 46    );
 47
 48    server_cx.update(HeadlessProject::init);
 49    let _headless_project =
 50        server_cx.new_model(|cx| HeadlessProject::new(server_ssh, fs.clone(), cx));
 51
 52    let project = build_project(client_ssh, cx);
 53    let (worktree, _) = project
 54        .update(cx, |project, cx| {
 55            project.find_or_create_worktree("/code/project1", true, cx)
 56        })
 57        .await
 58        .unwrap();
 59
 60    // The client sees the worktree's contents.
 61    cx.executor().run_until_parked();
 62    let worktree_id = worktree.read_with(cx, |worktree, _| worktree.id());
 63    worktree.update(cx, |worktree, _cx| {
 64        assert_eq!(
 65            worktree.paths().map(Arc::as_ref).collect::<Vec<_>>(),
 66            vec![
 67                Path::new(".git"),
 68                Path::new("README.md"),
 69                Path::new("src"),
 70                Path::new("src/lib.rs"),
 71            ]
 72        );
 73    });
 74
 75    // The user opens a buffer in the remote worktree. The buffer's
 76    // contents are loaded from the remote filesystem.
 77    let buffer = project
 78        .update(cx, |project, cx| {
 79            project.open_buffer((worktree_id, Path::new("src/lib.rs")), cx)
 80        })
 81        .await
 82        .unwrap();
 83    buffer.update(cx, |buffer, cx| {
 84        assert_eq!(buffer.text(), "fn one() -> usize { 1 }");
 85        assert_eq!(
 86            buffer.diff_base().unwrap().to_string(),
 87            "fn one() -> usize { 0 }"
 88        );
 89        let ix = buffer.text().find('1').unwrap();
 90        buffer.edit([(ix..ix + 1, "100")], None, cx);
 91    });
 92
 93    // The user saves the buffer. The new contents are written to the
 94    // remote filesystem.
 95    project
 96        .update(cx, |project, cx| project.save_buffer(buffer.clone(), cx))
 97        .await
 98        .unwrap();
 99    assert_eq!(
100        fs.load("/code/project1/src/lib.rs".as_ref()).await.unwrap(),
101        "fn one() -> usize { 100 }"
102    );
103
104    // A new file is created in the remote filesystem. The user
105    // sees the new file.
106    fs.save(
107        "/code/project1/src/main.rs".as_ref(),
108        &"fn main() {}".into(),
109        Default::default(),
110    )
111    .await
112    .unwrap();
113    cx.executor().run_until_parked();
114    worktree.update(cx, |worktree, _cx| {
115        assert_eq!(
116            worktree.paths().map(Arc::as_ref).collect::<Vec<_>>(),
117            vec![
118                Path::new(".git"),
119                Path::new("README.md"),
120                Path::new("src"),
121                Path::new("src/lib.rs"),
122                Path::new("src/main.rs"),
123            ]
124        );
125    });
126
127    // A file that is currently open in a buffer is renamed.
128    fs.rename(
129        "/code/project1/src/lib.rs".as_ref(),
130        "/code/project1/src/lib2.rs".as_ref(),
131        Default::default(),
132    )
133    .await
134    .unwrap();
135    cx.executor().run_until_parked();
136    buffer.update(cx, |buffer, _| {
137        assert_eq!(&**buffer.file().unwrap().path(), Path::new("src/lib2.rs"));
138    });
139
140    fs.set_index_for_repo(
141        Path::new("/code/project1/.git"),
142        &[(Path::new("src/lib2.rs"), "fn one() -> usize { 100 }".into())],
143    );
144    cx.executor().run_until_parked();
145    buffer.update(cx, |buffer, _| {
146        assert_eq!(
147            buffer.diff_base().unwrap().to_string(),
148            "fn one() -> usize { 100 }"
149        );
150    });
151}
152
153fn build_project(ssh: Arc<SshSession>, cx: &mut TestAppContext) -> Model<Project> {
154    cx.update(|cx| {
155        let settings_store = SettingsStore::test(cx);
156        cx.set_global(settings_store);
157    });
158
159    let client = cx.update(|cx| {
160        Client::new(
161            Arc::new(FakeSystemClock::default()),
162            FakeHttpClient::with_404_response(),
163            cx,
164        )
165    });
166
167    let node = FakeNodeRuntime::new();
168    let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx));
169    let languages = Arc::new(LanguageRegistry::test(cx.executor()));
170    let fs = FakeFs::new(cx.executor());
171    cx.update(|cx| {
172        Project::init(&client, cx);
173        language::init(cx);
174    });
175
176    cx.update(|cx| Project::ssh(ssh, client, node, user_store, languages, fs, cx))
177}