use super::*;
use std::io::Cursor;
#[test]
fn test_simple_prompt_returns_input() {
let mut p = Prompt::new("Enter name");
let mut input = Cursor::new(b"Alice\n" as &[u8]);
let result = p.ask_with_input(&mut input);
assert_eq!(result, "Alice");
}
#[test]
fn test_prompt_with_default_empty_returns_default() {
let mut p = Prompt::new("Enter name").with_default("Bob");
let mut input = Cursor::new(b"\n" as &[u8]);
let result = p.ask_with_input(&mut input);
assert_eq!(result, "Bob");
}
#[test]
fn test_prompt_with_default_non_empty_returns_input() {
let mut p = Prompt::new("Enter name").with_default("Bob");
let mut input = Cursor::new(b"Charlie\n" as &[u8]);
let result = p.ask_with_input(&mut input);
assert_eq!(result, "Charlie");
}
#[test]
fn test_prompt_with_choices_valid() {
let mut p = Prompt::new("Pick fruit").with_choices(vec![
"apple".into(),
"orange".into(),
"pear".into(),
]);
let mut input = Cursor::new(b"apple\n" as &[u8]);
let result = p.ask_with_input(&mut input);
assert_eq!(result, "apple");
}
#[test]
fn test_prompt_with_choices_invalid_then_valid() {
let mut p = Prompt::new("Pick fruit").with_choices(vec![
"apple".into(),
"orange".into(),
"pear".into(),
]);
let mut input = Cursor::new(b"banana\norange\n" as &[u8]);
let result = p.ask_with_input(&mut input);
assert_eq!(result, "orange");
}
#[test]
fn test_case_insensitive_choices() {
let mut p = Prompt::new("Pick")
.with_choices(vec!["Apple".into(), "Orange".into()])
.with_case_sensitive(false);
let mut input = Cursor::new(b"apple\n" as &[u8]);
let result = p.ask_with_input(&mut input);
assert_eq!(result, "Apple");
}
#[test]
fn test_case_sensitive_choices_reject_wrong_case() {
let mut p = Prompt::new("Pick")
.with_choices(vec!["Apple".into(), "Orange".into()])
.with_case_sensitive(true);
let mut input = Cursor::new(b"apple\nApple\n" as &[u8]);
let result = p.ask_with_input(&mut input);
assert_eq!(result, "Apple");
}
#[test]
fn test_confirm_yes() {
let mut input = Cursor::new(b"y\n" as &[u8]);
let result = confirm_with_input("Continue?", &mut input);
assert!(result);
}
#[test]
fn test_confirm_yes_full() {
let mut input = Cursor::new(b"yes\n" as &[u8]);
let result = confirm_with_input("Continue?", &mut input);
assert!(result);
}
#[test]
fn test_confirm_no() {
let mut input = Cursor::new(b"n\n" as &[u8]);
let result = confirm_with_input("Continue?", &mut input);
assert!(!result);
}
#[test]
fn test_confirm_no_full() {
let mut input = Cursor::new(b"no\n" as &[u8]);
let result = confirm_with_input("Continue?", &mut input);
assert!(!result);
}
#[test]
fn test_confirm_case_insensitive() {
let mut input = Cursor::new(b"Y\n" as &[u8]);
let result = confirm_with_input("Continue?", &mut input);
assert!(result);
}
#[test]
fn test_confirm_invalid_then_valid() {
let mut input = Cursor::new(b"maybe\ny\n" as &[u8]);
let result = confirm_with_input("Continue?", &mut input);
assert!(result);
}
#[test]
fn test_ask_int_valid() {
let mut input = Cursor::new(b"42\n" as &[u8]);
let result = ask_int_with_input("Enter number", &mut input);
assert_eq!(result, 42);
}
#[test]
fn test_ask_int_negative() {
let mut input = Cursor::new(b"-7\n" as &[u8]);
let result = ask_int_with_input("Enter number", &mut input);
assert_eq!(result, -7);
}
#[test]
fn test_ask_int_invalid_then_valid() {
let mut input = Cursor::new(b"abc\n42\n" as &[u8]);
let result = ask_int_with_input("Enter number", &mut input);
assert_eq!(result, 42);
}
#[test]
fn test_ask_float_valid() {
let mut input = Cursor::new(b"4.5\n" as &[u8]);
let result = ask_float_with_input("Enter number", &mut input);
assert!((result - 4.5).abs() < f64::EPSILON);
}
#[test]
fn test_ask_float_integer_input() {
let mut input = Cursor::new(b"7\n" as &[u8]);
let result = ask_float_with_input("Enter number", &mut input);
assert!((result - 7.0).abs() < f64::EPSILON);
}
#[test]
fn test_ask_float_invalid_then_valid() {
let mut input = Cursor::new(b"xyz\n2.5\n" as &[u8]);
let result = ask_float_with_input("Enter number", &mut input);
assert!((result - 2.5).abs() < f64::EPSILON);
}
#[test]
fn ask_int_returns_on_eof_instead_of_looping() {
let mut input = Cursor::new(b"" as &[u8]);
let result = ask_int_with_input("Enter number", &mut input);
assert_eq!(result, 0, "EOF must terminate and return 0");
}
#[test]
fn ask_float_returns_on_eof_instead_of_looping() {
let mut input = Cursor::new(b"" as &[u8]);
let result = ask_float_with_input("Enter number", &mut input);
assert!(
(result - 0.0).abs() < f64::EPSILON,
"EOF must terminate and return 0.0"
);
}
#[test]
fn test_prompt_text_includes_choices() {
let p = Prompt::new("Pick fruit")
.with_choices(vec!["apple".into(), "orange".into()])
.with_show_choices(true);
let text = p.make_prompt();
let plain = text.plain().to_string();
assert!(plain.contains("[apple/orange]"));
}
#[test]
fn test_prompt_text_hides_choices_when_disabled() {
let p = Prompt::new("Pick fruit")
.with_choices(vec!["apple".into(), "orange".into()])
.with_show_choices(false);
let text = p.make_prompt();
let plain = text.plain().to_string();
assert!(!plain.contains("apple"));
assert!(!plain.contains("orange"));
}
#[test]
fn test_prompt_text_includes_default() {
let p = Prompt::new("Enter name")
.with_default("World")
.with_show_default(true);
let text = p.make_prompt();
let plain = text.plain().to_string();
assert!(plain.contains("(World)"));
}
#[test]
fn test_prompt_text_hides_default_when_disabled() {
let p = Prompt::new("Enter name")
.with_default("World")
.with_show_default(false);
let text = p.make_prompt();
let plain = text.plain().to_string();
assert!(!plain.contains("(World)"));
}
#[test]
fn test_password_flag() {
let p = Prompt::new("Password").with_password(true);
assert!(p.password);
let p2 = Prompt::new("Password").with_password(false);
assert!(!p2.password);
}
#[test]
fn test_builder_with_choices() {
let p = Prompt::new("test").with_choices(vec!["a".into(), "b".into()]);
assert_eq!(p.choices, Some(vec!["a".to_string(), "b".to_string()]));
}
#[test]
fn test_builder_with_default() {
let p = Prompt::new("test").with_default("val");
assert_eq!(p.default, Some("val".to_string()));
}
#[test]
fn test_builder_with_case_sensitive() {
let p = Prompt::new("test").with_case_sensitive(false);
assert!(!p.case_sensitive);
}
#[test]
fn test_builder_with_show_default() {
let p = Prompt::new("test").with_show_default(false);
assert!(!p.show_default);
}
#[test]
fn test_builder_with_show_choices() {
let p = Prompt::new("test").with_show_choices(false);
assert!(!p.show_choices);
}
#[test]
fn test_builder_with_password() {
let p = Prompt::new("test").with_password(true);
assert!(p.password);
}
#[test]
fn test_builder_with_completions() {
let p = Prompt::new("test").with_completions(vec!["foo".into(), "bar".into()]);
assert_eq!(
p.completions,
Some(vec!["foo".to_string(), "bar".to_string()])
);
}
#[test]
fn test_builder_completions_default_none() {
let p = Prompt::new("test");
assert!(p.completions.is_none());
}
#[test]
fn test_prompt_suffix() {
let p = Prompt::new("Enter value");
let text = p.make_prompt();
let plain = text.plain().to_string();
assert!(plain.ends_with(": "));
}
#[test]
fn test_default_on_eof() {
let mut p = Prompt::new("Enter").with_default("fallback");
let mut input = Cursor::new(b"" as &[u8]); let result = p.ask_with_input(&mut input);
assert_eq!(result, "fallback");
}
#[test]
fn test_no_default_empty_returns_empty() {
let mut p = Prompt::new("Enter");
let mut input = Cursor::new(b"\n" as &[u8]);
let result = p.ask_with_input(&mut input);
assert_eq!(result, "");
}
#[test]
fn test_prompt_text_choices_and_default() {
let p = Prompt::new("Pick")
.with_choices(vec!["a".into(), "b".into()])
.with_default("a")
.with_show_choices(true)
.with_show_default(true);
let text = p.make_prompt();
let plain = text.plain().to_string();
assert!(plain.contains("[a/b]"));
assert!(plain.contains("(a)"));
assert!(plain.ends_with(": "));
}
#[test]
fn test_choices_default_on_empty() {
let mut p = Prompt::new("Pick")
.with_choices(vec!["a".into(), "b".into()])
.with_default("a");
let mut input = Cursor::new(b"\n" as &[u8]);
let result = p.ask_with_input(&mut input);
assert_eq!(result, "a");
}
#[test]
fn test_prompt_has_styled_choices_span() {
let p = Prompt::new("Pick")
.with_choices(vec!["x".into(), "y".into()])
.with_show_choices(true);
let text = p.make_prompt();
assert!(!text.spans().is_empty());
}
#[test]
fn test_prompt_has_styled_default_span() {
let p = Prompt::new("Pick")
.with_default("z")
.with_show_default(true);
let text = p.make_prompt();
assert!(!text.spans().is_empty());
}
#[test]
fn test_invalid_response_display() {
let err = InvalidResponse {
message: "bad input".to_string(),
};
assert_eq!(format!("{}", err), "bad input");
}
#[test]
fn test_invalid_response_debug() {
let err = InvalidResponse {
message: "bad".to_string(),
};
let debug = format!("{:?}", err);
assert!(debug.contains("InvalidResponse"));
assert!(debug.contains("bad"));
}
#[test]
fn test_password_ask_with_input_reads_normally() {
let mut p = Prompt::new("Password").with_password(true);
let mut input = Cursor::new(b"secret123\n" as &[u8]);
let result = p.ask_with_input(&mut input);
assert_eq!(result, "secret123");
}
#[test]
fn test_password_with_default_on_empty() {
let mut p = Prompt::new("Password")
.with_password(true)
.with_default("default_pass");
let mut input = Cursor::new(b"\n" as &[u8]);
let result = p.ask_with_input(&mut input);
assert_eq!(result, "default_pass");
}
#[test]
fn test_password_with_default_on_eof() {
let mut p = Prompt::new("Password")
.with_password(true)
.with_default("fallback");
let mut input = Cursor::new(b"" as &[u8]);
let result = p.ask_with_input(&mut input);
assert_eq!(result, "fallback");
}
#[test]
fn test_password_prompt_text_unchanged() {
let p1 = Prompt::new("Enter password").with_password(true);
let p2 = Prompt::new("Enter password").with_password(false);
let text1 = p1.make_prompt().plain().to_string();
let text2 = p2.make_prompt().plain().to_string();
assert_eq!(text1, text2);
}
#[test]
fn test_select_parse_single_number() {
let s = Select::new("Pick", vec!["A".into(), "B".into(), "C".into()]);
assert_eq!(s.parse_input("2"), Ok(1)); }
#[test]
fn test_select_parse_first_choice() {
let s = Select::new("Pick", vec!["A".into(), "B".into()]);
assert_eq!(s.parse_input("1"), Ok(0));
}
#[test]
fn test_select_parse_last_choice() {
let s = Select::new("Pick", vec!["A".into(), "B".into(), "C".into(), "D".into()]);
assert_eq!(s.parse_input("4"), Ok(3));
}
#[test]
fn test_select_validate_in_range() {
let s = Select::new("Pick", vec!["A".into(), "B".into(), "C".into()]);
assert!(s.parse_input("1").is_ok());
assert!(s.parse_input("2").is_ok());
assert!(s.parse_input("3").is_ok());
}
#[test]
fn test_select_validate_out_of_range_high() {
let s = Select::new("Pick", vec!["A".into(), "B".into()]);
let result = s.parse_input("3");
assert!(result.is_err());
assert!(result.unwrap_err().message.contains("between 1 and 2"));
}
#[test]
fn test_select_validate_out_of_range_zero() {
let s = Select::new("Pick", vec!["A".into(), "B".into()]);
let result = s.parse_input("0");
assert!(result.is_err());
}
#[test]
fn test_select_validate_not_a_number() {
let s = Select::new("Pick", vec!["A".into(), "B".into()]);
let result = s.parse_input("abc");
assert!(result.is_err());
assert!(result.unwrap_err().message.contains("not a valid number"));
}
#[test]
fn test_select_default_on_empty() {
let s = Select::new("Pick", vec!["A".into(), "B".into(), "C".into()]).with_default(1);
assert_eq!(s.parse_input(""), Ok(1));
}
#[test]
fn test_select_no_default_on_empty() {
let s = Select::new("Pick", vec!["A".into(), "B".into()]);
let result = s.parse_input("");
assert!(result.is_err());
assert!(result.unwrap_err().message.contains("enter a number"));
}
#[test]
fn test_select_whitespace_input() {
let s = Select::new("Pick", vec!["A".into(), "B".into(), "C".into()]);
assert_eq!(s.parse_input(" 2 "), Ok(1));
}
#[test]
fn test_select_empty_choices() {
let s = Select::new("Pick", vec![]);
let mut console = Console::builder().quiet(true).build();
let mut input = Cursor::new(b"1\n" as &[u8]);
let result = s.ask_with_input(&mut console, &mut input);
assert!(result.is_err());
assert_eq!(result.unwrap_err().message, "No choices provided");
}
#[test]
fn test_select_format_choices() {
let s = Select::new(
"Select a color",
vec!["Red".into(), "Green".into(), "Blue".into()],
);
let output = s.format_choices();
assert!(output.contains("? Select a color:"));
assert!(output.contains(" 1) Red"));
assert!(output.contains(" 2) Green"));
assert!(output.contains(" 3) Blue"));
}
#[test]
fn test_select_format_input_prompt_no_default() {
let s = Select::new("Pick", vec!["A".into(), "B".into(), "C".into()]);
let prompt = s.format_input_prompt();
assert_eq!(prompt, "Enter choice [1-3]: ");
}
#[test]
fn test_select_format_input_prompt_with_default() {
let s = Select::new("Pick", vec!["A".into(), "B".into(), "C".into()]).with_default(1);
let prompt = s.format_input_prompt();
assert_eq!(prompt, "Enter choice [1-3] (2): ");
}
#[test]
fn test_select_ask_with_input_valid() {
let s = Select::new("Pick", vec!["A".into(), "B".into(), "C".into()]);
let mut console = Console::builder().quiet(true).build();
let mut input = Cursor::new(b"2\n" as &[u8]);
let result = s.ask_with_input(&mut console, &mut input);
assert_eq!(result.unwrap(), 1);
}
#[test]
fn test_select_ask_with_input_invalid_then_valid() {
let s = Select::new("Pick", vec!["A".into(), "B".into()]);
let mut console = Console::builder().quiet(true).build();
let mut input = Cursor::new(b"5\n1\n" as &[u8]);
let result = s.ask_with_input(&mut console, &mut input);
assert_eq!(result.unwrap(), 0);
}
#[test]
fn test_select_ask_value_with_input() {
let s = Select::new("Pick", vec!["Red".into(), "Green".into(), "Blue".into()]);
let mut console = Console::builder().quiet(true).build();
let mut input = Cursor::new(b"3\n" as &[u8]);
let result = s.ask_value_with_input(&mut console, &mut input);
assert_eq!(result.unwrap(), "Blue");
}
#[test]
fn test_select_default_on_eof() {
let s = Select::new("Pick", vec!["A".into(), "B".into()]).with_default(0);
let mut console = Console::builder().quiet(true).build();
let mut input = Cursor::new(b"" as &[u8]);
let result = s.ask_with_input(&mut console, &mut input);
assert_eq!(result.unwrap(), 0);
}
#[test]
fn test_select_builder_with_default() {
let s = Select::new("Pick", vec!["A".into()]).with_default(0);
assert_eq!(s.default, Some(0));
}
#[test]
fn test_select_builder_with_style() {
let style = Style::parse("red bold");
let s = Select::new("Pick", vec!["A".into()]).with_style(style);
assert_eq!(s.style.bold(), Some(true));
}
#[test]
fn test_select_builder_with_highlight_style() {
let style = Style::parse("green");
let s = Select::new("Pick", vec!["A".into()]).with_highlight_style(style);
assert!(s.highlight_style.color().is_some());
}
#[test]
fn test_multiselect_parse_comma_separated() {
let ms = MultiSelect::new("Pick", vec!["A".into(), "B".into(), "C".into(), "D".into()]);
assert_eq!(ms.parse_input("1,3"), Ok(vec![0, 2]));
}
#[test]
fn test_multiselect_parse_single() {
let ms = MultiSelect::new("Pick", vec!["A".into(), "B".into()]);
assert_eq!(ms.parse_input("2"), Ok(vec![1]));
}
#[test]
fn test_multiselect_parse_all() {
let ms = MultiSelect::new("Pick", vec!["A".into(), "B".into(), "C".into()]);
assert_eq!(ms.parse_input("all"), Ok(vec![0, 1, 2]));
}
#[test]
fn test_multiselect_parse_all_case_insensitive() {
let ms = MultiSelect::new("Pick", vec!["A".into(), "B".into()]);
assert_eq!(ms.parse_input("ALL"), Ok(vec![0, 1]));
assert_eq!(ms.parse_input("All"), Ok(vec![0, 1]));
}
#[test]
fn test_multiselect_whitespace_input() {
let ms = MultiSelect::new("Pick", vec!["A".into(), "B".into(), "C".into()]);
assert_eq!(ms.parse_input(" 1 , 3 "), Ok(vec![0, 2]));
}
#[test]
fn test_multiselect_trailing_comma() {
let ms = MultiSelect::new("Pick", vec!["A".into(), "B".into()]);
assert_eq!(ms.parse_input("1,2,"), Ok(vec![0, 1]));
}
#[test]
fn test_multiselect_default_on_empty() {
let ms = MultiSelect::new("Pick", vec!["A".into(), "B".into(), "C".into()])
.with_defaults(vec![0, 2]);
assert_eq!(ms.parse_input(""), Ok(vec![0, 2]));
}
#[test]
fn test_multiselect_min_validation() {
let ms = MultiSelect::new("Pick", vec!["A".into(), "B".into(), "C".into()]).with_min(2);
let result = ms.parse_input("1");
assert!(result.is_err());
assert!(result.unwrap_err().message.contains("at least 2"));
}
#[test]
fn test_multiselect_min_satisfied() {
let ms = MultiSelect::new("Pick", vec!["A".into(), "B".into(), "C".into()]).with_min(2);
assert_eq!(ms.parse_input("1,2"), Ok(vec![0, 1]));
}
#[test]
fn test_multiselect_max_validation() {
let ms = MultiSelect::new("Pick", vec!["A".into(), "B".into(), "C".into()]).with_max(1);
let result = ms.parse_input("1,2");
assert!(result.is_err());
assert!(result.unwrap_err().message.contains("at most 1"));
}
#[test]
fn test_multiselect_max_satisfied() {
let ms = MultiSelect::new("Pick", vec!["A".into(), "B".into(), "C".into()]).with_max(2);
assert_eq!(ms.parse_input("1,3"), Ok(vec![0, 2]));
}
#[test]
fn test_multiselect_min_max_combined() {
let ms = MultiSelect::new("Pick", vec!["A".into(), "B".into(), "C".into(), "D".into()])
.with_min(1)
.with_max(3);
assert!(ms.parse_input("").is_err()); assert!(ms.parse_input("1").is_ok()); assert!(ms.parse_input("1,2,3").is_ok()); assert!(ms.parse_input("1,2,3,4").is_err()); }
#[test]
fn test_multiselect_empty_choices() {
let ms = MultiSelect::new("Pick", vec![]);
let mut console = Console::builder().quiet(true).build();
let mut input = Cursor::new(b"1\n" as &[u8]);
let result = ms.ask_with_input(&mut console, &mut input);
assert!(result.is_err());
assert_eq!(result.unwrap_err().message, "No choices provided");
}
#[test]
fn test_multiselect_out_of_range() {
let ms = MultiSelect::new("Pick", vec!["A".into(), "B".into()]);
let result = ms.parse_input("3");
assert!(result.is_err());
assert!(result.unwrap_err().message.contains("out of range"));
}
#[test]
fn test_multiselect_zero() {
let ms = MultiSelect::new("Pick", vec!["A".into(), "B".into()]);
let result = ms.parse_input("0");
assert!(result.is_err());
}
#[test]
fn test_multiselect_format_choices() {
let ms = MultiSelect::new(
"Select colors",
vec!["Red".into(), "Green".into(), "Blue".into()],
);
let output = ms.format_choices();
assert!(output.contains("? Select colors (comma-separated):"));
assert!(output.contains(" 1) Red"));
assert!(output.contains(" 2) Green"));
assert!(output.contains(" 3) Blue"));
}
#[test]
fn test_multiselect_format_input_prompt_no_defaults() {
let ms = MultiSelect::new("Pick", vec!["A".into(), "B".into(), "C".into()]);
let prompt = ms.format_input_prompt();
assert_eq!(prompt, "Enter choices [1-3, e.g. 1,3]: ");
}
#[test]
fn test_multiselect_format_input_prompt_with_defaults() {
let ms = MultiSelect::new("Pick", vec!["A".into(), "B".into(), "C".into()])
.with_defaults(vec![0, 2]);
let prompt = ms.format_input_prompt();
assert_eq!(prompt, "Enter choices [1-3, e.g. 1,3] (1,3): ");
}
#[test]
fn test_multiselect_ask_with_input_valid() {
let ms = MultiSelect::new("Pick", vec!["A".into(), "B".into(), "C".into()]);
let mut console = Console::builder().quiet(true).build();
let mut input = Cursor::new(b"1,3\n" as &[u8]);
let result = ms.ask_with_input(&mut console, &mut input);
assert_eq!(result.unwrap(), vec![0, 2]);
}
#[test]
fn test_multiselect_ask_with_input_invalid_then_valid() {
let ms = MultiSelect::new("Pick", vec!["A".into(), "B".into()]).with_min(1);
let mut console = Console::builder().quiet(true).build();
let mut input = Cursor::new(b"\n2\n" as &[u8]);
let result = ms.ask_with_input(&mut console, &mut input);
assert_eq!(result.unwrap(), vec![1]);
}
#[test]
fn test_multiselect_ask_values_with_input() {
let ms = MultiSelect::new("Pick", vec!["Red".into(), "Green".into(), "Blue".into()]);
let mut console = Console::builder().quiet(true).build();
let mut input = Cursor::new(b"1,3\n" as &[u8]);
let result = ms.ask_values_with_input(&mut console, &mut input);
assert_eq!(result.unwrap(), vec!["Red".to_string(), "Blue".to_string()]);
}
#[test]
fn test_multiselect_deduplicate() {
let ms = MultiSelect::new("Pick", vec!["A".into(), "B".into(), "C".into()]);
assert_eq!(ms.parse_input("1,1,2"), Ok(vec![0, 1]));
}
#[test]
fn test_multiselect_empty_input_min_zero() {
let ms = MultiSelect::new("Pick", vec!["A".into(), "B".into()]);
assert_eq!(ms.parse_input(""), Ok(vec![]));
}
#[test]
fn test_multiselect_builder_with_defaults() {
let ms = MultiSelect::new("Pick", vec!["A".into()]).with_defaults(vec![0]);
assert_eq!(ms.defaults, vec![0]);
}
#[test]
fn test_multiselect_builder_with_min() {
let ms = MultiSelect::new("Pick", vec!["A".into()]).with_min(1);
assert_eq!(ms.min_selections, 1);
}
#[test]
fn test_multiselect_builder_with_max() {
let ms = MultiSelect::new("Pick", vec!["A".into()]).with_max(3);
assert_eq!(ms.max_selections, Some(3));
}
#[test]
fn test_multiselect_builder_with_style() {
let style = Style::parse("red bold");
let ms = MultiSelect::new("Pick", vec!["A".into()]).with_style(style);
assert_eq!(ms.style.bold(), Some(true));
}
#[test]
fn test_multiselect_builder_with_highlight_style() {
let style = Style::parse("green");
let ms = MultiSelect::new("Pick", vec!["A".into()]).with_highlight_style(style);
assert!(ms.highlight_style.color().is_some());
}
#[test]
fn test_select_display_via_capture() {
let s = Select::new(
"Pick a fruit",
vec!["Apple".into(), "Banana".into(), "Cherry".into()],
);
let mut console = Console::builder().width(80).force_terminal(true).build();
console.begin_capture();
console.print_text(&s.format_choices());
let captured = console.end_capture();
assert!(captured.contains("? Pick a fruit:"));
assert!(captured.contains("1) Apple"));
assert!(captured.contains("2) Banana"));
assert!(captured.contains("3) Cherry"));
}
#[test]
fn test_multiselect_display_via_capture() {
let ms = MultiSelect::new(
"Pick colors",
vec!["Red".into(), "Green".into(), "Blue".into(), "Yellow".into()],
);
let mut console = Console::builder().width(80).force_terminal(true).build();
console.begin_capture();
console.print_text(&ms.format_choices());
let captured = console.end_capture();
assert!(captured.contains("? Pick colors (comma-separated):"));
assert!(captured.contains("1) Red"));
assert!(captured.contains("2) Green"));
assert!(captured.contains("3) Blue"));
assert!(captured.contains("4) Yellow"));
}
#[test]
fn test_multiselect_all_with_max() {
let ms = MultiSelect::new("Pick", vec!["A".into(), "B".into(), "C".into()]).with_max(2);
let result = ms.parse_input("all");
assert!(result.is_err());
assert!(result.unwrap_err().message.contains("at most 2"));
}
#[test]
fn test_select_negative_number() {
let s = Select::new("Pick", vec!["A".into(), "B".into()]);
let result = s.parse_input("-1");
assert!(result.is_err());
}
#[test]
fn test_multiselect_invalid_number_in_list() {
let ms = MultiSelect::new("Pick", vec!["A".into(), "B".into()]);
let result = ms.parse_input("1,abc");
assert!(result.is_err());
assert!(result.unwrap_err().message.contains("not a valid number"));
}
#[test]
fn test_typed_prompt_valid_input_first_try() {
let mut tp = Prompt::new("Enter u16")
.with_converter(|s: &str| s.parse::<u16>().map_err(|e| e.to_string()));
let mut input = Cursor::new(b"42\n" as &[u8]);
let v = tp.ask_with_input(&mut input).unwrap();
assert_eq!(v, 42u16);
}
#[test]
fn test_typed_prompt_invalid_then_valid() {
let mut tp = Prompt::new("Enter u16")
.with_converter(|s: &str| s.parse::<u16>().map_err(|e| e.to_string()));
let mut input = Cursor::new(b"abc\n42\n" as &[u8]);
let v = tp.ask_with_input(&mut input).unwrap();
assert_eq!(v, 42u16);
}
#[test]
fn test_typed_prompt_eof_no_default_returns_error() {
let mut tp = Prompt::new("Enter u16")
.with_converter(|s: &str| s.parse::<u16>().map_err(|e| e.to_string()));
let mut input = Cursor::new(b"" as &[u8]); let result = tp.ask_with_input(&mut input);
assert!(result.is_err(), "EOF with no default should be Err");
}
#[test]
fn test_typed_prompt_multiple_retries_then_valid() {
let mut tp = Prompt::new("Enter u16")
.with_converter(|s: &str| s.parse::<u16>().map_err(|e| e.to_string()));
let mut input = Cursor::new(b"bad\nworse\n7\n" as &[u8]);
let v = tp.ask_with_input(&mut input).unwrap();
assert_eq!(v, 7u16);
}
#[test]
fn test_typed_prompt_custom_converter() {
let mut tp = Prompt::new("yes or no").with_converter(|s: &str| match s.trim() {
"yes" => Ok(true),
"no" => Ok(false),
other => Err(format!("'{}' is not 'yes' or 'no'", other)),
});
let mut input = Cursor::new(b"maybe\nyes\n" as &[u8]);
let v = tp.ask_with_input(&mut input).unwrap();
assert!(v);
}
#[test]
fn test_int_prompt_valid_input() {
let mut p = IntPrompt::new("Enter integer");
let mut input = Cursor::new(b"42\n" as &[u8]);
let result = p.ask_with_input(&mut input);
assert_eq!(result, 42);
}
#[test]
fn test_int_prompt_default_on_empty() {
let mut p = IntPrompt::new("Enter integer").with_default(99);
let mut input = Cursor::new(b"\n" as &[u8]);
let result = p.ask_with_input(&mut input);
assert_eq!(result, 99);
}
#[test]
fn test_int_prompt_default_on_eof() {
let mut p = IntPrompt::new("Enter integer").with_default(7);
let mut input = Cursor::new(b"" as &[u8]);
let result = p.ask_with_input(&mut input);
assert_eq!(result, 7);
}
#[test]
fn test_int_prompt_no_default_eof_returns_zero() {
let mut p = IntPrompt::new("Enter integer");
let mut input = Cursor::new(b"" as &[u8]);
let result = p.ask_with_input(&mut input);
assert_eq!(result, 0);
}
#[test]
fn test_int_prompt_invalid_then_valid() {
let mut p = IntPrompt::new("Enter integer");
let mut input = Cursor::new(b"abc\n5\n" as &[u8]);
let result = p.ask_with_input(&mut input);
assert_eq!(result, 5);
}
#[test]
fn test_float_prompt_valid_input() {
let mut p = FloatPrompt::new("Enter float");
let mut input = Cursor::new(b"3.14\n" as &[u8]);
let result = p.ask_with_input(&mut input);
assert!((result - 3.14).abs() < f64::EPSILON);
}
#[test]
fn test_float_prompt_default_on_empty() {
let mut p = FloatPrompt::new("Enter float").with_default(2.71);
let mut input = Cursor::new(b"\n" as &[u8]);
let result = p.ask_with_input(&mut input);
assert!((result - 2.71).abs() < f64::EPSILON);
}
#[test]
fn test_float_prompt_default_on_eof() {
let mut p = FloatPrompt::new("Enter float").with_default(1.5);
let mut input = Cursor::new(b"" as &[u8]);
let result = p.ask_with_input(&mut input);
assert!((result - 1.5).abs() < f64::EPSILON);
}
#[test]
fn test_float_prompt_no_default_eof_returns_zero() {
let mut p = FloatPrompt::new("Enter float");
let mut input = Cursor::new(b"" as &[u8]);
let result = p.ask_with_input(&mut input);
assert!((result - 0.0).abs() < f64::EPSILON);
}
#[test]
fn test_confirm_struct_yes() {
let mut c = Confirm::new("Continue?");
let mut input = Cursor::new(b"y\n" as &[u8]);
let result = c.ask_with_input(&mut input);
assert!(result);
}
#[test]
fn test_confirm_struct_no() {
let mut c = Confirm::new("Continue?");
let mut input = Cursor::new(b"n\n" as &[u8]);
let result = c.ask_with_input(&mut input);
assert!(!result);
}
#[test]
fn test_confirm_struct_default_true_on_empty() {
let mut c = Confirm::new("Continue?").with_default(true);
let mut input = Cursor::new(b"\n" as &[u8]);
let result = c.ask_with_input(&mut input);
assert!(result);
}
#[test]
fn test_confirm_struct_default_false_on_empty() {
let mut c = Confirm::new("Continue?").with_default(false);
let mut input = Cursor::new(b"\n" as &[u8]);
let result = c.ask_with_input(&mut input);
assert!(!result);
}
#[test]
fn test_confirm_struct_default_on_eof() {
let mut c = Confirm::new("Continue?").with_default(true);
let mut input = Cursor::new(b"" as &[u8]);
let result = c.ask_with_input(&mut input);
assert!(result);
}
#[test]
fn test_confirm_struct_no_default_eof_returns_false() {
let mut c = Confirm::new("Continue?");
let mut input = Cursor::new(b"" as &[u8]);
let result = c.ask_with_input(&mut input);
assert!(!result);
}
#[test]
fn test_console_input_with_stream() {
let mut console = Console::builder().quiet(true).build();
let mut stream = Cursor::new(b"hello\n" as &[u8]);
let result = console.input_with_stream("Test prompt", Some(&mut stream));
assert_eq!(result.unwrap(), "hello");
}
#[test]
fn test_console_input_stream_strips_newline() {
let mut console = Console::builder().quiet(true).build();
let mut stream = Cursor::new(b"world\n" as &[u8]);
let result = console.input_with_stream("Prompt", Some(&mut stream));
assert_eq!(result.unwrap(), "world");
}
#[test]
fn test_console_input_stream_eof_returns_empty() {
let mut console = Console::builder().quiet(true).build();
let mut stream = Cursor::new(b"" as &[u8]);
let result = console.input_with_stream("Prompt", Some(&mut stream));
assert_eq!(result.unwrap(), "");
}
#[test]
fn test_pre_prompt_hook_called() {
use std::sync::{Arc, Mutex};
let called = Arc::new(Mutex::new(false));
let called_clone = Arc::clone(&called);
let mut p = Prompt::new("Pick")
.with_choices(vec!["a".into(), "b".into()])
.with_pre_prompt(move || {
*called_clone.lock().unwrap() = true;
});
let mut input = Cursor::new(b"a\n" as &[u8]);
let _ = p.ask_with_input(&mut input);
assert!(
*called.lock().unwrap(),
"pre_prompt hook should have been called"
);
}
#[test]
fn test_on_validate_error_hook_called_on_invalid_choice() {
use std::sync::{Arc, Mutex};
let errors = Arc::new(Mutex::new(vec![]));
let errors_clone = Arc::clone(&errors);
let mut p = Prompt::new("Pick")
.with_choices(vec!["a".into(), "b".into()])
.with_on_validate_error(move |msg: &str| {
errors_clone.lock().unwrap().push(msg.to_string());
});
let mut input = Cursor::new(b"bad\na\n" as &[u8]);
let _ = p.ask_with_input(&mut input);
let errs = errors.lock().unwrap();
assert!(
!errs.is_empty(),
"on_validate_error should have been called"
);
}
#[test]
fn test_prompt_default_suffix_is_colon_space() {
let p = Prompt::new("Enter value");
let text = p.make_prompt();
let plain = text.plain().to_string();
assert!(plain.ends_with(": "), "default suffix should be ': '");
}
#[test]
fn test_prompt_custom_suffix() {
let p = Prompt::new("Enter value").with_suffix(" -> ");
let text = p.make_prompt();
let plain = text.plain().to_string();
assert!(plain.ends_with(" -> "), "custom suffix should be ' -> '");
}
#[test]
fn test_prompt_suffix_field_default() {
let p = Prompt::new("test");
assert_eq!(p.prompt_suffix, ": ");
}
#[test]
fn test_prompt_suffix_field_custom() {
let p = Prompt::new("test").with_suffix("? ");
assert_eq!(p.prompt_suffix, "? ");
}
#[test]
fn test_confirm_styled_default_true_choices_uppercase_y() {
let mut input = Cursor::new(b"y\n" as &[u8]);
let result = confirm_with_input_and_default("Continue?", Some(true), &mut input);
assert!(result);
}
#[test]
fn test_confirm_styled_default_false_choices_uppercase_n() {
let mut input = Cursor::new(b"n\n" as &[u8]);
let result = confirm_with_input_and_default("Continue?", Some(false), &mut input);
assert!(!result);
}
#[test]
fn test_confirm_styled_no_default_blank_loops() {
let mut input = Cursor::new(b"\ny\n" as &[u8]);
let result = confirm_with_input_and_default("Continue?", None, &mut input);
assert!(result);
}
#[test]
fn test_pre_prompt_hook_stored_in_readline_path() {
use std::sync::{Arc, Mutex};
let count = Arc::new(Mutex::new(0u32));
let count_clone = Arc::clone(&count);
let mut p = Prompt::new("Enter value").with_pre_prompt(move || {
*count_clone.lock().unwrap() += 1;
});
let mut input = Cursor::new(b"hello\n" as &[u8]);
let _ = p.ask_with_input(&mut input);
let calls = *count.lock().unwrap();
assert!(
calls >= 1,
"pre_prompt hook should have been called at least once"
);
}
#[test]
fn test_pre_prompt_hook_called_multiple_iterations_ask_with_input() {
use std::sync::{Arc, Mutex};
let count = Arc::new(Mutex::new(0u32));
let count_clone = Arc::clone(&count);
let mut p = Prompt::new("Pick")
.with_choices(vec!["a".into(), "b".into()])
.with_pre_prompt(move || {
*count_clone.lock().unwrap() += 1;
});
let mut input = Cursor::new(b"bad\na\n" as &[u8]);
let _ = p.ask_with_input(&mut input);
let calls = *count.lock().unwrap();
assert!(
calls >= 2,
"pre_prompt should be called once per iteration (got {})",
calls
);
}
#[test]
fn test_confirm_styled_pipeline_returns_correct_answers() {
let mut input = Cursor::new(b"\n" as &[u8]);
assert!(confirm_with_input_and_default(
"Ok?",
Some(true),
&mut input
));
let mut input = Cursor::new(b"\n" as &[u8]);
assert!(!confirm_with_input_and_default(
"Ok?",
Some(false),
&mut input
));
let mut input = Cursor::new(b"yes\n" as &[u8]);
assert!(confirm_with_input_and_default("Ok?", None, &mut input));
let mut input = Cursor::new(b"no\n" as &[u8]);
assert!(!confirm_with_input_and_default("Ok?", None, &mut input));
}
#[test]
fn test_validate_error_hook_type_is_public() {
use super::ValidateErrorHook;
let hook: ValidateErrorHook = Box::new(|_msg: &str| {});
hook("test error");
}
#[test]
fn test_confirm_with_crlf_input() {
let mut input = Cursor::new(b"yes\r\n" as &[u8]);
let result = confirm_with_input_and_default("Ok?", None, &mut input);
assert!(result, "CRLF input 'yes\\r\\n' should return true");
}
#[test]
fn test_int_prompt_with_crlf_input() {
let mut p = IntPrompt::new("Enter age").with_default(18);
let mut input = Cursor::new(b"42\r\n" as &[u8]);
assert_eq!(p.ask_with_input(&mut input), 42);
}
#[test]
fn test_float_prompt_with_crlf_input() {
let mut p = FloatPrompt::new("Enter rate").with_default(1.5);
let mut input = Cursor::new(b"3.14\r\n" as &[u8]);
assert!((p.ask_with_input(&mut input) - 3.14).abs() < f64::EPSILON);
}
#[test]
fn test_confirm_struct_with_crlf_input() {
let mut c = Confirm::new("Continue?").with_default(true);
let mut input = Cursor::new(b"no\r\n" as &[u8]);
assert!(!c.ask_with_input(&mut input));
}