magi-code 0.80.1

Repository-aware CLI coding agent for terminal work
Documentation
use super::super::*;
use super::support::*;

#[test]
fn autocomplete_tab_accepts_when_visible() {
    let mut state = MissionControlState {
        prompt_editor: crate::tui::state::PromptEditor::new("/n", 2),
        focus_pane: crate::tui::state::TuiFocusPane::Prompt,
        ..Default::default()
    };
    state.recompute_autocomplete(&autocomplete_candidates());
    assert!(state.autocomplete_visible());
    assert!(matches!(
        handle_key_with_candidates(key(KeyCode::Tab), &mut state),
        InputAction::None
    ));
    assert_eq!(state.prompt_plain_text(), "/new");
    assert!(state.is_prompt_focused());
}

#[test]
fn autocomplete_enter_accepts_when_visible_without_submitting() {
    let mut state = MissionControlState {
        prompt_editor: crate::tui::state::PromptEditor::new("/n", 2),
        focus_pane: crate::tui::state::TuiFocusPane::Prompt,
        ..Default::default()
    };
    state.recompute_autocomplete(&autocomplete_candidates());
    assert!(state.autocomplete_visible());

    assert!(matches!(
        handle_key_with_candidates(key(KeyCode::Enter), &mut state),
        InputAction::None
    ));
    assert_eq!(state.prompt_plain_text(), "/new");
    assert!(state.is_prompt_focused());
}

#[test]
fn autocomplete_arrows_navigate_when_visible_and_move_cursor_when_hidden() {
    let mut state = MissionControlState {
        prompt_editor: crate::tui::state::PromptEditor::new("/q", 2),
        focus_pane: crate::tui::state::TuiFocusPane::Prompt,
        ..Default::default()
    };
    state.recompute_autocomplete(&autocomplete_candidates());
    assert!(matches!(
        handle_key_with_candidates(key(KeyCode::Down), &mut state),
        InputAction::None
    ));
    assert_eq!(state.autocomplete.as_ref().unwrap().selected, 0);
    assert_eq!(state.prompt_cursor(), 2);
    assert!(matches!(
        handle_key_with_candidates(key(KeyCode::Up), &mut state),
        InputAction::None
    ));
    assert_eq!(state.autocomplete.as_ref().unwrap().selected, 0);

    state.hide_autocomplete();
    state.set_prompt_text("a\nb", "a\nb".len());
    assert!(matches!(
        handle_key(key(KeyCode::Up), &mut state),
        InputAction::None
    ));
    assert!(state.prompt_cursor() < state.prompt_plain_text().len());
}

#[test]
fn autocomplete_esc_dismisses_before_focus_and_help_behavior() {
    let mut state = MissionControlState {
        prompt_editor: crate::tui::state::PromptEditor::new("/n", 2),
        focus_pane: crate::tui::state::TuiFocusPane::Prompt,
        show_help: true,
        ..Default::default()
    };
    state.recompute_autocomplete(&autocomplete_candidates());
    assert!(matches!(
        handle_key_with_candidates(key(KeyCode::Esc), &mut state),
        InputAction::None
    ));
    assert!(!state.autocomplete_visible());
    assert!(state.show_help);
    assert!(state.is_prompt_focused());

    state.focus_activity_tree();
    assert!(matches!(
        handle_key(key(KeyCode::Esc), &mut state),
        InputAction::None
    ));
    assert!(state.is_prompt_focused());
    assert!(!state.show_help);
}

#[test]
fn autocomplete_reopens_after_editing_an_escaped_token() {
    let candidates = vec![
        AutocompleteCandidate::slash_command("new", "new session"),
        AutocompleteCandidate::file_tag("src/main.rs"),
        AutocompleteCandidate::skill_tag("rust-dev"),
        AutocompleteCandidate::context_injection("tree", "directory tree"),
    ];
    for trigger in ['/', '@', '$', '#'] {
        for edit in [KeyCode::Backspace, KeyCode::Char('z')] {
            let mut state = MissionControlState {
                focus_pane: crate::tui::state::TuiFocusPane::Prompt,
                ..Default::default()
            };
            handle_key_with_viewport(key(KeyCode::Char(trigger)), &mut state, None, &candidates);
            assert!(state.autocomplete_visible(), "trigger {trigger}");
            handle_key_with_viewport(key(KeyCode::Esc), &mut state, None, &candidates);
            state.recompute_autocomplete(&candidates);
            assert!(!state.autocomplete_visible());

            handle_key_with_viewport(key(edit), &mut state, None, &candidates);
            assert!(!state.autocomplete_visible());
            let restore = if edit == KeyCode::Backspace {
                KeyCode::Char(trigger)
            } else {
                KeyCode::Backspace
            };
            handle_key_with_viewport(key(restore), &mut state, None, &candidates);
            assert!(
                state.autocomplete_visible(),
                "trigger {trigger}, edit {edit:?}"
            );
        }
    }
}

