magi-code 0.62.0

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

#[test]
fn picker_clamp_resets_empty_lists() {
    let mut session = SessionPickerState {
        rows: Vec::new(),
        selected: 4,
        focus: SessionPickerFocus::List,
    };
    session.clamp();
    assert_eq!(session.selected, 0);

    let mut skills = SkillsModalState {
        rows: Vec::new(),
        selected: 4,
        settings_scope: crate::config::SettingsScope::Global,
        scope_label: "Global".to_string(),
    };
    skills.clamp();
    assert_eq!(skills.selected, 0);
}

#[test]
fn picker_clamp_bounds_selected_past_end_for_all_pickers() {
    assert_all_picker_clamps(9, 0, 2, (2, 1));
}

#[test]
fn picker_clamp_moves_scroll_up_when_selected_is_above_visible_window_for_all_pickers() {
    assert_all_picker_clamps(1, 2, 2, (1, 1));
}

#[test]
fn picker_clamp_moves_scroll_up_when_selected_is_below_visible_window_for_all_pickers() {
    assert_all_picker_clamps(2, 0, 2, (2, 1));
}

#[test]
fn picker_clamp_treats_zero_visible_rows_as_one_for_all_pickers() {
    assert_all_picker_clamps(1, 0, 0, (1, 1));
}

#[test]
fn matching_session_title_update_updates_footer_and_picker() {
    let mut state = MissionControlState {
        footer_session_id: Some("session-abcdef12".to_string()),
        footer_session: "abcd...ef12".to_string(),
        ..MissionControlState::default()
    };
    state.open_session_picker(
        vec![
            SessionPickerRow {
                id: "session-abcdef12".to_string(),
                label: "abcd...ef12".to_string(),
                is_current: true,
                preview: None,
            },
            SessionPickerRow {
                id: "session-other99".to_string(),
                label: "othe...er99".to_string(),
                is_current: false,
                preview: None,
            },
        ],
        crate::tui::input::DEFAULT_PICKER_VISIBLE_ROWS as usize,
    );

    state.apply_session_title_update("session-abcdef12", "Friendly title");

    assert_eq!(state.footer_session, "Friendly title  [sess...ef12]");
    let rows = &state.modals.session_picker.as_ref().unwrap().rows;
    assert_eq!(rows[0].label, "Friendly title  [sess...ef12]");
    assert_eq!(rows[1].label, "othe...er99");
}

#[test]
fn stale_session_title_update_does_not_update_active_footer() {
    let mut state = MissionControlState {
        footer_session_id: Some("session-active1".to_string()),
        footer_session: "acti...ive1".to_string(),
        ..MissionControlState::default()
    };
    state.open_session_picker(
        vec![SessionPickerRow {
            id: "session-stale99".to_string(),
            label: "stal...le99".to_string(),
            is_current: false,
            preview: None,
        }],
        crate::tui::input::DEFAULT_PICKER_VISIBLE_ROWS as usize,
    );

    state.apply_session_title_update("session-stale99", "Old run title");

    assert_eq!(state.footer_session, "acti...ive1");
    assert_eq!(
        state.modals.session_picker.as_ref().unwrap().rows[0].label,
        "Old run title  [sess...le99]"
    );
}

#[test]
fn session_picker_open_hides_autocomplete_and_clamps_empty_state() {
    let mut state = MissionControlState {
        autocomplete: Some(AutocompleteState {
            candidates: Vec::new(),
            selected: 0,
            token_start: 0,
            token_end: 2,
            token: "/s".into(),
        }),
        ..Default::default()
    };
    state.open_session_picker(Vec::new(), 3);
    let picker = state.modals.session_picker.as_ref().unwrap();
    assert_eq!(picker.selected, 0);
    assert_eq!(state.selected_session_id(), None);
    assert!(!state.autocomplete_visible());
}

#[test]
fn session_picker_keyboard_navigation_keeps_selection_visible() {
    let mut state = MissionControlState::default();
    state.open_session_picker(
        (0..5)
            .map(|index| SessionPickerRow {
                id: format!("session-{index}"),
                label: format!("session-{index}"),
                is_current: false,
                preview: None,
            })
            .collect(),
        2,
    );
    for _ in 0..3 {
        state.session_picker_down(2);
    }
    let picker = state.modals.session_picker.as_ref().unwrap();
    assert_eq!(picker.selected, 3);
    assert!(state.scroll_offset(&state.scroll_views.session_list) + 2 > picker.selected);
}

