@@ -15,7 +15,7 @@ use editor::{
self, ConfirmCodeAction, ConfirmCompletion, ConfirmRename, Editor, ExcerptRange, MultiBuffer,
Redo, Rename, ToOffset, ToggleCodeActions, Undo,
};
-use fs::{FakeFs, Fs as _, HomeDir, LineEnding};
+use fs::{FakeFs, Fs as _, HomeDir, LineEnding, RemoveOptions};
use futures::{channel::oneshot, StreamExt as _};
use gpui::{
executor::Deterministic, geometry::vector::vec2f, test::EmptyView, ModelHandle, Task,
@@ -1306,6 +1306,98 @@ async fn test_host_disconnect(
project_a.read_with(cx_a, |project, _| assert!(!project.is_shared()));
}
+#[gpui::test(iterations = 10)]
+async fn test_host_reconnect(
+ deterministic: Arc<Deterministic>,
+ cx_a: &mut TestAppContext,
+ cx_b: &mut TestAppContext,
+) {
+ cx_b.update(editor::init);
+ deterministic.forbid_parking();
+ let mut server = TestServer::start(&deterministic).await;
+ let client_a = server.create_client(cx_a, "user_a").await;
+ let client_b = server.create_client(cx_b, "user_b").await;
+ server
+ .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
+ .await;
+
+ client_a
+ .fs
+ .insert_tree(
+ "/root",
+ json!({
+ "dir1": {
+ "a.txt": "a-contents",
+ "b.txt": "b-contents",
+ "subdir1": {
+ "c.txt": "c-contents",
+ "d.txt": "d-contents",
+ "e.txt": "e-contents",
+ }
+ },
+ "dir2": {
+ "x.txt": "x-contents",
+ },
+ }),
+ )
+ .await;
+
+ let active_call_a = cx_a.read(ActiveCall::global);
+ let (project_a, _) = client_a.build_local_project("/root/dir1", cx_a).await;
+ let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().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.build_remote_project(project_id, cx_b).await;
+ assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
+
+ // Drop client A's connection.
+ server.forbid_connections();
+ server.disconnect_client(client_a.peer_id().unwrap());
+ deterministic.advance_clock(RECEIVE_TIMEOUT);
+ project_a.read_with(cx_a, |project, _| {
+ assert!(project.collaborators().is_empty())
+ });
+ project_a.read_with(cx_a, |project, _| assert!(!project.is_shared()));
+ project_b.read_with(cx_b, |project, _| assert!(project.is_read_only()));
+ worktree_a.read_with(cx_a, |tree, _| {
+ assert!(!tree.as_local().unwrap().is_shared())
+ });
+
+ // While disconnected, add and remove files from the client A's project.
+ client_a
+ .fs
+ .insert_tree(
+ "/root/dir1/subdir2",
+ json!({
+ "f.txt": "f-contents",
+ "g.txt": "g-contents",
+ "h.txt": "h-contents",
+ "i.txt": "i-contents",
+ }),
+ )
+ .await;
+ client_a
+ .fs
+ .remove_dir(
+ "/root/dir1/subdir1".as_ref(),
+ RemoveOptions {
+ recursive: true,
+ ..Default::default()
+ },
+ )
+ .await
+ .unwrap();
+
+ // Client A reconnects. Their project is re-shared, and client B re-joins it.
+ server.allow_connections();
+ deterministic.advance_clock(RECEIVE_TIMEOUT);
+ project_a.read_with(cx_a, |project, _| assert!(project.is_shared()));
+ project_b.read_with(cx_b, |project, _| assert!(!project.is_read_only()));
+}
+
#[gpui::test(iterations = 10)]
async fn test_active_call_events(
deterministic: Arc<Deterministic>,