#[test]
fn autocomplete_recomputes_and_hides_after_prompt_edits() {
    let mut state = MissionControlState {
        focus_pane: crate::tui::state::TuiFocusPane::Prompt,
        ..Default::default()
    };
    assert!(matches!(
        handle_key_with_candidates(key(KeyCode::Char('/')), &mut state),
        InputAction::None
    ));
    assert!(state.autocomplete_visible());
    assert!(matches!(
        handle_key_with_candidates(key(KeyCode::Char('n')), &mut state),
        InputAction::None
    ));
    assert_eq!(
        state.autocomplete.as_ref().unwrap().candidates[0].name,
        "new"
    );
    assert!(matches!(
        handle_key_with_candidates(key(KeyCode::Backspace), &mut state),
        InputAction::None
    ));
    assert!(state.autocomplete_visible());
    assert!(matches!(
        handle_key_with_candidates(key(KeyCode::Backspace), &mut state),
        InputAction::None
    ));
    assert!(state.autocomplete.is_none());
}

#[test]
fn file_autocomplete_tab_accepts_active_at_token_anywhere_without_toggling_focus() {
    let candidates = vec![AutocompleteCandidate::file_tag("src/tui/input/mod.rs")];
    let mut state = MissionControlState {
        prompt_editor: crate::tui::state::PromptEditor::new("check @tuiinp", "check @tuiinp".len()),
        focus_pane: crate::tui::state::TuiFocusPane::Prompt,
        ..Default::default()
    };
    state.recompute_autocomplete(&candidates);
    assert!(state.autocomplete_visible());

    assert!(matches!(
        handle_key_with_viewport(key(KeyCode::Tab), &mut state, None, &candidates),
        InputAction::None
    ));

    assert_eq!(state.prompt_plain_text(), "check @src/tui/input/mod.rs ");
    assert!(state.is_prompt_focused());
}

#[test]
fn skill_autocomplete_accepts_active_dollar_token_anywhere_without_submitting() {
    let candidates = vec![AutocompleteCandidate::skill_tag_with_argument_hint(
        "rust-dev",
        Some("path to review"),
    )];
    let mut state = MissionControlState {
        prompt_editor: crate::tui::state::PromptEditor::new(
            "please use $rust",
            "please use $rust".len(),
        ),
        focus_pane: crate::tui::state::TuiFocusPane::Prompt,
        ..Default::default()
    };
    state.recompute_autocomplete(&candidates);
    assert!(state.autocomplete_visible());

    assert!(matches!(
        handle_key_with_viewport(key(KeyCode::Enter), &mut state, None, &candidates),
        InputAction::None
    ));

    assert_eq!(state.prompt_plain_text(), "please use $rust-dev ");
    assert!(state.is_prompt_focused());
}

#[test]
fn autocomplete_uses_visible_offsets_around_collapsed_paste_atoms() {
    let payload = "one\ntwo\n$rust";
    let candidates = vec![AutocompleteCandidate::skill_tag("rust-dev")];
    let mut state = MissionControlState {
        focus_pane: crate::tui::state::TuiFocusPane::Prompt,
        ..Default::default()
    };
    state.prompt_editor.insert_paste(payload, 1, 80);
    state.recompute_autocomplete(&candidates);
    assert!(!state.autocomplete_visible());

    state.prompt_editor.insert_paste(" $ru", 1, 80);
    state.recompute_autocomplete(&candidates);
    let autocomplete = state.autocomplete.as_ref().expect("skill suggestions");
    let label = "[Pasted #1 · 3 lines]";
    assert_eq!(autocomplete.token_start, label.len() + 1);
    assert_eq!(
        autocomplete.token_end,
        state.prompt_editor.visible_text().len()
    );

    assert!(matches!(
        handle_key_with_viewport(key(KeyCode::Enter), &mut state, None, &candidates),
        InputAction::None
    ));
    assert_eq!(
        state.prompt_editor.visible_text(),
        format!("{label} $rust-dev ")
    );
    assert_eq!(state.prompt_plain_text(), "one\ntwo\n$rust $rust-dev ");
}

