1// Copyright 2021 The AccessKit Authors. All rights reserved.
2// Licensed under the Apache License, Version 2.0 (found in
3// the LICENSE-APACHE file) or the MIT license (found in
4// the LICENSE-MIT file), at your option.
5
6// Derived from Chromium's accessibility abstraction.
7// Copyright 2021 The Chromium Authors. All rights reserved.
8// Use of this source code is governed by a BSD-style license that can be
9// found in the LICENSE.chromium file.
10
11use std::{iter::FusedIterator, ops::Deref};
12
13use accesskit::{
14 Action, Affine, Checked, DefaultActionVerb, Live, Node as NodeData, NodeId, Point, Rect, Role,
15 TextSelection,
16};
17
18use crate::filters::FilterResult;
19use crate::iterators::{
20 FilteredChildren, FollowingFilteredSiblings, FollowingSiblings, LabelledBy,
21 PrecedingFilteredSiblings, PrecedingSiblings,
22};
23use crate::tree::State as TreeState;
24
25#[derive(Clone, Copy, PartialEq, Eq)]
26pub(crate) struct ParentAndIndex(pub(crate) NodeId, pub(crate) usize);
27
28#[derive(Clone)]
29pub struct NodeState {
30 pub(crate) id: NodeId,
31 pub(crate) parent_and_index: Option<ParentAndIndex>,
32 pub(crate) data: NodeData,
33}
34
35#[derive(Copy, Clone)]
36pub struct Node<'a> {
37 pub tree_state: &'a TreeState,
38 pub(crate) state: &'a NodeState,
39}
40
41impl NodeState {
42 pub(crate) fn data(&self) -> &NodeData {
43 &self.data
44 }
45}
46
47impl<'a> Node<'a> {
48 pub fn detached(&self) -> DetachedNode {
49 DetachedNode {
50 state: self.state.clone(),
51 is_focused: self.is_focused(),
52 is_root: self.is_root(),
53 name: self.name(),
54 value: self.value(),
55 live: self.live(),
56 supports_text_ranges: self.supports_text_ranges(),
57 }
58 }
59
60 pub fn is_focused(&self) -> bool {
61 self.tree_state.focus_id() == Some(self.id())
62 }
63}
64
65impl NodeState {
66 pub fn is_focusable(&self) -> bool {
67 self.supports_action(Action::Focus)
68 }
69}
70
71impl<'a> Node<'a> {
72 pub fn is_root(&self) -> bool {
73 // Don't check for absence of a parent node, in case a non-root node
74 // somehow gets detached from the tree.
75 self.id() == self.tree_state.root_id()
76 }
77}
78
79impl NodeState {
80 pub fn parent_id(&self) -> Option<NodeId> {
81 self.parent_and_index
82 .as_ref()
83 .map(|ParentAndIndex(id: &NodeId, _)| *id)
84 }
85}
86
87impl<'a> Node<'a> {
88 pub fn parent(&self) -> Option<Node<'a>> {
89 self.parent_id()
90 .map(|id| self.tree_state.node_by_id(id).unwrap())
91 }
92
93 pub fn filtered_parent(&self, filter: &impl Fn(&Node) -> FilterResult) -> Option<Node<'a>> {
94 self.parent().and_then(move |parent| {
95 if filter(&parent) == FilterResult::Include {
96 Some(parent)
97 } else {
98 parent.filtered_parent(filter)
99 }
100 })
101 }
102
103 pub fn parent_and_index(self) -> Option<(Node<'a>, usize)> {
104 self.state
105 .parent_and_index
106 .as_ref()
107 .map(|ParentAndIndex(parent, index)| {
108 (self.tree_state.node_by_id(*parent).unwrap(), *index)
109 })
110 }
111}
112
113impl NodeState {
114 pub fn child_ids(
115 &self,
116 ) -> impl DoubleEndedIterator<Item = NodeId>
117 + ExactSizeIterator<Item = NodeId>
118 + FusedIterator<Item = NodeId>
119 + '_ {
120 let data: &Node = &self.data;
121 data.children().iter().copied()
122 }
123}
124
125impl<'a> Node<'a> {
126 pub fn children(
127 &self,
128 ) -> impl DoubleEndedIterator<Item = Node<'a>>
129 + ExactSizeIterator<Item = Node<'a>>
130 + FusedIterator<Item = Node<'a>>
131 + 'a {
132 let state = self.tree_state;
133 self.state
134 .child_ids()
135 .map(move |id| state.node_by_id(id).unwrap())
136 }
137
138 pub fn filtered_children(
139 &self,
140 filter: impl Fn(&Node) -> FilterResult + 'a,
141 ) -> impl DoubleEndedIterator<Item = Node<'a>> + FusedIterator<Item = Node<'a>> + 'a {
142 FilteredChildren::new(*self, filter)
143 }
144
145 pub fn following_sibling_ids(
146 &self,
147 ) -> impl DoubleEndedIterator<Item = NodeId>
148 + ExactSizeIterator<Item = NodeId>
149 + FusedIterator<Item = NodeId>
150 + 'a {
151 FollowingSiblings::new(*self)
152 }
153
154 pub fn following_siblings(
155 &self,
156 ) -> impl DoubleEndedIterator<Item = Node<'a>>
157 + ExactSizeIterator<Item = Node<'a>>
158 + FusedIterator<Item = Node<'a>>
159 + 'a {
160 let state = self.tree_state;
161 self.following_sibling_ids()
162 .map(move |id| state.node_by_id(id).unwrap())
163 }
164
165 pub fn following_filtered_siblings(
166 &self,
167 filter: impl Fn(&Node) -> FilterResult + 'a,
168 ) -> impl DoubleEndedIterator<Item = Node<'a>> + FusedIterator<Item = Node<'a>> + 'a {
169 FollowingFilteredSiblings::new(*self, filter)
170 }
171
172 pub fn preceding_sibling_ids(
173 &self,
174 ) -> impl DoubleEndedIterator<Item = NodeId>
175 + ExactSizeIterator<Item = NodeId>
176 + FusedIterator<Item = NodeId>
177 + 'a {
178 PrecedingSiblings::new(*self)
179 }
180
181 pub fn preceding_siblings(
182 &self,
183 ) -> impl DoubleEndedIterator<Item = Node<'a>>
184 + ExactSizeIterator<Item = Node<'a>>
185 + FusedIterator<Item = Node<'a>>
186 + 'a {
187 let state = self.tree_state;
188 self.preceding_sibling_ids()
189 .map(move |id| state.node_by_id(id).unwrap())
190 }
191
192 pub fn preceding_filtered_siblings(
193 &self,
194 filter: impl Fn(&Node) -> FilterResult + 'a,
195 ) -> impl DoubleEndedIterator<Item = Node<'a>> + FusedIterator<Item = Node<'a>> + 'a {
196 PrecedingFilteredSiblings::new(*self, filter)
197 }
198
199 pub fn deepest_first_child(self) -> Option<Node<'a>> {
200 let mut deepest_child = self.children().next()?;
201 while let Some(first_child) = deepest_child.children().next() {
202 deepest_child = first_child;
203 }
204 Some(deepest_child)
205 }
206
207 pub fn deepest_first_filtered_child(
208 &self,
209 filter: &impl Fn(&Node) -> FilterResult,
210 ) -> Option<Node<'a>> {
211 let mut deepest_child = self.first_filtered_child(filter)?;
212 while let Some(first_child) = deepest_child.first_filtered_child(filter) {
213 deepest_child = first_child;
214 }
215 Some(deepest_child)
216 }
217
218 pub fn deepest_last_child(self) -> Option<Node<'a>> {
219 let mut deepest_child = self.children().next_back()?;
220 while let Some(last_child) = deepest_child.children().next_back() {
221 deepest_child = last_child;
222 }
223 Some(deepest_child)
224 }
225
226 pub fn deepest_last_filtered_child(
227 &self,
228 filter: &impl Fn(&Node) -> FilterResult,
229 ) -> Option<Node<'a>> {
230 let mut deepest_child = self.last_filtered_child(filter)?;
231 while let Some(last_child) = deepest_child.last_filtered_child(filter) {
232 deepest_child = last_child;
233 }
234 Some(deepest_child)
235 }
236
237 pub fn is_descendant_of(&self, ancestor: &Node) -> bool {
238 if self.id() == ancestor.id() {
239 return true;
240 }
241 if let Some(parent) = self.parent() {
242 return parent.is_descendant_of(ancestor);
243 }
244 false
245 }
246}
247
248impl NodeState {
249 /// Returns the transform defined directly on this node, or the identity
250 /// transform, without taking into account transforms on ancestors.
251 pub fn direct_transform(&self) -> Affine {
252 self.data()
253 .transform()
254 .map_or(default:Affine::IDENTITY, |value: &Affine| *value)
255 }
256}
257
258impl<'a> Node<'a> {
259 /// Returns the combined affine transform of this node and its ancestors,
260 /// up to and including the root of this node's tree.
261 pub fn transform(&self) -> Affine {
262 self.parent()
263 .map_or(default:Affine::IDENTITY, |parent: Node<'_>| parent.transform())
264 * self.direct_transform()
265 }
266
267 pub(crate) fn relative_transform(&self, stop_at: &Node) -> Affine {
268 let parent_transform: Affine = if let Some(parent: Node<'_>) = self.parent() {
269 if parent.id() == stop_at.id() {
270 Affine::IDENTITY
271 } else {
272 parent.relative_transform(stop_at)
273 }
274 } else {
275 Affine::IDENTITY
276 };
277 parent_transform * self.direct_transform()
278 }
279}
280
281impl NodeState {
282 pub fn raw_bounds(&self) -> Option<Rect> {
283 self.data().bounds()
284 }
285}
286
287impl<'a> Node<'a> {
288 pub fn has_bounds(&self) -> bool {
289 self.state.raw_bounds().is_some()
290 }
291
292 /// Returns the node's transformed bounding box relative to the tree's
293 /// container (e.g. window).
294 pub fn bounding_box(&self) -> Option<Rect> {
295 self.state
296 .raw_bounds()
297 .as_ref()
298 .map(|rect| self.transform().transform_rect_bbox(*rect))
299 }
300
301 pub(crate) fn bounding_box_in_coordinate_space(&self, other: &Node) -> Option<Rect> {
302 self.state
303 .raw_bounds()
304 .as_ref()
305 .map(|rect| self.relative_transform(other).transform_rect_bbox(*rect))
306 }
307
308 pub(crate) fn hit_test(
309 &self,
310 point: Point,
311 filter: &impl Fn(&Node) -> FilterResult,
312 ) -> Option<(Node<'a>, Point)> {
313 let filter_result = filter(self);
314
315 if filter_result == FilterResult::ExcludeSubtree {
316 return None;
317 }
318
319 for child in self.children().rev() {
320 let point = child.direct_transform().inverse() * point;
321 if let Some(result) = child.hit_test(point, filter) {
322 return Some(result);
323 }
324 }
325
326 if filter_result == FilterResult::Include {
327 if let Some(rect) = &self.state.raw_bounds() {
328 if rect.contains(point) {
329 return Some((*self, point));
330 }
331 }
332 }
333
334 None
335 }
336
337 /// Returns the deepest filtered node, either this node or a descendant,
338 /// at the given point in this node's coordinate space.
339 pub fn node_at_point(
340 &self,
341 point: Point,
342 filter: &impl Fn(&Node) -> FilterResult,
343 ) -> Option<Node<'a>> {
344 self.hit_test(point, filter).map(|(node, _)| node)
345 }
346}
347
348impl NodeState {
349 pub fn id(&self) -> NodeId {
350 self.id
351 }
352
353 pub fn role(&self) -> Role {
354 self.data().role()
355 }
356
357 pub fn role_description(&self) -> Option<String> {
358 self.data().role_description().map(String::from)
359 }
360
361 pub fn has_role_description(&self) -> bool {
362 self.data().role_description().is_some()
363 }
364
365 pub fn is_hidden(&self) -> bool {
366 self.data().is_hidden()
367 }
368
369 pub fn is_disabled(&self) -> bool {
370 self.data().is_disabled()
371 }
372
373 pub fn is_read_only(&self) -> bool {
374 let data = self.data();
375 if data.is_read_only() {
376 true
377 } else {
378 self.should_have_read_only_state_by_default() || !self.is_read_only_supported()
379 }
380 }
381
382 pub fn is_read_only_or_disabled(&self) -> bool {
383 self.is_read_only() || self.is_disabled()
384 }
385
386 pub fn checked(&self) -> Option<Checked> {
387 self.data().checked()
388 }
389
390 pub fn numeric_value(&self) -> Option<f64> {
391 self.data().numeric_value()
392 }
393
394 pub fn min_numeric_value(&self) -> Option<f64> {
395 self.data().min_numeric_value()
396 }
397
398 pub fn max_numeric_value(&self) -> Option<f64> {
399 self.data().max_numeric_value()
400 }
401
402 pub fn numeric_value_step(&self) -> Option<f64> {
403 self.data().numeric_value_step()
404 }
405
406 pub fn numeric_value_jump(&self) -> Option<f64> {
407 self.data().numeric_value_jump()
408 }
409
410 pub fn is_text_input(&self) -> bool {
411 matches!(
412 self.role(),
413 Role::TextInput
414 | Role::MultilineTextInput
415 | Role::SearchInput
416 | Role::DateInput
417 | Role::DateTimeInput
418 | Role::WeekInput
419 | Role::MonthInput
420 | Role::TimeInput
421 | Role::EmailInput
422 | Role::NumberInput
423 | Role::PasswordInput
424 | Role::PhoneNumberInput
425 | Role::UrlInput
426 | Role::EditableComboBox
427 | Role::SpinButton
428 )
429 }
430
431 pub fn is_multiline(&self) -> bool {
432 self.role() == Role::MultilineTextInput
433 }
434
435 pub fn default_action_verb(&self) -> Option<DefaultActionVerb> {
436 self.data().default_action_verb()
437 }
438
439 // When probing for supported actions as the next several functions do,
440 // it's tempting to check the role. But it's better to not assume anything
441 // beyond what the provider has explicitly told us. Rationale:
442 // if the provider developer forgot to correctly set `default_action_verb`,
443 // an AT (or even AccessKit itself) can fall back to simulating
444 // a mouse click. But if the provider doesn't handle an action request
445 // and we assume that it will based on the role, the attempted action
446 // does nothing. This stance is a departure from Chromium.
447
448 pub fn is_clickable(&self) -> bool {
449 // If it has a custom default action verb except for
450 // `DefaultActionVerb::ClickAncestor`, it's definitely clickable.
451 // `DefaultActionVerb::ClickAncestor` is used when a node with a
452 // click listener is present in its ancestry chain.
453 if let Some(verb) = self.default_action_verb() {
454 if verb != DefaultActionVerb::ClickAncestor {
455 return true;
456 }
457 }
458
459 false
460 }
461
462 pub fn supports_toggle(&self) -> bool {
463 self.checked().is_some()
464 }
465
466 pub fn supports_expand_collapse(&self) -> bool {
467 self.data().is_expanded().is_some()
468 }
469
470 pub fn is_invocable(&self) -> bool {
471 // A control is "invocable" if it initiates an action when activated but
472 // does not maintain any state. A control that maintains state
473 // when activated would be considered a toggle or expand-collapse
474 // control - these controls are "clickable" but not "invocable".
475 // Similarly, if the action only gives the control keyboard focus,
476 // such as when clicking a text input, the control is not considered
477 // "invocable", as the "invoke" action would be a redundant synonym
478 // for the "set focus" action. The same logic applies to selection.
479 self.is_clickable()
480 && !matches!(
481 self.default_action_verb(),
482 Some(
483 DefaultActionVerb::Focus
484 | DefaultActionVerb::Select
485 | DefaultActionVerb::Unselect
486 )
487 )
488 && !self.supports_toggle()
489 && !self.supports_expand_collapse()
490 }
491
492 // The future of the `Action` enum is undecided, so keep the following
493 // function private for now.
494 fn supports_action(&self, action: Action) -> bool {
495 self.data().supports_action(action)
496 }
497
498 pub fn supports_increment(&self) -> bool {
499 self.supports_action(Action::Increment)
500 }
501
502 pub fn supports_decrement(&self) -> bool {
503 self.supports_action(Action::Decrement)
504 }
505}
506
507fn descendant_label_filter(node: &Node) -> FilterResult {
508 match node.role() {
509 Role::StaticText | Role::Image => FilterResult::Include,
510 Role::GenericContainer => FilterResult::ExcludeNode,
511 _ => FilterResult::ExcludeSubtree,
512 }
513}
514
515impl<'a> Node<'a> {
516 pub fn labelled_by(
517 &self,
518 ) -> impl DoubleEndedIterator<Item = Node<'a>> + FusedIterator<Item = Node<'a>> + 'a {
519 let explicit = &self.state.data.labelled_by();
520 if explicit.is_empty()
521 && matches!(self.role(), Role::Button | Role::DefaultButton | Role::Link)
522 {
523 LabelledBy::FromDescendants(FilteredChildren::new(*self, &descendant_label_filter))
524 } else {
525 LabelledBy::Explicit {
526 ids: explicit.iter(),
527 tree_state: self.tree_state,
528 }
529 }
530 }
531
532 pub fn name(&self) -> Option<String> {
533 if let Some(name) = &self.data().name() {
534 Some(name.to_string())
535 } else {
536 let names = self
537 .labelled_by()
538 .filter_map(|node| node.name())
539 .collect::<Vec<String>>();
540 (!names.is_empty()).then(move || names.join(" "))
541 }
542 }
543
544 pub fn value(&self) -> Option<String> {
545 if let Some(value) = &self.data().value() {
546 Some(value.to_string())
547 } else if self.supports_text_ranges() && !self.is_multiline() {
548 Some(self.document_range().text())
549 } else {
550 None
551 }
552 }
553
554 pub fn has_value(&self) -> bool {
555 self.data().value().is_some() || (self.supports_text_ranges() && !self.is_multiline())
556 }
557}
558
559impl NodeState {
560 pub fn is_read_only_supported(&self) -> bool {
561 self.is_text_input()
562 || matches!(
563 self.role(),
564 Role::CheckBox
565 | Role::ColorWell
566 | Role::ComboBox
567 | Role::Grid
568 | Role::ListBox
569 | Role::MenuItemCheckBox
570 | Role::MenuItemRadio
571 | Role::MenuListPopup
572 | Role::RadioButton
573 | Role::RadioGroup
574 | Role::Slider
575 | Role::Switch
576 | Role::ToggleButton
577 | Role::TreeGrid
578 )
579 }
580
581 pub fn should_have_read_only_state_by_default(&self) -> bool {
582 matches!(
583 self.role(),
584 Role::Article
585 | Role::Definition
586 | Role::DescriptionList
587 | Role::DescriptionListTerm
588 | Role::Directory
589 | Role::Document
590 | Role::GraphicsDocument
591 | Role::Image
592 | Role::List
593 | Role::ListItem
594 | Role::PdfRoot
595 | Role::ProgressIndicator
596 | Role::RootWebArea
597 | Role::Term
598 | Role::Timer
599 | Role::Toolbar
600 | Role::Tooltip
601 )
602 }
603}
604
605impl<'a> Node<'a> {
606 pub fn live(&self) -> Live {
607 self.data()
608 .live()
609 .unwrap_or_else(|| self.parent().map_or(default:Live::Off, |parent: Node<'_>| parent.live()))
610 }
611}
612
613impl NodeState {
614 pub fn is_selected(&self) -> Option<bool> {
615 self.data().is_selected()
616 }
617
618 pub fn raw_text_selection(&self) -> Option<&TextSelection> {
619 self.data().text_selection()
620 }
621
622 pub fn raw_value(&self) -> Option<&str> {
623 self.data().value()
624 }
625}
626
627impl<'a> Node<'a> {
628 pub fn index_path(&self) -> Vec<usize> {
629 self.relative_index_path(self.tree_state.root_id())
630 }
631
632 pub fn relative_index_path(&self, ancestor_id: NodeId) -> Vec<usize> {
633 let mut result = Vec::new();
634 let mut current = *self;
635 while current.id() != ancestor_id {
636 let (parent, index) = current.parent_and_index().unwrap();
637 result.push(index);
638 current = parent;
639 }
640 result.reverse();
641 result
642 }
643
644 pub(crate) fn first_filtered_child(
645 &self,
646 filter: &impl Fn(&Node) -> FilterResult,
647 ) -> Option<Node<'a>> {
648 for child in self.children() {
649 let result = filter(&child);
650 if result == FilterResult::Include {
651 return Some(child);
652 }
653 if result == FilterResult::ExcludeNode {
654 if let Some(descendant) = child.first_filtered_child(filter) {
655 return Some(descendant);
656 }
657 }
658 }
659 None
660 }
661
662 pub(crate) fn last_filtered_child(
663 &self,
664 filter: &impl Fn(&Node) -> FilterResult,
665 ) -> Option<Node<'a>> {
666 for child in self.children().rev() {
667 let result = filter(&child);
668 if result == FilterResult::Include {
669 return Some(child);
670 }
671 if result == FilterResult::ExcludeNode {
672 if let Some(descendant) = child.last_filtered_child(filter) {
673 return Some(descendant);
674 }
675 }
676 }
677 None
678 }
679
680 pub fn state(&self) -> &'a NodeState {
681 self.state
682 }
683}
684
685impl<'a> Deref for Node<'a> {
686 type Target = NodeState;
687
688 fn deref(&self) -> &NodeState {
689 self.state
690 }
691}
692
693#[derive(Clone)]
694pub struct DetachedNode {
695 pub(crate) state: NodeState,
696 pub(crate) is_focused: bool,
697 pub(crate) is_root: bool,
698 pub(crate) name: Option<String>,
699 pub(crate) value: Option<String>,
700 pub(crate) live: Live,
701 pub(crate) supports_text_ranges: bool,
702}
703
704impl DetachedNode {
705 pub fn is_focused(&self) -> bool {
706 self.is_focused
707 }
708
709 pub fn is_root(&self) -> bool {
710 self.is_root
711 }
712
713 pub fn name(&self) -> Option<String> {
714 self.name.clone()
715 }
716
717 pub fn value(&self) -> Option<String> {
718 self.value.clone()
719 }
720
721 pub fn has_value(&self) -> bool {
722 self.value.is_some()
723 }
724
725 pub fn live(&self) -> Live {
726 self.live
727 }
728
729 pub fn supports_text_ranges(&self) -> bool {
730 self.supports_text_ranges
731 }
732
733 pub fn state(&self) -> &NodeState {
734 &self.state
735 }
736}
737
738impl Deref for DetachedNode {
739 type Target = NodeState;
740
741 fn deref(&self) -> &NodeState {
742 &self.state
743 }
744}
745
746#[cfg(test)]
747mod tests {
748 use accesskit::{NodeBuilder, NodeClassSet, NodeId, Point, Rect, Role, Tree, TreeUpdate};
749
750 use crate::tests::*;
751
752 #[test]
753 fn parent_and_index() {
754 let tree = test_tree();
755 assert!(tree.state().root().parent_and_index().is_none());
756 assert_eq!(
757 Some((ROOT_ID, 0)),
758 tree.state()
759 .node_by_id(PARAGRAPH_0_ID)
760 .unwrap()
761 .parent_and_index()
762 .map(|(parent, index)| (parent.id(), index))
763 );
764 assert_eq!(
765 Some((PARAGRAPH_0_ID, 0)),
766 tree.state()
767 .node_by_id(STATIC_TEXT_0_0_IGNORED_ID)
768 .unwrap()
769 .parent_and_index()
770 .map(|(parent, index)| (parent.id(), index))
771 );
772 assert_eq!(
773 Some((ROOT_ID, 1)),
774 tree.state()
775 .node_by_id(PARAGRAPH_1_IGNORED_ID)
776 .unwrap()
777 .parent_and_index()
778 .map(|(parent, index)| (parent.id(), index))
779 );
780 }
781
782 #[test]
783 fn deepest_first_child() {
784 let tree = test_tree();
785 assert_eq!(
786 STATIC_TEXT_0_0_IGNORED_ID,
787 tree.state().root().deepest_first_child().unwrap().id()
788 );
789 assert_eq!(
790 STATIC_TEXT_0_0_IGNORED_ID,
791 tree.state()
792 .node_by_id(PARAGRAPH_0_ID)
793 .unwrap()
794 .deepest_first_child()
795 .unwrap()
796 .id()
797 );
798 assert!(tree
799 .state()
800 .node_by_id(STATIC_TEXT_0_0_IGNORED_ID)
801 .unwrap()
802 .deepest_first_child()
803 .is_none());
804 }
805
806 #[test]
807 fn filtered_parent() {
808 let tree = test_tree();
809 assert_eq!(
810 ROOT_ID,
811 tree.state()
812 .node_by_id(STATIC_TEXT_1_0_ID)
813 .unwrap()
814 .filtered_parent(&test_tree_filter)
815 .unwrap()
816 .id()
817 );
818 assert!(tree
819 .state()
820 .root()
821 .filtered_parent(&test_tree_filter)
822 .is_none());
823 }
824
825 #[test]
826 fn deepest_first_filtered_child() {
827 let tree = test_tree();
828 assert_eq!(
829 PARAGRAPH_0_ID,
830 tree.state()
831 .root()
832 .deepest_first_filtered_child(&test_tree_filter)
833 .unwrap()
834 .id()
835 );
836 assert!(tree
837 .state()
838 .node_by_id(PARAGRAPH_0_ID)
839 .unwrap()
840 .deepest_first_filtered_child(&test_tree_filter)
841 .is_none());
842 assert!(tree
843 .state()
844 .node_by_id(STATIC_TEXT_0_0_IGNORED_ID)
845 .unwrap()
846 .deepest_first_filtered_child(&test_tree_filter)
847 .is_none());
848 }
849
850 #[test]
851 fn deepest_last_child() {
852 let tree = test_tree();
853 assert_eq!(
854 EMPTY_CONTAINER_3_3_IGNORED_ID,
855 tree.state().root().deepest_last_child().unwrap().id()
856 );
857 assert_eq!(
858 EMPTY_CONTAINER_3_3_IGNORED_ID,
859 tree.state()
860 .node_by_id(PARAGRAPH_3_IGNORED_ID)
861 .unwrap()
862 .deepest_last_child()
863 .unwrap()
864 .id()
865 );
866 assert!(tree
867 .state()
868 .node_by_id(BUTTON_3_2_ID)
869 .unwrap()
870 .deepest_last_child()
871 .is_none());
872 }
873
874 #[test]
875 fn deepest_last_filtered_child() {
876 let tree = test_tree();
877 assert_eq!(
878 BUTTON_3_2_ID,
879 tree.state()
880 .root()
881 .deepest_last_filtered_child(&test_tree_filter)
882 .unwrap()
883 .id()
884 );
885 assert_eq!(
886 BUTTON_3_2_ID,
887 tree.state()
888 .node_by_id(PARAGRAPH_3_IGNORED_ID)
889 .unwrap()
890 .deepest_last_filtered_child(&test_tree_filter)
891 .unwrap()
892 .id()
893 );
894 assert!(tree
895 .state()
896 .node_by_id(BUTTON_3_2_ID)
897 .unwrap()
898 .deepest_last_filtered_child(&test_tree_filter)
899 .is_none());
900 assert!(tree
901 .state()
902 .node_by_id(PARAGRAPH_0_ID)
903 .unwrap()
904 .deepest_last_filtered_child(&test_tree_filter)
905 .is_none());
906 }
907
908 #[test]
909 fn is_descendant_of() {
910 let tree = test_tree();
911 assert!(tree
912 .state()
913 .node_by_id(PARAGRAPH_0_ID)
914 .unwrap()
915 .is_descendant_of(&tree.state().root()));
916 assert!(tree
917 .state()
918 .node_by_id(STATIC_TEXT_0_0_IGNORED_ID)
919 .unwrap()
920 .is_descendant_of(&tree.state().root()));
921 assert!(tree
922 .state()
923 .node_by_id(STATIC_TEXT_0_0_IGNORED_ID)
924 .unwrap()
925 .is_descendant_of(&tree.state().node_by_id(PARAGRAPH_0_ID).unwrap()));
926 assert!(!tree
927 .state()
928 .node_by_id(STATIC_TEXT_0_0_IGNORED_ID)
929 .unwrap()
930 .is_descendant_of(&tree.state().node_by_id(PARAGRAPH_2_ID).unwrap()));
931 assert!(!tree
932 .state()
933 .node_by_id(PARAGRAPH_0_ID)
934 .unwrap()
935 .is_descendant_of(&tree.state().node_by_id(PARAGRAPH_2_ID).unwrap()));
936 }
937
938 #[test]
939 fn is_root() {
940 let tree = test_tree();
941 assert!(tree.state().node_by_id(ROOT_ID).unwrap().is_root());
942 assert!(!tree.state().node_by_id(PARAGRAPH_0_ID).unwrap().is_root());
943 }
944
945 #[test]
946 fn bounding_box() {
947 let tree = test_tree();
948 assert!(tree
949 .state()
950 .node_by_id(ROOT_ID)
951 .unwrap()
952 .bounding_box()
953 .is_none());
954 assert_eq!(
955 Some(Rect {
956 x0: 10.0,
957 y0: 40.0,
958 x1: 810.0,
959 y1: 80.0,
960 }),
961 tree.state()
962 .node_by_id(PARAGRAPH_1_IGNORED_ID)
963 .unwrap()
964 .bounding_box()
965 );
966 assert_eq!(
967 Some(Rect {
968 x0: 20.0,
969 y0: 50.0,
970 x1: 100.0,
971 y1: 70.0,
972 }),
973 tree.state()
974 .node_by_id(STATIC_TEXT_1_0_ID)
975 .unwrap()
976 .bounding_box()
977 );
978 }
979
980 #[test]
981 fn node_at_point() {
982 let tree = test_tree();
983 assert!(tree
984 .state()
985 .root()
986 .node_at_point(Point::new(10.0, 40.0), &test_tree_filter)
987 .is_none());
988 assert_eq!(
989 Some(STATIC_TEXT_1_0_ID),
990 tree.state()
991 .root()
992 .node_at_point(Point::new(20.0, 50.0), &test_tree_filter)
993 .map(|node| node.id())
994 );
995 assert_eq!(
996 Some(STATIC_TEXT_1_0_ID),
997 tree.state()
998 .root()
999 .node_at_point(Point::new(50.0, 60.0), &test_tree_filter)
1000 .map(|node| node.id())
1001 );
1002 assert!(tree
1003 .state()
1004 .root()
1005 .node_at_point(Point::new(100.0, 70.0), &test_tree_filter)
1006 .is_none());
1007 }
1008
1009 #[test]
1010 fn no_name_or_labelled_by() {
1011 let mut classes = NodeClassSet::new();
1012 let update = TreeUpdate {
1013 nodes: vec![
1014 (NodeId(0), {
1015 let mut builder = NodeBuilder::new(Role::Window);
1016 builder.set_children(vec![NodeId(1)]);
1017 builder.build(&mut classes)
1018 }),
1019 (
1020 NodeId(1),
1021 NodeBuilder::new(Role::Button).build(&mut classes),
1022 ),
1023 ],
1024 tree: Some(Tree::new(NodeId(0))),
1025 focus: NodeId(0),
1026 };
1027 let tree = crate::Tree::new(update, false);
1028 assert_eq!(None, tree.state().node_by_id(NodeId(1)).unwrap().name());
1029 }
1030
1031 #[test]
1032 fn name_from_labelled_by() {
1033 // The following mock UI probably isn't very localization-friendly,
1034 // but it's good for this test.
1035 const LABEL_1: &str = "Check email every";
1036 const LABEL_2: &str = "minutes";
1037
1038 let mut classes = NodeClassSet::new();
1039 let update = TreeUpdate {
1040 nodes: vec![
1041 (NodeId(0), {
1042 let mut builder = NodeBuilder::new(Role::Window);
1043 builder.set_children(vec![NodeId(1), NodeId(2), NodeId(3), NodeId(4)]);
1044 builder.build(&mut classes)
1045 }),
1046 (NodeId(1), {
1047 let mut builder = NodeBuilder::new(Role::CheckBox);
1048 builder.set_labelled_by(vec![NodeId(2), NodeId(4)]);
1049 builder.build(&mut classes)
1050 }),
1051 (NodeId(2), {
1052 let mut builder = NodeBuilder::new(Role::StaticText);
1053 builder.set_name(LABEL_1);
1054 builder.build(&mut classes)
1055 }),
1056 (NodeId(3), {
1057 let mut builder = NodeBuilder::new(Role::TextInput);
1058 builder.push_labelled_by(NodeId(4));
1059 builder.build(&mut classes)
1060 }),
1061 (NodeId(4), {
1062 let mut builder = NodeBuilder::new(Role::StaticText);
1063 builder.set_name(LABEL_2);
1064 builder.build(&mut classes)
1065 }),
1066 ],
1067 tree: Some(Tree::new(NodeId(0))),
1068 focus: NodeId(0),
1069 };
1070 let tree = crate::Tree::new(update, false);
1071 assert_eq!(
1072 Some([LABEL_1, LABEL_2].join(" ")),
1073 tree.state().node_by_id(NodeId(1)).unwrap().name()
1074 );
1075 assert_eq!(
1076 Some(LABEL_2.into()),
1077 tree.state().node_by_id(NodeId(3)).unwrap().name()
1078 );
1079 }
1080
1081 #[test]
1082 fn name_from_descendant_label() {
1083 const BUTTON_LABEL: &str = "Play";
1084 const LINK_LABEL: &str = "Watch in browser";
1085
1086 let mut classes = NodeClassSet::new();
1087 let update = TreeUpdate {
1088 nodes: vec![
1089 (NodeId(0), {
1090 let mut builder = NodeBuilder::new(Role::Window);
1091 builder.set_children(vec![NodeId(1), NodeId(3)]);
1092 builder.build(&mut classes)
1093 }),
1094 (NodeId(1), {
1095 let mut builder = NodeBuilder::new(Role::Button);
1096 builder.push_child(NodeId(2));
1097 builder.build(&mut classes)
1098 }),
1099 (NodeId(2), {
1100 let mut builder = NodeBuilder::new(Role::Image);
1101 builder.set_name(BUTTON_LABEL);
1102 builder.build(&mut classes)
1103 }),
1104 (NodeId(3), {
1105 let mut builder = NodeBuilder::new(Role::Link);
1106 builder.push_child(NodeId(4));
1107 builder.build(&mut classes)
1108 }),
1109 (NodeId(4), {
1110 let mut builder = NodeBuilder::new(Role::GenericContainer);
1111 builder.push_child(NodeId(5));
1112 builder.build(&mut classes)
1113 }),
1114 (NodeId(5), {
1115 let mut builder = NodeBuilder::new(Role::StaticText);
1116 builder.set_name(LINK_LABEL);
1117 builder.build(&mut classes)
1118 }),
1119 ],
1120 tree: Some(Tree::new(NodeId(0))),
1121 focus: NodeId(0),
1122 };
1123 let tree = crate::Tree::new(update, false);
1124 assert_eq!(
1125 Some(BUTTON_LABEL.into()),
1126 tree.state().node_by_id(NodeId(1)).unwrap().name()
1127 );
1128 assert_eq!(
1129 Some(LINK_LABEL.into()),
1130 tree.state().node_by_id(NodeId(3)).unwrap().name()
1131 );
1132 }
1133}
1134