Skip to main content

assertr/assertions/core/
option.rs

1use crate::{AssertThat, Mode, actual::Actual, mode::Panic, tracking::AssertionTracking};
2use alloc::string::String;
3use core::fmt::{Debug, Write};
4use core::option::Option;
5use indoc::writedoc;
6
7/// Data-extracting assertion for `Option` values.
8#[cfg_attr(feature = "fluent", assertr_derive::fluent_aliases)]
9pub trait OptionExtractAssertions<'t, T> {
10    /// Test if this option is of the `Some` variant.
11    /// This is a terminal operation on the contained `Option`,
12    /// as there is nothing meaningful to do with the option if its variant was ensured.
13    /// This allows you to chain additional expectations on the contained success value.
14    ///
15    /// Only available in `Panic` mode, as the extracted `T` cannot be produced when the value is
16    /// `None`. Use `OptionAssertions::is_some_satisfying` for capture mode.
17    fn is_some(self) -> AssertThat<'t, T, Panic>
18    where
19        T: Debug;
20}
21
22impl<'t, T> OptionExtractAssertions<'t, T> for AssertThat<'t, Option<T>, Panic> {
23    #[track_caller]
24    fn is_some(self) -> AssertThat<'t, T, Panic>
25    where
26        T: Debug,
27    {
28        self.track_assertion();
29
30        if !self.actual().is_some() {
31            let actual = self.actual();
32            self.fail(|w: &mut String| {
33                writedoc! {w, r"
34                    Actual: {actual:#?}
35
36                    is not of expected variant: Option::Some
37                "}
38            });
39        }
40
41        self.map(|actual| match actual {
42            Actual::Owned(o) => Actual::Owned(o.unwrap()),
43            Actual::Borrowed(b) => Actual::Borrowed(b.as_ref().unwrap()),
44        })
45    }
46}
47
48/// Non-extracting assertions for `Option` values.
49/// These work in any mode (Panic or Capture).
50#[allow(clippy::return_self_not_must_use)]
51#[cfg_attr(feature = "fluent", assertr_derive::fluent_aliases)]
52pub trait OptionAssertions<'t, T, M: Mode> {
53    /// Test if this option is of the `Some` variant, then run additional assertions on the contained value.
54    fn is_some_satisfying<A>(self, assertions: A) -> Self
55    where
56        T: Debug,
57        A: for<'a> FnOnce(AssertThat<'a, &'a T, M>);
58
59    /// Test if this option is of the `None` variant.
60    /// This is a terminal operation on the contained `Option`,
61    /// as there is nothing meaningful to do with the option after its variant was ensured.
62    fn is_none(self) -> AssertThat<'t, (), M>
63    where
64        T: Debug;
65}
66
67impl<'t, T, M: Mode> OptionAssertions<'t, T, M> for AssertThat<'t, Option<T>, M> {
68    #[track_caller]
69    fn is_some_satisfying<A>(self, assertions: A) -> Self
70    where
71        T: Debug,
72        A: for<'a> FnOnce(AssertThat<'a, &'a T, M>),
73    {
74        self.track_assertion();
75
76        if self.actual().is_some() {
77            self.satisfies_ref(|it| it.as_ref().unwrap(), assertions)
78        } else {
79            let actual = self.actual();
80            self.fail(|w: &mut String| {
81                writedoc! {w, r"
82                    Actual: {actual:#?}
83
84                    is not of expected variant: Option::Some
85                "}
86            });
87            self
88        }
89    }
90
91    #[track_caller]
92    fn is_none(self) -> AssertThat<'t, (), M>
93    where
94        T: Debug,
95    {
96        self.track_assertion();
97
98        if !self.actual().is_none() {
99            let actual = self.actual();
100            self.fail(|w: &mut String| {
101                writedoc! {w, r"
102                    Actual: {actual:#?}
103
104                    is not of expected variant: Option::None
105                "}
106            });
107        }
108
109        self.map(|_actual| Actual::Owned(()))
110    }
111}
112
113#[cfg(test)]
114mod tests {
115    mod is_some {
116        use crate::prelude::*;
117        use indoc::formatdoc;
118
119        #[test]
120        fn succeeds_when_some() {
121            assert_that!(Option::<i32>::Some(42))
122                .is_some()
123                .is_equal_to(42);
124        }
125
126        #[test]
127        fn panics_when_none() {
128            assert_that_panic_by(|| {
129                assert_that!(Option::<i32>::None)
130                    .with_location(false)
131                    .is_some()
132            })
133            .has_type::<String>()
134            .is_equal_to(formatdoc! {"
135                -------- assertr --------
136                Actual: None
137
138                is not of expected variant: Option::Some
139                -------- assertr --------
140            "});
141        }
142    }
143
144    mod is_some_satisfying {
145        use crate::prelude::*;
146        use indoc::formatdoc;
147
148        #[test]
149        fn succeeds_when_some_and_assertions_pass() {
150            assert_that!(Option::<i32>::Some(42)).is_some_satisfying(|some| {
151                some.is_equal_to(&42);
152            });
153        }
154
155        #[test]
156        fn captures_inner_failure_when_some_and_assertion_fails() {
157            let failures = assert_that!(Option::<i32>::Some(42))
158                .with_capture()
159                .with_location(false)
160                .is_some_satisfying(|some| {
161                    some.is_greater_than(&9000);
162                })
163                .capture_failures();
164
165            assert_that!(failures).contains_exactly::<String>([formatdoc! {"
166                -------- assertr --------
167                Actual: 42
168
169                is not greater than
170
171                Expected: 9000
172                -------- assertr --------
173            "}]);
174        }
175
176        #[test]
177        fn captures_variant_failure_when_none() {
178            let failures = assert_that!(Option::<i32>::None)
179                .with_capture()
180                .with_location(false)
181                .is_some_satisfying(|_| panic!("assertions should not run"))
182                .capture_failures();
183
184            assert_that!(failures).contains_exactly::<String>([formatdoc! {"
185                -------- assertr --------
186                Actual: None
187
188                is not of expected variant: Option::Some
189                -------- assertr --------
190            "}]);
191        }
192
193        #[test]
194        fn panics_when_none() {
195            assert_that_panic_by(|| {
196                assert_that!(Option::<i32>::None)
197                    .with_location(false)
198                    .is_some_satisfying(|_| panic!("assertions should not run"))
199            })
200            .has_type::<String>()
201            .is_equal_to(formatdoc! {"
202                -------- assertr --------
203                Actual: None
204
205                is not of expected variant: Option::Some
206                -------- assertr --------
207            "});
208        }
209    }
210
211    mod is_none {
212        use crate::prelude::*;
213        use alloc::string::String;
214        use indoc::formatdoc;
215
216        #[test]
217        fn succeeds_when_none() {
218            assert_that!(Option::<i32>::None).is_none();
219        }
220
221        #[test]
222        fn panics_when_some() {
223            assert_that_panic_by(|| {
224                assert_that!(Option::<i32>::Some(42))
225                    .with_location(false)
226                    .is_none()
227            })
228            .has_type::<String>()
229            .is_equal_to(formatdoc! {"
230                -------- assertr --------
231                Actual: Some(
232                    42,
233                )
234
235                is not of expected variant: Option::None
236                -------- assertr --------
237            "});
238        }
239    }
240}