use crate::tests;
const CMD: &str = "echo mock";
const EXPL: &str = "✅ explain mock";
fn setup(suffix: &str, responses: &[&str]) -> (std::path::PathBuf, u16) {
let home = tests::temp_home(suffix);
let port = tests::mock_ollama(responses);
tests::write_ollama_config(&home, port);
(home, port)
}
fn run(home: &std::path::Path, stdin: &[u8]) -> std::process::Output {
tests::run_with_stdin_interactive(home, &["show disk usage"], stdin)
}
fn stdout(out: &std::process::Output) -> String {
String::from_utf8_lossy(&out.stdout).trim().to_string()
}
#[test]
fn confirm_y_executes_command() {
let (home, _) = setup("edit_confirm_y", &[CMD]);
let out = run(&home, b"y");
assert!(out.status.success());
assert_eq!(stdout(&out), "mock");
}
#[test]
fn confirm_enter_executes_command() {
let (home, _) = setup("edit_confirm_enter", &[CMD]);
let out = run(&home, b"\n");
assert!(out.status.success());
assert_eq!(stdout(&out), "mock");
}
#[test]
fn confirm_n_prints_nothing() {
let (home, _) = setup("edit_confirm_n", &[CMD]);
let out = run(&home, b"n");
assert_eq!(out.status.code(), Some(130));
assert_eq!(stdout(&out), "");
}
#[test]
fn edit_no_change() {
let (home, _) = setup("edit_no_change", &[CMD]);
let out = run(&home, b"\x1b[A\ny");
assert!(out.status.success());
assert_eq!(stdout(&out), "mock");
}
#[test]
fn edit_append_suffix() {
let (home, _) = setup("edit_append_suffix", &[CMD]);
let out = run(&home, b"\x1b[A suffix\ny");
assert!(out.status.success());
assert_eq!(stdout(&out), "mock suffix");
}
#[test]
fn edit_backspace_removes_chars() {
let (home, _) = setup("edit_backspace", &[CMD]);
let out = run(&home, b"\x1b[A\x7f\x7f\ny");
assert!(out.status.success());
assert_eq!(stdout(&out), "mo");
}
#[test]
fn edit_delete_key_removes_char_at_cursor() {
let (home, _) = setup("edit_delete_key", &[CMD]);
let out = run(&home, b"\x1b[A\x1b[D\x1b[3~\ny");
assert!(out.status.success());
assert_eq!(stdout(&out), "moc");
}
#[test]
fn edit_left_then_insert_char() {
let (home, _) = setup("edit_left_insert", &[CMD]);
let out = run(&home, b"\x1b[A\x1b[D\x1b[D\x1b[D\x1b[DX\x1b[F\ny");
assert!(out.status.success());
assert_eq!(stdout(&out), "Xmock");
}
#[test]
fn edit_home_then_insert_prefix() {
let (home, _) = setup("edit_home_insert", &[CMD]);
let out = run(&home, b"\x1b[A\x1b[Hprefix \ny");
assert!(out.status.success());
assert_eq!(stdout(&out), "");
}
#[test]
fn edit_insert_at_start_delete_from_end() {
let (home, _) = setup("edit_both_ends", &[CMD]);
let out = run(&home, b"\x1b[A\x1b[HX\x1b[F\x7f\ny");
assert!(out.status.success());
assert_eq!(stdout(&out), "");
}
#[test]
fn edit_right_at_end_is_noop() {
let (home, _) = setup("edit_right_noop", &[CMD]);
let out = run(&home, b"\x1b[A\x1b[C\ny");
assert!(out.status.success());
assert_eq!(stdout(&out), "mock");
}
#[test]
fn edit_twice_accumulates() {
let (home, _) = setup("edit_twice", &[CMD]);
let out = run(&home, b"\x1b[A a\n\x1b[A b\ny");
assert!(out.status.success());
assert_eq!(stdout(&out), "mock a b");
}
#[test]
fn explain_then_y_executes_command() {
let (home, _) = setup("explain_y", &[CMD, EXPL]);
let out = run(&home, b"ey");
assert!(out.status.success());
assert_eq!(stdout(&out), "mock");
}
#[test]
fn explain_then_n_prints_nothing() {
let (home, _) = setup("explain_n", &[CMD, EXPL]);
let out = run(&home, b"en");
assert_eq!(out.status.code(), Some(130));
assert_eq!(stdout(&out), "");
}
#[test]
fn explain_then_enter_executes_command() {
let (home, _) = setup("explain_enter", &[CMD, EXPL]);
let out = run(&home, b"e\n");
assert!(out.status.success());
assert_eq!(stdout(&out), "mock");
}
#[test]
fn explain_then_edit_then_y() {
let (home, _) = setup("explain_edit_y", &[CMD, EXPL]);
let out = run(&home, b"e\x1b[A fix\ny");
assert!(out.status.success());
assert_eq!(stdout(&out), "mock fix");
}
#[test]
fn explain_then_edit_then_explain_again_then_y() {
let (home, _) = setup("explain_edit_explain_y", &[CMD, EXPL, EXPL]);
let out = run(&home, b"e\x1b[A fix\ney");
assert!(out.status.success());
assert_eq!(stdout(&out), "mock fix");
}
#[test]
fn edit_then_explain_then_y() {
let (home, _) = setup("edit_explain_y", &[CMD, EXPL]);
let out = run(&home, b"\x1b[A fix\ney");
assert!(out.status.success());
assert_eq!(stdout(&out), "mock fix");
}
#[test]
fn edit_then_explain_then_n() {
let (home, _) = setup("edit_explain_n", &[CMD, EXPL]);
let out = run(&home, b"\x1b[A fix\nen");
assert_eq!(out.status.code(), Some(130));
assert_eq!(stdout(&out), "");
}
#[test]
fn edit_explain_edit_again_then_y() {
let (home, _) = setup("edit_expl_edit_y", &[CMD, EXPL]);
let out = run(&home, b"\x1b[A a\ne\x1b[A b\ny");
assert!(out.status.success());
assert_eq!(stdout(&out), "mock a b");
}
#[test]
fn interactive_mode_edit_then_execute() {
let home = tests::temp_home("interactive_edit");
let port = tests::mock_ollama(&["echo before"]);
tests::write_ollama_config(&home, port);
let out = tests::run_with_stdin_interactive(&home, &[], b"show files\n\x1b[A after\ny");
assert!(
out.status.success(),
"stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
let output = String::from_utf8_lossy(&out.stdout);
assert!(
output.contains("before after"),
"expected executed output to contain 'before after', got: {output}"
);
}