Add unit test for `Project::definition`

Antonio Scandurra and Nathan Sobo created

Co-Authored-By: Nathan Sobo <nathan@zed.dev>

Change summary

crates/project/src/project.rs | 135 +++++++++++++++++++++++++++++++++++-
1 file changed, 128 insertions(+), 7 deletions(-)

Detailed changes

crates/project/src/project.rs 🔗

@@ -1498,7 +1498,8 @@ mod tests {
     use futures::StreamExt;
     use gpui::{test::subscribe, TestAppContext};
     use language::{
-        tree_sitter_rust, Diagnostic, LanguageConfig, LanguageRegistry, LanguageServerConfig, Point,
+        tree_sitter_rust, AnchorRangeExt, Diagnostic, LanguageConfig, LanguageRegistry,
+        LanguageServerConfig, Point,
     };
     use lsp::Url;
     use serde_json::json;
@@ -1532,9 +1533,9 @@ mod tests {
 
         let project = build_project(&mut cx);
 
-        let tree = project
+        let (tree, _) = project
             .update(&mut cx, |project, cx| {
-                project.add_local_worktree(&root_link_path, false, cx)
+                project.find_or_create_worktree_for_abs_path(&root_link_path, false, cx)
             })
             .await
             .unwrap();
@@ -1607,9 +1608,9 @@ mod tests {
             )
         });
 
-        let tree = project
+        let (tree, _) = project
             .update(&mut cx, |project, cx| {
-                project.add_local_worktree(dir.path(), false, cx)
+                project.find_or_create_worktree_for_abs_path(dir.path(), false, cx)
             })
             .await
             .unwrap();
@@ -1713,9 +1714,9 @@ mod tests {
         }));
 
         let project = build_project(&mut cx);
-        let tree = project
+        let (tree, _) = project
             .update(&mut cx, |project, cx| {
-                project.add_local_worktree(&dir.path(), false, cx)
+                project.find_or_create_worktree_for_abs_path(&dir.path(), false, cx)
             })
             .await
             .unwrap();
@@ -1733,6 +1734,126 @@ mod tests {
         assert!(results.is_empty());
     }
 
+    #[gpui::test]
+    async fn test_definition(mut cx: gpui::TestAppContext) {
+        let (language_server_config, mut fake_server) =
+            LanguageServerConfig::fake(cx.background()).await;
+
+        let mut languages = LanguageRegistry::new();
+        languages.add(Arc::new(Language::new(
+            LanguageConfig {
+                name: "Rust".to_string(),
+                path_suffixes: vec!["rs".to_string()],
+                language_server: Some(language_server_config),
+                ..Default::default()
+            },
+            Some(tree_sitter_rust::language()),
+        )));
+
+        let dir = temp_tree(json!({
+            "a.rs": "const fn a() { A }",
+            "b.rs": "const y: i32 = crate::a()",
+        }));
+
+        let http_client = FakeHttpClient::with_404_response();
+        let client = Client::new(http_client.clone());
+        let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
+        let project = cx.update(|cx| {
+            Project::local(
+                client,
+                user_store,
+                Arc::new(languages),
+                Arc::new(RealFs),
+                cx,
+            )
+        });
+
+        let (tree, _) = project
+            .update(&mut cx, |project, cx| {
+                project.find_or_create_worktree_for_abs_path(dir.path().join("b.rs"), false, cx)
+            })
+            .await
+            .unwrap();
+        let worktree_id = tree.read_with(&cx, |tree, _| tree.id());
+        cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
+            .await;
+
+        // Cause worktree to start the fake language server
+        let buffer = project
+            .update(&mut cx, |project, cx| {
+                project.open_buffer(
+                    ProjectPath {
+                        worktree_id,
+                        path: Path::new("").into(),
+                    },
+                    cx,
+                )
+            })
+            .await
+            .unwrap();
+        let definitions =
+            project.update(&mut cx, |project, cx| project.definition(&buffer, 22, cx));
+        let (request_id, request) = fake_server
+            .receive_request::<lsp::request::GotoDefinition>()
+            .await;
+        let request_params = request.text_document_position_params;
+        assert_eq!(
+            request_params.text_document.uri.to_file_path().unwrap(),
+            dir.path().join("b.rs")
+        );
+        assert_eq!(request_params.position, lsp::Position::new(0, 22));
+
+        fake_server
+            .respond(
+                request_id,
+                Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location::new(
+                    lsp::Url::from_file_path(dir.path().join("a.rs")).unwrap(),
+                    lsp::Range::new(lsp::Position::new(0, 9), lsp::Position::new(0, 10)),
+                ))),
+            )
+            .await;
+        let mut definitions = definitions.await.unwrap();
+        assert_eq!(definitions.len(), 1);
+        let definition = definitions.pop().unwrap();
+        cx.update(|cx| {
+            let target_buffer = definition.target_buffer.read(cx);
+            assert_eq!(
+                target_buffer.file().unwrap().abs_path(),
+                Some(dir.path().join("a.rs"))
+            );
+            assert_eq!(definition.target_range.to_offset(target_buffer), 9..10);
+            assert_eq!(
+                list_worktrees(&project, cx),
+                [
+                    (dir.path().join("b.rs"), false),
+                    (dir.path().join("a.rs"), true)
+                ]
+            );
+
+            drop(definition);
+        });
+        cx.read(|cx| {
+            assert_eq!(
+                list_worktrees(&project, cx),
+                [(dir.path().join("b.rs"), false)]
+            );
+        });
+
+        fn list_worktrees(project: &ModelHandle<Project>, cx: &AppContext) -> Vec<(PathBuf, bool)> {
+            project
+                .read(cx)
+                .worktrees(cx)
+                .map(|worktree| {
+                    let worktree = worktree.read(cx);
+                    (
+                        worktree.as_local().unwrap().abs_path().to_path_buf(),
+                        worktree.is_weak(),
+                    )
+                })
+                .collect::<Vec<_>>()
+        }
+    }
+
     fn build_project(cx: &mut TestAppContext) -> ModelHandle<Project> {
         let languages = Arc::new(LanguageRegistry::new());
         let fs = Arc::new(RealFs);