testing_library_dom/queries/
label_text.rs

1use web_sys::HtmlElement;
2
3use crate::{
4    build_queries,
5    error::QueryError,
6    label_helpers::{get_labels, get_real_labels},
7    matches::{fuzzy_matches, make_normalizer, matches},
8    query_all_by_attribute,
9    types::{Matcher, MatcherOptions, NormalizerOptions, SelectorMatcherOptions},
10    util::node_list_to_vec,
11};
12
13pub fn _query_all_by_label_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 matcher = match options.exact.unwrap_or(true) {
21        true => matches,
22        false => fuzzy_matches,
23    };
24    let match_normalizer = make_normalizer(NormalizerOptions {
25        trim: options.trim,
26        collapse_whitespace: options.collapse_whitespace,
27        normalizer: options.normalizer,
28    })?;
29
30    let mut matching_labelled_elements = node_list_to_vec::<HtmlElement>(
31        container
32            .query_selector_all("*")
33            .map_err(QueryError::JsError)?,
34    )
35    .into_iter()
36    .filter(|element| {
37        !get_real_labels(element).is_empty() || element.has_attribute("aria-labelledby")
38    })
39    .fold(vec![], |mut labelled_elements, labelled_element| {
40        let label_list = get_labels(container, &labelled_element, Some(selector.clone()));
41
42        for label in &label_list {
43            if let Some(form_control) = label.form_control.as_ref() {
44                if matcher(
45                    label.content.clone(),
46                    label.form_control.as_deref(),
47                    &text,
48                    match_normalizer.as_ref(),
49                ) {
50                    labelled_elements.push(form_control.clone());
51                }
52            }
53        }
54
55        let labels_value = label_list
56            .into_iter()
57            .filter_map(|label| label.content)
58            .collect::<Vec<_>>();
59
60        if matcher(
61            Some(labels_value.join(" ")),
62            Some(labelled_element.as_ref()),
63            &text,
64            match_normalizer.as_ref(),
65        ) {
66            labelled_elements.push(labelled_element.clone());
67        }
68
69        if labels_value.len() > 1 {
70            for (index, label_value) in labels_value.iter().enumerate() {
71                if matcher(
72                    Some(label_value.clone()),
73                    Some(labelled_element.as_ref()),
74                    &text,
75                    match_normalizer.as_ref(),
76                ) {
77                    labelled_elements.push(labelled_element.clone());
78                }
79
80                let labels_filtered = labels_value
81                    .clone()
82                    .splice(index..index + 1, vec![])
83                    .collect::<Vec<_>>();
84
85                if labels_filtered.len() > 1
86                    && matcher(
87                        Some(labels_filtered.join(" ")),
88                        Some(labelled_element.as_ref()),
89                        &text,
90                        match_normalizer.as_ref(),
91                    )
92                {
93                    labelled_elements.push(labelled_element.clone());
94                }
95            }
96        }
97
98        labelled_elements
99    });
100
101    matching_labelled_elements.append(&mut query_all_by_attribute(
102        "aria-label".into(),
103        container,
104        text,
105        MatcherOptions {
106            exact: options.exact,
107            normalizer: Some(match_normalizer),
108            trim: None,
109            collapse_whitespace: None,
110            suggest: None,
111        },
112    )?);
113
114    let mut unique_matching_labelled_elements = vec![];
115    for element in matching_labelled_elements {
116        if !element.matches(&selector).unwrap_or(false) {
117            continue;
118        }
119
120        if !unique_matching_labelled_elements.contains(&element) {
121            unique_matching_labelled_elements.push(element);
122        }
123    }
124
125    Ok(unique_matching_labelled_elements)
126}
127
128// TODO: implement get_all_by_label_text override
129
130fn get_multiple_error(
131    _container: &HtmlElement,
132    text: Matcher,
133    _options: SelectorMatcherOptions,
134) -> Result<String, QueryError> {
135    Ok(format!(
136        "Found multiple elements with the label text: {text}"
137    ))
138}
139
140fn get_missing_error(
141    _container: &HtmlElement,
142    text: Matcher,
143    _options: SelectorMatcherOptions,
144) -> Result<String, QueryError> {
145    Ok(format!(
146        // "Unable to find an element with the label text: {text}"
147        "Unable to find a label with the text of: {text}"
148    ))
149}
150
151build_queries!(
152    _query_all_by_label_text,
153    get_multiple_error,
154    get_missing_error,
155    label_text,
156    crate::types::Matcher,
157    crate::types::SelectorMatcherOptions
158);
159
160pub use internal::{
161    find_all_by_label_text, find_by_label_text, get_all_by_label_text, get_by_label_text,
162    query_all_by_label_text, query_by_label_text,
163};