use crossterm::event::{KeyCode, KeyEvent};
use crate::state::{AppState, SecureString};
pub(super) fn handle_password_prompt(
ke: KeyEvent,
app: &mut AppState,
input: &mut SecureString,
cursor: &mut usize,
) -> bool {
match ke.code {
KeyCode::Esc => {
app.modal = crate::state::Modal::None;
false
}
KeyCode::Enter | KeyCode::Char('\n' | '\r') => {
true
}
KeyCode::Backspace => {
if *cursor > 0 && *cursor <= input.len() {
input.remove(*cursor - 1);
*cursor -= 1;
}
false
}
KeyCode::Left => {
if *cursor > 0 {
*cursor -= 1;
}
false
}
KeyCode::Right => {
if *cursor < input.len() {
*cursor += 1;
}
false
}
KeyCode::Home => {
*cursor = 0;
false
}
KeyCode::End => {
*cursor = input.len();
false
}
KeyCode::Char(ch) => {
if !ch.is_control() {
if *cursor <= input.len() {
input.insert(*cursor, ch);
*cursor += 1;
} else {
input.push(ch);
*cursor = input.len();
}
}
false
}
_ => false,
}
}
#[cfg(test)]
mod tests {
use crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
use crate::state::{AppState, Modal};
use super::handle_password_prompt;
fn key_event(code: KeyCode, modifiers: KeyModifiers) -> KeyEvent {
let mut ke = KeyEvent::new(code, modifiers);
ke.kind = KeyEventKind::Press;
ke
}
#[test]
fn key_numpad_enter_carriage_return_returns_true() {
let mut app = AppState::default();
let mut input = crate::state::SecureString::default();
let mut cursor = 0_usize;
let ke = key_event(KeyCode::Char('\r'), KeyModifiers::empty());
let result = handle_password_prompt(ke, &mut app, &mut input, &mut cursor);
assert!(result, "numpad Enter as \\r should return true");
assert!(input.is_empty(), "input must be unchanged");
assert_eq!(cursor, 0, "cursor must be unchanged");
}
#[test]
fn key_numpad_enter_newline_returns_true() {
let mut app = AppState::default();
let mut input = crate::state::SecureString::default();
let mut cursor = 0_usize;
let ke = key_event(KeyCode::Char('\n'), KeyModifiers::empty());
let result = handle_password_prompt(ke, &mut app, &mut input, &mut cursor);
assert!(result, "numpad Enter as \\n should return true");
assert!(input.is_empty(), "input must be unchanged");
assert_eq!(cursor, 0, "cursor must be unchanged");
}
#[test]
fn key_main_enter_returns_true() {
let mut app = AppState::default();
let mut input = crate::state::SecureString::default();
let mut cursor = 0_usize;
let ke = key_event(KeyCode::Enter, KeyModifiers::empty());
let result = handle_password_prompt(ke, &mut app, &mut input, &mut cursor);
assert!(result, "main Enter should return true");
}
#[test]
fn key_esc_closes_modal_returns_false() {
let mut app = AppState {
modal: Modal::PasswordPrompt {
purpose: crate::state::modal::PasswordPurpose::Install,
items: vec![],
input: crate::state::SecureString::default(),
cursor: 0,
error: None,
},
..Default::default()
};
let mut input = crate::state::SecureString::default();
let mut cursor = 0_usize;
let ke = key_event(KeyCode::Esc, KeyModifiers::empty());
let result = handle_password_prompt(ke, &mut app, &mut input, &mut cursor);
assert!(!result, "Esc should return false");
assert!(matches!(app.modal, Modal::None), "Esc should close modal");
}
#[test]
fn key_backspace_returns_false_and_edits() {
let mut app = AppState::default();
let mut input = crate::state::SecureString::from("ab");
let mut cursor = 1_usize;
let ke = key_event(KeyCode::Backspace, KeyModifiers::empty());
let result = handle_password_prompt(ke, &mut app, &mut input, &mut cursor);
assert!(!result, "Backspace should return false");
assert_eq!(
input.as_str(),
"b",
"Backspace should remove char before cursor"
);
assert_eq!(cursor, 0, "cursor should decrement");
}
#[test]
fn key_left_right_home_end_return_false() {
let mut app = AppState::default();
let mut input = crate::state::SecureString::from("ab");
let mut cursor = 1_usize;
let ke = key_event(KeyCode::Left, KeyModifiers::empty());
let result = handle_password_prompt(ke, &mut app, &mut input, &mut cursor);
assert!(!result);
assert_eq!(cursor, 0);
let ke = key_event(KeyCode::Right, KeyModifiers::empty());
let result = handle_password_prompt(ke, &mut app, &mut input, &mut cursor);
assert!(!result);
assert_eq!(cursor, 1);
let ke = key_event(KeyCode::End, KeyModifiers::empty());
let result = handle_password_prompt(ke, &mut app, &mut input, &mut cursor);
assert!(!result);
assert_eq!(cursor, 2);
let ke = key_event(KeyCode::Home, KeyModifiers::empty());
let result = handle_password_prompt(ke, &mut app, &mut input, &mut cursor);
assert!(!result);
assert_eq!(cursor, 0);
}
#[test]
fn key_char_inserts_and_returns_false() {
let mut app = AppState::default();
let mut input = crate::state::SecureString::default();
let mut cursor = 0_usize;
let ke = key_event(KeyCode::Char('x'), KeyModifiers::empty());
let result = handle_password_prompt(ke, &mut app, &mut input, &mut cursor);
assert!(!result, "Char should return false");
assert_eq!(input.as_str(), "x", "Char should insert");
assert_eq!(cursor, 1, "cursor should advance");
}
#[test]
fn submit_with_empty_input() {
let mut app = AppState::default();
for code in [KeyCode::Enter, KeyCode::Char('\r'), KeyCode::Char('\n')] {
let mut input = crate::state::SecureString::default();
let mut cursor = 0_usize;
let ke = key_event(code, KeyModifiers::empty());
let result = handle_password_prompt(ke, &mut app, &mut input, &mut cursor);
assert!(
result,
"submit with empty input for {code:?} should return true"
);
}
}
#[test]
fn submit_with_non_empty_input() {
let mut app = AppState::default();
for code in [KeyCode::Enter, KeyCode::Char('\r'), KeyCode::Char('\n')] {
let mut input = crate::state::SecureString::from("pass");
let mut cursor = 4_usize;
let ke = key_event(code, KeyModifiers::empty());
let result = handle_password_prompt(ke, &mut app, &mut input, &mut cursor);
assert!(
result,
"submit with non-empty input for {code:?} should return true"
);
assert_eq!(input.as_str(), "pass", "input must be unchanged");
assert_eq!(cursor, 4, "cursor must be unchanged");
}
}
#[test]
fn control_chars_not_inserted_and_no_submit() {
let mut app = AppState::default();
let mut input = crate::state::SecureString::default();
let mut cursor = 0_usize;
for code in [KeyCode::Char('\t'), KeyCode::Char('\x00')] {
let ke = key_event(code, KeyModifiers::empty());
let result = handle_password_prompt(ke, &mut app, &mut input, &mut cursor);
assert!(!result, "control char {code:?} should not submit");
assert!(input.is_empty(), "control char should not be inserted");
}
}
#[test]
fn other_key_codes_return_false() {
let mut app = AppState::default();
let mut input = crate::state::SecureString::from("x");
let mut cursor = 1_usize;
for code in [KeyCode::Tab, KeyCode::Down, KeyCode::Up] {
let ke = key_event(code, KeyModifiers::empty());
let result = handle_password_prompt(ke, &mut app, &mut input, &mut cursor);
assert!(!result, "{code:?} should return false");
assert_eq!(input.as_str(), "x", "input must be unchanged");
assert_eq!(cursor, 1, "cursor must be unchanged");
}
}
#[test]
fn cursor_at_start_submit() {
let mut app = AppState::default();
let mut input = crate::state::SecureString::from("pwd");
let mut cursor = 0_usize;
let ke = key_event(KeyCode::Char('\r'), KeyModifiers::empty());
let result = handle_password_prompt(ke, &mut app, &mut input, &mut cursor);
assert!(result);
assert_eq!(input.as_str(), "pwd");
assert_eq!(cursor, 0);
}
#[test]
fn cursor_in_middle_submit() {
let mut app = AppState::default();
let mut input = crate::state::SecureString::from("pwd");
let mut cursor = 2_usize;
let ke = key_event(KeyCode::Char('\n'), KeyModifiers::empty());
let result = handle_password_prompt(ke, &mut app, &mut input, &mut cursor);
assert!(result);
assert_eq!(input.as_str(), "pwd");
assert_eq!(cursor, 2);
}
}