use super::*;
#[test]
fn sessions_modal_renders_title_help_current_marker_and_selection() {
let mut state = MissionControlState::default();
state.open_session_picker(
vec![
crate::tui::state::SessionPickerRow {
id: "abcd1234efgh".into(),
label: "abcd...efgh".into(),
is_current: true,
preview: None,
},
crate::tui::state::SessionPickerRow {
id: "wxyz9876lmno".into(),
label: "wxyz...lmno".into(),
is_current: false,
preview: None,
},
],
TEST_PICKER_VISIBLE_ROWS,
);
state.modals.session_picker.as_mut().unwrap().selected = 1;
let backend = TestBackend::new(120, 30);
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("Sessions"), "{rendered}");
assert!(rendered.contains("Mouse wheel scroll"), "{rendered}");
assert!(rendered.contains("● current abcd...efgh"), "{rendered}");
assert!(rendered.contains("> wxyz...lmno"), "{rendered}");
}
#[test]
fn sessions_modal_uses_two_columns_when_space_permits() {
let areas = sessions_modal_areas(ratatui::layout::Rect::new(0, 0, 100, 30)).unwrap();
let preview = areas.preview.unwrap();
assert!(areas.list.width < preview.width);
assert!(areas.list.height > 0);
assert!(preview.height > 0);
}
#[test]
fn sessions_modal_empty_state_and_small_terminal_do_not_panic() {
let mut state = MissionControlState::default();
state.open_session_picker(Vec::new(), TEST_PICKER_VISIBLE_ROWS);
let backend = TestBackend::new(120, 30);
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("No saved sessions found"), "{rendered}");
for (width, height) in [(10, 4), (12, 5)] {
let backend = TestBackend::new(width, height);
let mut terminal = ratatui::Terminal::new(backend).unwrap();
terminal.draw(|frame| draw(frame, &state)).unwrap();
}
}
#[test]
fn sessions_modal_does_not_render_secret_payload_fixture_strings() {
let mut state = MissionControlState::default();
state.open_session_picker(
vec![crate::tui::state::SessionPickerRow {
id: "safe-session".into(),
label: "safe-session".into(),
is_current: false,
preview: None,
}],
TEST_PICKER_VISIBLE_ROWS,
);
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("sk-testSecret123456"), "{rendered}");
}
#[test]
fn skills_modal_renders_title_toggles_help_selection_and_empty_state() {
let mut state = MissionControlState::default();
state.open_skills_modal(
vec![
crate::tui::state::SkillToggleRow {
name: "enabled-skill".to_string(),
source: "test".to_string(),
enabled: true,
},
crate::tui::state::SkillToggleRow {
name: "disabled-skill".to_string(),
source: "test".to_string(),
enabled: false,
},
],
TEST_PICKER_VISIBLE_ROWS,
);
state.modals.skills_modal.as_mut().unwrap().selected = 1;
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("Skills"), "{rendered}");
assert!(rendered.contains("[x] enabled-skill (test)"), "{rendered}");
assert!(rendered.contains("[ ] disabled-skill (test)"), "{rendered}");
assert!(rendered.contains("[Up]/[Down] move"), "{rendered}");
assert!(
rendered.contains("> [ ] disabled-skill (test)"),
"{rendered}"
);
state.open_skills_modal(Vec::new(), TEST_PICKER_VISIBLE_ROWS);
terminal.draw(|frame| draw(frame, &state)).unwrap();
let rendered = terminal
.backend()
.buffer()
.content()
.iter()
.map(|cell| cell.symbol())
.collect::<String>();
assert!(rendered.contains("No skills discovered"), "{rendered}");
}
#[test]
fn skills_modal_small_viewports_do_not_panic_or_overflow() {
for (width, height) in [(10, 4), (12, 5)] {
let mut state = MissionControlState::default();
state.open_skills_modal(
vec![crate::tui::state::SkillToggleRow {
name: "tiny-skill".to_string(),
source: "test".to_string(),
enabled: true,
}],
TEST_PICKER_VISIBLE_ROWS,
);
let backend = TestBackend::new(width, height);
let mut terminal = ratatui::Terminal::new(backend).unwrap();
terminal.draw(|frame| draw(frame, &state)).unwrap();
}
}
#[test]
fn tools_modal_renders_title_toggles_kind_help_selection_and_empty_state() {
let mut state = MissionControlState::default();
state.open_tools_modal(
vec![
crate::tui::state::ToolToggleRow {
name: "bash".to_string(),
kind: "built-in".to_string(),
enabled: true,
},
crate::tui::state::ToolToggleRow {
name: "mcp__mock__echo".to_string(),
kind: "mcp:mock".to_string(),
enabled: false,
},
],
TEST_PICKER_VISIBLE_ROWS,
);
state.modals.tools_modal.as_mut().unwrap().selected = 1;
let backend = TestBackend::new(90, 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("Tools"), "{rendered}");
assert!(rendered.contains("[x] bash (built-in)"), "{rendered}");
assert!(
rendered.contains("[ ] mcp__mock__echo (mcp:mock)"),
"{rendered}"
);
assert!(rendered.contains("> [ ] mcp__mock__echo"), "{rendered}");
state.open_tools_modal(Vec::new(), TEST_PICKER_VISIBLE_ROWS);
terminal.draw(|frame| draw(frame, &state)).unwrap();
let rendered = terminal
.backend()
.buffer()
.content()
.iter()
.map(|cell| cell.symbol())
.collect::<String>();
assert!(rendered.contains("No tools available"), "{rendered}");
}
#[test]
fn active_login_modal_wraps_long_oauth_url_and_shows_keyboard_actions() {
let mut state = MissionControlState::default();
state.start_active_login("openai-codex".to_string());
let url = "https://auth.openai.com/oauth/authorize?code_challenge=abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz&originator=pi";
state.set_login_instructions(
url.to_string(),
format!(
"OpenAI Codex login: open this URL in your browser:\n{url}\nWaiting for browser callback. If callback cannot reach this terminal, paste the final redirect URL or authorization code into the active login prompt."
),
);
let backend = TestBackend::new(80, 24);
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("[Ctrl-Y] copy URL"));
assert!(rendered.contains("[Ctrl-O] open browser"));
assert!(rendered.contains("Manual fallback input is transient and redacted."));
assert!(rendered.contains("[Enter] submit fallback"));
let fallback_submit = find_text(terminal.backend().buffer(), "[Enter]>")
.expect("fallback placeholder [Enter] rendered");
assert_eq!(
terminal.backend().buffer()[fallback_submit].fg,
MATRIX_GREEN.green_dim
);
assert!(rendered.contains("originator=pi"));
assert_eq!(rendered.matches("https://auth.openai.com").count(), 1);
}
#[test]
fn custom_provider_setup_modal_shows_steps_preview_and_optional_no_auth_copy() {
let mut state = MissionControlState::default();
state.start_custom_provider_setup();
{
let setup = state.modals.custom_provider_setup.as_mut().unwrap();
setup.label = "Local Llama".to_string();
setup.provider_id = Some("local-llama".to_string());
setup.step = CustomProviderSetupStep::ApiKeyEnvVar;
setup.error = Some("API key environment variable name must match".to_string());
}
let backend = TestBackend::new(90, 24);
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("Set up custom provider"));
assert!(rendered.contains("Step 3/3 API key environment variable"));
assert!(rendered.contains("Provider ID: local-llama"));
assert!(rendered.contains("Optional. Leave blank"));
assert!(rendered.contains("API key not required"));
assert!(rendered.contains("Error: API key environment variable name must match"));
assert!(!rendered.contains("| API_KEY"));
}
#[test]
fn render_prompt_prompts_login_when_provider_is_not_ready() {
let state = MissionControlState {
provider: "openai-codex".to_string(),
model: "gpt-5.5".to_string(),
..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 metadata = find_text(buffer, "Use /login to add a provider").unwrap();
let layout = layout_for(Rect::new(0, 0, 100, 20), &state);
assert_eq!(metadata.1, layout.prompt.y + layout.prompt.height - 1);
assert!(metadata.0 > layout.prompt.x + layout.prompt.width / 2);
assert!(find_text(buffer, "openai-codex • gpt-5.5").is_none());
}
#[test]
fn render_logout_confirmation_shows_provider_and_keyboard_controls() {
let mut state = MissionControlState {
provider: "openai-codex".to_string(),
model: "gpt-5.5".to_string(),
provider_ready: true,
..Default::default()
};
state.open_logout_confirmation("openai-codex".to_string(), "OpenAI Codex".to_string());
let backend = TestBackend::new(100, 24);
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("Confirm Logout"));
assert!(rendered.contains("Remove local auth for OpenAI Codex (openai-codex)?"));
assert!(rendered.contains("[Enter] remove local auth"));
assert!(rendered.contains("[Esc] cancel"));
assert!(rendered.contains("openai-codex • gpt-5.5 • Thinking:"));
assert!(rendered.contains("[Alt-T]"));
}
#[test]
fn render_model_picker_modal_shows_current_model_and_available_models() {
let mut state = MissionControlState {
provider: "openai-codex".to_string(),
model: "gpt-current".to_string(),
..Default::default()
};
state.open_model_picker(
vec![
crate::model_catalog::ModelCatalogEntry::new_codex("gpt-current"),
crate::model_catalog::ModelCatalogEntry::new_codex("gpt-next"),
],
std::collections::BTreeSet::new(),
Some("using stale openai-codex model catalog".to_string()),
5,
);
let backend = TestBackend::new(100, 30);
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("Model Picker"));
assert!(rendered.contains("current: openai-codex/gpt-current"));
assert!(rendered.contains("openai-codex/gpt-next"));
assert!(!rendered.contains("[disabled]"));
assert!(rendered.contains("using stale openai-codex model catalog"));
}
#[test]
fn models_modal_renders_title_help_scope_states_rows_and_selection() {
let mut state = MissionControlState::default();
state.open_models_modal_scoped(
vec![
crate::tui::state::ModelToggleRow {
id: "openai-codex/gpt-enabled".to_string(),
provider: "openai-codex".to_string(),
display_name: "Enabled".to_string(),
enabled: true,
},
crate::tui::state::ModelToggleRow {
id: "openai-codex/gpt-disabled".to_string(),
provider: "openai-codex".to_string(),
display_name: "Disabled".to_string(),
enabled: false,
},
],
TEST_PICKER_VISIBLE_ROWS,
crate::config::SettingsScope::Global,
"Global".to_string(),
false,
);
state.modals.models_modal.as_mut().unwrap().selected = 1;
let backend = TestBackend::new(110, 24);
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("Models"), "{rendered}");
assert!(rendered.contains("[Tab] switch scope"), "{rendered}");
assert!(rendered.contains("Scope: Global"), "{rendered}");
assert!(
rendered.contains("[x] openai-codex/gpt-enabled Enabled"),
"{rendered}"
);
assert!(
rendered.contains("[ ] openai-codex/gpt-disabled Disabled [disabled]"),
"{rendered}"
);
assert!(
rendered.contains("> [ ] openai-codex/gpt-disabled"),
"{rendered}"
);
state.open_models_modal_scoped(
Vec::new(),
TEST_PICKER_VISIBLE_ROWS,
crate::config::SettingsScope::Global,
"Local".to_string(),
true,
);
terminal.draw(|frame| draw(frame, &state)).unwrap();
let rendered = terminal
.backend()
.buffer()
.content()
.iter()
.map(|cell| cell.symbol())
.collect::<String>();
assert!(rendered.contains("Scope: Local"), "{rendered}");
assert!(rendered.contains("Loading models..."), "{rendered}");
state.set_models_rows(Vec::new(), false, TEST_PICKER_VISIBLE_ROWS);
terminal.draw(|frame| draw(frame, &state)).unwrap();
let rendered = terminal
.backend()
.buffer()
.content()
.iter()
.map(|cell| cell.symbol())
.collect::<String>();
assert!(rendered.contains("No models discovered"), "{rendered}");
}