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