Further improve sidebar project integration test

Anthony Eid created

Change summary

crates/sidebar/src/sidebar_tests.rs | 162 ++++++++++++++++++++++++------
1 file changed, 126 insertions(+), 36 deletions(-)

Detailed changes

crates/sidebar/src/sidebar_tests.rs 🔗

@@ -5802,19 +5802,16 @@ async fn test_remote_project_integration_does_not_briefly_render_as_separate_pro
         .await;
     server_fs.set_branch_name(Path::new("/project/.git"), Some("main"));
 
-    // Create a linked worktree on the remote server so that opening
-    // /project-wt-1 succeeds and the worktree has a .git file pointing
-    // back to the main repo.
+    // Create the linked worktree checkout path on the remote server,
+    // but do not yet register it as a git-linked worktree. The real
+    // regrouping update in this test should happen only after the
+    // sidebar opens the closed remote thread.
     server_fs
-        .add_linked_worktree_for_repo(
-            Path::new("/project/.git"),
-            false,
-            git::repository::Worktree {
-                path: PathBuf::from("/project-wt-1"),
-                ref_name: Some("refs/heads/feature-wt".into()),
-                sha: "abc123".into(),
-                is_main: false,
-            },
+        .insert_tree(
+            "/project-wt-1",
+            serde_json::json!({
+                "src": { "main.rs": "fn main() {}" }
+            }),
         )
         .await;
 
@@ -5926,44 +5923,129 @@ async fn test_remote_project_integration_does_not_briefly_render_as_separate_pro
     });
     cx.run_until_parked();
 
-    let main_thread_id_for_observer = main_thread_id.clone();
-    let remote_thread_id_for_observer = remote_thread_id.clone();
+    focus_sidebar(&sidebar, cx);
+    sidebar.update_in(cx, |sidebar, _window, _cx| {
+        sidebar.selection = sidebar.contents.entries.iter().position(|entry| {
+            matches!(
+                entry,
+                ListEntry::Thread(thread) if thread.metadata.session_id == remote_thread_id
+            )
+        });
+    });
+
+    let saw_separate_project_header = Arc::new(std::sync::atomic::AtomicBool::new(false));
+    let saw_separate_project_header_for_observer = saw_separate_project_header.clone();
 
     sidebar
         .update(cx, |_, cx| {
             cx.observe_self(move |sidebar, _cx| {
-                assert_remote_project_integration_sidebar_state(
-                    sidebar,
-                    &main_thread_id_for_observer,
-                    &remote_thread_id_for_observer,
-                );
+                let mut project_headers = sidebar.contents.entries.iter().filter_map(|entry| {
+                    if let ListEntry::ProjectHeader { label, .. } = entry {
+                        Some(label.as_ref())
+                    } else {
+                        None
+                    }
+                });
+
+                let Some(project_header) = project_headers.next() else {
+                    saw_separate_project_header_for_observer
+                        .store(true, std::sync::atomic::Ordering::SeqCst);
+                    return;
+                };
+
+                if project_header != "project" || project_headers.next().is_some() {
+                    saw_separate_project_header_for_observer
+                        .store(true, std::sync::atomic::Ordering::SeqCst);
+                }
             })
         })
         .detach();
 
-    // Simulate what happens in production when a new remote workspace
-    // is opened for a linked worktree: insert_workspace() computes
-    // project_group_key() before root_repo_common_dir is populated.
-    // The fallback uses abs_path(), producing key ("/project-wt-1")
-    // instead of the correct ("/project").
-    let remote_host = project.read_with(cx, |p, cx| p.remote_connection_options(cx));
-    let stale_key = ProjectGroupKey::new(
-        remote_host,
-        PathList::new(&[PathBuf::from("/project-wt-1")]),
-    );
-    multi_workspace.update(cx, |mw, _cx| {
-        mw.add_project_group_key(stale_key);
+    multi_workspace.update(cx, |multi_workspace, cx| {
+        let workspace = multi_workspace.workspace().clone();
+        workspace.update(cx, |workspace: &mut Workspace, cx| {
+            let remote_client = workspace
+                .project()
+                .read(cx)
+                .remote_client()
+                .expect("main remote project should have a remote client");
+            remote_client.update(cx, |remote_client: &mut remote::RemoteClient, cx| {
+                remote_client.force_server_not_running(cx);
+            });
+        });
+    });
+    cx.run_until_parked();
+
+    let (server_session_2, connect_guard_2) =
+        remote::RemoteClient::fake_server_with_opts(&original_opts, cx, server_cx);
+    let _headless_2 = server_cx.new(|cx| {
+        remote_server::HeadlessProject::new(
+            remote_server::HeadlessAppState {
+                session: server_session_2,
+                fs: server_fs.clone(),
+                http_client: Arc::new(http_client::BlockedHttpClient),
+                node_runtime: node_runtime::NodeRuntime::unavailable(),
+                languages: Arc::new(language::LanguageRegistry::new(server_executor.clone())),
+                extension_host_proxy: Arc::new(extension::ExtensionHostProxy::new()),
+                startup_time: std::time::Instant::now(),
+            },
+            false,
+            cx,
+        )
     });
+    drop(connect_guard_2);
+
+    let window = cx.windows()[0];
+    cx.update_window(window, |_, window, cx| {
+        window.dispatch_action(Confirm.boxed_clone(), cx);
+    })
+    .unwrap();
 
-    // Force the sidebar to rebuild immediately from the current
-    // MultiWorkspace state so the observer can detect transient
-    // duplicate headers instead of only checking the final settled view.
-    sidebar.update(cx, |sidebar, cx| {
-        sidebar.update_entries(cx);
+    cx.run_until_parked();
+
+    let new_workspace = multi_workspace.read_with(cx, |mw, _| {
+        assert_eq!(
+            mw.workspaces().count(),
+            2,
+            "confirming a closed remote thread should open a second workspace"
+        );
+        mw.workspaces()
+            .find(|workspace| workspace.entity_id() != mw.workspace().entity_id())
+            .unwrap()
+            .clone()
     });
 
+    server_fs
+        .add_linked_worktree_for_repo(
+            Path::new("/project/.git"),
+            true,
+            git::repository::Worktree {
+                path: PathBuf::from("/project-wt-1"),
+                ref_name: Some("refs/heads/feature-wt".into()),
+                sha: "abc123".into(),
+                is_main: false,
+            },
+        )
+        .await;
+
+    server_cx.run_until_parked();
+    cx.run_until_parked();
+    server_cx.run_until_parked();
     cx.run_until_parked();
 
+    let entries_after_update = visible_entries_as_strings(&sidebar, cx);
+    let group_after_update = new_workspace.read_with(cx, |workspace, cx| {
+        workspace.project().read(cx).project_group_key(cx)
+    });
+
+    assert_eq!(
+        group_after_update,
+        project.read_with(cx, |project, cx| project.project_group_key(cx)),
+        "expected the remote worktree workspace to be grouped under the main remote project after the real update; \
+         final sidebar entries: {:?}",
+        entries_after_update,
+    );
+
     sidebar.update(cx, |sidebar, _cx| {
         assert_remote_project_integration_sidebar_state(
             sidebar,
@@ -5971,4 +6053,12 @@ async fn test_remote_project_integration_does_not_briefly_render_as_separate_pro
             &remote_thread_id,
         );
     });
+
+    assert!(
+        !saw_separate_project_header.load(std::sync::atomic::Ordering::SeqCst),
+        "sidebar briefly rendered the remote worktree as a separate project during the real remote open/update sequence; \
+         final group: {:?}; final sidebar entries: {:?}",
+        group_after_update,
+        entries_after_update,
+    );
 }