pub(crate) fn truncate_chars(s: &str, max_chars: usize) -> &str {
match s.char_indices().nth(max_chars) {
Some((byte_idx, _)) => s.split_at(byte_idx).0,
None => s,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn shorter_than_limit_is_unchanged() {
assert_eq!(truncate_chars("abc", 10), "abc");
assert_eq!(truncate_chars("", 5), "");
}
#[test]
fn exact_length_is_unchanged() {
assert_eq!(truncate_chars("abc", 3), "abc");
}
#[test]
fn ascii_truncates_at_limit() {
assert_eq!(truncate_chars("abcdef", 3), "abc");
assert_eq!(truncate_chars("abc", 0), "");
}
#[test]
fn multibyte_never_splits_a_character() {
assert_eq!(truncate_chars("日本語", 2), "日本");
assert_eq!(truncate_chars("日本語", 1), "日");
assert_eq!(truncate_chars("日本語", 3), "日本語");
assert_eq!(truncate_chars("日本語", 99), "日本語");
}
#[test]
fn handles_four_byte_characters() {
let s = "𝄞𝄞𝄞";
assert_eq!(s.len(), 12);
assert_eq!(truncate_chars(s, 1), "𝄞");
assert_eq!(truncate_chars(s, 2).chars().count(), 2);
}
#[test]
fn mixed_width_input() {
let s = "ab日cd";
assert_eq!(s.len(), 7);
assert_eq!(truncate_chars(s, 3), "ab日");
assert_eq!(truncate_chars(s, 4), "ab日c");
}
#[test]
fn all_prefixes_are_valid_for_adversarial_input() {
let s = "a日b𝄞c\u{0301}d";
for n in 0..=s.chars().count().saturating_add(3) {
let got = truncate_chars(s, n);
assert!(
s.starts_with(got),
"prefix {n} is not a prefix of the input"
);
assert!(got.chars().count() <= n);
}
}
}