blob: 15969e9c831b8c70d0ac580d996a7ef3344a089a [file] [log] [blame]
// Copyright 2019 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 "ax_platform_node_base.h"
#include "gtest/gtest.h"
#include "base/string_utils.h"
#include "test_ax_node_wrapper.h"
namespace ui {
namespace {
void MakeStaticText(AXNodeData* node, int id, const std::string& text) {
node->id = id;
node->role = ax::mojom::Role::kStaticText;
node->SetName(text);
}
void MakeGroup(AXNodeData* node, int id, std::vector<int> child_ids) {
node->id = id;
node->role = ax::mojom::Role::kGroup;
node->child_ids = child_ids;
}
void SetIsInvisible(AXTree* tree, int id, bool invisible) {
AXTreeUpdate update;
update.nodes.resize(1);
update.nodes[0] = tree->GetFromId(id)->data();
if (invisible)
update.nodes[0].AddState(ax::mojom::State::kInvisible);
else
update.nodes[0].RemoveState(ax::mojom::State::kInvisible);
tree->Unserialize(update);
}
void SetRole(AXTree* tree, int id, ax::mojom::Role role) {
AXTreeUpdate update;
update.nodes.resize(1);
update.nodes[0] = tree->GetFromId(id)->data();
update.nodes[0].role = role;
tree->Unserialize(update);
}
} // namespace
TEST(AXPlatformNodeBaseTest, GetHypertext) {
AXTreeUpdate update;
// RootWebArea #1
// ++++StaticText "text1" #2
// ++++StaticText "text2" #3
// ++++StaticText "text3" #4
update.root_id = 1;
update.nodes.resize(4);
update.nodes[0].id = 1;
update.nodes[0].role = ax::mojom::Role::kWebArea;
update.nodes[0].child_ids = {2, 3, 4};
MakeStaticText(&update.nodes[1], 2, "text1");
MakeStaticText(&update.nodes[2], 3, "text2");
MakeStaticText(&update.nodes[3], 4, "text3");
AXTree tree(update);
// Set an AXMode on the AXPlatformNode as some platforms (auralinux) use it to
// determine if it should enable accessibility.
AXPlatformNodeBase::NotifyAddAXModeFlags(kAXModeComplete);
AXPlatformNodeBase* root = static_cast<AXPlatformNodeBase*>(
TestAXNodeWrapper::GetOrCreate(&tree, tree.root())->ax_platform_node());
EXPECT_EQ(root->GetHypertext(), base::UTF8ToUTF16("text1text2text3"));
AXPlatformNodeBase* text1 = static_cast<AXPlatformNodeBase*>(
AXPlatformNode::FromNativeViewAccessible(root->ChildAtIndex(0)));
EXPECT_EQ(text1->GetHypertext(), base::UTF8ToUTF16("text1"));
AXPlatformNodeBase* text2 = static_cast<AXPlatformNodeBase*>(
AXPlatformNode::FromNativeViewAccessible(root->ChildAtIndex(1)));
EXPECT_EQ(text2->GetHypertext(), base::UTF8ToUTF16("text2"));
AXPlatformNodeBase* text3 = static_cast<AXPlatformNodeBase*>(
AXPlatformNode::FromNativeViewAccessible(root->ChildAtIndex(2)));
EXPECT_EQ(text3->GetHypertext(), base::UTF8ToUTF16("text3"));
}
TEST(AXPlatformNodeBaseTest, GetHypertextIgnoredContainerSiblings) {
AXTreeUpdate update;
// RootWebArea #1
// ++genericContainer IGNORED #2
// ++++StaticText "text1" #3
// ++genericContainer IGNORED #4
// ++++StaticText "text2" #5
// ++genericContainer IGNORED #6
// ++++StaticText "text3" #7
update.root_id = 1;
update.nodes.resize(7);
update.nodes[0].id = 1;
update.nodes[0].role = ax::mojom::Role::kWebArea;
update.nodes[0].child_ids = {2, 4, 6};
update.nodes[1].id = 2;
update.nodes[1].child_ids = {3};
update.nodes[1].role = ax::mojom::Role::kGenericContainer;
update.nodes[1].AddState(ax::mojom::State::kIgnored);
MakeStaticText(&update.nodes[2], 3, "text1");
update.nodes[3].id = 4;
update.nodes[3].child_ids = {5};
update.nodes[3].role = ax::mojom::Role::kGenericContainer;
update.nodes[3].AddState(ax::mojom::State::kIgnored);
MakeStaticText(&update.nodes[4], 5, "text2");
update.nodes[5].id = 6;
update.nodes[5].child_ids = {7};
update.nodes[5].role = ax::mojom::Role::kGenericContainer;
update.nodes[5].AddState(ax::mojom::State::kIgnored);
MakeStaticText(&update.nodes[6], 7, "text3");
AXTree tree(update);
// Set an AXMode on the AXPlatformNode as some platforms (auralinux) use it to
// determine if it should enable accessibility.
AXPlatformNodeBase::NotifyAddAXModeFlags(kAXModeComplete);
AXPlatformNodeBase* root = static_cast<AXPlatformNodeBase*>(
TestAXNodeWrapper::GetOrCreate(&tree, tree.root())->ax_platform_node());
EXPECT_EQ(root->GetHypertext(), base::UTF8ToUTF16("text1text2text3"));
AXPlatformNodeBase* text1_ignored_container =
static_cast<AXPlatformNodeBase*>(
AXPlatformNode::FromNativeViewAccessible(root->ChildAtIndex(0)));
EXPECT_EQ(text1_ignored_container->GetHypertext(),
base::UTF8ToUTF16("text1"));
AXPlatformNodeBase* text2_ignored_container =
static_cast<AXPlatformNodeBase*>(
AXPlatformNode::FromNativeViewAccessible(root->ChildAtIndex(1)));
EXPECT_EQ(text2_ignored_container->GetHypertext(),
base::UTF8ToUTF16("text2"));
AXPlatformNodeBase* text3_ignored_container =
static_cast<AXPlatformNodeBase*>(
AXPlatformNode::FromNativeViewAccessible(root->ChildAtIndex(2)));
EXPECT_EQ(text3_ignored_container->GetHypertext(),
base::UTF8ToUTF16("text3"));
}
TEST(AXPlatformNodeBaseTest, InnerTextIgnoresInvisibleAndIgnored) {
AXTreeUpdate update;
update.root_id = 1;
update.nodes.resize(6);
MakeStaticText(&update.nodes[1], 2, "a");
MakeStaticText(&update.nodes[2], 3, "b");
MakeStaticText(&update.nodes[4], 5, "d");
MakeStaticText(&update.nodes[5], 6, "e");
MakeGroup(&update.nodes[3], 4, {5, 6});
MakeGroup(&update.nodes[0], 1, {2, 3, 4});
AXTree tree(update);
auto* root = static_cast<AXPlatformNodeBase*>(
TestAXNodeWrapper::GetOrCreate(&tree, tree.root())->ax_platform_node());
// Set an AXMode on the AXPlatformNode as some platforms (auralinux) use it to
// determine if it should enable accessibility.
AXPlatformNodeBase::NotifyAddAXModeFlags(kAXModeComplete);
EXPECT_EQ(root->GetInnerText(), base::UTF8ToUTF16("abde"));
// Setting invisible or ignored on a static text node causes it to be included
// or excluded from the root node's inner text:
{
SetIsInvisible(&tree, 2, true);
EXPECT_EQ(root->GetInnerText(), base::UTF8ToUTF16("bde"));
SetIsInvisible(&tree, 2, false);
EXPECT_EQ(root->GetInnerText(), base::UTF8ToUTF16("abde"));
SetRole(&tree, 2, ax::mojom::Role::kIgnored);
EXPECT_EQ(root->GetInnerText(), base::UTF8ToUTF16("bde"));
SetRole(&tree, 2, ax::mojom::Role::kStaticText);
EXPECT_EQ(root->GetInnerText(), base::UTF8ToUTF16("abde"));
}
// Setting invisible or ignored on a group node has no effect on the inner
// text:
{
SetIsInvisible(&tree, 4, true);
EXPECT_EQ(root->GetInnerText(), base::UTF8ToUTF16("abde"));
SetRole(&tree, 4, ax::mojom::Role::kIgnored);
EXPECT_EQ(root->GetInnerText(), base::UTF8ToUTF16("abde"));
}
}
TEST(AXPlatformNodeBaseTest, TestSelectedChildren) {
AXPlatformNode::NotifyAddAXModeFlags(kAXModeComplete);
AXNodeData root_data;
root_data.id = 1;
root_data.role = ax::mojom::Role::kListBox;
root_data.AddState(ax::mojom::State::kFocusable);
root_data.child_ids = {2, 3};
AXNodeData item_1_data;
item_1_data.id = 2;
item_1_data.role = ax::mojom::Role::kListBoxOption;
item_1_data.AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
AXNodeData item_2_data;
item_2_data.id = 3;
item_2_data.role = ax::mojom::Role::kListBoxOption;
AXTreeUpdate update;
update.root_id = 1;
update.nodes = {root_data, item_1_data, item_2_data};
AXTree tree(update);
auto* root = static_cast<AXPlatformNodeBase*>(
TestAXNodeWrapper::GetOrCreate(&tree, tree.root())->ax_platform_node());
int num = root->GetSelectionCount();
EXPECT_EQ(num, 1);
gfx::NativeViewAccessible first_child = root->ChildAtIndex(0);
AXPlatformNodeBase* first_selected_node = root->GetSelectedItem(0);
EXPECT_EQ(first_child, first_selected_node->GetNativeViewAccessible());
EXPECT_EQ(nullptr, root->GetSelectedItem(1));
}
TEST(AXPlatformNodeBaseTest, TestSelectedChildrenWithGroup) {
AXPlatformNode::NotifyAddAXModeFlags(kAXModeComplete);
AXNodeData root_data;
root_data.id = 1;
root_data.role = ax::mojom::Role::kListBox;
root_data.AddState(ax::mojom::State::kFocusable);
root_data.AddState(ax::mojom::State::kMultiselectable);
root_data.child_ids = {2, 3};
AXNodeData group_1_data;
group_1_data.id = 2;
group_1_data.role = ax::mojom::Role::kGroup;
group_1_data.child_ids = {4, 5};
AXNodeData group_2_data;
group_2_data.id = 3;
group_2_data.role = ax::mojom::Role::kGroup;
group_2_data.child_ids = {6, 7};
AXNodeData item_1_data;
item_1_data.id = 4;
item_1_data.role = ax::mojom::Role::kListBoxOption;
item_1_data.AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
AXNodeData item_2_data;
item_2_data.id = 5;
item_2_data.role = ax::mojom::Role::kListBoxOption;
AXNodeData item_3_data;
item_3_data.id = 6;
item_3_data.role = ax::mojom::Role::kListBoxOption;
AXNodeData item_4_data;
item_4_data.id = 7;
item_4_data.role = ax::mojom::Role::kListBoxOption;
item_4_data.AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
AXTreeUpdate update;
update.root_id = 1;
update.nodes = {root_data, group_1_data, group_2_data, item_1_data,
item_2_data, item_3_data, item_4_data};
AXTree tree(update);
auto* root = static_cast<AXPlatformNodeBase*>(
TestAXNodeWrapper::GetOrCreate(&tree, tree.root())->ax_platform_node());
int num = root->GetSelectionCount();
EXPECT_EQ(num, 2);
gfx::NativeViewAccessible first_group_child =
static_cast<AXPlatformNodeBase*>(
AXPlatformNode::FromNativeViewAccessible(root->ChildAtIndex(0)))
->ChildAtIndex(0);
AXPlatformNodeBase* first_selected_node = root->GetSelectedItem(0);
EXPECT_EQ(first_group_child, first_selected_node->GetNativeViewAccessible());
gfx::NativeViewAccessible second_group_child =
static_cast<AXPlatformNodeBase*>(
AXPlatformNode::FromNativeViewAccessible(root->ChildAtIndex(1)))
->ChildAtIndex(1);
AXPlatformNodeBase* second_selected_node = root->GetSelectedItem(1);
EXPECT_EQ(second_group_child,
second_selected_node->GetNativeViewAccessible());
}
TEST(AXPlatformNodeBaseTest, TestSelectedChildrenMixed) {
AXPlatformNode::NotifyAddAXModeFlags(kAXModeComplete);
// Build the below tree which is mixed with listBoxOption and group.
// id=1 listBox FOCUSABLE MULTISELECTABLE (0, 0)-(0, 0) child_ids=2,3,4,9
// ++id=2 listBoxOption (0, 0)-(0, 0) selected=true
// ++id=3 group (0, 0)-(0, 0) child_ids=5,6
// ++++id=5 listBoxOption (0, 0)-(0, 0) selected=true
// ++++id=6 listBoxOption (0, 0)-(0, 0)
// ++id=4 group (0, 0)-(0, 0) child_ids=7,8
// ++++id=7 listBoxOption (0, 0)-(0, 0)
// ++++id=8 listBoxOption (0, 0)-(0, 0) selected=true
// ++id=9 listBoxOption (0, 0)-(0, 0) selected=true
AXNodeData root_data;
root_data.id = 1;
root_data.role = ax::mojom::Role::kListBox;
root_data.AddState(ax::mojom::State::kFocusable);
root_data.AddState(ax::mojom::State::kMultiselectable);
root_data.child_ids = {2, 3, 4, 9};
AXNodeData item_1_data;
item_1_data.id = 2;
item_1_data.role = ax::mojom::Role::kListBoxOption;
item_1_data.AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
AXNodeData group_1_data;
group_1_data.id = 3;
group_1_data.role = ax::mojom::Role::kGroup;
group_1_data.child_ids = {5, 6};
AXNodeData item_2_data;
item_2_data.id = 5;
item_2_data.role = ax::mojom::Role::kListBoxOption;
item_2_data.AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
AXNodeData item_3_data;
item_3_data.id = 6;
item_3_data.role = ax::mojom::Role::kListBoxOption;
AXNodeData group_2_data;
group_2_data.id = 4;
group_2_data.role = ax::mojom::Role::kGroup;
group_2_data.child_ids = {7, 8};
AXNodeData item_4_data;
item_4_data.id = 7;
item_4_data.role = ax::mojom::Role::kListBoxOption;
AXNodeData item_5_data;
item_5_data.id = 8;
item_5_data.role = ax::mojom::Role::kListBoxOption;
item_5_data.AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
AXNodeData item_6_data;
item_6_data.id = 9;
item_6_data.role = ax::mojom::Role::kListBoxOption;
item_6_data.AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, true);
AXTreeUpdate update;
update.root_id = 1;
update.nodes = {root_data, item_1_data, group_1_data,
item_2_data, item_3_data, group_2_data,
item_4_data, item_5_data, item_6_data};
AXTree tree(update);
auto* root = static_cast<AXPlatformNodeBase*>(
TestAXNodeWrapper::GetOrCreate(&tree, tree.root())->ax_platform_node());
int num = root->GetSelectionCount();
EXPECT_EQ(num, 4);
gfx::NativeViewAccessible first_child = root->ChildAtIndex(0);
AXPlatformNodeBase* first_selected_node = root->GetSelectedItem(0);
EXPECT_EQ(first_child, first_selected_node->GetNativeViewAccessible());
gfx::NativeViewAccessible first_group_child =
static_cast<AXPlatformNodeBase*>(
AXPlatformNode::FromNativeViewAccessible(root->ChildAtIndex(1)))
->ChildAtIndex(0);
AXPlatformNodeBase* second_selected_node = root->GetSelectedItem(1);
EXPECT_EQ(first_group_child, second_selected_node->GetNativeViewAccessible());
gfx::NativeViewAccessible second_group_child =
static_cast<AXPlatformNodeBase*>(
AXPlatformNode::FromNativeViewAccessible(root->ChildAtIndex(2)))
->ChildAtIndex(1);
AXPlatformNodeBase* third_selected_node = root->GetSelectedItem(2);
EXPECT_EQ(second_group_child, third_selected_node->GetNativeViewAccessible());
gfx::NativeViewAccessible fourth_child = root->ChildAtIndex(3);
AXPlatformNodeBase* fourth_selected_node = root->GetSelectedItem(3);
EXPECT_EQ(fourth_child, fourth_selected_node->GetNativeViewAccessible());
}
TEST(AXPlatformNodeBaseTest, CompareTo) {
// Compare the nodes' logical orders for the following tree. Node name is
// denoted according to its id (i.e. "n#" is id#). Nodes that have smaller ids
// are always logically less than nodes with bigger ids.
//
// n1
// |
// __ n2 ___
// / \ \
// n3 _ n8 n9
// / \ \ \
// n4 n5 n6 n10
// /
// n7
AXPlatformNode::NotifyAddAXModeFlags(kAXModeComplete);
AXNodeData node1;
node1.id = 1;
node1.role = ax::mojom::Role::kWebArea;
node1.child_ids = {2};
AXNodeData node2;
node2.id = 2;
node2.role = ax::mojom::Role::kStaticText;
node2.child_ids = {3, 8, 9};
AXNodeData node3;
node3.id = 3;
node3.role = ax::mojom::Role::kStaticText;
node3.child_ids = {4, 5, 6};
AXNodeData node4;
node4.id = 4;
node4.role = ax::mojom::Role::kStaticText;
AXNodeData node5;
node5.id = 5;
node5.role = ax::mojom::Role::kStaticText;
AXNodeData node6;
node6.id = 6;
node6.role = ax::mojom::Role::kStaticText;
node6.child_ids = {7};
AXNodeData node7;
node7.id = 7;
node7.role = ax::mojom::Role::kStaticText;
AXNodeData node8;
node8.id = 8;
node8.role = ax::mojom::Role::kStaticText;
AXNodeData node9;
node9.id = 9;
node9.role = ax::mojom::Role::kStaticText;
node9.child_ids = {10};
AXNodeData node10;
node10.id = 10;
node10.role = ax::mojom::Role::kStaticText;
AXTreeUpdate update;
update.root_id = 1;
update.nodes = {node1, node2, node3, node4, node5,
node6, node7, node8, node9, node10};
AXTree tree(update);
// Retrieve the nodes in a level-order traversal way.
auto* n1 = static_cast<AXPlatformNodeBase*>(
TestAXNodeWrapper::GetOrCreate(&tree, tree.root())->ax_platform_node());
auto* n2 = static_cast<AXPlatformNodeBase*>(
AXPlatformNode::FromNativeViewAccessible(n1->ChildAtIndex(0)));
auto* n3 = static_cast<AXPlatformNodeBase*>(
AXPlatformNode::FromNativeViewAccessible(n2->ChildAtIndex(0)));
auto* n8 = static_cast<AXPlatformNodeBase*>(
AXPlatformNode::FromNativeViewAccessible(n2->ChildAtIndex(1)));
auto* n9 = static_cast<AXPlatformNodeBase*>(
AXPlatformNode::FromNativeViewAccessible(n2->ChildAtIndex(2)));
auto* n4 = static_cast<AXPlatformNodeBase*>(
AXPlatformNode::FromNativeViewAccessible(n3->ChildAtIndex(0)));
auto* n5 = static_cast<AXPlatformNodeBase*>(
AXPlatformNode::FromNativeViewAccessible(n3->ChildAtIndex(1)));
auto* n6 = static_cast<AXPlatformNodeBase*>(
AXPlatformNode::FromNativeViewAccessible(n3->ChildAtIndex(2)));
auto* n10 = static_cast<AXPlatformNodeBase*>(
AXPlatformNode::FromNativeViewAccessible(n9->ChildAtIndex(0)));
auto* n7 = static_cast<AXPlatformNodeBase*>(
AXPlatformNode::FromNativeViewAccessible(n6->ChildAtIndex(0)));
// Test for two nodes that do not share the same root. They should not be
// comparable.
AXPlatformNodeBase detached_node;
EXPECT_EQ(std::nullopt, n1->CompareTo(detached_node));
// Create a test vector of all the tree nodes arranged in a pre-order
// traversal way. The node that has a smaller index in the vector should also
// be logically less (comes before) the nodes with bigger index.
std::vector<AXPlatformNodeBase*> preorder_tree_nodes = {n1, n2, n3, n4, n5,
n6, n7, n8, n9, n10};
// Test through all permutations of lhs/rhs comparisons of nodes from
// |preorder_tree_nodes|.
for (auto* lhs : preorder_tree_nodes) {
for (auto* rhs : preorder_tree_nodes) {
int expected_result = 0;
if (lhs->GetData().id < rhs->GetData().id)
expected_result = -1;
else if (lhs->GetData().id > rhs->GetData().id)
expected_result = 1;
EXPECT_NE(std::nullopt, lhs->CompareTo(*rhs));
int actual_result = 0;
if (lhs->CompareTo(*rhs) < 0)
actual_result = -1;
else if (lhs->CompareTo(*rhs) > 0)
actual_result = 1;
SCOPED_TRACE(testing::Message()
<< "lhs.id=" << base::NumberToString(lhs->GetData().id)
<< ", rhs.id=" << base::NumberToString(rhs->GetData().id)
<< ", lhs->CompareTo(*rhs)={actual:"
<< base::NumberToString(actual_result) << ", expected:"
<< base::NumberToString(expected_result) << "}");
EXPECT_EQ(expected_result, actual_result);
}
}
}
} // namespace ui