use std::sync::{Arc, Mutex};
use crate::{
EditorContext, PROMPT_HISTORY_PREVIEW_MAX_BYTES, PROMPT_HISTORY_PREVIEW_TOTAL_BYTES,
PROMPT_HISTORY_SEARCH_MAX_ROWS, PROMPT_HISTORY_SUMMARY_MAX_CHARS, PromptShellAction,
PromptShellCommand, PromptShellResult, prompt_history_preview_dir, prompt_history_search_rows,
run_prompt_shell_action,
};
#[test]
fn search_rows_are_newest_first_and_keep_multiline_prompts_one_row() {
let history = vec![
"old prompt".to_owned(),
"newer\nmultiline prompt".to_owned(),
];
let rows = prompt_history_search_rows(&history);
assert_eq!(rows, "1\tnewer multiline prompt\n0\told prompt\n");
}
#[test]
fn search_rows_and_previews_are_bounded_before_picker_launch() {
let huge_prompt = "word ".repeat(PROMPT_HISTORY_SUMMARY_MAX_CHARS + 100)
+ &"x".repeat(PROMPT_HISTORY_PREVIEW_MAX_BYTES * 2);
let history: Vec<String> = (0..(PROMPT_HISTORY_SEARCH_MAX_ROWS + 10))
.map(|index| format!("prompt {index} {huge_prompt}"))
.collect();
let rows = prompt_history_search_rows(&history);
assert_eq!(rows.lines().count(), PROMPT_HISTORY_SEARCH_MAX_ROWS);
assert!(
rows.lines()
.all(|line| line.chars().count() < PROMPT_HISTORY_SUMMARY_MAX_CHARS + 32)
);
assert!(rows.starts_with(&(PROMPT_HISTORY_SEARCH_MAX_ROWS + 9).to_string()));
let preview_dir = prompt_history_preview_dir(&history).expect("preview dir");
let previews: Vec<_> = std::fs::read_dir(preview_dir.path())
.expect("read preview dir")
.collect::<Result<_, _>>()
.expect("preview entries");
assert_eq!(previews.len(), PROMPT_HISTORY_SEARCH_MAX_ROWS);
let mut total_preview_bytes = 0usize;
for entry in previews {
let len = entry.metadata().expect("preview metadata").len() as usize;
total_preview_bytes += len;
assert!(
len <= PROMPT_HISTORY_PREVIEW_MAX_BYTES,
"preview {:?} had {len} bytes",
entry.path()
);
}
assert!(
total_preview_bytes <= PROMPT_HISTORY_PREVIEW_TOTAL_BYTES,
"preview directory used {total_preview_bytes} bytes"
);
}
#[test]
fn search_rows_truncate_single_token_summary_at_cap() {
let huge_prompt = "x".repeat(PROMPT_HISTORY_SUMMARY_MAX_CHARS * 100);
let rows = prompt_history_search_rows(&[huge_prompt]);
let summary = rows
.strip_prefix("0\t")
.and_then(|row| row.strip_suffix('\n'))
.expect("single history row");
assert_eq!(summary.chars().count(), PROMPT_HISTORY_SUMMARY_MAX_CHARS);
assert!(summary.ends_with('…'));
}
#[test]
fn empty_history_search_returns_none_without_spawning_picker() {
let (term, handle, _input_tx) = tau_cli_term_raw::Term::new_virtual(
80,
24,
"> ",
Box::new(std::io::sink()),
crate::CursorShape::Bar,
);
let temp_dir = tempfile::tempdir().expect("marker tempdir");
let marker = temp_dir.path().join("spawned");
let action = PromptShellAction::HistorySearch(PromptShellCommand {
command: format!("printf spawned > '{}'", marker.display()),
trim: true,
});
let result = run_prompt_shell_action(
&term,
&handle,
Arc::new(Mutex::new(EditorContext::default())),
None,
&[],
action,
)
.expect("empty history search action");
assert!(result.is_none());
assert!(!marker.exists(), "history picker command was spawned");
}
#[test]
fn selected_history_prompt_replaces_buffer_and_can_be_undone() {
let (term, handle, _input_tx) = tau_cli_term_raw::Term::new_virtual(
80,
24,
"> ",
Box::new(std::io::sink()),
crate::CursorShape::Bar,
);
handle.set_buffer("current draft".to_owned(), "current draft".len());
let history = vec!["old".to_owned(), "chosen\noriginal".to_owned()];
let action = PromptShellAction::HistorySearch(PromptShellCommand {
command: r#"index=$(head -n 1 | cut -f1); expected=$(printf 'chosen\noriginal'); test "$(cat "$TAU_PROMPT_HISTORY_DIR/$index")" = "$expected"; printf '%s\n' "$index""#.to_owned(),
trim: true,
});
let result = run_prompt_shell_action(
&term,
&handle,
Arc::new(Mutex::new(EditorContext::default())),
None,
&history,
action,
)
.expect("history search action")
.expect("selected prompt");
match result {
PromptShellResult::ReplacePreservingUndo(text) => {
assert_eq!(text, "chosen\noriginal");
handle.set_buffer_preserving_undo(text, "chosen\noriginal".len());
}
_ => panic!("expected undo-preserving replacement"),
}
assert_eq!(handle.get_buffer(), "chosen\noriginal");
assert!(term.trigger_undo());
assert_eq!(handle.get_buffer(), "current draft");
}