Skip to main content

ferridriver_expect/
lib.rs

1#![allow(
2  clippy::missing_errors_doc,
3  clippy::missing_panics_doc,
4  clippy::must_use_candidate,
5  clippy::module_name_repetitions,
6  clippy::cast_possible_truncation,
7  clippy::cast_precision_loss,
8  clippy::cast_sign_loss,
9  clippy::cast_possible_wrap,
10  clippy::uninlined_format_args,
11  clippy::needless_pass_by_value,
12  clippy::doc_markdown,
13  clippy::match_same_arms,
14  clippy::should_implement_trait,
15  clippy::manual_let_else,
16  clippy::too_many_lines,
17  clippy::return_self_not_must_use,
18  // `to_*` builders that consume `self` are the conventional shape for
19  // poll-style terminal matchers (`.to_equal()` consumes the
20  // `ExpectPoll` to drop the closure).
21  clippy::wrong_self_convention,
22  clippy::redundant_closure_for_method_calls
23)]
24
25//! Value matchers (Jest-compatible) and asymmetric matchers for
26//! ferridriver's `expect()` API. Shared between the test runner
27//! (`ferridriver-test`) and the QuickJS scripting layer
28//! (`ferridriver-script`) so both surfaces dispatch through the same
29//! Rust core (per [the Rust-is-the-source-of-truth rule]).
30//!
31//! Web-first matchers (locator/page/apiResponse) still live in
32//! `ferridriver-test::expect` because they need the test runner's
33//! polling + screenshot context. This crate is intentionally tiny:
34//! `regex`, `serde`, `serde_json` deps only.
35//!
36//! [the Rust-is-the-source-of-truth rule]: https://github.com/salamaashoush/ferridriver/blob/main/CLAUDE.md
37
38pub mod api_response;
39pub mod asymmetric;
40pub mod builder;
41pub mod diff;
42pub mod locator;
43pub mod page;
44pub mod poll;
45pub mod throw;
46pub mod value;
47
48pub use asymmetric::{ASYM_TAG_KEY, Asymmetric, TypeTag, deep_equal, match_object};
49pub use builder::{
50  Expect, ExpectPoll, HaveCssOptions, InViewportOptions, ToPassOptions, expect, expect_configured, expect_poll,
51  to_pass, to_pass_with_options,
52};
53pub use diff::{json_diff, pretty_json, unified_diff};
54pub use poll::{DEFAULT_EXPECT_TIMEOUT, ExpectContext, MatchError, POLL_INTERVALS, poll_until};
55pub use throw::{ExpectFn, ThrowMatcher, ThrownError, expect_fn};
56pub use value::{ExpectValue, StringOrRegex, expect_value};
57
58/// Failure produced by a synchronous value matcher.
59///
60/// `message` is the formatted, JS-ready failure body (already includes
61/// the diff inline so the rquickjs error and a Rust panic carry the
62/// same text). `diff` is the same diff in isolation — handy for
63/// reporters that want to colorize / re-render it. `location` is the
64/// `#[track_caller]` site that invoked the matcher; the test-runner
65/// adapter splices it into the printed failure.
66#[derive(Debug, Clone)]
67pub struct AssertionFailure {
68  pub message: String,
69  pub diff: Option<String>,
70  pub location: Option<CallerLocation>,
71}
72
73/// Source location captured at the matcher call site. `'static` strings
74/// come straight from `std::panic::Location` — no allocations on the
75/// happy path.
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77pub struct CallerLocation {
78  pub file: &'static str,
79  pub line: u32,
80  pub column: u32,
81}
82
83impl CallerLocation {
84  #[must_use]
85  pub fn from_std(loc: &'static std::panic::Location<'static>) -> Self {
86    Self {
87      file: loc.file(),
88      line: loc.line(),
89      column: loc.column(),
90    }
91  }
92}
93
94impl std::fmt::Display for CallerLocation {
95  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96    write!(f, "{}:{}:{}", self.file, self.line, self.column)
97  }
98}
99
100impl AssertionFailure {
101  pub fn new(message: impl Into<String>, diff: Option<String>) -> Self {
102    Self {
103      message: message.into(),
104      diff,
105      location: None,
106    }
107  }
108
109  #[must_use]
110  pub fn with_location(mut self, loc: CallerLocation) -> Self {
111    self.location = Some(loc);
112    self
113  }
114}
115
116impl std::fmt::Display for AssertionFailure {
117  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
118    f.write_str(&self.message)
119  }
120}
121
122impl std::error::Error for AssertionFailure {}