use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use ghostscope_ui::action::Action;
use ghostscope_ui::components::command_panel::{InputHandler, OptimizedInputHandler, ScriptEditor};
use ghostscope_ui::model::panel_state::{
CommandPanelState, CommandType, InputState, InteractionMode, JkEscapeState, ScriptStatus,
};
use std::time::{Duration, Instant};
#[cfg(test)]
mod jk_escape_sequence_tests {
use super::*;
#[test]
fn test_jk_timeout_inserts_j() {
let mut state = CommandPanelState {
mode: InteractionMode::Input,
cursor_position: 4,
input_text: "test".to_string(),
jk_escape_state: JkEscapeState::J,
jk_timer: Some(Instant::now() - Duration::from_millis(500)), ..Default::default()
};
let timed_out = InputHandler::check_jk_timeout(&mut state);
assert!(timed_out);
assert_eq!(state.input_text, "testj");
assert_eq!(state.cursor_position, 5);
assert_eq!(state.jk_escape_state, JkEscapeState::None);
}
#[test]
fn test_jk_followed_by_different_char() {
let mut state = CommandPanelState {
mode: InteractionMode::Input,
input_text: "test".to_string(),
cursor_position: 4,
..Default::default()
};
InputHandler::insert_char(&mut state, 'j');
assert_eq!(state.jk_escape_state, JkEscapeState::J);
InputHandler::insert_char(&mut state, 'a');
assert_eq!(state.jk_escape_state, JkEscapeState::None);
assert_eq!(state.input_text, "testja");
assert_eq!(state.cursor_position, 6);
assert_eq!(state.mode, InteractionMode::Input); }
}
#[cfg(test)]
mod history_search_tests {
use super::*;
#[test]
fn test_ctrl_r_history_search() {
let mut state = CommandPanelState {
mode: InteractionMode::Input,
..Default::default()
};
state.command_history_manager.add_command("trace main");
state.command_history_manager.add_command("print x");
state.command_history_manager.add_command("trace test_func");
let key = KeyEvent::new(KeyCode::Char('r'), KeyModifiers::CONTROL);
let _actions = InputHandler::handle_key_event(&mut state, key);
assert!(state.is_in_history_search());
}
#[test]
fn test_history_search_navigation() {
let mut state = CommandPanelState::default();
state.command_history_manager.add_command("first command");
state.command_history_manager.add_command("second command");
state.command_history_manager.add_command("third command");
state.start_history_search();
state.update_history_search("command".to_string());
let up_key = KeyEvent::new(KeyCode::Up, KeyModifiers::empty());
InputHandler::handle_key_event(&mut state, up_key);
let down_key = KeyEvent::new(KeyCode::Down, KeyModifiers::empty());
InputHandler::handle_key_event(&mut state, down_key);
let esc_key = KeyEvent::new(KeyCode::Esc, KeyModifiers::empty());
let actions = InputHandler::handle_key_event(&mut state, esc_key);
assert!(!state.is_in_history_search());
assert!(!actions
.iter()
.any(|a| matches!(a, Action::AddResponseWithStyle { .. })));
}
}
#[cfg(test)]
mod script_editor_tests {
use super::*;
#[test]
fn test_enter_script_mode() {
let mut state = CommandPanelState {
mode: InteractionMode::Input,
input_text: "trace main".to_string(),
..Default::default()
};
let _actions = ScriptEditor::enter_script_mode(&mut state, "trace main");
assert_eq!(state.mode, InteractionMode::ScriptEditor);
assert!(state.script_cache.is_some());
let script = state.script_cache.as_ref().unwrap();
assert_eq!(script.target, "main");
assert_eq!(script.original_command, "trace main");
assert_eq!(script.status, ScriptStatus::Draft);
}
#[test]
fn test_script_editor_newline() {
let mut state = CommandPanelState::default();
ScriptEditor::enter_script_mode(&mut state, "trace test");
ScriptEditor::insert_char(&mut state, 'p');
ScriptEditor::insert_char(&mut state, 'r');
ScriptEditor::insert_newline(&mut state);
let script = state.script_cache.as_ref().unwrap();
assert_eq!(script.lines.len(), 2);
assert_eq!(script.lines[0], "pr");
assert_eq!(script.lines[1], "");
assert_eq!(script.cursor_line, 1);
assert_eq!(script.cursor_col, 0);
}
#[test]
fn test_script_editor_tab_insertion() {
let mut state = CommandPanelState::default();
ScriptEditor::enter_script_mode(&mut state, "trace test");
ScriptEditor::insert_tab(&mut state);
let script = state.script_cache.as_ref().unwrap();
assert_eq!(script.lines[0], " "); assert_eq!(script.cursor_col, 4);
}
#[test]
fn test_script_word_navigation() {
let mut state = CommandPanelState::default();
ScriptEditor::enter_script_mode(&mut state, "trace test");
if let Some(ref mut script) = state.script_cache {
script.lines[0] = "one two three four".to_string();
script.cursor_col = 0;
}
ScriptEditor::move_to_next_word(&mut state);
assert_eq!(state.script_cache.as_ref().unwrap().cursor_col, 4);
ScriptEditor::move_to_next_word(&mut state);
assert_eq!(state.script_cache.as_ref().unwrap().cursor_col, 8);
ScriptEditor::move_to_previous_word(&mut state);
assert_eq!(state.script_cache.as_ref().unwrap().cursor_col, 4);
}
#[test]
fn test_script_clear() {
let mut state = CommandPanelState::default();
ScriptEditor::enter_script_mode(&mut state, "trace test");
if let Some(ref mut script) = state.script_cache {
script.lines = vec!["line1".to_string(), "line2".to_string()];
}
ScriptEditor::clear_script(&mut state);
let script = state.script_cache.as_ref().unwrap();
assert_eq!(script.lines, vec![""]);
assert_eq!(script.cursor_line, 0);
assert_eq!(script.cursor_col, 0);
}
}
#[cfg(test)]
mod auto_suggestion_tests {
use super::*;
#[test]
fn test_tab_completion_for_commands() {
let mut state = CommandPanelState {
mode: InteractionMode::Input,
input_text: "tr".to_string(),
cursor_position: 2,
..Default::default()
};
let key = KeyEvent::new(KeyCode::Tab, KeyModifiers::empty());
InputHandler::handle_key_event(&mut state, key);
assert!(state.input_text.starts_with("trace"));
}
}
#[cfg(test)]
mod word_deletion_tests {
use super::*;
#[test]
fn test_delete_to_end() {
let mut state = CommandPanelState {
mode: InteractionMode::Input,
input_text: "one two three".to_string(),
cursor_position: 4, ..Default::default()
};
InputHandler::delete_to_end(&mut state);
assert_eq!(state.input_text, "one ");
assert_eq!(state.cursor_position, 4);
}
#[test]
fn test_delete_to_beginning() {
let mut state = CommandPanelState {
mode: InteractionMode::Input,
input_text: "one two three".to_string(),
cursor_position: 8, ..Default::default()
};
InputHandler::delete_to_beginning(&mut state);
assert_eq!(state.input_text, "three");
assert_eq!(state.cursor_position, 0);
}
}
#[cfg(test)]
mod optimized_input_tests {
use super::*;
#[test]
fn test_optimized_char_batching() {
let mut handler = OptimizedInputHandler::new();
let mut state = CommandPanelState {
mode: InteractionMode::Input,
..Default::default()
};
let chars = vec!['t', 'e', 's', 't'];
for ch in chars {
handler.handle_char_input(&mut state, ch);
}
assert_eq!(state.input_text, "test");
}
#[test]
fn test_handle_backspace() {
let mut handler = OptimizedInputHandler::new();
let mut state = CommandPanelState {
mode: InteractionMode::Input,
input_text: "test".to_string(),
cursor_position: 4,
..Default::default()
};
handler.handle_backspace(&mut state);
assert_eq!(state.input_text, "tes");
assert_eq!(state.cursor_position, 3);
}
}
#[cfg(test)]
mod command_mode_special_tests {
use super::*;
#[test]
fn test_yank_and_paste() {
let mut handler = OptimizedInputHandler::new();
let mut state = CommandPanelState {
mode: InteractionMode::Command,
static_lines: vec![ghostscope_ui::model::panel_state::StaticTextLine {
content: "line to yank".to_string(),
line_type: ghostscope_ui::model::panel_state::LineType::Response,
history_index: None,
response_type: None,
styled_content: None,
}],
..Default::default()
};
let _actions = handler.handle_char_input(&mut state, 'y');
}
#[test]
fn test_search_in_command_mode() {
let mut handler = OptimizedInputHandler::new();
let mut state = CommandPanelState {
mode: InteractionMode::Command,
..Default::default()
};
let _actions = handler.handle_char_input(&mut state, '/');
}
}
#[cfg(test)]
mod input_state_tests {
use super::*;
#[test]
fn test_input_state_transitions() {
let mut state = CommandPanelState::default();
assert!(matches!(state.input_state, InputState::Ready));
state.input_state = InputState::WaitingResponse {
command: "trace main".to_string(),
sent_time: Instant::now(),
command_type: CommandType::Script,
};
assert!(matches!(
state.input_state,
InputState::WaitingResponse { .. }
));
state.input_state = InputState::Ready;
assert!(matches!(state.input_state, InputState::Ready));
}
}
#[cfg(test)]
mod script_editor_cache_tests {
use super::*;
#[test]
fn test_empty_script_cache_restoration() {
let mut state = CommandPanelState::default();
let actions = ScriptEditor::enter_script_mode(&mut state, "trace test.c:10");
assert!(!actions.is_empty());
assert!(state.script_cache.is_some());
assert_eq!(state.mode, InteractionMode::ScriptEditor);
let cache = state.script_cache.as_ref().unwrap();
assert_eq!(cache.target, "test.c:10");
assert!(
!cache.lines.is_empty(),
"First entry should have at least one line"
);
assert_eq!(cache.lines.len(), 1);
assert_eq!(cache.lines[0], "");
assert_eq!(cache.cursor_line, 0);
assert_eq!(cache.cursor_col, 0);
let actions = ScriptEditor::exit_script_mode(&mut state);
assert!(!actions.is_empty());
assert_eq!(state.mode, InteractionMode::Input);
let cache = state.script_cache.as_ref().unwrap();
assert!(cache.saved_scripts.contains_key("test.c:10"));
let saved = cache.saved_scripts.get("test.c:10").unwrap();
assert_eq!(saved.content, "");
assert_eq!(saved.cursor_line, 0);
assert_eq!(saved.cursor_col, 0);
let actions = ScriptEditor::enter_script_mode(&mut state, "trace test.c:10");
assert!(!actions.is_empty());
assert_eq!(state.mode, InteractionMode::ScriptEditor);
let cache = state.script_cache.as_ref().unwrap();
assert!(
!cache.lines.is_empty(),
"Second entry should have at least one line"
);
assert_eq!(cache.lines.len(), 1);
assert_eq!(cache.lines[0], "");
assert_eq!(cache.cursor_line, 0);
assert_eq!(cache.cursor_col, 0);
}
#[test]
fn test_nonempty_script_cache_restoration() {
let mut state = CommandPanelState::default();
ScriptEditor::enter_script_mode(&mut state, "trace main");
ScriptEditor::insert_char(&mut state, 'p');
ScriptEditor::insert_char(&mut state, 'r');
ScriptEditor::insert_char(&mut state, 'i');
ScriptEditor::insert_newline(&mut state);
ScriptEditor::insert_char(&mut state, 'n');
ScriptEditor::insert_char(&mut state, 't');
let cache = state.script_cache.as_ref().unwrap();
assert_eq!(cache.lines.len(), 2);
assert_eq!(cache.lines[0], "pri");
assert_eq!(cache.lines[1], "nt");
assert_eq!(cache.cursor_line, 1);
assert_eq!(cache.cursor_col, 2);
ScriptEditor::exit_script_mode(&mut state);
ScriptEditor::enter_script_mode(&mut state, "trace main");
let cache = state.script_cache.as_ref().unwrap();
assert_eq!(cache.lines.len(), 2);
assert_eq!(cache.lines[0], "pri");
assert_eq!(cache.lines[1], "nt");
assert_eq!(cache.cursor_line, 1);
assert_eq!(cache.cursor_col, 2);
}
}