diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index ddae186679..4cb0c009bb 100644 --- a/crates/editor/src/editor_tests.rs +++ b/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", ); } diff --git a/crates/node_runtime/src/node_runtime.rs b/crates/node_runtime/src/node_runtime.rs index 125318dd67..dcb8833f8c 100644 --- a/crates/node_runtime/src/node_runtime.rs +++ b/crates/node_runtime/src/node_runtime.rs @@ -220,18 +220,83 @@ impl NodeRuntime for RealNodeRuntime { } } -pub struct FakeNodeRuntime; +pub struct FakeNodeRuntime(Option); + +struct PrettierSupport { + plugins: Vec<&'static str>, +} impl FakeNodeRuntime { pub fn new() -> Arc { - Arc::new(FakeNodeRuntime) + Arc::new(FakeNodeRuntime(None)) + } + + pub fn with_prettier_support(plugins: &[&'static str]) -> Arc { + Arc::new(FakeNodeRuntime(Some(PrettierSupport::new(plugins)))) } } #[async_trait::async_trait] impl NodeRuntime for FakeNodeRuntime { async fn binary_path(&self) -> anyhow::Result { - // 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 { + 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 { + 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 { 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 { - 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(()) } } diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 6c1d0665e2..656c84b46c 100644 --- a/crates/prettier/src/prettier.rs +++ b/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, @@ -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), diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index ebc2aaaf60..e31ecca257 100644 --- a/crates/project/src/project.rs +++ b/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) { 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())