use std::borrow::Cow;
use std::io::Write;
use termdiff::{diff, DrawDiff, Theme};
fn render(old: &str, new: &str, theme: &dyn Theme) -> String {
format!("{}", DrawDiff::new(old, new, theme))
}
#[test]
#[cfg(feature = "arrows")]
fn arrows_theme_single_line_change() {
let theme = termdiff::ArrowsTheme::default();
assert_eq!(
render("old", "new", &theme),
"< left / > right\n<old\n>new\n",
);
}
#[test]
#[cfg(feature = "arrows")]
fn arrows_theme_multiline_change() {
let old = "The quick brown fox and\njumps over the sleepy dog";
let new = "The quick red fox and\njumps over the lazy dog";
let theme = termdiff::ArrowsTheme::default();
assert_eq!(
render(old, new, &theme),
"\
< left / > right
<The quick brown fox and
<jumps over the sleepy dog
>The quick red fox and
>jumps over the lazy dog
",
);
}
#[test]
#[cfg(feature = "signs")]
fn signs_theme_single_line_change() {
let old = "The quick brown fox and\njumps over the sleepy dog";
let new = "The quick red fox and\njumps over the lazy dog";
let theme = termdiff::SignsTheme::default();
assert_eq!(
render(old, new, &theme),
"\
--- remove | insert +++
-The quick brown fox and
-jumps over the sleepy dog
+The quick red fox and
+jumps over the lazy dog
",
);
}
#[test]
#[cfg(feature = "arrows")]
fn diff_writes_to_writer() {
let old = "The quick brown fox";
let new = "The quick red fox";
let theme = termdiff::ArrowsTheme::default();
let mut buffer = Vec::new();
diff(&mut buffer, old, new, &theme).unwrap();
let output = String::from_utf8(buffer).expect("Not valid UTF-8");
assert_eq!(output, render(old, new, &theme));
assert!(output.contains("<The quick brown fox"));
assert!(output.contains(">The quick red fox"));
assert!(output.contains("< left / > right"));
}
#[test]
fn diff_writer_error() {
struct ErrorWriter;
impl Write for ErrorWriter {
fn write(&mut self, _buf: &[u8]) -> std::io::Result<usize> {
Err(std::io::Error::other("write failed"))
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
#[derive(Debug)]
struct StubTheme;
impl Theme for StubTheme {
fn equal_prefix<'a>(&self) -> Cow<'a, str> {
" ".into()
}
fn delete_prefix<'a>(&self) -> Cow<'a, str> {
"<".into()
}
fn insert_prefix<'a>(&self) -> Cow<'a, str> {
">".into()
}
fn header<'a>(&self) -> Cow<'a, str> {
"\n".into()
}
}
let result = diff(&mut ErrorWriter, "old", "new", &StubTheme);
assert!(result.is_err());
}
#[test]
#[cfg(all(feature = "arrows", feature = "myers"))]
fn diff_with_algorithm_myers() {
use termdiff::{diff_with_algorithm, Algorithm};
let old = "The quick brown fox";
let new = "The quick red fox";
let theme = termdiff::ArrowsTheme::default();
let mut buffer = Vec::new();
diff_with_algorithm(&mut buffer, old, new, &theme, Algorithm::Myers).unwrap();
let output = String::from_utf8(buffer).expect("Not valid UTF-8");
assert!(output.contains("<The quick brown fox"));
assert!(output.contains(">The quick red fox"));
}
#[test]
#[cfg(all(feature = "arrows", feature = "similar"))]
fn diff_with_algorithm_similar() {
use termdiff::{diff_with_algorithm, Algorithm};
let old = "The quick brown fox";
let new = "The quick red fox";
let theme = termdiff::ArrowsTheme::default();
let mut buffer = Vec::new();
diff_with_algorithm(&mut buffer, old, new, &theme, Algorithm::Similar).unwrap();
let output = String::from_utf8(buffer).expect("Not valid UTF-8");
assert!(output.contains("<The quick brown fox"));
assert!(output.contains(">The quick red fox"));
}
#[test]
#[cfg(feature = "arrows")]
fn draw_diff_to_string() {
let theme = termdiff::ArrowsTheme::default();
let output: String = DrawDiff::new("old", "new", &theme).into();
assert!(output.contains("<old"));
assert!(output.contains(">new"));
}
#[test]
#[cfg(feature = "arrows")]
fn empty_inputs_produce_header_only() {
let theme = termdiff::ArrowsTheme::default();
assert_eq!(render("", "", &theme), "< left / > right\n");
}
#[test]
#[cfg(feature = "arrows")]
fn identical_inputs_show_no_changes() {
let theme = termdiff::ArrowsTheme::default();
let output = render("same text", "same text", &theme);
assert!(output.contains(" same text"));
assert!(!output.contains("<same text"));
assert!(!output.contains(">same text"));
}
#[test]
#[cfg(feature = "arrows")]
fn multiline_preserves_unchanged_lines() {
let old = "line 1\nline 2\nline 3\nline 4";
let new = "line 1\nmodified line 2\nline 3\nmodified line 4";
let theme = termdiff::ArrowsTheme::default();
let output = render(old, new, &theme);
assert!(output.contains(" line 1\n"));
assert!(output.contains("<line 2\n"));
assert!(output.contains(">modified line 2\n"));
assert!(output.contains(" line 3\n"));
assert!(output.contains("<line 4"));
assert!(output.contains(">modified line 4"));
}
#[test]
#[cfg(feature = "arrows")]
fn added_lines() {
let old = "line 1\nline 3";
let new = "line 1\nline 2\nline 3";
let theme = termdiff::ArrowsTheme::default();
let output = render(old, new, &theme);
assert!(output.contains(" line 1\n"));
assert!(output.contains(">line 2\n"));
assert!(output.contains(" line 3"));
}
#[test]
#[cfg(feature = "arrows")]
fn removed_lines() {
let old = "line 1\nline 2\nline 3";
let new = "line 1\nline 3";
let theme = termdiff::ArrowsTheme::default();
let output = render(old, new, &theme);
assert!(output.contains(" line 1"));
assert!(output.contains("<line 2"));
assert!(output.contains(" line 3"));
}
#[test]
#[cfg(feature = "arrows")]
fn large_input_stress() {
let old = "a\n".repeat(1000);
let new = "a\n".repeat(500) + &"b\n".repeat(500);
let theme = termdiff::ArrowsTheme::default();
let output = render(&old, &new, &theme);
assert_eq!(output.lines().count(), 1 + 500 + 500 + 500);
assert!(output.contains(" a"));
assert!(output.contains("<a"));
assert!(output.contains(">b"));
}
#[test]
fn custom_theme_minimal() {
#[derive(Debug)]
struct MinimalTheme;
impl Theme for MinimalTheme {
fn equal_prefix<'a>(&self) -> Cow<'a, str> {
"=".into()
}
fn delete_prefix<'a>(&self) -> Cow<'a, str> {
"!".into()
}
fn insert_prefix<'a>(&self) -> Cow<'a, str> {
"|".into()
}
fn header<'a>(&self) -> Cow<'a, str> {
"CUSTOM\n".into()
}
}
let output = render("old", "new", &MinimalTheme);
assert!(output.contains("CUSTOM"));
assert!(output.contains("!old"));
assert!(output.contains("|new"));
}
#[test]
fn custom_theme_overrides_all_methods() {
#[derive(Debug)]
struct FullTheme;
impl Theme for FullTheme {
fn equal_prefix<'a>(&self) -> Cow<'a, str> {
"=".into()
}
fn delete_prefix<'a>(&self) -> Cow<'a, str> {
"-".into()
}
fn insert_prefix<'a>(&self) -> Cow<'a, str> {
"+".into()
}
fn header<'a>(&self) -> Cow<'a, str> {
"HEADER\n".into()
}
fn highlight_insert<'a>(&self, input: &'a str) -> Cow<'a, str> {
format!("*{input}*").into()
}
fn highlight_delete<'a>(&self, input: &'a str) -> Cow<'a, str> {
format!("~{input}~").into()
}
fn equal_content<'a>(&self, input: &'a str) -> Cow<'a, str> {
format!("={input}").into()
}
fn delete_content<'a>(&self, input: &'a str) -> Cow<'a, str> {
format!("-{input}").into()
}
fn insert_line<'a>(&self, input: &'a str) -> Cow<'a, str> {
format!("+{input}").into()
}
fn line_end<'a>(&self) -> Cow<'a, str> {
"\r\n".into()
}
fn trailing_lf_marker<'a>(&self) -> Cow<'a, str> {
"[LF]".into()
}
}
let theme = FullTheme;
assert_eq!(theme.equal_prefix(), "=");
assert_eq!(theme.delete_prefix(), "-");
assert_eq!(theme.insert_prefix(), "+");
assert_eq!(theme.header(), "HEADER\n");
let input = "test";
assert_eq!(theme.highlight_insert(input), "*test*");
assert_eq!(theme.highlight_delete(input), "~test~");
assert_eq!(theme.equal_content(input), "=test");
assert_eq!(theme.delete_content(input), "-test");
assert_eq!(theme.insert_line(input), "+test");
assert_eq!(theme.line_end(), "\r\n");
assert_eq!(theme.trailing_lf_marker(), "[LF]");
}
#[test]
#[cfg(feature = "arrows_color")]
fn arrows_color_theme_renders() {
let old = "The quick brown fox and\njumps over the sleepy dog";
let new = "The quick red fox and\njumps over the lazy dog";
let theme = termdiff::ArrowsColorTheme::default();
let output = render(old, new, &theme);
assert!(output.contains("The quick brown fox and"));
assert!(output.contains("The quick red fox and"));
assert!(output.contains('\u{1b}'));
}
#[test]
#[cfg(feature = "signs_color")]
fn signs_color_theme_renders() {
let old = "The quick brown fox and\njumps over the sleepy dog";
let new = "The quick red fox and\njumps over the lazy dog";
let theme = termdiff::SignsColorTheme::default();
let output = render(old, new, &theme);
assert!(output.contains("The quick brown fox and"));
assert!(output.contains("The quick red fox and"));
assert!(output.contains('\u{1b}'));
}