accesskit_consumer/
node.rs

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