From 1503f5766d268ec83edf61145acd1bb34dc996bc Mon Sep 17 00:00:00 2001 From: Mikael Mello Date: Tue, 26 Dec 2023 22:39:02 -0800 Subject: [PATCH] Simplify creation of fake backend for tests (#206) * Simplify creation of fake backend for tests * Remove test temporarily --- inquire/src/prompts/dateselect/test.rs | 26 +-- inquire/src/prompts/mod.rs | 2 + inquire/src/prompts/multiselect/test.rs | 138 ++++----------- inquire/src/prompts/password/test.rs | 172 +++++++++---------- inquire/src/prompts/select/test.rs | 126 ++++---------- inquire/src/prompts/test.rs | 18 ++ inquire/src/prompts/text/test.rs | 87 +++++----- inquire/src/terminal/crossterm.rs | 214 ++++++++++++++---------- 8 files changed, 345 insertions(+), 438 deletions(-) create mode 100644 inquire/src/prompts/test.rs diff --git a/inquire/src/prompts/dateselect/test.rs b/inquire/src/prompts/dateselect/test.rs index 4ec6ef59..76d1d15c 100644 --- a/inquire/src/prompts/dateselect/test.rs +++ b/inquire/src/prompts/dateselect/test.rs @@ -1,12 +1,11 @@ use crate::{ date_utils::get_current_date, - terminal::crossterm::CrosstermTerminal, - ui::{Backend, RenderConfig}, + test::fake_backend, + ui::{Key, KeyModifiers}, validator::Validation, DateSelect, }; use chrono::NaiveDate; -use crossterm::event::{KeyCode, KeyEvent}; fn default<'a>() -> DateSelect<'a> { DateSelect::new("Question?") @@ -20,12 +19,7 @@ macro_rules! date_test { ($name:ident,$input:expr,$output:expr,$prompt:expr) => { #[test] fn $name() { - let read: Vec = $input.into_iter().map(KeyEvent::from).collect(); - let mut read = read.iter(); - - let mut write: Vec = Vec::new(); - let terminal = CrosstermTerminal::new_with_io(&mut write, &mut read); - let mut backend = Backend::new(terminal, RenderConfig::default()).unwrap(); + let mut backend = fake_backend($input); let ans = $prompt.prompt_with_backend(&mut backend).unwrap(); @@ -34,11 +28,11 @@ macro_rules! date_test { }; } -date_test!(today_date, vec![KeyCode::Enter], get_current_date()); +date_test!(today_date, vec![Key::Enter], get_current_date()); date_test!( custom_default_date, - vec![KeyCode::Enter], + vec![Key::Enter], NaiveDate::from_ymd_opt(2021, 1, 9).unwrap(), DateSelect::new("Date").with_default(NaiveDate::from_ymd_opt(2021, 1, 9).unwrap()) ); @@ -47,11 +41,7 @@ date_test!( /// Tests that a closure that actually closes on a variable can be used /// as a DateSelect validator. fn closure_validator() { - let read: Vec = vec![KeyCode::Enter, KeyCode::Left, KeyCode::Enter] - .into_iter() - .map(KeyEvent::from) - .collect(); - let mut read = read.iter(); + let mut backend = fake_backend(vec![Key::Enter, Key::Left(KeyModifiers::NONE), Key::Enter]); let today_date = get_current_date(); @@ -63,10 +53,6 @@ fn closure_validator() { } }; - let mut write: Vec = Vec::new(); - let terminal = CrosstermTerminal::new_with_io(&mut write, &mut read); - let mut backend = Backend::new(terminal, RenderConfig::default()).unwrap(); - let ans = DateSelect::new("Question") .with_validator(validator) .prompt_with_backend(&mut backend) diff --git a/inquire/src/prompts/mod.rs b/inquire/src/prompts/mod.rs index 9c6b600c..1475629a 100644 --- a/inquire/src/prompts/mod.rs +++ b/inquire/src/prompts/mod.rs @@ -10,6 +10,8 @@ mod one_liners; mod password; mod prompt; mod select; +#[cfg(test)] +pub(crate) mod test; mod text; pub use action::*; diff --git a/inquire/src/prompts/multiselect/test.rs b/inquire/src/prompts/multiselect/test.rs index 6a60800e..8c5bd098 100644 --- a/inquire/src/prompts/multiselect/test.rs +++ b/inquire/src/prompts/multiselect/test.rs @@ -1,31 +1,22 @@ use crate::{ formatter::MultiOptionFormatter, list_option::ListOption, - terminal::crossterm::CrosstermTerminal, - ui::{Backend, RenderConfig}, + test::fake_backend, + ui::{Key, KeyModifiers}, MultiSelect, }; -use crossterm::event::{KeyCode, KeyEvent}; #[test] /// Tests that a closure that actually closes on a variable can be used /// as a Select formatter. fn closure_formatter() { - let read: Vec = vec![KeyCode::Char(' '), KeyCode::Enter] - .into_iter() - .map(KeyEvent::from) - .collect(); - let mut read = read.iter(); + let mut backend = fake_backend(vec![Key::Char(' ', KeyModifiers::NONE), Key::Enter]); let formatted = String::from("Thanks!"); let formatter: MultiOptionFormatter<'_, i32> = &|_| formatted.clone(); let options = vec![1, 2, 3]; - let mut write: Vec = Vec::new(); - let terminal = CrosstermTerminal::new_with_io(&mut write, &mut read); - let mut backend = Backend::new(terminal, RenderConfig::default()).unwrap(); - let ans = MultiSelect::new("Question", options) .with_formatter(formatter) .prompt_with_backend(&mut backend) @@ -37,27 +28,18 @@ fn closure_formatter() { #[test] // Anti-regression test: https://github.com/mikaelmello/inquire/issues/30 fn down_arrow_on_empty_list_does_not_panic() { - let read: Vec = [ - KeyCode::Char('9'), - KeyCode::Down, - KeyCode::Backspace, - KeyCode::Char('3'), - KeyCode::Down, - KeyCode::Backspace, - KeyCode::Enter, - ] - .iter() - .map(|c| KeyEvent::from(*c)) - .collect(); - - let mut read = read.iter(); + let mut backend = fake_backend(vec![ + Key::Char('9', KeyModifiers::NONE), + Key::Down(KeyModifiers::NONE), + Key::Backspace, + Key::Char('3', KeyModifiers::NONE), + Key::Down(KeyModifiers::NONE), + Key::Backspace, + Key::Enter, + ]); let options = vec![1, 2, 3]; - let mut write: Vec = Vec::new(); - let terminal = CrosstermTerminal::new_with_io(&mut write, &mut read); - let mut backend = Backend::new(terminal, RenderConfig::default()).unwrap(); - let ans = MultiSelect::new("Question", options) .prompt_with_backend(&mut backend) .unwrap(); @@ -67,19 +49,9 @@ fn down_arrow_on_empty_list_does_not_panic() { #[test] fn selecting_all_by_default_behavior() { - let read: Vec = [KeyCode::Enter, KeyCode::Enter] - .iter() - .map(|c| KeyEvent::from(*c)) - .collect(); - - let mut read = read.iter(); - + let mut backend = fake_backend(vec![Key::Enter, Key::Enter]); let options = vec![1, 2, 3]; - let mut write: Vec = Vec::new(); - let terminal = CrosstermTerminal::new_with_io(&mut write, &mut read); - let mut backend = Backend::new(terminal, RenderConfig::default()).unwrap(); - let answer_with_all_selected_by_default = MultiSelect::new("Question", options.clone()) .with_all_selected_by_default() .prompt_with_backend(&mut backend) @@ -105,24 +77,16 @@ fn selecting_all_by_default_behavior() { #[test] // Anti-regression test: https://github.com/mikaelmello/inquire/issues/31 fn list_option_indexes_are_relative_to_input_vec() { - let read: Vec = vec![ - KeyCode::Down, - KeyCode::Char(' '), - KeyCode::Down, - KeyCode::Char(' '), - KeyCode::Enter, - ] - .into_iter() - .map(KeyEvent::from) - .collect(); - let mut read = read.iter(); + let mut backend = fake_backend(vec![ + Key::Down(KeyModifiers::NONE), + Key::Char(' ', KeyModifiers::NONE), + Key::Down(KeyModifiers::NONE), + Key::Char(' ', KeyModifiers::NONE), + Key::Enter, + ]); let options = vec![1, 2, 3]; - let mut write: Vec = Vec::new(); - let terminal = CrosstermTerminal::new_with_io(&mut write, &mut read); - let mut backend = Backend::new(terminal, RenderConfig::default()).unwrap(); - let ans = MultiSelect::new("Question", options) .prompt_with_backend(&mut backend) .unwrap(); @@ -133,19 +97,9 @@ fn list_option_indexes_are_relative_to_input_vec() { #[test] // Anti-regression test: https://github.com/mikaelmello/inquire/issues/195 fn starting_cursor_is_respected() { - let read: Vec = [KeyCode::Char(' '), KeyCode::Enter] - .iter() - .map(|c| KeyEvent::from(*c)) - .collect(); - - let mut read = read.iter(); - + let mut backend = fake_backend(vec![Key::Char(' ', KeyModifiers::NONE), Key::Enter]); let options = vec![1, 2, 3]; - let mut write: Vec = Vec::new(); - let terminal = CrosstermTerminal::new_with_io(&mut write, &mut read); - let mut backend = Backend::new(terminal, RenderConfig::default()).unwrap(); - let ans = MultiSelect::new("Question", options) .with_starting_cursor(2) .prompt_with_backend(&mut backend) @@ -156,19 +110,14 @@ fn starting_cursor_is_respected() { #[test] fn naive_assert_fuzzy_match_as_default_scorer() { - let read: Vec = [ - KeyCode::Char('w'), - KeyCode::Char('r'), - KeyCode::Char('r'), - KeyCode::Char('y'), - KeyCode::Char(' '), - KeyCode::Enter, - ] - .iter() - .map(|c| KeyEvent::from(*c)) - .collect(); - - let mut read = read.iter(); + let mut backend = fake_backend(vec![ + Key::Char('w', KeyModifiers::NONE), + Key::Char('r', KeyModifiers::NONE), + Key::Char('r', KeyModifiers::NONE), + Key::Char('y', KeyModifiers::NONE), + Key::Char(' ', KeyModifiers::NONE), + Key::Enter, + ]); let options = vec![ "Banana", @@ -184,10 +133,6 @@ fn naive_assert_fuzzy_match_as_default_scorer() { "Pineapple", ]; - let mut write: Vec = Vec::new(); - let terminal = CrosstermTerminal::new_with_io(&mut write, &mut read); - let mut backend = Backend::new(terminal, RenderConfig::default()).unwrap(); - let ans = MultiSelect::new("Question", options) .prompt_with_backend(&mut backend) .unwrap(); @@ -197,19 +142,14 @@ fn naive_assert_fuzzy_match_as_default_scorer() { #[test] fn chars_do_not_affect_prompt_without_filtering() { - let read: Vec = [ - KeyCode::Char('w'), - KeyCode::Char('r'), - KeyCode::Char('r'), - KeyCode::Char('y'), - KeyCode::Char(' '), - KeyCode::Enter, - ] - .iter() - .map(|c| KeyEvent::from(*c)) - .collect(); - - let mut read = read.iter(); + let mut backend = fake_backend(vec![ + Key::Char('w', KeyModifiers::NONE), + Key::Char('r', KeyModifiers::NONE), + Key::Char('r', KeyModifiers::NONE), + Key::Char('y', KeyModifiers::NONE), + Key::Char(' ', KeyModifiers::NONE), + Key::Enter, + ]); let options = vec![ "Banana", @@ -225,10 +165,6 @@ fn chars_do_not_affect_prompt_without_filtering() { "Pineapple", ]; - let mut write: Vec = Vec::new(); - let terminal = CrosstermTerminal::new_with_io(&mut write, &mut read); - let mut backend = Backend::new(terminal, RenderConfig::default()).unwrap(); - let ans = MultiSelect::new("Question", options) .without_filtering() .prompt_with_backend(&mut backend) diff --git a/inquire/src/prompts/password/test.rs b/inquire/src/prompts/password/test.rs index 494d2abc..cfad798a 100644 --- a/inquire/src/prompts/password/test.rs +++ b/inquire/src/prompts/password/test.rs @@ -1,14 +1,13 @@ use super::Password; -use crate::{ - terminal::crossterm::CrosstermTerminal, - ui::{Backend, RenderConfig}, - validator::{ErrorMessage, Validation}, -}; -use crossterm::event::{KeyCode, KeyEvent}; +use crate::ui::{Key, KeyModifiers}; +use crate::validator::{ErrorMessage, Validation}; macro_rules! text_to_events { ($text:expr) => {{ - $text.chars().map(KeyCode::Char) + $text + .chars() + .map(|c| Key::Char(c, KeyModifiers::NONE)) + .collect() }}; } @@ -17,12 +16,7 @@ macro_rules! password_test { #[test] $(#[$meta])? fn $name() { - let read: Vec = $input.into_iter().map(KeyEvent::from).collect(); - let mut read = read.iter(); - - let mut write: Vec = Vec::new(); - let terminal = CrosstermTerminal::new_with_io(&mut write, &mut read); - let mut backend = Backend::new(terminal, RenderConfig::default()).unwrap(); + let mut backend = crate::prompts::test::fake_backend($input); let ans = $prompt.prompt_with_backend(&mut backend).unwrap(); @@ -33,14 +27,14 @@ macro_rules! password_test { password_test!( empty, - vec![KeyCode::Enter], + vec![Key::Enter], "", Password::new("").without_confirmation() ); password_test!( single_letter, - vec![KeyCode::Char('b'), KeyCode::Enter], + vec![Key::Char('b', KeyModifiers::NONE), Key::Enter], "b", Password::new("").without_confirmation() ); @@ -63,13 +57,13 @@ password_test!( input_and_correction, { let mut events = vec![]; - events.append(&mut text_to_events!("anor").collect()); - events.push(KeyCode::Backspace); - events.push(KeyCode::Backspace); - events.push(KeyCode::Backspace); - events.push(KeyCode::Backspace); - events.append(&mut text_to_events!("normal input").collect()); - events.push(KeyCode::Enter); + events.append(&mut text_to_events!("anor")); + events.push(Key::Backspace); + events.push(Key::Backspace); + events.push(Key::Backspace); + events.push(Key::Backspace); + events.append(&mut text_to_events!("normal input")); + events.push(Key::Enter); events }, "normal input", @@ -80,19 +74,19 @@ password_test!( input_and_excessive_correction, { let mut events = vec![]; - events.append(&mut text_to_events!("anor").collect()); - events.push(KeyCode::Backspace); - events.push(KeyCode::Backspace); - events.push(KeyCode::Backspace); - events.push(KeyCode::Backspace); - events.push(KeyCode::Backspace); - events.push(KeyCode::Backspace); - events.push(KeyCode::Backspace); - events.push(KeyCode::Backspace); - events.push(KeyCode::Backspace); - events.push(KeyCode::Backspace); - events.append(&mut text_to_events!("normal input").collect()); - events.push(KeyCode::Enter); + events.append(&mut text_to_events!("anor")); + events.push(Key::Backspace); + events.push(Key::Backspace); + events.push(Key::Backspace); + events.push(Key::Backspace); + events.push(Key::Backspace); + events.push(Key::Backspace); + events.push(Key::Backspace); + events.push(Key::Backspace); + events.push(Key::Backspace); + events.push(Key::Backspace); + events.append(&mut text_to_events!("normal input")); + events.push(Key::Enter); events }, "normal input", @@ -103,15 +97,15 @@ password_test!( input_correction_after_validation_when_masked, { let mut events = vec![]; - events.append(&mut text_to_events!("1234567890").collect()); - events.push(KeyCode::Enter); - events.push(KeyCode::Backspace); - events.push(KeyCode::Backspace); - events.push(KeyCode::Backspace); - events.push(KeyCode::Backspace); - events.push(KeyCode::Backspace); - events.append(&mut text_to_events!("yes").collect()); - events.push(KeyCode::Enter); + events.append(&mut text_to_events!("1234567890")); + events.push(Key::Enter); + events.push(Key::Backspace); + events.push(Key::Backspace); + events.push(Key::Backspace); + events.push(Key::Backspace); + events.push(Key::Backspace); + events.append(&mut text_to_events!("yes")); + events.push(Key::Enter); events }, "12345yes", @@ -128,15 +122,15 @@ password_test!( input_correction_after_validation_when_full, { let mut events = vec![]; - events.append(&mut text_to_events!("1234567890").collect()); - events.push(KeyCode::Enter); - events.push(KeyCode::Backspace); - events.push(KeyCode::Backspace); - events.push(KeyCode::Backspace); - events.push(KeyCode::Backspace); - events.push(KeyCode::Backspace); - events.append(&mut text_to_events!("yes").collect()); - events.push(KeyCode::Enter); + events.append(&mut text_to_events!("1234567890")); + events.push(Key::Enter); + events.push(Key::Backspace); + events.push(Key::Backspace); + events.push(Key::Backspace); + events.push(Key::Backspace); + events.push(Key::Backspace); + events.append(&mut text_to_events!("yes")); + events.push(Key::Enter); events }, "12345yes", @@ -153,10 +147,10 @@ password_test!( input_correction_after_validation_when_hidden, { let mut events = vec![]; - events.append(&mut text_to_events!("1234567890").collect()); - events.push(KeyCode::Enter); - events.append(&mut text_to_events!("yesyes").collect()); - events.push(KeyCode::Enter); + events.append(&mut text_to_events!("1234567890")); + events.push(Key::Enter); + events.append(&mut text_to_events!("yesyes")); + events.push(Key::Enter); events }, "yesyes", @@ -173,10 +167,10 @@ password_test!( input_confirmation_same, { let mut events = vec![]; - events.append(&mut text_to_events!("1234567890").collect()); - events.push(KeyCode::Enter); - events.append(&mut text_to_events!("1234567890").collect()); - events.push(KeyCode::Enter); + events.append(&mut text_to_events!("1234567890")); + events.push(Key::Enter); + events.append(&mut text_to_events!("1234567890")); + events.push(Key::Enter); events }, "1234567890", @@ -188,10 +182,10 @@ password_test!( input_confirmation_different, { let mut events = vec![]; - events.append(&mut text_to_events!("1234567890").collect()); - events.push(KeyCode::Enter); - events.append(&mut text_to_events!("abcdefghij").collect()); - events.push(KeyCode::Enter); + events.append(&mut text_to_events!("1234567890")); + events.push(Key::Enter); + events.append(&mut text_to_events!("abcdefghij")); + events.push(Key::Enter); events }, "", @@ -203,16 +197,16 @@ password_test!( prompt_with_hidden_should_clear_on_mismatch, { let mut events = vec![]; - events.append(&mut text_to_events!("anor").collect()); - events.push(KeyCode::Enter); - events.append(&mut text_to_events!("anor2").collect()); - events.push(KeyCode::Enter); + events.append(&mut text_to_events!("anor")); + events.push(Key::Enter); + events.append(&mut text_to_events!("anor2")); + events.push(Key::Enter); // The problem is that the 1st input values were not cleared // and the lack of a change in the 1st prompt can be confusing. - events.append(&mut text_to_events!("anor").collect()); - events.push(KeyCode::Enter); - events.append(&mut text_to_events!("anor").collect()); - events.push(KeyCode::Enter); + events.append(&mut text_to_events!("anor")); + events.push(Key::Enter); + events.append(&mut text_to_events!("anor")); + events.push(Key::Enter); events }, "anor", @@ -224,16 +218,16 @@ password_test!( prompt_with_full_should_clear_1st_on_mismatch, { let mut events = vec![]; - events.append(&mut text_to_events!("anor").collect()); - events.push(KeyCode::Enter); - events.append(&mut text_to_events!("anor2").collect()); - events.push(KeyCode::Enter); + events.append(&mut text_to_events!("anor")); + events.push(Key::Enter); + events.append(&mut text_to_events!("anor2")); + events.push(Key::Enter); // The problem is that the 1st input values were not cleared // and the lack of a change in the 1st prompt can be confusing. - events.append(&mut text_to_events!("anor").collect()); - events.push(KeyCode::Enter); - events.append(&mut text_to_events!("anor").collect()); - events.push(KeyCode::Enter); + events.append(&mut text_to_events!("anor")); + events.push(Key::Enter); + events.append(&mut text_to_events!("anor")); + events.push(Key::Enter); events }, "anor", @@ -245,16 +239,16 @@ password_test!( prompt_with_masked_should_clear_1st_on_mismatch, { let mut events = vec![]; - events.append(&mut text_to_events!("anor").collect()); - events.push(KeyCode::Enter); - events.append(&mut text_to_events!("anor2").collect()); - events.push(KeyCode::Enter); + events.append(&mut text_to_events!("anor")); + events.push(Key::Enter); + events.append(&mut text_to_events!("anor2")); + events.push(Key::Enter); // The problem is that the 1st input values were not cleared // and the lack of a change in the 1st prompt can be confusing. - events.append(&mut text_to_events!("anor").collect()); - events.push(KeyCode::Enter); - events.append(&mut text_to_events!("anor").collect()); - events.push(KeyCode::Enter); + events.append(&mut text_to_events!("anor")); + events.push(Key::Enter); + events.append(&mut text_to_events!("anor")); + events.push(Key::Enter); events }, "anor", diff --git a/inquire/src/prompts/select/test.rs b/inquire/src/prompts/select/test.rs index c3d2b29e..8b00e387 100644 --- a/inquire/src/prompts/select/test.rs +++ b/inquire/src/prompts/select/test.rs @@ -1,31 +1,20 @@ use crate::{ formatter::OptionFormatter, list_option::ListOption, - terminal::crossterm::CrosstermTerminal, - ui::{Backend, RenderConfig}, + test::fake_backend, + ui::{Key, KeyModifiers}, Select, }; -use crossterm::event::{KeyCode, KeyEvent}; #[test] /// Tests that a closure that actually closes on a variable can be used /// as a Select formatter. fn closure_formatter() { - let read: Vec = vec![KeyCode::Down, KeyCode::Enter] - .into_iter() - .map(KeyEvent::from) - .collect(); - let mut read = read.iter(); - - let formatted = String::from("Thanks!"); - let formatter: OptionFormatter<'_, i32> = &|_| formatted.clone(); + let mut backend = fake_backend(vec![Key::Down(KeyModifiers::NONE), Key::Enter]); + let formatter: OptionFormatter<'_, i32> = &|_| String::from("Thanks!"); let options = vec![1, 2, 3]; - let mut write: Vec = Vec::new(); - let terminal = CrosstermTerminal::new_with_io(&mut write, &mut read); - let mut backend = Backend::new(terminal, RenderConfig::default()).unwrap(); - let ans = Select::new("Question", options) .with_formatter(formatter) .prompt_with_backend(&mut backend) @@ -37,25 +26,16 @@ fn closure_formatter() { #[test] // Anti-regression test: https://github.com/mikaelmello/inquire/issues/29 fn enter_arrow_on_empty_list_does_not_panic() { - let read: Vec = [ - KeyCode::Char('9'), - KeyCode::Enter, - KeyCode::Backspace, - KeyCode::Char('3'), - KeyCode::Enter, - ] - .iter() - .map(|c| KeyEvent::from(*c)) - .collect(); - - let mut read = read.iter(); + let mut backend = fake_backend(vec![ + Key::Char('9', KeyModifiers::NONE), + Key::Enter, + Key::Backspace, + Key::Char('3', KeyModifiers::NONE), + Key::Enter, + ]); let options = vec![1, 2, 3]; - let mut write: Vec = Vec::new(); - let terminal = CrosstermTerminal::new_with_io(&mut write, &mut read); - let mut backend = Backend::new(terminal, RenderConfig::default()).unwrap(); - let ans = Select::new("Question", options) .prompt_with_backend(&mut backend) .unwrap(); @@ -66,27 +46,18 @@ fn enter_arrow_on_empty_list_does_not_panic() { #[test] // Anti-regression test: https://github.com/mikaelmello/inquire/issues/30 fn down_arrow_on_empty_list_does_not_panic() { - let read: Vec = [ - KeyCode::Char('9'), - KeyCode::Down, - KeyCode::Backspace, - KeyCode::Char('3'), - KeyCode::Down, - KeyCode::Backspace, - KeyCode::Enter, - ] - .iter() - .map(|c| KeyEvent::from(*c)) - .collect(); - - let mut read = read.iter(); + let mut backend = fake_backend(vec![ + Key::Char('9', KeyModifiers::NONE), + Key::Down(KeyModifiers::NONE), + Key::Backspace, + Key::Char('3', KeyModifiers::NONE), + Key::Down(KeyModifiers::NONE), + Key::Backspace, + Key::Enter, + ]); let options = vec![1, 2, 3]; - let mut write: Vec = Vec::new(); - let terminal = CrosstermTerminal::new_with_io(&mut write, &mut read); - let mut backend = Backend::new(terminal, RenderConfig::default()).unwrap(); - let ans = Select::new("Question", options) .prompt_with_backend(&mut backend) .unwrap(); @@ -97,19 +68,10 @@ fn down_arrow_on_empty_list_does_not_panic() { #[test] // Anti-regression test: https://github.com/mikaelmello/inquire/issues/195 fn starting_cursor_is_respected() { - let read: Vec = [KeyCode::Enter] - .iter() - .map(|c| KeyEvent::from(*c)) - .collect(); - - let mut read = read.iter(); + let mut backend = fake_backend(vec![Key::Enter]); let options = vec![1, 2, 3]; - let mut write: Vec = Vec::new(); - let terminal = CrosstermTerminal::new_with_io(&mut write, &mut read); - let mut backend = Backend::new(terminal, RenderConfig::default()).unwrap(); - let ans = Select::new("Question", options) .with_starting_cursor(2) .prompt_with_backend(&mut backend) @@ -120,18 +82,13 @@ fn starting_cursor_is_respected() { #[test] fn naive_assert_fuzzy_match_as_default_scorer() { - let read: Vec = [ - KeyCode::Char('w'), - KeyCode::Char('r'), - KeyCode::Char('r'), - KeyCode::Char('y'), - KeyCode::Enter, - ] - .iter() - .map(|c| KeyEvent::from(*c)) - .collect(); - - let mut read = read.iter(); + let mut backend = fake_backend(vec![ + Key::Char('w', KeyModifiers::NONE), + Key::Char('r', KeyModifiers::NONE), + Key::Char('r', KeyModifiers::NONE), + Key::Char('y', KeyModifiers::NONE), + Key::Enter, + ]); let options = vec![ "Banana", @@ -147,10 +104,6 @@ fn naive_assert_fuzzy_match_as_default_scorer() { "Pineapple", ]; - let mut write: Vec = Vec::new(); - let terminal = CrosstermTerminal::new_with_io(&mut write, &mut read); - let mut backend = Backend::new(terminal, RenderConfig::default()).unwrap(); - let ans = Select::new("Question", options) .prompt_with_backend(&mut backend) .unwrap(); @@ -160,18 +113,13 @@ fn naive_assert_fuzzy_match_as_default_scorer() { #[test] fn chars_do_not_affect_prompt_without_filtering() { - let read: Vec = [ - KeyCode::Char('w'), - KeyCode::Char('r'), - KeyCode::Char('r'), - KeyCode::Char('y'), - KeyCode::Enter, - ] - .iter() - .map(|c| KeyEvent::from(*c)) - .collect(); - - let mut read = read.iter(); + let mut backend = fake_backend(vec![ + Key::Char('w', KeyModifiers::NONE), + Key::Char('r', KeyModifiers::NONE), + Key::Char('r', KeyModifiers::NONE), + Key::Char('y', KeyModifiers::NONE), + Key::Enter, + ]); let options = vec![ "Banana", @@ -187,10 +135,6 @@ fn chars_do_not_affect_prompt_without_filtering() { "Pineapple", ]; - let mut write: Vec = Vec::new(); - let terminal = CrosstermTerminal::new_with_io(&mut write, &mut read); - let mut backend = Backend::new(terminal, RenderConfig::default()).unwrap(); - let ans = Select::new("Question", options) .without_filtering() .prompt_with_backend(&mut backend) diff --git a/inquire/src/prompts/test.rs b/inquire/src/prompts/test.rs new file mode 100644 index 00000000..2f9f629c --- /dev/null +++ b/inquire/src/prompts/test.rs @@ -0,0 +1,18 @@ +use std::convert::TryFrom; + +use crossterm::event::KeyEvent; + +use crate::{ + terminal::crossterm::CrosstermTerminal, + ui::{Backend, Key, RenderConfig}, +}; + +pub fn fake_backend(input: Vec) -> Backend<'static, CrosstermTerminal> { + let events: Vec = input + .into_iter() + .map(|k| KeyEvent::try_from(k).expect("Could not convert Key to KeyEvent")) + .collect(); + let terminal = CrosstermTerminal::new_with_io(events.into()); + + Backend::new(terminal, RenderConfig::default()).unwrap() +} diff --git a/inquire/src/prompts/text/test.rs b/inquire/src/prompts/text/test.rs index 94f0099e..35d43e58 100644 --- a/inquire/src/prompts/text/test.rs +++ b/inquire/src/prompts/text/test.rs @@ -1,10 +1,6 @@ use super::Text; -use crate::{ - terminal::crossterm::CrosstermTerminal, - ui::{Backend, RenderConfig}, - validator::{ErrorMessage, Validation}, -}; -use crossterm::event::{KeyCode, KeyEvent}; +use crate::ui::{Key, KeyModifiers}; +use crate::validator::{ErrorMessage, Validation}; fn default<'a>() -> Text<'a> { Text::new("Question?") @@ -12,7 +8,10 @@ fn default<'a>() -> Text<'a> { macro_rules! text_to_events { ($text:expr) => {{ - $text.chars().map(KeyCode::Char) + $text + .chars() + .map(|c| Key::Char(c, KeyModifiers::NONE)) + .collect::>() }}; } @@ -24,13 +23,7 @@ macro_rules! text_test { ($name:ident,$input:expr,$output:expr,$prompt:expr) => { #[test] fn $name() { - let read: Vec = $input.into_iter().map(KeyEvent::from).collect(); - let mut read = read.iter(); - - let mut write: Vec = Vec::new(); - - let terminal = CrosstermTerminal::new_with_io(&mut write, &mut read); - let mut backend = Backend::new(terminal, RenderConfig::default()).unwrap(); + let mut backend = crate::prompts::test::fake_backend($input); let ans = $prompt.prompt_with_backend(&mut backend).unwrap(); @@ -39,9 +32,13 @@ macro_rules! text_test { }; } -text_test!(empty, vec![KeyCode::Enter], ""); +text_test!(empty, vec![Key::Enter], ""); -text_test!(single_letter, vec![KeyCode::Char('b'), KeyCode::Enter], "b"); +text_test!( + single_letter, + vec![Key::Char('b', KeyModifiers::NONE), Key::Enter], + "b" +); text_test!( letters_and_enter, @@ -59,13 +56,13 @@ text_test!( input_and_correction, { let mut events = vec![]; - events.append(&mut text_to_events!("anor").collect()); - events.push(KeyCode::Backspace); - events.push(KeyCode::Backspace); - events.push(KeyCode::Backspace); - events.push(KeyCode::Backspace); - events.append(&mut text_to_events!("normal input").collect()); - events.push(KeyCode::Enter); + events.append(&mut text_to_events!("anor")); + events.push(Key::Backspace); + events.push(Key::Backspace); + events.push(Key::Backspace); + events.push(Key::Backspace); + events.append(&mut text_to_events!("normal input")); + events.push(Key::Enter); events }, "normal input" @@ -75,19 +72,19 @@ text_test!( input_and_excessive_correction, { let mut events = vec![]; - events.append(&mut text_to_events!("anor").collect()); - events.push(KeyCode::Backspace); - events.push(KeyCode::Backspace); - events.push(KeyCode::Backspace); - events.push(KeyCode::Backspace); - events.push(KeyCode::Backspace); - events.push(KeyCode::Backspace); - events.push(KeyCode::Backspace); - events.push(KeyCode::Backspace); - events.push(KeyCode::Backspace); - events.push(KeyCode::Backspace); - events.append(&mut text_to_events!("normal input").collect()); - events.push(KeyCode::Enter); + events.append(&mut text_to_events!("anor")); + events.push(Key::Backspace); + events.push(Key::Backspace); + events.push(Key::Backspace); + events.push(Key::Backspace); + events.push(Key::Backspace); + events.push(Key::Backspace); + events.push(Key::Backspace); + events.push(Key::Backspace); + events.push(Key::Backspace); + events.push(Key::Backspace); + events.append(&mut text_to_events!("normal input")); + events.push(Key::Enter); events }, "normal input" @@ -97,15 +94,15 @@ text_test!( input_correction_after_validation, { let mut events = vec![]; - events.append(&mut text_to_events!("1234567890").collect()); - events.push(KeyCode::Enter); - events.push(KeyCode::Backspace); - events.push(KeyCode::Backspace); - events.push(KeyCode::Backspace); - events.push(KeyCode::Backspace); - events.push(KeyCode::Backspace); - events.append(&mut text_to_events!("yes").collect()); - events.push(KeyCode::Enter); + events.append(&mut text_to_events!("1234567890")); + events.push(Key::Enter); + events.push(Key::Backspace); + events.push(Key::Backspace); + events.push(Key::Backspace); + events.push(Key::Backspace); + events.push(Key::Backspace); + events.append(&mut text_to_events!("yes")); + events.push(Key::Enter); events }, "12345yes", diff --git a/inquire/src/terminal/crossterm.rs b/inquire/src/terminal/crossterm.rs index d0adcdf9..555c7102 100644 --- a/inquire/src/terminal/crossterm.rs +++ b/inquire/src/terminal/crossterm.rs @@ -1,4 +1,7 @@ -use std::io::{stderr, Result, Stderr, Write}; +use std::{ + collections::VecDeque, + io::{stderr, Result, Stderr, Write}, +}; use crossterm::{ cursor, @@ -16,23 +19,23 @@ use crate::{ use super::{Terminal, INITIAL_IN_MEMORY_CAPACITY}; -enum IO<'a> { +enum IO { Std { w: Stderr, }, #[allow(unused)] - Custom { - r: &'a mut dyn Iterator, - w: &'a mut (dyn Write), + Test { + w: Vec, + r: VecDeque, }, } -pub struct CrosstermTerminal<'a> { - io: IO<'a>, +pub struct CrosstermTerminal { + io: IO, in_memory_content: String, } -impl<'a> CrosstermTerminal<'a> { +impl CrosstermTerminal { pub fn new() -> InquireResult { crossterm::terminal::enable_raw_mode()?; @@ -42,27 +45,10 @@ impl<'a> CrosstermTerminal<'a> { }) } - /// # Errors - /// - /// Will return `std::io::Error` if it fails to get terminal size - #[cfg(test)] - pub fn new_with_io( - writer: &'a mut W, - reader: &'a mut dyn Iterator, - ) -> Self { - Self { - io: IO::Custom { - r: reader, - w: writer, - }, - in_memory_content: String::with_capacity(INITIAL_IN_MEMORY_CAPACITY), - } - } - fn get_writer(&mut self) -> &mut dyn Write { match &mut self.io { IO::Std { w } => w, - IO::Custom { r: _, w } => w, + IO::Test { r: _, w } => w, } } @@ -102,7 +88,7 @@ impl<'a> CrosstermTerminal<'a> { } } -impl<'a> Terminal for CrosstermTerminal<'a> { +impl Terminal for CrosstermTerminal { fn cursor_up(&mut self, cnt: u16) -> Result<()> { self.write_command(cursor::MoveUp(cnt)) } @@ -123,9 +109,11 @@ impl<'a> Terminal for CrosstermTerminal<'a> { return Ok(key_event.into()); } } - IO::Custom { r, w: _ } => { - let key = r.next().expect("Custom stream of characters has ended"); - return Ok((*key).into()); + IO::Test { r, w: _ } => { + let key = r + .pop_front() + .expect("Custom stream of characters has ended"); + return Ok(key.into()); } } } @@ -194,12 +182,12 @@ impl<'a> Terminal for CrosstermTerminal<'a> { } } -impl<'a> Drop for CrosstermTerminal<'a> { +impl Drop for CrosstermTerminal { fn drop(&mut self) { let _unused = self.flush(); let _unused = match self.io { IO::Std { w: _ } => terminal::disable_raw_mode(), - IO::Custom { r: _, w: _ } => Ok(()), + IO::Test { r: _, w: _ } => Ok(()), }; } } @@ -322,116 +310,158 @@ impl From for Key { #[cfg(test)] mod test { + use std::collections::VecDeque; + use std::convert::TryFrom; + use std::convert::TryInto; + + use crossterm::event::KeyCode; + use crossterm::event::KeyEvent; + use crossterm::event::KeyModifiers; + use crate::terminal::Terminal; + use crate::terminal::INITIAL_IN_MEMORY_CAPACITY; use crate::ui::Color; + use crate::ui::Key; use super::Attributes; use super::CrosstermTerminal; + use super::IO; - #[test] - fn writer() { - let mut write: Vec = Vec::new(); - let read = Vec::new(); - let mut read = read.iter(); + impl TryFrom for KeyModifiers { + type Error = (); - { - let mut terminal = CrosstermTerminal::new_with_io(&mut write, &mut read); + fn try_from(value: crate::ui::KeyModifiers) -> Result { + Self::from_bits(value.bits()).ok_or(()) + } + } - terminal.write("testing ").unwrap(); - terminal.write("writing ").unwrap(); - terminal.flush().unwrap(); - terminal.write("wow").unwrap(); + impl TryFrom for KeyEvent { + type Error = (); + + fn try_from(value: Key) -> Result { + let key_event = match value { + Key::Escape => KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE), + Key::Enter => KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE), + Key::Backspace => KeyEvent::new(KeyCode::Backspace, KeyModifiers::NONE), + Key::Tab => KeyEvent::new(KeyCode::Tab, KeyModifiers::NONE), + Key::Delete(m) => KeyEvent::new(KeyCode::Delete, m.try_into()?), + Key::Home => KeyEvent::new(KeyCode::Home, KeyModifiers::NONE), + Key::End => KeyEvent::new(KeyCode::End, KeyModifiers::NONE), + Key::PageUp => KeyEvent::new(KeyCode::PageUp, KeyModifiers::NONE), + Key::PageDown => KeyEvent::new(KeyCode::PageDown, KeyModifiers::NONE), + Key::Up(m) => KeyEvent::new(KeyCode::Up, m.try_into()?), + Key::Down(m) => KeyEvent::new(KeyCode::Down, m.try_into()?), + Key::Left(m) => KeyEvent::new(KeyCode::Left, m.try_into()?), + Key::Right(m) => KeyEvent::new(KeyCode::Right, m.try_into()?), + Key::Char(c, m) => KeyEvent::new(KeyCode::Char(c), m.try_into()?), + #[allow(deprecated)] + Key::Any => KeyEvent::new(KeyCode::Null, KeyModifiers::NONE), + }; + + Ok(key_event) } + } + + impl CrosstermTerminal { + pub fn new_with_io(events: VecDeque) -> Self { + Self { + io: IO::Test { + r: events, + w: Vec::new(), + }, + in_memory_content: String::with_capacity(INITIAL_IN_MEMORY_CAPACITY), + } + } + + pub fn get_buffer_content(&mut self) -> Vec { + match &mut self.io { + IO::Std { w: _ } => panic!("Cannot get write buffer from standard output"), + IO::Test { r: _, w } => { + let mut buffer = Vec::new(); + std::mem::swap(&mut buffer, w); + buffer + } + } + } + } + + #[test] + fn writer() { + let mut terminal = CrosstermTerminal::new_with_io(VecDeque::new()); + + terminal.write("testing ").unwrap(); + terminal.write("writing ").unwrap(); + terminal.flush().unwrap(); + terminal.write("wow").unwrap(); #[cfg(unix)] - assert_eq!("testing writing wow", std::str::from_utf8(&write).unwrap()); + assert_eq!( + "testing writing wow", + std::str::from_utf8(&terminal.get_buffer_content()).unwrap() + ); } #[test] fn style_management() { - let mut write: Vec = Vec::new(); - let read = Vec::new(); - let mut read = read.iter(); + let mut terminal = CrosstermTerminal::new_with_io(VecDeque::new()); - { - let mut terminal = CrosstermTerminal::new_with_io(&mut write, &mut read); - - terminal.set_attributes(Attributes::BOLD).unwrap(); - terminal.set_attributes(Attributes::ITALIC).unwrap(); - terminal.set_attributes(Attributes::BOLD).unwrap(); - terminal.reset_attributes().unwrap(); - } + terminal.set_attributes(Attributes::BOLD).unwrap(); + terminal.set_attributes(Attributes::ITALIC).unwrap(); + terminal.set_attributes(Attributes::BOLD).unwrap(); + terminal.reset_attributes().unwrap(); #[cfg(unix)] assert_eq!( "\x1B[1m\x1B[3m\x1B[1m\x1B[0m", - std::str::from_utf8(&write).unwrap() + std::str::from_utf8(&terminal.get_buffer_content()).unwrap() ); } #[test] fn style_management_with_flags() { - let mut write: Vec = Vec::new(); - let read = Vec::new(); - let mut read = read.iter(); + let mut terminal = CrosstermTerminal::new_with_io(VecDeque::new()); - { - let mut terminal = CrosstermTerminal::new_with_io(&mut write, &mut read); - - terminal - .set_attributes(Attributes::BOLD | Attributes::ITALIC | Attributes::BOLD) - .unwrap(); - terminal.reset_attributes().unwrap(); - } + terminal + .set_attributes(Attributes::BOLD | Attributes::ITALIC | Attributes::BOLD) + .unwrap(); + terminal.reset_attributes().unwrap(); #[cfg(unix)] assert_eq!( "\x1B[1m\x1B[3m\x1B[0m", - std::str::from_utf8(&write).unwrap() + std::str::from_utf8(&terminal.get_buffer_content()).unwrap() ); } #[test] fn fg_color_management() { - let mut write: Vec = Vec::new(); - let read = Vec::new(); - let mut read = read.iter(); + let mut terminal = CrosstermTerminal::new_with_io(VecDeque::new()); - { - let mut terminal = CrosstermTerminal::new_with_io(&mut write, &mut read); - - terminal.set_fg_color(Color::LightRed).unwrap(); - terminal.reset_fg_color().unwrap(); - terminal.set_fg_color(Color::Black).unwrap(); - terminal.set_fg_color(Color::LightGreen).unwrap(); - } + terminal.set_fg_color(Color::LightRed).unwrap(); + terminal.reset_fg_color().unwrap(); + terminal.set_fg_color(Color::Black).unwrap(); + terminal.set_fg_color(Color::LightGreen).unwrap(); #[cfg(unix)] assert_eq!( "\x1B[38;5;9m\x1B[39m\x1B[38;5;0m\x1B[38;5;10m", - std::str::from_utf8(&write).unwrap() + std::str::from_utf8(&terminal.get_buffer_content()).unwrap() ); } #[test] fn bg_color_management() { - let mut write: Vec = Vec::new(); - let read = Vec::new(); - let mut read = read.iter(); + let mut terminal = CrosstermTerminal::new_with_io(VecDeque::new()); - { - let mut terminal = CrosstermTerminal::new_with_io(&mut write, &mut read); - - terminal.set_bg_color(Color::LightRed).unwrap(); - terminal.reset_bg_color().unwrap(); - terminal.set_bg_color(Color::Black).unwrap(); - terminal.set_bg_color(Color::LightGreen).unwrap(); - } + terminal.set_bg_color(Color::LightRed).unwrap(); + terminal.reset_bg_color().unwrap(); + terminal.set_bg_color(Color::Black).unwrap(); + terminal.set_bg_color(Color::LightGreen).unwrap(); #[cfg(unix)] assert_eq!( "\x1B[48;5;9m\x1B[49m\x1B[48;5;0m\x1B[48;5;10m", - std::str::from_utf8(&write).unwrap() + std::str::from_utf8(&terminal.get_buffer_content()).unwrap() ); } }