Make the first prettier test pass

Kirill Bulatov created

Change summary

crates/editor/src/editor_tests.rs       |  27 ++----
crates/node_runtime/src/node_runtime.rs | 107 ++++++++++++++++++++++++--
crates/prettier/src/prettier.rs         |   5 +
crates/project/src/project.rs           |  14 +++
4 files changed, 124 insertions(+), 29 deletions(-)

Detailed changes

crates/editor/src/editor_tests.rs 🔗

@@ -7830,11 +7830,12 @@ async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
         Some(tree_sitter_rust::language()),
     );
 
+    let test_plugin = "test_plugin";
     let _ = language
         .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
             enabled_formatters: vec![BundledFormatter::Prettier {
                 parser_name: Some("test_parser"),
-                plugin_names: vec!["test_plugin"],
+                plugin_names: vec![test_plugin],
             }],
             ..Default::default()
         }))
@@ -7843,37 +7844,31 @@ async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
     let fs = FakeFs::new(cx.background());
     fs.insert_file("/file.rs", Default::default()).await;
 
-    // TODO kb have to specify some test node runtime
     let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
-    project.update(cx, |project, _| project.languages().add(Arc::new(language)));
+    let prettier_format_suffix = project.update(cx, |project, _| {
+        let suffix = project.enable_test_prettier(&[test_plugin]);
+        project.languages().add(Arc::new(language));
+        suffix
+    });
     let buffer = project
         .update(cx, |project, cx| project.open_local_buffer("/file.rs", cx))
         .await
         .unwrap();
 
+    let buffer_text = "one\ntwo\nthree\n";
     let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
     let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
-    editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
+    editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
 
     let format = editor.update(cx, |editor, cx| {
         editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
     });
     format.await.unwrap();
-    assert_eq!(
-        editor.read_with(cx, |editor, cx| editor.text(cx)),
-        "one, two\nthree\n"
-    );
 
-    editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
-    let format = editor.update(cx, |editor, cx| {
-        editor.perform_format(project, FormatTrigger::Manual, cx)
-    });
-    cx.foreground().advance_clock(super::FORMAT_TIMEOUT);
-    cx.foreground().start_waiting();
-    format.await.unwrap();
     assert_eq!(
         editor.read_with(cx, |editor, cx| editor.text(cx)),
-        "one\ntwo\nthree\n"
+        buffer_text.to_string() + prettier_format_suffix,
+        "Test prettier formatting was not applied to the original buffer text",
     );
 }
 

crates/node_runtime/src/node_runtime.rs 🔗

@@ -220,18 +220,83 @@ impl NodeRuntime for RealNodeRuntime {
     }
 }
 
