use super::*;
#[cfg(test)]
mod textinput_tests {
use super::*;
#[test]
fn test_new_default_values() {
let input = new();
assert_eq!(input.prompt, "> ");
assert_eq!(input.placeholder, "");
assert_eq!(input.echo_character, '*');
assert_eq!(input.char_limit, 0);
assert_eq!(input.width, 0);
assert_eq!(input.value(), "");
assert_eq!(input.position(), 0);
assert!(!input.focused());
assert_eq!(input.echo_mode, EchoMode::EchoNormal);
assert!(input.err.is_none());
}
#[test]
fn test_deprecated_new_model() {
#[allow(deprecated)]
let input = new_model();
assert_eq!(input.prompt, "> ");
assert_eq!(input.value(), "");
}
#[test]
fn test_set_value() {
let mut input = new();
input.set_value("hello world");
assert_eq!(input.value(), "hello world");
assert_eq!(input.position(), input.value().len());
}
#[test]
fn test_set_value_with_char_limit() {
let mut input = new();
input.set_char_limit(5);
input.set_value("hello world");
assert_eq!(input.value(), "hello");
assert_eq!(input.value().len(), 5);
}
#[test]
fn test_position() {
let mut input = new();
input.set_value("test");
assert_eq!(input.position(), 4);
input.set_cursor(2);
assert_eq!(input.position(), 2);
}
#[test]
fn test_set_cursor() {
let mut input = new();
input.set_value("hello");
input.set_cursor(2);
assert_eq!(input.position(), 2);
input.set_cursor(100); assert_eq!(input.position(), 5);
input.set_cursor(0);
assert_eq!(input.position(), 0);
}
#[test]
fn test_cursor_start_end() {
let mut input = new();
input.set_value("hello world");
input.set_cursor(5);
input.cursor_start();
assert_eq!(input.position(), 0);
input.cursor_end();
assert_eq!(input.position(), 11);
}
#[test]
fn test_focused() {
let mut input = new();
assert!(!input.focused());
std::mem::drop(input.focus());
assert!(input.focused());
input.blur();
assert!(!input.focused());
}
#[test]
fn test_reset() {
let mut input = new();
input.set_value("some text");
input.set_cursor(5);
input.reset();
assert_eq!(input.value(), "");
assert_eq!(input.position(), 0);
}
#[test]
fn test_echo_modes() {
let mut input = new();
input.set_value("secret");
input.set_echo_mode(EchoMode::EchoNormal);
let view_normal = input.view();
assert!(view_normal.contains("secret"));
input.set_echo_mode(EchoMode::EchoPassword);
let view_password = input.view();
assert!(view_password.contains("******")); assert!(!view_password.contains("secret"));
input.set_echo_mode(EchoMode::EchoNone);
let view_none = input.view();
assert!(!view_none.contains("secret"));
assert!(!view_none.contains("*"));
}
#[test]
fn test_placeholder() {
let mut input = new();
input.set_placeholder("Enter text...");
let view_empty = input.view();
assert!(
view_empty.contains("nter text"),
"Should contain placeholder remainder"
);
input.set_value("actual text");
let view_with_text = input.view();
assert!(!view_with_text.contains("Enter text"));
assert!(!view_with_text.contains("nter text"));
assert!(view_with_text.contains("actual"));
}
#[test]
fn test_width_setting() {
let mut input = new();
input.set_width(50);
assert_eq!(input.width, 50);
}
#[test]
fn test_char_limit() {
let mut input = new();
input.set_char_limit(10);
assert_eq!(input.char_limit, 10);
input.set_value("this is a very long string");
assert!(input.value().len() <= 10);
}
#[test]
fn test_suggestions() {
let mut input = new();
let suggestions = vec![
"apple".to_string(),
"application".to_string(),
"banana".to_string(),
"cherry".to_string(),
];
input.set_suggestions(suggestions.clone());
assert_eq!(input.available_suggestions(), suggestions);
input.set_value("app");
input.update_suggestions();
let matched = input.matched_suggestions();
assert_eq!(matched.len(), 2);
assert!(matched.contains(&"apple".to_string()));
assert!(matched.contains(&"application".to_string()));
assert!(!matched.contains(&"banana".to_string()));
}
#[test]
fn test_current_suggestion() {
let mut input = new();
input.set_suggestions(vec!["apple".to_string(), "application".to_string()]);
input.set_value("app");
input.update_suggestions();
assert_eq!(input.current_suggestion_index(), 0);
assert_eq!(input.current_suggestion(), "apple");
input.next_suggestion();
assert_eq!(input.current_suggestion_index(), 1);
assert_eq!(input.current_suggestion(), "application");
input.previous_suggestion();
assert_eq!(input.current_suggestion_index(), 0);
assert_eq!(input.current_suggestion(), "apple");
}
#[test]
fn test_default_trait_implementation() {
let input = Model::default();
assert_eq!(input.value(), "");
assert_eq!(input.prompt, "> ");
assert!(!input.focused());
}
#[test]
fn test_current_suggestion_go_compat() {
let mut textinput = new();
let suggestion = textinput.current_suggestion();
let expected = "";
assert_eq!(
suggestion, expected,
"Error: expected no current suggestion but was {}",
suggestion
);
textinput.set_suggestions(vec![
"test1".to_string(),
"test2".to_string(),
"test3".to_string(),
]);
let suggestion = textinput.current_suggestion();
let expected = "";
assert_eq!(
suggestion, expected,
"Error: expected no current suggestion but was {}",
suggestion
);
textinput.set_value("test");
textinput.update_suggestions();
textinput.next_suggestion();
let suggestion = textinput.current_suggestion();
let expected = "test2";
assert_eq!(
suggestion, expected,
"Error: expected first suggestion but was {}",
suggestion
);
textinput.blur();
let view = textinput.view();
assert!(!view.ends_with("test2"), "Error: suggestions should not be rendered when input isn't focused. expected \"> test\" but got \"{}\"", view);
}
#[test]
fn test_slicing_outside_cap() {
let mut textinput = new();
textinput.set_placeholder("作業ディレクトリを指定してください"); textinput.set_width(32);
let _view = textinput.view();
}
#[test]
fn test_validate_func_credit_card_example() {
let mut credit_card_number = new();
credit_card_number.set_placeholder("4505 **** **** 1234");
std::mem::drop(credit_card_number.focus());
credit_card_number.set_char_limit(20);
credit_card_number.set_width(30);
credit_card_number.prompt = "".to_string();
let credit_card_validator: ValidateFunc = Box::new(|s: &str| {
if s.len() > 19 {
return Err("CCN is too long".to_string());
}
let chars: Vec<char> = s.chars().collect();
for (i, ch) in chars.iter().enumerate() {
if i == 4 || i == 9 || i == 14 {
if *ch != ' ' {
return Err("CCN must separate groups with spaces".to_string());
}
} else if !ch.is_ascii_digit() {
return Err("Invalid number format".to_string());
}
}
Ok(())
});
credit_card_number.set_validate(credit_card_validator);
credit_card_number.set_value("4505 1234 5678 1234");
assert!(
credit_card_number.err.is_none(),
"Valid credit card should not have error"
);
credit_card_number.set_value("4505 1234 5678 1234 5678");
assert!(credit_card_number.err.is_some());
assert!(credit_card_number
.err
.as_ref()
.unwrap()
.contains("too long"));
credit_card_number.set_value("45051234");
assert!(credit_card_number.err.is_some());
assert!(credit_card_number
.err
.as_ref()
.unwrap()
.contains("separate groups"));
credit_card_number.set_value("450a 1234 5678 1234");
assert!(credit_card_number.err.is_some());
}
#[test]
fn test_component_trait_implementation() {
use crate::Component;
let mut input = new();
assert!(
!Component::focused(&input),
"Input should not be focused initially"
);
let focus_cmd = Component::focus(&mut input);
assert!(
focus_cmd.is_some(),
"Component::focus should return Some(Cmd)"
);
assert!(
Component::focused(&input),
"Input should be focused after Component::focus() call"
);
Component::blur(&mut input);
assert!(
!Component::focused(&input),
"Input should not be focused after Component::blur() call"
);
for _ in 0..3 {
let _ = Component::focus(&mut input);
assert!(
Component::focused(&input),
"Input should be focused after Component::focus()"
);
Component::blur(&mut input);
assert!(
!Component::focused(&input),
"Input should not be focused after Component::blur()"
);
}
std::mem::drop(input.focus()); assert!(
Component::focused(&input),
"Component::focused should work with regular focus"
);
Component::blur(&mut input); assert!(
!input.focused(),
"Regular focused() should work with Component::blur"
);
}
mod placeholder_rendering_tests {
use super::*;
#[test]
fn test_placeholder_no_duplication_basic() {
let mut input = new();
input.set_placeholder("Nickname");
std::mem::drop(input.focus());
let view = input.view();
assert!(view.starts_with("> "), "Should start with prompt");
let n_count = view.chars().filter(|&c| c == 'N').count();
assert_eq!(
n_count, 1,
"Should have exactly one 'N' character, found {} in: '{}'",
n_count, view
);
assert!(
view.contains("ickname"),
"Should contain remaining placeholder 'ickname' in: '{}'",
view
);
}
#[test]
fn test_placeholder_specific_examples() {
let test_cases = [
("Nickname", "ickname"),
("Email", "mail"),
("Password", "assword"),
];
for (placeholder, expected_remainder) in test_cases {
let mut input = new();
input.set_placeholder(placeholder);
std::mem::drop(input.focus());
let view = input.view();
assert!(
view.starts_with("> "),
"Placeholder '{}' should start with prompt",
placeholder
);
assert!(
view.contains(expected_remainder),
"Placeholder '{}' should contain remainder '{}' but view is: '{}'",
placeholder,
expected_remainder,
view
);
let first_char = placeholder.chars().next().unwrap();
let full_placeholder_occurrences = view.matches(placeholder).count();
assert_eq!(
full_placeholder_occurrences, 0,
"Placeholder '{}' should not appear in full in view: '{}'",
placeholder, view
);
let first_char_count = view.chars().filter(|&c| c == first_char).count();
assert_eq!(
first_char_count, 1,
"First character '{}' should appear exactly once, found {} in: '{}'",
first_char, first_char_count, view
);
}
}
#[test]
fn test_placeholder_with_different_cursor_modes() {
use crate::cursor::Mode;
let mut input = new();
input.set_placeholder("Test");
std::mem::drop(input.focus());
let modes = [Mode::Blink, Mode::Static, Mode::Hide];
for mode in modes {
let _ = input.cursor.set_mode(mode);
let view = input.view();
let t_count = view.chars().filter(|&c| c == 'T').count();
assert!(
t_count <= 1,
"With cursor mode {:?}, should have at most one 'T', found {} in: '{}'",
mode,
t_count,
view
);
assert!(
view.contains("est") || mode == Mode::Hide,
"With cursor mode {:?}, should contain 'est' or be hidden, view: '{}'",
mode,
view
);
}
}
#[test]
fn test_placeholder_blurred_vs_focused() {
let mut input = new();
input.set_placeholder("Example");
input.blur();
let blurred_view = input.view();
assert!(
blurred_view.contains("xample"),
"Blurred view should show placeholder content: '{}'",
blurred_view
);
std::mem::drop(input.focus());
let focused_view = input.view();
let example_count = focused_view.matches("Example").count();
assert_eq!(
example_count, 0,
"Focused view should not contain full 'Example': '{}'",
focused_view
);
assert!(
focused_view.contains("xample"),
"Focused view should contain 'xample': '{}'",
focused_view
);
}
#[test]
fn test_placeholder_edge_cases() {
let mut input = new();
input.set_placeholder("A");
std::mem::drop(input.focus());
let view = input.view();
let a_count = view.chars().filter(|&c| c == 'A').count();
assert_eq!(
a_count, 1,
"Single char placeholder should have exactly one 'A': '{}'",
view
);
let mut input2 = new();
input2.set_placeholder("");
std::mem::drop(input2.focus());
let view2 = input2.view();
assert!(
view2.starts_with("> "),
"Empty placeholder should show prompt"
);
let mut input3 = new();
input3.set_placeholder("测试"); std::mem::drop(input3.focus());
let view3 = input3.view();
let first_char_count = view3.chars().filter(|&c| c == '测').count();
assert!(
first_char_count <= 1,
"Unicode placeholder should not duplicate first char: '{}'",
view3
);
}
#[test]
fn test_placeholder_transitions() {
let mut input = new();
input.set_placeholder("Username");
std::mem::drop(input.focus());
let empty_focused = input.view();
assert!(
empty_focused.contains("sername"),
"Should show remainder when focused and empty"
);
input.set_value("user");
let with_text = input.view();
assert!(with_text.contains("user"), "Should show actual text");
assert!(
!with_text.contains("Username"),
"Should not show placeholder when text present"
);
assert!(
!with_text.contains("sername"),
"Should not show placeholder remainder when text present"
);
input.set_value("");
let cleared = input.view();
assert!(
cleared.contains("sername"),
"Should show remainder again when cleared"
);
}
#[test]
fn test_placeholder_with_width_constraints() {
let mut input = new();
input.set_placeholder("VeryLongPlaceholderText");
input.set_width(10);
std::mem::drop(input.focus());
let view = input.view();
let v_count = view.chars().filter(|&c| c == 'V').count();
assert_eq!(
v_count, 1,
"Should have exactly one 'V' even with width constraints: '{}'",
view
);
assert!(view.len() >= 10, "Should respect minimum width");
}
#[test]
fn test_regression_original_bug_would_fail() {
let mut input = new();
input.set_placeholder("Bug");
std::mem::drop(input.focus());
let view = input.view();
assert!(
!view.contains("BBug"),
"Should not show duplicated 'BBug' - this indicates the original bug: '{}'",
view
);
let b_count = view.chars().filter(|&c| c == 'B').count();
assert_eq!(b_count, 1, "Should have exactly one 'B': '{}'", view);
assert!(
view.contains("ug"),
"Should contain remainder 'ug': '{}'",
view
);
}
#[test]
fn test_placeholder_styling_preserved() {
use lipgloss_extras::prelude::*;
let mut input = new();
input.set_placeholder("Styled");
input.placeholder_style = Style::new().foreground(Color::from("blue"));
std::mem::drop(input.focus());
let view = input.view();
let s_count = view.chars().filter(|&c| c == 'S').count();
assert_eq!(
s_count, 1,
"Styled placeholder should not duplicate: '{}'",
view
);
assert!(
view.contains("tyled"),
"Should contain styled remainder: '{}'",
view
);
}
}
}