diff --git a/Cargo.toml b/Cargo.toml index 9aa339d..c97b8c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,3 +21,6 @@ auto-color = ["concolor", "concolor/auto"] yansi = "0.5" unicode-width = "0.1.9" concolor = { version = "0.1", optional = true } + +[dev-dependencies] +insta = "1.31.0" diff --git a/src/write.rs b/src/write.rs index 1ddf4d0..db82e78 100644 --- a/src/write.rs +++ b/src/write.rs @@ -794,3 +794,286 @@ impl Label { self.span.end().saturating_sub(1).max(self.span.start()) } } + +#[cfg(test)] +mod tests { + //! These tests use [insta](https://insta.rs/). If you do `cargo install cargo-insta` you can + //! automatically update the snapshots with `cargo insta review` or `cargo insta accept`. + //! + //! When adding new tests you can leave the string in the `assert_snapshot!` macro call empty: + //! + //! assert_snapshot!(msg, @""); + //! + //! and insta will fill it in. + + use std::ops::Range; + + use insta::assert_snapshot; + + use crate::{Cache, CharSet, Config, Label, Report, ReportKind, Source, Span}; + + impl Report<'_, S> { + fn write_to_string>(&self, cache: C) -> String { + let mut vec = Vec::new(); + self.write(cache, &mut vec).unwrap(); + String::from_utf8(vec).unwrap() + } + } + + fn no_color_and_ascii() -> Config { + Config::default() + .with_color(false) + // Using Ascii so that the inline snapshots display correctly + // even with fonts where characters like '┬' take up more space. + .with_char_set(CharSet::Ascii) + } + + #[test] + fn one_message() { + let msg = Report::>::build(ReportKind::Error, (), 0) + .with_config(no_color_and_ascii()) + .with_message("can't compare apples with oranges") + .finish() + .write_to_string(Source::from("")); + assert_snapshot!(msg, @r###" + Error: can't compare apples with oranges + "###) + } + + #[test] + fn two_labels_without_messages() { + let source = "apple == orange;"; + let msg = Report::>::build(ReportKind::Error, (), 0) + .with_config(no_color_and_ascii()) + .with_message("can't compare apples with oranges") + .with_label(Label::new(0..5)) + .with_label(Label::new(9..15)) + .finish() + .write_to_string(Source::from(source)); + // TODO: it would be nice if these spans still showed up (like codespan-reporting does) + assert_snapshot!(msg, @r###" + Error: can't compare apples with oranges + ,-[:1:1] + | + 1 | apple == orange; + ---' + "###); + } + + #[test] + fn two_labels_with_messages() { + let source = "apple == orange;"; + let msg = Report::>::build(ReportKind::Error, (), 0) + .with_config(no_color_and_ascii()) + .with_message("can't compare apples with oranges") + .with_label(Label::new(0..5).with_message("This is an apple")) + .with_label(Label::new(9..15).with_message("This is an orange")) + .finish() + .write_to_string(Source::from(source)); + // TODO: it would be nice if these lines didn't cross + assert_snapshot!(msg, @r###" + Error: can't compare apples with oranges + ,-[:1:1] + | + 1 | apple == orange; + | ^^|^^ ^^^|^^ + | `-------------- This is an apple + | | + | `---- This is an orange + ---' + "###); + } + + #[test] + #[should_panic] + fn backwards_label_should_panic() { + let _ = Report::>::build(ReportKind::Error, (), 0) + .with_label(Label::new(5..0)) + .finish() + .write_to_string(Source::from("")); + } + + #[test] + fn label_at_end_of_long_line() { + let source = format!("{}orange", "apple == ".repeat(100)); + let msg = Report::>::build(ReportKind::Error, (), 0) + .with_config(no_color_and_ascii()) + .with_message("can't compare apples with oranges") + .with_label( + Label::new(source.len() - 5..source.len()).with_message("This is an orange"), + ) + .finish() + .write_to_string(Source::from(source)); + // TODO: it would be nice if the start of long lines would be omitted (like rustc does) + assert_snapshot!(msg, @r###" + Error: can't compare apples with oranges + ,-[:1:1] + | + 1 | apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == apple == orange + | ^^|^^ + | `---- This is an orange + ---' + "###); + } + + #[test] + fn multiline_label() { + let source = "apple\n==\norange"; + let msg = Report::>::build(ReportKind::Error, (), 0) + .with_config(no_color_and_ascii()) + .with_label(Label::new(0..source.len()).with_message("illegal comparison")) + .finish() + .write_to_string(Source::from(source)); + // TODO: it would be nice if the 2nd line wasn't omitted + assert_snapshot!(msg, @r###" + Error: + ,-[:1:1] + | + 1 | ,-> apple + : : + 3 | |-> orange + | | + | `----------- illegal comparison + ---' + "###); + } + + #[test] + fn partially_overlapping_labels() { + let source = "https://example.com/"; + let msg = Report::>::build(ReportKind::Error, (), 0) + .with_config(no_color_and_ascii()) + .with_label(Label::new(0..source.len()).with_message("URL")) + .with_label(Label::new(0..source.find(':').unwrap()).with_message("scheme")) + .finish() + .write_to_string(Source::from(source)); + // TODO: it would be nice if you could tell where the spans start and end. + assert_snapshot!(msg, @r###" + Error: + ,-[:1:1] + | + 1 | https://example.com/ + | ^^|^^^^^^^|^^^^^^^^^ + | `------------------- scheme + | | + | `----------- URL + ---' + "###); + } + + #[test] + fn multiple_labels_same_span() { + let source = "apple == orange;"; + let msg = Report::>::build(ReportKind::Error, (), 0) + .with_config(no_color_and_ascii()) + .with_message("can't compare apples with oranges") + .with_label(Label::new(0..5).with_message("This is an apple")) + .with_label(Label::new(0..5).with_message("Have I mentioned that this is an apple?")) + .with_label(Label::new(0..5).with_message("No really, have I mentioned that?")) + .with_label(Label::new(9..15).with_message("This is an orange")) + .with_label(Label::new(9..15).with_message("Have I mentioned that this is an orange?")) + .with_label(Label::new(9..15).with_message("No really, have I mentioned that?")) + .finish() + .write_to_string(Source::from(source)); + assert_snapshot!(msg, @r###" + Error: can't compare apples with oranges + ,-[:1:1] + | + 1 | apple == orange; + | ^^|^^ ^^^|^^ + | `-------------- This is an apple + | | | + | `-------------- Have I mentioned that this is an apple? + | | | + | `-------------- No really, have I mentioned that? + | | + | `---- This is an orange + | | + | `---- Have I mentioned that this is an orange? + | | + | `---- No really, have I mentioned that? + ---' + "###) + } + + #[test] + fn note() { + let source = "apple == orange;"; + let msg = Report::>::build(ReportKind::Error, (), 0) + .with_config(no_color_and_ascii()) + .with_message("can't compare apples with oranges") + .with_label(Label::new(0..5).with_message("This is an apple")) + .with_label(Label::new(9..15).with_message("This is an orange")) + .with_note("stop trying ... this is a fruitless endeavor") + .finish() + .write_to_string(Source::from(source)); + assert_snapshot!(msg, @r###" + Error: can't compare apples with oranges + ,-[:1:1] + | + 1 | apple == orange; + | ^^|^^ ^^^|^^ + | `-------------- This is an apple + | | + | `---- This is an orange + | + | Note: stop trying ... this is a fruitless endeavor + ---' + "###) + } + + #[test] + fn help() { + let source = "apple == orange;"; + let msg = Report::>::build(ReportKind::Error, (), 0) + .with_config(no_color_and_ascii()) + .with_message("can't compare apples with oranges") + .with_label(Label::new(0..5).with_message("This is an apple")) + .with_label(Label::new(9..15).with_message("This is an orange")) + .with_help("have you tried peeling the orange?") + .finish() + .write_to_string(Source::from(source)); + assert_snapshot!(msg, @r###" + Error: can't compare apples with oranges + ,-[:1:1] + | + 1 | apple == orange; + | ^^|^^ ^^^|^^ + | `-------------- This is an apple + | | + | `---- This is an orange + | + | Help: have you tried peeling the orange? + ---' + "###) + } + + #[test] + fn help_and_note() { + let source = "apple == orange;"; + let msg = Report::>::build(ReportKind::Error, (), 0) + .with_config(no_color_and_ascii()) + .with_message("can't compare apples with oranges") + .with_label(Label::new(0..5).with_message("This is an apple")) + .with_label(Label::new(9..15).with_message("This is an orange")) + .with_help("have you tried peeling the orange?") + .with_note("stop trying ... this is a fruitless endeavor") + .finish() + .write_to_string(Source::from(source)); + assert_snapshot!(msg, @r###" + Error: can't compare apples with oranges + ,-[:1:1] + | + 1 | apple == orange; + | ^^|^^ ^^^|^^ + | `-------------- This is an apple + | | + | `---- This is an orange + | + | Help: have you tried peeling the orange? + | + | Note: stop trying ... this is a fruitless endeavor + ---' + "###) + } +}