1use crate::headless_project::HeadlessProject;
2use client::{Client, UserStore};
3use clock::FakeSystemClock;
4use fs::{FakeFs, Fs as _};
5use gpui::{Context, Model, TestAppContext};
6use http::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
15#[gpui::test]
16async fn test_remote_editing(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
17 let (client_ssh, server_ssh) = SshSession::fake(cx, server_cx);
18
19 let fs = FakeFs::new(server_cx.executor());
20 fs.insert_tree(
21 "/code",
22 json!({
23 "project1": {
24 "README.md": "# project 1",
25 "src": {
26 "lib.rs": "fn one() -> usize { 1 }"
27 }
28 },
29 "project2": {
30 "README.md": "# project 2",
31 },
32 }),
33 )
34 .await;
35
36 server_cx.update(HeadlessProject::init);
37 let _headless_project =
38 server_cx.new_model(|cx| HeadlessProject::new(server_ssh, fs.clone(), cx));
39
40 let project = build_project(client_ssh, cx);
41 let (worktree, _) = project
42 .update(cx, |project, cx| {
43 project.find_or_create_worktree("/code/project1", true, cx)
44 })
45 .await
46 .unwrap();
47
48 // The client sees the worktree's contents.
49 cx.executor().run_until_parked();
50 let worktree_id = worktree.read_with(cx, |worktree, _| worktree.id());
51 worktree.update(cx, |worktree, _cx| {
52 assert_eq!(
53 worktree.paths().map(Arc::as_ref).collect::<Vec<_>>(),
54 vec![
55 Path::new("README.md"),
56 Path::new("src"),
57 Path::new("src/lib.rs"),
58 ]
59 );
60 });
61
62 // The user opens a buffer in the remote worktree. The buffer's
63 // contents are loaded from the remote filesystem.
64 let buffer = project
65 .update(cx, |project, cx| {
66 project.open_buffer((worktree_id, Path::new("src/lib.rs")), cx)
67 })
68 .await
69 .unwrap();
70 buffer.update(cx, |buffer, cx| {
71 assert_eq!(buffer.text(), "fn one() -> usize { 1 }");
72 let ix = buffer.text().find('1').unwrap();
73 buffer.edit([(ix..ix + 1, "100")], None, cx);
74 });
75
76 // The user saves the buffer. The new contents are written to the
77 // remote filesystem.
78 project
79 .update(cx, |project, cx| project.save_buffer(buffer, cx))
80 .await
81 .unwrap();
82 assert_eq!(
83 fs.load("/code/project1/src/lib.rs".as_ref()).await.unwrap(),
84 "fn one() -> usize { 100 }"
85 );
86
87 // A new file is created in the remote filesystem. The user
88 // sees the new file.
89 fs.save(
90 "/code/project1/src/main.rs".as_ref(),
91 &"fn main() {}".into(),
92 Default::default(),
93 )
94 .await
95 .unwrap();
96 cx.executor().run_until_parked();
97 worktree.update(cx, |worktree, _cx| {
98 assert_eq!(
99 worktree.paths().map(Arc::as_ref).collect::<Vec<_>>(),
100 vec![
101 Path::new("README.md"),
102 Path::new("src"),
103 Path::new("src/lib.rs"),
104 Path::new("src/main.rs"),
105 ]
106 );
107 });
108}
109
110fn build_project(ssh: Arc<SshSession>, cx: &mut TestAppContext) -> Model<Project> {
111 cx.update(|cx| {
112 let settings_store = SettingsStore::test(cx);
113 cx.set_global(settings_store);
114 });
115
116 let client = cx.update(|cx| {
117 Client::new(
118 Arc::new(FakeSystemClock::default()),
119 FakeHttpClient::with_404_response(),
120 cx,
121 )
122 });
123
124 let node = FakeNodeRuntime::new();
125 let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx));
126 let languages = Arc::new(LanguageRegistry::test(cx.executor()));
127 let fs = FakeFs::new(cx.executor());
128 cx.update(|cx| {
129 Project::init(&client, cx);
130 language::init(cx);
131 });
132
133 cx.update(|cx| Project::ssh(ssh, client, node, user_store, languages, fs, cx))
134}