use super::*;
#[test]
fn render_skill_autocomplete_popup_title_and_rows() {
let mut state = MissionControlState {
focus_pane: crate::tui::state::TuiFocusPane::Prompt,
prompt_editor: crate::tui::state::PromptEditor::new("$ru", 3),
..Default::default()
};
state.autocomplete = Some(crate::tui::state::AutocompleteState {
candidates: vec![
crate::tui::state::AutocompleteCandidate::skill_tag_with_argument_hint(
"rust-dev",
Some("path to review"),
),
],
selected: 0,
token_start: 0,
token_end: 3,
token: "$ru".to_string(),
});
let backend = TestBackend::new(80, 20);
let mut terminal = ratatui::Terminal::new(backend).unwrap();
terminal.draw(|frame| draw(frame, &state)).unwrap();
let rendered = terminal
.backend()
.buffer()
.content()
.iter()
.map(|cell| cell.symbol())
.collect::<String>();
assert!(rendered.contains("Skill tags"), "{rendered}");
assert!(rendered.contains("$rust-dev"), "{rendered}");
assert!(rendered.contains("$rust-dev path to review"), "{rendered}");
}
#[test]
fn render_prompt_shows_provider_model_and_reasoning_beside_agent_on_top_border() {
let mut state = MissionControlState {
provider: "openai-codex".to_string(),
model: "gpt-5.5".to_string(),
provider_ready: true,
thinking_levels: crate::thinking::ThinkingLevel::EFFORT_GENERIC.to_vec(),
..Default::default()
};
state.refresh_thinking_levels(
crate::thinking::ThinkingLevel::High,
state.thinking_levels.clone(),
);
let backend = TestBackend::new(160, 20);
let mut terminal = ratatui::Terminal::new(backend).unwrap();
terminal.draw(|frame| draw(frame, &state)).unwrap();
let buffer = terminal.backend().buffer();
let metadata = find_text(buffer, "[Alt-M] openai-codex/gpt-5.5 [Alt-T] (High)").unwrap();
let layout = layout_for(Rect::new(0, 0, 160, 20), &state);
assert_eq!(metadata.1, layout.prompt.y);
let agent = find_text(buffer, "[Shift-Tab] Agent: None").unwrap();
assert_eq!(agent.1, metadata.1);
assert!(agent.0 > metadata.0);
assert!(find_text(buffer, "Fast:").is_none());
let version = find_text(buffer, &format!("MAGI-CODE v{}", env!("CARGO_PKG_VERSION"))).unwrap();
assert_eq!(version.1, layout.prompt.bottom() - 1);
}
#[test]
fn render_prompt_shows_alt_m_hint_when_idle() {
let mut state = MissionControlState {
provider: "zai".to_string(),
model: "glm-5.2".to_string(),
provider_ready: true,
thinking_levels: crate::thinking::ThinkingLevel::EFFORT_GENERIC.to_vec(),
..Default::default()
};
state.refresh_thinking_levels(
crate::thinking::ThinkingLevel::High,
state.thinking_levels.clone(),
);
let backend = TestBackend::new(100, 20);
let mut terminal = ratatui::Terminal::new(backend).unwrap();
terminal.draw(|frame| draw(frame, &state)).unwrap();
let buffer = terminal.backend().buffer();
assert!(
find_text(buffer, "[Alt-M]").is_some(),
"Alt-M hint should show when idle"
);
let layout = layout_for(Rect::new(0, 0, 100, 20), &state);
let hint = find_text(buffer, "[Alt-M]").unwrap();
assert_eq!(hint.1, layout.prompt.y);
}
#[test]
fn render_prompt_hides_alt_m_hint_while_running() {
let mut state = MissionControlState {
provider: "zai".to_string(),
model: "glm-5.2".to_string(),
provider_ready: true,
thinking_levels: crate::thinking::ThinkingLevel::EFFORT_GENERIC.to_vec(),
..Default::default()
};
state.refresh_thinking_levels(
crate::thinking::ThinkingLevel::High,
state.thinking_levels.clone(),
);
state.start_running_prompt("inspect".to_string());
let backend = TestBackend::new(100, 20);
let mut terminal = ratatui::Terminal::new(backend).unwrap();
terminal.draw(|frame| draw(frame, &state)).unwrap();
let buffer = terminal.backend().buffer();
assert!(
find_text(buffer, "[Alt-M]").is_none(),
"Alt-M hint should hide while running"
);
}
#[test]
fn render_prompt_context_metadata_stays_on_footer_and_out_of_transcript() {
let state = MissionControlState {
provider: "openai-codex".to_string(),
model: "gpt-5.5".to_string(),
provider_ready: true,
context_usage: Some(crate::tui::state::ContextUsageState {
current_tokens: 16_274,
max_tokens: 272_000,
reasoning_tokens: None,
source: crate::output::ContextUsageSource::ProviderExact,
request_sequence: 1,
}),
..Default::default()
};
let area = Rect::new(0, 0, 160, 20);
let mut terminal = ratatui::Terminal::new(TestBackend::new(area.width, area.height)).unwrap();
terminal.draw(|frame| draw(frame, &state)).unwrap();
let buffer = terminal.backend().buffer();
let layout = layout_for(area, &state);
let context = find_text_in_area(buffer, layout.prompt, "Current Ctx: 5% [..........] 272k")
.expect("context");
let provider = find_text_in_area(buffer, layout.prompt, "[Alt-M]").expect("provider hint");
assert_eq!(context.1, layout.prompt.y + layout.prompt.height - 1);
assert_eq!(provider.1, layout.prompt.y);
assert!(!rendered_text_in_area(buffer, layout.transcript).contains("Current Ctx:"));
}
#[test]
fn render_running_status_and_cancel_hint_on_prompt_top_left_only() {
let mut state = MissionControlState {
status: "running prompt…".to_string(),
provider_ready: true,
..Default::default()
};
state.start_running_prompt("inspect".to_string());
let backend = TestBackend::new(100, 20);
let mut terminal = ratatui::Terminal::new(backend).unwrap();
terminal.draw(|frame| draw(frame, &state)).unwrap();
let buffer = terminal.backend().buffer();
let layout = layout_for(Rect::new(0, 0, 100, 20), &state);
let status = find_text_in_area(buffer, layout.prompt, "running prompt…").expect("status");
let cancel = find_text_in_area(buffer, layout.prompt, "[Alt-C]").expect("cancel hint");
assert_eq!(status.1, layout.prompt.y);
assert_eq!(cancel.1, layout.prompt.y);
assert!(status.0 < layout.prompt.x + layout.prompt.width / 2);
let transcript = rendered_text_in_area(buffer, layout.transcript);
assert!(!transcript.contains("run ◐"), "{transcript}");
assert!(!transcript.contains("running prompt…"), "{transcript}");
assert!(!transcript.contains("[Alt-C]"), "{transcript}");
}
#[test]
fn render_prompt_shows_pending_steering_count_on_top_left_border() {
let state = MissionControlState {
pending_steering_count: 3,
provider_ready: true,
..Default::default()
};
let backend = TestBackend::new(100, 20);
let mut terminal = ratatui::Terminal::new(backend).unwrap();
terminal.draw(|frame| draw(frame, &state)).unwrap();
let buffer = terminal.backend().buffer();
let layout = layout_for(Rect::new(0, 0, 100, 20), &state);
let queued = find_text_in_area(buffer, layout.prompt, "3 steering queued").expect("queued");
assert_eq!(queued.1, layout.prompt.y);
let transcript = rendered_text_in_area(buffer, layout.transcript);
assert!(!transcript.contains("3 steering queued"), "{transcript}");
}
#[test]
fn render_prompt_hides_pending_steering_count_when_clear() {
let state = MissionControlState {
pending_steering_count: 0,
provider_ready: true,
..Default::default()
};
let backend = TestBackend::new(100, 20);
let mut terminal = ratatui::Terminal::new(backend).unwrap();
terminal.draw(|frame| draw(frame, &state)).unwrap();
let rendered = terminal
.backend()
.buffer()
.content()
.iter()
.map(|cell| cell.symbol())
.collect::<String>();
assert!(!rendered.contains("steering queued"));
}
#[test]
fn render_uses_native_prompt_cursor_without_a_cursor_glyph() {
let mut state = MissionControlState {
prompt_editor: crate::tui::state::PromptEditor::new_with_visibility("hello", 0, true),
..Default::default()
};
let area = Rect::new(0, 0, 80, 20);
let backend = TestBackend::new(area.width, area.height);
let mut terminal = ratatui::Terminal::new(backend).unwrap();
terminal.draw(|frame| draw(frame, &state)).unwrap();
let buffer = terminal.backend().buffer();
let text_area = content_text_area_for_pane(TuiPane::Prompt, layout_for(area, &state).prompt);
assert!(find_text(buffer, "hello").is_some());
assert!(find_text(buffer, "█").is_none());
assert_eq!(
state.prompt_editor.rendered_cursor_position(),
Some(ratatui::layout::Position::new(text_area.x, text_area.y))
);
state.set_prompt_cursor_visible(false);
terminal.draw(|frame| draw(frame, &state)).unwrap();
assert!(find_text(terminal.backend().buffer(), "hello").is_some());
assert!(find_text(terminal.backend().buffer(), "█").is_none());
state.focus_activity_tree();
state.set_prompt_cursor_visible(true);
terminal.draw(|frame| draw(frame, &state)).unwrap();
assert!(find_text(terminal.backend().buffer(), "hello").is_some());
assert!(find_text(terminal.backend().buffer(), "█").is_none());
}
#[test]
fn render_places_native_prompt_cursor_at_logical_position() {
let state = MissionControlState {
prompt_editor: crate::tui::state::PromptEditor::new_with_visibility("abcd", 2, true),
..Default::default()
};
let area = Rect::new(0, 0, 40, 20);
let backend = TestBackend::new(area.width, area.height);
let mut terminal = ratatui::Terminal::new(backend).unwrap();
terminal.draw(|frame| draw(frame, &state)).unwrap();
let buffer = terminal.backend().buffer();
let text_area = content_text_area_for_pane(TuiPane::Prompt, layout_for(area, &state).prompt);
assert!(find_text_in_area(buffer, text_area, "abcd").is_some());
assert!(find_text_in_area(buffer, text_area, "█").is_none());
assert_eq!(
state.prompt_editor.rendered_cursor_position(),
Some(ratatui::layout::Position::new(
text_area.x.saturating_add(2),
text_area.y,
))
);
}
#[test]
fn render_prompt_preserves_explicit_newlines_with_native_cursor_position() {
let state = MissionControlState {
prompt_editor: crate::tui::state::PromptEditor::new_with_visibility(
"ALPHA\nBETA",
"ALPHA\nBE".len(),
true,
),
..Default::default()
};
let area = Rect::new(0, 0, 80, 20);
let backend = TestBackend::new(area.width, area.height);
let mut terminal = ratatui::Terminal::new(backend).unwrap();
terminal.draw(|frame| draw(frame, &state)).unwrap();
let buffer = terminal.backend().buffer();
let text_area = content_text_area_for_pane(TuiPane::Prompt, layout_for(area, &state).prompt);
let alpha = find_text_in_area(buffer, text_area, "ALPHA").unwrap();
let be = find_text_in_area(buffer, text_area, "BE").unwrap();
let ta = find_text_in_area(buffer, text_area, "TA").unwrap();
assert_eq!(be.1, alpha.1 + 1);
assert_eq!(ta.1, be.1);
assert!(be.0 < ta.0);
assert_eq!(
state.prompt_editor.rendered_cursor_position(),
Some(ratatui::layout::Position::new(
text_area.x.saturating_add(2),
be.1,
))
);
assert_eq!(
buffer[alpha].fg,
MissionControlTheme::default().text_primary()
);
assert_eq!(buffer[be].fg, MissionControlTheme::default().text_primary());
assert_eq!(buffer[ta].fg, MissionControlTheme::default().text_primary());
}
#[test]
fn render_prompt_can_show_start_after_cursor_auto_reveal() {
let state = MissionControlState {
prompt_editor: crate::tui::state::PromptEditor::new_with_visibility(
&format!("HEAD{}TAIL", "x".repeat(130)),
0,
true,
),
..Default::default()
};
let backend = TestBackend::new(20, 20);
let mut terminal = ratatui::Terminal::new(backend).unwrap();
terminal.draw(|frame| draw(frame, &state)).unwrap();
let rendered = terminal
.backend()
.buffer()
.content()
.iter()
.map(|cell| cell.symbol())
.collect::<String>();
assert!(rendered.contains("HEAD"));
assert!(!rendered.contains("TAIL"));
}
#[test]
fn render_prompt_tails_overflow_after_five_wrapped_rows() {
let text = format!("HEAD{}TAIL", "x".repeat(75));
let state = MissionControlState {
prompt_editor: crate::tui::state::PromptEditor::new_with_visibility(
&text,
text.len(),
true,
),
..Default::default()
};
let backend = TestBackend::new(20, 20);
let mut terminal = ratatui::Terminal::new(backend).unwrap();
terminal.draw(|frame| draw(frame, &state)).unwrap();
let rendered = terminal
.backend()
.buffer()
.content()
.iter()
.map(|cell| cell.symbol())
.collect::<String>();
assert!(rendered.contains("TAIL"));
assert!(!rendered.contains("HEAD"));
}
#[test]
fn render_autocomplete_popup_when_visible() {
let state = state_with_autocomplete();
let backend = TestBackend::new(80, 20);
let mut terminal = ratatui::Terminal::new(backend).unwrap();
terminal.draw(|frame| draw(frame, &state)).unwrap();
let rendered = terminal
.backend()
.buffer()
.content()
.iter()
.map(|cell| cell.symbol())
.collect::<String>();
assert!(rendered.contains("Slash commands"));
assert!(rendered.contains("/new"));
assert!(rendered.contains("start a new session"));
}
#[test]
fn render_autocomplete_popup_uses_themed_background_and_selection() {
let mut state = MissionControlState {
prompt_editor: crate::tui::state::PromptEditor::new("/", 1),
focus_pane: crate::tui::state::TuiFocusPane::Prompt,
..Default::default()
};
state.recompute_autocomplete(&[
crate::tui::state::AutocompleteCandidate::slash_command("new", "start a new session"),
crate::tui::state::AutocompleteCandidate::slash_command("quit", "exit magi-code"),
]);
let backend = TestBackend::new(80, 20);
let mut terminal = ratatui::Terminal::new(backend).unwrap();
terminal.draw(|frame| draw(frame, &state)).unwrap();
let buffer = terminal.backend().buffer();
let title = find_text(buffer, "Slash commands").unwrap();
assert_eq!(
buffer[title].fg,
MissionControlTheme::default().text_accent()
);
assert_eq!(
buffer[title].bg,
MissionControlTheme::default().surface_panel_alt()
);
let selected = find_text(buffer, "/new").unwrap();
assert_eq!(buffer[selected].fg, state.theme.text_accent());
assert_eq!(
buffer[selected].bg,
MissionControlTheme::default().surface_selection()
);
let description = find_text(buffer, "start a new session").unwrap();
assert_eq!(buffer[description].fg, state.theme.text_accent());
assert_eq!(
buffer[description].bg,
MissionControlTheme::default().surface_selection()
);
let unselected = find_text(buffer, "/quit").unwrap();
assert_eq!(
buffer[unselected].fg,
MissionControlTheme::default().text_accent()
);
assert_eq!(
buffer[unselected].bg,
MissionControlTheme::default().surface_panel_alt()
);
}
#[test]
fn render_autocomplete_popup_absent_when_hidden() {
let state = MissionControlState::default();
let backend = TestBackend::new(80, 20);
let mut terminal = ratatui::Terminal::new(backend).unwrap();
terminal.draw(|frame| draw(frame, &state)).unwrap();
let rendered = terminal
.backend()
.buffer()
.content()
.iter()
.map(|cell| cell.symbol())
.collect::<String>();
assert!(!rendered.contains("Slash commands"));
}
#[test]
fn render_autocomplete_small_terminal_does_not_panic() {
let state = state_with_autocomplete();
let backend = TestBackend::new(12, 5);
let mut terminal = ratatui::Terminal::new(backend).unwrap();
terminal.draw(|frame| draw(frame, &state)).unwrap();
}
#[test]
#[ignore = "requires an upstream tui-textarea-2 viewport-row fix"]
fn render_prompt_with_more_than_u16_max_visual_rows_keeps_cursor_and_hit_testing_visible() {
let logical_rows = usize::from(u16::MAX) + 2;
let mut text = String::with_capacity(logical_rows.saturating_mul(2).saturating_add(3));
for _ in 0..logical_rows.saturating_sub(1) {
text.push_str("x\n");
}
text.push_str("END");
assert!(text.split('\n').count() > usize::from(u16::MAX));
let state = MissionControlState {
focus_pane: crate::tui::state::TuiFocusPane::Prompt,
prompt_editor: crate::tui::state::PromptEditor::new_with_visibility(
&text,
text.len(),
true,
),
..Default::default()
};
let area = Rect::new(0, 0, 80, 20);
let mut terminal = ratatui::Terminal::new(TestBackend::new(area.width, area.height)).unwrap();
terminal.draw(|frame| draw(frame, &state)).unwrap();
let prompt_area = layout_for(area, &state).prompt;
let text_area = content_text_area_for_pane(TuiPane::Prompt, prompt_area);
let cursor = state
.prompt_editor
.rendered_cursor_position()
.expect("the final prompt cursor should be visible after rendering");
assert!(text_area.contains(cursor));
assert_eq!(
cursor.y,
text_area
.y
.saturating_add(text_area.height.saturating_sub(1))
);
assert_eq!(
state.prompt_editor.cursor_at_position(cursor),
Some(text.len())
);
}