Skip to main content

tablo_test/protocol/
tabs.rs

1//! Reads a page's `Tabs` blocks: `[role=tab]` for each trigger, `aria-selected` for the active
2//! one, `aria-controls` for its `[role=tabpanel]`, `hidden` on a panel the page hides, and
3//! `data-tab-error` on the trigger of a tab holding a field error.
4
5use super::dom::{attr_value, strip_text, tags_where};
6
7/// One tab of a `Tabs` block.
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct Tab {
10    /// The trigger's text.
11    pub label: String,
12    /// Whether the page opens on this tab: its trigger is selected and its panel shows.
13    pub active: bool,
14    /// Whether the trigger marks a field error in the tab.
15    pub errored: bool,
16    /// The keys the controls in the tab's panel post, in document order, each once.
17    pub keys: Vec<String>,
18}
19
20/// The tabs of every `Tabs` block `html` renders, in document order: a nested block's after the
21/// tab holding it, whose `keys` hold the nested block's too.
22pub fn tabs(html: &str) -> Vec<Tab> {
23    tags_where(html, |tag, attrs| {
24        tag == "button" && attr_value(attrs, "role").as_deref() == Some("tab")
25    })
26    .into_iter()
27    .map(|(_, attrs, inner)| {
28        let panel = attr_value(&attrs, "aria-controls").and_then(|id| {
29            tags_where(html, |_, attrs| {
30                attr_value(attrs, "id").as_deref() == Some(id.as_str())
31            })
32            .into_iter()
33            .next()
34        });
35        let shown = panel
36            .as_ref()
37            .is_some_and(|(_, attrs, _)| !attrs.iter().any(|(name, _)| name == "hidden"));
38        let mut keys: Vec<String> = Vec::new();
39        if let Some((_, _, panel)) = &panel {
40            for (_, attrs, _) in tags_where(panel, |_, attrs| attr_value(attrs, "name").is_some()) {
41                let key = attr_value(&attrs, "name").unwrap_or_default();
42                if !keys.contains(&key) {
43                    keys.push(key);
44                }
45            }
46        }
47        Tab {
48            label: strip_text(&inner),
49            active: shown && attr_value(&attrs, "aria-selected").as_deref() == Some("true"),
50            errored: attrs.iter().any(|(name, _)| name == "data-tab-error"),
51            keys,
52        }
53    })
54    .collect()
55}