#[test]
fn session_picker_scroll_clamps_at_bounds() {
    let mut state = MissionControlState::default();
    state.open_session_picker(
        (0..2)
            .map(|index| SessionPickerRow {
                id: format!("session-{index}"),
                label: format!("session-{index}"),
                is_current: false,
                preview: None,
            })
            .collect(),
        1,
    );
    assert!(!state.session_picker_up(1));
    state.session_picker_down(1);
    assert!(!state.session_picker_down(1));
    let picker = state.modals.session_picker.as_ref().unwrap();
    assert_eq!(
        (
            picker.selected,
            state.scroll_offset(&state.scroll_views.session_list)
        ),
        (1, 1)
    );
}

#[test]
fn reset_for_new_session_closes_session_picker() {
    let mut state = MissionControlState::default();
    state.open_session_picker(
        vec![SessionPickerRow {
            id: "abc".into(),
            label: "abc".into(),
            is_current: false,
            preview: None,
        }],
        1,
    );
    state.reset_for_new_session();
    assert!(!state.session_picker_visible());
}

#[test]
fn reset_for_new_session_clears_previous_session_display_state() {
    let mut state = MissionControlState {
        selected: 1,
        context_usage: Some(ContextUsageState {
            current_tokens: 12,
            max_tokens: 34,
            reasoning_tokens: None,
            source: ContextUsageSource::FallbackEstimate,
            request_sequence: 0,
        }),
        status: "keep status".to_string(),
        focus_pane: crate::tui::state::TuiFocusPane::Prompt,
        show_help: true,
        autocomplete: Some(AutocompleteState {
            candidates: vec![AutocompleteCandidate::slash_command(
                "new",
                "start a new session",
            )],
            selected: 0,
            token_start: 0,
            token_end: 2,
            token: "/n".to_string(),
        }),
        autocomplete_dismissed_token: Some("/n".to_string()),
        ..Default::default()
    };
    state.apply_output_event(&OutputEvent::UserPrompt {
        text: "old prompt".to_string(),
    });
    let root = ActivityId::new("root");
    let child = ActivityId::new("child");
    state.apply_activity_event(ActivityEvent::Started {
        id: root.clone(),
        parent_id: None,
        kind: ActivityKind::Tool,
        status: ActivityStatus::Running,
        metadata: ActivityMetadata::new("root"),
    });
    state.apply_activity_event(ActivityEvent::Started {
        id: child,
        parent_id: Some(root),
        kind: ActivityKind::Tool,
        status: ActivityStatus::Running,
        metadata: ActivityMetadata::new("child"),
    });

    state.reset_for_new_session();

    assert!(state.nodes.is_empty());
    assert!(state.roots.is_empty());
    assert!(state.children.is_empty());
    assert!(state.expanded.is_empty());
    assert!(state.transcript.is_empty());
    assert!(state.context_usage.is_none());
    assert_eq!(state.selected_activity_text(), "No activity selected.");
    assert_eq!(state.selected, 0);
    assert_eq!(state.transcript_scroll_offset(), 0);
    assert_eq!(state.activity_scroll_offset(), 0);
    assert_eq!(state.detail_offset(), 0);
    assert_eq!(state.help_offset(), 0);
    assert!(!state.autocomplete_visible());
    assert!(state.autocomplete_dismissed_token.is_none());
    assert_eq!(state.status, "keep status");
    assert!(state.is_prompt_focused());
    assert!(state.show_help);
}

#[test]
fn prompt_up_down_move_between_wrapped_rows_and_clamp_column() {
    let mut state = MissionControlState {
        input: "abcdefghijkl".to_string(),
        prompt_cursor: 2,
        ..Default::default()
    };

    state.move_prompt_cursor_down(2, 4);
    assert_eq!(state.prompt_cursor, 6);
    state.move_prompt_cursor_down(2, 4);
    assert_eq!(state.prompt_cursor, 10);
    state.move_prompt_cursor_down(2, 4);
    assert_eq!(state.prompt_cursor, 10);
    state.move_prompt_cursor_up(2, 4);
    assert_eq!(state.prompt_cursor, 6);
}

#[test]
fn prompt_manual_scroll_clamps_without_changing_cursor_or_input() {
    let mut state = MissionControlState {
        input: "x".repeat(20),
        prompt_cursor: 2,
        ..Default::default()
    };

    assert!(state.scroll_prompt_down_by(10, 2, 4));
    assert_eq!(state.prompt_offset(), 3);
    assert_eq!(state.prompt_cursor, 2);
    assert_eq!(state.input, "x".repeat(20));
    assert!(!state.scroll_prompt_down_by(10, 2, 4));
    assert!(state.scroll_prompt_up_by(10, 2, 4));
    assert_eq!(state.prompt_offset(), 0);
}

