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.rs231
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(())
-}