ferridriver_expect/
builder.rs1use std::future::Future;
7use std::time::Duration;
8
9use crate::AssertionFailure;
10use crate::poll::{DEFAULT_EXPECT_TIMEOUT, POLL_INTERVALS};
11
12#[derive(Debug, Clone, Default)]
14pub struct InViewportOptions {
15 pub ratio: Option<f64>,
18}
19
20#[derive(Debug, Clone, Default)]
22pub struct HaveCssOptions {
23 pub pseudo: Option<String>,
25}
26
27#[must_use]
29pub fn expect<T>(subject: &T) -> Expect<'_, T> {
30 Expect {
31 subject,
32 timeout: DEFAULT_EXPECT_TIMEOUT,
33 is_not: false,
34 is_soft: false,
35 message: None,
36 }
37}
38
39#[must_use]
42pub fn expect_configured<T>(subject: &T, timeout: Duration) -> Expect<'_, T> {
43 Expect {
44 subject,
45 timeout,
46 is_not: false,
47 is_soft: false,
48 message: None,
49 }
50}
51
52pub struct Expect<'a, T> {
54 pub subject: &'a T,
55 pub timeout: Duration,
56 pub is_not: bool,
57 pub is_soft: bool,
58 pub message: Option<String>,
59}
60
61impl<T> Expect<'_, T> {
62 #[must_use]
64 pub fn not(mut self) -> Self {
65 self.is_not = !self.is_not;
66 self
67 }
68
69 #[must_use]
71 pub fn with_timeout(mut self, timeout: Duration) -> Self {
72 self.timeout = timeout;
73 self
74 }
75
76 #[must_use]
78 pub fn with_message(mut self, msg: impl Into<String>) -> Self {
79 self.message = Some(msg.into());
80 self
81 }
82
83 #[must_use]
86 pub fn soft(mut self) -> Self {
87 self.is_soft = true;
88 self
89 }
90}
91
92pub struct ExpectPoll<F> {
97 generator: F,
98 timeout: Duration,
99 intervals: Vec<u64>,
100}
101
102pub fn expect_poll<F, Fut, T>(generator: F, timeout: Duration) -> ExpectPoll<F>
104where
105 F: Fn() -> Fut,
106 Fut: Future<Output = T>,
107{
108 ExpectPoll {
109 generator,
110 timeout,
111 intervals: POLL_INTERVALS.to_vec(),
112 }
113}
114
115impl<F, Fut, T> ExpectPoll<F>
116where
117 F: Fn() -> Fut,
118 Fut: Future<Output = T>,
119 T: PartialEq + std::fmt::Debug,
120{
121 #[must_use]
122 pub fn with_intervals(mut self, intervals: Vec<u64>) -> Self {
123 self.intervals = intervals;
124 self
125 }
126
127 pub async fn to_equal(self, expected: T) -> Result<(), AssertionFailure> {
129 let deadline = tokio::time::Instant::now() + self.timeout;
130 let mut interval_idx = 0;
131 loop {
132 let actual = (self.generator)().await;
133 if actual == expected {
134 return Ok(());
135 }
136 let interval_ms = self
137 .intervals
138 .get(interval_idx)
139 .copied()
140 .unwrap_or_else(|| self.intervals.last().copied().unwrap_or(1000));
141 interval_idx += 1;
142 let sleep_dur = Duration::from_millis(interval_ms);
143 if tokio::time::Instant::now() + sleep_dur > deadline {
144 return Err(AssertionFailure::new(
145 "expect.poll().to_equal() timed out".to_string(),
146 Some(format!("Expected: {expected:?}\nReceived: {actual:?}")),
147 ));
148 }
149 tokio::time::sleep(sleep_dur).await;
150 }
151 }
152
153 pub async fn to_satisfy(self, predicate: impl Fn(&T) -> bool, description: &str) -> Result<(), AssertionFailure> {
155 let deadline = tokio::time::Instant::now() + self.timeout;
156 let mut interval_idx = 0;
157 loop {
158 let actual = (self.generator)().await;
159 if predicate(&actual) {
160 return Ok(());
161 }
162 let interval_ms = self
163 .intervals
164 .get(interval_idx)
165 .copied()
166 .unwrap_or_else(|| self.intervals.last().copied().unwrap_or(1000));
167 interval_idx += 1;
168 let sleep_dur = Duration::from_millis(interval_ms);
169 if tokio::time::Instant::now() + sleep_dur > deadline {
170 return Err(AssertionFailure::new(
171 "expect.poll().to_satisfy() timed out".to_string(),
172 Some(format!("Expected: {description}\nReceived: {actual:?}")),
173 ));
174 }
175 tokio::time::sleep(sleep_dur).await;
176 }
177 }
178}
179
180pub struct ToPassOptions {
183 pub timeout: Duration,
184 pub intervals: Vec<u64>,
185 pub message: Option<String>,
186}
187
188impl Default for ToPassOptions {
189 fn default() -> Self {
190 Self {
191 timeout: DEFAULT_EXPECT_TIMEOUT,
192 intervals: POLL_INTERVALS.to_vec(),
193 message: None,
194 }
195 }
196}
197
198pub async fn to_pass<F, Fut>(timeout: Duration, body: F) -> Result<(), AssertionFailure>
201where
202 F: Fn() -> Fut,
203 Fut: Future<Output = Result<(), AssertionFailure>>,
204{
205 to_pass_with_options(
206 body,
207 ToPassOptions {
208 timeout,
209 ..Default::default()
210 },
211 )
212 .await
213}
214
215pub async fn to_pass_with_options<F, Fut>(body: F, options: ToPassOptions) -> Result<(), AssertionFailure>
216where
217 F: Fn() -> Fut,
218 Fut: Future<Output = Result<(), AssertionFailure>>,
219{
220 let deadline = tokio::time::Instant::now() + options.timeout;
221 let mut interval_idx = 0;
222 let mut attempts = 0u32;
223
224 let final_err: AssertionFailure = loop {
227 attempts += 1;
228 match body().await {
229 Ok(()) => return Ok(()),
230 Err(e) => {
231 let interval_ms = options
232 .intervals
233 .get(interval_idx)
234 .copied()
235 .unwrap_or_else(|| options.intervals.last().copied().unwrap_or(1000));
236 interval_idx += 1;
237 let sleep_dur = Duration::from_millis(interval_ms);
238 if tokio::time::Instant::now() + sleep_dur > deadline {
239 break e;
240 }
241 tokio::time::sleep(sleep_dur).await;
242 },
243 }
244 };
245
246 let mut err = final_err;
247 let prefix = options.message.as_deref().unwrap_or("toPass()");
248 err.message = format!(
249 "{prefix} failed after {attempts} attempt(s) ({:?}): {}",
250 options.timeout, err.message
251 );
252 Err(err)
253}