use super::super::*;
use super::helpers::*;
#[test]
fn down_opens_local_agents_drawer_when_input_is_empty() {
let mut session = app_session_with_input("", 0);
session.handle_command(app_types::InlineCommand::SetLocalAgents {
entries: vec![sample_local_agent_entry(
app_types::LocalAgentKind::Delegated,
)],
});
session.close_transient();
let event = session.process_key(KeyEvent::new(KeyCode::Down, KeyModifiers::NONE));
assert!(event.is_none());
assert!(session.local_agents_visible());
}
#[test]
fn new_local_agent_auto_opens_drawer() {
let mut session = app_session_with_input("", 0);
session.handle_command(app_types::InlineCommand::SetLocalAgents {
entries: vec![sample_local_agent_entry(
app_types::LocalAgentKind::Delegated,
)],
});
assert!(session.local_agents_visible());
}
#[test]
fn local_agents_drawer_hides_input_while_open() {
let mut session = app_session_with_input("draft command", "draft command".len());
session.handle_command(app_types::InlineCommand::SetLocalAgents {
entries: vec![sample_local_agent_entry(
app_types::LocalAgentKind::Delegated,
)],
});
assert!(session.local_agents_visible());
assert!(!session.core.input_enabled());
assert!(
!session
.core
.build_input_widget_data(VIEW_WIDTH, 1)
.cursor_should_be_visible
);
let lines = rendered_app_session_lines(&mut session, 20);
assert!(session.core.input_area().is_none());
assert!(session.core.bottom_panel_area().is_some());
assert!(
lines.iter().any(|line| line.contains("Local Agents")),
"drawer should still render"
);
assert!(
!lines.iter().any(|line| line.contains("draft command")),
"hidden composer should not render draft text"
);
}
#[test]
fn closing_local_agents_drawer_restores_input_and_draft() {
let mut session = app_session_with_input("draft command", "draft command".len());
session.handle_command(app_types::InlineCommand::SetLocalAgents {
entries: vec![sample_local_agent_entry(
app_types::LocalAgentKind::Delegated,
)],
});
let _ = rendered_app_session_lines(&mut session, 20);
let event = session.process_key(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE));
assert!(event.is_none());
assert!(!session.local_agents_visible());
assert!(session.core.input_enabled());
assert!(
session
.core
.build_input_widget_data(VIEW_WIDTH, 1)
.cursor_should_be_visible
);
assert_eq!(session.core.input_manager.content(), "draft command");
let lines = rendered_app_session_lines(&mut session, 20);
assert!(session.core.input_area().is_some());
assert!(
lines.iter().any(|line| line.contains("draft command")),
"composer should re-render its preserved draft"
);
}
#[test]
fn local_agents_drawer_navigation_works_with_existing_draft() {
let mut session = app_session_with_input("draft command", "draft command".len());
session.handle_command(app_types::InlineCommand::SetLocalAgents {
entries: vec![
sample_local_agent_entry_with_id(
"agent-1",
"rust-engineer",
app_types::LocalAgentKind::Delegated,
),
sample_local_agent_entry_with_id(
"agent-2",
"qa-reviewer",
app_types::LocalAgentKind::Delegated,
),
],
});
let down = session.process_key(KeyEvent::new(KeyCode::Down, KeyModifiers::NONE));
assert!(down.is_none());
let enter = session.process_key(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
assert!(matches!(
enter,
Some(app_types::InlineEvent::Submit(value)) if value == "/agent inspect agent-2"
));
assert_eq!(session.core.input_manager.content(), "draft command");
}
#[test]
fn new_background_local_agent_does_not_auto_open_drawer() {
let mut session = app_session_with_input("", 0);
session.handle_command(app_types::InlineCommand::SetLocalAgents {
entries: vec![sample_local_agent_entry(
app_types::LocalAgentKind::Background,
)],
});
assert!(!session.local_agents_visible());
}
#[test]
fn auto_opened_local_agents_drawer_closes_after_last_delegated_entry_is_removed() {
let mut session = app_session_with_input("", 0);
session.handle_command(app_types::InlineCommand::SetLocalAgents {
entries: vec![sample_local_agent_entry(
app_types::LocalAgentKind::Delegated,
)],
});
session.handle_command(app_types::InlineCommand::SetLocalAgents { entries: vec![] });
assert!(!session.local_agents_visible());
}
#[test]
fn manually_opened_empty_local_agents_drawer_stays_open() {
let mut session = app_session_with_input("", 0);
session.handle_command(app_types::InlineCommand::ShowTransient {
request: Box::new(app_types::TransientRequest::LocalAgents(
app_types::LocalAgentsTransientRequest {
visible: Some(true),
},
)),
});
session.handle_command(app_types::InlineCommand::SetLocalAgents { entries: vec![] });
assert!(session.local_agents_visible());
let lines = rendered_app_session_lines(&mut session, 20);
assert!(
lines
.iter()
.any(|line| line.contains("No local agents yet")),
"drawer should remain visible and show the empty state"
);
}
#[test]
fn alt_s_remains_subprocesses_entrypoint() {
let mut session = app_session_with_input("", 0);
let event = session.process_key(KeyEvent::new(KeyCode::Char('s'), KeyModifiers::ALT));
assert!(matches!(
event,
Some(app_types::InlineEvent::Submit(value)) if value == "/subprocesses"
));
}
#[test]
fn tab_cycles_primary_agent_when_composer_is_empty() {
let mut session = app_session_with_input("", 0);
let event = session.process_key(KeyEvent::new(KeyCode::Tab, KeyModifiers::NONE));
assert!(matches!(
event,
Some(app_types::InlineEvent::CyclePrimaryAgent)
));
}
#[test]
fn tab_character_cycles_primary_agent_when_composer_is_empty() {
let mut session = app_session_with_input("", 0);
let event = session.process_key(KeyEvent::new(KeyCode::Char('\t'), KeyModifiers::NONE));
assert!(matches!(
event,
Some(app_types::InlineEvent::CyclePrimaryAgent)
));
}
#[test]
fn core_tab_cycles_primary_agent_when_composer_is_empty() {
let mut session = Session::new(InlineTheme::default(), None, VIEW_ROWS);
let event = session.process_key(KeyEvent::new(KeyCode::Tab, KeyModifiers::NONE));
assert!(matches!(event, Some(InlineEvent::CyclePrimaryAgent)));
}
#[test]
fn tab_does_not_cycle_primary_agent_while_running() {
let mut session = app_session_with_input("", 0);
load_primary_agent_palette(&mut session);
set_app_session_busy_status(&mut session);
let event = session.process_key(KeyEvent::new(KeyCode::Tab, KeyModifiers::NONE));
assert!(event.is_none());
assert_eq!(session.core.input_manager.content(), "");
}
#[test]
fn tab_queues_draft_while_running_instead_of_switching_primary_agent() {
let mut session = app_session_with_input("Review this", "Review this".len());
load_primary_agent_palette(&mut session);
set_app_session_busy_status(&mut session);
let event = session.process_key(KeyEvent::new(KeyCode::Tab, KeyModifiers::NONE));
assert!(matches!(
event,
Some(app_types::InlineEvent::QueueSubmit(value)) if value == "Review this"
));
}
#[test]
fn tab_cycles_primary_agent_back_to_default_after_last_agent() {
let mut session = app_session_with_input("", 0);
session.handle_command(app_types::InlineCommand::SetPrimaryAgent {
name: Some("beta".to_string()),
});
let event = session.process_key(KeyEvent::new(KeyCode::Tab, KeyModifiers::NONE));
assert!(matches!(
event,
Some(app_types::InlineEvent::CyclePrimaryAgent)
));
}
#[test]
fn tab_accepts_inline_prompt_suggestion_before_primary_agent_cycle() {
let mut session = app_session_with_input("Review the current", "Review the current".len());
load_primary_agent_palette(&mut session);
session
.core
.set_inline_prompt_suggestion("Review the current diff".to_string(), true);
let event = session.process_key(KeyEvent::new(KeyCode::Tab, KeyModifiers::NONE));
assert!(event.is_none());
assert_eq!(
session.core.input_manager.content(),
"Review the current diff"
);
assert!(session.core.inline_prompt_suggestion.suggestion.is_none());
}
#[test]
fn tab_queues_draft_instead_of_switching_primary_agent() {
let mut session = app_session_with_input("Review this", "Review this".len());
load_primary_agent_palette(&mut session);
let event = session.process_key(KeyEvent::new(KeyCode::Tab, KeyModifiers::NONE));
assert!(matches!(
event,
Some(app_types::InlineEvent::QueueSubmit(value)) if value == "Review this"
));
}
#[test]
fn tab_does_not_switch_primary_agent_when_queued_input_exists() {
let mut session = app_session_with_input("", 0);
load_primary_agent_palette(&mut session);
set_app_session_queued_inputs(&mut session, vec!["queued follow-up".to_string()]);
let event = session.process_key(KeyEvent::new(KeyCode::Tab, KeyModifiers::NONE));
assert!(event.is_none());
assert_eq!(session.core.queued_inputs, vec!["queued follow-up"]);
}
#[test]
fn header_suggestions_include_subagent_shortcuts() {
let mut session = Session::new(InlineTheme::default(), None, VIEW_ROWS);
session.local_agents = vec![sample_local_agent_entry(
app_types::LocalAgentKind::Delegated,
)];
let line = session
.header_suggestions_line()
.expect("header suggestions line");
let rendered = line
.spans
.iter()
.map(|span| span.content.as_ref())
.collect::<String>();
assert!(rendered.contains("Alt+S"));
assert!(rendered.contains("Ctrl+B"));
}
#[test]
fn header_suggestions_hide_subagent_shortcuts_without_agents() {
let session = Session::new(InlineTheme::default(), None, VIEW_ROWS);
let line = session
.header_suggestions_line()
.expect("header suggestions line");
let rendered = line
.spans
.iter()
.map(|span| span.content.as_ref())
.collect::<String>();
assert!(!rendered.contains("Alt+S"));
assert!(!rendered.contains("Ctrl+B"));
}
#[test]
fn header_suggestions_hide_subagent_shortcuts_with_background_only() {
let mut session = Session::new(InlineTheme::default(), None, VIEW_ROWS);
session.local_agents = vec![sample_local_agent_entry(
app_types::LocalAgentKind::Background,
)];
let line = session
.header_suggestions_line()
.expect("header suggestions line");
let rendered = line
.spans
.iter()
.map(|span| span.content.as_ref())
.collect::<String>();
assert!(!rendered.contains("Alt+S"));
assert!(!rendered.contains("Ctrl+B"));
}
#[test]
fn empty_input_status_shows_subagent_shortcuts() {
let mut session = Session::new(InlineTheme::default(), None, VIEW_ROWS);
session.local_agents = vec![sample_local_agent_entry(
app_types::LocalAgentKind::Delegated,
)];
let line = session
.render_input_status_line(VIEW_WIDTH)
.expect("input status line");
let rendered = line
.spans
.iter()
.map(|span| span.content.as_ref())
.collect::<String>();
assert!(rendered.contains("Alt+S"));
assert!(rendered.contains("Ctrl+B"));
}
#[test]
fn empty_input_status_hides_subagent_shortcuts_without_agents() {
let session = Session::new(InlineTheme::default(), None, VIEW_ROWS);
let rendered = session
.render_input_status_line(VIEW_WIDTH)
.map(|line| {
line.spans
.iter()
.map(|span| span.content.as_ref())
.collect::<String>()
})
.unwrap_or_default();
assert!(!rendered.contains("Alt+S"));
assert!(!rendered.contains("Ctrl+B"));
}
#[test]
fn empty_input_status_hides_subagent_shortcuts_with_background_only() {
let mut session = Session::new(InlineTheme::default(), None, VIEW_ROWS);
session.local_agents = vec![sample_local_agent_entry(
app_types::LocalAgentKind::Background,
)];
let rendered = session
.render_input_status_line(VIEW_WIDTH)
.map(|line| {
line.spans
.iter()
.map(|span| span.content.as_ref())
.collect::<String>()
})
.unwrap_or_default();
assert!(!rendered.contains("Alt+S"));
assert!(!rendered.contains("Ctrl+B"));
}
#[test]
fn active_subagent_input_border_adds_extra_height() {
let mut session = Session::new(InlineTheme::default(), None, VIEW_ROWS);
session.header_context.subagent_badges = vec![InlineHeaderBadge {
text: "rust-engineer".to_string(),
style: InlineTextStyle {
color: Some(AnsiColorEnum::Rgb(RgbColor(0xFF, 0xFF, 0xFF))),
bg_color: Some(AnsiColorEnum::Rgb(RgbColor(0x4F, 0x8F, 0xD8))),
..InlineTextStyle::default()
},
full_background: true,
}];
assert_eq!(session.input_block_extra_height(), 2);
}
#[test]
fn header_shows_active_subagent_badge_with_full_background() {
let mut session = Session::new(InlineTheme::default(), None, VIEW_ROWS);
session.header_context.subagent_badges = vec![InlineHeaderBadge {
text: "rust-engineer".to_string(),
style: InlineTextStyle {
color: Some(AnsiColorEnum::Rgb(RgbColor(0xFF, 0xFF, 0xFF))),
bg_color: Some(AnsiColorEnum::Rgb(RgbColor(0x4F, 0x8F, 0xD8))),
..InlineTextStyle::default()
},
full_background: true,
}];
let line = session.header_meta_line();
let badge_span = line
.spans
.iter()
.find(|span| span.content.as_ref() == " rust-engineer ")
.expect("subagent badge span");
assert_eq!(badge_span.style.fg, Some(Color::Rgb(0xFF, 0xFF, 0xFF)));
assert_eq!(badge_span.style.bg, Some(Color::Rgb(0x4F, 0x8F, 0xD8)));
assert!(badge_span.style.add_modifier.contains(Modifier::BOLD));
}
#[test]
fn input_block_shows_active_subagent_title_with_badge_style() {
let mut session = Session::new(InlineTheme::default(), None, VIEW_ROWS);
session.set_input("review current code".to_string());
session.header_context.subagent_badges = vec![InlineHeaderBadge {
text: "rust-engineer".to_string(),
style: InlineTextStyle {
color: Some(AnsiColorEnum::Rgb(RgbColor(0xFF, 0xFF, 0xFF))),
bg_color: Some(AnsiColorEnum::Rgb(RgbColor(0x4F, 0x8F, 0xD8))),
..InlineTextStyle::default()
},
full_background: true,
}];
let title = session
.active_subagent_input_title()
.expect("active subagent input title");
let span = title.spans.first().expect("title span");
assert_eq!(span.content.as_ref(), " rust-engineer ");
assert_eq!(span.style.fg, Some(Color::Rgb(0xFF, 0xFF, 0xFF)));
assert_eq!(span.style.bg, Some(Color::Rgb(0x4F, 0x8F, 0xD8)));
assert!(span.style.add_modifier.contains(Modifier::BOLD));
assert_eq!(session.input_block_extra_height(), 2);
}
#[test]
fn header_suggestions_do_not_show_memory_shortcut_when_enabled() {
let mut session = Session::new(InlineTheme::default(), None, VIEW_ROWS);
session.header_context.persistent_memory = Some(InlineHeaderStatusBadge {
text: "Memory: auto".to_string(),
tone: InlineHeaderStatusTone::Ready,
});
let line = session
.header_suggestions_line()
.expect("header suggestions line");
let summary = line_text(&line);
assert!(!summary.contains("/memory"));
}
fn load_primary_agent_palette(session: &mut AppSession) {
session.handle_command(app_types::InlineCommand::ShowTransient {
request: Box::new(app_types::TransientRequest::AgentPalette(
app_types::AgentPaletteTransientRequest {
agents: vec![
app_types::AgentPaletteItem {
name: "beta".to_string(),
description: None,
},
app_types::AgentPaletteItem {
name: "alpha".to_string(),
description: None,
},
],
visible: None,
},
)),
});
session.close_transient();
}