-pub struct FakeNodeRuntime;
+pub struct FakeNodeRuntime(Option<PrettierSupport>);
+
+struct PrettierSupport {
+    plugins: Vec<&'static str>,
+}
 
 impl FakeNodeRuntime {
     pub fn new() -> Arc<dyn NodeRuntime> {
-        Arc::new(FakeNodeRuntime)
+        Arc::new(FakeNodeRuntime(None))
+    }
+
+    pub fn with_prettier_support(plugins: &[&'static str]) -> Arc<dyn NodeRuntime> {
+        Arc::new(FakeNodeRuntime(Some(PrettierSupport::new(plugins))))
     }
 }
 
 #[async_trait::async_trait]
 impl NodeRuntime for FakeNodeRuntime {
     async fn binary_path(&self) -> anyhow::Result<PathBuf> {
-        // TODO kb move away into a separate type + a Project's setter (for test code)
+        if let Some(prettier_support) = &self.0 {
+            prettier_support.binary_path().await
+        } else {
+            unreachable!()
+        }
+    }
+
+    async fn run_npm_subcommand(
+        &self,
+        directory: Option<&Path>,
+        subcommand: &str,
+        args: &[&str],
+    ) -> anyhow::Result<Output> {
+        if let Some(prettier_support) = &self.0 {
+            prettier_support
+                .run_npm_subcommand(directory, subcommand, args)
+                .await
+        } else {
+            unreachable!()
+        }
+    }
+
+    async fn npm_package_latest_version(&self, name: &str) -> anyhow::Result<String> {
+        if let Some(prettier_support) = &self.0 {
+            prettier_support.npm_package_latest_version(name).await
+        } else {
+            unreachable!()
+        }
+    }
+
+    async fn npm_install_packages(
+        &self,
+        directory: &Path,
+        packages: &[(&str, &str)],
+    ) -> anyhow::Result<()> {
+        if let Some(prettier_support) = &self.0 {
+            prettier_support
+                .npm_install_packages(directory, packages)
+                .await
+        } else {
+            unreachable!()
+        }
+    }
+}
+
+impl PrettierSupport {
+    const PACKAGE_VERSION: &str = "0.0.1";
+
+    fn new(plugins: &[&'static str]) -> Self {
+        Self {
+            plugins: plugins.to_vec(),
+        }
+    }
+}
+
+#[async_trait::async_trait]
+impl NodeRuntime for PrettierSupport {
+    async fn binary_path(&self) -> anyhow::Result<PathBuf> {
         Ok(PathBuf::from("prettier_fake_node"))
     }
 
@@ -240,10 +305,10 @@ impl NodeRuntime for FakeNodeRuntime {
     }
 
     async fn npm_package_latest_version(&self, name: &str) -> anyhow::Result<String> {
-        if name == "prettier" {
-            Ok("0.0.1".to_string())
+        if name == "prettier" || self.plugins.contains(&name) {
+            Ok(Self::PACKAGE_VERSION.to_string())
         } else {
-            unreachable!("Unexpected package name: {name}")
+            panic!("Unexpected package name: {name}")
         }
     }
 
@@ -252,10 +317,32 @@ impl NodeRuntime for FakeNodeRuntime {
         _: &Path,
         packages: &[(&str, &str)],
     ) -> anyhow::Result<()> {
-        if packages == [("prettier", "0.0.1")] {
-            Ok(())
-        } else {
-            unreachable!("Unexpected packages to install: {packages:?}")
+        assert_eq!(
+            packages.len(),
+            self.plugins.len() + 1,
+            "Unexpected packages length to install: {:?}, expected `prettier` + {:?}",
+            packages,
+            self.plugins
+        );
+        for (name, version) in packages {
+            assert!(
+                name == &"prettier" || self.plugins.contains(name),
+                "Unexpected package `{}` to install in packages {:?}, expected {} for `prettier` + {:?}",
+                name,
+                packages,
+                Self::PACKAGE_VERSION,
+                self.plugins
+            );
+            assert_eq!(
+                version,
+                &Self::PACKAGE_VERSION,
+                "Unexpected package version `{}` to install in packages {:?}, expected {} for `prettier` + {:?}",
+                version,
+                packages,
+                Self::PACKAGE_VERSION,
+                self.plugins
+            );
         }
+        Ok(())
     }
 }

crates/prettier/src/prettier.rs 🔗

@@ -71,6 +71,9 @@ impl Prettier {
         ".editorconfig",
     ];
 
+    #[cfg(any(test, feature = "test-support"))]
+    pub const FORMAT_SUFFIX: &str = "\nformatted by test prettier";
+
     pub fn remote(
         project_id: u64,
         worktree_id: Option<usize>,
@@ -433,7 +436,7 @@ impl Prettier {
             #[cfg(any(test, feature = "test-support"))]
             Self::Test(_) => Ok(buffer
                 .read_with(cx, |buffer, cx| {
-                    let formatted_text = buffer.text() + "\nformatted by test prettier";
+                    let formatted_text = buffer.text() + Self::FORMAT_SUFFIX;
                     buffer.diff(formatted_text, cx)
                 })
                 .await),

crates/project/src/project.rs 🔗

@@ -837,6 +837,16 @@ impl Project {
         project
     }
 
+    /// Enables a prettier mock that avoids interacting with node runtime, prettier LSP wrapper, or any real file changes.
+    /// Instead, if appends the suffix to every input, this suffix is returned by this method.
+    #[cfg(any(test, feature = "test-support"))]
+    pub fn enable_test_prettier(&mut self, plugins: &[&'static str]) -> &'static str {
+        self.node = Some(node_runtime::FakeNodeRuntime::with_prettier_support(
+            plugins,
+        ));
+        Prettier::FORMAT_SUFFIX
+    }
+
     fn on_settings_changed(&mut self, cx: &mut ModelContext<Self>) {
         let mut language_servers_to_start = Vec::new();
         let mut language_formatters_to_check = Vec::new();
@@ -8442,7 +8452,7 @@ impl Project {
                     return Some(existing_prettier);
                 }
 
-                log::info!("Found prettier at {prettier_dir:?}, starting.");
+                log::info!("Found prettier in {prettier_dir:?}, starting.");
                 let task_prettier_dir = prettier_dir.clone();
                 let weak_project = this.downgrade();
                 let new_server_id =
@@ -8459,7 +8469,7 @@ impl Project {
                         .await
                         .context("prettier start")
                         .map_err(Arc::new)?;
-                        log::info!("Had started prettier in {:?}", prettier.prettier_dir());
+                        log::info!("Started prettier in {:?}", prettier.prettier_dir());
 
                         if let Some((project, prettier_server)) =
                             weak_project.upgrade(&mut cx).zip(prettier.server())