1#![allow(clippy::unwrap_used, clippy::disallowed_methods)]
2use crate::geometry::{Point, Rect};
11use crate::widget::{AccessibleRole, WidgetId};
12use std::collections::HashMap;
13
14#[derive(Debug, Clone)]
16pub struct AccessibleNode {
17 pub id: AccessibleNodeId,
19 pub widget_id: Option<WidgetId>,
21 pub name: Option<String>,
23 pub description: Option<String>,
25 pub role: AccessibleRole,
27 pub bounds: Rect,
29 pub focusable: bool,
31 pub focused: bool,
33 pub enabled: bool,
35 pub visible: bool,
37 pub expanded: Option<bool>,
39 pub checked: Option<CheckedState>,
41 pub value: Option<String>,
43 pub value_min: Option<f64>,
45 pub value_max: Option<f64>,
47 pub value_text: Option<String>,
49 pub children: Vec<AccessibleNodeId>,
51 pub parent: Option<AccessibleNodeId>,
53 pub tab_index: i32,
55 pub level: Option<u8>,
57 pub live: LiveRegion,
59 pub properties: HashMap<String, String>,
61}
62
63impl AccessibleNode {
64 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 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 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 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 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 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 pub fn contains_point(&self, point: Point) -> bool {
154 self.visible && self.bounds.contains_point(&point)
155 }
156
157 pub fn with_name(mut self, name: &str) -> Self {
159 self.name = Some(name.to_string());
160 self
161 }
162
163 pub fn with_description(mut self, desc: &str) -> Self {
165 self.description = Some(desc.to_string());
166 self
167 }
168
169 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 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
187pub struct AccessibleNodeId(pub u64);
188
189impl AccessibleNodeId {
190 pub const fn new(id: u64) -> Self {
192 Self(id)
193 }
194}
195
196#[derive(Debug, Clone, Copy, PartialEq, Eq)]
198pub enum CheckedState {
199 Unchecked,
201 Checked,
203 Mixed,
205}
206
207#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
209pub enum LiveRegion {
210 #[default]
212 Off,
213 Polite,
215 Assertive,
217}
218
219#[derive(Debug, Default)]
221pub struct AccessibilityTree {
222 nodes: HashMap<AccessibleNodeId, AccessibleNode>,
224 root: Option<AccessibleNodeId>,
226 next_id: u64,
228 focus: Option<AccessibleNodeId>,
230 focus_order: Vec<AccessibleNodeId>,
232 focus_order_dirty: bool,
234}
235
236impl AccessibilityTree {
237 pub fn new() -> Self {
239 Self::default()
240 }
241
242 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 pub fn set_root(&mut self, id: AccessibleNodeId) {
251 self.root = Some(id);
252 }
253
254 pub fn root(&self) -> Option<&AccessibleNode> {
256 self.root.and_then(|id| self.nodes.get(&id))
257 }
258
259 pub fn get(&self, id: AccessibleNodeId) -> Option<&AccessibleNode> {
261 self.nodes.get(&id)
262 }
263
264 pub fn get_mut(&mut self, id: AccessibleNodeId) -> Option<&mut AccessibleNode> {
266 self.nodes.get_mut(&id)
267 }
268
269 pub fn insert(&mut self, node: AccessibleNode) {
271 self.focus_order_dirty = true;
272 self.nodes.insert(node.id, node);
273 }
274
275 pub fn remove(&mut self, id: AccessibleNodeId) -> Option<AccessibleNode> {
277 self.focus_order_dirty = true;
278 self.nodes.remove(&id)
279 }
280
281 pub fn len(&self) -> usize {
283 self.nodes.len()
284 }
285
286 pub fn is_empty(&self) -> bool {
288 self.nodes.is_empty()
289 }
290
291 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 pub fn focused(&self) -> Option<&AccessibleNode> {
302 self.focus.and_then(|id| self.nodes.get(&id))
303 }
304
305 pub fn focused_id(&self) -> Option<AccessibleNodeId> {
307 self.focus
308 }
309
310 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 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 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 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 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 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 fn ensure_focus_order(&mut self) {
388 if self.focus_order_dirty {
389 self.compute_focus_order();
390 }
391 }
392
393 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 focusable.sort_by(|a, b| {
404 match (a.1, b.1) {
405 (0, 0) => {
406 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) => std::cmp::Ordering::Less,
413 _ => a.1.cmp(&b.1), }
415 });
416
417 self.focus_order = focusable.into_iter().map(|(id, _, _, _)| id).collect();
418 self.focus_order_dirty = false;
419 }
420
421 pub fn get_focus_order(&mut self) -> &[AccessibleNodeId] {
423 self.ensure_focus_order();
424 &self.focus_order
425 }
426}
427
428#[derive(Debug, Default)]
430pub struct HitTester {
431 tree: AccessibilityTree,
433}
434
435impl HitTester {
436 pub fn new() -> Self {
438 Self::default()
439 }
440
441 pub fn with_tree(tree: AccessibilityTree) -> Self {
443 Self { tree }
444 }
445
446 pub fn tree(&self) -> &AccessibilityTree {
448 &self.tree
449 }
450
451 pub fn tree_mut(&mut self) -> &mut AccessibilityTree {
453 &mut self.tree
454 }
455
456 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 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 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 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 Some(node_id)
488 }
489
490 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 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(); results
504 }
505
506 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 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 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#[derive(Debug)]
547pub struct AccessibilityTreeBuilder {
548 tree: AccessibilityTree,
549 current_parent: Option<AccessibleNodeId>,
550}
551
552impl AccessibilityTreeBuilder {
553 pub fn new() -> Self {
555 Self {
556 tree: AccessibilityTree::new(),
557 current_parent: None,
558 }
559 }
560
561 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 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 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 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 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 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 #[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 let h0 = AccessibleNode::heading(id, "H0", 0, bounds);
711 assert_eq!(h0.level, Some(1));
712
713 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))); 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 #[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 let first = tree.focus_next();
894 assert!(first.is_some());
895
896 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 tree.set_focus(id2);
918
919 let prev = tree.focus_previous();
921 assert_eq!(prev, Some(id1));
922 }
923
924 #[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 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 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 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 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 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 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 let all_hits = tester.hit_test_all(Point::new(100.0, 100.0));
1022 assert_eq!(all_hits.len(), 2);
1023 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 #[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 #[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 #[test]
1151 fn test_live_region_default() {
1152 let region = LiveRegion::default();
1153 assert_eq!(region, LiveRegion::Off);
1154 }
1155
1156 #[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}