Skip to main content

tablo_test/protocol/
wizard.rs

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