| // Copyright 2017 The Chromium 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 "ui/accessibility/ax_event_generator.h" |
| |
| #include "testing/gmock/include/gmock/gmock-matchers.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/accessibility/ax_enums.mojom.h" |
| #include "ui/accessibility/ax_node.h" |
| #include "ui/accessibility/ax_serializable_tree.h" |
| #include "ui/accessibility/ax_tree_serializer.h" |
| |
| namespace ui { |
| |
| // Required by gmock to print TargetedEvent in a human-readable way. |
| void PrintTo(const AXEventGenerator::TargetedEvent& event, std::ostream* os) { |
| *os << event.event_params.event << " on " << event.node->id(); |
| } |
| |
| namespace { |
| |
| using testing::IsEmpty; |
| using testing::IsSupersetOf; |
| using testing::Matches; |
| using testing::PrintToString; |
| using testing::UnorderedElementsAre; |
| |
| // TODO(gilmanmh): Improve printing of test failure messages when the expected |
| // values are themselves matchers (e.g. Not(3)). |
| MATCHER_P2(HasEventAtNode, |
| expected_event_type, |
| expected_node_id, |
| std::string(negation ? "does not have" : "has") + " " + |
| PrintToString(expected_event_type) + " on " + |
| PrintToString(expected_node_id)) { |
| const auto& event = arg; |
| return Matches(expected_event_type)(event.event_params.event) && |
| Matches(expected_node_id)(event.node->id()); |
| } |
| |
| } // namespace |
| |
| TEST(AXEventGeneratorTest, IterateThroughEmptyEventSets) { |
| // The event map contains the following: |
| // node1, <> |
| // node2, <> |
| // node3, <IGNORED_CHANGED, SUBTREE_CREATED, NAME_CHANGED> |
| // node4, <> |
| // node5, <> |
| // node6, <> |
| // node7, <IGNORED_CHANGED> |
| // node8, <> |
| // node9, <> |
| // Verify AXEventGenerator can iterate through empty event sets, and returning |
| // the correct events. |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(9); |
| initial_state.nodes[0].id = 1; |
| initial_state.nodes[0].child_ids.push_back(2); |
| |
| initial_state.nodes[1].id = 2; |
| initial_state.nodes[1].child_ids.push_back(3); |
| |
| initial_state.nodes[2].id = 3; |
| initial_state.nodes[2].child_ids.push_back(4); |
| |
| initial_state.nodes[3].id = 4; |
| initial_state.nodes[3].child_ids.push_back(5); |
| |
| initial_state.nodes[4].id = 5; |
| initial_state.nodes[4].child_ids.push_back(6); |
| |
| initial_state.nodes[5].id = 6; |
| initial_state.nodes[5].child_ids.push_back(7); |
| |
| initial_state.nodes[6].id = 7; |
| initial_state.nodes[6].child_ids.push_back(8); |
| |
| initial_state.nodes[7].id = 8; |
| initial_state.nodes[7].child_ids.push_back(9); |
| |
| initial_state.nodes[8].id = 9; |
| initial_state.has_tree_data = true; |
| |
| AXTree tree(initial_state); |
| AXEventGenerator event_generator(&tree); |
| AXNode* node1 = tree.root(); |
| AXNode* node2 = tree.GetFromId(2); |
| AXNode* node3 = tree.GetFromId(3); |
| AXNode* node4 = tree.GetFromId(4); |
| AXNode* node5 = tree.GetFromId(5); |
| AXNode* node6 = tree.GetFromId(6); |
| AXNode* node7 = tree.GetFromId(7); |
| AXNode* node8 = tree.GetFromId(8); |
| AXNode* node9 = tree.GetFromId(9); |
| |
| // Node1 contains no event. |
| std::set<AXEventGenerator::EventParams> node1_events; |
| // Node2 contains no event. |
| std::set<AXEventGenerator::EventParams> node2_events; |
| // Node3 contains IGNORED_CHANGED, SUBTREE_CREATED, NAME_CHANGED. |
| std::set<AXEventGenerator::EventParams> node3_events; |
| node3_events.emplace(AXEventGenerator::Event::IGNORED_CHANGED, |
| ax::mojom::EventFrom::kNone, tree.event_intents()); |
| node3_events.emplace(AXEventGenerator::Event::SUBTREE_CREATED, |
| ax::mojom::EventFrom::kNone, tree.event_intents()); |
| node3_events.emplace(AXEventGenerator::Event::NAME_CHANGED, |
| ax::mojom::EventFrom::kNone, tree.event_intents()); |
| // Node4 contains no event. |
| std::set<AXEventGenerator::EventParams> node4_events; |
| // Node5 contains no event. |
| std::set<AXEventGenerator::EventParams> node5_events; |
| // Node6 contains no event. |
| std::set<AXEventGenerator::EventParams> node6_events; |
| // Node7 contains IGNORED_CHANGED. |
| std::set<AXEventGenerator::EventParams> node7_events; |
| node7_events.emplace(AXEventGenerator::Event::IGNORED_CHANGED, |
| ax::mojom::EventFrom::kNone, tree.event_intents()); |
| // Node8 contains no event. |
| std::set<AXEventGenerator::EventParams> node8_events; |
| // Node9 contains no event. |
| std::set<AXEventGenerator::EventParams> node9_events; |
| |
| event_generator.AddEventsForTesting(node1, node1_events); |
| event_generator.AddEventsForTesting(node2, node2_events); |
| event_generator.AddEventsForTesting(node3, node3_events); |
| event_generator.AddEventsForTesting(node4, node4_events); |
| event_generator.AddEventsForTesting(node5, node5_events); |
| event_generator.AddEventsForTesting(node6, node6_events); |
| event_generator.AddEventsForTesting(node7, node7_events); |
| event_generator.AddEventsForTesting(node8, node8_events); |
| event_generator.AddEventsForTesting(node9, node9_events); |
| |
| std::map<AXNode*, std::set<AXEventGenerator::Event>> expected_event_map; |
| expected_event_map[node3] = {AXEventGenerator::Event::IGNORED_CHANGED, |
| AXEventGenerator::Event::SUBTREE_CREATED, |
| AXEventGenerator::Event::NAME_CHANGED}; |
| expected_event_map[node7] = {AXEventGenerator::Event::IGNORED_CHANGED}; |
| |
| for (const auto& targeted_event : event_generator) { |
| auto map_iter = expected_event_map.find(targeted_event.node); |
| |
| ASSERT_NE(map_iter, expected_event_map.end()) |
| << "|expected_event_map| contains node.id=" << targeted_event.node->id() |
| << "\nExpected: true" |
| << "\nActual: " << std::boolalpha |
| << (map_iter != expected_event_map.end()); |
| |
| std::set<AXEventGenerator::Event>& node_events = map_iter->second; |
| auto event_iter = node_events.find(targeted_event.event_params.event); |
| |
| ASSERT_NE(event_iter, node_events.end()) |
| << "Event=" << targeted_event.event_params.event |
| << ", on node.id=" << targeted_event.node->id() |
| << " NOT found in |expected_event_map|"; |
| |
| // If the event from |event_generator| is found in |expected_event_map|, |
| // we want to delete the corresponding entry in |expected_event_map|. |
| node_events.erase(event_iter); |
| if (node_events.empty()) |
| expected_event_map.erase(map_iter); |
| } |
| |
| // We should expect |expected_event_map_| to be empty, when all the generated |
| // events match expected events. |
| EXPECT_TRUE(expected_event_map.empty()); |
| } |
| |
| TEST(AXEventGeneratorTest, LoadCompleteSameTree) { |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(1); |
| initial_state.nodes[0].id = 1; |
| initial_state.nodes[0].relative_bounds.bounds = gfx::RectF(0, 0, 800, 600); |
| initial_state.has_tree_data = true; |
| AXTree tree(initial_state); |
| |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate load_complete_update = initial_state; |
| load_complete_update.tree_data.loaded = true; |
| |
| ASSERT_TRUE(tree.Unserialize(load_complete_update)); |
| EXPECT_THAT(event_generator, UnorderedElementsAre(HasEventAtNode( |
| AXEventGenerator::Event::LOAD_COMPLETE, 1))); |
| } |
| |
| TEST(AXEventGeneratorTest, LoadCompleteNewTree) { |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(1); |
| initial_state.nodes[0].id = 1; |
| initial_state.has_tree_data = true; |
| initial_state.tree_data.loaded = true; |
| AXTree tree(initial_state); |
| |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate load_complete_update; |
| load_complete_update.root_id = 2; |
| load_complete_update.nodes.resize(1); |
| load_complete_update.nodes[0].id = 2; |
| load_complete_update.nodes[0].relative_bounds.bounds = |
| gfx::RectF(0, 0, 800, 600); |
| load_complete_update.has_tree_data = true; |
| load_complete_update.tree_data.loaded = true; |
| |
| ASSERT_TRUE(tree.Unserialize(load_complete_update)); |
| EXPECT_THAT(event_generator, |
| UnorderedElementsAre( |
| HasEventAtNode(AXEventGenerator::Event::LOAD_COMPLETE, 2), |
| HasEventAtNode(AXEventGenerator::Event::SUBTREE_CREATED, 2))); |
| |
| // Load complete should not be emitted for sizeless roots. |
| load_complete_update.root_id = 3; |
| load_complete_update.nodes.resize(1); |
| load_complete_update.nodes[0].id = 3; |
| load_complete_update.nodes[0].relative_bounds.bounds = gfx::RectF(0, 0, 0, 0); |
| load_complete_update.has_tree_data = true; |
| load_complete_update.tree_data.loaded = true; |
| |
| ASSERT_TRUE(tree.Unserialize(load_complete_update)); |
| EXPECT_THAT(event_generator, |
| UnorderedElementsAre( |
| HasEventAtNode(AXEventGenerator::Event::SUBTREE_CREATED, 3))); |
| |
| // TODO(accessibility): http://crbug.com/888758 |
| // Load complete should not be emitted for chrome-search URLs. |
| load_complete_update.root_id = 4; |
| load_complete_update.nodes.resize(1); |
| load_complete_update.nodes[0].id = 4; |
| load_complete_update.nodes[0].relative_bounds.bounds = |
| gfx::RectF(0, 0, 800, 600); |
| load_complete_update.nodes[0].AddStringAttribute( |
| ax::mojom::StringAttribute::kUrl, "chrome-search://foo"); |
| load_complete_update.has_tree_data = true; |
| load_complete_update.tree_data.loaded = true; |
| |
| ASSERT_TRUE(tree.Unserialize(load_complete_update)); |
| EXPECT_THAT(event_generator, |
| UnorderedElementsAre( |
| HasEventAtNode(AXEventGenerator::Event::LOAD_COMPLETE, 4), |
| HasEventAtNode(AXEventGenerator::Event::SUBTREE_CREATED, 4))); |
| } |
| |
| TEST(AXEventGeneratorTest, LoadStart) { |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(1); |
| initial_state.nodes[0].id = 1; |
| initial_state.nodes[0].relative_bounds.bounds = gfx::RectF(0, 0, 800, 600); |
| initial_state.has_tree_data = true; |
| AXTree tree(initial_state); |
| |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate load_start_update; |
| load_start_update.root_id = 2; |
| load_start_update.nodes.resize(1); |
| load_start_update.nodes[0].id = 2; |
| load_start_update.nodes[0].relative_bounds.bounds = |
| gfx::RectF(0, 0, 800, 600); |
| load_start_update.has_tree_data = true; |
| load_start_update.tree_data.loaded = false; |
| |
| ASSERT_TRUE(tree.Unserialize(load_start_update)); |
| EXPECT_THAT(event_generator, |
| UnorderedElementsAre( |
| HasEventAtNode(AXEventGenerator::Event::LOAD_START, 2), |
| HasEventAtNode(AXEventGenerator::Event::SUBTREE_CREATED, 2))); |
| } |
| |
| TEST(AXEventGeneratorTest, DocumentSelectionChanged) { |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(1); |
| initial_state.nodes[0].id = 1; |
| initial_state.has_tree_data = true; |
| initial_state.tree_data.sel_focus_object_id = 1; |
| initial_state.tree_data.sel_focus_offset = 1; |
| AXTree tree(initial_state); |
| |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate update = initial_state; |
| update.tree_data.sel_focus_offset = 2; |
| |
| ASSERT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT(event_generator, |
| UnorderedElementsAre(HasEventAtNode( |
| AXEventGenerator::Event::DOCUMENT_SELECTION_CHANGED, 1))); |
| } |
| |
| TEST(AXEventGeneratorTest, DocumentTitleChanged) { |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(1); |
| initial_state.nodes[0].id = 1; |
| initial_state.has_tree_data = true; |
| initial_state.tree_data.title = "Before"; |
| AXTree tree(initial_state); |
| |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate update = initial_state; |
| update.tree_data.title = "After"; |
| |
| ASSERT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT(event_generator, |
| UnorderedElementsAre(HasEventAtNode( |
| AXEventGenerator::Event::DOCUMENT_TITLE_CHANGED, 1))); |
| } |
| |
| TEST(AXEventGeneratorTest, ExpandedAndRowCount) { |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(4); |
| initial_state.nodes[0].id = 1; |
| initial_state.nodes[0].role = ax::mojom::Role::kRootWebArea; |
| initial_state.nodes[0].child_ids.push_back(2); |
| initial_state.nodes[0].child_ids.push_back(4); |
| initial_state.nodes[1].id = 2; |
| initial_state.nodes[1].role = ax::mojom::Role::kTable; |
| initial_state.nodes[1].child_ids.push_back(3); |
| initial_state.nodes[2].id = 3; |
| initial_state.nodes[2].role = ax::mojom::Role::kRow; |
| initial_state.nodes[3].id = 4; |
| initial_state.nodes[3].role = ax::mojom::Role::kPopUpButton; |
| initial_state.nodes[3].AddState(ax::mojom::State::kExpanded); |
| AXTree tree(initial_state); |
| |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate update = initial_state; |
| update.nodes[2].AddState(ax::mojom::State::kExpanded); |
| update.nodes[3].state = 0; |
| |
| ASSERT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT( |
| event_generator, |
| UnorderedElementsAre( |
| HasEventAtNode(AXEventGenerator::Event::COLLAPSED, 4), |
| HasEventAtNode(AXEventGenerator::Event::EXPANDED, 3), |
| HasEventAtNode(AXEventGenerator::Event::ROW_COUNT_CHANGED, 2), |
| HasEventAtNode(AXEventGenerator::Event::STATE_CHANGED, 3), |
| HasEventAtNode(AXEventGenerator::Event::WIN_IACCESSIBLE_STATE_CHANGED, |
| 3), |
| HasEventAtNode(AXEventGenerator::Event::STATE_CHANGED, 4), |
| HasEventAtNode(AXEventGenerator::Event::WIN_IACCESSIBLE_STATE_CHANGED, |
| 4))); |
| } |
| |
| TEST(AXEventGeneratorTest, SelectedAndSelectedChildren) { |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(4); |
| initial_state.nodes[0].id = 1; |
| initial_state.nodes[0].role = ax::mojom::Role::kRootWebArea; |
| initial_state.nodes[0].child_ids.push_back(2); |
| initial_state.nodes[0].child_ids.push_back(4); |
| initial_state.nodes[1].id = 2; |
| initial_state.nodes[1].role = ax::mojom::Role::kMenu; |
| initial_state.nodes[1].child_ids.push_back(3); |
| initial_state.nodes[2].id = 3; |
| initial_state.nodes[2].role = ax::mojom::Role::kMenuItem; |
| initial_state.nodes[3].id = 4; |
| initial_state.nodes[3].role = ax::mojom::Role::kListBoxOption; |
| initial_state.nodes[3].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, |
| true); |
| AXTree tree(initial_state); |
| |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate update = initial_state; |
| update.nodes[2].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true); |
| update.nodes.pop_back(); |
| update.nodes.emplace_back(); |
| update.nodes[3].id = 4; |
| update.nodes[3].role = ax::mojom::Role::kListBoxOption; |
| update.nodes[3].AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, false); |
| |
| ASSERT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT( |
| event_generator, |
| UnorderedElementsAre( |
| HasEventAtNode(AXEventGenerator::Event::SELECTED_CHILDREN_CHANGED, 2), |
| HasEventAtNode(AXEventGenerator::Event::SELECTED_CHANGED, 3), |
| HasEventAtNode(AXEventGenerator::Event::WIN_IACCESSIBLE_STATE_CHANGED, |
| 3), |
| HasEventAtNode(AXEventGenerator::Event::SELECTED_CHANGED, 4), |
| HasEventAtNode(AXEventGenerator::Event::WIN_IACCESSIBLE_STATE_CHANGED, |
| 4))); |
| } |
| |
| TEST(AXEventGeneratorTest, SelectedAndSelectedValueChanged) { |
| // This test is based on the following HTML snippet which produces the below |
| // simplified accessibility tree. |
| // |
| // <select> |
| // <option selected>Item 1</option> |
| // <option>Item 2</option> |
| // </select> |
| // <select size="2"> |
| // <option>Item 1</option> |
| // <option selected>Item 2</option> |
| // </select> |
| // |
| // kRootWebArea |
| // ++kPopUpButton value="Item 1" |
| // ++++kMenuListPopup invisible |
| // ++++++kMenuListOption name="Item 1" selected=true |
| // ++++++kMenuListOption name="Item 2" selected=false |
| // ++kListBox value="Item 2" |
| // ++++kListBoxOption name="Item 1" selected=false |
| // ++++kListBoxOption name="Item 2" selected=true |
| |
| AXNodeData root; |
| root.id = 1; |
| root.role = ax::mojom::Role::kRootWebArea; |
| |
| AXNodeData popup_button; |
| popup_button.id = 2; |
| popup_button.role = ax::mojom::Role::kPopUpButton; |
| popup_button.SetValue("Item 1"); |
| |
| AXNodeData menu_list_popup; |
| menu_list_popup.id = 3; |
| menu_list_popup.role = ax::mojom::Role::kMenuListPopup; |
| menu_list_popup.AddState(ax::mojom::State::kInvisible); |
| |
| AXNodeData menu_list_option_1; |
| menu_list_option_1.id = 4; |
| menu_list_option_1.role = ax::mojom::Role::kMenuListOption; |
| menu_list_option_1.SetName("Item 1"); |
| menu_list_option_1.AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, |
| true); |
| |
| AXNodeData menu_list_option_2; |
| menu_list_option_2.id = 5; |
| menu_list_option_2.role = ax::mojom::Role::kMenuListOption; |
| menu_list_option_2.SetName("Item 2"); |
| |
| AXNodeData list_box; |
| list_box.id = 6; |
| list_box.role = ax::mojom::Role::kListBox; |
| list_box.SetValue("Item 2"); |
| |
| AXNodeData list_box_option_1; |
| list_box_option_1.id = 7; |
| list_box_option_1.role = ax::mojom::Role::kListBoxOption; |
| list_box_option_1.SetName("Item 1"); |
| |
| AXNodeData list_box_option_2; |
| list_box_option_2.id = 8; |
| list_box_option_2.role = ax::mojom::Role::kRootWebArea; |
| list_box_option_2.SetName("Item 2"); |
| list_box_option_2.AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true); |
| |
| popup_button.child_ids = {menu_list_popup.id}; |
| menu_list_popup.child_ids = {menu_list_option_1.id, menu_list_option_2.id}; |
| list_box.child_ids = {list_box_option_1.id, list_box_option_2.id}; |
| root.child_ids = {popup_button.id, list_box.id}; |
| |
| AXTreeUpdate initial_state; |
| initial_state.root_id = root.id; |
| initial_state.nodes = {root, |
| popup_button, |
| menu_list_popup, |
| menu_list_option_1, |
| menu_list_option_2, |
| list_box, |
| list_box_option_1, |
| list_box_option_2}; |
| |
| AXTree tree(initial_state); |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| |
| popup_button.SetValue("Item 2"); |
| menu_list_option_1.RemoveBoolAttribute(ax::mojom::BoolAttribute::kSelected); |
| menu_list_option_2.AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, |
| true); |
| list_box.SetValue("Item 1"); |
| list_box_option_1.AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true); |
| list_box_option_2.RemoveBoolAttribute(ax::mojom::BoolAttribute::kSelected); |
| |
| AXTreeUpdate update; |
| update.nodes = {popup_button, menu_list_option_1, menu_list_option_2, |
| list_box, list_box_option_1, list_box_option_2}; |
| |
| ASSERT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT( |
| event_generator, |
| IsSupersetOf( |
| {HasEventAtNode(AXEventGenerator::Event::SELECTED_VALUE_CHANGED, |
| popup_button.id), |
| HasEventAtNode(AXEventGenerator::Event::SELECTED_CHANGED, |
| menu_list_option_1.id), |
| HasEventAtNode(AXEventGenerator::Event::SELECTED_CHANGED, |
| menu_list_option_2.id), |
| HasEventAtNode(AXEventGenerator::Event::SELECTED_VALUE_CHANGED, |
| list_box.id), |
| HasEventAtNode(AXEventGenerator::Event::SELECTED_CHANGED, |
| list_box_option_1.id), |
| HasEventAtNode(AXEventGenerator::Event::SELECTED_CHANGED, |
| list_box_option_2.id)})); |
| } |
| |
| TEST(AXEventGeneratorTest, SelectionInTextFieldChanged) { |
| AXNodeData root; |
| root.id = 1; |
| root.role = ax::mojom::Role::kRootWebArea; |
| |
| AXNodeData text_field; |
| text_field.id = 2; |
| text_field.role = ax::mojom::Role::kTextField; |
| text_field.SetValue("Testing"); |
| text_field.AddState(ax::mojom::State::kEditable); |
| text_field.AddBoolAttribute(ax::mojom::BoolAttribute::kEditableRoot, true); |
| |
| root.child_ids = {text_field.id}; |
| |
| AXTreeUpdate initial_state; |
| initial_state.root_id = root.id; |
| initial_state.nodes = {root, text_field}; |
| |
| AXTreeData tree_data; |
| tree_data.sel_anchor_object_id = text_field.id; |
| tree_data.sel_anchor_offset = 0; |
| tree_data.sel_focus_object_id = text_field.id; |
| tree_data.sel_focus_offset = 0; |
| initial_state.tree_data = tree_data; |
| initial_state.has_tree_data = true; |
| |
| AXTree tree(initial_state); |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| |
| { |
| AXTreeData tree_data; |
| tree_data.sel_anchor_object_id = text_field.id; |
| tree_data.sel_anchor_offset = 0; |
| tree_data.sel_focus_object_id = text_field.id; |
| tree_data.sel_focus_offset = 2; |
| AXTreeUpdate update; |
| update.tree_data = tree_data; |
| update.has_tree_data = true; |
| |
| ASSERT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT( |
| event_generator, |
| UnorderedElementsAre( |
| HasEventAtNode(AXEventGenerator::Event::DOCUMENT_SELECTION_CHANGED, |
| root.id), |
| HasEventAtNode( |
| AXEventGenerator::Event::SELECTION_IN_TEXT_FIELD_CHANGED, |
| text_field.id))); |
| } |
| |
| event_generator.ClearEvents(); |
| { |
| // A selection that does not include a text field in it should not raise the |
| // "SELECTION_IN_TEXT_FIELD_CHANGED" event. |
| AXTreeData tree_data; |
| tree_data.sel_anchor_object_id = root.id; |
| tree_data.sel_anchor_offset = 0; |
| tree_data.sel_focus_object_id = root.id; |
| tree_data.sel_focus_offset = 0; |
| AXTreeUpdate update; |
| update.tree_data = tree_data; |
| update.has_tree_data = true; |
| |
| ASSERT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT( |
| event_generator, |
| UnorderedElementsAre(HasEventAtNode( |
| AXEventGenerator::Event::DOCUMENT_SELECTION_CHANGED, root.id))); |
| } |
| |
| event_generator.ClearEvents(); |
| { |
| // A selection that spans more than one node but which nevertheless ends on |
| // a text field should still raise the "SELECTION_IN_TEXT_FIELD_CHANGED" |
| // event. |
| AXTreeData tree_data; |
| tree_data.sel_anchor_object_id = root.id; |
| tree_data.sel_anchor_offset = 0; |
| tree_data.sel_focus_object_id = text_field.id; |
| tree_data.sel_focus_offset = 2; |
| AXTreeUpdate update; |
| update.tree_data = tree_data; |
| update.has_tree_data = true; |
| |
| ASSERT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT( |
| event_generator, |
| UnorderedElementsAre( |
| HasEventAtNode(AXEventGenerator::Event::DOCUMENT_SELECTION_CHANGED, |
| root.id), |
| HasEventAtNode( |
| AXEventGenerator::Event::SELECTION_IN_TEXT_FIELD_CHANGED, |
| text_field.id))); |
| } |
| } |
| |
| TEST(AXEventGeneratorTest, ValueInTextFieldChanged) { |
| AXNodeData text_field; |
| text_field.id = 1; |
| text_field.role = ax::mojom::Role::kTextField; |
| text_field.AddState(ax::mojom::State::kEditable); |
| text_field.AddBoolAttribute(ax::mojom::BoolAttribute::kEditableRoot, true); |
| text_field.SetValue("Before"); |
| |
| AXTreeUpdate initial_state; |
| initial_state.root_id = text_field.id; |
| initial_state.nodes = {text_field}; |
| |
| AXTree tree(initial_state); |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| |
| text_field.SetValue("After"); |
| AXTreeUpdate update; |
| update.nodes = {text_field}; |
| |
| ASSERT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT(event_generator, |
| UnorderedElementsAre(HasEventAtNode( |
| AXEventGenerator::Event::VALUE_IN_TEXT_FIELD_CHANGED, |
| text_field.id))); |
| } |
| |
| TEST(AXEventGeneratorTest, FloatValueChanged) { |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(1); |
| initial_state.nodes[0].id = 1; |
| initial_state.nodes[0].role = ax::mojom::Role::kSlider; |
| initial_state.nodes[0].AddFloatAttribute( |
| ax::mojom::FloatAttribute::kValueForRange, 1.0); |
| AXTree tree(initial_state); |
| |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate update = initial_state; |
| update.nodes[0].float_attributes.clear(); |
| update.nodes[0].AddFloatAttribute(ax::mojom::FloatAttribute::kValueForRange, |
| 2.0); |
| |
| ASSERT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT(event_generator, |
| UnorderedElementsAre(HasEventAtNode( |
| AXEventGenerator::Event::RANGE_VALUE_CHANGED, 1))); |
| } |
| |
| TEST(AXEventGeneratorTest, InvalidStatusChanged) { |
| AXNodeData text_field; |
| text_field.id = 1; |
| text_field.role = ax::mojom::Role::kTextField; |
| text_field.AddState(ax::mojom::State::kEditable); |
| text_field.AddBoolAttribute(ax::mojom::BoolAttribute::kEditableRoot, true); |
| text_field.AddStringAttribute(ax::mojom::StringAttribute::kValue, "Text"); |
| |
| AXTreeUpdate initial_state; |
| initial_state.root_id = text_field.id; |
| initial_state.nodes = {text_field}; |
| |
| AXTree tree(initial_state); |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| |
| AXTreeUpdate update; |
| text_field.SetInvalidState(ax::mojom::InvalidState::kTrue); |
| update.nodes = {text_field}; |
| |
| ASSERT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT(event_generator, |
| UnorderedElementsAre(HasEventAtNode( |
| AXEventGenerator::Event::INVALID_STATUS_CHANGED, 1))); |
| } |
| |
| TEST(AXEventGeneratorTest, AddLiveRegionAttribute) { |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(1); |
| initial_state.nodes[0].id = 1; |
| AXTree tree(initial_state); |
| |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate update = initial_state; |
| update.nodes[0].AddStringAttribute(ax::mojom::StringAttribute::kLiveStatus, |
| "polite"); |
| ASSERT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT( |
| event_generator, |
| UnorderedElementsAre( |
| HasEventAtNode(AXEventGenerator::Event::LIVE_STATUS_CHANGED, 1), |
| HasEventAtNode(AXEventGenerator::Event::LIVE_REGION_CREATED, 1))); |
| |
| event_generator.ClearEvents(); |
| update.nodes[0].AddStringAttribute(ax::mojom::StringAttribute::kLiveStatus, |
| "assertive"); |
| ASSERT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT(event_generator, |
| UnorderedElementsAre(HasEventAtNode( |
| AXEventGenerator::Event::LIVE_STATUS_CHANGED, 1))); |
| |
| event_generator.ClearEvents(); |
| update.nodes[0].AddStringAttribute(ax::mojom::StringAttribute::kLiveStatus, |
| "off"); |
| |
| ASSERT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT(event_generator, |
| UnorderedElementsAre(HasEventAtNode( |
| AXEventGenerator::Event::LIVE_STATUS_CHANGED, 1))); |
| } |
| |
| TEST(AXEventGeneratorTest, CheckedStateChanged) { |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(1); |
| initial_state.nodes[0].id = 1; |
| initial_state.nodes[0].role = ax::mojom::Role::kCheckBox; |
| AXTree tree(initial_state); |
| |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate update = initial_state; |
| update.nodes[0].SetCheckedState(ax::mojom::CheckedState::kTrue); |
| ASSERT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT( |
| event_generator, |
| UnorderedElementsAre( |
| HasEventAtNode(AXEventGenerator::Event::CHECKED_STATE_CHANGED, 1), |
| HasEventAtNode(AXEventGenerator::Event::WIN_IACCESSIBLE_STATE_CHANGED, |
| 1))); |
| } |
| |
| TEST(AXEventGeneratorTest, ActiveDescendantChanged) { |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(3); |
| initial_state.nodes[0].id = 1; |
| initial_state.nodes[0].role = ax::mojom::Role::kListBox; |
| initial_state.nodes[0].child_ids.push_back(2); |
| initial_state.nodes[0].child_ids.push_back(3); |
| initial_state.nodes[0].AddIntAttribute( |
| ax::mojom::IntAttribute::kActivedescendantId, 2); |
| initial_state.nodes[1].id = 2; |
| initial_state.nodes[1].role = ax::mojom::Role::kListBoxOption; |
| initial_state.nodes[2].id = 3; |
| initial_state.nodes[2].role = ax::mojom::Role::kListBoxOption; |
| AXTree tree(initial_state); |
| |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate update = initial_state; |
| update.nodes[0].int_attributes.clear(); |
| update.nodes[0].AddIntAttribute(ax::mojom::IntAttribute::kActivedescendantId, |
| 3); |
| ASSERT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT( |
| event_generator, |
| UnorderedElementsAre( |
| HasEventAtNode(AXEventGenerator::Event::ACTIVE_DESCENDANT_CHANGED, 1), |
| HasEventAtNode(AXEventGenerator::Event::RELATED_NODE_CHANGED, 1))); |
| } |
| |
| TEST(AXEventGeneratorTest, CreateAlertAndLiveRegion) { |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(1); |
| initial_state.nodes[0].id = 1; |
| AXTree tree(initial_state); |
| |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate update = initial_state; |
| update.nodes.resize(4); |
| update.nodes[0].child_ids.push_back(2); |
| update.nodes[0].child_ids.push_back(3); |
| update.nodes[0].child_ids.push_back(4); |
| |
| update.nodes[1].id = 2; |
| update.nodes[1].AddStringAttribute(ax::mojom::StringAttribute::kLiveStatus, |
| "polite"); |
| |
| // Blink should automatically add aria-live="assertive" to elements with role |
| // kAlert, but we should fire an alert event regardless. |
| update.nodes[2].id = 3; |
| update.nodes[2].role = ax::mojom::Role::kAlert; |
| |
| // Elements with role kAlertDialog will *not* usually have a live region |
| // status, but again, we should always fire an alert event. |
| update.nodes[3].id = 4; |
| update.nodes[3].role = ax::mojom::Role::kAlertDialog; |
| |
| ASSERT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT( |
| event_generator, |
| UnorderedElementsAre( |
| HasEventAtNode(AXEventGenerator::Event::ALERT, 3), |
| HasEventAtNode(AXEventGenerator::Event::ALERT, 4), |
| HasEventAtNode(AXEventGenerator::Event::CHILDREN_CHANGED, 1), |
| HasEventAtNode(AXEventGenerator::Event::LIVE_REGION_CREATED, 2), |
| HasEventAtNode(AXEventGenerator::Event::SUBTREE_CREATED, 2), |
| HasEventAtNode(AXEventGenerator::Event::SUBTREE_CREATED, 3), |
| HasEventAtNode(AXEventGenerator::Event::SUBTREE_CREATED, 4))); |
| } |
| |
| TEST(AXEventGeneratorTest, LiveRegionChanged) { |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(3); |
| initial_state.nodes[0].id = 1; |
| initial_state.nodes[0].AddStringAttribute( |
| ax::mojom::StringAttribute::kLiveStatus, "polite"); |
| initial_state.nodes[0].AddStringAttribute( |
| ax::mojom::StringAttribute::kContainerLiveStatus, "polite"); |
| initial_state.nodes[0].child_ids.push_back(2); |
| initial_state.nodes[0].child_ids.push_back(3); |
| initial_state.nodes[1].id = 2; |
| initial_state.nodes[1].role = ax::mojom::Role::kStaticText; |
| initial_state.nodes[1].AddStringAttribute( |
| ax::mojom::StringAttribute::kContainerLiveStatus, "polite"); |
| initial_state.nodes[1].AddStringAttribute(ax::mojom::StringAttribute::kName, |
| "Before 1"); |
| initial_state.nodes[2].id = 3; |
| initial_state.nodes[2].role = ax::mojom::Role::kStaticText; |
| initial_state.nodes[2].AddStringAttribute( |
| ax::mojom::StringAttribute::kContainerLiveStatus, "polite"); |
| initial_state.nodes[2].AddStringAttribute(ax::mojom::StringAttribute::kName, |
| "Before 2"); |
| AXTree tree(initial_state); |
| |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate update = initial_state; |
| update.nodes[1].string_attributes.clear(); |
| update.nodes[1].AddStringAttribute( |
| ax::mojom::StringAttribute::kContainerLiveStatus, "polite"); |
| update.nodes[1].AddStringAttribute(ax::mojom::StringAttribute::kName, |
| "After 1"); |
| update.nodes[2].string_attributes.clear(); |
| update.nodes[2].AddStringAttribute( |
| ax::mojom::StringAttribute::kContainerLiveStatus, "polite"); |
| update.nodes[2].AddStringAttribute(ax::mojom::StringAttribute::kName, |
| "After 2"); |
| |
| ASSERT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT( |
| event_generator, |
| UnorderedElementsAre( |
| HasEventAtNode(AXEventGenerator::Event::LIVE_REGION_CHANGED, 1), |
| HasEventAtNode(AXEventGenerator::Event::LIVE_REGION_NODE_CHANGED, 2), |
| HasEventAtNode(AXEventGenerator::Event::LIVE_REGION_NODE_CHANGED, 3), |
| HasEventAtNode(AXEventGenerator::Event::NAME_CHANGED, 2), |
| HasEventAtNode(AXEventGenerator::Event::NAME_CHANGED, 3))); |
| } |
| |
| TEST(AXEventGeneratorTest, LiveRegionOnlyTextChanges) { |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(3); |
| initial_state.nodes[0].id = 1; |
| initial_state.nodes[0].AddStringAttribute( |
| ax::mojom::StringAttribute::kLiveStatus, "polite"); |
| initial_state.nodes[0].AddStringAttribute( |
| ax::mojom::StringAttribute::kContainerLiveStatus, "polite"); |
| initial_state.nodes[0].child_ids.push_back(2); |
| initial_state.nodes[0].child_ids.push_back(3); |
| initial_state.nodes[1].id = 2; |
| initial_state.nodes[1].role = ax::mojom::Role::kStaticText; |
| initial_state.nodes[1].AddStringAttribute( |
| ax::mojom::StringAttribute::kContainerLiveStatus, "polite"); |
| initial_state.nodes[1].AddStringAttribute(ax::mojom::StringAttribute::kName, |
| "Before 1"); |
| initial_state.nodes[2].id = 3; |
| initial_state.nodes[2].role = ax::mojom::Role::kStaticText; |
| initial_state.nodes[2].AddStringAttribute( |
| ax::mojom::StringAttribute::kContainerLiveStatus, "polite"); |
| initial_state.nodes[2].AddStringAttribute(ax::mojom::StringAttribute::kName, |
| "Before 2"); |
| AXTree tree(initial_state); |
| |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate update = initial_state; |
| update.nodes[1].AddStringAttribute(ax::mojom::StringAttribute::kDescription, |
| "Description 1"); |
| update.nodes[2].SetCheckedState(ax::mojom::CheckedState::kTrue); |
| |
| // Note that we do NOT expect a LIVE_REGION_CHANGED event here, because |
| // the name did not change. |
| ASSERT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT( |
| event_generator, |
| UnorderedElementsAre( |
| HasEventAtNode(AXEventGenerator::Event::CHECKED_STATE_CHANGED, 3), |
| HasEventAtNode(AXEventGenerator::Event::WIN_IACCESSIBLE_STATE_CHANGED, |
| 3), |
| HasEventAtNode(AXEventGenerator::Event::DESCRIPTION_CHANGED, 2))); |
| } |
| |
| TEST(AXEventGeneratorTest, BusyLiveRegionChanged) { |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(3); |
| initial_state.nodes[0].id = 1; |
| initial_state.nodes[0].AddStringAttribute( |
| ax::mojom::StringAttribute::kLiveStatus, "polite"); |
| initial_state.nodes[0].AddStringAttribute( |
| ax::mojom::StringAttribute::kContainerLiveStatus, "polite"); |
| initial_state.nodes[0].AddBoolAttribute(ax::mojom::BoolAttribute::kBusy, |
| true); |
| initial_state.nodes[0].child_ids.push_back(2); |
| initial_state.nodes[0].child_ids.push_back(3); |
| initial_state.nodes[1].id = 2; |
| initial_state.nodes[1].role = ax::mojom::Role::kStaticText; |
| initial_state.nodes[1].AddStringAttribute( |
| ax::mojom::StringAttribute::kContainerLiveStatus, "polite"); |
| initial_state.nodes[1].AddStringAttribute(ax::mojom::StringAttribute::kName, |
| "Before 1"); |
| initial_state.nodes[2].id = 3; |
| initial_state.nodes[2].role = ax::mojom::Role::kStaticText; |
| initial_state.nodes[2].AddStringAttribute( |
| ax::mojom::StringAttribute::kContainerLiveStatus, "polite"); |
| initial_state.nodes[2].AddStringAttribute(ax::mojom::StringAttribute::kName, |
| "Before 2"); |
| AXTree tree(initial_state); |
| |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate update = initial_state; |
| update.nodes[1].string_attributes.clear(); |
| update.nodes[1].AddStringAttribute( |
| ax::mojom::StringAttribute::kContainerLiveStatus, "polite"); |
| update.nodes[1].AddStringAttribute(ax::mojom::StringAttribute::kName, |
| "After 1"); |
| update.nodes[2].string_attributes.clear(); |
| update.nodes[2].AddStringAttribute( |
| ax::mojom::StringAttribute::kContainerLiveStatus, "polite"); |
| update.nodes[2].AddStringAttribute(ax::mojom::StringAttribute::kName, |
| "After 2"); |
| |
| ASSERT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT(event_generator, |
| UnorderedElementsAre( |
| HasEventAtNode(AXEventGenerator::Event::NAME_CHANGED, 2), |
| HasEventAtNode(AXEventGenerator::Event::NAME_CHANGED, 3))); |
| } |
| |
| TEST(AXEventGeneratorTest, AddChild) { |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(2); |
| initial_state.nodes[0].id = 1; |
| initial_state.nodes[0].child_ids.push_back(2); |
| initial_state.nodes[1].id = 2; |
| AXTree tree(initial_state); |
| |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate update = initial_state; |
| update.nodes.resize(3); |
| update.nodes[0].child_ids.push_back(3); |
| update.nodes[2].id = 3; |
| |
| ASSERT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT(event_generator, |
| UnorderedElementsAre( |
| HasEventAtNode(AXEventGenerator::Event::CHILDREN_CHANGED, 1), |
| HasEventAtNode(AXEventGenerator::Event::SUBTREE_CREATED, 3))); |
| } |
| |
| TEST(AXEventGeneratorTest, RemoveChild) { |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(3); |
| initial_state.nodes[0].id = 1; |
| initial_state.nodes[0].child_ids.push_back(2); |
| initial_state.nodes[0].child_ids.push_back(3); |
| initial_state.nodes[1].id = 2; |
| initial_state.nodes[2].id = 3; |
| AXTree tree(initial_state); |
| |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate update = initial_state; |
| update.nodes.resize(2); |
| update.nodes[0].child_ids.clear(); |
| update.nodes[0].child_ids.push_back(2); |
| |
| ASSERT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT(event_generator, |
| UnorderedElementsAre(HasEventAtNode( |
| AXEventGenerator::Event::CHILDREN_CHANGED, 1))); |
| } |
| |
| TEST(AXEventGeneratorTest, ReorderChildren) { |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(3); |
| initial_state.nodes[0].id = 1; |
| initial_state.nodes[0].child_ids.push_back(2); |
| initial_state.nodes[0].child_ids.push_back(3); |
| initial_state.nodes[1].id = 2; |
| initial_state.nodes[2].id = 3; |
| AXTree tree(initial_state); |
| |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate update = initial_state; |
| update.nodes[0].child_ids.clear(); |
| update.nodes[0].child_ids.push_back(3); |
| update.nodes[0].child_ids.push_back(2); |
| |
| ASSERT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT(event_generator, |
| UnorderedElementsAre(HasEventAtNode( |
| AXEventGenerator::Event::CHILDREN_CHANGED, 1))); |
| } |
| |
| TEST(AXEventGeneratorTest, ScrollHorizontalPositionChanged) { |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(1); |
| initial_state.nodes[0].id = 1; |
| AXTree tree(initial_state); |
| |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate update = initial_state; |
| update.nodes[0].AddIntAttribute(ax::mojom::IntAttribute::kScrollX, 10); |
| EXPECT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT( |
| event_generator, |
| UnorderedElementsAre(HasEventAtNode( |
| AXEventGenerator::Event::SCROLL_HORIZONTAL_POSITION_CHANGED, 1))); |
| } |
| |
| TEST(AXEventGeneratorTest, ScrollVerticalPositionChanged) { |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(1); |
| initial_state.nodes[0].id = 1; |
| AXTree tree(initial_state); |
| |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate update = initial_state; |
| update.nodes[0].AddIntAttribute(ax::mojom::IntAttribute::kScrollY, 10); |
| ASSERT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT( |
| event_generator, |
| UnorderedElementsAre(HasEventAtNode( |
| AXEventGenerator::Event::SCROLL_VERTICAL_POSITION_CHANGED, 1))); |
| } |
| |
| TEST(AXEventGeneratorTest, TextAttributeChanged) { |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(17); |
| initial_state.nodes[0].id = 1; |
| initial_state.nodes[0].child_ids = {2, 3, 4, 5, 6, 7, 8, 9, |
| 10, 11, 12, 13, 14, 15, 16, 17}; |
| initial_state.nodes[1].id = 2; |
| initial_state.nodes[2].id = 3; |
| initial_state.nodes[3].id = 4; |
| initial_state.nodes[4].id = 5; |
| initial_state.nodes[5].id = 6; |
| initial_state.nodes[6].id = 7; |
| initial_state.nodes[7].id = 8; |
| initial_state.nodes[8].id = 9; |
| initial_state.nodes[9].id = 10; |
| initial_state.nodes[10].id = 11; |
| initial_state.nodes[11].id = 12; |
| initial_state.nodes[12].id = 13; |
| initial_state.nodes[13].id = 14; |
| initial_state.nodes[14].id = 15; |
| initial_state.nodes[15].id = 16; |
| initial_state.nodes[16].id = 17; |
| |
| // To test changing the start and end of existing markers. |
| initial_state.nodes[11].AddIntListAttribute( |
| ax::mojom::IntListAttribute::kMarkerTypes, |
| {static_cast<int32_t>(ax::mojom::MarkerType::kTextMatch)}); |
| initial_state.nodes[11].AddIntListAttribute( |
| ax::mojom::IntListAttribute::kMarkerStarts, {5}); |
| initial_state.nodes[11].AddIntListAttribute( |
| ax::mojom::IntListAttribute::kMarkerEnds, {10}); |
| |
| AXTree tree(initial_state); |
| |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate update = initial_state; |
| update.nodes[1].AddIntAttribute(ax::mojom::IntAttribute::kColor, 0); |
| update.nodes[2].AddIntAttribute(ax::mojom::IntAttribute::kBackgroundColor, 0); |
| update.nodes[3].AddIntAttribute( |
| ax::mojom::IntAttribute::kTextDirection, |
| static_cast<int32_t>(ax::mojom::WritingDirection::kRtl)); |
| update.nodes[4].AddIntAttribute( |
| ax::mojom::IntAttribute::kTextPosition, |
| static_cast<int32_t>(ax::mojom::TextPosition::kSuperscript)); |
| update.nodes[5].AddIntAttribute( |
| ax::mojom::IntAttribute::kTextStyle, |
| static_cast<int32_t>(ax::mojom::TextStyle::kBold)); |
| update.nodes[6].AddIntAttribute( |
| ax::mojom::IntAttribute::kTextOverlineStyle, |
| static_cast<int32_t>(ax::mojom::TextDecorationStyle::kSolid)); |
| update.nodes[7].AddIntAttribute( |
| ax::mojom::IntAttribute::kTextStrikethroughStyle, |
| static_cast<int32_t>(ax::mojom::TextDecorationStyle::kWavy)); |
| update.nodes[8].AddIntAttribute( |
| ax::mojom::IntAttribute::kTextUnderlineStyle, |
| static_cast<int32_t>(ax::mojom::TextDecorationStyle::kDotted)); |
| update.nodes[9].AddIntListAttribute( |
| ax::mojom::IntListAttribute::kMarkerTypes, |
| {static_cast<int32_t>(ax::mojom::MarkerType::kSpelling)}); |
| update.nodes[10].AddIntListAttribute( |
| ax::mojom::IntListAttribute::kMarkerTypes, |
| {static_cast<int32_t>(ax::mojom::MarkerType::kGrammar)}); |
| update.nodes[11].AddIntListAttribute(ax::mojom::IntListAttribute::kMarkerEnds, |
| {11}); |
| update.nodes[12].AddIntListAttribute( |
| ax::mojom::IntListAttribute::kMarkerTypes, |
| {static_cast<int32_t>(ax::mojom::MarkerType::kActiveSuggestion)}); |
| update.nodes[13].AddIntListAttribute( |
| ax::mojom::IntListAttribute::kMarkerTypes, |
| {static_cast<int32_t>(ax::mojom::MarkerType::kSuggestion)}); |
| update.nodes[14].AddFloatAttribute(ax::mojom::FloatAttribute::kFontSize, |
| 12.0f); |
| update.nodes[15].AddFloatAttribute(ax::mojom::FloatAttribute::kFontWeight, |
| 600.0f); |
| update.nodes[16].AddStringAttribute(ax::mojom::StringAttribute::kFontFamily, |
| "sans"); |
| |
| ASSERT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT( |
| event_generator, |
| UnorderedElementsAre( |
| HasEventAtNode(AXEventGenerator::Event::TEXT_ATTRIBUTE_CHANGED, 2), |
| HasEventAtNode(AXEventGenerator::Event::TEXT_ATTRIBUTE_CHANGED, 3), |
| HasEventAtNode(AXEventGenerator::Event::TEXT_ATTRIBUTE_CHANGED, 4), |
| HasEventAtNode(AXEventGenerator::Event::TEXT_ATTRIBUTE_CHANGED, 5), |
| HasEventAtNode(AXEventGenerator::Event::TEXT_ATTRIBUTE_CHANGED, 6), |
| HasEventAtNode(AXEventGenerator::Event::TEXT_ATTRIBUTE_CHANGED, 7), |
| HasEventAtNode(AXEventGenerator::Event::TEXT_ATTRIBUTE_CHANGED, 8), |
| HasEventAtNode(AXEventGenerator::Event::TEXT_ATTRIBUTE_CHANGED, 9), |
| HasEventAtNode(AXEventGenerator::Event::TEXT_ATTRIBUTE_CHANGED, 10), |
| HasEventAtNode(AXEventGenerator::Event::TEXT_ATTRIBUTE_CHANGED, 11), |
| HasEventAtNode(AXEventGenerator::Event::TEXT_ATTRIBUTE_CHANGED, 12), |
| HasEventAtNode(AXEventGenerator::Event::TEXT_ATTRIBUTE_CHANGED, 13), |
| HasEventAtNode(AXEventGenerator::Event::TEXT_ATTRIBUTE_CHANGED, 14), |
| HasEventAtNode(AXEventGenerator::Event::TEXT_ATTRIBUTE_CHANGED, 15), |
| HasEventAtNode(AXEventGenerator::Event::TEXT_ATTRIBUTE_CHANGED, 16), |
| HasEventAtNode(AXEventGenerator::Event::TEXT_ATTRIBUTE_CHANGED, 17))); |
| } |
| |
| TEST(AXEventGeneratorTest, ObjectAttributeChanged) { |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(3); |
| initial_state.nodes[0].id = 1; |
| initial_state.nodes[0].child_ids = {2, 3}; |
| initial_state.nodes[1].id = 2; |
| initial_state.nodes[2].id = 3; |
| AXTree tree(initial_state); |
| |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate update = initial_state; |
| update.nodes[1].AddIntAttribute(ax::mojom::IntAttribute::kTextAlign, 2); |
| update.nodes[2].AddFloatAttribute(ax::mojom::FloatAttribute::kTextIndent, |
| 10.0f); |
| ASSERT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT( |
| event_generator, |
| UnorderedElementsAre( |
| HasEventAtNode( |
| AXEventGenerator::Event::ATK_TEXT_OBJECT_ATTRIBUTE_CHANGED, 2), |
| HasEventAtNode( |
| AXEventGenerator::Event::ATK_TEXT_OBJECT_ATTRIBUTE_CHANGED, 3), |
| HasEventAtNode(AXEventGenerator::Event::OBJECT_ATTRIBUTE_CHANGED, 2), |
| HasEventAtNode(AXEventGenerator::Event::OBJECT_ATTRIBUTE_CHANGED, |
| 3))); |
| } |
| |
| TEST(AXEventGeneratorTest, OtherAttributeChanged) { |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(6); |
| initial_state.nodes[0].id = 1; |
| initial_state.nodes[0].child_ids.push_back(2); |
| initial_state.nodes[0].child_ids.push_back(3); |
| initial_state.nodes[0].child_ids.push_back(4); |
| initial_state.nodes[0].child_ids.push_back(5); |
| initial_state.nodes[0].child_ids.push_back(6); |
| initial_state.nodes[1].id = 2; |
| initial_state.nodes[2].id = 3; |
| initial_state.nodes[3].id = 4; |
| initial_state.nodes[4].id = 5; |
| initial_state.nodes[5].id = 6; |
| AXTree tree(initial_state); |
| |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate update = initial_state; |
| update.nodes[1].AddStringAttribute(ax::mojom::StringAttribute::kLanguage, |
| "de"); |
| update.nodes[2].AddIntAttribute(ax::mojom::IntAttribute::kAriaCellColumnIndex, |
| 7); |
| update.nodes[3].AddFloatAttribute(ax::mojom::FloatAttribute::kFontSize, |
| 12.0f); |
| update.nodes[4].AddBoolAttribute(ax::mojom::BoolAttribute::kModal, true); |
| std::vector<int> ids = {2}; |
| update.nodes[5].AddIntListAttribute(ax::mojom::IntListAttribute::kControlsIds, |
| ids); |
| ASSERT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT( |
| event_generator, |
| UnorderedElementsAre( |
| HasEventAtNode(AXEventGenerator::Event::CONTROLS_CHANGED, 6), |
| HasEventAtNode(AXEventGenerator::Event::LANGUAGE_CHANGED, 2), |
| HasEventAtNode(AXEventGenerator::Event::OTHER_ATTRIBUTE_CHANGED, 3), |
| HasEventAtNode(AXEventGenerator::Event::TEXT_ATTRIBUTE_CHANGED, 4), |
| HasEventAtNode(AXEventGenerator::Event::OTHER_ATTRIBUTE_CHANGED, 5), |
| HasEventAtNode(AXEventGenerator::Event::RELATED_NODE_CHANGED, 6))); |
| } |
| |
| TEST(AXEventGeneratorTest, NameChanged) { |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(2); |
| initial_state.nodes[0].id = 1; |
| initial_state.nodes[0].child_ids.push_back(2); |
| initial_state.nodes[1].id = 2; |
| AXTree tree(initial_state); |
| |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate update = initial_state; |
| update.nodes[1].AddStringAttribute(ax::mojom::StringAttribute::kName, |
| "Hello"); |
| ASSERT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT(event_generator, UnorderedElementsAre(HasEventAtNode( |
| AXEventGenerator::Event::NAME_CHANGED, 2))); |
| } |
| |
| TEST(AXEventGeneratorTest, DescriptionChanged) { |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(1); |
| initial_state.nodes[0].id = 1; |
| AXTree tree(initial_state); |
| |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate update = initial_state; |
| update.nodes[0].AddStringAttribute(ax::mojom::StringAttribute::kDescription, |
| "Hello"); |
| ASSERT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT(event_generator, |
| UnorderedElementsAre(HasEventAtNode( |
| AXEventGenerator::Event::DESCRIPTION_CHANGED, 1))); |
| } |
| |
| TEST(AXEventGeneratorTest, RoleChanged) { |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(1); |
| initial_state.nodes[0].id = 1; |
| AXTree tree(initial_state); |
| |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate update = initial_state; |
| update.nodes[0].role = ax::mojom::Role::kCheckBox; |
| ASSERT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT(event_generator, UnorderedElementsAre(HasEventAtNode( |
| AXEventGenerator::Event::ROLE_CHANGED, 1))); |
| } |
| |
| TEST(AXEventGeneratorTest, MenuItemSelected) { |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(3); |
| initial_state.nodes[0].id = 1; |
| initial_state.nodes[0].role = ax::mojom::Role::kMenu; |
| initial_state.nodes[0].child_ids.push_back(2); |
| initial_state.nodes[0].child_ids.push_back(3); |
| initial_state.nodes[0].AddIntAttribute( |
| ax::mojom::IntAttribute::kActivedescendantId, 2); |
| initial_state.nodes[1].id = 2; |
| initial_state.nodes[1].role = ax::mojom::Role::kMenuListOption; |
| initial_state.nodes[2].id = 3; |
| initial_state.nodes[2].role = ax::mojom::Role::kMenuListOption; |
| AXTree tree(initial_state); |
| |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate update = initial_state; |
| update.nodes[0].int_attributes.clear(); |
| update.nodes[0].AddIntAttribute(ax::mojom::IntAttribute::kActivedescendantId, |
| 3); |
| ASSERT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT( |
| event_generator, |
| UnorderedElementsAre( |
| HasEventAtNode(AXEventGenerator::Event::ACTIVE_DESCENDANT_CHANGED, 1), |
| HasEventAtNode(AXEventGenerator::Event::MENU_ITEM_SELECTED, 3), |
| HasEventAtNode(AXEventGenerator::Event::RELATED_NODE_CHANGED, 1))); |
| } |
| |
| TEST(AXEventGeneratorTest, NodeBecomesIgnored) { |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(5); |
| initial_state.nodes[0].id = 1; |
| initial_state.nodes[0].role = ax::mojom::Role::kRootWebArea; |
| initial_state.nodes[0].child_ids.push_back(2); |
| initial_state.nodes[1].id = 2; |
| initial_state.nodes[1].role = ax::mojom::Role::kArticle; |
| initial_state.nodes[1].child_ids.push_back(3); |
| initial_state.nodes[2].id = 3; |
| initial_state.nodes[2].role = ax::mojom::Role::kGroup; |
| initial_state.nodes[2].AddState(ax::mojom::State::kIgnored); |
| initial_state.nodes[2].child_ids.push_back(4); |
| initial_state.nodes[3].id = 4; |
| initial_state.nodes[3].role = ax::mojom::Role::kGroup; |
| initial_state.nodes[3].child_ids.push_back(5); |
| initial_state.nodes[4].id = 5; |
| initial_state.nodes[4].role = ax::mojom::Role::kStaticText; |
| |
| AXTree tree(initial_state); |
| |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate update = initial_state; |
| update.nodes[3].AddState(ax::mojom::State::kIgnored); |
| ASSERT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT(event_generator, |
| UnorderedElementsAre( |
| HasEventAtNode(AXEventGenerator::Event::CHILDREN_CHANGED, 2), |
| HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 4), |
| HasEventAtNode(AXEventGenerator::Event::PARENT_CHANGED, 5))); |
| } |
| |
| TEST(AXEventGeneratorTest, NodeBecomesIgnored2) { |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(5); |
| initial_state.nodes[0].id = 1; |
| initial_state.nodes[0].role = ax::mojom::Role::kRootWebArea; |
| initial_state.nodes[0].child_ids.push_back(2); |
| initial_state.nodes[1].id = 2; |
| initial_state.nodes[1].role = ax::mojom::Role::kArticle; |
| initial_state.nodes[1].child_ids.push_back(3); |
| initial_state.nodes[2].id = 3; |
| initial_state.nodes[2].role = ax::mojom::Role::kGroup; |
| initial_state.nodes[2].AddState(ax::mojom::State::kIgnored); |
| initial_state.nodes[2].child_ids.push_back(4); |
| initial_state.nodes[3].id = 4; |
| initial_state.nodes[3].role = ax::mojom::Role::kGroup; |
| initial_state.nodes[3].child_ids.push_back(5); |
| initial_state.nodes[4].id = 5; |
| initial_state.nodes[4].role = ax::mojom::Role::kStaticText; |
| |
| AXTree tree(initial_state); |
| |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate update = initial_state; |
| // Marking as ignored should fire CHILDREN_CHANGED on 2 |
| update.nodes[3].AddState(ax::mojom::State::kIgnored); |
| // Remove node id 5 so it also fires CHILDREN_CHANGED on 4. |
| update.nodes.pop_back(); |
| update.nodes[3].child_ids.clear(); |
| ASSERT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT(event_generator, |
| UnorderedElementsAre( |
| HasEventAtNode(AXEventGenerator::Event::CHILDREN_CHANGED, 2), |
| HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 4))); |
| } |
| |
| TEST(AXEventGeneratorTest, NodeBecomesUnignored) { |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(5); |
| initial_state.nodes[0].id = 1; |
| initial_state.nodes[0].role = ax::mojom::Role::kRootWebArea; |
| initial_state.nodes[0].child_ids.push_back(2); |
| initial_state.nodes[1].id = 2; |
| initial_state.nodes[1].role = ax::mojom::Role::kArticle; |
| initial_state.nodes[1].child_ids.push_back(3); |
| initial_state.nodes[2].id = 3; |
| initial_state.nodes[2].role = ax::mojom::Role::kGroup; |
| initial_state.nodes[2].AddState(ax::mojom::State::kIgnored); |
| initial_state.nodes[2].child_ids.push_back(4); |
| initial_state.nodes[3].id = 4; |
| initial_state.nodes[3].role = ax::mojom::Role::kGroup; |
| initial_state.nodes[3].AddState(ax::mojom::State::kIgnored); |
| initial_state.nodes[3].child_ids.push_back(5); |
| initial_state.nodes[4].id = 5; |
| initial_state.nodes[4].role = ax::mojom::Role::kStaticText; |
| |
| AXTree tree(initial_state); |
| |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate update = initial_state; |
| update.nodes[3].state = 0; |
| ASSERT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT(event_generator, |
| UnorderedElementsAre( |
| HasEventAtNode(AXEventGenerator::Event::CHILDREN_CHANGED, 2), |
| HasEventAtNode(AXEventGenerator::Event::SUBTREE_CREATED, 4), |
| HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 4), |
| HasEventAtNode(AXEventGenerator::Event::PARENT_CHANGED, 5))); |
| } |
| |
| TEST(AXEventGeneratorTest, NodeBecomesUnignored2) { |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(5); |
| initial_state.nodes[0].id = 1; |
| initial_state.nodes[0].role = ax::mojom::Role::kRootWebArea; |
| initial_state.nodes[0].child_ids.push_back(2); |
| initial_state.nodes[1].id = 2; |
| initial_state.nodes[1].role = ax::mojom::Role::kArticle; |
| initial_state.nodes[1].child_ids.push_back(3); |
| initial_state.nodes[2].id = 3; |
| initial_state.nodes[2].role = ax::mojom::Role::kGroup; |
| initial_state.nodes[2].AddState(ax::mojom::State::kIgnored); |
| initial_state.nodes[2].child_ids.push_back(4); |
| initial_state.nodes[3].id = 4; |
| initial_state.nodes[3].role = ax::mojom::Role::kGroup; |
| initial_state.nodes[3].AddState(ax::mojom::State::kIgnored); |
| initial_state.nodes[3].child_ids.push_back(5); |
| initial_state.nodes[4].id = 5; |
| initial_state.nodes[4].role = ax::mojom::Role::kStaticText; |
| |
| AXTree tree(initial_state); |
| |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate update = initial_state; |
| // Marking as no longer ignored should fire CHILDREN_CHANGED on 2 |
| update.nodes[3].state = 0; |
| // Remove node id 5 so it also fires CHILDREN_CHANGED on 4. |
| update.nodes.pop_back(); |
| update.nodes[3].child_ids.clear(); |
| ASSERT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT(event_generator, |
| UnorderedElementsAre( |
| HasEventAtNode(AXEventGenerator::Event::CHILDREN_CHANGED, 2), |
| HasEventAtNode(AXEventGenerator::Event::SUBTREE_CREATED, 4), |
| HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 4))); |
| } |
| |
| TEST(AXEventGeneratorTest, NodeInsertedViaRoleChange) { |
| // This test inserts a kSearch in between the kRootWebArea and the kTextField, |
| // but the node id are updated reflecting position in the tree. This results |
| // in node 2's role changing along with node 3 being created and added as a |
| // child of node 2. |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(2); |
| initial_state.nodes[0].id = 1; |
| initial_state.nodes[0].role = ax::mojom::Role::kRootWebArea; |
| initial_state.nodes[0].child_ids.push_back(2); |
| initial_state.nodes[1].id = 2; |
| initial_state.nodes[1].role = ax::mojom::Role::kTextField; |
| AXTree tree(initial_state); |
| |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate update; |
| update.root_id = 1; |
| update.nodes.resize(3); |
| update.nodes[0].id = 1; |
| update.nodes[0].role = ax::mojom::Role::kRootWebArea; |
| update.nodes[0].child_ids.push_back(2); |
| update.nodes[1].id = 2; |
| update.nodes[1].role = ax::mojom::Role::kSearch; |
| update.nodes[1].child_ids.push_back(3); |
| update.nodes[2].id = 3; |
| update.nodes[2].role = ax::mojom::Role::kTextField; |
| ASSERT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT(event_generator, |
| UnorderedElementsAre( |
| HasEventAtNode(AXEventGenerator::Event::SUBTREE_CREATED, 3), |
| HasEventAtNode(AXEventGenerator::Event::CHILDREN_CHANGED, 2), |
| HasEventAtNode(AXEventGenerator::Event::ROLE_CHANGED, 2))); |
| } |
| |
| TEST(AXEventGeneratorTest, NodeInserted) { |
| // This test inserts a kSearch in between the kRootWebArea and the kTextField. |
| // The node ids reflect the creation order, and the kTextField is not changed. |
| // Thus this is more like a reparenting. |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(2); |
| initial_state.nodes[0].id = 1; |
| initial_state.nodes[0].role = ax::mojom::Role::kRootWebArea; |
| initial_state.nodes[0].child_ids.push_back(2); |
| initial_state.nodes[1].id = 2; |
| initial_state.nodes[1].role = ax::mojom::Role::kTextField; |
| AXTree tree(initial_state); |
| |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate update; |
| update.root_id = 1; |
| update.nodes.resize(3); |
| update.nodes[0].id = 1; |
| update.nodes[0].role = ax::mojom::Role::kRootWebArea; |
| update.nodes[0].child_ids.push_back(3); |
| update.nodes[1].id = 3; |
| update.nodes[1].role = ax::mojom::Role::kSearch; |
| update.nodes[1].child_ids.push_back(2); |
| update.nodes[2].id = 2; |
| update.nodes[2].role = ax::mojom::Role::kTextField; |
| ASSERT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT(event_generator, |
| UnorderedElementsAre( |
| HasEventAtNode(AXEventGenerator::Event::SUBTREE_CREATED, 3), |
| HasEventAtNode(AXEventGenerator::Event::CHILDREN_CHANGED, 1), |
| HasEventAtNode(AXEventGenerator::Event::PARENT_CHANGED, 2))); |
| } |
| |
| TEST(AXEventGeneratorTest, SubtreeBecomesUnignored) { |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(3); |
| initial_state.nodes[0].id = 1; |
| initial_state.nodes[0].role = ax::mojom::Role::kRootWebArea; |
| initial_state.nodes[0].child_ids.push_back(2); |
| initial_state.nodes[1].id = 2; |
| initial_state.nodes[1].role = ax::mojom::Role::kArticle; |
| initial_state.nodes[1].AddState(ax::mojom::State::kIgnored); |
| initial_state.nodes[1].child_ids.push_back(3); |
| initial_state.nodes[2].id = 3; |
| initial_state.nodes[2].role = ax::mojom::Role::kGroup; |
| initial_state.nodes[2].AddState(ax::mojom::State::kIgnored); |
| |
| AXTree tree(initial_state); |
| |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate update = initial_state; |
| update.nodes[1].RemoveState(ax::mojom::State::kIgnored); |
| update.nodes[2].RemoveState(ax::mojom::State::kIgnored); |
| ASSERT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT(event_generator, |
| UnorderedElementsAre( |
| HasEventAtNode(AXEventGenerator::Event::CHILDREN_CHANGED, 1), |
| HasEventAtNode(AXEventGenerator::Event::SUBTREE_CREATED, 2), |
| HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 2))); |
| } |
| |
| TEST(AXEventGeneratorTest, TwoNodesSwapIgnored) { |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(3); |
| initial_state.nodes[0].id = 1; |
| initial_state.nodes[0].role = ax::mojom::Role::kRootWebArea; |
| initial_state.nodes[0].child_ids.push_back(2); |
| initial_state.nodes[1].id = 2; |
| initial_state.nodes[1].role = ax::mojom::Role::kArticle; |
| initial_state.nodes[1].child_ids.push_back(3); |
| initial_state.nodes[2].id = 3; |
| initial_state.nodes[2].role = ax::mojom::Role::kGroup; |
| initial_state.nodes[2].AddState(ax::mojom::State::kIgnored); |
| |
| AXTree tree(initial_state); |
| |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate update = initial_state; |
| update.nodes[1].AddState(ax::mojom::State::kIgnored); |
| update.nodes[2].RemoveState(ax::mojom::State::kIgnored); |
| ASSERT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT(event_generator, |
| UnorderedElementsAre( |
| HasEventAtNode(AXEventGenerator::Event::CHILDREN_CHANGED, 1), |
| HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 2), |
| HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 3), |
| HasEventAtNode(AXEventGenerator::Event::SUBTREE_CREATED, 3))); |
| } |
| |
| TEST(AXEventGeneratorTest, TwoNodesSwapIgnored2) { |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(3); |
| initial_state.nodes[0].id = 1; |
| initial_state.nodes[0].role = ax::mojom::Role::kRootWebArea; |
| initial_state.nodes[0].child_ids.push_back(2); |
| initial_state.nodes[1].id = 2; |
| initial_state.nodes[1].role = ax::mojom::Role::kArticle; |
| initial_state.nodes[1].AddState(ax::mojom::State::kIgnored); |
| initial_state.nodes[1].child_ids.push_back(3); |
| initial_state.nodes[2].id = 3; |
| initial_state.nodes[2].role = ax::mojom::Role::kGroup; |
| |
| AXTree tree(initial_state); |
| |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate update = initial_state; |
| update.nodes[1].RemoveState(ax::mojom::State::kIgnored); |
| update.nodes[2].AddState(ax::mojom::State::kIgnored); |
| ASSERT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT(event_generator, |
| UnorderedElementsAre( |
| HasEventAtNode(AXEventGenerator::Event::CHILDREN_CHANGED, 1), |
| HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 2), |
| HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 3), |
| HasEventAtNode(AXEventGenerator::Event::SUBTREE_CREATED, 2))); |
| } |
| |
| TEST(AXEventGeneratorTest, IgnoredChangedFiredOnAncestorOnly1) { |
| // BEFORE |
| // 1 (IGN) |
| // / \ |
| // 2 3 (IGN) |
| |
| // AFTER |
| // 1 (IGN) |
| // / \ |
| // 2 (IGN) 3 |
| // IGNORED_CHANGED expected on #2, #3 |
| |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(3); |
| initial_state.nodes[0].id = 1; |
| initial_state.nodes[0].role = ax::mojom::Role::kRootWebArea; |
| initial_state.nodes[0].AddState(ax::mojom::State::kIgnored); |
| initial_state.nodes[0].child_ids = {2, 3}; |
| |
| initial_state.nodes[1].id = 2; |
| initial_state.nodes[1].role = ax::mojom::Role::kStaticText; |
| |
| initial_state.nodes[2].id = 3; |
| initial_state.nodes[2].role = ax::mojom::Role::kStaticText; |
| initial_state.nodes[2].AddState(ax::mojom::State::kIgnored); |
| |
| AXTree tree(initial_state); |
| |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate update = initial_state; |
| update.nodes[1].AddState(ax::mojom::State::kIgnored); |
| update.nodes[2].RemoveState(ax::mojom::State::kIgnored); |
| ASSERT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT(event_generator, |
| UnorderedElementsAre( |
| HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 2), |
| HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 3), |
| HasEventAtNode(AXEventGenerator::Event::SUBTREE_CREATED, 3))); |
| } |
| |
| TEST(AXEventGeneratorTest, IgnoredChangedFiredOnAncestorOnly2) { |
| // BEFORE |
| // 1 |
| // | |
| // 2 |
| // / \ |
| // 3 4 (IGN) |
| |
| // AFTER |
| // 1 |
| // | |
| // 2 ___ |
| // / \ |
| // 3 (IGN) 4 |
| // IGNORED_CHANGED expected on #3, #4 |
| |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(4); |
| initial_state.nodes[0].id = 1; |
| initial_state.nodes[0].role = ax::mojom::Role::kRootWebArea; |
| initial_state.nodes[0].child_ids = {2}; |
| |
| initial_state.nodes[1].id = 2; |
| initial_state.nodes[1].role = ax::mojom::Role::kGroup; |
| initial_state.nodes[1].child_ids = {3, 4}; |
| |
| initial_state.nodes[2].id = 3; |
| initial_state.nodes[2].role = ax::mojom::Role::kStaticText; |
| |
| initial_state.nodes[3].id = 4; |
| initial_state.nodes[3].role = ax::mojom::Role::kStaticText; |
| initial_state.nodes[3].AddState(ax::mojom::State::kIgnored); |
| |
| AXTree tree(initial_state); |
| |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate update = initial_state; |
| update.nodes[2].AddState(ax::mojom::State::kIgnored); |
| update.nodes[3].RemoveState(ax::mojom::State::kIgnored); |
| ASSERT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT(event_generator, |
| UnorderedElementsAre( |
| HasEventAtNode(AXEventGenerator::Event::CHILDREN_CHANGED, 2), |
| HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 3), |
| HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 4), |
| HasEventAtNode(AXEventGenerator::Event::SUBTREE_CREATED, 4))); |
| } |
| |
| TEST(AXEventGeneratorTest, IgnoredChangedFiredOnAncestorOnly3) { |
| // BEFORE |
| // 1 |
| // | |
| // 2 ___ |
| // / \ |
| // 3 (IGN) 4 |
| |
| // AFTER |
| // 1 (IGN) |
| // | |
| // 2 |
| // / \ |
| // 3 4 (IGN) |
| // IGNORED_CHANGED expected on #1, #3 |
| |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(4); |
| initial_state.nodes[0].id = 1; |
| initial_state.nodes[0].role = ax::mojom::Role::kRootWebArea; |
| initial_state.nodes[0].child_ids = {2}; |
| |
| initial_state.nodes[1].id = 2; |
| initial_state.nodes[1].role = ax::mojom::Role::kGroup; |
| initial_state.nodes[1].child_ids = {3, 4}; |
| |
| initial_state.nodes[2].id = 3; |
| initial_state.nodes[2].role = ax::mojom::Role::kStaticText; |
| initial_state.nodes[2].AddState(ax::mojom::State::kIgnored); |
| |
| initial_state.nodes[3].id = 4; |
| initial_state.nodes[3].role = ax::mojom::Role::kStaticText; |
| |
| AXTree tree(initial_state); |
| |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate update = initial_state; |
| update.nodes[0].AddState(ax::mojom::State::kIgnored); |
| update.nodes[2].RemoveState(ax::mojom::State::kIgnored); |
| update.nodes[3].AddState(ax::mojom::State::kIgnored); |
| ASSERT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT(event_generator, |
| UnorderedElementsAre( |
| HasEventAtNode(AXEventGenerator::Event::CHILDREN_CHANGED, 2), |
| HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 1), |
| HasEventAtNode(AXEventGenerator::Event::PARENT_CHANGED, 2), |
| HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 3), |
| HasEventAtNode(AXEventGenerator::Event::SUBTREE_CREATED, 3))); |
| } |
| |
| TEST(AXEventGeneratorTest, IgnoredChangedFiredOnAncestorOnly4) { |
| // BEFORE |
| // 1 (IGN) |
| // | |
| // 2 |
| // | |
| // 3 (IGN) |
| // | |
| // 4 (IGN) |
| // | |
| // ____ 5 _____ |
| // / | \ |
| // 6 (IGN) 7 (IGN) 8 |
| |
| // AFTER |
| // 1 (IGN) |
| // | |
| // 2 |
| // | |
| // 3 (IGN) |
| // | |
| // 4 (IGN) |
| // | |
| // ____ 5 _____ |
| // / | \ |
| // 6 7 8 (IGN) |
| |
| // IGNORED_CHANGED expected on #6, #7, #8 |
| |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(8); |
| initial_state.nodes[0].id = 1; |
| initial_state.nodes[0].role = ax::mojom::Role::kRootWebArea; |
| initial_state.nodes[0].child_ids = {2}; |
| |
| initial_state.nodes[1].id = 2; |
| initial_state.nodes[1].role = ax::mojom::Role::kGroup; |
| initial_state.nodes[1].child_ids = {3}; |
| |
| initial_state.nodes[2].id = 3; |
| initial_state.nodes[2].role = ax::mojom::Role::kGroup; |
| initial_state.nodes[2].child_ids = {4}; |
| initial_state.nodes[2].AddState(ax::mojom::State::kIgnored); |
| |
| initial_state.nodes[3].id = 4; |
| initial_state.nodes[3].role = ax::mojom::Role::kGroup; |
| initial_state.nodes[3].child_ids = {5}; |
| initial_state.nodes[3].AddState(ax::mojom::State::kIgnored); |
| |
| initial_state.nodes[4].id = 5; |
| initial_state.nodes[4].role = ax::mojom::Role::kGroup; |
| initial_state.nodes[4].child_ids = {6, 7, 8}; |
| |
| initial_state.nodes[5].id = 6; |
| initial_state.nodes[5].role = ax::mojom::Role::kStaticText; |
| initial_state.nodes[5].AddState(ax::mojom::State::kIgnored); |
| |
| initial_state.nodes[6].id = 7; |
| initial_state.nodes[6].role = ax::mojom::Role::kStaticText; |
| initial_state.nodes[6].AddState(ax::mojom::State::kIgnored); |
| |
| initial_state.nodes[7].id = 8; |
| initial_state.nodes[7].role = ax::mojom::Role::kStaticText; |
| |
| AXTree tree(initial_state); |
| |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate update = initial_state; |
| update.nodes[5].RemoveState(ax::mojom::State::kIgnored); |
| update.nodes[6].RemoveState(ax::mojom::State::kIgnored); |
| update.nodes[7].AddState(ax::mojom::State::kIgnored); |
| ASSERT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT(event_generator, |
| UnorderedElementsAre( |
| HasEventAtNode(AXEventGenerator::Event::CHILDREN_CHANGED, 5), |
| HasEventAtNode(AXEventGenerator::Event::SUBTREE_CREATED, 6), |
| HasEventAtNode(AXEventGenerator::Event::SUBTREE_CREATED, 7), |
| HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 6), |
| HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 7), |
| HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 8))); |
| } |
| |
| TEST(AXEventGeneratorTest, IgnoredChangedFiredOnAncestorOnly5) { |
| // BEFORE |
| // 1 |
| // | |
| // 2 |
| // | |
| // 3 (IGN) |
| // | |
| // 4 (IGN) |
| // | |
| // ____ 5 _____ |
| // / | \ |
| // 6 (IGN) 7 8 |
| |
| // AFTER |
| // 1 (IGN) |
| // | |
| // 2 |
| // | |
| // 3 (IGN) |
| // | |
| // 4 (IGN) |
| // | |
| // ____ 5 _____ |
| // / | \ |
| // 6 7 (IGN) 8 (IGN) |
| |
| // IGNORED_CHANGED expected on #1, #6 |
| |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(8); |
| initial_state.nodes[0].id = 1; |
| initial_state.nodes[0].role = ax::mojom::Role::kRootWebArea; |
| initial_state.nodes[0].child_ids = {2}; |
| |
| initial_state.nodes[1].id = 2; |
| initial_state.nodes[1].role = ax::mojom::Role::kGroup; |
| initial_state.nodes[1].child_ids = {3}; |
| |
| initial_state.nodes[2].id = 3; |
| initial_state.nodes[2].role = ax::mojom::Role::kGroup; |
| initial_state.nodes[2].child_ids = {4}; |
| initial_state.nodes[2].AddState(ax::mojom::State::kIgnored); |
| |
| initial_state.nodes[3].id = 4; |
| initial_state.nodes[3].role = ax::mojom::Role::kGroup; |
| initial_state.nodes[3].child_ids = {5}; |
| initial_state.nodes[3].AddState(ax::mojom::State::kIgnored); |
| |
| initial_state.nodes[4].id = 5; |
| initial_state.nodes[4].role = ax::mojom::Role::kGroup; |
| initial_state.nodes[4].child_ids = {6, 7, 8}; |
| |
| initial_state.nodes[5].id = 6; |
| initial_state.nodes[5].role = ax::mojom::Role::kStaticText; |
| initial_state.nodes[5].AddState(ax::mojom::State::kIgnored); |
| |
| initial_state.nodes[6].id = 7; |
| initial_state.nodes[6].role = ax::mojom::Role::kStaticText; |
| |
| initial_state.nodes[7].id = 8; |
| initial_state.nodes[7].role = ax::mojom::Role::kStaticText; |
| |
| AXTree tree(initial_state); |
| |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate update = initial_state; |
| update.nodes[0].AddState(ax::mojom::State::kIgnored); |
| update.nodes[5].RemoveState(ax::mojom::State::kIgnored); |
| update.nodes[6].AddState(ax::mojom::State::kIgnored); |
| update.nodes[7].AddState(ax::mojom::State::kIgnored); |
| ASSERT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT(event_generator, |
| UnorderedElementsAre( |
| HasEventAtNode(AXEventGenerator::Event::CHILDREN_CHANGED, 5), |
| HasEventAtNode(AXEventGenerator::Event::SUBTREE_CREATED, 6), |
| HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 1), |
| HasEventAtNode(AXEventGenerator::Event::PARENT_CHANGED, 2), |
| HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 6))); |
| } |
| |
| TEST(AXEventGeneratorTest, IgnoredChangedFiredOnAncestorOnly6) { |
| // BEFORE |
| // 1 (IGN) |
| // | |
| // 2 |
| // | |
| // 3 |
| // | |
| // 4 |
| // | |
| // ____ 5 _____ |
| // / | \ |
| // 6 (IGN) 7 (IGN) 8 |
| |
| // AFTER |
| // 1 |
| // | |
| // 2 |
| // | |
| // 3 |
| // | |
| // 4 |
| // | |
| // ____ 5 _____ |
| // / | \ |
| // 6 7 8 (IGN) |
| |
| // IGNORED_CHANGED expected on #1, #8 |
| |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(8); |
| initial_state.nodes[0].id = 1; |
| initial_state.nodes[0].role = ax::mojom::Role::kRootWebArea; |
| initial_state.nodes[0].child_ids = {2}; |
| initial_state.nodes[0].AddState(ax::mojom::State::kIgnored); |
| |
| initial_state.nodes[1].id = 2; |
| initial_state.nodes[1].role = ax::mojom::Role::kGroup; |
| initial_state.nodes[1].child_ids = {3}; |
| |
| initial_state.nodes[2].id = 3; |
| initial_state.nodes[2].role = ax::mojom::Role::kGroup; |
| initial_state.nodes[2].child_ids = {4}; |
| |
| initial_state.nodes[3].id = 4; |
| initial_state.nodes[3].role = ax::mojom::Role::kGroup; |
| initial_state.nodes[3].child_ids = {5}; |
| |
| initial_state.nodes[4].id = 5; |
| initial_state.nodes[4].role = ax::mojom::Role::kGroup; |
| initial_state.nodes[4].child_ids = {6, 7, 8}; |
| |
| initial_state.nodes[5].id = 6; |
| initial_state.nodes[5].role = ax::mojom::Role::kStaticText; |
| initial_state.nodes[5].AddState(ax::mojom::State::kIgnored); |
| |
| initial_state.nodes[6].id = 7; |
| initial_state.nodes[6].role = ax::mojom::Role::kStaticText; |
| initial_state.nodes[6].AddState(ax::mojom::State::kIgnored); |
| |
| initial_state.nodes[7].id = 8; |
| initial_state.nodes[7].role = ax::mojom::Role::kStaticText; |
| |
| AXTree tree(initial_state); |
| |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate update = initial_state; |
| update.nodes[0].RemoveState(ax::mojom::State::kIgnored); |
| update.nodes[5].RemoveState(ax::mojom::State::kIgnored); |
| update.nodes[6].RemoveState(ax::mojom::State::kIgnored); |
| update.nodes[7].AddState(ax::mojom::State::kIgnored); |
| ASSERT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT(event_generator, |
| UnorderedElementsAre( |
| HasEventAtNode(AXEventGenerator::Event::CHILDREN_CHANGED, 5), |
| HasEventAtNode(AXEventGenerator::Event::SUBTREE_CREATED, 1), |
| HasEventAtNode(AXEventGenerator::Event::SUBTREE_CREATED, 6), |
| HasEventAtNode(AXEventGenerator::Event::SUBTREE_CREATED, 7), |
| HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 1), |
| HasEventAtNode(AXEventGenerator::Event::PARENT_CHANGED, 2), |
| HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 8))); |
| } |
| |
| TEST(AXEventGeneratorTest, IgnoredChangedFiredOnAncestorOnly7) { |
| // BEFORE |
| // 1 (IGN) |
| // | |
| // 2 (IGN) |
| // | |
| // 3 |
| // | |
| // __ 4 ___ |
| // / \ |
| // 5 (IGN) 6 (IGN) |
| |
| // AFTER |
| // 1 |
| // | |
| // 2 |
| // | |
| // 3 (IGN) |
| // | |
| // __ 4 (IGN) |
| // / \ |
| // 5 (IGN) 6 (IGN) |
| |
| // IGNORED_CHANGED expected on #1, #3 |
| |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(6); |
| initial_state.nodes[0].id = 1; |
| initial_state.nodes[0].role = ax::mojom::Role::kRootWebArea; |
| initial_state.nodes[0].child_ids = {2}; |
| initial_state.nodes[0].AddState(ax::mojom::State::kIgnored); |
| |
| initial_state.nodes[1].id = 2; |
| initial_state.nodes[1].role = ax::mojom::Role::kGroup; |
| initial_state.nodes[1].child_ids = {3}; |
| initial_state.nodes[1].AddState(ax::mojom::State::kIgnored); |
| |
| initial_state.nodes[2].id = 3; |
| initial_state.nodes[2].role = ax::mojom::Role::kGroup; |
| initial_state.nodes[2].child_ids = {4}; |
| |
| initial_state.nodes[3].id = 4; |
| initial_state.nodes[3].role = ax::mojom::Role::kGroup; |
| initial_state.nodes[3].child_ids = {5, 6}; |
| |
| initial_state.nodes[4].id = 5; |
| initial_state.nodes[4].role = ax::mojom::Role::kStaticText; |
| initial_state.nodes[4].AddState(ax::mojom::State::kIgnored); |
| |
| initial_state.nodes[5].id = 6; |
| initial_state.nodes[5].role = ax::mojom::Role::kStaticText; |
| initial_state.nodes[5].AddState(ax::mojom::State::kIgnored); |
| |
| AXTree tree(initial_state); |
| |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate update = initial_state; |
| update.nodes[0].RemoveState(ax::mojom::State::kIgnored); |
| update.nodes[1].RemoveState(ax::mojom::State::kIgnored); |
| update.nodes[2].AddState(ax::mojom::State::kIgnored); |
| update.nodes[3].AddState(ax::mojom::State::kIgnored); |
| ASSERT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT(event_generator, |
| UnorderedElementsAre( |
| HasEventAtNode(AXEventGenerator::Event::SUBTREE_CREATED, 1), |
| HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 1), |
| HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 3))); |
| } |
| |
| TEST(AXEventGeneratorTest, IgnoredChangedFiredOnAncestorOnly8) { |
| // BEFORE |
| // ____ 1 ____ |
| // | | |
| // 2 (IGN) 7 |
| // | |
| // 3 (IGN) |
| // | |
| // 4 (IGN) |
| // | |
| // 5 (IGN) |
| // | |
| // 6 (IGN) |
| |
| // AFTER |
| // ____ 1 ____ |
| // | | |
| // 2 7 (IGN) |
| // | |
| // 3 |
| // | |
| // 4 |
| // | |
| // 5 |
| // | |
| // 6 |
| |
| // IGNORED_CHANGED expected on #2, #7 |
| |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(7); |
| initial_state.nodes[0].id = 1; |
| initial_state.nodes[0].role = ax::mojom::Role::kRootWebArea; |
| initial_state.nodes[0].child_ids = {2, 7}; |
| |
| initial_state.nodes[1].id = 2; |
| initial_state.nodes[1].role = ax::mojom::Role::kGroup; |
| initial_state.nodes[1].child_ids = {3}; |
| initial_state.nodes[1].AddState(ax::mojom::State::kIgnored); |
| |
| initial_state.nodes[2].id = 3; |
| initial_state.nodes[2].role = ax::mojom::Role::kGroup; |
| initial_state.nodes[2].child_ids = {4}; |
| initial_state.nodes[2].AddState(ax::mojom::State::kIgnored); |
| |
| initial_state.nodes[3].id = 4; |
| initial_state.nodes[3].role = ax::mojom::Role::kGroup; |
| initial_state.nodes[3].child_ids = {5}; |
| initial_state.nodes[3].AddState(ax::mojom::State::kIgnored); |
| |
| initial_state.nodes[4].id = 5; |
| initial_state.nodes[4].role = ax::mojom::Role::kGroup; |
| initial_state.nodes[4].child_ids = {6}; |
| initial_state.nodes[4].AddState(ax::mojom::State::kIgnored); |
| |
| initial_state.nodes[5].id = 5; |
| initial_state.nodes[5].role = ax::mojom::Role::kStaticText; |
| initial_state.nodes[5].AddState(ax::mojom::State::kIgnored); |
| |
| initial_state.nodes[6].id = 7; |
| initial_state.nodes[6].role = ax::mojom::Role::kStaticText; |
| |
| AXTree tree(initial_state); |
| |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate update = initial_state; |
| update.nodes[1].RemoveState(ax::mojom::State::kIgnored); |
| update.nodes[2].RemoveState(ax::mojom::State::kIgnored); |
| update.nodes[3].RemoveState(ax::mojom::State::kIgnored); |
| update.nodes[4].RemoveState(ax::mojom::State::kIgnored); |
| update.nodes[5].RemoveState(ax::mojom::State::kIgnored); |
| update.nodes[6].AddState(ax::mojom::State::kIgnored); |
| ASSERT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT(event_generator, |
| UnorderedElementsAre( |
| HasEventAtNode(AXEventGenerator::Event::CHILDREN_CHANGED, 1), |
| HasEventAtNode(AXEventGenerator::Event::SUBTREE_CREATED, 2), |
| HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 2), |
| HasEventAtNode(AXEventGenerator::Event::IGNORED_CHANGED, 7))); |
| } |
| |
| TEST(AXEventGeneratorTest, ActiveDescendantChangeOnDescendant) { |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(5); |
| initial_state.nodes[0].id = 1; |
| initial_state.nodes[0].role = ax::mojom::Role::kRootWebArea; |
| initial_state.nodes[0].child_ids.push_back(2); |
| initial_state.nodes[1].id = 2; |
| initial_state.nodes[1].role = ax::mojom::Role::kGenericContainer; |
| initial_state.nodes[1].child_ids.push_back(3); |
| initial_state.nodes[2].id = 3; |
| initial_state.nodes[2].role = ax::mojom::Role::kGroup; |
| initial_state.nodes[2].AddIntAttribute( |
| ax::mojom::IntAttribute::kActivedescendantId, 4); |
| initial_state.nodes[2].child_ids.push_back(4); |
| initial_state.nodes[2].child_ids.push_back(5); |
| initial_state.nodes[3].id = 4; |
| initial_state.nodes[3].role = ax::mojom::Role::kGroup; |
| initial_state.nodes[4].id = 5; |
| initial_state.nodes[4].role = ax::mojom::Role::kGroup; |
| |
| AXTree tree(initial_state); |
| |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| initial_state.nodes[2].RemoveIntAttribute( |
| ax::mojom::IntAttribute::kActivedescendantId); |
| initial_state.nodes[2].AddIntAttribute( |
| ax::mojom::IntAttribute::kActivedescendantId, 5); |
| AXTreeUpdate update = initial_state; |
| // Setting the node_id_to_clear causes AXTree::ComputePendingChangesToNode to |
| // create all of the node's children. Since node 3 already exists and remains |
| // in the tree, that (re)created child is reporting a new parent. |
| update.node_id_to_clear = 2; |
| ASSERT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT( |
| event_generator, |
| UnorderedElementsAre( |
| HasEventAtNode(AXEventGenerator::Event::ACTIVE_DESCENDANT_CHANGED, 3), |
| HasEventAtNode(AXEventGenerator::Event::RELATED_NODE_CHANGED, 3), |
| HasEventAtNode(AXEventGenerator::Event::PARENT_CHANGED, 3))); |
| } |
| |
| TEST(AXEventGeneratorTest, ImageAnnotationChanged) { |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(1); |
| initial_state.nodes[0].id = 1; |
| AXTree tree(initial_state); |
| |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate update = initial_state; |
| update.nodes[0].AddStringAttribute( |
| ax::mojom::StringAttribute::kImageAnnotation, "Hello"); |
| ASSERT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT(event_generator, |
| UnorderedElementsAre(HasEventAtNode( |
| AXEventGenerator::Event::IMAGE_ANNOTATION_CHANGED, 1))); |
| } |
| |
| TEST(AXEventGeneratorTest, ImageAnnotationStatusChanged) { |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(1); |
| initial_state.nodes[0].id = 1; |
| AXTree tree(initial_state); |
| |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate update = initial_state; |
| update.nodes[0].SetImageAnnotationStatus( |
| ax::mojom::ImageAnnotationStatus::kAnnotationSucceeded); |
| |
| ASSERT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT(event_generator, |
| UnorderedElementsAre(HasEventAtNode( |
| AXEventGenerator::Event::IMAGE_ANNOTATION_CHANGED, 1))); |
| } |
| |
| TEST(AXEventGeneratorTest, StringPropertyChanges) { |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(1); |
| initial_state.nodes[0].id = 1; |
| |
| struct { |
| ax::mojom::StringAttribute id; |
| std::string old_value; |
| std::string new_value; |
| } attributes[] = { |
| {ax::mojom::StringAttribute::kAccessKey, "a", "b"}, |
| {ax::mojom::StringAttribute::kClassName, "a", "b"}, |
| {ax::mojom::StringAttribute::kKeyShortcuts, "a", "b"}, |
| {ax::mojom::StringAttribute::kLanguage, "a", "b"}, |
| {ax::mojom::StringAttribute::kPlaceholder, "a", "b"}, |
| }; |
| for (auto&& attrib : attributes) { |
| initial_state.nodes.push_back({}); |
| initial_state.nodes.back().id = initial_state.nodes.size(); |
| initial_state.nodes.back().AddStringAttribute(attrib.id, attrib.old_value); |
| initial_state.nodes[0].child_ids.push_back(initial_state.nodes.size()); |
| } |
| |
| AXTree tree(initial_state); |
| |
| AXEventGenerator event_generator(&tree); |
| int index = 1; |
| for (auto&& attrib : attributes) { |
| initial_state.nodes[index++].AddStringAttribute(attrib.id, |
| attrib.new_value); |
| } |
| |
| AXTreeUpdate update = initial_state; |
| EXPECT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT( |
| event_generator, |
| UnorderedElementsAre( |
| HasEventAtNode(AXEventGenerator::Event::ACCESS_KEY_CHANGED, 2), |
| HasEventAtNode(AXEventGenerator::Event::CLASS_NAME_CHANGED, 3), |
| HasEventAtNode(AXEventGenerator::Event::KEY_SHORTCUTS_CHANGED, 4), |
| HasEventAtNode(AXEventGenerator::Event::LANGUAGE_CHANGED, 5), |
| HasEventAtNode(AXEventGenerator::Event::PLACEHOLDER_CHANGED, 6))); |
| } |
| |
| TEST(AXEventGeneratorTest, IntPropertyChanges) { |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(1); |
| initial_state.nodes[0].id = 1; |
| |
| struct { |
| ax::mojom::IntAttribute id; |
| int old_value; |
| int new_value; |
| } attributes[] = { |
| {ax::mojom::IntAttribute::kHierarchicalLevel, 1, 2}, |
| {ax::mojom::IntAttribute::kPosInSet, 1, 2}, |
| {ax::mojom::IntAttribute::kSetSize, 1, 2}, |
| }; |
| for (auto&& attrib : attributes) { |
| initial_state.nodes.push_back({}); |
| initial_state.nodes.back().id = initial_state.nodes.size(); |
| initial_state.nodes.back().AddIntAttribute(attrib.id, attrib.old_value); |
| initial_state.nodes[0].child_ids.push_back(initial_state.nodes.size()); |
| } |
| |
| AXTree tree(initial_state); |
| |
| AXEventGenerator event_generator(&tree); |
| int index = 1; |
| for (auto&& attrib : attributes) |
| initial_state.nodes[index++].AddIntAttribute(attrib.id, attrib.new_value); |
| |
| AXTreeUpdate update = initial_state; |
| EXPECT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT( |
| event_generator, |
| UnorderedElementsAre( |
| HasEventAtNode(AXEventGenerator::Event::HIERARCHICAL_LEVEL_CHANGED, |
| 2), |
| HasEventAtNode(AXEventGenerator::Event::POSITION_IN_SET_CHANGED, 3), |
| HasEventAtNode(AXEventGenerator::Event::SET_SIZE_CHANGED, 4))); |
| } |
| |
| TEST(AXEventGeneratorTest, IntListPropertyChanges) { |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(1); |
| initial_state.nodes[0].id = 1; |
| |
| struct { |
| ax::mojom::IntListAttribute id; |
| std::vector<int> old_value; |
| std::vector<int> new_value; |
| } attributes[] = { |
| {ax::mojom::IntListAttribute::kDescribedbyIds, {1}, {2}}, |
| {ax::mojom::IntListAttribute::kFlowtoIds, {1}, {2}}, |
| {ax::mojom::IntListAttribute::kLabelledbyIds, {1}, {2}}, |
| }; |
| for (auto&& attrib : attributes) { |
| initial_state.nodes.push_back({}); |
| initial_state.nodes.back().id = initial_state.nodes.size(); |
| initial_state.nodes.back().AddIntListAttribute(attrib.id, attrib.old_value); |
| initial_state.nodes[0].child_ids.push_back(initial_state.nodes.size()); |
| } |
| |
| AXTree tree(initial_state); |
| |
| AXEventGenerator event_generator(&tree); |
| int index = 1; |
| for (auto&& attrib : attributes) { |
| initial_state.nodes[index++].AddIntListAttribute(attrib.id, |
| attrib.new_value); |
| } |
| |
| AXTreeUpdate update = initial_state; |
| EXPECT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT( |
| event_generator, |
| UnorderedElementsAre( |
| HasEventAtNode(AXEventGenerator::Event::DESCRIBED_BY_CHANGED, 2), |
| HasEventAtNode(AXEventGenerator::Event::FLOW_FROM_CHANGED, 1), |
| HasEventAtNode(AXEventGenerator::Event::FLOW_FROM_CHANGED, 2), |
| HasEventAtNode(AXEventGenerator::Event::FLOW_TO_CHANGED, 3), |
| HasEventAtNode(AXEventGenerator::Event::LABELED_BY_CHANGED, 4), |
| HasEventAtNode(AXEventGenerator::Event::RELATED_NODE_CHANGED, 2), |
| HasEventAtNode(AXEventGenerator::Event::RELATED_NODE_CHANGED, 3), |
| HasEventAtNode(AXEventGenerator::Event::RELATED_NODE_CHANGED, 4))); |
| } |
| |
| TEST(AXEventGeneratorTest, AriaBusyChanged) { |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(1); |
| initial_state.nodes[0].id = 1; |
| AXTree tree(initial_state); |
| initial_state.nodes[0].AddBoolAttribute(ax::mojom::BoolAttribute::kBusy, |
| true); |
| |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate update = initial_state; |
| update.nodes[0].AddBoolAttribute(ax::mojom::BoolAttribute::kBusy, false); |
| |
| ASSERT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT( |
| event_generator, |
| UnorderedElementsAre( |
| HasEventAtNode(AXEventGenerator::Event::BUSY_CHANGED, 1), |
| HasEventAtNode(AXEventGenerator::Event::LAYOUT_INVALIDATED, 1), |
| HasEventAtNode(AXEventGenerator::Event::WIN_IACCESSIBLE_STATE_CHANGED, |
| 1))); |
| } |
| |
| TEST(AXEventGeneratorTest, MultiselectableStateChanged) { |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(1); |
| initial_state.nodes[0].id = 1; |
| initial_state.nodes[0].role = ax::mojom::Role::kGrid; |
| |
| AXTree tree(initial_state); |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate update = initial_state; |
| |
| update.nodes[0].AddState(ax::mojom::State::kMultiselectable); |
| EXPECT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT( |
| event_generator, |
| UnorderedElementsAre( |
| HasEventAtNode(AXEventGenerator::Event::MULTISELECTABLE_STATE_CHANGED, |
| 1), |
| HasEventAtNode(AXEventGenerator::Event::STATE_CHANGED, 1), |
| HasEventAtNode(AXEventGenerator::Event::WIN_IACCESSIBLE_STATE_CHANGED, |
| 1))); |
| } |
| |
| TEST(AXEventGeneratorTest, RequiredStateChanged) { |
| AXNodeData text_field; |
| text_field.id = 1; |
| text_field.role = ax::mojom::Role::kTextField; |
| text_field.AddState(ax::mojom::State::kEditable); |
| text_field.AddBoolAttribute(ax::mojom::BoolAttribute::kEditableRoot, true); |
| |
| AXTreeUpdate initial_state; |
| initial_state.root_id = text_field.id; |
| initial_state.nodes = {text_field}; |
| |
| AXTree tree(initial_state); |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| |
| AXTreeUpdate update; |
| text_field.AddState(ax::mojom::State::kRequired); |
| update.nodes = {text_field}; |
| |
| ASSERT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT( |
| event_generator, |
| UnorderedElementsAre( |
| HasEventAtNode(AXEventGenerator::Event::REQUIRED_STATE_CHANGED, 1), |
| HasEventAtNode(AXEventGenerator::Event::STATE_CHANGED, 1), |
| HasEventAtNode(AXEventGenerator::Event::WIN_IACCESSIBLE_STATE_CHANGED, |
| 1))); |
| } |
| |
| TEST(AXEventGeneratorTest, FlowToChanged) { |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(6); |
| initial_state.nodes[0].id = 1; |
| initial_state.nodes[0].role = ax::mojom::Role::kGenericContainer; |
| initial_state.nodes[0].child_ids.assign({2, 3, 4, 5, 6}); |
| initial_state.nodes[1].id = 2; |
| initial_state.nodes[1].role = ax::mojom::Role::kGenericContainer; |
| initial_state.nodes[1].AddIntListAttribute( |
| ax::mojom::IntListAttribute::kFlowtoIds, {3, 4}); |
| initial_state.nodes[2].id = 3; |
| initial_state.nodes[2].role = ax::mojom::Role::kGenericContainer; |
| initial_state.nodes[3].id = 4; |
| initial_state.nodes[3].role = ax::mojom::Role::kGenericContainer; |
| initial_state.nodes[4].id = 5; |
| initial_state.nodes[4].role = ax::mojom::Role::kGenericContainer; |
| initial_state.nodes[5].id = 6; |
| initial_state.nodes[5].role = ax::mojom::Role::kGenericContainer; |
| |
| AXTree tree(initial_state); |
| |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate update = initial_state; |
| update.nodes[1].AddIntListAttribute(ax::mojom::IntListAttribute::kFlowtoIds, |
| {4, 5, 6}); |
| |
| EXPECT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT( |
| event_generator, |
| UnorderedElementsAre( |
| HasEventAtNode(AXEventGenerator::Event::FLOW_FROM_CHANGED, 3), |
| HasEventAtNode(AXEventGenerator::Event::FLOW_FROM_CHANGED, 5), |
| HasEventAtNode(AXEventGenerator::Event::FLOW_FROM_CHANGED, 6), |
| HasEventAtNode(AXEventGenerator::Event::FLOW_TO_CHANGED, 2), |
| HasEventAtNode(AXEventGenerator::Event::RELATED_NODE_CHANGED, 2))); |
| } |
| |
| TEST(AXEventGeneratorTest, ControlsChanged) { |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(2); |
| initial_state.nodes[0].id = 1; |
| initial_state.nodes[0].child_ids.push_back(2); |
| initial_state.nodes[1].id = 2; |
| |
| AXTree tree(initial_state); |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate update = initial_state; |
| |
| std::vector<int> ids = {2}; |
| update.nodes[0].AddIntListAttribute(ax::mojom::IntListAttribute::kControlsIds, |
| ids); |
| EXPECT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT( |
| event_generator, |
| UnorderedElementsAre( |
| HasEventAtNode(AXEventGenerator::Event::CONTROLS_CHANGED, 1), |
| HasEventAtNode(AXEventGenerator::Event::RELATED_NODE_CHANGED, 1))); |
| } |
| |
| TEST(AXEventGeneratorTest, AtomicChanged) { |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(1); |
| initial_state.nodes[0].id = 1; |
| |
| AXTree tree(initial_state); |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate update = initial_state; |
| |
| update.nodes[0].AddBoolAttribute(ax::mojom::BoolAttribute::kLiveAtomic, true); |
| EXPECT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT(event_generator, |
| UnorderedElementsAre( |
| HasEventAtNode(AXEventGenerator::Event::ATOMIC_CHANGED, 1))); |
| } |
| |
| TEST(AXEventGeneratorTest, DropeffectChanged) { |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(1); |
| initial_state.nodes[0].id = 1; |
| |
| AXTree tree(initial_state); |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate update = initial_state; |
| |
| update.nodes[0].AddDropeffect(ax::mojom::Dropeffect::kCopy); |
| EXPECT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT(event_generator, |
| UnorderedElementsAre(HasEventAtNode( |
| AXEventGenerator::Event::DROPEFFECT_CHANGED, 1))); |
| } |
| |
| TEST(AXEventGeneratorTest, GrabbedChanged) { |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(1); |
| initial_state.nodes[0].id = 1; |
| |
| AXTree tree(initial_state); |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate update = initial_state; |
| |
| update.nodes[0].AddBoolAttribute(ax::mojom::BoolAttribute::kGrabbed, true); |
| EXPECT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT(event_generator, |
| UnorderedElementsAre( |
| HasEventAtNode(AXEventGenerator::Event::GRABBED_CHANGED, 1))); |
| } |
| |
| TEST(AXEventGeneratorTest, HasPopupChanged) { |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(1); |
| initial_state.nodes[0].id = 1; |
| |
| AXTree tree(initial_state); |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate update = initial_state; |
| |
| update.nodes[0].SetHasPopup(ax::mojom::HasPopup::kTrue); |
| EXPECT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT( |
| event_generator, |
| UnorderedElementsAre( |
| HasEventAtNode(AXEventGenerator::Event::HASPOPUP_CHANGED, 1), |
| HasEventAtNode(AXEventGenerator::Event::WIN_IACCESSIBLE_STATE_CHANGED, |
| 1))); |
| } |
| |
| TEST(AXEventGeneratorTest, LiveRelevantChanged) { |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(1); |
| initial_state.nodes[0].id = 1; |
| |
| AXTree tree(initial_state); |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate update = initial_state; |
| |
| update.nodes[0].AddStringAttribute(ax::mojom::StringAttribute::kLiveRelevant, |
| "all"); |
| EXPECT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT(event_generator, |
| UnorderedElementsAre(HasEventAtNode( |
| AXEventGenerator::Event::LIVE_RELEVANT_CHANGED, 1))); |
| } |
| |
| TEST(AXEventGeneratorTest, MultilineStateChanged) { |
| AXTreeUpdate initial_state; |
| initial_state.root_id = 1; |
| initial_state.nodes.resize(1); |
| initial_state.nodes[0].id = 1; |
| |
| AXTree tree(initial_state); |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| AXTreeUpdate update = initial_state; |
| |
| update.nodes[0].AddState(ax::mojom::State::kMultiline); |
| EXPECT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT( |
| event_generator, |
| UnorderedElementsAre( |
| HasEventAtNode(AXEventGenerator::Event::MULTILINE_STATE_CHANGED, 1), |
| HasEventAtNode(AXEventGenerator::Event::STATE_CHANGED, 1), |
| HasEventAtNode(AXEventGenerator::Event::WIN_IACCESSIBLE_STATE_CHANGED, |
| 1))); |
| } |
| |
| TEST(AXEventGeneratorTest, EditableTextChanged) { |
| AXNodeData root; |
| root.id = 1; |
| root.role = ax::mojom::Role::kRootWebArea; |
| |
| AXNodeData text_field; |
| text_field.id = 2; |
| text_field.role = ax::mojom::Role::kTextField; |
| text_field.AddState(ax::mojom::State::kEditable); |
| text_field.AddBoolAttribute(ax::mojom::BoolAttribute::kEditableRoot, true); |
| text_field.SetValue("Before"); |
| root.child_ids = {text_field.id}; |
| |
| AXNodeData static_text; |
| static_text.id = 3; |
| static_text.role = ax::mojom::Role::kStaticText; |
| static_text.AddState(ax::mojom::State::kEditable); |
| static_text.SetName("Before"); |
| text_field.child_ids = {static_text.id}; |
| |
| AXTreeUpdate initial_state; |
| initial_state.root_id = root.id; |
| initial_state.nodes = {root, text_field, static_text}; |
| AXTree tree(initial_state); |
| AXEventGenerator event_generator(&tree); |
| ASSERT_THAT(event_generator, IsEmpty()); |
| |
| text_field.SetValue("After"); |
| static_text.SetName("After"); |
| AXTreeUpdate update; |
| update.nodes = {text_field, static_text}; |
| |
| ASSERT_TRUE(tree.Unserialize(update)); |
| EXPECT_THAT( |
| event_generator, |
| UnorderedElementsAre( |
| HasEventAtNode(AXEventGenerator::Event::VALUE_IN_TEXT_FIELD_CHANGED, |
| text_field.id), |
| HasEventAtNode(AXEventGenerator::Event::NAME_CHANGED, static_text.id), |
| HasEventAtNode(AXEventGenerator::Event::EDITABLE_TEXT_CHANGED, |
| text_field.id))); |
| } |
| |
| } // namespace ui |