diff --git a/README.md b/README.md index 234dcaf..0bc640c 100644 --- a/README.md +++ b/README.md @@ -368,6 +368,7 @@ for types that implement `core::fmt::Debug`: |----------------------------|----------------------------------------------------------------------------| | has_debug_string | verify that a type formatted for debug is equal to the expected string | | does_not_have_debug_string | verify that a type formatted for debug is not equal to the expected string | +| debug_string | map the subject to its debug string representation | for types that implement `core::fmt::Display`: @@ -375,6 +376,7 @@ for types that implement `core::fmt::Display`: |------------------------------|------------------------------------------------------------------------------| | has_display_string | verify that a type formatted for display is equal to the expected string | | does_not_have_display_string | verify that a type formatted for display is not equal to the expected string | +| display_string | map the subject to its display string representation | ### Emptiness diff --git a/src/assertions.rs b/src/assertions.rs index cdfe63c..fa0de9b 100644 --- a/src/assertions.rs +++ b/src/assertions.rs @@ -2332,6 +2332,47 @@ pub trait AssertHasDebugString { fn does_not_have_debug_string(self, expected: E) -> Self; } +/// Mapping the subject into its debug string representation to do assertions on +/// its debug string. +/// +/// # Examples +/// +/// ``` +/// use asserting::prelude::*; +/// +/// #[derive(Debug)] +/// struct Foo { +/// hello: String, +/// } +/// +/// let subject = Foo { hello: "World".into() }; +/// +/// assert_that!(&subject).debug_string().contains("World"); +/// assert_that!(&subject).debug_string().does_not_start_with("Bar"); +/// ``` +pub trait AssertDebugString<'a, R> { + /// Maps the subject into its debug string representation to do assertions + /// on its debug string. + /// + /// # Examples + /// + /// ``` + /// use asserting::prelude::*; + /// + /// #[derive(Debug)] + /// struct Foo { + /// hello: String, + /// } + /// + /// let subject = Foo { hello: "World".into() }; + /// + /// assert_that!(&subject).debug_string().contains("World"); + /// assert_that!(&subject).debug_string().does_not_start_with("Bar"); + /// ``` + #[track_caller] + fn debug_string(self) -> Spec<'a, String, R>; +} + /// Assert a type formatted into a display string. /// /// The subject's type must implement `Display` and the expected type must @@ -2409,6 +2450,57 @@ pub trait AssertHasDisplayString { fn does_not_have_display_string(self, expected: E) -> Self; } +/// Mapping the subject into its display string representation to do assertions +/// on its display string. +/// +/// # Examples +/// +/// ``` +/// use core::fmt::{self, Display}; +/// use asserting::prelude::*; +/// +/// struct Foo { +/// hello: String, +/// } +/// +/// impl Display for Foo {fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +/// write!(f, "Hello, {}", self.hello) +/// } +/// } +/// +/// let subject = Foo { hello: "World".into() }; +/// +/// assert_that!(&subject).display_string().is_equal_to("Hello, World"); +/// assert_that!(&subject).display_string().does_not_end_with('!'); +/// ``` +pub trait AssertDisplayString<'a, R> { + /// Maps the subject into its display string representation to do assertions + /// on its display string. + /// + /// # Examples + /// + /// ``` + /// use core::fmt::{self, Display}; + /// use asserting::prelude::*; + /// + /// struct Foo { + /// hello: String, + /// } + /// + /// impl Display for Foo {fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + /// write!(f, "Hello, {}", self.hello) + /// } + /// } + /// + /// let subject = Foo { hello: "World".into() }; + /// + /// assert_that!(&subject).display_string().is_equal_to("Hello, World"); + /// assert_that!(&subject).display_string().does_not_end_with('!'); + /// ``` + #[track_caller] + fn display_string(self) -> Spec<'a, String, R>; +} + /// Assert that a string contains a substring or character. /// /// # Examples diff --git a/src/error/tests.rs b/src/error/tests.rs index c7670ce..8701454 100644 --- a/src/error/tests.rs +++ b/src/error/tests.rs @@ -359,3 +359,70 @@ mod colored { ); } } + +#[cfg(feature = "regex")] +mod with_regex { + use super::*; + + #[test] + fn error_has_debug_string_matching_regex() { + let error = SuperError { + source: SourceError::Bar, + }; + + assert_that(error).debug_string().matches(r"SuperError.*"); + } + + #[test] + fn verify_error_has_debug_string_matching_regex_fails() { + let error = SuperError { + source: SourceError::Bar, + }; + + let failures = verify_that(error) + .debug_string() + .matches(r"SuperError \{ (source|target): Foo \}") + .display_failures(); + + assert_eq!( + failures, + &[ + r"expected subject's debug string to match the regex SuperError \{ (source|target): Foo \} + but was: SuperError { source: Bar } + does not match regex: SuperError \{ (source|target): Foo \} +" + ] + ); + } + + #[test] + fn error_has_display_string_matching_regex() { + let error = SuperError { + source: SourceError::Bar, + }; + + assert_that(error).display_string().matches(r".*-error .*"); + } + + #[test] + fn verify_error_has_display_string_matching_regex_fails() { + let error = SuperError { + source: SourceError::Bar, + }; + + let failures = verify_that(error) + .display_string() + .matches(r".*-error-caused.*") + .display_failures(); + + assert_eq!( + failures, + &[ + r"expected subject's display string to match the regex .*-error-caused.* + but was: super-error caused by bar error + does not match regex: .*-error-caused.* +" + ] + ); + } +} diff --git a/src/lib.rs b/src/lib.rs index 99fe25c..08b3027 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -839,6 +839,7 @@ mod integer; mod iterator; mod length; mod map; +mod mapping; #[cfg(feature = "num-bigint")] mod num_bigint; mod number; diff --git a/src/mapping.rs b/src/mapping.rs new file mode 100644 index 0000000..b8283d1 --- /dev/null +++ b/src/mapping.rs @@ -0,0 +1,29 @@ +use crate::assertions::{AssertDebugString, AssertDisplayString}; +use crate::spec::{FailingStrategy, Spec}; +use crate::std::fmt::{Debug, Display}; +use crate::std::format; +use crate::std::string::{String, ToString}; + +impl<'a, S, R> AssertDebugString<'a, R> for Spec<'a, S, R> +where + S: Debug, + R: FailingStrategy, +{ + fn debug_string(self) -> Spec<'a, String, R> { + let expression_debug_string = format!("{}'s debug string", self.expression()); + self.mapping(|subject| format!("{subject:?}")) + .named(expression_debug_string) + } +} + +impl<'a, S, R> AssertDisplayString<'a, R> for Spec<'a, S, R> +where + S: Display, + R: FailingStrategy, +{ + fn display_string(self) -> Spec<'a, String, R> { + let expression_display_string = format!("{}'s display string", self.expression()); + self.mapping(|subject| subject.to_string()) + .named(expression_display_string) + } +}