Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-term/tests/test/helpers.rs')
| -rw-r--r-- | helix-term/tests/test/helpers.rs | 231 |
1 files changed, 68 insertions, 163 deletions
diff --git a/helix-term/tests/test/helpers.rs b/helix-term/tests/test/helpers.rs index 567422c3..2c5043d6 100644 --- a/helix-term/tests/test/helpers.rs +++ b/helix-term/tests/test/helpers.rs @@ -1,62 +1,18 @@ use std::{ + fs::File, io::{Read, Write}, - mem::replace, path::PathBuf, time::Duration, }; use anyhow::bail; +use crossterm::event::{Event, KeyEvent}; use helix_core::{diagnostic::Severity, test, Selection, Transaction}; -use helix_term::{application::Application, args::Args, config::Config, keymap::merge_keys}; -use helix_view::{current_ref, doc, editor::LspConfig, input::parse_macro, Editor}; +use helix_term::{application::Application, args::Args, config::Config}; +use helix_view::{doc, input::parse_macro, Editor}; use tempfile::NamedTempFile; use tokio_stream::wrappers::UnboundedReceiverStream; -#[cfg(windows)] -use crossterm::event::{Event, KeyEvent}; -#[cfg(not(windows))] -use termina::event::{Event, KeyEvent}; - -/// Specify how to set up the input text with line feeds -#[derive(Clone, Debug)] -pub enum LineFeedHandling { - /// Replaces all LF chars with the system's appropriate line feed character, - /// and if one doesn't exist already, appends the system's appropriate line - /// ending to the end of a string. - Native, - - /// Do not modify the input text in any way. What you give is what you test. - AsIs, -} - -impl LineFeedHandling { - /// Apply the line feed handling to the input string, yielding a set of - /// resulting texts with the appropriate line feed substitutions. - pub fn apply(&self, text: &str) -> String { - let line_end = match self { - LineFeedHandling::Native => helix_core::NATIVE_LINE_ENDING, - LineFeedHandling::AsIs => return text.into(), - } - .as_str(); - - // we can assume that the source files in this code base will always - // be LF, so indoc strings will always insert LF - let mut output = text.replace('\n', line_end); - - if !output.ends_with(line_end) { - output.push_str(line_end); - } - - output - } -} - -impl Default for LineFeedHandling { - fn default() -> Self { - Self::Native - } -} - #[derive(Clone, Debug)] pub struct TestCase { pub in_text: String, @@ -64,30 +20,12 @@ pub struct TestCase { pub in_keys: String, pub out_text: String, pub out_selection: Selection, - - pub line_feed_handling: LineFeedHandling, -} - -impl<S, R, V> From<(S, R, V)> for TestCase -where - S: Into<String>, - R: Into<String>, - V: Into<String>, -{ - fn from((input, keys, output): (S, R, V)) -> Self { - TestCase::from((input, keys, output, LineFeedHandling::default())) - } } -impl<S, R, V> From<(S, R, V, LineFeedHandling)> for TestCase -where - S: Into<String>, - R: Into<String>, - V: Into<String>, -{ - fn from((input, keys, output, line_feed_handling): (S, R, V, LineFeedHandling)) -> Self { - let (in_text, in_selection) = test::print(&line_feed_handling.apply(&input.into())); - let (out_text, out_selection) = test::print(&line_feed_handling.apply(&output.into())); +impl<S: Into<String>> From<(S, S, S)> for TestCase { + fn from((input, keys, output): (S, S, S)) -> Self { + let (in_text, in_selection) = test::print(&input.into()); + let (out_text, out_selection) = test::print(&output.into()); TestCase { in_text, @@ -95,7 +33,6 @@ where in_keys: keys.into(), out_text, out_selection, - line_feed_handling, } } } @@ -122,11 +59,6 @@ pub async fn test_key_sequences( let num_inputs = inputs.len(); for (i, (in_keys, test_fn)) in inputs.into_iter().enumerate() { - let (view, doc) = current_ref!(app.editor); - let state = test::plain(doc.text().slice(..), doc.selection(view.id)); - - log::debug!("executing test with document state:\n\n-----\n\n{}", state); - if let Some(in_keys) = in_keys { for key_event in parse_macro(in_keys)?.into_iter() { let key = Event::Key(KeyEvent::from(key_event)); @@ -137,16 +69,6 @@ pub async fn test_key_sequences( let app_exited = !app.event_loop_until_idle(&mut rx_stream).await; - if !app_exited { - let (view, doc) = current_ref!(app.editor); - let state = test::plain(doc.text().slice(..), doc.selection(view.id)); - - log::debug!( - "finished running test with document state:\n\n-----\n\n{}", - state - ); - } - // the app should not exit from any test until the last one if i < num_inputs - 1 && app_exited { bail!("application exited before test function could run"); @@ -194,10 +116,9 @@ pub async fn test_key_sequence_with_input_text<T: Into<TestCase>>( should_exit: bool, ) -> anyhow::Result<()> { let test_case = test_case.into(); - let mut app = match app { Some(app) => app, - None => Application::new(Args::default(), test_config(), test_syntax_loader(None))?, + None => Application::new(Args::default(), Config::default(), test_syntax_conf(None))?, }; let (view, doc) = helix_view::current!(app.editor); @@ -209,7 +130,7 @@ pub async fn test_key_sequence_with_input_text<T: Into<TestCase>>( }) .with_selection(test_case.in_selection.clone()); - doc.apply(&transaction, view.id); + helix_view::apply_transaction(&transaction, doc, view); test_key_sequence( &mut app, @@ -220,28 +141,48 @@ pub async fn test_key_sequence_with_input_text<T: Into<TestCase>>( .await } -/// Generates language config loader that merge in overrides, like a user language +/// Generates language configs that merge in overrides, like a user language /// config. The argument string must be a raw TOML document. -pub fn test_syntax_loader(overrides: Option<String>) -> helix_core::syntax::Loader { +/// +/// By default, language server configuration is dropped from the languages.toml +/// document. If a language-server is necessary for a test, it must be explicitly +/// added in `overrides`. +pub fn test_syntax_conf(overrides: Option<String>) -> helix_core::syntax::Configuration { let mut lang = helix_loader::config::default_lang_config(); + for lang_config in lang + .as_table_mut() + .expect("Expected languages.toml to be a table") + .get_mut("language") + .expect("Expected languages.toml to have \"language\" keys") + .as_array_mut() + .expect("Expected an array of language configurations") + { + lang_config + .as_table_mut() + .expect("Expected language config to be a TOML table") + .remove("language-server"); + } + if let Some(overrides) = overrides { let override_toml = toml::from_str(&overrides).unwrap(); lang = helix_loader::merge_toml_values(lang, override_toml, 3); } - helix_core::syntax::Loader::new(lang.try_into().unwrap()).unwrap() + lang.try_into().unwrap() } /// Use this for very simple test cases where there is one input /// document, selection, and sequence of key presses, and you just /// want to verify the resulting document and selection. pub async fn test_with_config<T: Into<TestCase>>( - app_builder: AppBuilder, + args: Args, + config: Config, + syn_conf: helix_core::syntax::Configuration, test_case: T, ) -> anyhow::Result<()> { let test_case = test_case.into(); - let app = app_builder.build()?; + let app = Application::new(args, config, syn_conf)?; test_key_sequence_with_input_text( Some(app), @@ -262,7 +203,13 @@ pub async fn test_with_config<T: Into<TestCase>>( } pub async fn test<T: Into<TestCase>>(test_case: T) -> anyhow::Result<()> { - test_with_config(AppBuilder::default(), test_case).await + test_with_config( + Args::default(), + Config::default(), + test_syntax_conf(None), + test_case, + ) + .await } pub fn temp_file_with_contents<S: AsRef<str>>( @@ -279,23 +226,21 @@ pub fn temp_file_with_contents<S: AsRef<str>>( Ok(temp_file) } -/// Generates a config with defaults more suitable for integration tests -pub fn test_config() -> Config { - Config { - editor: test_editor_config(), - keys: helix_term::keymap::default(), - ..Default::default() - } -} +/// Replaces all LF chars with the system's appropriate line feed +/// character, and if one doesn't exist already, appends the system's +/// appropriate line ending to the end of a string. +pub fn platform_line(input: &str) -> String { + let line_end = helix_core::DEFAULT_LINE_ENDING.as_str(); -pub fn test_editor_config() -> helix_view::editor::Config { - helix_view::editor::Config { - lsp: LspConfig { - enable: false, - ..Default::default() - }, - ..Default::default() + // we can assume that the source files in this code base will always + // be LF, so indoc strings will always insert LF + let mut output = input.replace('\n', line_end); + + if !output.ends_with(line_end) { + output.push_str(line_end); } + + output } /// Creates a new temporary file that is set to read only. Useful for @@ -309,22 +254,10 @@ pub fn new_readonly_tempfile() -> anyhow::Result<NamedTempFile> { Ok(file) } -/// Creates a new temporary file in the directory that is set to read only. Useful for -/// testing write failures. -pub fn new_readonly_tempfile_in_dir( - dir: impl AsRef<std::path::Path>, -) -> anyhow::Result<NamedTempFile> { - let mut file = tempfile::NamedTempFile::new_in(dir)?; - let metadata = file.as_file().metadata()?; - let mut perms = metadata.permissions(); - perms.set_readonly(true); - file.as_file_mut().set_permissions(perms)?; - Ok(file) -} pub struct AppBuilder { args: Args, config: Config, - syn_loader: helix_core::syntax::Loader, + syn_conf: helix_core::syntax::Configuration, input: Option<(String, Selection)>, } @@ -332,8 +265,8 @@ impl Default for AppBuilder { fn default() -> Self { Self { args: Args::default(), - config: test_config(), - syn_loader: test_syntax_loader(None), + config: Config::default(), + syn_conf: test_syntax_conf(None), input: None, } } @@ -349,18 +282,13 @@ impl AppBuilder { path: P, pos: Option<helix_core::Position>, ) -> Self { - self.args - .files - .insert(path.into(), vec![pos.unwrap_or_default()]); - + self.args.files.push((path.into(), pos.unwrap_or_default())); self } // Remove this attribute once `with_config` is used in a test: #[allow(dead_code)] - pub fn with_config(mut self, mut config: Config) -> Self { - let keys = replace(&mut config.keys, helix_term::keymap::default()); - merge_keys(&mut config.keys, keys); + pub fn with_config(mut self, config: Config) -> Self { self.config = config; self } @@ -370,21 +298,13 @@ impl AppBuilder { self } - pub fn with_lang_loader(mut self, syn_loader: helix_core::syntax::Loader) -> Self { - self.syn_loader = syn_loader; + pub fn with_lang_config(mut self, syn_conf: helix_core::syntax::Configuration) -> Self { + self.syn_conf = syn_conf; self } pub fn build(self) -> anyhow::Result<Application> { - if let Some(path) = &self.args.working_directory { - bail!("Changing the working directory to {path:?} is not yet supported for integration tests"); - } - - if let Some((path, _)) = self.args.files.first().filter(|p| p.0.is_dir()) { - bail!("Having the directory {path:?} in args.files[0] is not yet supported for integration tests"); - } - - let mut app = Application::new(self.args, self.config, self.syn_loader)?; + let mut app = Application::new(self.args, self.config, self.syn_conf)?; if let Some((text, selection)) = self.input { let (view, doc) = helix_view::current!(app.editor); @@ -395,25 +315,20 @@ impl AppBuilder { .with_selection(selection); // replace the initial text with the input text - doc.apply(&trans, view.id); + helix_view::apply_transaction(&trans, doc, view); } Ok(app) } } -pub async fn run_event_loop_until_idle(app: &mut Application) { - let (_, rx) = tokio::sync::mpsc::unbounded_channel(); - let mut rx_stream = UnboundedReceiverStream::new(rx); - app.event_loop_until_idle(&mut rx_stream).await; -} - -pub fn assert_file_has_content(file: &mut NamedTempFile, content: &str) -> anyhow::Result<()> { - reload_file(file)?; +pub fn assert_file_has_content(file: &mut File, content: &str) -> anyhow::Result<()> { + file.flush()?; + file.sync_all()?; let mut file_content = String::new(); file.read_to_string(&mut file_content)?; - assert_eq!(file_content, content); + assert_eq!(content, file_content); Ok(()) } @@ -423,13 +338,3 @@ pub fn assert_status_not_error(editor: &Editor) { assert_ne!(&Severity::Error, sev); } } - -pub fn reload_file(file: &mut NamedTempFile) -> anyhow::Result<()> { - let path = file.path(); - let f = std::fs::OpenOptions::new() - .write(true) - .read(true) - .open(&path)?; - *file.as_file_mut() = f; - Ok(()) -} |