1 // Copyright 2019 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "ui/accessibility/ax_tree_source_checker.h"
6
7 #include "base/strings/string_util.h"
8 #include "testing/gtest/include/gtest/gtest.h"
9 #include "ui/accessibility/ax_enums.mojom.h"
10 #include "ui/accessibility/ax_node_data.h"
11 #include "ui/accessibility/ax_tree_source.h"
12
13 namespace ui {
14 namespace {
15
16 struct FakeAXTreeData {};
17
18 struct FakeAXNode {
19 int32_t id;
20 ax::mojom::Role role;
21 std::vector<int32_t> child_ids;
22 int32_t parent_id;
23 };
24
25 // It's distracting to see an empty bounding box from every node, so do a
26 // search-and-replace to get rid of those strings.
CleanAXNodeDataString(std::string * error_str)27 void CleanAXNodeDataString(std::string* error_str) {
28 base::ReplaceSubstringsAfterOffset(error_str, 0, " (0, 0)-(0, 0)", "");
29 }
30
31 // A simple implementation of AXTreeSource initialized from a simple static
32 // vector of node data, where both the child and parent connections are
33 // explicit. This allows us to test that AXTreeSourceChecker properly warns
34 // about errors in accessibility trees that have inconsistent parent/child
35 // links.
36 class FakeAXTreeSource
37 : public AXTreeSource<const FakeAXNode*, ui::AXNodeData, FakeAXTreeData> {
38 public:
FakeAXTreeSource(std::vector<FakeAXNode> nodes,int32_t root_id)39 FakeAXTreeSource(std::vector<FakeAXNode> nodes, int32_t root_id)
40 : nodes_(nodes), root_id_(root_id) {
41 for (size_t i = 0; i < nodes_.size(); ++i)
42 id_to_node_[nodes_[i].id] = &nodes_[i];
43 }
44
45 // AXTreeSource overrides.
GetTreeData(FakeAXTreeData * data) const46 bool GetTreeData(FakeAXTreeData* data) const override { return true; }
47
GetRoot() const48 const FakeAXNode* GetRoot() const override { return GetFromId(root_id_); }
49
GetFromId(int32_t id) const50 const FakeAXNode* GetFromId(int32_t id) const override {
51 const auto& iter = id_to_node_.find(id);
52 if (iter != id_to_node_.end())
53 return iter->second;
54 return nullptr;
55 }
56
GetId(const FakeAXNode * node) const57 int32_t GetId(const FakeAXNode* node) const override { return node->id; }
58
GetChildren(const FakeAXNode * node,std::vector<const FakeAXNode * > * out_children) const59 void GetChildren(
60 const FakeAXNode* node,
61 std::vector<const FakeAXNode*>* out_children) const override {
62 for (size_t i = 0; i < node->child_ids.size(); ++i)
63 out_children->push_back(GetFromId(node->child_ids[i]));
64 }
65
GetParent(const FakeAXNode * node) const66 const FakeAXNode* GetParent(const FakeAXNode* node) const override {
67 return GetFromId(node->parent_id);
68 }
69
IsIgnored(const FakeAXNode * node) const70 bool IsIgnored(const FakeAXNode* node) const override { return false; }
71
IsValid(const FakeAXNode * node) const72 bool IsValid(const FakeAXNode* node) const override {
73 return node != nullptr;
74 }
75
IsEqual(const FakeAXNode * node1,const FakeAXNode * node2) const76 bool IsEqual(const FakeAXNode* node1,
77 const FakeAXNode* node2) const override {
78 return node1 == node2;
79 }
80
GetNull() const81 const FakeAXNode* GetNull() const override { return nullptr; }
82
SerializeNode(const FakeAXNode * node,AXNodeData * out_data) const83 void SerializeNode(const FakeAXNode* node,
84 AXNodeData* out_data) const override {
85 out_data->id = node->id;
86 out_data->role = node->role;
87 }
88
89 private:
90 std::vector<FakeAXNode> nodes_;
91 std::map<int32_t, FakeAXNode*> id_to_node_;
92 int32_t root_id_;
93 };
94
95 } // namespace
96
97 using FakeAXTreeSourceChecker =
98 AXTreeSourceChecker<const FakeAXNode*, ui::AXNodeData, FakeAXTreeData>;
99
TEST(AXTreeSourceCheckerTest,SimpleValidTree)100 TEST(AXTreeSourceCheckerTest, SimpleValidTree) {
101 std::vector<FakeAXNode> nodes = {
102 {1, ax::mojom::Role::kRootWebArea, {2}, -1},
103 {2, ax::mojom::Role::kRootWebArea, {}, 1},
104 };
105 FakeAXTreeSource node_source(nodes, 1);
106 FakeAXTreeSourceChecker checker(&node_source);
107 std::string error_string;
108 EXPECT_TRUE(checker.CheckAndGetErrorString(&error_string));
109 }
110
TEST(AXTreeSourceCheckerTest,BadRoot)111 TEST(AXTreeSourceCheckerTest, BadRoot) {
112 std::vector<FakeAXNode> nodes = {
113 {1, ax::mojom::Role::kRootWebArea, {2}, -1},
114 {2, ax::mojom::Role::kRootWebArea, {}, 1},
115 };
116 FakeAXTreeSource node_source(nodes, 3);
117 FakeAXTreeSourceChecker checker(&node_source);
118 std::string error_string;
119 EXPECT_FALSE(checker.CheckAndGetErrorString(&error_string));
120 CleanAXNodeDataString(&error_string);
121 EXPECT_EQ("Root is not valid.", error_string);
122 }
123
TEST(AXTreeSourceCheckerTest,BadNodeIdOfRoot)124 TEST(AXTreeSourceCheckerTest, BadNodeIdOfRoot) {
125 std::vector<FakeAXNode> nodes = {
126 {0, ax::mojom::Role::kRootWebArea, {2}, -1},
127 {2, ax::mojom::Role::kRootWebArea, {}, 0},
128 };
129 FakeAXTreeSource node_source(nodes, 0);
130 FakeAXTreeSourceChecker checker(&node_source);
131 std::string error_string;
132 EXPECT_FALSE(checker.CheckAndGetErrorString(&error_string));
133 CleanAXNodeDataString(&error_string);
134 EXPECT_EQ(
135 "Got a node with id 0, but all node IDs should be >= 1:\n"
136 "id=0 rootWebArea child_ids=2 parent_id=-1\n"
137 "id=0 rootWebArea child_ids=2 parent_id=-1",
138 error_string);
139 }
140
TEST(AXTreeSourceCheckerTest,BadNodeIdOfChild)141 TEST(AXTreeSourceCheckerTest, BadNodeIdOfChild) {
142 std::vector<FakeAXNode> nodes = {
143 {1, ax::mojom::Role::kRootWebArea, {-5}, -1},
144 {-5, ax::mojom::Role::kRootWebArea, {}, 1},
145 };
146 FakeAXTreeSource node_source(nodes, -5);
147 FakeAXTreeSourceChecker checker(&node_source);
148 std::string error_string;
149 EXPECT_FALSE(checker.CheckAndGetErrorString(&error_string));
150 CleanAXNodeDataString(&error_string);
151 EXPECT_EQ(
152 "Got a node with id -5, but all node IDs should be >= 1:\n"
153 "id=-5 rootWebArea (no children) parent_id=1\n"
154 "id=-5 rootWebArea (no children) parent_id=1",
155 error_string);
156 }
157
TEST(AXTreeSourceCheckerTest,RootShouldNotBeNodeWithParent)158 TEST(AXTreeSourceCheckerTest, RootShouldNotBeNodeWithParent) {
159 std::vector<FakeAXNode> nodes = {
160 {1, ax::mojom::Role::kRootWebArea, {2}, -1},
161 {2, ax::mojom::Role::kRootWebArea, {}, 1},
162 };
163 FakeAXTreeSource node_source(nodes, 2);
164 FakeAXTreeSourceChecker checker(&node_source);
165 std::string error_string;
166 EXPECT_FALSE(checker.CheckAndGetErrorString(&error_string));
167 CleanAXNodeDataString(&error_string);
168 EXPECT_EQ(
169 "Node 2 is the root, so its parent should be invalid, "
170 "but we got a node with id 1.\n"
171 "Node: id=2 rootWebArea (no children) parent_id=1\n"
172 "Parent: id=1 rootWebArea child_ids=2 parent_id=-1\n"
173 "id=2 rootWebArea (no children) parent_id=1",
174 error_string);
175 }
176
TEST(AXTreeSourceCheckerTest,MissingParent)177 TEST(AXTreeSourceCheckerTest, MissingParent) {
178 std::vector<FakeAXNode> nodes = {
179 {1, ax::mojom::Role::kRootWebArea, {2}, -1},
180 {2, ax::mojom::Role::kRootWebArea, {}, -1},
181 };
182 FakeAXTreeSource node_source(nodes, 1);
183 FakeAXTreeSourceChecker checker(&node_source);
184 std::string error_string;
185 EXPECT_FALSE(checker.CheckAndGetErrorString(&error_string));
186 CleanAXNodeDataString(&error_string);
187 EXPECT_EQ(
188 "Node 2 is not the root, but its parent was invalid:\n"
189 "id=2 rootWebArea (no children) parent_id=-1\n"
190 "id=1 rootWebArea child_ids=2 parent_id=-1\n"
191 " id=2 rootWebArea (no children) parent_id=-1",
192 error_string);
193 }
194
TEST(AXTreeSourceCheckerTest,InvalidParent)195 TEST(AXTreeSourceCheckerTest, InvalidParent) {
196 std::vector<FakeAXNode> nodes = {
197 {1, ax::mojom::Role::kRootWebArea, {2, 3}, -1},
198 {2, ax::mojom::Role::kButton, {}, 1},
199 {3, ax::mojom::Role::kParagraph, {}, 2},
200 };
201 FakeAXTreeSource node_source(nodes, 1);
202 FakeAXTreeSourceChecker checker(&node_source);
203 std::string error_string;
204 EXPECT_FALSE(checker.CheckAndGetErrorString(&error_string));
205 CleanAXNodeDataString(&error_string);
206 EXPECT_EQ(
207 "Expected node 3 to have a parent of 1, but found a parent of 2.\n"
208 "Node: id=3 paragraph (no children) parent_id=2\n"
209 "Parent: id=2 button (no children) parent_id=1\n"
210 "Expected parent: id=1 rootWebArea child_ids=2,3 parent_id=-1\n"
211 "id=1 rootWebArea child_ids=2,3 parent_id=-1\n"
212 " id=2 button (no children) parent_id=1\n"
213 " id=3 paragraph (no children) parent_id=2",
214 error_string);
215 }
216
217 } // namespace ui
218