From 61f1a3b35f825ad6d25d49c845550da22aa6d0de Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Sat, 5 Jul 2025 08:35:50 -0500 Subject: [PATCH 1/3] feat: Let parents declare actions supported for their children --- common/src/lib.rs | 34 +++++++++++++++++++++++++- consumer/src/node.rs | 34 +++++++++++++++++--------- platforms/android/src/adapter.rs | 12 ++++++--- platforms/android/src/node.rs | 39 +++++++++++++++++++++++------- platforms/atspi-common/src/node.rs | 18 ++++++++------ platforms/macos/src/node.rs | 24 +++++++++--------- platforms/unix/Cargo.toml | 1 + platforms/windows/src/node.rs | 4 +-- platforms/winit/Cargo.toml | 1 + 9 files changed, 120 insertions(+), 47 deletions(-) diff --git a/common/src/lib.rs b/common/src/lib.rs index 888c1a657..c78c0726d 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -915,6 +915,7 @@ struct Properties { pub struct Node { role: Role, actions: u32, + child_actions: u32, flags: u32, properties: Properties, } @@ -1600,6 +1601,31 @@ impl Node { pub fn clear_actions(&mut self) { self.actions = 0; } + + /// Return whether the specified action is in the set supported on this node's + /// direct children in the filtered tree. + #[inline] + pub fn child_supports_action(&self, action: Action) -> bool { + (self.child_actions & action.mask()) != 0 + } + /// Add the specified action to the set supported on this node's direct + /// children in the filtered tree. + #[inline] + pub fn add_child_action(&mut self, action: Action) { + self.child_actions |= action.mask(); + } + /// Remove the specified action from the set supported on this node's direct + /// children in the filtered tree. + #[inline] + pub fn remove_child_action(&mut self, action: Action) { + self.child_actions &= !(action.mask()); + } + /// Clear the set of actions supported on this node's direct children in the + /// filtered tree. + #[inline] + pub fn clear_child_actions(&mut self) { + self.child_actions = 0; + } } flag_methods! { @@ -2141,6 +2167,11 @@ impl fmt::Debug for Node { fmt.field("actions", &supported_actions); } + let child_supported_actions = action_mask_to_action_vec(self.child_actions); + if !child_supported_actions.is_empty() { + fmt.field("child_actions", &child_supported_actions); + } + self.debug_flag_properties(&mut fmt); self.debug_node_id_vec_properties(&mut fmt); self.debug_node_id_properties(&mut fmt); @@ -2887,6 +2918,7 @@ mod tests { let mut node = Node::new(Role::Unknown); node.add_action(Action::Click); node.add_action(Action::Focus); + node.add_child_action(Action::ScrollIntoView); node.set_hidden(); node.set_multiselectable(); node.set_children([NodeId(0), NodeId(1)]); @@ -2898,7 +2930,7 @@ mod tests { assert_eq!( &format!("{node:?}"), - r#"Node { role: Unknown, actions: [Click, Focus], is_hidden: true, is_multiselectable: true, children: [#0, #1], active_descendant: #2, custom_actions: [CustomAction { id: 0, description: "test action" }] }"# + r#"Node { role: Unknown, actions: [Click, Focus], child_actions: [ScrollIntoView], is_hidden: true, is_multiselectable: true, children: [#0, #1], active_descendant: #2, custom_actions: [CustomAction { id: 0, description: "test action" }] }"# ); } diff --git a/consumer/src/node.rs b/consumer/src/node.rs index 5cb05185d..d989abe12 100644 --- a/consumer/src/node.rs +++ b/consumer/src/node.rs @@ -54,8 +54,8 @@ impl<'a> Node<'a> { self.tree_state.focus == self.id() } - pub fn is_focusable(&self) -> bool { - self.supports_action(Action::Focus) || self.is_focused_in_tree() + pub fn is_focusable(&self, parent_filter: &impl Fn(&Node) -> FilterResult) -> bool { + self.supports_action(Action::Focus, parent_filter) || self.is_focused_in_tree() } pub fn is_root(&self) -> bool { @@ -452,8 +452,8 @@ impl<'a> Node<'a> { // and we assume that it will based on the role, the attempted action // does nothing. This stance is a departure from Chromium. - pub fn is_clickable(&self) -> bool { - self.supports_action(Action::Click) + pub fn is_clickable(&self, parent_filter: &impl Fn(&Node) -> FilterResult) -> bool { + self.supports_action(Action::Click, parent_filter) } pub fn is_selectable(&self) -> bool { @@ -491,7 +491,7 @@ impl<'a> Node<'a> { self.data().is_expanded().is_some() } - pub fn is_invocable(&self) -> bool { + pub fn is_invocable(&self, parent_filter: &impl Fn(&Node) -> FilterResult) -> bool { // A control is "invocable" if it initiates an action when activated but // does not maintain any state. A control that maintains state // when activated would be considered a toggle or expand-collapse @@ -500,7 +500,7 @@ impl<'a> Node<'a> { // such as when clicking a text input, the control is not considered // "invocable", as the "invoke" action would be a redundant synonym // for the "set focus" action. The same logic applies to selection. - self.is_clickable() + self.is_clickable(parent_filter) && !self.is_text_input() && !matches!(self.role(), Role::Document | Role::Terminal) && !self.supports_toggle() @@ -508,16 +508,26 @@ impl<'a> Node<'a> { && self.is_selected().is_none() } - pub fn supports_action(&self, action: Action) -> bool { - self.data().supports_action(action) + pub fn supports_action( + &self, + action: Action, + parent_filter: &impl Fn(&Node) -> FilterResult, + ) -> bool { + if self.data().supports_action(action) { + return true; + } + if let Some(parent) = self.filtered_parent(parent_filter) { + return parent.data().child_supports_action(action); + } + false } - pub fn supports_increment(&self) -> bool { - self.supports_action(Action::Increment) + pub fn supports_increment(&self, parent_filter: &impl Fn(&Node) -> FilterResult) -> bool { + self.supports_action(Action::Increment, parent_filter) } - pub fn supports_decrement(&self) -> bool { - self.supports_action(Action::Decrement) + pub fn supports_decrement(&self, parent_filter: &impl Fn(&Node) -> FilterResult) -> bool { + self.supports_action(Action::Decrement, parent_filter) } } diff --git a/platforms/android/src/adapter.rs b/platforms/android/src/adapter.rs index 41555259c..6bca14eb5 100644 --- a/platforms/android/src/adapter.rs +++ b/platforms/android/src/adapter.rs @@ -387,7 +387,10 @@ impl Adapter { ACTION_CLICK => ActionRequest { action: { let node = tree_state.node_by_id(target).unwrap(); - if node.is_focusable() && !node.is_focused() && !node.is_clickable() { + if node.is_focusable(&filter) + && !node.is_focused() + && !node.is_clickable(&filter) + { Action::Focus } else { Action::Click @@ -422,12 +425,13 @@ impl Adapter { } } } else if action == ACTION_SCROLL_BACKWARD { - if node.supports_action(Action::ScrollUp) { + if node.supports_action(Action::ScrollUp, &filter) { Action::ScrollUp } else { Action::ScrollLeft } - } else if node.supports_action(Action::ScrollDown) { + } else if node.supports_action(Action::ScrollDown, &filter) + { Action::ScrollDown } else { Action::ScrollRight @@ -492,7 +496,7 @@ impl Adapter { // TalkBack expects the text selection change to take effect // immediately, so we optimistically update the node. // But don't be *too* optimistic. - if !node.supports_action(Action::SetTextSelection) { + if !node.supports_action(Action::SetTextSelection, &filter) { return None; } let (anchor, focus, extra) = selection_factory(&node)?; diff --git a/platforms/android/src/node.rs b/platforms/android/src/node.rs index be4766c03..1082955bc 100644 --- a/platforms/android/src/node.rs +++ b/platforms/android/src/node.rs @@ -12,7 +12,10 @@ use accesskit::{Action, Live, Role, Toggled}; use accesskit_consumer::Node; use jni::{objects::JObject, sys::jint, JNIEnv}; -use crate::{filters::filter, util::*}; +use crate::{ + filters::filter, + util::*, +}; pub(crate) fn add_action(env: &mut JNIEnv, node_info: &JObject, action: jint) { // Note: We're using the deprecated addAction signature. @@ -36,7 +39,7 @@ impl NodeWrapper<'_> { } fn is_focusable(&self) -> bool { - self.0.is_focusable() && self.0.role() != Role::ScrollView + self.0.is_focusable(&filter) && self.0.role() != Role::ScrollView } fn is_focused(&self) -> bool { @@ -60,10 +63,17 @@ impl NodeWrapper<'_> { } fn is_scrollable(&self) -> bool { - self.0.supports_action(Action::ScrollDown) - || self.0.supports_action(Action::ScrollLeft) - || self.0.supports_action(Action::ScrollRight) - || self.0.supports_action(Action::ScrollUp) + self.0 + .supports_action(Action::ScrollDown, &filter) + || self + .0 + .supports_action(Action::ScrollLeft, &filter) + || self + .0 + .supports_action(Action::ScrollRight, &filter) + || self + .0 + .supports_action(Action::ScrollUp, &filter) } fn is_selected(&self) -> bool { @@ -331,7 +341,7 @@ impl NodeWrapper<'_> { .unwrap(); let can_focus = self.is_focusable() && !self.0.is_focused(); - if self.0.is_clickable() || can_focus { + if self.0.is_clickable(&filter) || can_focus { add_action(env, node_info, ACTION_CLICK); } if can_focus { @@ -353,10 +363,21 @@ impl NodeWrapper<'_> { ) .unwrap(); } - if self.0.supports_action(Action::ScrollLeft) || self.0.supports_action(Action::ScrollUp) { + if self + .0 + .supports_action(Action::ScrollLeft, &filter) + || self + .0 + .supports_action(Action::ScrollUp, &filter) + { add_action(env, node_info, ACTION_SCROLL_BACKWARD); } - if self.0.supports_action(Action::ScrollRight) || self.0.supports_action(Action::ScrollDown) + if self + .0 + .supports_action(Action::ScrollRight, &filter) + || self + .0 + .supports_action(Action::ScrollDown, &filter) { add_action(env, node_info, ACTION_SCROLL_FORWARD); } diff --git a/platforms/atspi-common/src/node.rs b/platforms/atspi-common/src/node.rs index 2e47aa23e..afc7bbb87 100644 --- a/platforms/atspi-common/src/node.rs +++ b/platforms/atspi-common/src/node.rs @@ -291,7 +291,7 @@ impl NodeWrapper<'_> { atspi_state.insert(State::Editable); } // TODO: Focus and selection. - if state.is_focusable() { + if state.is_focusable(&filter) { atspi_state.insert(State::Focusable); } let filter_result = filter(self.0); @@ -392,7 +392,7 @@ impl NodeWrapper<'_> { } fn supports_action(&self) -> bool { - self.0.is_clickable() + self.0.is_clickable(&filter) } fn supports_component(&self) -> bool { @@ -441,7 +441,7 @@ impl NodeWrapper<'_> { } fn n_actions(&self) -> i32 { - if self.0.is_clickable() { + if self.0.is_clickable(&filter) { 1 } else { 0 @@ -452,7 +452,11 @@ impl NodeWrapper<'_> { if index != 0 { return String::new(); } - String::from(if self.0.is_clickable() { "click" } else { "" }) + String::from(if self.0.is_clickable(&filter) { + "click" + } else { + "" + }) } fn raw_bounds_and_transform(&self) -> (Option, Affine) { @@ -1061,7 +1065,7 @@ impl PlatformNode { if let Some(child) = node.filtered_children(filter).nth(child_index) { if let Some(true) = child.is_selected() { Ok(true) - } else if child.is_selectable() && child.is_clickable() { + } else if child.is_selectable() && child.is_clickable(&filter) { context.do_action(ActionRequest { action: Action::Click, target: child.id(), @@ -1084,7 +1088,7 @@ impl PlatformNode { .filter(|c| c.is_selected() == Some(true)) .nth(selected_child_index) { - if child.is_clickable() { + if child.is_clickable(&filter) { context.do_action(ActionRequest { action: Action::Click, target: child.id(), @@ -1124,7 +1128,7 @@ impl PlatformNode { if let Some(child) = node.filtered_children(filter).nth(child_index) { if let Some(false) = child.is_selected() { Ok(true) - } else if child.is_selectable() && child.is_clickable() { + } else if child.is_selectable() && child.is_clickable(&filter) { context.do_action(ActionRequest { action: Action::Click, target: child.id(), diff --git a/platforms/macos/src/node.rs b/platforms/macos/src/node.rs index 13e1f0f8c..27a599e76 100644 --- a/platforms/macos/src/node.rs +++ b/platforms/macos/src/node.rs @@ -600,7 +600,7 @@ declare_class!( fn set_focused(&self, focused: bool) { self.resolve_with_context(|node, context| { if focused { - if node.is_focusable() { + if node.is_focusable(&filter) { context.do_action(ActionRequest { action: Action::Focus, target: node.id(), @@ -609,7 +609,7 @@ declare_class!( } } else { let root = node.tree_state.root(); - if root.is_focusable() { + if root.is_focusable(&filter) { context.do_action(ActionRequest { action: Action::Focus, target: root.id(), @@ -623,7 +623,7 @@ declare_class!( #[method(accessibilityPerformPress)] fn press(&self) -> bool { self.resolve_with_context(|node, context| { - let clickable = node.is_clickable(); + let clickable = node.is_clickable(&filter); if clickable { context.do_action(ActionRequest { action: Action::Click, @@ -639,7 +639,7 @@ declare_class!( #[method(accessibilityPerformIncrement)] fn increment(&self) -> bool { self.resolve_with_context(|node, context| { - let supports_increment = node.supports_increment(); + let supports_increment = node.supports_increment(&filter); if supports_increment { context.do_action(ActionRequest { action: Action::Increment, @@ -655,7 +655,7 @@ declare_class!( #[method(accessibilityPerformDecrement)] fn decrement(&self) -> bool { self.resolve_with_context(|node, context| { - let supports_decrement = node.supports_decrement(); + let supports_decrement = node.supports_decrement(&filter); if supports_decrement { context.do_action(ActionRequest { action: Action::Decrement, @@ -859,7 +859,7 @@ declare_class!( fn set_selected(&self, selected: bool) { self.resolve_with_context(|node, context| { let wrapper = NodeWrapper(node); - if !node.is_clickable() + if !node.is_clickable(&filter) || !wrapper.is_item_like() || !node.is_selectable() { @@ -913,7 +913,7 @@ declare_class!( fn pick(&self) -> bool { self.resolve_with_context(|node, context| { let wrapper = NodeWrapper(node); - let selectable = node.is_clickable() + let selectable = node.is_clickable(&filter) && wrapper.is_item_like() && node.is_selectable(); if selectable { @@ -965,16 +965,16 @@ declare_class!( fn is_selector_allowed(&self, selector: Sel) -> bool { self.resolve(|node| { if selector == sel!(setAccessibilityFocused:) { - return node.is_focusable(); + return node.is_focusable(&filter); } if selector == sel!(accessibilityPerformPress) { - return node.is_clickable(); + return node.is_clickable(&filter); } if selector == sel!(accessibilityPerformIncrement) { - return node.supports_increment(); + return node.supports_increment(&filter); } if selector == sel!(accessibilityPerformDecrement) { - return node.supports_decrement(); + return node.supports_decrement(&filter); } if selector == sel!(accessibilityNumberOfCharacters) || selector == sel!(accessibilitySelectedText) @@ -1011,7 +1011,7 @@ declare_class!( || selector == sel!(accessibilityPerformPick) { let wrapper = NodeWrapper(node); - return node.is_clickable() + return node.is_clickable(&filter) && wrapper.is_item_like() && node.is_selectable(); } diff --git a/platforms/unix/Cargo.toml b/platforms/unix/Cargo.toml index 56cf375d2..45294c394 100644 --- a/platforms/unix/Cargo.toml +++ b/platforms/unix/Cargo.toml @@ -36,3 +36,4 @@ tokio-stream = { version = "0.1.14", optional = true } version = "1.32.0" optional = true features = ["macros", "net", "rt", "sync", "time"] + diff --git a/platforms/windows/src/node.rs b/platforms/windows/src/node.rs index e2ff22bd6..5cc7433fe 100644 --- a/platforms/windows/src/node.rs +++ b/platforms/windows/src/node.rs @@ -289,7 +289,7 @@ impl NodeWrapper<'_> { } fn is_focusable(&self) -> bool { - self.0.is_focusable() + self.0.is_focusable(&filter) } fn is_focused(&self) -> bool { @@ -334,7 +334,7 @@ impl NodeWrapper<'_> { } fn is_invoke_pattern_supported(&self) -> bool { - self.0.is_invocable() + self.0.is_invocable(&filter) } fn is_value_pattern_supported(&self) -> bool { diff --git a/platforms/winit/Cargo.toml b/platforms/winit/Cargo.toml index 345f439d5..24d7e737e 100644 --- a/platforms/winit/Cargo.toml +++ b/platforms/winit/Cargo.toml @@ -40,3 +40,4 @@ accesskit_android = { version = "0.3.0", path = "../android", optional = true, f version = "0.30.5" default-features = false features = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"] + From a3821e9d7b6b664c791ddb96071fd37ffcfa4747 Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Sat, 5 Jul 2025 08:43:44 -0500 Subject: [PATCH 2/3] reformat --- platforms/android/src/adapter.rs | 3 +-- platforms/android/src/node.rs | 36 ++++++++------------------------ 2 files changed, 10 insertions(+), 29 deletions(-) diff --git a/platforms/android/src/adapter.rs b/platforms/android/src/adapter.rs index 6bca14eb5..3b7d52cb7 100644 --- a/platforms/android/src/adapter.rs +++ b/platforms/android/src/adapter.rs @@ -430,8 +430,7 @@ impl Adapter { } else { Action::ScrollLeft } - } else if node.supports_action(Action::ScrollDown, &filter) - { + } else if node.supports_action(Action::ScrollDown, &filter) { Action::ScrollDown } else { Action::ScrollRight diff --git a/platforms/android/src/node.rs b/platforms/android/src/node.rs index 1082955bc..378483544 100644 --- a/platforms/android/src/node.rs +++ b/platforms/android/src/node.rs @@ -12,10 +12,7 @@ use accesskit::{Action, Live, Role, Toggled}; use accesskit_consumer::Node; use jni::{objects::JObject, sys::jint, JNIEnv}; -use crate::{ - filters::filter, - util::*, -}; +use crate::{filters::filter, util::*}; pub(crate) fn add_action(env: &mut JNIEnv, node_info: &JObject, action: jint) { // Note: We're using the deprecated addAction signature. @@ -63,17 +60,10 @@ impl NodeWrapper<'_> { } fn is_scrollable(&self) -> bool { - self.0 - .supports_action(Action::ScrollDown, &filter) - || self - .0 - .supports_action(Action::ScrollLeft, &filter) - || self - .0 - .supports_action(Action::ScrollRight, &filter) - || self - .0 - .supports_action(Action::ScrollUp, &filter) + self.0.supports_action(Action::ScrollDown, &filter) + || self.0.supports_action(Action::ScrollLeft, &filter) + || self.0.supports_action(Action::ScrollRight, &filter) + || self.0.supports_action(Action::ScrollUp, &filter) } fn is_selected(&self) -> bool { @@ -363,21 +353,13 @@ impl NodeWrapper<'_> { ) .unwrap(); } - if self - .0 - .supports_action(Action::ScrollLeft, &filter) - || self - .0 - .supports_action(Action::ScrollUp, &filter) + if self.0.supports_action(Action::ScrollLeft, &filter) + || self.0.supports_action(Action::ScrollUp, &filter) { add_action(env, node_info, ACTION_SCROLL_BACKWARD); } - if self - .0 - .supports_action(Action::ScrollRight, &filter) - || self - .0 - .supports_action(Action::ScrollDown, &filter) + if self.0.supports_action(Action::ScrollRight, &filter) + || self.0.supports_action(Action::ScrollDown, &filter) { add_action(env, node_info, ACTION_SCROLL_FORWARD); } From a70a41eb251b1bedc160a4818fe00d984c11f55b Mon Sep 17 00:00:00 2001 From: Matt Campbell Date: Tue, 8 Jul 2025 06:12:20 -0500 Subject: [PATCH 3/3] Add tests for child actions --- common/src/lib.rs | 86 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 64 insertions(+), 22 deletions(-) diff --git a/common/src/lib.rs b/common/src/lib.rs index c78c0726d..df680a3a9 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -2851,31 +2851,38 @@ mod tests { assert_eq!(node.role(), Role::CheckBox); } + macro_rules! assert_absent_action { + ($node:ident, $action:ident) => { + assert!(!$node.supports_action(Action::$action)); + assert!(!$node.child_supports_action(Action::$action)); + }; + } + #[test] fn new_node_should_not_support_anyaction() { let node = Node::new(Role::Unknown); - assert!(!node.supports_action(Action::Click)); - assert!(!node.supports_action(Action::Focus)); - assert!(!node.supports_action(Action::Blur)); - assert!(!node.supports_action(Action::Collapse)); - assert!(!node.supports_action(Action::Expand)); - assert!(!node.supports_action(Action::CustomAction)); - assert!(!node.supports_action(Action::Decrement)); - assert!(!node.supports_action(Action::Increment)); - assert!(!node.supports_action(Action::HideTooltip)); - assert!(!node.supports_action(Action::ShowTooltip)); - assert!(!node.supports_action(Action::ReplaceSelectedText)); - assert!(!node.supports_action(Action::ScrollDown)); - assert!(!node.supports_action(Action::ScrollLeft)); - assert!(!node.supports_action(Action::ScrollRight)); - assert!(!node.supports_action(Action::ScrollUp)); - assert!(!node.supports_action(Action::ScrollIntoView)); - assert!(!node.supports_action(Action::ScrollToPoint)); - assert!(!node.supports_action(Action::SetScrollOffset)); - assert!(!node.supports_action(Action::SetTextSelection)); - assert!(!node.supports_action(Action::SetSequentialFocusNavigationStartingPoint)); - assert!(!node.supports_action(Action::SetValue)); - assert!(!node.supports_action(Action::ShowContextMenu)); + assert_absent_action!(node, Click); + assert_absent_action!(node, Focus); + assert_absent_action!(node, Blur); + assert_absent_action!(node, Collapse); + assert_absent_action!(node, Expand); + assert_absent_action!(node, CustomAction); + assert_absent_action!(node, Decrement); + assert_absent_action!(node, Increment); + assert_absent_action!(node, HideTooltip); + assert_absent_action!(node, ShowTooltip); + assert_absent_action!(node, ReplaceSelectedText); + assert_absent_action!(node, ScrollDown); + assert_absent_action!(node, ScrollLeft); + assert_absent_action!(node, ScrollRight); + assert_absent_action!(node, ScrollUp); + assert_absent_action!(node, ScrollIntoView); + assert_absent_action!(node, ScrollToPoint); + assert_absent_action!(node, SetScrollOffset); + assert_absent_action!(node, SetTextSelection); + assert_absent_action!(node, SetSequentialFocusNavigationStartingPoint); + assert_absent_action!(node, SetValue); + assert_absent_action!(node, ShowContextMenu); } #[test] @@ -2887,6 +2894,15 @@ mod tests { assert!(node.supports_action(Action::Blur)); } + #[test] + fn node_add_child_action_should_add_the_action() { + let mut node = Node::new(Role::Unknown); + node.add_child_action(Action::Focus); + assert!(node.child_supports_action(Action::Focus)); + node.add_child_action(Action::Blur); + assert!(node.child_supports_action(Action::Blur)); + } + #[test] fn node_add_action_should_do_nothing_if_the_action_is_already_supported() { let mut node = Node::new(Role::Unknown); @@ -2895,6 +2911,14 @@ mod tests { assert!(node.supports_action(Action::Focus)); } + #[test] + fn node_add_child_action_should_do_nothing_if_the_action_is_already_supported() { + let mut node = Node::new(Role::Unknown); + node.add_child_action(Action::Focus); + node.add_child_action(Action::Focus); + assert!(node.child_supports_action(Action::Focus)); + } + #[test] fn node_remove_action_should_remove_the_action() { let mut node = Node::new(Role::Unknown); @@ -2903,6 +2927,14 @@ mod tests { assert!(!node.supports_action(Action::Blur)); } + #[test] + fn node_remove_child_action_should_remove_the_action() { + let mut node = Node::new(Role::Unknown); + node.add_child_action(Action::Blur); + node.remove_child_action(Action::Blur); + assert!(!node.child_supports_action(Action::Blur)); + } + #[test] fn node_clear_actions_should_remove_all_actions() { let mut node = Node::new(Role::Unknown); @@ -2913,6 +2945,16 @@ mod tests { assert!(!node.supports_action(Action::Blur)); } + #[test] + fn node_clear_child_actions_should_remove_all_actions() { + let mut node = Node::new(Role::Unknown); + node.add_child_action(Action::Focus); + node.add_child_action(Action::Blur); + node.clear_child_actions(); + assert!(!node.child_supports_action(Action::Focus)); + assert!(!node.child_supports_action(Action::Blur)); + } + #[test] fn node_should_have_debug_repr() { let mut node = Node::new(Role::Unknown);