#[test]
fn transcript_mouse_scroll_clamps_stored_scroll_and_reports_boundaries() {
    let mut state = MissionControlState::default();
    for line in 0..6 {
        state.push_transcript(format!("line {line}"));
    }

    let expected_overflow = state.transcript_visual_rows(80).saturating_sub(4);
    assert_eq!(state.transcript_scroll_overflow(4, 80), expected_overflow);

    while state.scroll_transcript_mouse_up(4, 80) {}
    assert_eq!(state.transcript_scroll_offset(), expected_overflow);
    assert!(!state.scroll_transcript_mouse_up(4, 80));
    assert_eq!(state.transcript_scroll_offset(), expected_overflow);

    while state.scroll_transcript_mouse_down(4, 80) {}
    assert_eq!(state.transcript_scroll_offset(), 0);
    assert!(!state.scroll_transcript_mouse_down(4, 80));
}

#[test]
fn detail_mouse_scroll_clamps_to_wrapped_overflow() {
    let mut state = MissionControlState::default();
    state.apply_activity_event(ActivityEvent::Started {
        id: ActivityId::new("tool-1"),
        parent_id: None,
        kind: ActivityKind::Tool,
        status: ActivityStatus::Running,
        metadata: ActivityMetadata::new("long detail"),
    });
    state.apply_activity_event(ActivityEvent::Delta {
        id: ActivityId::new("tool-1"),
        preview: "abcdefghij".repeat(10),
    });

    let overflow = state.detail_overflow(3, 10);
    assert!(overflow >= usize::from(MOUSE_WHEEL_SCROLL_ROWS));
    assert!(state.scroll_detail_mouse_down(3, 10));
    assert_eq!(state.detail_offset(), usize::from(MOUSE_WHEEL_SCROLL_ROWS));
    while state.scroll_detail_mouse_down(3, 10) {}
    assert_eq!(state.detail_offset(), overflow);
    assert!(!state.scroll_detail_mouse_down(3, 10));
    assert_eq!(state.detail_offset(), overflow);
    assert!(state.scroll_detail_up_by(MOUSE_WHEEL_SCROLL_ROWS, 3, 10));
}

#[test]
fn help_clamps_to_wrapped_overflow() {
    let mut state = MissionControlState::default();
    let help_text = (0..12)
        .map(|line| format!("help line {line}"))
        .collect::<Vec<_>>()
        .join("\n");
    let overflow = state.help_overflow(4, 80, &help_text);
    assert!(overflow >= usize::from(MOUSE_WHEEL_SCROLL_ROWS));

    assert_eq!(state.help_render_offset(4, 80, &help_text), 0);
    assert!(state.scroll_help_down_by(MOUSE_WHEEL_SCROLL_ROWS, 4, 80, &help_text));
    assert_eq!(state.help_offset(), usize::from(MOUSE_WHEEL_SCROLL_ROWS));
    assert_eq!(
        state.help_render_offset(4, 80, &help_text),
        usize::from(MOUSE_WHEEL_SCROLL_ROWS)
    );
    while state.scroll_help_down_by(MOUSE_WHEEL_SCROLL_ROWS, 4, 80, &help_text) {}
    assert_eq!(state.help_offset(), overflow);
    assert!(!state.scroll_help_down_by(MOUSE_WHEEL_SCROLL_ROWS, 4, 80, &help_text));
    assert!(state.scroll_help_up_by(MOUSE_WHEEL_SCROLL_ROWS, 4, 80, &help_text));
    assert_eq!(
        state.help_offset(),
        overflow.saturating_sub(usize::from(MOUSE_WHEEL_SCROLL_ROWS))
    );
}

#[test]
fn keyboard_detail_keeps_five_row_step_and_render_clamps() {
    let mut state = MissionControlState::default();
    state.apply_activity_event(ActivityEvent::Started {
        id: ActivityId::new("tool-1"),
        parent_id: None,
        kind: ActivityKind::Tool,
        status: ActivityStatus::Running,
        metadata: ActivityMetadata::new("detail"),
    });

    state.scroll_detail_down();
    assert_eq!(
        state.detail_offset(),
        usize::from(KEYBOARD_DETAIL_SCROLL_ROWS)
    );
    assert_eq!(state.detail_offset(), 1);
    state.scroll_detail_up();
    assert_eq!(state.detail_offset(), 0);
}

#[test]
fn autocomplete_selection_clamps_or_resets_after_filtering_changes() {
    let candidates = autocomplete_candidates();
    let mut state = MissionControlState {
        input: "/q".to_string(),
        prompt_cursor: 2,
        ..Default::default()
    };
    state.recompute_autocomplete(&candidates);
    state.move_autocomplete_selection_down();
    assert_eq!(state.autocomplete.as_ref().unwrap().selected, 0);

    state.input = "/n".to_string();
    state.prompt_cursor = 2;
    state.recompute_autocomplete(&candidates);
    let autocomplete = state.autocomplete.as_ref().unwrap();
    assert_eq!(autocomplete.candidates.len(), 1);
    assert_eq!(autocomplete.candidates[0].name, "new");
    assert_eq!(autocomplete.selected, 0);
}