| // Copyright 2013 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "accessibility_bridge.h" |
| |
| #include "gmock/gmock.h" |
| #include "gtest/gtest.h" |
| |
| #include "flutter/third_party/accessibility/ax/ax_tree_manager_map.h" |
| #include "test_accessibility_bridge.h" |
| |
| namespace flutter { |
| namespace testing { |
| |
| using ::testing::Contains; |
| |
| FlutterSemanticsNode2 CreateSemanticsNode( |
| int32_t id, |
| const char* label, |
| const std::vector<int32_t>* children = nullptr) { |
| return { |
| .id = id, |
| .flags = static_cast<FlutterSemanticsFlag>(0), |
| .actions = static_cast<FlutterSemanticsAction>(0), |
| .text_selection_base = -1, |
| .text_selection_extent = -1, |
| .label = label, |
| .hint = "", |
| .value = "", |
| .increased_value = "", |
| .decreased_value = "", |
| .child_count = children ? children->size() : 0, |
| .children_in_traversal_order = children ? children->data() : nullptr, |
| .custom_accessibility_actions_count = 0, |
| .tooltip = "", |
| }; |
| } |
| |
| TEST(AccessibilityBridgeTest, BasicTest) { |
| std::shared_ptr<TestAccessibilityBridge> bridge = |
| std::make_shared<TestAccessibilityBridge>(); |
| |
| std::vector<int32_t> children{1, 2}; |
| FlutterSemanticsNode2 root = CreateSemanticsNode(0, "root", &children); |
| FlutterSemanticsNode2 child1 = CreateSemanticsNode(1, "child 1"); |
| FlutterSemanticsNode2 child2 = CreateSemanticsNode(2, "child 2"); |
| |
| bridge->AddFlutterSemanticsNodeUpdate(root); |
| bridge->AddFlutterSemanticsNodeUpdate(child1); |
| bridge->AddFlutterSemanticsNodeUpdate(child2); |
| bridge->CommitUpdates(); |
| |
| auto root_node = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock(); |
| auto child1_node = bridge->GetFlutterPlatformNodeDelegateFromID(1).lock(); |
| auto child2_node = bridge->GetFlutterPlatformNodeDelegateFromID(2).lock(); |
| EXPECT_EQ(root_node->GetChildCount(), 2); |
| EXPECT_EQ(root_node->GetData().child_ids[0], 1); |
| EXPECT_EQ(root_node->GetData().child_ids[1], 2); |
| EXPECT_EQ(root_node->GetName(), "root"); |
| |
| EXPECT_EQ(child1_node->GetChildCount(), 0); |
| EXPECT_EQ(child1_node->GetName(), "child 1"); |
| |
| EXPECT_EQ(child2_node->GetChildCount(), 0); |
| EXPECT_EQ(child2_node->GetName(), "child 2"); |
| } |
| |
| // Flutter used to assume that the accessibility root had ID 0. |
| // In a multi-view world, each view has its own accessibility root |
| // with a globally unique node ID. |
| TEST(AccessibilityBridgeTest, AccessibilityRootId) { |
| std::shared_ptr<TestAccessibilityBridge> bridge = |
| std::make_shared<TestAccessibilityBridge>(); |
| |
| std::vector<int32_t> children{456, 789}; |
| FlutterSemanticsNode2 root = CreateSemanticsNode(123, "root", &children); |
| FlutterSemanticsNode2 child1 = CreateSemanticsNode(456, "child 1"); |
| FlutterSemanticsNode2 child2 = CreateSemanticsNode(789, "child 2"); |
| |
| bridge->AddFlutterSemanticsNodeUpdate(root); |
| bridge->AddFlutterSemanticsNodeUpdate(child1); |
| bridge->AddFlutterSemanticsNodeUpdate(child2); |
| bridge->CommitUpdates(); |
| |
| auto root_node = bridge->GetFlutterPlatformNodeDelegateFromID(123).lock(); |
| auto child1_node = bridge->GetFlutterPlatformNodeDelegateFromID(456).lock(); |
| auto child2_node = bridge->GetFlutterPlatformNodeDelegateFromID(789).lock(); |
| auto fake_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock(); |
| |
| EXPECT_EQ(bridge->GetRootAsAXNode()->id(), 123); |
| EXPECT_EQ(bridge->RootDelegate()->GetName(), "root"); |
| |
| EXPECT_EQ(root_node->GetChildCount(), 2); |
| EXPECT_EQ(root_node->GetData().child_ids[0], 456); |
| EXPECT_EQ(root_node->GetData().child_ids[1], 789); |
| EXPECT_EQ(root_node->GetName(), "root"); |
| |
| EXPECT_EQ(child1_node->GetChildCount(), 0); |
| EXPECT_EQ(child1_node->GetName(), "child 1"); |
| |
| EXPECT_EQ(child2_node->GetChildCount(), 0); |
| EXPECT_EQ(child2_node->GetName(), "child 2"); |
| |
| ASSERT_FALSE(fake_delegate); |
| } |
| |
| // Semantic nodes can be added in any order. |
| TEST(AccessibilityBridgeTest, AddOrder) { |
| std::shared_ptr<TestAccessibilityBridge> bridge = |
| std::make_shared<TestAccessibilityBridge>(); |
| |
| std::vector<int32_t> root_children{34, 56}; |
| std::vector<int32_t> child2_children{78}; |
| std::vector<int32_t> child3_children{90}; |
| FlutterSemanticsNode2 root = CreateSemanticsNode(12, "root", &root_children); |
| FlutterSemanticsNode2 child1 = CreateSemanticsNode(34, "child 1"); |
| FlutterSemanticsNode2 child2 = |
| CreateSemanticsNode(56, "child 2", &child2_children); |
| FlutterSemanticsNode2 child3 = |
| CreateSemanticsNode(78, "child 3", &child3_children); |
| FlutterSemanticsNode2 child4 = CreateSemanticsNode(90, "child 4"); |
| |
| bridge->AddFlutterSemanticsNodeUpdate(child3); |
| bridge->AddFlutterSemanticsNodeUpdate(child2); |
| bridge->AddFlutterSemanticsNodeUpdate(root); |
| bridge->AddFlutterSemanticsNodeUpdate(child1); |
| bridge->AddFlutterSemanticsNodeUpdate(child4); |
| bridge->CommitUpdates(); |
| |
| auto root_node = bridge->GetFlutterPlatformNodeDelegateFromID(12).lock(); |
| auto child1_node = bridge->GetFlutterPlatformNodeDelegateFromID(34).lock(); |
| auto child2_node = bridge->GetFlutterPlatformNodeDelegateFromID(56).lock(); |
| auto child3_node = bridge->GetFlutterPlatformNodeDelegateFromID(78).lock(); |
| auto child4_node = bridge->GetFlutterPlatformNodeDelegateFromID(90).lock(); |
| |
| EXPECT_EQ(bridge->GetRootAsAXNode()->id(), 12); |
| EXPECT_EQ(bridge->RootDelegate()->GetName(), "root"); |
| |
| EXPECT_EQ(root_node->GetChildCount(), 2); |
| EXPECT_EQ(root_node->GetData().child_ids[0], 34); |
| EXPECT_EQ(root_node->GetData().child_ids[1], 56); |
| EXPECT_EQ(root_node->GetName(), "root"); |
| |
| EXPECT_EQ(child1_node->GetChildCount(), 0); |
| EXPECT_EQ(child1_node->GetName(), "child 1"); |
| |
| EXPECT_EQ(child2_node->GetChildCount(), 1); |
| EXPECT_EQ(child2_node->GetData().child_ids[0], 78); |
| EXPECT_EQ(child2_node->GetName(), "child 2"); |
| |
| EXPECT_EQ(child3_node->GetChildCount(), 1); |
| EXPECT_EQ(child3_node->GetData().child_ids[0], 90); |
| EXPECT_EQ(child3_node->GetName(), "child 3"); |
| |
| EXPECT_EQ(child4_node->GetChildCount(), 0); |
| EXPECT_EQ(child4_node->GetName(), "child 4"); |
| } |
| |
| TEST(AccessibilityBridgeTest, CanFireChildrenChangedCorrectly) { |
| std::shared_ptr<TestAccessibilityBridge> bridge = |
| std::make_shared<TestAccessibilityBridge>(); |
| |
| std::vector<int32_t> children{1}; |
| FlutterSemanticsNode2 root = CreateSemanticsNode(0, "root", &children); |
| FlutterSemanticsNode2 child1 = CreateSemanticsNode(1, "child 1"); |
| |
| bridge->AddFlutterSemanticsNodeUpdate(root); |
| bridge->AddFlutterSemanticsNodeUpdate(child1); |
| |
| bridge->CommitUpdates(); |
| |
| auto root_node = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock(); |
| auto child1_node = bridge->GetFlutterPlatformNodeDelegateFromID(1).lock(); |
| EXPECT_EQ(root_node->GetChildCount(), 1); |
| EXPECT_EQ(root_node->GetData().child_ids[0], 1); |
| EXPECT_EQ(root_node->GetName(), "root"); |
| |
| EXPECT_EQ(child1_node->GetChildCount(), 0); |
| EXPECT_EQ(child1_node->GetName(), "child 1"); |
| bridge->accessibility_events.clear(); |
| |
| // Add a child to root. |
| root.child_count = 2; |
| int32_t new_children[] = {1, 2}; |
| root.children_in_traversal_order = new_children; |
| |
| FlutterSemanticsNode2 child2 = CreateSemanticsNode(2, "child 2"); |
| |
| bridge->AddFlutterSemanticsNodeUpdate(root); |
| bridge->AddFlutterSemanticsNodeUpdate(child2); |
| bridge->CommitUpdates(); |
| |
| root_node = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock(); |
| |
| EXPECT_EQ(root_node->GetChildCount(), 2); |
| EXPECT_EQ(root_node->GetData().child_ids[0], 1); |
| EXPECT_EQ(root_node->GetData().child_ids[1], 2); |
| EXPECT_EQ(bridge->accessibility_events.size(), size_t{2}); |
| std::set<ui::AXEventGenerator::Event> actual_event{ |
| bridge->accessibility_events.begin(), bridge->accessibility_events.end()}; |
| EXPECT_THAT(actual_event, |
| Contains(ui::AXEventGenerator::Event::CHILDREN_CHANGED)); |
| EXPECT_THAT(actual_event, |
| Contains(ui::AXEventGenerator::Event::SUBTREE_CREATED)); |
| } |
| |
| TEST(AccessibilityBridgeTest, CanHandleSelectionChangeCorrectly) { |
| std::shared_ptr<TestAccessibilityBridge> bridge = |
| std::make_shared<TestAccessibilityBridge>(); |
| FlutterSemanticsNode2 root = CreateSemanticsNode(0, "root"); |
| root.flags = static_cast<FlutterSemanticsFlag>( |
| FlutterSemanticsFlag::kFlutterSemanticsFlagIsTextField | |
| FlutterSemanticsFlag::kFlutterSemanticsFlagIsFocused); |
| bridge->AddFlutterSemanticsNodeUpdate(root); |
| bridge->CommitUpdates(); |
| |
| const ui::AXTreeData& tree = bridge->GetAXTreeData(); |
| EXPECT_EQ(tree.sel_anchor_object_id, ui::AXNode::kInvalidAXID); |
| bridge->accessibility_events.clear(); |
| |
| // Update the selection. |
| root.text_selection_base = 0; |
| root.text_selection_extent = 5; |
| bridge->AddFlutterSemanticsNodeUpdate(root); |
| |
| bridge->CommitUpdates(); |
| |
| EXPECT_EQ(tree.sel_anchor_object_id, 0); |
| EXPECT_EQ(tree.sel_anchor_offset, 0); |
| EXPECT_EQ(tree.sel_focus_object_id, 0); |
| EXPECT_EQ(tree.sel_focus_offset, 5); |
| ASSERT_EQ(bridge->accessibility_events.size(), size_t{2}); |
| EXPECT_EQ(bridge->accessibility_events[0], |
| ui::AXEventGenerator::Event::DOCUMENT_SELECTION_CHANGED); |
| EXPECT_EQ(bridge->accessibility_events[1], |
| ui::AXEventGenerator::Event::OTHER_ATTRIBUTE_CHANGED); |
| } |
| |
| TEST(AccessibilityBridgeTest, DoesNotAssignEditableRootToSelectableText) { |
| std::shared_ptr<TestAccessibilityBridge> bridge = |
| std::make_shared<TestAccessibilityBridge>(); |
| FlutterSemanticsNode2 root = CreateSemanticsNode(0, "root"); |
| root.flags = static_cast<FlutterSemanticsFlag>( |
| FlutterSemanticsFlag::kFlutterSemanticsFlagIsTextField | |
| FlutterSemanticsFlag::kFlutterSemanticsFlagIsReadOnly); |
| bridge->AddFlutterSemanticsNodeUpdate(root); |
| bridge->CommitUpdates(); |
| |
| auto root_node = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock(); |
| |
| EXPECT_FALSE(root_node->GetData().GetBoolAttribute( |
| ax::mojom::BoolAttribute::kEditableRoot)); |
| } |
| |
| TEST(AccessibilityBridgeTest, SwitchHasSwitchRole) { |
| std::shared_ptr<TestAccessibilityBridge> bridge = |
| std::make_shared<TestAccessibilityBridge>(); |
| FlutterSemanticsNode2 root = CreateSemanticsNode(0, "root"); |
| root.flags = static_cast<FlutterSemanticsFlag>( |
| FlutterSemanticsFlag::kFlutterSemanticsFlagHasToggledState | |
| FlutterSemanticsFlag::kFlutterSemanticsFlagHasEnabledState | |
| FlutterSemanticsFlag::kFlutterSemanticsFlagIsEnabled); |
| bridge->AddFlutterSemanticsNodeUpdate(root); |
| bridge->CommitUpdates(); |
| |
| auto root_node = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock(); |
| EXPECT_EQ(root_node->GetData().role, ax::mojom::Role::kSwitch); |
| } |
| |
| TEST(AccessibilityBridgeTest, SliderHasSliderRole) { |
| std::shared_ptr<TestAccessibilityBridge> bridge = |
| std::make_shared<TestAccessibilityBridge>(); |
| FlutterSemanticsNode2 root = CreateSemanticsNode(0, "root"); |
| root.flags = static_cast<FlutterSemanticsFlag>( |
| FlutterSemanticsFlag::kFlutterSemanticsFlagIsSlider | |
| FlutterSemanticsFlag::kFlutterSemanticsFlagHasEnabledState | |
| FlutterSemanticsFlag::kFlutterSemanticsFlagIsEnabled | |
| FlutterSemanticsFlag::kFlutterSemanticsFlagIsFocusable); |
| bridge->AddFlutterSemanticsNodeUpdate(root); |
| bridge->CommitUpdates(); |
| |
| auto root_node = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock(); |
| EXPECT_EQ(root_node->GetData().role, ax::mojom::Role::kSlider); |
| } |
| |
| // Ensure that checkboxes have their checked status set apropriately |
| // Previously, only Radios could have this flag updated |
| // Resulted in the issue seen at |
| // https://github.com/flutter/flutter/issues/96218 |
| // As this fix involved code run on all platforms, it is included here. |
| TEST(AccessibilityBridgeTest, CanSetCheckboxChecked) { |
| std::shared_ptr<TestAccessibilityBridge> bridge = |
| std::make_shared<TestAccessibilityBridge>(); |
| FlutterSemanticsNode2 root = CreateSemanticsNode(0, "root"); |
| root.flags = static_cast<FlutterSemanticsFlag>( |
| FlutterSemanticsFlag::kFlutterSemanticsFlagHasCheckedState | |
| FlutterSemanticsFlag::kFlutterSemanticsFlagIsChecked); |
| bridge->AddFlutterSemanticsNodeUpdate(root); |
| bridge->CommitUpdates(); |
| |
| auto root_node = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock(); |
| EXPECT_EQ(root_node->GetData().role, ax::mojom::Role::kCheckBox); |
| EXPECT_EQ(root_node->GetData().GetCheckedState(), |
| ax::mojom::CheckedState::kTrue); |
| } |
| |
| // Verify that a node can be moved from one parent to another. |
| TEST(AccessibilityBridgeTest, CanReparentNode) { |
| std::shared_ptr<TestAccessibilityBridge> bridge = |
| std::make_shared<TestAccessibilityBridge>(); |
| |
| std::vector<int32_t> root_children{1}; |
| std::vector<int32_t> child1_children{2}; |
| FlutterSemanticsNode2 root = CreateSemanticsNode(0, "root", &root_children); |
| FlutterSemanticsNode2 child1 = |
| CreateSemanticsNode(1, "child 1", &child1_children); |
| FlutterSemanticsNode2 child2 = CreateSemanticsNode(2, "child 2"); |
| |
| bridge->AddFlutterSemanticsNodeUpdate(root); |
| bridge->AddFlutterSemanticsNodeUpdate(child1); |
| bridge->AddFlutterSemanticsNodeUpdate(child2); |
| bridge->CommitUpdates(); |
| bridge->accessibility_events.clear(); |
| |
| // Reparent child2 from child1 to the root. |
| child1.child_count = 0; |
| child1.children_in_traversal_order = nullptr; |
| |
| int32_t new_root_children[] = {1, 2}; |
| root.child_count = 2; |
| root.children_in_traversal_order = new_root_children; |
| |
| bridge->AddFlutterSemanticsNodeUpdate(root); |
| bridge->AddFlutterSemanticsNodeUpdate(child1); |
| bridge->AddFlutterSemanticsNodeUpdate(child2); |
| bridge->CommitUpdates(); |
| |
| auto root_node = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock(); |
| auto child1_node = bridge->GetFlutterPlatformNodeDelegateFromID(1).lock(); |
| auto child2_node = bridge->GetFlutterPlatformNodeDelegateFromID(2).lock(); |
| |
| EXPECT_EQ(root_node->GetChildCount(), 2); |
| EXPECT_EQ(root_node->GetData().child_ids[0], 1); |
| EXPECT_EQ(root_node->GetData().child_ids[1], 2); |
| EXPECT_EQ(root_node->GetName(), "root"); |
| |
| EXPECT_EQ(child1_node->GetChildCount(), 0); |
| EXPECT_EQ(child1_node->GetName(), "child 1"); |
| |
| EXPECT_EQ(child2_node->GetChildCount(), 0); |
| EXPECT_EQ(child2_node->GetName(), "child 2"); |
| |
| ASSERT_EQ(bridge->accessibility_events.size(), size_t{5}); |
| |
| // Child2 is moved from child1 to root. |
| EXPECT_THAT(bridge->accessibility_events, |
| Contains(ui::AXEventGenerator::Event::CHILDREN_CHANGED).Times(2)); |
| EXPECT_THAT(bridge->accessibility_events, |
| Contains(ui::AXEventGenerator::Event::SUBTREE_CREATED).Times(1)); |
| |
| // Child1 is no longer a parent. It loses its group role and disables its |
| // 'clip children' attribute. |
| EXPECT_THAT( |
| bridge->accessibility_events, |
| Contains(ui::AXEventGenerator::Event::OTHER_ATTRIBUTE_CHANGED).Times(1)); |
| EXPECT_THAT(bridge->accessibility_events, |
| Contains(ui::AXEventGenerator::Event::ROLE_CHANGED).Times(1)); |
| } |
| |
| // Verify that multiple nodes can be moved to new parents. |
| TEST(AccessibilityBridgeTest, CanReparentMultipleNodes) { |
| std::shared_ptr<TestAccessibilityBridge> bridge = |
| std::make_shared<TestAccessibilityBridge>(); |
| |
| int32_t root_id = 0; |
| int32_t intermediary1_id = 1; |
| int32_t intermediary2_id = 2; |
| int32_t leaf1_id = 3; |
| int32_t leaf2_id = 4; |
| int32_t leaf3_id = 5; |
| |
| std::vector<int32_t> root_children{intermediary1_id, intermediary2_id}; |
| std::vector<int32_t> intermediary1_children{leaf1_id}; |
| std::vector<int32_t> intermediary2_children{leaf2_id, leaf3_id}; |
| FlutterSemanticsNode2 root = |
| CreateSemanticsNode(root_id, "root", &root_children); |
| FlutterSemanticsNode2 intermediary1 = CreateSemanticsNode( |
| intermediary1_id, "intermediary 1", &intermediary1_children); |
| FlutterSemanticsNode2 intermediary2 = CreateSemanticsNode( |
| intermediary2_id, "intermediary 2", &intermediary2_children); |
| FlutterSemanticsNode2 leaf1 = CreateSemanticsNode(leaf1_id, "leaf 1"); |
| FlutterSemanticsNode2 leaf2 = CreateSemanticsNode(leaf2_id, "leaf 2"); |
| FlutterSemanticsNode2 leaf3 = CreateSemanticsNode(leaf3_id, "leaf 3"); |
| |
| bridge->AddFlutterSemanticsNodeUpdate(root); |
| bridge->AddFlutterSemanticsNodeUpdate(intermediary1); |
| bridge->AddFlutterSemanticsNodeUpdate(intermediary2); |
| bridge->AddFlutterSemanticsNodeUpdate(leaf1); |
| bridge->AddFlutterSemanticsNodeUpdate(leaf2); |
| bridge->AddFlutterSemanticsNodeUpdate(leaf3); |
| bridge->CommitUpdates(); |
| bridge->accessibility_events.clear(); |
| |
| // Swap intermediary 1's and intermediary2's children. |
| int32_t new_intermediary1_children[] = {leaf2_id, leaf3_id}; |
| intermediary1.child_count = 2; |
| intermediary1.children_in_traversal_order = new_intermediary1_children; |
| |
| int32_t new_intermediary2_children[] = {leaf1_id}; |
| intermediary2.child_count = 1; |
| intermediary2.children_in_traversal_order = new_intermediary2_children; |
| |
| bridge->AddFlutterSemanticsNodeUpdate(intermediary1); |
| bridge->AddFlutterSemanticsNodeUpdate(intermediary2); |
| bridge->AddFlutterSemanticsNodeUpdate(leaf1); |
| bridge->AddFlutterSemanticsNodeUpdate(leaf2); |
| bridge->AddFlutterSemanticsNodeUpdate(leaf3); |
| bridge->CommitUpdates(); |
| |
| auto root_node = bridge->GetFlutterPlatformNodeDelegateFromID(root_id).lock(); |
| auto intermediary1_node = |
| bridge->GetFlutterPlatformNodeDelegateFromID(intermediary1_id).lock(); |
| auto intermediary2_node = |
| bridge->GetFlutterPlatformNodeDelegateFromID(intermediary2_id).lock(); |
| auto leaf1_node = |
| bridge->GetFlutterPlatformNodeDelegateFromID(leaf1_id).lock(); |
| auto leaf2_node = |
| bridge->GetFlutterPlatformNodeDelegateFromID(leaf2_id).lock(); |
| auto leaf3_node = |
| bridge->GetFlutterPlatformNodeDelegateFromID(leaf3_id).lock(); |
| |
| EXPECT_EQ(root_node->GetChildCount(), 2); |
| EXPECT_EQ(root_node->GetData().child_ids[0], intermediary1_id); |
| EXPECT_EQ(root_node->GetData().child_ids[1], intermediary2_id); |
| EXPECT_EQ(root_node->GetName(), "root"); |
| |
| EXPECT_EQ(intermediary1_node->GetChildCount(), 2); |
| EXPECT_EQ(intermediary1_node->GetData().child_ids[0], leaf2_id); |
| EXPECT_EQ(intermediary1_node->GetData().child_ids[1], leaf3_id); |
| EXPECT_EQ(intermediary1_node->GetName(), "intermediary 1"); |
| |
| EXPECT_EQ(intermediary2_node->GetChildCount(), 1); |
| EXPECT_EQ(intermediary2_node->GetData().child_ids[0], leaf1_id); |
| EXPECT_EQ(intermediary2_node->GetName(), "intermediary 2"); |
| |
| EXPECT_EQ(leaf1_node->GetChildCount(), 0); |
| EXPECT_EQ(leaf1_node->GetName(), "leaf 1"); |
| |
| EXPECT_EQ(leaf2_node->GetChildCount(), 0); |
| EXPECT_EQ(leaf2_node->GetName(), "leaf 2"); |
| |
| EXPECT_EQ(leaf3_node->GetChildCount(), 0); |
| EXPECT_EQ(leaf3_node->GetName(), "leaf 3"); |
| |
| // Intermediary 1 and intermediary 2 have new children. |
| // Leaf 1, 2, and 3 are all moved. |
| ASSERT_EQ(bridge->accessibility_events.size(), size_t{5}); |
| EXPECT_THAT(bridge->accessibility_events, |
| Contains(ui::AXEventGenerator::Event::CHILDREN_CHANGED).Times(2)); |
| EXPECT_THAT(bridge->accessibility_events, |
| Contains(ui::AXEventGenerator::Event::SUBTREE_CREATED).Times(3)); |
| } |
| |
| // Verify that a node with a child can be moved from one parent to another. |
| TEST(AccessibilityBridgeTest, CanReparentNodeWithChild) { |
| std::shared_ptr<TestAccessibilityBridge> bridge = |
| std::make_shared<TestAccessibilityBridge>(); |
| |
| int32_t root_id = 0; |
| int32_t intermediary1_id = 1; |
| int32_t intermediary2_id = 2; |
| int32_t leaf1_id = 3; |
| |
| std::vector<int32_t> root_children{intermediary1_id, intermediary2_id}; |
| std::vector<int32_t> intermediary1_children{leaf1_id}; |
| FlutterSemanticsNode2 root = |
| CreateSemanticsNode(root_id, "root", &root_children); |
| FlutterSemanticsNode2 intermediary1 = CreateSemanticsNode( |
| intermediary1_id, "intermediary 1", &intermediary1_children); |
| FlutterSemanticsNode2 intermediary2 = |
| CreateSemanticsNode(intermediary2_id, "intermediary 2"); |
| FlutterSemanticsNode2 leaf1 = CreateSemanticsNode(leaf1_id, "leaf 1"); |
| |
| bridge->AddFlutterSemanticsNodeUpdate(root); |
| bridge->AddFlutterSemanticsNodeUpdate(intermediary1); |
| bridge->AddFlutterSemanticsNodeUpdate(intermediary2); |
| bridge->AddFlutterSemanticsNodeUpdate(leaf1); |
| bridge->CommitUpdates(); |
| bridge->accessibility_events.clear(); |
| |
| // Move intermediary1 from root to intermediary 2. |
| int32_t new_root_children[] = {intermediary2_id}; |
| root.child_count = 1; |
| root.children_in_traversal_order = new_root_children; |
| |
| int32_t new_intermediary2_children[] = {intermediary1_id}; |
| intermediary2.child_count = 1; |
| intermediary2.children_in_traversal_order = new_intermediary2_children; |
| |
| bridge->AddFlutterSemanticsNodeUpdate(root); |
| bridge->AddFlutterSemanticsNodeUpdate(intermediary1); |
| bridge->AddFlutterSemanticsNodeUpdate(intermediary2); |
| bridge->AddFlutterSemanticsNodeUpdate(leaf1); |
| bridge->CommitUpdates(); |
| |
| auto root_node = bridge->GetFlutterPlatformNodeDelegateFromID(root_id).lock(); |
| auto intermediary1_node = |
| bridge->GetFlutterPlatformNodeDelegateFromID(intermediary1_id).lock(); |
| auto intermediary2_node = |
| bridge->GetFlutterPlatformNodeDelegateFromID(intermediary2_id).lock(); |
| auto leaf1_node = |
| bridge->GetFlutterPlatformNodeDelegateFromID(leaf1_id).lock(); |
| |
| EXPECT_EQ(root_node->GetChildCount(), 1); |
| EXPECT_EQ(root_node->GetData().child_ids[0], intermediary2_id); |
| EXPECT_EQ(root_node->GetName(), "root"); |
| |
| EXPECT_EQ(intermediary2_node->GetChildCount(), 1); |
| EXPECT_EQ(intermediary2_node->GetData().child_ids[0], intermediary1_id); |
| EXPECT_EQ(intermediary2_node->GetName(), "intermediary 2"); |
| |
| EXPECT_EQ(intermediary1_node->GetChildCount(), 1); |
| EXPECT_EQ(intermediary1_node->GetData().child_ids[0], leaf1_id); |
| EXPECT_EQ(intermediary1_node->GetName(), "intermediary 1"); |
| |
| EXPECT_EQ(leaf1_node->GetChildCount(), 0); |
| EXPECT_EQ(leaf1_node->GetName(), "leaf 1"); |
| |
| ASSERT_EQ(bridge->accessibility_events.size(), size_t{5}); |
| |
| EXPECT_THAT(bridge->accessibility_events, |
| Contains(ui::AXEventGenerator::Event::CHILDREN_CHANGED).Times(2)); |
| EXPECT_THAT(bridge->accessibility_events, |
| Contains(ui::AXEventGenerator::Event::SUBTREE_CREATED).Times(1)); |
| |
| // Intermediary 2 becomes a parent node. It updates to group role and enables |
| // its 'clip children' attribute. |
| EXPECT_THAT( |
| bridge->accessibility_events, |
| Contains(ui::AXEventGenerator::Event::OTHER_ATTRIBUTE_CHANGED).Times(1)); |
| EXPECT_THAT(bridge->accessibility_events, |
| Contains(ui::AXEventGenerator::Event::ROLE_CHANGED).Times(1)); |
| } |
| |
| TEST(AccessibilityBridgeTest, AXTreeManagerTest) { |
| std::shared_ptr<TestAccessibilityBridge> bridge = |
| std::make_shared<TestAccessibilityBridge>(); |
| |
| ui::AXTreeID tree_id = bridge->GetTreeID(); |
| ui::AXTreeManager* manager = |
| ui::AXTreeManagerMap::GetInstance().GetManager(tree_id); |
| ASSERT_EQ(manager, static_cast<ui::AXTreeManager*>(bridge.get())); |
| } |
| |
| TEST(AccessibilityBridgeTest, LineBreakingObjectTest) { |
| std::shared_ptr<TestAccessibilityBridge> bridge = |
| std::make_shared<TestAccessibilityBridge>(); |
| |
| const int32_t root_id = 0; |
| |
| FlutterSemanticsNode2 root = CreateSemanticsNode(root_id, "root", {}); |
| |
| bridge->AddFlutterSemanticsNodeUpdate(root); |
| bridge->CommitUpdates(); |
| |
| auto root_node = bridge->GetFlutterPlatformNodeDelegateFromID(root_id).lock(); |
| auto root_data = root_node->GetData(); |
| EXPECT_TRUE(root_data.HasBoolAttribute( |
| ax::mojom::BoolAttribute::kIsLineBreakingObject)); |
| EXPECT_TRUE(root_data.GetBoolAttribute( |
| ax::mojom::BoolAttribute::kIsLineBreakingObject)); |
| } |
| |
| } // namespace testing |
| } // namespace flutter |