#[test]
fn submitted_skill_tags_remain_literal_prompt_text() {
    let mut state = MissionControlState {
        prompt_editor: crate::tui::state::PromptEditor::new(
            "please use $rust-dev",
            "please use $rust-dev".len(),
        ),
        focus_pane: crate::tui::state::TuiFocusPane::Prompt,
        ..Default::default()
    };

    match handle_key_with_viewport(key(KeyCode::Enter), &mut state, None, &[]) {
        InputAction::Submit(prompt) => assert_eq!(prompt, "please use $rust-dev"),
        other => panic!("expected submit, got {other:?}"),
    }
    assert!(state.autocomplete.is_none());
}

fn history_state(prefix: &str) -> MissionControlState {
    MissionControlState {
        prompt_editor: crate::tui::state::PromptEditor::new(prefix, prefix.len()),
        focus_pane: crate::tui::state::TuiFocusPane::Prompt,
        prompt_history: serde_json::from_value(serde_json::json!({"entries": [
            {"text": "check café tests", "count": 2, "last_used": 1},
            {"text": "check $rust-dev tests", "count": 1, "last_used": 1}
        ]}))
        .unwrap(),
        ..Default::default()
    }
}

fn paint_history(state: &MissionControlState) {
    let mut terminal = ratatui::Terminal::new(ratatui::backend::TestBackend::new(100, 30)).unwrap();
    terminal
        .draw(|frame| crate::tui::render::draw(frame, state))
        .unwrap();
}

#[test]
fn prompt_history_right_accepts_only_painted_suggestion_at_draft_end() {
    let mut state = history_state("check café ");
    handle_key_with_candidates(key(KeyCode::Right), &mut state);
    assert_eq!(state.prompt_plain_text(), "check café ");
    paint_history(&state);
    assert_eq!(state.visible_prompt_history_suffix(), Some("tests"));
    handle_key_with_candidates(key(KeyCode::Left), &mut state);
    handle_key_with_candidates(key(KeyCode::Right), &mut state);
    assert_eq!(state.prompt_plain_text(), "check café ");
    paint_history(&state);
    handle_key_with_candidates(key(KeyCode::Right), &mut state);
    assert_eq!(state.prompt_plain_text(), "check café tests");
    state.undo_prompt_edit();
    assert_eq!(state.prompt_plain_text(), "check café ");
}

#[test]
fn prompt_history_escape_dismisses_until_edit_and_tab_enter_are_unchanged() {
    let mut state = history_state("check café ");
    paint_history(&state);
    handle_key_with_candidates(key(KeyCode::Esc), &mut state);
    paint_history(&state);
    assert_eq!(state.visible_prompt_history_suffix(), None);
    handle_key_with_candidates(key(KeyCode::Char('t')), &mut state);
    paint_history(&state);
    assert_eq!(state.visible_prompt_history_suffix(), Some("ests"));
    handle_key_with_candidates(key(KeyCode::Tab), &mut state);
    assert_eq!(state.prompt_plain_text(), "check café t");
    let mut state = history_state("check café ");
    paint_history(&state);
    assert!(
        matches!(handle_key_with_candidates(key(KeyCode::Enter), &mut state), InputAction::Submit(text) if text == "check café ")
    );
}

#[test]
fn prompt_history_yields_to_token_autocomplete() {
    let mut state = history_state("check $ru");
    let candidates = vec![AutocompleteCandidate::skill_tag("rust-dev")];
    state.recompute_autocomplete(&candidates);
    assert!(state.autocomplete_visible());
    paint_history(&state);
    assert_eq!(state.visible_prompt_history_suffix(), None);
    handle_key_with_candidates(key(KeyCode::Right), &mut state);
    assert_eq!(state.prompt_plain_text(), "check $ru");
}