use anyhow::Result;
use chrono::{TimeZone, Utc};
use command_vault::{
db::{Command, Database},
ui::{app::App, AddCommandApp},
};
use crate::test_utils::create_test_db;
use command_vault::ui::add::InputMode;
use ratatui::style::Color;
mod test_utils;
fn create_test_commands() -> Vec<Command> {
vec![
Command {
id: Some(1),
command: "ls -la".to_string(),
timestamp: Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap(),
directory: "/home/user".to_string(),
tags: vec!["file".to_string(), "list".to_string()],
parameters: vec![],
},
Command {
id: Some(2),
command: "git status".to_string(),
timestamp: Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 1).unwrap(),
directory: "/home/user/project".to_string(),
tags: vec!["git".to_string()],
parameters: vec![],
},
Command {
id: Some(3),
command: "docker ps".to_string(),
timestamp: Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 2).unwrap(),
directory: "/home/user".to_string(),
tags: vec!["docker".to_string()],
parameters: vec![],
},
]
}
#[test]
fn test_app_new() -> Result<()> {
let (mut db, _dir) = create_test_db()?;
let commands = vec![];
let app = App::new(commands.clone(), &mut db, false);
assert_eq!(app.commands.len(), 0);
assert_eq!(app.selected, None);
assert_eq!(app.show_help, false);
assert_eq!(app.message, None);
assert_eq!(app.filter_text, "");
assert_eq!(app.filtered_commands.len(), 0);
assert_eq!(app.debug_mode, false);
Ok(())
}
#[test]
fn test_app_filter() -> Result<()> {
let (mut db, _dir) = create_test_db()?;
let commands = vec![
Command {
id: Some(1),
command: "ls -l".to_string(),
timestamp: Utc::now(),
directory: "/".to_string(),
tags: vec![],
parameters: vec![],
},
Command {
id: Some(2),
command: "pwd".to_string(),
timestamp: Utc::now(),
directory: "/".to_string(),
tags: vec![],
parameters: vec![],
},
];
let mut app = App::new(commands.clone(), &mut db, false);
app.filter_text = "ls".to_string();
app.update_filtered_commands();
assert_eq!(app.filtered_commands.len(), 1);
assert_eq!(app.filtered_commands[0], 0);
Ok(())
}
#[test]
fn test_app_filtering() -> Result<()> {
let (mut db, _dir) = create_test_db()?;
let commands = create_test_commands();
let mut app = App::new(commands.clone(), &mut db, false);
assert_eq!(app.filter_text, "");
assert_eq!(app.filtered_commands.len(), 3);
app.filter_text = "git".to_string();
app.update_filtered_commands();
assert_eq!(app.filtered_commands.len(), 1);
assert_eq!(app.commands[app.filtered_commands[0]].command, "git status");
app.filter_text = "file".to_string();
app.update_filtered_commands();
assert_eq!(app.filtered_commands.len(), 1);
assert_eq!(app.commands[app.filtered_commands[0]].command, "ls -la");
app.filter_text = "project".to_string();
app.update_filtered_commands();
assert_eq!(app.filtered_commands.len(), 1);
assert_eq!(app.commands[app.filtered_commands[0]].command, "git status");
app.filter_text = "nonexistent".to_string();
app.update_filtered_commands();
assert_eq!(app.filtered_commands.len(), 0);
Ok(())
}
#[test]
fn test_add_command_app_new() {
let app = AddCommandApp::new();
assert!(app.command.is_empty());
assert!(app.tags.is_empty());
assert!(app.current_tag.is_empty());
assert_eq!(app.command_cursor, 0);
}
#[test]
fn test_add_command_app_command_input() {
let mut app = AddCommandApp::new();
app.set_command("ls -la".to_string());
assert_eq!(app.command, "ls -la");
}
#[test]
fn test_add_command_app_tag_input() {
let mut app = AddCommandApp::new();
app.set_tags(vec!["git".to_string()]);
assert_eq!(app.tags, vec!["git"]);
app.set_tags(vec!["git".to_string(), "docker".to_string()]);
assert_eq!(app.tags, vec!["git", "docker"]);
}
#[test]
fn test_app_message() -> Result<()> {
let (mut db, _dir) = create_test_db()?;
let mut commands = create_test_commands();
for cmd in &mut commands {
cmd.id = Some(db.add_command(cmd)?);
}
let mut app = App::new(commands.clone(), &mut db, false);
assert_eq!(app.message, None);
app.message = Some(("Command copied to clipboard!".to_string(), Color::Green));
assert_eq!(app.message, Some(("Command copied to clipboard!".to_string(), Color::Green)));
app.message = Some(("Failed to delete command".to_string(), Color::Red));
assert_eq!(app.message, Some(("Failed to delete command".to_string(), Color::Red)));
app.message = None;
assert_eq!(app.message, None);
app.message = Some(("Type to filter commands...".to_string(), Color::Blue));
assert_eq!(app.message, Some(("Type to filter commands...".to_string(), Color::Blue)));
app.message = Some(("Delete operation cancelled".to_string(), Color::Yellow));
assert_eq!(app.message, Some(("Delete operation cancelled".to_string(), Color::Yellow)));
Ok(())
}
#[test]
fn test_app_selection() -> Result<()> {
let (mut db, _dir) = create_test_db()?;
let commands = create_test_commands();
let mut app = App::new(commands.clone(), &mut db, false);
assert_eq!(app.selected, None);
app.selected = Some(0);
assert_eq!(app.selected, Some(0));
app.selected = Some(1);
assert_eq!(app.selected, Some(1));
app.selected = Some(app.commands.len() - 1);
assert_eq!(app.selected, Some(2));
Ok(())
}
#[test]
fn test_app_confirm_delete() -> Result<()> {
let (mut db, _dir) = create_test_db()?;
let commands = create_test_commands();
let mut app = App::new(commands.clone(), &mut db, false);
app.selected = Some(0);
app.confirm_delete = Some(0);
assert_eq!(app.confirm_delete, Some(0));
app.confirm_delete = None;
assert_eq!(app.confirm_delete, None);
Ok(())
}
#[test]
fn test_app_debug_mode() -> Result<()> {
let (mut db, _dir) = create_test_db()?;
let commands = create_test_commands();
let mut app = App::new(commands.clone(), &mut db, true);
assert_eq!(app.debug_mode, true);
let mut app = App::new(commands.clone(), &mut db, false);
assert_eq!(app.debug_mode, false);
Ok(())
}
#[test]
fn test_app_help_toggle() -> Result<()> {
let (mut db, _dir) = create_test_db()?;
let commands = create_test_commands();
let mut app = App::new(commands.clone(), &mut db, false);
assert_eq!(app.show_help, false);
app.show_help = true;
assert_eq!(app.show_help, true);
app.show_help = false;
assert_eq!(app.show_help, false);
Ok(())
}
#[test]
fn test_add_command_app_cursor_movement() {
let mut app = AddCommandApp::new();
assert_eq!(app.command_cursor, 0);
app.set_command("ls -la".to_string());
app.command_cursor = 3;
assert_eq!(app.command_cursor, 3);
}
#[test]
fn test_app_filter_clear() -> Result<()> {
let (mut db, _dir) = create_test_db()?;
let commands = create_test_commands();
let mut app = App::new(commands.clone(), &mut db, false);
assert_eq!(app.filter_text, "");
assert_eq!(app.filtered_commands.len(), 3);
app.filter_text = "git".to_string();
app.update_filtered_commands();
assert_eq!(app.filtered_commands.len(), 1);
assert_eq!(app.commands[app.filtered_commands[0]].command, "git status");
app.filter_text.clear();
app.update_filtered_commands();
assert_eq!(app.filter_text, "");
assert_eq!(app.filtered_commands.len(), 3);
Ok(())
}
#[test]
fn test_add_command_app_tag_operations() {
let mut app = AddCommandApp::new();
app.current_tag = "git".to_string();
app.tags.push(app.current_tag.clone());
assert_eq!(app.tags, vec!["git"]);
app.current_tag.clear();
assert!(app.current_tag.is_empty());
app.tags.push("docker".to_string());
app.tags.push("test".to_string());
assert_eq!(app.tags, vec!["git", "docker", "test"]);
}
#[test]
fn test_add_command_app_input_modes() {
use command_vault::ui::add::InputMode;
let mut app = AddCommandApp::new();
assert!(matches!(app.input_mode, InputMode::Command));
app.input_mode = InputMode::Tag;
assert!(matches!(app.input_mode, InputMode::Tag));
app.input_mode = InputMode::Confirm;
assert!(matches!(app.input_mode, InputMode::Confirm));
app.input_mode = InputMode::Help;
assert!(matches!(app.input_mode, InputMode::Help));
app.previous_mode = InputMode::Command;
assert!(matches!(app.previous_mode, InputMode::Command));
}
#[test]
fn test_add_command_app_tag_suggestions() {
let mut app = AddCommandApp::new();
assert!(app.suggested_tags.is_empty());
app.suggested_tags = vec!["git".to_string(), "docker".to_string()];
assert_eq!(app.suggested_tags, vec!["git", "docker"]);
app.suggested_tags.clear();
assert!(app.suggested_tags.is_empty());
}
#[test]
fn test_add_command_app_multiline() {
let mut app = AddCommandApp::new();
assert_eq!(app.command_line, 0);
app.set_command("line1\nline2\nline3".to_string());
assert_eq!(app.command, "line1\nline2\nline3");
app.command_cursor = 6; assert_eq!(app.command_cursor, 6);
}
#[test]
fn test_add_command_app_key_events() {
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
let mut app = AddCommandApp::new();
app.handle_key_event(KeyEvent::new(KeyCode::Char('l'), KeyModifiers::empty()));
app.handle_key_event(KeyEvent::new(KeyCode::Char('s'), KeyModifiers::empty()));
assert_eq!(app.command, "ls");
assert_eq!(app.command_cursor, 2);
app.handle_key_event(KeyEvent::new(KeyCode::Backspace, KeyModifiers::empty()));
assert_eq!(app.command, "l");
assert_eq!(app.command_cursor, 1);
app.handle_key_event(KeyEvent::new(KeyCode::Left, KeyModifiers::empty()));
assert_eq!(app.command_cursor, 0);
app.handle_key_event(KeyEvent::new(KeyCode::Right, KeyModifiers::empty()));
assert_eq!(app.command_cursor, 1);
app.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::SHIFT));
app.handle_key_event(KeyEvent::new(KeyCode::Char('a'), KeyModifiers::empty()));
assert_eq!(app.command, "l\na");
assert_eq!(app.command_line, 1);
app.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::empty()));
assert_eq!(app.input_mode, InputMode::Tag);
app.handle_key_event(KeyEvent::new(KeyCode::Char('t'), KeyModifiers::empty()));
app.handle_key_event(KeyEvent::new(KeyCode::Char('a'), KeyModifiers::empty()));
app.handle_key_event(KeyEvent::new(KeyCode::Char('g'), KeyModifiers::empty()));
assert_eq!(app.current_tag, "tag");
app.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::empty()));
assert_eq!(app.tags, vec!["tag"]);
assert_eq!(app.current_tag, "");
app.handle_key_event(KeyEvent::new(KeyCode::Char('?'), KeyModifiers::empty()));
assert_eq!(app.input_mode, InputMode::Help);
assert_eq!(app.previous_mode, InputMode::Tag);
app.handle_key_event(KeyEvent::new(KeyCode::Char('?'), KeyModifiers::empty()));
assert_eq!(app.input_mode, InputMode::Tag);
}
#[test]
fn test_add_command_app_help_mode() {
use command_vault::ui::add::InputMode;
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
let mut app = AddCommandApp::new();
app.handle_key_event(KeyEvent::new(KeyCode::Char('?'), KeyModifiers::empty()));
assert!(matches!(app.input_mode, InputMode::Help));
assert!(matches!(app.previous_mode, InputMode::Command));
app.handle_key_event(KeyEvent::new(KeyCode::Char('?'), KeyModifiers::empty()));
assert!(matches!(app.input_mode, InputMode::Command));
app.handle_key_event(KeyEvent::new(KeyCode::Char('?'), KeyModifiers::empty()));
assert!(matches!(app.input_mode, InputMode::Help));
app.handle_key_event(KeyEvent::new(KeyCode::Esc, KeyModifiers::empty()));
assert!(matches!(app.input_mode, InputMode::Command));
}
#[test]
fn test_add_command_app_multiline_command() {
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
let mut app = AddCommandApp::new();
app.handle_key_event(KeyEvent::new(KeyCode::Char('l'), KeyModifiers::empty()));
app.handle_key_event(KeyEvent::new(KeyCode::Char('s'), KeyModifiers::empty()));
app.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::SHIFT));
app.handle_key_event(KeyEvent::new(KeyCode::Char('p'), KeyModifiers::empty()));
app.handle_key_event(KeyEvent::new(KeyCode::Char('w'), KeyModifiers::empty()));
app.handle_key_event(KeyEvent::new(KeyCode::Char('d'), KeyModifiers::empty()));
assert_eq!(app.command, "ls\npwd");
assert_eq!(app.command_line, 1);
assert_eq!(app.command_cursor, 6);
}
#[test]
fn test_app_command_copy() -> Result<()> {
let (mut db, _dir) = create_test_db()?;
let commands = create_test_commands();
let mut app = App::new(commands.clone(), &mut db, false);
app.selected = Some(0);
assert_eq!(app.selected, Some(0));
assert_eq!(app.filtered_commands[0], 0);
assert_eq!(app.commands[app.filtered_commands[0]].command, "ls -la");
if let Some(selected) = app.selected {
if let Some(&idx) = app.filtered_commands.get(selected) {
if let Some(cmd) = app.commands.get(idx) {
assert_eq!(cmd.command, "ls -la");
}
}
}
Ok(())
}
#[test]
fn test_app_navigation() -> Result<()> {
let (mut db, _dir) = create_test_db()?;
let commands = create_test_commands();
let mut app = App::new(commands.clone(), &mut db, false);
assert_eq!(app.selected, None);
assert_eq!(app.filtered_commands.len(), 3);
app.selected = Some(0);
assert_eq!(app.selected, Some(0));
app.selected = Some(1);
assert_eq!(app.selected, Some(1));
app.selected = Some(0);
assert_eq!(app.selected, Some(0));
app.selected = Some(0);
assert_eq!(app.selected, Some(0));
app.selected = Some(2);
assert_eq!(app.selected, Some(2));
app.filter_text = "git".to_string();
app.update_filtered_commands();
assert_eq!(app.filtered_commands.len(), 1);
app.selected = Some(0);
assert_eq!(app.selected, Some(0));
Ok(())
}
#[test]
fn test_app_delete_command() -> Result<()> {
let (mut db, _dir) = create_test_db()?;
let mut commands = create_test_commands();
for cmd in &mut commands {
cmd.id = Some(db.add_command(cmd)?);
}
let mut app = App::new(commands.clone(), &mut db, false);
assert_eq!(app.commands.len(), 3);
assert_eq!(app.filtered_commands.len(), 3);
assert_eq!(app.confirm_delete, None);
app.selected = Some(0);
assert_eq!(app.selected, Some(0));
app.confirm_delete = Some(0);
assert_eq!(app.confirm_delete, Some(0));
app.confirm_delete = None;
assert_eq!(app.confirm_delete, None);
assert_eq!(app.commands.len(), 3);
app.confirm_delete = Some(0);
assert_eq!(app.confirm_delete, Some(0));
if let Some(selected) = app.selected {
if let Some(confirm_idx) = app.confirm_delete {
if confirm_idx == selected {
if let Some(&filtered_idx) = app.filtered_commands.get(selected) {
if let Some(command_id) = app.commands[filtered_idx].id {
match app.db.delete_command(command_id) {
Ok(_) => {
app.commands.remove(filtered_idx);
app.update_filtered_commands();
if app.filtered_commands.is_empty() {
app.selected = None;
} else {
app.selected = Some(selected.min(app.filtered_commands.len() - 1));
}
}
Err(e) => panic!("Failed to delete command: {}", e),
}
app.confirm_delete = None;
}
}
}
}
}
assert_eq!(app.commands.len(), 2);
assert_eq!(app.filtered_commands.len(), 2);
assert_eq!(app.confirm_delete, None);
assert_eq!(app.selected, Some(0));
Ok(())
}
#[test]
fn test_app_edit_command() -> Result<()> {
let (mut db, _dir) = create_test_db()?;
let mut commands = create_test_commands();
for cmd in &mut commands {
cmd.id = Some(db.add_command(cmd)?);
}
let mut app = App::new(commands.clone(), &mut db, false);
assert_eq!(app.commands.len(), 3);
assert_eq!(app.filtered_commands.len(), 3);
app.selected = Some(0);
assert_eq!(app.selected, Some(0));
let original_command = app.commands[0].clone();
assert_eq!(original_command.command, "ls -la");
let updated_command = Command {
id: original_command.id,
command: "ls -lah".to_string(),
timestamp: original_command.timestamp,
directory: original_command.directory.clone(),
tags: vec!["test".to_string(), "updated".to_string()],
parameters: vec![],
};
app.db.update_command(&updated_command)?;
app.commands[0] = updated_command.clone();
assert_eq!(app.commands[0].command, "ls -lah");
assert_eq!(app.commands[0].tags, vec!["test".to_string(), "updated".to_string()]);
assert_eq!(app.commands[0].id, original_command.id);
Ok(())
}
#[test]
fn test_app_help_mode() -> Result<()> {
let (mut db, _dir) = create_test_db()?;
let mut commands = create_test_commands();
for cmd in &mut commands {
cmd.id = Some(db.add_command(cmd)?);
}
let mut app = App::new(commands.clone(), &mut db, false);
assert_eq!(app.show_help, false);
app.show_help = true;
assert_eq!(app.show_help, true);
app.show_help = false;
assert_eq!(app.show_help, false);
assert_eq!(app.commands.len(), 3);
assert_eq!(app.filtered_commands.len(), 3);
assert_eq!(app.selected, None);
assert_eq!(app.message, None);
assert_eq!(app.filter_text, "");
assert_eq!(app.confirm_delete, None);
assert_eq!(app.debug_mode, false);
Ok(())
}