diff --git a/Cargo.lock b/Cargo.lock index 467dd43d..60614355 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -75,9 +75,9 @@ dependencies = [ [[package]] name = "anstyle-svg" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84fef329014c87809e539be8caa9b817f2d072d7ecb39d7e3c6d96da17848a89" +checksum = "3c56c1ad3bf9b4773521547a33e2a90abf8c29c0f009b0ec2ff4c3b1c0ad9d59" dependencies = [ "anstream", "anstyle", diff --git a/crates/snapbox/Cargo.toml b/crates/snapbox/Cargo.toml index aee8ab84..8513bc9f 100644 --- a/crates/snapbox/Cargo.toml +++ b/crates/snapbox/Cargo.toml @@ -93,7 +93,7 @@ anstream = { version = "0.6.7", optional = true } document-features = { version = "0.2.6", optional = true } serde_json = { version = "1.0.85", optional = true} -anstyle-svg = { version = "0.1.0", optional = true } +anstyle-svg = { version = "0.1.1", optional = true } [target.'cfg(windows)'.dependencies] windows-sys = { version = "0.52.0", features = ["Win32_Foundation"], optional = true } diff --git a/crates/snapbox/src/data/mod.rs b/crates/snapbox/src/data/mod.rs index 796899d8..0abcf349 100644 --- a/crates/snapbox/src/data/mod.rs +++ b/crates/snapbox/src/data/mod.rs @@ -106,8 +106,8 @@ pub struct Data { source: Option, } -#[derive(Clone, Debug, PartialEq, Eq)] -enum DataInner { +#[derive(Clone, Debug)] +pub(crate) enum DataInner { Error(crate::Error), Binary(Vec), Text(String), @@ -367,6 +367,18 @@ impl Data { DataInner::TermSvg(_) => DataFormat::TermSvg, } } + + pub(crate) fn relevant(&self) -> Option<&str> { + match &self.inner { + DataInner::Error(_) => None, + DataInner::Binary(_) => None, + DataInner::Text(_) => None, + #[cfg(feature = "json")] + DataInner::Json(_) => None, + #[cfg(feature = "term-svg")] + DataInner::TermSvg(data) => text_elem(data), + } + } } impl From for Data { @@ -394,10 +406,44 @@ impl std::fmt::Display for Data { impl PartialEq for Data { fn eq(&self, other: &Data) -> bool { - self.inner == other.inner + match (&self.inner, &other.inner) { + (DataInner::Error(left), DataInner::Error(right)) => left == right, + (DataInner::Binary(left), DataInner::Binary(right)) => left == right, + (DataInner::Text(left), DataInner::Text(right)) => left == right, + #[cfg(feature = "json")] + (DataInner::Json(left), DataInner::Json(right)) => left == right, + #[cfg(feature = "term-svg")] + (DataInner::TermSvg(left), DataInner::TermSvg(right)) => { + // HACK: avoid including `width` and `height` in the comparison + let left = text_elem(left.as_str()); + let right = text_elem(right.as_str()); + left == right + } + (_, _) => false, + } } } +#[cfg(feature = "term-svg")] +fn text_elem(svg: &str) -> Option<&str> { + let open_elem_start_idx = svg.find("')?; + let open_elem_line_start_idx = svg[..open_elem_start_idx] + .rfind('\n') + .map(|idx| idx + 1) + .unwrap_or(svg.len()); + + let close_elem = ""; + let close_elem_start_idx = svg.rfind(close_elem).unwrap_or(svg.len()); + let close_elem_line_end_idx = svg[close_elem_start_idx..] + .find('\n') + .map(|idx| idx + close_elem_start_idx + 1) + .unwrap_or(svg.len()); + + let body = &svg[open_elem_line_start_idx..close_elem_line_end_idx]; + Some(body) +} + impl Eq for Data {} impl Default for Data { diff --git a/crates/snapbox/src/data/tests.rs b/crates/snapbox/src/data/tests.rs index 044448b2..680cf064 100644 --- a/crates/snapbox/src/data/tests.rs +++ b/crates/snapbox/src/data/tests.rs @@ -3,6 +3,74 @@ use serde_json::json; use super::*; +#[test] +#[cfg(feature = "term-svg")] +fn term_svg_eq() { + let left = Data::from(DataInner::TermSvg( + " +irrelevant + relevant + + +irrelevant" + .to_owned(), + )); + let right = Data::from(DataInner::TermSvg( + " +irrelevant + relevant + + +irrelevant" + .to_owned(), + )); + assert_eq!(left, right); + + let left = Data::from(DataInner::TermSvg( + " +irrelevant 1 + relevant + + +irrelevant 1" + .to_owned(), + )); + let right = Data::from(DataInner::TermSvg( + " +irrelevant 2 + relevant + + +irrelevant 2" + .to_owned(), + )); + assert_eq!(left, right); +} + +#[test] +#[cfg(feature = "term-svg")] +fn term_svg_ne() { + let left = Data::from(DataInner::TermSvg( + " +irrelevant 1 + relevant 1 + + +irrelevant 1" + .to_owned(), + )); + let right = Data::from(DataInner::TermSvg( + " +irrelevant 2 + relevant 2 + + +irrelevant 2" + .to_owned(), + )); + assert_ne!(left, right); +} + // Tests for checking to_bytes and render produce the same results #[test] fn text_to_bytes_render() { @@ -492,3 +560,69 @@ fn json_normalize_wildcard_array_middle_last_early_return() { assert_eq!(act, actual); } } + +#[cfg(feature = "term-svg")] +mod text_elem { + use super::super::*; + + #[test] + fn empty() { + let input = ""; + let expected = None; + let actual = text_elem(input); + assert_eq!(expected, actual); + } + + #[test] + fn no_open_tag() { + let input = "hello + +world!"; + let expected = None; + let actual = text_elem(input); + assert_eq!(expected, actual); + } + + #[test] + fn unclosed_open_text() { + let input = " +Hello + +world + +", + ); + let actual = text_elem(input); + assert_eq!(expected, actual); + } + + #[test] + fn no_end_tag() { + let input = " +Hello + +world"; + let expected = Some( + " +world", + ); + let actual = text_elem(input); + assert_eq!(expected, actual); + } +} diff --git a/crates/snapbox/src/report/diff.rs b/crates/snapbox/src/report/diff.rs index b980733b..6e8bd1cf 100644 --- a/crates/snapbox/src/report/diff.rs +++ b/crates/snapbox/src/report/diff.rs @@ -11,7 +11,35 @@ pub fn write_diff( #[allow(unused_mut)] let mut rendered = false; #[cfg(feature = "diff")] - if let (Some(expected), Some(actual)) = (expected.render(), actual.render()) { + if let (Some(expected_relevant), Some(actual_relevant)) = + (expected.relevant(), actual.relevant()) + { + let expected_rendered = expected.render().unwrap(); + let expected_line_offset = expected_rendered[..expected_rendered + .find(expected_relevant) + .unwrap_or(expected_rendered.len())] + .lines() + .count(); + let actual_rendered = actual.render().unwrap(); + let actual_line_offset = actual_rendered[..actual_rendered + .find(actual_relevant) + .unwrap_or(actual_rendered.len())] + .lines() + .count(); + write_diff_inner( + writer, + expected_relevant, + actual_relevant, + expected_name, + actual_name, + palette, + expected_line_offset, + actual_line_offset, + )?; + rendered = true; + } else if let (Some(expected), Some(actual)) = (expected.render(), actual.render()) { + let expected_line_offset = 0; + let actual_line_offset = 0; write_diff_inner( writer, &expected, @@ -19,6 +47,8 @@ pub fn write_diff( expected_name, actual_name, palette, + expected_line_offset, + actual_line_offset, )?; rendered = true; } @@ -41,6 +71,7 @@ pub fn write_diff( } #[cfg(feature = "diff")] +#[allow(clippy::too_many_arguments)] fn write_diff_inner( writer: &mut dyn std::fmt::Write, expected: &str, @@ -48,6 +79,8 @@ fn write_diff_inner( expected_name: Option<&dyn std::fmt::Display>, actual_name: Option<&dyn std::fmt::Display>, palette: crate::report::Palette, + expected_line_offset: usize, + actual_line_offset: usize, ) -> Result<(), std::fmt::Error> { let timeout = std::time::Duration::from_millis(500); let min_elide = 20; @@ -137,7 +170,16 @@ fn write_diff_inner( elided = false; match change.tag() { similar::ChangeTag::Insert => { - write_change(writer, change, "+", palette.actual, palette.info, palette)?; + write_change( + writer, + change, + "+", + palette.actual, + palette.info, + palette, + expected_line_offset, + actual_line_offset, + )?; } similar::ChangeTag::Delete => { write_change( @@ -147,10 +189,21 @@ fn write_diff_inner( palette.expected, palette.error, palette, + expected_line_offset, + actual_line_offset, )?; } similar::ChangeTag::Equal => { - write_change(writer, change, "|", palette.hint, palette.hint, palette)?; + write_change( + writer, + change, + "|", + palette.hint, + palette.hint, + palette, + expected_line_offset, + actual_line_offset, + )?; } } } @@ -160,6 +213,7 @@ fn write_diff_inner( } #[cfg(feature = "diff")] +#[allow(clippy::too_many_arguments)] fn write_change( writer: &mut dyn std::fmt::Write, change: similar::InlineChange, @@ -167,14 +221,24 @@ fn write_change( em_style: crate::report::Style, style: crate::report::Style, palette: crate::report::Palette, + expected_line_offset: usize, + actual_line_offset: usize, ) -> Result<(), std::fmt::Error> { if let Some(index) = change.old_index() { - write!(writer, "{:>4} ", palette.hint(index + 1),)?; + write!( + writer, + "{:>4} ", + palette.hint(index + 1 + expected_line_offset), + )?; } else { write!(writer, "{:>4} ", " ",)?; } if let Some(index) = change.new_index() { - write!(writer, "{:>4} ", palette.hint(index + 1),)?; + write!( + writer, + "{:>4} ", + palette.hint(index + 1 + actual_line_offset), + )?; } else { write!(writer, "{:>4} ", " ",)?; } @@ -211,6 +275,8 @@ mod test { Some(&expected_name), Some(&actual_name), palette, + 0, + 0, ) .unwrap(); let expected_diff = " @@ -240,6 +306,8 @@ mod test { Some(&expected_name), Some(&actual_name), palette, + 0, + 0, ) .unwrap(); let expected_diff = " @@ -269,6 +337,8 @@ mod test { Some(&expected_name), Some(&actual_name), palette, + 0, + 0, ) .unwrap(); let expected_diff = " @@ -299,6 +369,8 @@ mod test { Some(&expected_name), Some(&actual_name), palette, + 0, + 0, ) .unwrap(); let expected_diff = " @@ -353,6 +425,8 @@ mod test { Some(&expected_name), Some(&actual_name), palette, + 0, + 0, ) .unwrap(); let expected_diff = " @@ -390,4 +464,44 @@ mod test { assert_eq!(expected_diff, actual_diff); } + + #[cfg(feature = "diff")] + #[cfg(feature = "term-svg")] + #[test] + fn diff_ne_ignore_irrelevant_details() { + let expected = " + +Hello Moon + +"; + let expected_name = "A"; + let actual = " + +Hello World + +"; + let actual_name = "B"; + let palette = crate::report::Palette::plain(); + + let mut actual_diff = String::new(); + write_diff( + &mut actual_diff, + &crate::data::DataInner::TermSvg(expected.to_owned()).into(), + &crate::data::DataInner::TermSvg(actual.to_owned()).into(), + Some(&expected_name), + Some(&actual_name), + palette, + ) + .unwrap(); + let expected_diff = " +---- expected: A +++++ actual: B + 2 2 | + 3 - Hello Moon + 3 + Hello World + 4 4 | +"; + + assert_eq!(expected_diff, actual_diff); + } }