a3s 0.9.8

a3s — A3S coding agent CLI; `a3s code` launches the interactive TUI
Documentation
use super::*;
use a3s_tui::style::{strip_ansi, visible_len};

fn key(code: KeyCode) -> KeyEvent {
    KeyEvent {
        code,
        modifiers: KeyModifiers::NONE,
    }
}

fn ctrl(code: KeyCode) -> KeyEvent {
    KeyEvent {
        code,
        modifiers: KeyModifiers::CONTROL,
    }
}

fn texts(history: &[String], matches: &[HistoryMatch]) -> Vec<String> {
    matches
        .iter()
        .map(|found| history[found.history_index].clone())
        .collect()
}

#[test]
fn empty_query_returns_recent_prompts_first_and_bounds_results() {
    let history = (0..120)
        .map(|index| format!("prompt {index}"))
        .collect::<Vec<_>>();

    let matches = history_matches(&history, "");

    assert_eq!(matches.len(), HISTORY_MAX_RESULTS);
    assert_eq!(history[matches[0].history_index], "prompt 119");
    assert_eq!(history[matches[99].history_index], "prompt 20");
}

#[test]
fn fuzzy_search_matches_non_contiguous_words_and_prefers_exact_text() {
    let history = vec![
        "inspect the relay dashboard".to_string(),
        "run cargo focused tests".to_string(),
        "cargo fmt then test".to_string(),
    ];

    let matches = history_matches(&history, "cargo test");
    let result = texts(&history, &matches);

    assert_eq!(result[0], "run cargo focused tests");
    assert!(result.contains(&"cargo fmt then test".to_string()));
    assert!(!result.contains(&"inspect the relay dashboard".to_string()));
}

#[test]
fn duplicate_prompts_keep_distinct_recent_positions() {
    let history = vec![
        "same prompt".to_string(),
        "other".to_string(),
        "same prompt".to_string(),
    ];

    let matches = history_matches(&history, "same");

    assert_eq!(matches.len(), 2);
    assert_eq!(matches[0].history_index, 2);
    assert_eq!(matches[1].history_index, 0);
}

#[test]
fn selection_tracks_the_same_history_entry_until_query_changes() {
    let history = vec![
        "first rust task".to_string(),
        "second rust task".to_string(),
        "third docs task".to_string(),
    ];
    let mut panel = HistoryPanel::new(&history, "rust");
    panel.move_selection(1);

    assert_eq!(panel.selected_prompt(&history), Some("first rust task"));

    let mut extended = history.clone();
    extended.push("new unrelated prompt".to_string());
    assert_eq!(panel.selected_prompt(&extended), Some("first rust task"));

    panel.query.push_str(" second");
    panel.select_first(&extended);
    assert_eq!(panel.selected_prompt(&extended), Some("second rust task"));
}

#[test]
fn ctrl_r_cycles_matches_and_enter_uses_the_selected_prompt() {
    let history = vec![
        "older test prompt".to_string(),
        "newer test prompt".to_string(),
    ];
    let mut panel = HistoryPanel::new(&history, "test");
    assert_eq!(panel.selected_prompt(&history), Some("newer test prompt"));

    assert!(matches!(
        panel.handle_key(&ctrl(KeyCode::Char('r')), &history),
        HistoryPanelAction::None
    ));
    assert_eq!(panel.selected_prompt(&history), Some("older test prompt"));

    match panel.handle_key(&key(KeyCode::Enter), &history) {
        HistoryPanelAction::Use(prompt) => assert_eq!(prompt, "older test prompt"),
        _ => panic!("Enter should accept the selected prompt"),
    }
}

#[test]
fn typing_filters_backspace_restores_and_ctrl_u_clears() {
    let history = vec!["alpha prompt".to_string(), "beta prompt".to_string()];
    let mut panel = HistoryPanel::new(&history, "");

    for character in "beta".chars() {
        panel.handle_key(&key(KeyCode::Char(character)), &history);
    }
    assert_eq!(panel.query, "beta");
    assert_eq!(panel.selected_prompt(&history), Some("beta prompt"));

    panel.handle_key(&key(KeyCode::Backspace), &history);
    assert_eq!(panel.query, "bet");
    panel.handle_key(&ctrl(KeyCode::Char('u')), &history);
    assert!(panel.query.is_empty());
    assert_eq!(panel.selected_prompt(&history), Some("beta prompt"));
}

#[test]
fn pasted_search_text_is_filtered_without_control_characters() {
    let history = vec!["cargo focused tests".to_string(), "update docs".to_string()];
    let mut panel = HistoryPanel::new(&history, "");

    panel.insert_query("cargo\nfocused\u{0}", &history);

    assert_eq!(panel.query, "cargo focused");
    assert_eq!(panel.selected_prompt(&history), Some("cargo focused tests"));
}

#[test]
fn escape_closes_without_selecting_a_prompt() {
    let history = vec!["draft".to_string()];
    let mut panel = HistoryPanel::new(&history, "");

    assert!(matches!(
        panel.handle_key(&key(KeyCode::Esc), &history),
        HistoryPanelAction::Close
    ));
}

#[test]
fn ctrl_r_is_the_history_shortcut() {
    assert!(is_history_panel_key(&ctrl(KeyCode::Char('r'))));
    assert!(!is_history_panel_key(&key(KeyCode::Char('r'))));
}

#[test]
fn history_panel_rows_are_bounded_to_terminal_width() {
    let history =
        vec!["an intentionally long historical prompt that must be clipped safely".to_string()];
    let panel = HistoryPanel::new(&history, "historical");

    let lines = history_menu_lines(&panel, &history, 34, 12);
    for line in &lines {
        assert!(
            visible_len(line) <= 34,
            "{:?}",
            strip_ansi(line).to_string()
        );
    }
}