use super::super::*;
use super::support::*;
use std::collections::HashSet;
#[test]
fn keybindings_have_no_duplicate_keys_in_same_context() {
let mut seen = HashSet::new();
for binding in default_keybindings() {
assert!(
seen.insert((binding.context, binding.key)),
"duplicate keybinding in {:?}: {}",
binding.context,
binding.label
);
}
}
#[test]
fn alt_up_down_keybindings_are_context_specific() {
assert_eq!(
action_for_key(
modified_key(KeyCode::Up, KeyModifiers::ALT),
KeyContext::Prompt
),
Some(KeyCommand::ScrollTranscriptUp)
);
assert_eq!(
action_for_key(
modified_key(KeyCode::Down, KeyModifiers::ALT),
KeyContext::Prompt
),
Some(KeyCommand::ScrollTranscriptDown)
);
assert_eq!(
action_for_key(
modified_key(KeyCode::Up, KeyModifiers::ALT),
KeyContext::ActivityDetail
),
Some(KeyCommand::ScrollDetailUp)
);
assert_eq!(
action_for_key(
modified_key(KeyCode::Down, KeyModifiers::ALT),
KeyContext::ActivityDetail
),
Some(KeyCommand::ScrollDetailDown)
);
}
#[test]
fn ctrl_shift_left_right_keybindings_are_global_layout_shortcuts() {
assert_eq!(
action_for_key(
modified_key(KeyCode::Right, KeyModifiers::CONTROL | KeyModifiers::SHIFT),
KeyContext::Prompt
),
Some(KeyCommand::FocusActivityLayout)
);
assert_eq!(
action_for_key(
modified_key(KeyCode::Left, KeyModifiers::CONTROL | KeyModifiers::SHIFT),
KeyContext::Prompt
),
Some(KeyCommand::FocusTranscriptLayout)
);
assert_eq!(
action_for_key(
modified_key(KeyCode::Right, KeyModifiers::CONTROL | KeyModifiers::SHIFT),
KeyContext::ActivityTree
),
Some(KeyCommand::FocusActivityLayout)
);
assert_eq!(
action_for_key(
modified_key(KeyCode::Left, KeyModifiers::CONTROL | KeyModifiers::SHIFT),
KeyContext::ActivityTree
),
Some(KeyCommand::FocusTranscriptLayout)
);
}
#[test]
fn alt_left_right_keybindings_remain_global_layout_aliases() {
assert_eq!(
action_for_key(
modified_key(KeyCode::Right, KeyModifiers::ALT),
KeyContext::Prompt
),
Some(KeyCommand::FocusActivityLayout)
);
assert_eq!(
action_for_key(
modified_key(KeyCode::Left, KeyModifiers::ALT),
KeyContext::Prompt
),
Some(KeyCommand::FocusTranscriptLayout)
);
assert_eq!(
action_for_key(
modified_key(KeyCode::Right, KeyModifiers::ALT),
KeyContext::ActivityTree
),
Some(KeyCommand::FocusActivityLayout)
);
assert_eq!(
action_for_key(
modified_key(KeyCode::Left, KeyModifiers::ALT),
KeyContext::ActivityTree
),
Some(KeyCommand::FocusTranscriptLayout)
);
}
#[test]
fn ctrl_shift_left_right_switch_layout_without_changing_prompt_or_scroll_state() {
let candidates = autocomplete_candidates();
let mut state = state_with_activity();
state.focus_prompt();
state.set_prompt_text("/q", 2);
state.set_scroll_offset(&state.scroll_views.transcript, 4);
state.set_scroll_offset(&state.scroll_views.activity_tree, 5);
state.set_scroll_offset(&state.scroll_views.detail, 6);
state.recompute_autocomplete(&candidates);
let autocomplete_before = state.autocomplete.clone();
assert_eq!(
handle_key(
modified_key(KeyCode::Right, KeyModifiers::CONTROL | KeyModifiers::SHIFT),
&mut state
),
InputAction::None
);
assert_eq!(
state.layout_mode,
crate::tui::state::TuiLayoutMode::ActivityFocused
);
assert_eq!(
handle_key(
modified_key(KeyCode::Left, KeyModifiers::CONTROL | KeyModifiers::SHIFT),
&mut state
),
InputAction::None
);
assert_eq!(
state.layout_mode,
crate::tui::state::TuiLayoutMode::TranscriptFocused
);
assert_eq!(state.prompt_plain_text(), "/q");
assert_eq!(state.prompt_cursor(), 2);
assert_eq!(state.transcript_scroll_offset(), 4);
assert_eq!(state.activity_scroll_offset(), 5);
assert_eq!(state.detail_offset(), 6);
assert_eq!(state.selected, 0);
assert!(state.is_prompt_focused());
assert_eq!(state.autocomplete, autocomplete_before);
}
#[test]
fn alt_t_cycles_reasoning_without_editing_prompt_or_conflicting_with_plain_t_or_shift_tab() {
let mut state = MissionControlState {
provider: crate::providers::OPENAI_CODEX_PROVIDER.to_string(),
model: "gpt-5".to_string(),
focus_pane: crate::tui::state::TuiFocusPane::Prompt,
prompt_editor: crate::tui::state::PromptEditor::new("draft", 5),
..Default::default()
};
state.refresh_thinking_levels(
crate::thinking::ThinkingLevel::Default,
crate::thinking::ThinkingLevel::EFFORT_GENERIC.to_vec(),
);
state.set_primary_agents(vec![crate::tui::state::PrimaryAgentEntry {
id: "tars".to_string(),
name: "TARS".to_string(),
description: "tactical unit".to_string(),
prompt: "stay alive".to_string(),
}]);
assert_eq!(
handle_key(key(KeyCode::Char('t')), &mut state),
InputAction::None
);
assert_eq!(state.prompt_plain_text(), "draftt");
assert_eq!(
handle_key(
modified_key(KeyCode::Char('t'), KeyModifiers::ALT),
&mut state
),
InputAction::ThinkingLevelSelectionChanged(crate::thinking::ThinkingLevel::Low)
);
assert_eq!(state.prompt_plain_text(), "draftt");
assert_eq!(state.thinking_level, crate::thinking::ThinkingLevel::Low);
assert_eq!(
state.effective_thinking_level,
crate::thinking::ThinkingLevel::Low
);
assert_eq!(
handle_key(
modified_key(KeyCode::BackTab, KeyModifiers::SHIFT),
&mut state
),
InputAction::PrimaryAgentSelectionChanged(Some("tars".to_string()))
);
assert_eq!(state.prompt_plain_text(), "draftt");
}
#[test]
fn alt_t_on_unsupported_model_reports_diagnostic_without_changing_prompt() {
let mut state = MissionControlState {
provider: "local-ai".to_string(),
model: "model".to_string(),
focus_pane: crate::tui::state::TuiFocusPane::Prompt,
prompt_editor: crate::tui::state::PromptEditor::new("draft", 0),
..Default::default()
};
state.refresh_thinking_levels(
crate::thinking::ThinkingLevel::High,
state.thinking_levels.clone(),
);
assert_eq!(
handle_key(
modified_key(KeyCode::Char('t'), KeyModifiers::ALT),
&mut state
),
InputAction::None
);
assert_eq!(state.prompt_plain_text(), "draft");
assert_eq!(
state.effective_thinking_level,
crate::thinking::ThinkingLevel::Default
);
assert!(state.status.contains("not configurable"));
}
#[test]
fn help_text_is_generated_from_keymap_and_mentions_auth_commands() {
let help = help_text();
for binding in default_keybindings() {
assert!(help.contains(binding.label), "missing {}", binding.label);
assert!(
help.contains(binding.description),
"missing {}",
binding.description
);
}
assert!(help.contains("type normally"));
assert!(help.contains("arrows move the cursor"));
assert!(help.contains("[Alt-Up]/[Alt-Down] scroll transcript while Prompt is focused"));
assert!(help.contains("mouse wheel over Transcript also scrolls transcript"));
assert!(!help.contains("with Transcript focused"));
assert!(help.contains("[Ctrl-Shift-Right] uses activity-focused columns"));
assert!(help.contains("[Alt-Right]/[Alt-Left] remain secondary aliases"));
assert!(help.contains(
"Long prompt: use cursor keys to navigate; the prompt does not scroll with the mouse wheel."
));
assert!(!help.contains("Alt-Up/Alt-Down or mouse wheel over Prompt"));
assert!(!help.contains("scroll prompt up"));
assert!(!help.contains("scroll prompt down"));
assert!(help.contains("mouse wheel over Help scrolls this text"));
assert!(help.contains("Mouse: wheel follows the hovered scrollable pane; the Prompt editor is not mouse-scrollable"));
assert!(help.contains("System prompt modal: [Esc]/[q] close; [Up]/[Down]/[Alt-Up]/[Alt-Down]/[PageUp]/[PageDown]/[Home]/[End] and mouse wheel scroll."));
assert!(help.contains("@ anywhere tags cwd files with fuzzy matching"));
assert!(help.contains("$ anywhere tags enabled skills with fuzzy matching"));
assert!(help.contains("/login openai-codex authenticates"));
assert!(help.contains("/logout openai-codex removes local auth"));
assert!(help.contains("/new starts a fresh session"));
assert!(help.contains("/quit exits"));
assert!(help.contains("[Ctrl-T] focuses activity tree"));
assert!(help.contains("[Tab] cycles Prompt, Transcript"));
assert!(help.contains("[Alt-1] Activity, [Alt-2] Summary"));
assert!(help.contains("[Alt-T]"));
assert!(!help.contains("Ctrl-Shift-T"));
assert!(!help.contains("future work in TUI mode"));
}