use super::*;
#[test]
fn humanize_duration_compact_ladder() {
assert_eq!(humanize_duration_compact(0), "0s");
assert_eq!(humanize_duration_compact(42), "42s");
assert_eq!(humanize_duration_compact(59), "59s");
assert_eq!(humanize_duration_compact(60), "1m");
assert_eq!(humanize_duration_compact(300), "5m");
assert_eq!(humanize_duration_compact(3599), "59m");
assert_eq!(humanize_duration_compact(3600), "1h");
assert_eq!(humanize_duration_compact(3 * 3600), "3h");
assert_eq!(humanize_duration_compact(86399), "23h");
assert_eq!(humanize_duration_compact(86400), "1d");
assert_eq!(humanize_duration_compact(2 * 86400), "2d");
}
#[test]
fn format_size_ladder() {
assert_eq!(format_size(0), "0B");
assert_eq!(format_size(1), "1B");
assert_eq!(format_size(1023), "1023B");
assert_eq!(format_size(1024), "1.0KB");
assert_eq!(format_size(4312), "4.2KB");
assert_eq!(format_size(1024 * 1024), "1.0MB");
assert_eq!(format_size(5 * 1024 * 1024 + 512 * 1024), "5.5MB");
assert_eq!(format_size(3 * 1024 * 1024 * 1024), "3.0GB");
}
#[test]
fn format_float_display_collapses_f32_widening() {
assert_eq!(format_float_display(f64::from(0.7f32)), "0.7");
assert_eq!(format_float_display(f64::from(0.3f32)), "0.3");
assert_eq!(format_float_display(f64::from(-0.7f32)), "-0.7");
assert_eq!(format_float_display(0.3333333333333333), "0.3333333333333333");
}
#[test]
fn format_float_display_snaps_stepper_artifacts_to_grid() {
assert_eq!(format_float_display(f64::from(0.7f32) + 0.1), "0.8");
assert_eq!(format_float_display(0.799999988079071), "0.8");
assert_eq!(format_float_display(0.1 + 0.2), "0.3");
assert_eq!(format_float_display(-0.799999988079071), "-0.8");
assert_eq!(format_float_display(0.123456789), "0.123456789");
}
#[test]
fn format_float_display_keeps_plain_floats() {
assert_eq!(format_float_display(1.0), "1");
assert_eq!(format_float_display(0.0), "0");
assert_eq!(format_float_display(0.05), "0.05");
assert_eq!(format_float_display(0.75), "0.75");
}
#[test]
fn truncate_byte_budget_ascii() {
assert_eq!(truncate_byte_budget("hello world", 5, "..."), "hello...");
assert_eq!(truncate_byte_budget("hi", 10, "..."), "hi");
}
#[test]
fn contains_ignore_ascii_case_matches_str_contains_semantics() {
assert!(contains_ignore_ascii_case("MiniMax-M2.5", "minimax-m2.5"));
assert!(contains_ignore_ascii_case("minimax-m2.5", "MINIMAX"));
assert!(contains_ignore_ascii_case("openai/gpt-5", "GPT-5"));
assert!(contains_ignore_ascii_case("anything", ""));
assert!(!contains_ignore_ascii_case("glm", "glm-4.5"));
assert!(!contains_ignore_ascii_case("openai/gpt-5", "minimax"));
}
#[test]
fn contains_ignore_ascii_case_matches_lowercased_contains_for_unicode() {
for (haystack, needle) in [
("café GLM-5", "glm-5"),
("日本語 <THINK", "<think"),
("straße", "STRASSE"),
("naïve", "naïve"),
] {
assert_eq!(
contains_ignore_ascii_case(haystack, needle),
haystack.to_ascii_lowercase().contains(&needle.to_ascii_lowercase()),
"mismatch for haystack={haystack:?} needle={needle:?}"
);
}
}
#[test]
fn truncate_byte_budget_cjk_no_panic() {
let jp = "こんにちは";
assert_eq!(truncate_byte_budget(jp, 5, "…"), "こ…");
assert_eq!(truncate_byte_budget(jp, 6, "…"), "こん…");
}
#[test]
fn truncate_byte_budget_mixed_ascii_cjk() {
let mixed = "AB日本語CD";
assert_eq!(truncate_byte_budget(mixed, 4, ".."), "AB.."); assert_eq!(truncate_byte_budget(mixed, 5, ".."), "AB日.."); }
#[test]
fn truncate_byte_budget_emoji() {
let emoji = "👋🌍"; assert_eq!(truncate_byte_budget(emoji, 5, "!"), "👋!");
}
#[test]
fn truncate_byte_budget_zero() {
assert_eq!(truncate_byte_budget("abc", 0, "..."), "...");
}
#[test]
fn compact_reasoning_text_collapses_blank_runs() {
assert_eq!(compact_reasoning_text("line1\n\n\n\nline2\n"), "line1\n\nline2");
assert_eq!(compact_reasoning_text("a\n\n\n\n\n\nb"), "a\n\nb");
}
#[test]
fn compact_reasoning_text_preserves_single_paragraph_breaks() {
assert_eq!(compact_reasoning_text("para one\n\npara two\n"), "para one\n\npara two");
}
#[test]
fn compact_reasoning_text_trims_trailing_whitespace() {
assert_eq!(compact_reasoning_text(" a \n\n\n b \n"), "a\n\nb");
}
#[test]
fn compact_reasoning_text_strips_leading_trailing_blanks() {
assert_eq!(compact_reasoning_text("\n\n\nmid\n\n\n"), "mid");
assert_eq!(compact_reasoning_text("\n\n\n"), "");
assert_eq!(compact_reasoning_text(""), "");
}
#[test]
fn wrap_text_words_basic_and_continuation_width() {
assert_eq!(wrap_text_words("the quick brown fox", 9, 9), vec!["the quick", "brown fox"]);
assert_eq!(wrap_text_words("alpha beta gamma delta", 11, 5), vec!["alpha beta", "gamma", "delta"]);
}
#[test]
fn wrap_text_words_blank_and_unicode() {
assert!(wrap_text_words(" ", 5, 5).is_empty());
let wrapped = wrap_text_words("あいう えお かきく", 3, 3);
assert_eq!(wrapped, vec!["あいう", "えお", "かきく"]);
}
#[test]
fn wrap_shell_command_keeps_screenshot_pipeline_in_full() {
let command = "grep -rn \"@vinhnx/vtcode|npm install -g||npx @vinhnx\" docs | grep -v node_modules | grep -v package-lock | grep -v \"\\.backup\"";
let wrapped = wrap_shell_command(command, 62, 58);
assert_eq!(
wrapped,
vec![
"grep -rn \"@vinhnx/vtcode|npm install -g||npx @vinhnx\" docs |",
"grep -v node_modules | grep -v package-lock | grep -v",
"\"\\.backup\"",
]
);
assert!(wrapped.iter().all(|line| !line.contains('…')));
assert_eq!(wrapped.join(" "), command);
assert_eq!(wrapped.join("\n").matches('|').count(), 6);
}
#[test]
fn wrap_shell_command_breaks_only_at_unquoted_spaces() {
assert_eq!(wrap_shell_command("echo \"a b c\" d", 10, 10), vec!["echo", "\"a b c\" d"]);
assert_eq!(wrap_shell_command(" ", 5, 5), Vec::<String>::new());
}
#[test]
fn wrap_shell_command_splits_overlong_token_at_width() {
assert_eq!(wrap_shell_command("abcdefghij", 4, 4), vec!["abcd", "efgh", "ij"]);
}
#[test]
fn wrap_shell_command_with_continuations_marks_wrapped_lines() {
assert_eq!(wrap_shell_command_with_continuations("echo a b c d", 7, 7), vec!["echo a \\", "b c d"]);
assert_eq!(wrap_shell_command_with_continuations("git status --short", 62, 58), vec!["git status --short"]);
assert!(wrap_shell_command_with_continuations(" ", 5, 5).is_empty());
let command = "git add a b && git commit -m \"msg\" && git status --short";
let wrapped = wrap_shell_command_with_continuations(command, 20, 20);
assert!(wrapped.len() > 1, "expected wrapping: {wrapped:?}");
assert!(wrapped[..wrapped.len() - 1].iter().all(|line| line.ends_with(" \\")));
assert!(!wrapped.last().unwrap().ends_with(" \\"));
assert!(wrapped.iter().all(|line| !line.contains('…')));
assert_eq!(wrap_shell_command_with_continuations("echo a && echo b", 10, 10), vec!["echo a && \\", "echo b"]);
assert_eq!(wrap_shell_command_with_continuations("a | b | c", 7, 5), vec!["a | \\", "b | \\", "c"]);
assert_eq!(
wrap_shell_command_with_continuations("grep -rn \"a||b\" docs", 62, 58),
vec!["grep -rn \"a||b\" docs"]
);
assert_eq!(wrap_shell_command_with_continuations("echo a && echo b", 62, 58), vec!["echo a && echo b"]);
assert_eq!(wrap_shell_command_with_continuations("a | b | c", 62, 58), vec!["a | b | c"]);
assert_eq!(wrap_shell_command_lines("echo a && echo b", 10, 10), vec!["echo a &&", "echo b"]);
assert_eq!(wrap_shell_command_lines("git status", 62, 58), vec!["git status"]);
}
#[test]
fn wrap_shell_command_narrow_continuation_never_overflows() {
let token = "b".repeat(60);
let command = format!("aa {token} cc");
let wrapped = wrap_shell_command(&command, 62, 58);
assert!(wrapped.len() >= 3, "expected a split continuation: {wrapped:?}");
assert!(wrapped[0].chars().count() <= 62, "first line budget: {wrapped:?}");
for line in wrapped.iter().skip(1) {
assert!(line.chars().count() <= 58, "continuation budget: {wrapped:?}");
}
let wrapped = wrap_shell_command("aa bbbbbbbb cc", 10, 5);
assert_eq!(wrapped, vec!["aa", "bbbbb", "bbb", "cc"]);
}
#[test]
fn truncate_within_reserves_ellipsis_budget() {
assert_eq!(truncate_within("hello world", 8, "..."), "hello...");
assert_eq!(truncate_within("hi", 8, "..."), "hi");
assert_eq!(truncate_within("abcdef", 4, "…"), "abc…");
}
#[test]
fn truncate_within_counts_chars() {
let jp = "あいうえお"; assert_eq!(truncate_within(jp, 5, "…"), jp);
assert_eq!(truncate_within(jp, 3, "…"), "あい…");
}
#[test]
fn head_tail_truncate_keeps_both_ends() {
let value = "0123456789".repeat(10); let (out, truncated) = head_tail_truncate(&value, 40, " ... [truncated] ... ");
assert!(truncated);
assert!(out.chars().count() <= 40);
assert!(out.starts_with("012"));
assert!(out.contains("[truncated]"));
assert!(out.ends_with('9'));
}
#[test]
fn head_tail_truncate_passes_through_when_short() {
let (out, truncated) = head_tail_truncate("short", 64, " ... ");
assert_eq!(out, "short");
assert!(!truncated);
}
#[test]
fn head_tail_truncate_small_budget_falls_back_to_prefix() {
let marker = " ... [truncated] ... ";
let (out, truncated) = head_tail_truncate("abcdefghij", 5, marker);
assert!(truncated);
assert_eq!(out, "abcde");
let long_text = "abcdefghijklmnopqrstuvwxyz";
let (out2, truncated2) = head_tail_truncate(long_text, 17, marker);
assert!(truncated2);
assert_eq!(out2, "abcde [truncated]");
assert_eq!(out2.chars().count(), 17);
}
#[test]
fn truncate_text_counts_chars_not_bytes() {
let jp = "あいうえお"; assert_eq!(truncate_text(jp, 3, "…"), "あいう…");
assert_eq!(truncate_text(jp, 5, "…"), "あいうえお");
}
#[test]
fn truncate_middle_keeps_both_ends() {
assert_eq!(truncate_middle("short", 80), "short");
assert_eq!(truncate_middle("abcdefghij", 5), "ab…ij");
assert_eq!(truncate_middle("a b c", 80), "a b c");
assert_eq!(truncate_middle("abc", 0), "");
assert_eq!(truncate_middle("abc", 1), "…");
assert_eq!(truncate_middle("a\nb\tc", 80), "a b c");
}
#[test]
fn truncate_path_middle_breaks_at_separator() {
assert_eq!(truncate_path_middle("src/lib.rs", 80), "src/lib.rs");
assert_eq!(truncate_path_middle("foo/bar/baz/qux", 12), "foo…/qux");
assert_eq!(truncate_path_middle("abc", 0), "");
}
#[test]
fn truncate_path_middle_counts_chars_not_bytes() {
let out = truncate_path_middle("日本/日本語/日本語日本語/xyz", 6);
assert!(out.chars().count() <= 6, "{out:?}");
assert_eq!(truncate_path_middle("日本/日本語/xyz", 8), "日本…xyz");
}