use super::*;
#[test]
fn enter_on_sole_slash_completion_accepts_and_submits() {
let state = test_tui_state();
state.autocomplete.set_provider(Arc::new(
SlashCommandAutocompleteProvider::with_default_commands(),
));
let editor = state.editor.clone();
editor.set_text("/clea");
editor.set_cursor(0, 5);
let action = enter_completion_action(&state, &editor).expect("a decision");
assert!(
matches!(action, EnterCompletionAction::ApplyAndSubmit),
"a sole slash completion on Enter must apply and submit, got {action:?}"
);
}
#[test]
fn enter_on_ambiguous_slash_prefix_takes_highlighted() {
let state = test_tui_state();
state.autocomplete.set_provider(Arc::new(
SlashCommandAutocompleteProvider::with_default_commands(),
));
let editor = state.editor.clone();
editor.set_text("/c");
editor.set_cursor(0, 2);
let action = enter_completion_action(&state, &editor).expect("a decision");
assert!(
matches!(action, EnterCompletionAction::Submit),
"ambiguous popup must keep plain submit, got {action:?}"
);
assert!(navigate_autocomplete(&state, &editor, 1));
let action = enter_completion_action(&state, &editor).expect("a decision");
assert!(
matches!(action, EnterCompletionAction::ApplyAndSubmit),
"highlighted row + Enter must apply, got {action:?}"
);
}
#[test]
fn enter_on_path_completion_applies_without_submitting() {
let state = test_tui_state();
let dir = std::env::temp_dir().join("enter-complete-probe");
std::fs::create_dir_all(&dir).unwrap();
std::fs::write(dir.join("notes-a.txt"), b"x").unwrap();
std::fs::write(dir.join("notes-b.txt"), b"x").unwrap();
let mut provider = FilePathAutocompleteProvider::new();
let _ = &mut provider;
state
.autocomplete
.set_provider(Arc::new(FilePathAutocompleteProvider::with_root(dir)));
let editor = state.editor.clone();
editor.set_text("@notes-a");
editor.set_cursor(0, 8);
let action = enter_completion_action(&state, &editor).expect("a decision");
assert!(
matches!(action, EnterCompletionAction::ApplyOnly),
"a path completion on Enter must apply without submitting, got {action:?}"
);
}
#[test]
fn enter_key_variants_resolve_completion_before_editor_submit() {
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
let variants = [
KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE),
KeyEvent::new(KeyCode::Char('\n'), KeyModifiers::NONE),
KeyEvent::new(KeyCode::Char('\r'), KeyModifiers::NONE),
];
for key in variants {
assert!(
key.modifiers == KeyModifiers::NONE
&& matches!(
key.code,
KeyCode::Enter | KeyCode::Char('\n') | KeyCode::Char('\r')
),
"variant must be classified as bare Enter"
);
let state = test_tui_state();
state.autocomplete.set_provider(Arc::new(
SlashCommandAutocompleteProvider::with_default_commands(),
));
let editor = state.editor.clone();
editor.set_text("/hel");
editor.set_cursor(0, 4);
let completion = resolve_enter_completion(&state, &editor).expect("a decision");
assert!(
matches!(completion.action, EnterCompletionAction::ApplyAndSubmit),
"{key:?}: sole completion must apply and submit"
);
assert!(
completion.apply(&state, &editor),
"{key:?}: completion must be applied"
);
assert_eq!(editor.get_text(), "/help", "{key:?}");
}
}
#[test]
fn accept_enter_completion_completes_text_and_closes_popup() {
let state = test_tui_state();
state.autocomplete.set_provider(Arc::new(
SlashCommandAutocompleteProvider::with_default_commands(),
));
let editor = state.editor.clone();
editor.set_text("/clea");
editor.set_cursor(0, 5);
refresh_autocomplete(&state, &editor);
assert!(
state.autocomplete_container.child_count() > 0,
"popup must be showing before Enter"
);
let completion = resolve_enter_completion(&state, &editor).expect("a decision");
let applied = completion.apply(&state, &editor);
assert!(applied, "the sole completion must be applied");
assert_eq!(editor.get_text(), "/clear");
assert_eq!(
state.autocomplete_container.child_count(),
0,
"popup must be closed after Enter-accept"
);
assert_eq!(
*state.autocomplete_selection.lock().unwrap(),
0,
"selection must reset for the next popup"
);
}
#[test]
fn enter_completion_decision_is_prefix_scoped() {
let state = test_tui_state();
state.autocomplete.set_provider(Arc::new(
SlashCommandAutocompleteProvider::with_default_commands(),
));
let editor = state.editor.clone();
editor.set_text("hello /clea");
editor.set_cursor(0, 11);
assert!(
enter_completion_action(&state, &editor).is_none(),
"no popup context → no interception"
);
editor.set_text("hello /clea");
editor.set_cursor(0, 11);
let _ = state.autocomplete.get_suggestions("hello /clea", 11);
assert!(
enter_completion_action(&state, &editor).is_none(),
"non-start slash token must not be intercepted"
);
}
#[test]
fn unknown_slash_input_keeps_falling_through_to_the_model() {
let state = test_tui_state();
state.autocomplete.set_provider(Arc::new(
SlashCommandAutocompleteProvider::with_default_commands(),
));
let editor = state.editor.clone();
editor.set_text("/skill:draw");
editor.set_cursor(0, 11);
assert!(
enter_completion_action(&state, &editor).is_none()
|| matches!(
enter_completion_action(&state, &editor),
Some(EnterCompletionAction::Submit)
),
"unknown slash input must never be rewritten"
);
assert_eq!(editor.get_text(), "/skill:draw");
}
#[test]
fn already_complete_command_is_idempotent_under_enter_hook() {
let state = test_tui_state();
state.autocomplete.set_provider(Arc::new(
SlashCommandAutocompleteProvider::with_default_commands(),
));
let editor = state.editor.clone();
editor.set_text("/clear");
editor.set_cursor(0, 6);
let completion = resolve_enter_completion(&state, &editor).expect("a decision");
assert!(
matches!(completion.action, EnterCompletionAction::ApplyAndSubmit),
"exact command still resolves through the hook"
);
assert!(completion.apply(&state, &editor));
assert_eq!(
editor.get_text(),
"/clear",
"applying the completion of an already-complete command is idempotent"
);
}
#[test]
fn stale_highlight_is_clamped_when_candidates_narrow() {
let state = test_tui_state();
state.autocomplete.set_provider(Arc::new(
SlashCommandAutocompleteProvider::with_default_commands(),
));
let editor = state.editor.clone();
editor.set_text("/c");
editor.set_cursor(0, 2);
assert!(matches!(
enter_completion_action(&state, &editor),
Some(EnterCompletionAction::Submit)
));
editor.set_text("/clea");
editor.set_cursor(0, 5);
*state.autocomplete_selection.lock().unwrap() = 1; let completion = resolve_enter_completion(&state, &editor).expect("a decision");
assert_eq!(completion.action, EnterCompletionAction::ApplyAndSubmit);
assert!(completion.apply(&state, &editor));
assert_eq!(editor.get_text(), "/clear");
}
#[test]
fn multiline_draft_slash_line_matches_tab_completion_semantics() {
let state = test_tui_state();
state.autocomplete.set_provider(Arc::new(
SlashCommandAutocompleteProvider::with_default_commands(),
));
let editor = state.editor.clone();
editor.set_text("first\n/clea");
editor.set_cursor(1, 5);
assert_eq!(
state
.autocomplete
.get_suggestions("first\n/clea", 11)
.is_some(),
enter_completion_action(&state, &editor).is_some(),
"the Enter hook must never see a completion the Tab path doesn't"
);
editor.set_text("/clea");
editor.set_cursor(0, 5);
assert!(matches!(
enter_completion_action(&state, &editor),
Some(EnterCompletionAction::ApplyAndSubmit)
));
}