use super::*;
fn diff_str(d: &mut LineDiff, s: &str) -> String {
String::from_utf8(d.diff(s)).expect("ascii/utf8 escapes")
}
#[test]
fn bootstrap_single_frame() {
let mut d = LineDiff::new();
assert_eq!(diff_str(&mut d, "Hello\n"), "Hello\n");
}
#[test]
fn unchanged_frame_emits_zero_bytes() {
let mut d = LineDiff::new();
let first = d.diff("Hello\n");
assert_eq!(first, b"Hello\n");
let second = d.diff("Hello\n");
assert!(second.is_empty(), "unchanged frame must emit zero bytes");
}
#[test]
fn single_changed_line() {
let mut d = LineDiff::new();
let _ = d.diff("Line 1\nLine 2\nLine 3\n");
let out = diff_str(&mut d, "Line 1\nUpdated\nLine 3\n");
assert_eq!(out, "\u{1b}[3A\u{1b}[E\u{1b}[1GUpdated\u{1b}[K\n\u{1b}[E");
}
#[test]
fn shrink_three_to_one() {
let mut d = LineDiff::new();
let _ = d.diff("Line 1\nLine 2\nLine 3\n");
let out = diff_str(&mut d, "Line 1\n");
assert_eq!(
out,
"\u{1b}[2K\u{1b}[1A\u{1b}[2K\u{1b}[1A\u{1b}[2K\u{1b}[G\u{1b}[1A\u{1b}[E"
);
}
#[test]
fn grow_one_to_three() {
let mut d = LineDiff::new();
let _ = d.diff("Line 1\n");
let out = diff_str(&mut d, "Line 1\nLine 2\nLine 3\n");
assert_eq!(
out,
"\u{1b}[1A\u{1b}[E\u{1b}[1GLine 2\u{1b}[K\n\u{1b}[1GLine 3\u{1b}[K\n"
);
}
#[test]
fn fullscreen_no_trailing_changed_first_line() {
let mut d = LineDiff::new();
let _ = d.diff("A\nB");
let out = diff_str(&mut d, "X\nB");
assert_eq!(out, "\u{1b}[1A\u{1b}[1GX\u{1b}[K\n");
}
#[test]
fn fullscreen_no_trailing_newline_grow() {
let mut d = LineDiff::new();
let _ = d.diff("A");
let out = diff_str(&mut d, "A\nB\nC");
assert_eq!(
out,
"\u{1b}[0A\u{1b}[E\u{1b}[1GB\u{1b}[K\n\u{1b}[1GC\u{1b}[K"
);
}
#[test]
fn shrink_no_trailing_newline() {
let mut d = LineDiff::new();
let _ = d.diff("A\nB");
let out = diff_str(&mut d, "A");
assert_eq!(out, "\u{1b}[2K\u{1b}[G\u{1b}[1A");
}
#[test]
fn render_to_newline_only() {
let mut d = LineDiff::new();
let _ = d.diff("Line 1\nLine 2\nLine 3\n");
let out = diff_str(&mut d, "\n");
assert_eq!(
out,
"\u{1b}[2K\u{1b}[1A\u{1b}[2K\u{1b}[1A\u{1b}[2K\u{1b}[1A\u{1b}[2K\u{1b}[G\n"
);
assert!(d.diff("\n").is_empty());
}
#[test]
fn shrinking_tight_three_two_one() {
let mut d = LineDiff::new();
let _ = d.diff("Line 1\nLine 2\nLine 3\n");
let second = diff_str(&mut d, "Line 1\nLine 2\n");
assert_eq!(
second,
"\u{1b}[2K\u{1b}[1A\u{1b}[2K\u{1b}[G\u{1b}[2A\u{1b}[E\u{1b}[E"
);
let third = diff_str(&mut d, "Line 1\n");
assert_eq!(
third,
"\u{1b}[2K\u{1b}[1A\u{1b}[2K\u{1b}[G\u{1b}[1A\u{1b}[E"
);
}
#[test]
fn reset_restores_bootstrap() {
let mut d = LineDiff::new();
let _ = d.diff("Line 1\nLine 2\nLine 3\n");
d.reset();
assert_eq!(diff_str(&mut d, "Line 1\n"), "Line 1\n");
}
#[test]
fn escape_builders_match_oracle() {
assert_eq!(cursor_up(0), "\u{1b}[0A");
assert_eq!(cursor_up(3), "\u{1b}[3A");
assert_eq!(cursor_to(0), "\u{1b}[1G");
assert_eq!(cursor_to(5), "\u{1b}[6G");
assert_eq!(cursor_next_line(), "\u{1b}[E");
assert_eq!(erase_end_line(), "\u{1b}[K");
assert_eq!(erase_lines(0), "");
assert_eq!(erase_lines(1), "\u{1b}[2K\u{1b}[G");
assert_eq!(erase_lines(2), "\u{1b}[2K\u{1b}[1A\u{1b}[2K\u{1b}[G");
}
#[test]
fn line_ranges_match_split_on_corner_shapes() {
for s in ["", "\n", "a\n", "\na", "a\n\nb", "é\n😀\n", "no newline"] {
let via_ranges: Vec<&str> = line_ranges(s).into_iter().map(|r| &s[r]).collect();
let via_split: Vec<&str> = s.split('\n').collect();
assert_eq!(
via_ranges, via_split,
"line_ranges vs split('\\n') for {s:?}"
);
}
}
#[test]
fn changed_lines_is_zero_on_construction() {
let d = LineDiff::new();
assert_eq!(d.last_changed_lines(), 0);
}
#[test]
fn changed_lines_zero_on_unchanged_frame() {
let mut d = LineDiff::new();
let _ = d.diff("Line 1\nLine 2\nLine 3\n");
assert_eq!(d.last_changed_lines(), 3, "bootstrap repaints all 3 lines");
let second = d.diff("Line 1\nLine 2\nLine 3\n");
assert!(second.is_empty(), "unchanged frame emits zero bytes");
assert_eq!(
d.last_changed_lines(),
0,
"unchanged frame rewrites zero lines"
);
}
#[test]
fn changed_lines_one_on_single_line_change() {
let mut d = LineDiff::new();
let _ = d.diff("Line 1\nLine 2\nLine 3\n");
let out = diff_str(&mut d, "Line 1\nUpdated\nLine 3\n");
assert_eq!(out, "\u{1b}[3A\u{1b}[E\u{1b}[1GUpdated\u{1b}[K\n\u{1b}[E");
assert_eq!(
d.last_changed_lines(),
1,
"only the middle line was rewritten"
);
}
#[test]
fn changed_lines_counts_each_changed_line() {
let mut d = LineDiff::new();
let _ = d.diff("Line 1\nLine 2\nLine 3\n");
let _ = d.diff("Line 1\nAlpha\nBravo\n");
assert_eq!(
d.last_changed_lines(),
2,
"lines 2 and 3 changed, line 1 unchanged"
);
}
#[test]
fn changed_lines_full_repaint_on_bootstrap() {
for n in 1u32..=5 {
let mut d = LineDiff::new();
let frame: String = (1..=n).map(|i| format!("Line {i}\n")).collect();
let _ = d.diff(&frame);
assert_eq!(
d.last_changed_lines(),
n,
"bootstrap of {n} visible lines repaints all {n}"
);
}
}