presentar_core/
accessibility.rs

1#![allow(clippy::unwrap_used, clippy::disallowed_methods)]
2//! Accessibility support for screen readers and assistive technology.
3//!
4//! This module provides:
5//! - Hit testing for finding accessible elements at a point
6//! - Accessibility tree building for screen reader navigation
7//! - Focus order calculation
8//! - ARIA-like properties for widgets
9
10use crate::geometry::{Point, Rect};
11use crate::widget::{AccessibleRole, WidgetId};
12use std::collections::HashMap;
13
14/// An accessible element in the accessibility tree.
15#[derive(Debug, Clone)]
16pub struct AccessibleNode {
17    /// Unique identifier.
18    pub id: AccessibleNodeId,
19    /// Widget ID if associated with a widget.
20    pub widget_id: Option<WidgetId>,
21    /// Accessible name (label).
22    pub name: Option<String>,
23    /// Accessible description.
24    pub description: Option<String>,
25    /// Accessible role.
26    pub role: AccessibleRole,
27    /// Bounding rectangle.
28    pub bounds: Rect,
29    /// Whether the element is focusable.
30    pub focusable: bool,
31    /// Whether the element is currently focused.
32    pub focused: bool,
33    /// Whether the element is enabled.
34    pub enabled: bool,
35    /// Whether the element is visible.
36    pub visible: bool,
37    /// Whether the element is expanded (for expandable elements).
38    pub expanded: Option<bool>,
39    /// Whether the element is checked (for checkboxes/radios).
40    pub checked: Option<CheckedState>,
41    /// Current value (for sliders, inputs).
42    pub value: Option<String>,
43    /// Minimum value (for sliders).
44    pub value_min: Option<f64>,
45    /// Maximum value (for sliders).
46    pub value_max: Option<f64>,
47    /// Value text (human-readable value).
48    pub value_text: Option<String>,
49    /// Children node IDs.
50    pub children: Vec<AccessibleNodeId>,
51    /// Parent node ID.
52    pub parent: Option<AccessibleNodeId>,
53    /// Tab index for focus order (-1 = not focusable, 0 = natural order, >0 = explicit order).
54    pub tab_index: i32,
55    /// Level in heading hierarchy (1-6 for headings).
56    pub level: Option<u8>,
57    /// Live region type for dynamic content.
58    pub live: LiveRegion,
59    /// Custom properties.
60    pub properties: HashMap<String, String>,
61}
62
63impl AccessibleNode {
64    /// Create a new accessible node.
65    pub fn new(id: AccessibleNodeId, role: AccessibleRole, bounds: Rect) -> Self {
66        Self {
67            id,
68            widget_id: None,
69            name: None,
70            description: None,
71            role,
72            bounds,
73            focusable: false,
74            focused: false,
75            enabled: true,
76            visible: true,
77            expanded: None,
78            checked: None,
79            value: None,
80            value_min: None,
81            value_max: None,
82            value_text: None,
83            children: Vec::new(),
84            parent: None,
85            tab_index: -1,
86            level: None,
87            live: LiveRegion::Off,
88            properties: HashMap::new(),
89        }
90    }
91
92    /// Create a new button node.
93    pub fn button(id: AccessibleNodeId, name: &str, bounds: Rect) -> Self {
94        let mut node = Self::new(id, AccessibleRole::Button, bounds);
95        node.name = Some(name.to_string());
96        node.focusable = true;
97        node.tab_index = 0;
98        node
99    }
100
101    /// Create a new checkbox node.
102    pub fn checkbox(id: AccessibleNodeId, name: &str, checked: bool, bounds: Rect) -> Self {
103        let mut node = Self::new(id, AccessibleRole::Checkbox, bounds);
104        node.name = Some(name.to_string());
105        node.focusable = true;
106        node.tab_index = 0;
107        node.checked = Some(if checked {
108            CheckedState::Checked
109        } else {
110            CheckedState::Unchecked
111        });
112        node
113    }
114
115    /// Create a new text input node.
116    pub fn text_input(id: AccessibleNodeId, label: &str, value: &str, bounds: Rect) -> Self {
117        let mut node = Self::new(id, AccessibleRole::TextInput, bounds);
118        node.name = Some(label.to_string());
119        node.value = Some(value.to_string());
120        node.focusable = true;
121        node.tab_index = 0;
122        node
123    }
124
125    /// Create a new heading node.
126    pub fn heading(id: AccessibleNodeId, text: &str, level: u8, bounds: Rect) -> Self {
127        let mut node = Self::new(id, AccessibleRole::Heading, bounds);
128        node.name = Some(text.to_string());
129        node.level = Some(level.min(6).max(1));
130        node
131    }
132
133    /// Create a new slider node.
134    pub fn slider(
135        id: AccessibleNodeId,
136        name: &str,
137        value: f64,
138        min: f64,
139        max: f64,
140        bounds: Rect,
141    ) -> Self {
142        let mut node = Self::new(id, AccessibleRole::Slider, bounds);
143        node.name = Some(name.to_string());
144        node.value = Some(value.to_string());
145        node.value_min = Some(min);
146        node.value_max = Some(max);
147        node.focusable = true;
148        node.tab_index = 0;
149        node
150    }
151
152    /// Check if this node contains the given point.
153    pub fn contains_point(&self, point: Point) -> bool {
154        self.visible && self.bounds.contains_point(&point)
155    }
156
157    /// Set the node's name.
158    pub fn with_name(mut self, name: &str) -> Self {
159        self.name = Some(name.to_string());
160        self
161    }
162
163    /// Set the node's description.
164    pub fn with_description(mut self, desc: &str) -> Self {
165        self.description = Some(desc.to_string());
166        self
167    }
168
169    /// Set the node as focusable.
170    pub fn with_focusable(mut self, focusable: bool) -> Self {
171        self.focusable = focusable;
172        if focusable && self.tab_index < 0 {
173            self.tab_index = 0;
174        }
175        self
176    }
177
178    /// Set a custom property.
179    pub fn with_property(mut self, key: &str, value: &str) -> Self {
180        self.properties.insert(key.to_string(), value.to_string());
181        self
182    }
183}
184
185/// Unique identifier for an accessible node.
186#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
187pub struct AccessibleNodeId(pub u64);
188
189impl AccessibleNodeId {
190    /// Create a new node ID.
191    pub const fn new(id: u64) -> Self {
192        Self(id)
193    }
194}
195
196/// Checked state for checkboxes and radio buttons.
197#[derive(Debug, Clone, Copy, PartialEq, Eq)]
198pub enum CheckedState {
199    /// Not checked.
200    Unchecked,
201    /// Checked.
202    Checked,
203    /// Mixed/indeterminate state.
204    Mixed,
205}
206
207/// Live region type for dynamic content updates.
208#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
209pub enum LiveRegion {
210    /// No live region.
211    #[default]
212    Off,
213    /// Polite - announce when user is idle.
214    Polite,
215    /// Assertive - announce immediately.
216    Assertive,
217}
218
219/// Accessibility tree for hit testing and navigation.
220#[derive(Debug, Default)]
221pub struct AccessibilityTree {
222    /// All nodes in the tree.
223    nodes: HashMap<AccessibleNodeId, AccessibleNode>,
224    /// Root node ID.
225    root: Option<AccessibleNodeId>,
226    /// Next available node ID.
227    next_id: u64,
228    /// Current focus node.
229    focus: Option<AccessibleNodeId>,
230    /// Focus order (computed lazily).
231    focus_order: Vec<AccessibleNodeId>,
232    /// Whether focus order needs recomputation.
233    focus_order_dirty: bool,
234}
235
236impl AccessibilityTree {
237    /// Create a new empty accessibility tree.
238    pub fn new() -> Self {
239        Self::default()
240    }
241
242    /// Generate a new unique node ID.
243    pub fn next_id(&mut self) -> AccessibleNodeId {
244        let id = AccessibleNodeId::new(self.next_id);
245        self.next_id += 1;
246        id
247    }
248
249    /// Set the root node.
250    pub fn set_root(&mut self, id: AccessibleNodeId) {
251        self.root = Some(id);
252    }
253
254    /// Get the root node.
255    pub fn root(&self) -> Option<&AccessibleNode> {
256        self.root.and_then(|id| self.nodes.get(&id))
257    }
258
259    /// Get a node by ID.
260    pub fn get(&self, id: AccessibleNodeId) -> Option<&AccessibleNode> {
261        self.nodes.get(&id)
262    }
263
264    /// Get a mutable node by ID.
265    pub fn get_mut(&mut self, id: AccessibleNodeId) -> Option<&mut AccessibleNode> {
266        self.nodes.get_mut(&id)
267    }
268
269    /// Insert a node into the tree.
270    pub fn insert(&mut self, node: AccessibleNode) {
271        self.focus_order_dirty = true;
272        self.nodes.insert(node.id, node);
273    }
274
275    /// Remove a node from the tree.
276    pub fn remove(&mut self, id: AccessibleNodeId) -> Option<AccessibleNode> {
277        self.focus_order_dirty = true;
278        self.nodes.remove(&id)
279    }
280
281    /// Get the number of nodes in the tree.
282    pub fn len(&self) -> usize {
283        self.nodes.len()
284    }
285
286    /// Check if the tree is empty.
287    pub fn is_empty(&self) -> bool {
288        self.nodes.is_empty()
289    }
290
291    /// Clear all nodes from the tree.
292    pub fn clear(&mut self) {
293        self.nodes.clear();
294        self.root = None;
295        self.focus = None;
296        self.focus_order.clear();
297        self.focus_order_dirty = false;
298    }
299
300    /// Get the currently focused node.
301    pub fn focused(&self) -> Option<&AccessibleNode> {
302        self.focus.and_then(|id| self.nodes.get(&id))
303    }
304
305    /// Get the currently focused node ID.
306    pub fn focused_id(&self) -> Option<AccessibleNodeId> {
307        self.focus
308    }
309
310    /// Set focus to a node.
311    pub fn set_focus(&mut self, id: AccessibleNodeId) -> bool {
312        if let Some(node) = self.nodes.get(&id) {
313            if node.focusable && node.enabled && node.visible {
314                // Clear old focus
315                if let Some(old_id) = self.focus {
316                    if let Some(old_node) = self.nodes.get_mut(&old_id) {
317                        old_node.focused = false;
318                    }
319                }
320
321                // Set new focus
322                if let Some(new_node) = self.nodes.get_mut(&id) {
323                    new_node.focused = true;
324                    self.focus = Some(id);
325                    return true;
326                }
327            }
328        }
329        false
330    }
331
332    /// Clear focus.
333    pub fn clear_focus(&mut self) {
334        if let Some(id) = self.focus.take() {
335            if let Some(node) = self.nodes.get_mut(&id) {
336                node.focused = false;
337            }
338        }
339    }
340
341    /// Move focus to the next focusable element.
342    pub fn focus_next(&mut self) -> Option<AccessibleNodeId> {
343        self.ensure_focus_order();
344
345        if self.focus_order.is_empty() {
346            return None;
347        }
348
349        let current_idx = self
350            .focus
351            .and_then(|id| self.focus_order.iter().position(|&fid| fid == id));
352
353        let next_idx = match current_idx {
354            Some(idx) => (idx + 1) % self.focus_order.len(),
355            None => 0,
356        };
357
358        let next_id = self.focus_order[next_idx];
359        self.set_focus(next_id);
360        Some(next_id)
361    }
362
363    /// Move focus to the previous focusable element.
364    pub fn focus_previous(&mut self) -> Option<AccessibleNodeId> {
365        self.ensure_focus_order();
366
367        if self.focus_order.is_empty() {
368            return None;
369        }
370
371        let current_idx = self
372            .focus
373            .and_then(|id| self.focus_order.iter().position(|&fid| fid == id));
374
375        let prev_idx = match current_idx {
376            Some(idx) if idx > 0 => idx - 1,
377            Some(_) => self.focus_order.len() - 1,
378            None => self.focus_order.len() - 1,
379        };
380
381        let prev_id = self.focus_order[prev_idx];
382        self.set_focus(prev_id);
383        Some(prev_id)
384    }
385
386    /// Ensure focus order is computed.
387    fn ensure_focus_order(&mut self) {
388        if self.focus_order_dirty {
389            self.compute_focus_order();
390        }
391    }
392
393    /// Compute focus order based on tab indices and DOM order.
394    fn compute_focus_order(&mut self) {
395        let mut focusable: Vec<_> = self
396            .nodes
397            .values()
398            .filter(|n| n.focusable && n.enabled && n.visible && n.tab_index >= 0)
399            .map(|n| (n.id, n.tab_index, n.bounds.y, n.bounds.x))
400            .collect();
401
402        // Sort by tab_index (positive first, then by position)
403        focusable.sort_by(|a, b| {
404            match (a.1, b.1) {
405                (0, 0) => {
406                    // Both natural order - sort by position (top-to-bottom, left-to-right)
407                    a.2.partial_cmp(&b.2)
408                        .unwrap_or(std::cmp::Ordering::Equal)
409                        .then(a.3.partial_cmp(&b.3).unwrap_or(std::cmp::Ordering::Equal))
410                }
411                (0, _) => std::cmp::Ordering::Greater, // 0 comes after positive
412                (_, 0) => std::cmp::Ordering::Less,
413                _ => a.1.cmp(&b.1), // Compare positive tab indices
414            }
415        });
416
417        self.focus_order = focusable.into_iter().map(|(id, _, _, _)| id).collect();
418        self.focus_order_dirty = false;
419    }
420
421    /// Get the focus order.
422    pub fn get_focus_order(&mut self) -> &[AccessibleNodeId] {
423        self.ensure_focus_order();
424        &self.focus_order
425    }
426}
427
428/// Hit tester for finding accessible elements at a point.
429#[derive(Debug, Default)]
430pub struct HitTester {
431    /// The accessibility tree to test against.
432    tree: AccessibilityTree,
433}
434
435impl HitTester {
436    /// Create a new hit tester.
437    pub fn new() -> Self {
438        Self::default()
439    }
440
441    /// Create a hit tester with an existing tree.
442    pub fn with_tree(tree: AccessibilityTree) -> Self {
443        Self { tree }
444    }
445
446    /// Get the underlying tree.
447    pub fn tree(&self) -> &AccessibilityTree {
448        &self.tree
449    }
450
451    /// Get mutable access to the tree.
452    pub fn tree_mut(&mut self) -> &mut AccessibilityTree {
453        &mut self.tree
454    }
455
456    /// Find the deepest accessible element at the given point.
457    pub fn hit_test(&self, point: Point) -> Option<&AccessibleNode> {
458        self.hit_test_id(point).and_then(|id| self.tree.get(id))
459    }
460
461    /// Find the ID of the deepest accessible element at the given point.
462    pub fn hit_test_id(&self, point: Point) -> Option<AccessibleNodeId> {
463        let root_id = self.tree.root?;
464        self.hit_test_recursive(root_id, point)
465    }
466
467    /// Recursive hit test implementation.
468    fn hit_test_recursive(
469        &self,
470        node_id: AccessibleNodeId,
471        point: Point,
472    ) -> Option<AccessibleNodeId> {
473        let node = self.tree.get(node_id)?;
474
475        if !node.contains_point(point) {
476            return None;
477        }
478
479        // Check children in reverse order (last drawn = topmost)
480        for &child_id in node.children.iter().rev() {
481            if let Some(hit_id) = self.hit_test_recursive(child_id, point) {
482                return Some(hit_id);
483            }
484        }
485
486        // Return this node if no child was hit
487        Some(node_id)
488    }
489
490    /// Find all accessible elements at the given point (from topmost to root).
491    pub fn hit_test_all(&self, point: Point) -> Vec<&AccessibleNode> {
492        let ids = self.hit_test_all_ids(point);
493        ids.iter().filter_map(|&id| self.tree.get(id)).collect()
494    }
495
496    /// Find all element IDs at the given point.
497    pub fn hit_test_all_ids(&self, point: Point) -> Vec<AccessibleNodeId> {
498        let mut results = Vec::new();
499        if let Some(root_id) = self.tree.root {
500            self.hit_test_all_recursive(root_id, point, &mut results);
501        }
502        results.reverse(); // Topmost first
503        results
504    }
505
506    /// Recursive hit test that collects all hits.
507    fn hit_test_all_recursive(
508        &self,
509        node_id: AccessibleNodeId,
510        point: Point,
511        results: &mut Vec<AccessibleNodeId>,
512    ) {
513        let Some(node) = self.tree.get(node_id) else {
514            return;
515        };
516
517        if !node.contains_point(point) {
518            return;
519        }
520
521        results.push(node_id);
522
523        for &child_id in &node.children {
524            self.hit_test_all_recursive(child_id, point, results);
525        }
526    }
527
528    /// Find the first focusable element at the given point.
529    pub fn hit_test_focusable(&self, point: Point) -> Option<&AccessibleNode> {
530        let ids = self.hit_test_all_ids(point);
531        ids.into_iter()
532            .filter_map(|id| self.tree.get(id))
533            .find(|node| node.focusable && node.enabled)
534    }
535
536    /// Find all elements with a specific role at the given point.
537    pub fn hit_test_role(&self, point: Point, role: AccessibleRole) -> Vec<&AccessibleNode> {
538        self.hit_test_all(point)
539            .into_iter()
540            .filter(|node| node.role == role)
541            .collect()
542    }
543}
544
545/// Builder for constructing accessibility trees.
546#[derive(Debug)]
547pub struct AccessibilityTreeBuilder {
548    tree: AccessibilityTree,
549    current_parent: Option<AccessibleNodeId>,
550}
551
552impl AccessibilityTreeBuilder {
553    /// Create a new builder.
554    pub fn new() -> Self {
555        Self {
556            tree: AccessibilityTree::new(),
557            current_parent: None,
558        }
559    }
560
561    /// Add a root node.
562    pub fn root(mut self, role: AccessibleRole, bounds: Rect) -> Self {
563        let id = self.tree.next_id();
564        let node = AccessibleNode::new(id, role, bounds);
565        self.tree.insert(node);
566        self.tree.set_root(id);
567        self.current_parent = Some(id);
568        self
569    }
570
571    /// Add a child node to the current parent.
572    pub fn child(mut self, role: AccessibleRole, bounds: Rect) -> (Self, AccessibleNodeId) {
573        let id = self.tree.next_id();
574        let mut node = AccessibleNode::new(id, role, bounds);
575        node.parent = self.current_parent;
576
577        if let Some(parent_id) = self.current_parent {
578            if let Some(parent) = self.tree.get_mut(parent_id) {
579                parent.children.push(id);
580            }
581        }
582
583        self.tree.insert(node);
584        (self, id)
585    }
586
587    /// Add a child and descend into it.
588    pub fn push_child(mut self, role: AccessibleRole, bounds: Rect) -> Self {
589        let id = self.tree.next_id();
590        let mut node = AccessibleNode::new(id, role, bounds);
591        node.parent = self.current_parent;
592
593        if let Some(parent_id) = self.current_parent {
594            if let Some(parent) = self.tree.get_mut(parent_id) {
595                parent.children.push(id);
596            }
597        }
598
599        self.tree.insert(node);
600        self.current_parent = Some(id);
601        self
602    }
603
604    /// Pop back to the parent.
605    pub fn pop(mut self) -> Self {
606        if let Some(current) = self.current_parent {
607            if let Some(node) = self.tree.get(current) {
608                self.current_parent = node.parent;
609            }
610        }
611        self
612    }
613
614    /// Configure the current node.
615    pub fn configure<F>(mut self, f: F) -> Self
616    where
617        F: FnOnce(&mut AccessibleNode),
618    {
619        if let Some(id) = self.current_parent {
620            if let Some(node) = self.tree.get_mut(id) {
621                f(node);
622            }
623        }
624        self
625    }
626
627    /// Build the tree.
628    pub fn build(self) -> AccessibilityTree {
629        self.tree
630    }
631}
632
633impl Default for AccessibilityTreeBuilder {
634    fn default() -> Self {
635        Self::new()
636    }
637}
638
639#[cfg(test)]
640mod tests {
641    use super::*;
642
643    // AccessibleNode tests
644    #[test]
645    fn test_accessible_node_new() {
646        let id = AccessibleNodeId::new(1);
647        let bounds = Rect::new(0.0, 0.0, 100.0, 50.0);
648        let node = AccessibleNode::new(id, AccessibleRole::Button, bounds);
649
650        assert_eq!(node.id, id);
651        assert_eq!(node.role, AccessibleRole::Button);
652        assert_eq!(node.bounds, bounds);
653        assert!(!node.focusable);
654        assert!(node.enabled);
655        assert!(node.visible);
656    }
657
658    #[test]
659    fn test_accessible_node_button() {
660        let id = AccessibleNodeId::new(1);
661        let bounds = Rect::new(0.0, 0.0, 100.0, 50.0);
662        let node = AccessibleNode::button(id, "Click me", bounds);
663
664        assert_eq!(node.name, Some("Click me".to_string()));
665        assert_eq!(node.role, AccessibleRole::Button);
666        assert!(node.focusable);
667        assert_eq!(node.tab_index, 0);
668    }
669
670    #[test]
671    fn test_accessible_node_checkbox() {
672        let id = AccessibleNodeId::new(1);
673        let bounds = Rect::new(0.0, 0.0, 20.0, 20.0);
674
675        let unchecked = AccessibleNode::checkbox(id, "Accept terms", false, bounds);
676        assert_eq!(unchecked.checked, Some(CheckedState::Unchecked));
677
678        let checked = AccessibleNode::checkbox(id, "Accept terms", true, bounds);
679        assert_eq!(checked.checked, Some(CheckedState::Checked));
680    }
681
682    #[test]
683    fn test_accessible_node_text_input() {
684        let id = AccessibleNodeId::new(1);
685        let bounds = Rect::new(0.0, 0.0, 200.0, 30.0);
686        let node = AccessibleNode::text_input(id, "Username", "john_doe", bounds);
687
688        assert_eq!(node.name, Some("Username".to_string()));
689        assert_eq!(node.value, Some("john_doe".to_string()));
690        assert_eq!(node.role, AccessibleRole::TextInput);
691    }
692
693    #[test]
694    fn test_accessible_node_heading() {
695        let id = AccessibleNodeId::new(1);
696        let bounds = Rect::new(0.0, 0.0, 200.0, 40.0);
697        let node = AccessibleNode::heading(id, "Welcome", 1, bounds);
698
699        assert_eq!(node.name, Some("Welcome".to_string()));
700        assert_eq!(node.role, AccessibleRole::Heading);
701        assert_eq!(node.level, Some(1));
702    }
703
704    #[test]
705    fn test_accessible_node_heading_level_clamp() {
706        let id = AccessibleNodeId::new(1);
707        let bounds = Rect::new(0.0, 0.0, 200.0, 40.0);
708
709        // Level 0 should clamp to 1
710        let h0 = AccessibleNode::heading(id, "H0", 0, bounds);
711        assert_eq!(h0.level, Some(1));
712
713        // Level 10 should clamp to 6
714        let h10 = AccessibleNode::heading(id, "H10", 10, bounds);
715        assert_eq!(h10.level, Some(6));
716    }
717
718    #[test]
719    fn test_accessible_node_slider() {
720        let id = AccessibleNodeId::new(1);
721        let bounds = Rect::new(0.0, 0.0, 200.0, 20.0);
722        let node = AccessibleNode::slider(id, "Volume", 50.0, 0.0, 100.0, bounds);
723
724        assert_eq!(node.name, Some("Volume".to_string()));
725        assert_eq!(node.value, Some("50".to_string()));
726        assert_eq!(node.value_min, Some(0.0));
727        assert_eq!(node.value_max, Some(100.0));
728        assert_eq!(node.role, AccessibleRole::Slider);
729    }
730
731    #[test]
732    fn test_accessible_node_contains_point() {
733        let id = AccessibleNodeId::new(1);
734        let bounds = Rect::new(10.0, 10.0, 100.0, 50.0);
735        let node = AccessibleNode::new(id, AccessibleRole::Generic, bounds);
736
737        assert!(node.contains_point(Point::new(50.0, 30.0)));
738        assert!(node.contains_point(Point::new(10.0, 10.0))); // Edge
739        assert!(!node.contains_point(Point::new(5.0, 30.0)));
740        assert!(!node.contains_point(Point::new(120.0, 30.0)));
741    }
742
743    #[test]
744    fn test_accessible_node_invisible_not_contains() {
745        let id = AccessibleNodeId::new(1);
746        let bounds = Rect::new(0.0, 0.0, 100.0, 50.0);
747        let mut node = AccessibleNode::new(id, AccessibleRole::Generic, bounds);
748        node.visible = false;
749
750        assert!(!node.contains_point(Point::new(50.0, 25.0)));
751    }
752
753    #[test]
754    fn test_accessible_node_builder_pattern() {
755        let id = AccessibleNodeId::new(1);
756        let bounds = Rect::new(0.0, 0.0, 100.0, 50.0);
757        let node = AccessibleNode::new(id, AccessibleRole::Button, bounds)
758            .with_name("Submit")
759            .with_description("Submit the form")
760            .with_focusable(true)
761            .with_property("aria-pressed", "false");
762
763        assert_eq!(node.name, Some("Submit".to_string()));
764        assert_eq!(node.description, Some("Submit the form".to_string()));
765        assert!(node.focusable);
766        assert_eq!(
767            node.properties.get("aria-pressed"),
768            Some(&"false".to_string())
769        );
770    }
771
772    // AccessibilityTree tests
773    #[test]
774    fn test_tree_new() {
775        let tree = AccessibilityTree::new();
776        assert!(tree.is_empty());
777        assert!(tree.root().is_none());
778    }
779
780    #[test]
781    fn test_tree_insert_and_get() {
782        let mut tree = AccessibilityTree::new();
783        let id = tree.next_id();
784        let node = AccessibleNode::new(
785            id,
786            AccessibleRole::Generic,
787            Rect::new(0.0, 0.0, 100.0, 100.0),
788        );
789        tree.insert(node);
790        tree.set_root(id);
791
792        assert_eq!(tree.len(), 1);
793        assert!(tree.get(id).is_some());
794        assert!(tree.root().is_some());
795    }
796
797    #[test]
798    fn test_tree_remove() {
799        let mut tree = AccessibilityTree::new();
800        let id = tree.next_id();
801        let node = AccessibleNode::new(
802            id,
803            AccessibleRole::Generic,
804            Rect::new(0.0, 0.0, 100.0, 100.0),
805        );
806        tree.insert(node);
807
808        let removed = tree.remove(id);
809        assert!(removed.is_some());
810        assert!(tree.is_empty());
811    }
812
813    #[test]
814    fn test_tree_clear() {
815        let mut tree = AccessibilityTree::new();
816        let id1 = tree.next_id();
817        let id2 = tree.next_id();
818        tree.insert(AccessibleNode::new(
819            id1,
820            AccessibleRole::Generic,
821            Rect::default(),
822        ));
823        tree.insert(AccessibleNode::new(
824            id2,
825            AccessibleRole::Generic,
826            Rect::default(),
827        ));
828
829        tree.clear();
830        assert!(tree.is_empty());
831        assert!(tree.root().is_none());
832    }
833
834    #[test]
835    fn test_tree_focus() {
836        let mut tree = AccessibilityTree::new();
837        let id = tree.next_id();
838        let mut node = AccessibleNode::button(id, "Button", Rect::new(0.0, 0.0, 100.0, 50.0));
839        node.focusable = true;
840        tree.insert(node);
841
842        assert!(tree.set_focus(id));
843        assert_eq!(tree.focused_id(), Some(id));
844
845        let focused = tree.focused().unwrap();
846        assert!(focused.focused);
847    }
848
849    #[test]
850    fn test_tree_focus_non_focusable() {
851        let mut tree = AccessibilityTree::new();
852        let id = tree.next_id();
853        let node = AccessibleNode::new(
854            id,
855            AccessibleRole::Generic,
856            Rect::new(0.0, 0.0, 100.0, 50.0),
857        );
858        tree.insert(node);
859
860        assert!(!tree.set_focus(id));
861        assert!(tree.focused().is_none());
862    }
863
864    #[test]
865    fn test_tree_clear_focus() {
866        let mut tree = AccessibilityTree::new();
867        let id = tree.next_id();
868        let mut node = AccessibleNode::button(id, "Button", Rect::new(0.0, 0.0, 100.0, 50.0));
869        node.focusable = true;
870        tree.insert(node);
871        tree.set_focus(id);
872
873        tree.clear_focus();
874        assert!(tree.focused().is_none());
875        assert!(!tree.get(id).unwrap().focused);
876    }
877
878    #[test]
879    fn test_tree_focus_next() {
880        let mut tree = AccessibilityTree::new();
881
882        let id1 = tree.next_id();
883        let mut node1 = AccessibleNode::button(id1, "First", Rect::new(0.0, 0.0, 100.0, 50.0));
884        node1.focusable = true;
885        tree.insert(node1);
886
887        let id2 = tree.next_id();
888        let mut node2 = AccessibleNode::button(id2, "Second", Rect::new(0.0, 60.0, 100.0, 50.0));
889        node2.focusable = true;
890        tree.insert(node2);
891
892        // First focus_next should focus the first element
893        let first = tree.focus_next();
894        assert!(first.is_some());
895
896        // Second focus_next should focus the second element
897        let second = tree.focus_next();
898        assert!(second.is_some());
899        assert_ne!(first, second);
900    }
901
902    #[test]
903    fn test_tree_focus_previous() {
904        let mut tree = AccessibilityTree::new();
905
906        let id1 = tree.next_id();
907        let mut node1 = AccessibleNode::button(id1, "First", Rect::new(0.0, 0.0, 100.0, 50.0));
908        node1.focusable = true;
909        tree.insert(node1);
910
911        let id2 = tree.next_id();
912        let mut node2 = AccessibleNode::button(id2, "Second", Rect::new(0.0, 60.0, 100.0, 50.0));
913        node2.focusable = true;
914        tree.insert(node2);
915
916        // Set focus to second
917        tree.set_focus(id2);
918
919        // focus_previous should move to first
920        let prev = tree.focus_previous();
921        assert_eq!(prev, Some(id1));
922    }
923
924    // HitTester tests
925    #[test]
926    fn test_hit_tester_new() {
927        let tester = HitTester::new();
928        assert!(tester.tree().is_empty());
929    }
930
931    #[test]
932    fn test_hit_test_single_node() {
933        let mut tree = AccessibilityTree::new();
934        let id = tree.next_id();
935        let node = AccessibleNode::new(
936            id,
937            AccessibleRole::Button,
938            Rect::new(10.0, 10.0, 100.0, 50.0),
939        );
940        tree.insert(node);
941        tree.set_root(id);
942
943        let tester = HitTester::with_tree(tree);
944
945        // Hit inside
946        let result = tester.hit_test(Point::new(50.0, 30.0));
947        assert!(result.is_some());
948        assert_eq!(result.unwrap().id, id);
949
950        // Miss outside
951        let miss = tester.hit_test(Point::new(0.0, 0.0));
952        assert!(miss.is_none());
953    }
954
955    #[test]
956    fn test_hit_test_nested_nodes() {
957        let mut tree = AccessibilityTree::new();
958
959        // Parent node
960        let parent_id = tree.next_id();
961        let mut parent = AccessibleNode::new(
962            parent_id,
963            AccessibleRole::Generic,
964            Rect::new(0.0, 0.0, 200.0, 200.0),
965        );
966
967        // Child node (inside parent)
968        let child_id = tree.next_id();
969        let mut child = AccessibleNode::new(
970            child_id,
971            AccessibleRole::Button,
972            Rect::new(50.0, 50.0, 100.0, 100.0),
973        );
974        child.parent = Some(parent_id);
975
976        parent.children.push(child_id);
977        tree.insert(parent);
978        tree.insert(child);
979        tree.set_root(parent_id);
980
981        let tester = HitTester::with_tree(tree);
982
983        // Hit inside child should return child
984        let child_hit = tester.hit_test(Point::new(100.0, 100.0));
985        assert!(child_hit.is_some());
986        assert_eq!(child_hit.unwrap().id, child_id);
987
988        // Hit inside parent but outside child should return parent
989        let parent_hit = tester.hit_test(Point::new(10.0, 10.0));
990        assert!(parent_hit.is_some());
991        assert_eq!(parent_hit.unwrap().id, parent_id);
992    }
993
994    #[test]
995    fn test_hit_test_all() {
996        let mut tree = AccessibilityTree::new();
997
998        let parent_id = tree.next_id();
999        let mut parent = AccessibleNode::new(
1000            parent_id,
1001            AccessibleRole::Generic,
1002            Rect::new(0.0, 0.0, 200.0, 200.0),
1003        );
1004
1005        let child_id = tree.next_id();
1006        let mut child = AccessibleNode::new(
1007            child_id,
1008            AccessibleRole::Button,
1009            Rect::new(50.0, 50.0, 100.0, 100.0),
1010        );
1011        child.parent = Some(parent_id);
1012        parent.children.push(child_id);
1013
1014        tree.insert(parent);
1015        tree.insert(child);
1016        tree.set_root(parent_id);
1017
1018        let tester = HitTester::with_tree(tree);
1019
1020        // Hit inside child should return both child and parent
1021        let all_hits = tester.hit_test_all(Point::new(100.0, 100.0));
1022        assert_eq!(all_hits.len(), 2);
1023        // Topmost first
1024        assert_eq!(all_hits[0].id, child_id);
1025        assert_eq!(all_hits[1].id, parent_id);
1026    }
1027
1028    #[test]
1029    fn test_hit_test_focusable() {
1030        let mut tree = AccessibilityTree::new();
1031
1032        let parent_id = tree.next_id();
1033        let mut parent = AccessibleNode::new(
1034            parent_id,
1035            AccessibleRole::Generic,
1036            Rect::new(0.0, 0.0, 200.0, 200.0),
1037        );
1038
1039        let child_id = tree.next_id();
1040        let mut child =
1041            AccessibleNode::button(child_id, "Button", Rect::new(50.0, 50.0, 100.0, 100.0));
1042        child.parent = Some(parent_id);
1043        child.focusable = true;
1044        parent.children.push(child_id);
1045
1046        tree.insert(parent);
1047        tree.insert(child);
1048        tree.set_root(parent_id);
1049
1050        let tester = HitTester::with_tree(tree);
1051
1052        let focusable = tester.hit_test_focusable(Point::new(100.0, 100.0));
1053        assert!(focusable.is_some());
1054        assert_eq!(focusable.unwrap().id, child_id);
1055    }
1056
1057    #[test]
1058    fn test_hit_test_role() {
1059        let mut tree = AccessibilityTree::new();
1060
1061        let parent_id = tree.next_id();
1062        let mut parent = AccessibleNode::new(
1063            parent_id,
1064            AccessibleRole::Generic,
1065            Rect::new(0.0, 0.0, 200.0, 200.0),
1066        );
1067
1068        let button_id = tree.next_id();
1069        let mut button = AccessibleNode::new(
1070            button_id,
1071            AccessibleRole::Button,
1072            Rect::new(50.0, 50.0, 100.0, 100.0),
1073        );
1074        button.parent = Some(parent_id);
1075        parent.children.push(button_id);
1076
1077        tree.insert(parent);
1078        tree.insert(button);
1079        tree.set_root(parent_id);
1080
1081        let tester = HitTester::with_tree(tree);
1082
1083        let buttons = tester.hit_test_role(Point::new(100.0, 100.0), AccessibleRole::Button);
1084        assert_eq!(buttons.len(), 1);
1085        assert_eq!(buttons[0].role, AccessibleRole::Button);
1086
1087        let generic = tester.hit_test_role(Point::new(100.0, 100.0), AccessibleRole::Generic);
1088        assert_eq!(generic.len(), 1);
1089    }
1090
1091    // AccessibilityTreeBuilder tests
1092    #[test]
1093    fn test_builder_basic() {
1094        let tree = AccessibilityTreeBuilder::new()
1095            .root(AccessibleRole::Generic, Rect::new(0.0, 0.0, 800.0, 600.0))
1096            .build();
1097
1098        assert_eq!(tree.len(), 1);
1099        assert!(tree.root().is_some());
1100    }
1101
1102    #[test]
1103    fn test_builder_with_children() {
1104        let tree = AccessibilityTreeBuilder::new()
1105            .root(AccessibleRole::Generic, Rect::new(0.0, 0.0, 800.0, 600.0))
1106            .push_child(AccessibleRole::Button, Rect::new(10.0, 10.0, 100.0, 50.0))
1107            .pop()
1108            .push_child(
1109                AccessibleRole::TextInput,
1110                Rect::new(10.0, 70.0, 200.0, 30.0),
1111            )
1112            .pop()
1113            .build();
1114
1115        assert_eq!(tree.len(), 3);
1116
1117        let root = tree.root().unwrap();
1118        assert_eq!(root.children.len(), 2);
1119    }
1120
1121    #[test]
1122    fn test_builder_configure() {
1123        let tree = AccessibilityTreeBuilder::new()
1124            .root(AccessibleRole::Generic, Rect::new(0.0, 0.0, 800.0, 600.0))
1125            .push_child(AccessibleRole::Button, Rect::new(10.0, 10.0, 100.0, 50.0))
1126            .configure(|node| {
1127                node.name = Some("Submit".to_string());
1128                node.focusable = true;
1129            })
1130            .pop()
1131            .build();
1132
1133        let root = tree.root().unwrap();
1134        let child_id = root.children[0];
1135        let child = tree.get(child_id).unwrap();
1136
1137        assert_eq!(child.name, Some("Submit".to_string()));
1138        assert!(child.focusable);
1139    }
1140
1141    // CheckedState tests
1142    #[test]
1143    fn test_checked_state_equality() {
1144        assert_eq!(CheckedState::Checked, CheckedState::Checked);
1145        assert_ne!(CheckedState::Checked, CheckedState::Unchecked);
1146        assert_ne!(CheckedState::Mixed, CheckedState::Checked);
1147    }
1148
1149    // LiveRegion tests
1150    #[test]
1151    fn test_live_region_default() {
1152        let region = LiveRegion::default();
1153        assert_eq!(region, LiveRegion::Off);
1154    }
1155
1156    // AccessibleNodeId tests
1157    #[test]
1158    fn test_accessible_node_id() {
1159        let id1 = AccessibleNodeId::new(1);
1160        let id2 = AccessibleNodeId::new(1);
1161        let id3 = AccessibleNodeId::new(2);
1162
1163        assert_eq!(id1, id2);
1164        assert_ne!(id1, id3);
1165    }
1166
1167    #[test]
1168    fn test_accessible_node_id_default() {
1169        let id = AccessibleNodeId::default();
1170        assert_eq!(id.0, 0);
1171    }
1172}