From 839a0c5be37e12472267ef64b1108eacd564e652 Mon Sep 17 00:00:00 2001 From: haraldmaida Date: Sun, 20 Apr 2025 05:19:22 +0200 Subject: [PATCH 1/3] feat: implement additional order assertions `is_before`, `is_after` and `is_between` --- src/assertions.rs | 28 +++++++++++ src/expectations.rs | 16 ++++++ src/order/mod.rs | 82 ++++++++++++++++++++++++++++++- src/order/tests.rs | 117 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 242 insertions(+), 1 deletion(-) diff --git a/src/assertions.rs b/src/assertions.rs index 82a6605..fd4739e 100644 --- a/src/assertions.rs +++ b/src/assertions.rs @@ -122,6 +122,12 @@ pub trait AssertIsCloseToWithDefaultMargin { /// assert_that!(some_result).is_at_least(41); /// assert_that!(some_result).is_greater_than(41); /// assert_that!(some_result).is_less_than(43); +/// +/// let some_letter: char = 'M'; +/// +/// assert_that!(some_letter).is_before('P'); +/// assert_that!(some_letter).is_after('K'); +/// assert_that!(some_letter).is_between('A', 'Z'); ///``` pub trait AssertOrder { /// Verifies that the subject is less than some expected value. @@ -140,6 +146,28 @@ pub trait AssertOrder { /// value. #[track_caller] fn is_at_least(self, expected: E) -> Self; + + /// Verifies that the subject is before some expected value. + /// + /// This is equivalent to asserting a subject to be less than the expected + /// value. + #[track_caller] + fn is_before(self, expected: E) -> Self; + + /// Verifies that the subject is after some expected value. + /// + /// This is equivalent to asserting a subject to be greater than the + /// expected value. + #[track_caller] + fn is_after(self, expected: E) -> Self; + + /// Verifies that the subject is between a min value and a max value. + /// + /// Min and max values are included. This is equivalent to asserting a + /// subject to be greater than or equal to the min value and to be less than + /// or equal to the max value. + #[track_caller] + fn is_between(self, min: E, max: E) -> Self; } /// Assert whether a value is within an expected range. diff --git a/src/expectations.rs b/src/expectations.rs index cda726a..402c5d4 100644 --- a/src/expectations.rs +++ b/src/expectations.rs @@ -93,6 +93,22 @@ pub struct IsAtLeast { pub expected: E, } +#[must_use] +pub struct IsBefore { + pub expected: E, +} + +#[must_use] +pub struct IsAfter { + pub expected: E, +} + +#[must_use] +pub struct IsBetween { + pub min: E, + pub max: E, +} + #[must_use] pub struct IsInRange { pub expected_range: RangeInclusive, diff --git a/src/order/mod.rs b/src/order/mod.rs index 9f444ce..70256bd 100644 --- a/src/order/mod.rs +++ b/src/order/mod.rs @@ -2,7 +2,9 @@ use crate::assertions::AssertOrder; use crate::colored::{mark_missing, mark_unexpected}; -use crate::expectations::{IsAtLeast, IsAtMost, IsGreaterThan, IsLessThan}; +use crate::expectations::{ + IsAfter, IsAtLeast, IsAtMost, IsBefore, IsBetween, IsGreaterThan, IsLessThan, +}; use crate::spec::{DiffFormat, Expectation, Expression, FailingStrategy, Spec}; use crate::std::fmt::Debug; use crate::std::{format, string::String}; @@ -28,6 +30,18 @@ where fn is_at_least(self, expected: E) -> Self { self.expecting(IsAtLeast { expected }) } + + fn is_before(self, expected: E) -> Self { + self.expecting(IsBefore { expected }) + } + + fn is_after(self, expected: E) -> Self { + self.expecting(IsAfter { expected }) + } + + fn is_between(self, min: E, max: E) -> Self { + self.expecting(IsBetween { min, max }) + } } impl Expectation for IsLessThan @@ -106,5 +120,71 @@ where } } +impl Expectation for IsBefore +where + S: PartialOrd + Debug, + E: Debug, +{ + fn test(&mut self, subject: &S) -> bool { + subject < &self.expected + } + + fn message(&self, expression: Expression<'_>, actual: &S, format: &DiffFormat) -> String { + let marked_actual = mark_unexpected(actual, format); + let marked_expected = mark_missing(&self.expected, format); + format!( + "expected {expression} is before {:?}\n but was: {marked_actual}\n expected: < {marked_expected}", + self.expected, + ) + } +} + +impl Expectation for IsAfter +where + S: PartialOrd + Debug, + E: Debug, +{ + fn test(&mut self, subject: &S) -> bool { + subject > &self.expected + } + + fn message(&self, expression: Expression<'_>, actual: &S, format: &DiffFormat) -> String { + let marked_actual = mark_unexpected(actual, format); + let marked_expected = mark_missing(&self.expected, format); + format!( + "expected {expression} is after {:?}\n but was: {marked_actual}\n expected: > {marked_expected}", + self.expected, + ) + } +} + +impl Expectation for IsBetween +where + S: PartialOrd + Debug, + E: Debug, +{ + fn test(&mut self, subject: &S) -> bool { + subject >= &self.min && subject <= &self.max + } + + fn message(&self, expression: Expression<'_>, actual: &S, format: &DiffFormat) -> String { + let marked_actual = mark_unexpected(actual, format); + let marked_start = if actual < &self.min { + mark_missing(&self.min, format) + } else { + format!("{:?}", &self.min) + }; + let marked_end = if actual > &self.max { + mark_missing(&self.max, format) + } else { + format!("{:?}", &self.max) + }; + format!( + "expected {expression} is between {:?} and {:?}\n but was: {marked_actual}\n expected: {marked_start} <= x <= {marked_end}", + self.min, self.max + ) + } +} + #[cfg(test)] mod tests; diff --git a/src/order/tests.rs b/src/order/tests.rs index c782995..305a9e9 100644 --- a/src/order/tests.rs +++ b/src/order/tests.rs @@ -208,6 +208,83 @@ fn verify_char_is_at_least_other_char_fails() { ); } +#[test] +fn char_is_before_other_char() { + let subject = 'L'; + + assert_that(subject).is_before('M'); +} + +#[test] +fn verify_char_is_before_other_char_fails() { + let subject = 'L'; + + let failures = verify_that(subject) + .named("my_thing") + .is_before('L') + .display_failures(); + + assert_eq!( + failures, + &[r"assertion failed: expected my_thing is before 'L' + but was: 'L' + expected: < 'L' +"] + ); +} + +#[test] +fn char_is_after_other_char() { + let subject = 'L'; + + assert_that(subject).is_after('K'); +} + +#[test] +fn verify_char_is_after_other_char_fails() { + let subject = 'L'; + + let failures = verify_that(subject) + .named("my_thing") + .is_after('L') + .display_failures(); + + assert_eq!( + failures, + &[r"assertion failed: expected my_thing is after 'L' + but was: 'L' + expected: > 'L' +"] + ); +} + +#[test] +fn char_is_between_a_min_char_and_a_max_char() { + let subject = 'L'; + + assert_that(subject).is_between('K', 'M'); + assert_that(subject).is_between('L', 'P'); + assert_that(subject).is_between('H', 'L'); +} + +#[test] +fn verify_char_is_between_a_min_char_and_a_max_char_fails() { + let subject = 'L'; + + let failures = verify_that(subject) + .named("my_thing") + .is_between('M', 'P') + .display_failures(); + + assert_eq!( + failures, + &[r"assertion failed: expected my_thing is between 'M' and 'P' + but was: 'L' + expected: 'M' <= x <= 'P' +"] + ); +} + #[cfg(feature = "colored")] mod colored { use crate::prelude::*; @@ -285,4 +362,44 @@ mod colored { "] ); } + + #[test] + fn highlight_diffs_is_between_but_is_below_min() { + let subject = 'L'; + + let failures = verify_that(subject) + .with_diff_format(DIFF_FORMAT_RED_YELLOW) + .is_between('M', 'P') + .display_failures(); + + assert_eq!( + failures, + &[ + "assertion failed: expected subject is between 'M' and 'P'\n \ + but was: \u{1b}[31m'L'\u{1b}[0m\n \ + expected: \u{1b}[33m'M'\u{1b}[0m <= x <= 'P'\n\ + " + ] + ); + } + + #[test] + fn highlight_diffs_is_between_but_is_above_max() { + let subject = 'L'; + + let failures = verify_that(subject) + .with_diff_format(DIFF_FORMAT_RED_YELLOW) + .is_between('H', 'K') + .display_failures(); + + assert_eq!( + failures, + &[ + "assertion failed: expected subject is between 'H' and 'K'\n \ + but was: \u{1b}[31m'L'\u{1b}[0m\n \ + expected: 'H' <= x <= \u{1b}[33m'K'\u{1b}[0m\n\ + " + ] + ); + } } From 771a207a16c97e3402ad4895c66220df15a29336 Mon Sep 17 00:00:00 2001 From: haraldmaida Date: Sun, 20 Apr 2025 05:29:53 +0200 Subject: [PATCH 2/3] doc: add examples for `is_before`, `is_after` and `is_between` assertions for `time::Date` values --- Cargo.toml | 1 + examples/fixture/mod.rs | 1 + src/assertions.rs | 7 +++++++ src/lib.rs | 1 + tests/version_numbers.rs | 1 + 5 files changed, 11 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 9628385..8ba234f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ sdiff = { version = "0.1", optional = true } anyhow = "1" proptest = "1" serial_test = "3" +time = { version = "0.3", default-features = false, features = ["macros"] } version-sync = "0.9" [lints.rust] diff --git a/examples/fixture/mod.rs b/examples/fixture/mod.rs index 006b279..7b0e4d9 100644 --- a/examples/fixture/mod.rs +++ b/examples/fixture/mod.rs @@ -9,5 +9,6 @@ mod dummy_extern_uses { #[cfg(feature = "colored")] use sdiff as _; use serial_test as _; + use time as _; use version_sync as _; } diff --git a/src/assertions.rs b/src/assertions.rs index fd4739e..b15cdd7 100644 --- a/src/assertions.rs +++ b/src/assertions.rs @@ -112,6 +112,7 @@ pub trait AssertIsCloseToWithDefaultMargin { /// # Examples /// /// ``` +/// use time::macros::date; /// use asserting::prelude::*; /// /// let some_result: u16 = 42; @@ -128,6 +129,12 @@ pub trait AssertIsCloseToWithDefaultMargin { /// assert_that!(some_letter).is_before('P'); /// assert_that!(some_letter).is_after('K'); /// assert_that!(some_letter).is_between('A', 'Z'); +/// +/// let some_date = date!(2025-04-20); +/// +/// assert_that!(some_date).is_before(date!(2025-04-21)); +/// assert_that!(some_date).is_after(date!(2025-04-19)); +/// assert_that!(some_date).is_between(date!(2025-04-19), date!(2025-04-21)); ///``` pub trait AssertOrder { /// Verifies that the subject is less than some expected value. diff --git a/src/lib.rs b/src/lib.rs index af2b171..8711f84 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -653,5 +653,6 @@ type TestCodeSnippetsInReadme = (); mod dummy_extern_uses { use proptest as _; use serial_test as _; + use time as _; use version_sync as _; } diff --git a/tests/version_numbers.rs b/tests/version_numbers.rs index d387091..787572b 100644 --- a/tests/version_numbers.rs +++ b/tests/version_numbers.rs @@ -13,6 +13,7 @@ mod dummy_extern_uses { #[cfg(feature = "colored")] use sdiff as _; use serial_test as _; + use time as _; } #[test] From dd3665eba6f3ef9632149cda229354120de0b8e9 Mon Sep 17 00:00:00 2001 From: haraldmaida Date: Sun, 20 Apr 2025 05:33:18 +0200 Subject: [PATCH 3/3] doc: list added assertions `is_before`, `is_after` and `is_between` under available assertions in README --- README.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 4655e46..f3a0951 100644 --- a/README.md +++ b/README.md @@ -130,12 +130,15 @@ for all types that implement `PartialEq` with `E` being the type of the expec for all types that implement `PartialOrd` with `E` being the type of the expected value. -| assertion | description | -|-----------------|------------------------------------------------------------------------| -| is_greater_than | verify that the subject is greater than the expected value | -| is_less_than | verify that the subject is less than the expected value | -| is_at_least | verify that the subject is greater than or equal to the expected value | -| is_at_most | verify that the subject is less than or equal to the expected value | +| assertion | description | +|-----------------|----------------------------------------------------------------------------------------| +| is_greater_than | verify that the subject is greater than the expected value | +| is_less_than | verify that the subject is less than the expected value | +| is_at_least | verify that the subject is greater than or equal to the expected value | +| is_at_most | verify that the subject is less than or equal to the expected value | +| is_before | verify that the subject is less than (before) the expected value | +| is_after | verify that the subject is greater than (after) the expected value | +| is_between | verify that the subject is between a min value (inclusive) and a max value (inclusive) | ### Range