#[cfg(test)]
mod tests {
use super::super::agent_chat::*;
use anyhow::Result;
use crossterm::terminal::{
disable_raw_mode as crossterm_disable_raw_mode,
enable_raw_mode as crossterm_enable_raw_mode,
};
use std::time::Duration;
use tokio::sync::mpsc;
#[test]
fn test_input_state_initialization() {
let state = InputState::new();
assert_eq!(state.input, "");
assert_eq!(state.cursor_pos, 0);
assert_eq!(state.selected_suggestion, 0);
assert_eq!(state.history_index, 0); assert_eq!(state.config.max_history_size, 100);
assert_eq!(state.config.debounce_ms, 150);
assert_eq!(state.config.max_suggestions, 8);
assert!(state.command_history.contains(&"/balance".to_string()));
assert!(state.command_history.contains(&"/transactions".to_string()));
}
#[test]
fn test_command_history_size_limit() {
let mut state = InputState::new();
state.config.max_history_size = 8;
state.add_to_history("cmd1".to_string());
state.add_to_history("cmd2".to_string());
state.add_to_history("cmd3".to_string());
state.add_to_history("cmd4".to_string());
assert_eq!(state.command_history.len(), 8);
assert!(!state.command_history.contains(&"/balance".to_string()));
assert!(state.command_history.contains(&"cmd4".to_string()));
}
#[test]
fn test_empty_command_filtering() {
let mut state = InputState::new();
let initial_len = state.command_history.len();
state.add_to_history("".to_string());
state.add_to_history(" ".to_string());
state.add_to_history("\t\n".to_string());
assert_eq!(state.command_history.len(), initial_len);
}
#[test]
fn test_suggestion_debouncing() {
let mut state = InputState::new();
state.config.debounce_ms = 50;
std::thread::sleep(Duration::from_millis(60));
let first_result = state.should_update_suggestions();
assert!(first_result);
assert!(!state.should_update_suggestions());
std::thread::sleep(Duration::from_millis(150));
assert!(state.should_update_suggestions());
}
#[test]
fn test_input_char_classification() {
assert!(matches!(classify_char('a'), InputChar::Regular('a')));
assert!(matches!(classify_char('1'), InputChar::Regular('1')));
assert!(matches!(classify_char('/'), InputChar::Regular('/')));
assert!(matches!(classify_char('\n'), InputChar::Enter));
assert!(matches!(classify_char('\r'), InputChar::Enter));
assert!(matches!(classify_char('\x7f'), InputChar::Backspace));
assert!(matches!(classify_char('\x08'), InputChar::Backspace));
assert!(matches!(classify_char('\x03'), InputChar::CtrlC));
assert!(matches!(classify_char('\t'), InputChar::Tab));
}
#[tokio::test]
async fn test_suggestion_generation() {
let suggestions = get_instant_suggestions("/bal").await;
assert!(!suggestions.is_empty());
let balance_suggestion = suggestions.iter().find(|s| s.text.contains("balance"));
assert!(balance_suggestion.is_some());
if let Some(suggestion) = balance_suggestion {
assert!(!suggestion.description.is_empty());
assert!(!suggestion.category.is_empty());
}
}
#[tokio::test]
async fn test_suggestion_limiting() {
let suggestions = get_instant_suggestions("/").await;
assert!(suggestions.len() <= 8); }
#[test]
fn test_input_config_validation() {
let config = InputConfig::default();
assert!(config.max_history_size > 0);
assert!(config.debounce_ms > 0);
assert!(config.max_suggestions > 0);
assert!(config.max_suggestions <= 20); assert!(config.debounce_ms <= 1000); }
#[test]
fn test_arrow_key_parsing() {
let up_sequence = b"\x1b[A";
let down_sequence = b"\x1b[B";
let left_sequence = b"\x1b[D";
let right_sequence = b"\x1b[C";
assert_eq!(up_sequence[2], b'A');
assert_eq!(down_sequence[2], b'B');
assert_eq!(left_sequence[2], b'D');
assert_eq!(right_sequence[2], b'C');
}
#[test]
fn test_terminal_error_handling() {
#[cfg(unix)]
{
let _enable_result = crossterm_enable_raw_mode();
let _disable_result = crossterm_disable_raw_mode();
}
}
#[test]
fn test_memory_management() {
let mut state = InputState::new();
let long_input = "a".repeat(10000);
state.input = long_input.clone();
state.cursor_pos = long_input.len();
assert_eq!(state.input.len(), 10000);
assert_eq!(state.cursor_pos, 10000);
for i in 0..200 {
state.add_to_history(format!("command_{}", i));
}
assert!(state.command_history.len() <= state.config.max_history_size);
}
#[tokio::test]
async fn test_concurrent_suggestions() {
let (tx, mut rx) = mpsc::unbounded_channel();
let inputs = vec!["bal", "trans", "stake", "price"];
for input in inputs {
tx.send(input.to_string()).unwrap();
}
let mut received = 0;
while let Ok(_) = rx.try_recv() {
received += 1;
}
assert_eq!(received, 4);
}
fn classify_char(ch: char) -> InputChar {
match ch {
'\n' | '\r' => InputChar::Enter,
'\x7f' | '\x08' => InputChar::Backspace,
'\x03' => InputChar::CtrlC,
'\t' => InputChar::Tab,
'\x1b' => InputChar::Arrow(ArrowKey::Up), ch if ch.is_ascii() && !ch.is_control() => InputChar::Regular(ch),
_ => InputChar::Unknown,
}
}
#[tokio::test]
async fn test_input_flow_integration() {
let mut state = InputState::new();
let (tx, _rx) = mpsc::unbounded_channel();
let input_chars = vec!['/', 'b', 'a', 'l', 'a', 'n', 'c', 'e'];
for ch in input_chars {
if let Err(e) = handle_regular_character(&mut state, ch, &tx).await {
panic!("Input handling failed: {}", e);
}
}
assert_eq!(state.input, "/balance");
assert_eq!(state.cursor_pos, 8);
}
#[tokio::test]
async fn test_error_recovery() {
let mut state = InputState::new();
state.input = "test".to_string();
state.cursor_pos = 4;
let result: Result<()> = Err(anyhow::anyhow!("Ctrl-C detected"));
assert!(result.is_err());
assert_eq!(state.input, "test");
}
}
#[cfg(test)]
mod benchmarks {
use super::super::agent_chat::*;
use std::time::Instant;
#[tokio::test]
async fn bench_suggestion_generation() {
let start = Instant::now();
for _ in 0..100 {
let _suggestions = get_instant_suggestions("/bal").await;
}
let duration = start.elapsed();
println!("100 suggestion generations took: {:?}", duration);
assert!(duration.as_millis() < 1000);
}
#[test]
fn bench_history_management() {
let mut state = InputState::new();
let start = Instant::now();
for i in 0..1000 {
state.add_to_history(format!("command_{}", i));
}
let duration = start.elapsed();
println!("1000 history additions took: {:?}", duration);
assert!(duration.as_millis() < 100);
}
}