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