1use std::io::Write;
2
3use super::{diff_algorithm::Algorithm, draw_diff::DrawDiff, themes::Theme};
4
5pub fn diff(w: &mut dyn Write, old: &str, new: &str, theme: &dyn Theme) -> std::io::Result<()> {
52 if !Algorithm::has_available_algorithms() {
54 return write!(
55 w,
56 "Error: No diff algorithms are available. Enable either 'myers' or 'similar' feature."
57 );
58 }
59
60 let output: DrawDiff<'_> = DrawDiff::new(old, new, theme);
61 write!(w, "{output}")
62}
63
64pub fn diff_with_algorithm(
94 w: &mut dyn Write,
95 old: &str,
96 new: &str,
97 theme: &dyn Theme,
98 algorithm: Algorithm,
99) -> std::io::Result<()> {
100 if !Algorithm::has_available_algorithms() {
102 return write!(
103 w,
104 "Error: No diff algorithms are available. Enable either 'myers' or 'similar' feature."
105 );
106 }
107
108 let available_algorithms = Algorithm::available_algorithms();
110 if !available_algorithms.contains(&algorithm) {
111 if let Some(available_algo) = Algorithm::first_available() {
113 let output: DrawDiff<'_> = DrawDiff::with_algorithm(old, new, theme, available_algo);
114 return write!(w, "{output}");
115 }
116 return write!(
117 w,
118 "Error: No diff algorithms are available. Enable either 'myers' or 'similar' feature."
119 );
120 }
121
122 let output: DrawDiff<'_> = DrawDiff::with_algorithm(old, new, theme, algorithm);
123 write!(w, "{output}")
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129 use crate::themes::{ArrowsTheme, SignsTheme};
130 use std::io::{Cursor, Write};
131
132 #[test]
134 fn test_diff_with_arrows_theme() {
135 let old = "The quick brown fox";
136 let new = "The quick red fox";
137 let mut buffer = Cursor::new(Vec::new());
138 let theme = ArrowsTheme::default();
139
140 diff(&mut buffer, old, new, &theme).unwrap();
141
142 let output = String::from_utf8(buffer.into_inner()).expect("Not valid UTF-8");
143 assert!(output.contains("<The quick brown fox"));
144 assert!(output.contains(">The quick red fox"));
145 assert!(output.contains("< left / > right"));
146 }
147
148 #[test]
150 fn test_diff_with_signs_theme() {
151 let old = "The quick brown fox";
152 let new = "The quick red fox";
153 let mut buffer = Cursor::new(Vec::new());
154 let theme = SignsTheme::default();
155
156 diff(&mut buffer, old, new, &theme).unwrap();
157
158 let output = String::from_utf8(buffer.into_inner()).expect("Not valid UTF-8");
159 assert!(output.contains("-The quick brown fox"));
160 assert!(output.contains("+The quick red fox"));
161 assert!(output.contains("--- remove | insert +++"));
162 }
163
164 #[test]
166 fn test_diff_empty_inputs() {
167 let old = "";
168 let new = "";
169 let mut buffer = Cursor::new(Vec::new());
170 let theme = ArrowsTheme::default();
171
172 diff(&mut buffer, old, new, &theme).unwrap();
173
174 let output = String::from_utf8(buffer.into_inner()).expect("Not valid UTF-8");
175 assert_eq!(output, "< left / > right\n");
177 }
178
179 #[test]
181 fn test_diff_identical_inputs() {
182 let text = "same text";
183 let mut buffer = Cursor::new(Vec::new());
184 let theme = ArrowsTheme::default();
185
186 diff(&mut buffer, text, text, &theme).unwrap();
187
188 let output = String::from_utf8(buffer.into_inner()).expect("Not valid UTF-8");
189 assert!(output.contains("< left / > right"));
191 assert!(output.contains(" same text"));
192 assert!(!output.contains("<same text"));
193 assert!(!output.contains(">same text"));
194 }
195
196 #[test]
198 fn test_diff_multiline() {
199 let old = "line 1\nline 2\nline 3";
200 let new = "line 1\nmodified line 2\nline 3";
201 let mut buffer = Cursor::new(Vec::new());
202 let theme = ArrowsTheme::default();
203
204 diff(&mut buffer, old, new, &theme).unwrap();
205
206 let output = String::from_utf8(buffer.into_inner()).expect("Not valid UTF-8");
207 assert!(output.contains(" line 1\n"));
209 assert!(output.contains("<line 2\n"));
210 assert!(output.contains(">modified line 2\n"));
211 assert!(output.contains(" line 3"));
212 }
213
214 #[test]
216 fn test_diff_trailing_newline() {
217 let old = "line\n";
218 let new = "line";
219 let mut buffer = Cursor::new(Vec::new());
220 let theme = ArrowsTheme::default();
221
222 diff(&mut buffer, old, new, &theme).unwrap();
223
224 let output = String::from_utf8(buffer.into_inner()).expect("Not valid UTF-8");
225 assert!(output.contains("line␊"));
227 }
228
229 #[test]
231 fn test_diff_with_custom_theme() {
232 use std::borrow::Cow;
233
234 #[derive(Debug)]
235 struct CustomTheme;
236
237 impl Theme for CustomTheme {
238 fn equal_prefix<'this>(&self) -> Cow<'this, str> {
239 "=".into()
240 }
241
242 fn delete_prefix<'this>(&self) -> Cow<'this, str> {
243 "-".into()
244 }
245
246 fn insert_prefix<'this>(&self) -> Cow<'this, str> {
247 "+".into()
248 }
249
250 fn header<'this>(&self) -> Cow<'this, str> {
251 "CUSTOM HEADER\n".into()
252 }
253 }
254
255 let old = "old";
256 let new = "new";
257 let mut buffer = Cursor::new(Vec::new());
258 let theme = CustomTheme;
259
260 diff(&mut buffer, old, new, &theme).unwrap();
261
262 let output = String::from_utf8(buffer.into_inner()).expect("Not valid UTF-8");
263 assert!(output.contains("CUSTOM HEADER"));
264 assert!(output.contains("-old"));
265 assert!(output.contains("+new"));
266 }
267
268 #[test]
270 fn test_diff_writer_error() {
271 struct ErrorWriter;
272
273 impl Write for ErrorWriter {
274 fn write(&mut self, _buf: &[u8]) -> std::io::Result<usize> {
275 Err(std::io::Error::other("Test error"))
276 }
277
278 fn flush(&mut self) -> std::io::Result<()> {
279 Ok(())
280 }
281 }
282
283 let old = "old";
284 let new = "new";
285 let mut writer = ErrorWriter;
286 let theme = ArrowsTheme::default();
287
288 let result = diff(&mut writer, old, new, &theme);
289
290 assert!(result.is_err());
291 let error = result.unwrap_err();
292 assert_eq!(error.kind(), std::io::ErrorKind::Other);
293 assert_eq!(error.to_string(), "Test error");
294 }
295
296 #[test]
298 fn test_diff_with_algorithm_no_algorithms_available() {
299 let old = "old";
300 let new = "new";
301 let mut buffer = Cursor::new(Vec::new());
302 let theme = ArrowsTheme::default();
303
304 let mut test_buffer = Cursor::new(Vec::new());
306
307 if !Algorithm::has_available_algorithms() {
309 write!(
310 &mut test_buffer,
311 "Error: No diff algorithms are available. Enable either 'myers' or 'similar' feature."
312 ).unwrap();
313 }
314
315 let mut mock_buffer = Cursor::new(Vec::new());
317
318 let mock_no_algorithms = true;
320 if mock_no_algorithms {
321 write!(
322 &mut mock_buffer,
323 "Error: No diff algorithms are available. Enable either 'myers' or 'similar' feature."
324 ).unwrap();
325 }
326
327 let mock_output = String::from_utf8(mock_buffer.into_inner()).expect("Not valid UTF-8");
328 assert!(
329 mock_output.contains("Error: No diff algorithms are available"),
330 "Error message should be shown when no algorithms are available"
331 );
332
333 let result = diff_with_algorithm(&mut buffer, old, new, &theme, Algorithm::Myers);
335 assert!(result.is_ok());
336
337 let output = String::from_utf8(buffer.into_inner()).expect("Not valid UTF-8");
339
340 if Algorithm::has_available_algorithms() {
341 assert!(
343 !output.contains("Error: No diff algorithms are available"),
344 "Should not show error when algorithms are available"
345 );
346 } else {
347 assert!(
349 output.contains("Error: No diff algorithms are available"),
350 "Should show error when no algorithms are available"
351 );
352 }
353 }
354
355 #[test]
357 fn test_diff_large_inputs() {
358 let old = "a\n".repeat(1000);
360 let new = "a\n".repeat(500) + &"b\n".repeat(500);
361
362 let mut buffer = Cursor::new(Vec::new());
363 let theme = ArrowsTheme::default();
364
365 diff(&mut buffer, &old, &new, &theme).unwrap();
366
367 let output = String::from_utf8(buffer.into_inner()).expect("Not valid UTF-8");
368
369 let line_count = output.lines().count();
372 assert_eq!(line_count, 1 + 500 + 500 + 500);
373
374 assert!(output.contains(" a")); assert!(output.contains("<a")); assert!(output.contains(">b")); }
379
380 #[test]
384 #[cfg(all(feature = "myers", not(feature = "similar")))]
385 fn test_only_myers_algorithm() {
386 let old = "The quick brown fox";
387 let new = "The quick red fox";
388 let mut buffer = Cursor::new(Vec::new());
389 let theme = ArrowsTheme::default();
390
391 diff_with_algorithm(&mut buffer, old, new, &theme, Algorithm::Myers).unwrap();
393
394 let output = String::from_utf8(buffer.into_inner()).expect("Not valid UTF-8");
395 assert!(output.contains("<The quick brown fox"));
396 assert!(output.contains(">The quick red fox"));
397
398 let mut buffer = Cursor::new(Vec::new());
400 diff_with_algorithm(&mut buffer, old, new, &theme, Algorithm::Similar).unwrap();
401
402 let output = String::from_utf8(buffer.into_inner()).expect("Not valid UTF-8");
403 assert!(output.contains("<The quick brown fox"));
404 assert!(output.contains(">The quick red fox"));
405 }
406
407 #[test]
411 #[cfg(all(feature = "similar", not(feature = "myers")))]
412 fn test_only_similar_algorithm() {
413 let old = "The quick brown fox";
414 let new = "The quick red fox";
415 let mut buffer = Cursor::new(Vec::new());
416 let theme = ArrowsTheme::default();
417
418 diff_with_algorithm(&mut buffer, old, new, &theme, Algorithm::Similar).unwrap();
420
421 let output = String::from_utf8(buffer.into_inner()).expect("Not valid UTF-8");
422 assert!(output.contains("<The quick brown fox"));
423 assert!(output.contains(">The quick red fox"));
424
425 let mut buffer = Cursor::new(Vec::new());
427 diff_with_algorithm(&mut buffer, old, new, &theme, Algorithm::Myers).unwrap();
428
429 let output = String::from_utf8(buffer.into_inner()).expect("Not valid UTF-8");
430 assert!(output.contains("<The quick brown fox"));
431 assert!(output.contains(">The quick red fox"));
432 }
433
434 #[test]
438 #[cfg(not(any(feature = "myers", feature = "similar")))]
439 fn test_no_algorithms_available() {
440 let old = "The quick brown fox";
441 let new = "The quick red fox";
442 let mut buffer = Cursor::new(Vec::new());
443 let theme = ArrowsTheme::default();
444
445 diff(&mut buffer, old, new, &theme).unwrap();
447
448 let output = String::from_utf8(buffer.into_inner()).expect("Not valid UTF-8");
449 assert!(output.contains("Error: No diff algorithms are available"));
450
451 let mut buffer = Cursor::new(Vec::new());
453 diff_with_algorithm(&mut buffer, old, new, &theme, Algorithm::Myers).unwrap();
454
455 let output = String::from_utf8(buffer.into_inner()).expect("Not valid UTF-8");
456 assert!(output.contains("Error: No diff algorithms are available"));
457 }
458
459 #[test]
461 fn test_diff_with_algorithm_unavailable() {
462 let old = "old";
463 let new = "new";
464 let mut buffer = Cursor::new(Vec::new());
465 let theme = ArrowsTheme::default();
466
467 if !Algorithm::has_available_algorithms() {
469 return;
470 }
471
472 let available_algorithms = Algorithm::available_algorithms();
474
475 let unavailable_algorithm = if available_algorithms.contains(&Algorithm::Myers)
477 && !available_algorithms.contains(&Algorithm::Similar)
478 {
479 Algorithm::Similar
480 } else if !available_algorithms.contains(&Algorithm::Myers)
481 && available_algorithms.contains(&Algorithm::Similar)
482 {
483 Algorithm::Myers
484 } else {
485 return;
487 };
488
489 diff_with_algorithm(&mut buffer, old, new, &theme, unavailable_algorithm).unwrap();
491
492 let output = String::from_utf8(buffer.into_inner()).expect("Not valid UTF-8");
493
494 assert!(
496 !output.contains("Error: No diff algorithms are available"),
497 "Should use an available algorithm instead of showing an error"
498 );
499 assert!(
500 output.contains("old") || output.contains("new"),
501 "Output should contain diff content"
502 );
503 }
504}