testing_library_dom/queries/
text.rs

1use web_sys::HtmlElement;
2
3use crate::{
4    build_queries,
5    config::get_config,
6    error::QueryError,
7    get_node_text::get_node_text,
8    matches::{fuzzy_matches, make_normalizer, matches},
9    types::{Ignore, Matcher, NormalizerOptions, SelectorMatcherOptions},
10    util::node_list_to_vec,
11};
12
13pub fn _query_all_by_text<M: Into<Matcher>>(
14    container: &HtmlElement,
15    text: M,
16    options: SelectorMatcherOptions,
17) -> Result<Vec<HtmlElement>, QueryError> {
18    let text = text.into();
19    let selector = options.selector.unwrap_or("*".into());
20    let ignore = options.ignore.unwrap_or(get_config().default_ignore.into());
21    let matcher = match options.exact.unwrap_or(true) {
22        true => matches,
23        false => fuzzy_matches,
24    };
25    let match_normalizer = make_normalizer(NormalizerOptions {
26        trim: options.trim,
27        collapse_whitespace: options.collapse_whitespace,
28        normalizer: options.normalizer,
29    })?;
30
31    let mut base_array = vec![];
32    if container.matches(&selector).map_err(QueryError::JsError)? {
33        base_array.push(container.clone());
34    }
35
36    Ok(base_array
37        .into_iter()
38        .chain(node_list_to_vec::<HtmlElement>(
39            container
40                .query_selector_all(&selector)
41                .map_err(QueryError::JsError)?,
42        ))
43        .filter(|node| match &ignore {
44            Ignore::False => true,
45            Ignore::String(ignore) => !node.matches(ignore).unwrap_or(false),
46        })
47        .filter(|node| {
48            matcher(
49                Some(get_node_text(node)),
50                Some(node),
51                &text,
52                match_normalizer.as_ref(),
53            )
54        })
55        .collect())
56}
57
58fn get_multiple_error(
59    _container: &HtmlElement,
60    text: Matcher,
61    _options: SelectorMatcherOptions,
62) -> Result<String, QueryError> {
63    Ok(format!("Found multiple elements with the text: {text}"))
64}
65
66fn get_missing_error(
67    _container: &HtmlElement,
68    text: Matcher,
69    options: SelectorMatcherOptions,
70) -> Result<String, QueryError> {
71    let match_normalizer = make_normalizer(NormalizerOptions {
72        trim: options.trim,
73        collapse_whitespace: options.collapse_whitespace,
74        normalizer: options.normalizer,
75    })?;
76    let text = text.to_string();
77    let normalized_text = match_normalizer(text.clone());
78    let is_normalized_different = normalized_text != text;
79
80    let selector = options.selector.unwrap_or("*".into());
81    let is_custom_selector = selector != "*";
82
83    Ok(format!(
84        "Unable to find an element with the text: {}{}. \
85        This could be because the text is broken up by multiple elements. \
86        In this case, you can provide a function for your text matcher to make your matcher more flexible.",
87        match is_normalized_different {
88            true => format!("{normalized_text} (normalized from '{text}')"),
89            false => text,
90        },
91        match is_custom_selector {
92            true => format!(", which matches selector '{selector}'"),
93            false => "".into(),
94        }
95    ))
96}
97
98build_queries!(
99    _query_all_by_text,
100    get_multiple_error,
101    get_missing_error,
102    text,
103    crate::types::Matcher,
104    crate::types::SelectorMatcherOptions
105);
106
107pub use internal::{
108    find_all_by_text, find_by_text, get_all_by_text, get_by_text, query_all_by_text, query_by_text,
109};