Commit 4335a96e authored by Akihiro Ota's avatar Akihiro Ota Committed by Commit Bot

Improve PosInSet and SetSize functionality

Build on PosInSet and SetSize functions in ax_node.cc to handle
invalid or erroneous assignment. SetSize should not allow decreasing
assignments, while PosInSet should not allow decreasing nor duplicate
assignments among nodes in the same container.

Change-Id: I07b0e9c493439aca9ef50ecfe3c3f5bbf7a7ce7a
Reviewed-on: https://chromium-review.googlesource.com/c/1343374
Commit-Queue: Akihiro Ota <akihiroota@chromium.org>
Reviewed-by: default avatarDominic Mazzoni <dmazzoni@chromium.org>
Cr-Commit-Position: refs/heads/master@{#610920}
parent 184fd278
......@@ -473,7 +473,8 @@ void AXNode::IdVectorToNodeVector(std::vector<int32_t>& ids,
}
}
// Determines the roles in which PosInSet and SetSize are used
// Returns true if the node's role uses PosInSet and SetSize
// Returns false otherwise.
bool AXNode::IsSetSizePosInSetUsedInRole() const {
switch (data().role) {
case ax::mojom::Role::kArticle:
......@@ -492,111 +493,198 @@ bool AXNode::IsSetSizePosInSetUsedInRole() const {
}
}
// Finds the 0-based index of first element in the container (with same role as
// node) where kPosInSet is assigned. Returns -1 if there are no elements with
// kPosInSet assigned
int32_t AXNode::FirstAssignedPosInSet() const {
AXNode* parent = GetUnignoredParent();
for (int i = 0; i < parent->GetUnignoredChildCount(); ++i) {
AXNode* candidate = parent->GetUnignoredChildAtIndex(i);
if (!(candidate->data().role == data().role))
continue;
if (candidate->HasIntAttribute(ax::mojom::IntAttribute::kPosInSet))
return i;
}
return -1;
}
// Returns true if a node's role matches with the role of its container.
// Returns false otherwise.
bool AXNode::ContainerRoleMatches(AXNode* container) const {
ax::mojom::Role container_role = container->data().role;
switch (data().role) {
case ax::mojom::Role::kArticle:
return container_role == ax::mojom::Role::kFeed;
// Calculates node's position relative to first PosInSet-assigned element.
// Returns node's 0-based index within container (relative to nodes with the
// same role) if no element assigned PosInSet
int32_t AXNode::RelativePosFromFirstAssigned(
int32_t first_assigned_index) const {
AXNode* parent = GetUnignoredParent();
// Find nodes index in container
int nodes_index = 0;
for (int i = 0; i < parent->GetUnignoredChildCount(); ++i) {
AXNode* candidate = parent->GetUnignoredChildAtIndex(i);
if (candidate == this)
break;
if (candidate->data().role == data().role)
++nodes_index;
}
case ax::mojom::Role::kListItem:
return container_role == ax::mojom::Role::kList ||
container_role == ax::mojom::Role::kGroup;
if (first_assigned_index == -1)
return nodes_index;
case ax::mojom::Role::kMenuItem:
return container_role == ax::mojom::Role::kMenu ||
container_role == ax::mojom::Role::kGroup ||
container_role == ax::mojom::Role::kMenuBar;
// Calculate relative position
return nodes_index - first_assigned_index;
}
case ax::mojom::Role::kMenuItemRadio:
return container_role == ax::mojom::Role::kGroup ||
container_role == ax::mojom::Role::kMenu ||
container_role == ax::mojom::Role::kMenuBar;
// Finds the position of this node within its container, relative to other
// nodes with the same role.
// Returns 1-based position if present in container, and 0 if not.
int32_t AXNode::PosInSet() const {
// Error checks
if (!IsSetSizePosInSetUsedInRole())
return 0;
AXNode* parent = GetUnignoredParent();
if (!parent)
return 0;
if (parent->data().role != ax::mojom::Role::kList)
return 0;
case ax::mojom::Role::kTab:
return container_role == ax::mojom::Role::kTabList;
// Check if kPosInSet assigned and return if provided
if (HasIntAttribute(ax::mojom::IntAttribute::kPosInSet)) {
return GetIntAttribute(ax::mojom::IntAttribute::kPosInSet);
}
case ax::mojom::Role::kMenuItemCheckBox:
return container_role == ax::mojom::Role::kMenu ||
container_role == ax::mojom::Role::kMenuBar;
// Caluclate PosInSet
// 1. Find index of first element (with same role) that has kPosInSet Assigned
// 2. Find relative position of this node compared to first assigned
// 3. The PosInSet of this node = PosInSet(first assigned) + relative position
case ax::mojom::Role::kTreeItem:
return container_role == ax::mojom::Role::kTree ||
container_role == ax::mojom::Role::kGroup;
case ax::mojom::Role::kListBoxOption:
return container_role == ax::mojom::Role::kListBox;
case ax::mojom::Role::kRadioButton:
return container_role == ax::mojom::Role::kRadioGroup;
default:
return false;
}
}
int32_t first_assigned_index = FirstAssignedPosInSet();
int32_t relative_position =
RelativePosFromFirstAssigned(first_assigned_index);
int32_t AXNode::GetPosInSet() {
int32_t pos = -1;
int32_t size = -1;
ComputeSetSizePosInSet(&pos, &size);
return pos;
}
int32_t AXNode::GetSetSize() {
int32_t pos = -1;
int32_t size = -1;
ComputeSetSizePosInSet(&pos, &size);
return size;
}
// If no element assigned PosInSet, return this node's 1-based position in the
// container
if (first_assigned_index == -1)
return relative_position + 1;
// Finds and returns a pointer to node's container.
// Is not required to have a role that matches node's role.
// Returns nullptr if node is not contained within container.
AXNode* AXNode::GetContainer() const {
AXNode* result = parent();
// Continue walking up while parent is invalid, ignored, or is a generic
// container.
while ((result && result->data().HasState(ax::mojom::State::kIgnored)) ||
result->data().role == ax::mojom::Role::kGenericContainer ||
result->data().role == ax::mojom::Role::kIgnored) {
result = result->parent();
}
return result;
}
return parent->GetUnignoredChildAtIndex(first_assigned_index)
->GetIntAttribute(ax::mojom::IntAttribute::kPosInSet) +
relative_position;
// Populates items vector with all nodes within container whose roles match.
void AXNode::PopulateContainerItems(AXNode* container,
AXNode* local_parent,
std::vector<AXNode*>& items) const {
// Stop searching current path if roles of local_parent and container match.
// Don't compare the container to itself.
if (!(container == local_parent))
if (local_parent->data().role == container->data().role)
return;
for (int i = 0; i < local_parent->child_count(); ++i) {
AXNode* child = local_parent->children_[i];
// Add child to items if role matches with root container's role.
if (child->ContainerRoleMatches(container))
items.push_back(child);
// Recurse if there is a generic container or is ignored.
if (child->data().role == ax::mojom::Role::kGenericContainer ||
child->data().role == ax::mojom::Role::kIgnored) {
PopulateContainerItems(container, child, items);
}
}
}
// Calculates the number of elements within node's container that have the
// same role as node.
int32_t AXNode::SetSize() const {
// Computes pos_in_set and set_size values for this node.
void AXNode::ComputeSetSizePosInSet(int32_t* out_pos_in_set,
int32_t* out_set_size) {
// Error checks
if (!IsSetSizePosInSetUsedInRole())
return 0;
AXNode* parent = GetUnignoredParent();
if (!parent)
return 0;
if (parent->data().role != ax::mojom::Role::kList)
return 0;
AXNode* container = GetContainer();
if (!(container && IsSetSizePosInSetUsedInRole() &&
ContainerRoleMatches(container))) {
*out_pos_in_set = 0;
*out_set_size = 0;
return;
}
// TODO (akihiroota): List objects should report SetSize
// Find all items within parent container and add to vector.
std::vector<AXNode*> items;
PopulateContainerItems(container, container, items);
// Necessary for calculating set_size. Keeps track of largest assigned
// kSetSize for each role.
std::unordered_map<ax::mojom::Role, int> largest_assigned_set_size;
// Iterate over vector of items and calculate pos_in_set and set_size for
// each. Items is not guaranteed to be populated with items of the same role.
// Use dictionary that maps role to frequency to calculate pos_in_set.
std::unordered_map<ax::mojom::Role, int> role_counts;
AXNode* node;
ax::mojom::Role node_role;
// Compute pos_in_set values.
for (unsigned int i = 0; i < items.size(); ++i) {
node = items[i];
node_role = node->data().role;
int32_t pos_in_set_value = 0;
if (role_counts.find(node_role) == role_counts.end())
// This is the first node with its role.
pos_in_set_value = 1;
else {
// This is the next node with its role.
pos_in_set_value = role_counts[node_role] + 1;
}
// Check if node has kPosInSet assigned. This assignment takes precedence
// over previous assignment.
if (node->HasIntAttribute(ax::mojom::IntAttribute::kPosInSet)) {
pos_in_set_value =
node->GetIntAttribute(ax::mojom::IntAttribute::kPosInSet);
// If invalid assignment (decrease or duplicate), adjust value.
if (pos_in_set_value <= role_counts[node_role]) {
pos_in_set_value = role_counts[node_role] + 1;
}
}
// Check if kSetSize assigned and return if provided
if (HasIntAttribute(ax::mojom::IntAttribute::kSetSize))
return GetIntAttribute(ax::mojom::IntAttribute::kSetSize);
// Assign pos_in_set and update role counts.
if (node == this) {
*out_pos_in_set = pos_in_set_value;
}
role_counts[node_role] = pos_in_set_value;
// Check if kSetSize is assigned and update if it's the largest assigned
// kSetSize.
if (node->HasIntAttribute(ax::mojom::IntAttribute::kSetSize))
largest_assigned_set_size[node_role] =
std::max(largest_assigned_set_size[node_role],
node->GetIntAttribute(ax::mojom::IntAttribute::kSetSize));
}
// Check if kSetSize assigned in container and return if provided
if (parent->HasIntAttribute(ax::mojom::IntAttribute::kSetSize))
return parent->GetIntAttribute(ax::mojom::IntAttribute::kSetSize);
// Compute set_size values.
for (unsigned int j = 0; j < items.size(); ++j) {
node = items[j];
node_role = node->data().role;
// TODO (akihiroota): List objects should report SetSize
// The SetSize of a node is the maximum of the following candidate values:
// 1. The PosInSet of the last value in the container (with same role as
// node's)
// 2. The Largest assigned SetSize in the container
// 3. The SetSize assigned within the node's container
int32_t pos_candidate = role_counts[node_role];
int32_t largest_set_size_candidate = 0;
if (largest_assigned_set_size.find(node_role) !=
largest_assigned_set_size.end()) {
largest_set_size_candidate = largest_assigned_set_size[node_role];
}
int32_t container_candidate = 0;
if (container->HasIntAttribute(ax::mojom::IntAttribute::kSetSize)) {
container_candidate =
container->GetIntAttribute(ax::mojom::IntAttribute::kSetSize);
}
// Calculate SetSize
int count = 0;
for (int i = 0; i < parent->GetUnignoredChildCount(); ++i) {
AXNode* child = parent->GetUnignoredChildAtIndex(i);
if (child->data().role == data().role)
++count;
// Assign set_size
if (node == this) {
*out_set_size =
std::max(std::max(pos_candidate, largest_set_size_candidate),
container_candidate);
}
}
return count;
}
} // namespace ui
......@@ -181,13 +181,9 @@ class AX_EXPORT AXNode final {
return data().GetHtmlAttribute(attribute, value);
}
// Finds the position of this node within its container, relative to others
// with the same role.
// Returns 1-based index if present in container, and 0 if not.
int32_t PosInSet() const;
// Calculates the number of elements within node's container that have the
// same role as node. Returns 0 if the node is not present wihtin a container.
int32_t SetSize() const;
// PosInSet and SetSize public methods
int32_t GetPosInSet();
int32_t GetSetSize();
const std::string& GetInheritedStringAttribute(
ax::mojom::StringAttribute attribute) const;
......@@ -255,16 +251,21 @@ class AX_EXPORT AXNode final {
void IdVectorToNodeVector(std::vector<int32_t>& ids,
std::vector<AXNode*>* nodes) const;
// Helpers for PosInset and SetSize
// Returns true if the PosInSet or SetSize attributes are used in node's role
// Helpers for GetPosInSet and GetSetSize.
// Returns true if the role of parent container matches the role of node.
// Returns false otherwise.
bool ContainerRoleMatches(AXNode* parent) const;
// Returns true if the node's role uses PosInSet and SetSize
// Returns false otherwise.
bool IsSetSizePosInSetUsedInRole() const;
// Calculates 0-based index of first element with PosInSet assigned.
// Returns -1 if no elements assign PosInSet
int32_t FirstAssignedPosInSet() const;
// Calculates node's position relative to first PosInSet-assigned element.
// Returns node's 0-based index within container (relative to nodes with the
// same role) if no element assigned PosInSet
int32_t RelativePosFromFirstAssigned(int32_t earliest_index) const;
// Finds and returns a pointer to node's container.
AXNode* GetContainer() const;
// Populates items vector with all nodes within container whose roles match.
void PopulateContainerItems(AXNode* container,
AXNode* local_parent,
std::vector<AXNode*>& items) const;
// Computes pos_in_set and set_size values for this node.
void ComputeSetSizePosInSet(int32_t* out_pos_in_set, int32_t* out_set_size);
OwnerTree* tree_; // Owns this.
int index_in_parent_;
......
......@@ -1472,7 +1472,7 @@ TEST(AXTreeTest, ChildTreeIds) {
EXPECT_EQ(0U, child_tree_93_nodes.size());
}
// Tests PosInSet and SetSize int attributes work if assigned
// Tests PosInSet and SetSize int attributes work if assigned.
TEST(AXTreeTest, TestSetSizePosInSetAssigned) {
AXTreeUpdate tree_update;
tree_update.root_id = 1;
......@@ -1495,14 +1495,14 @@ TEST(AXTreeTest, TestSetSizePosInSetAssigned) {
AXTree tree(tree_update);
AXNode* item1 = tree.GetFromId(2);
EXPECT_EQ(item1->PosInSet(), 2);
EXPECT_EQ(item1->SetSize(), 12);
EXPECT_EQ(item1->GetPosInSet(), 2);
EXPECT_EQ(item1->GetSetSize(), 12);
AXNode* item2 = tree.GetFromId(3);
EXPECT_EQ(item2->PosInSet(), 5);
EXPECT_EQ(item2->SetSize(), 12);
EXPECT_EQ(item2->GetPosInSet(), 5);
EXPECT_EQ(item2->GetSetSize(), 12);
AXNode* item3 = tree.GetFromId(4);
EXPECT_EQ(item3->PosInSet(), 9);
EXPECT_EQ(item3->SetSize(), 12);
EXPECT_EQ(item3->GetPosInSet(), 9);
EXPECT_EQ(item3->GetSetSize(), 12);
}
// Tests that PosInSet and SetSize can be calculated if not assigned.
......@@ -1522,17 +1522,17 @@ TEST(AXTreeTest, TestSetSizePosInSetUnassigned) {
AXTree tree(tree_update);
AXNode* item1 = tree.GetFromId(2);
EXPECT_EQ(item1->PosInSet(), 1);
EXPECT_EQ(item1->SetSize(), 3);
EXPECT_EQ(item1->GetPosInSet(), 1);
EXPECT_EQ(item1->GetSetSize(), 3);
AXNode* item2 = tree.GetFromId(3);
EXPECT_EQ(item2->PosInSet(), 2);
EXPECT_EQ(item2->SetSize(), 3);
EXPECT_EQ(item2->GetPosInSet(), 2);
EXPECT_EQ(item2->GetSetSize(), 3);
AXNode* item3 = tree.GetFromId(4);
EXPECT_EQ(item3->PosInSet(), 3);
EXPECT_EQ(item3->SetSize(), 3);
EXPECT_EQ(item3->GetPosInSet(), 3);
EXPECT_EQ(item3->GetSetSize(), 3);
}
// Tests PosInSet unassigned, while SetSize assigned in container
// Tests PosInSet unassigned, while SetSize assigned in container.
TEST(AXTreeTest, TestSetSizeAssignedInContainer) {
AXTreeUpdate tree_update;
tree_update.root_id = 1;
......@@ -1549,16 +1549,17 @@ TEST(AXTreeTest, TestSetSizeAssignedInContainer) {
tree_update.nodes[3].role = ax::mojom::Role::kListItem;
AXTree tree(tree_update);
// Items should inherit SetSize from container if not specified
// Items should inherit SetSize from container if not specified.
AXNode* item1 = tree.GetFromId(2);
EXPECT_EQ(item1->SetSize(), 7);
EXPECT_EQ(item1->GetSetSize(), 7);
AXNode* item2 = tree.GetFromId(3);
EXPECT_EQ(item2->SetSize(), 7);
EXPECT_EQ(item2->GetSetSize(), 7);
AXNode* item3 = tree.GetFromId(4);
EXPECT_EQ(item3->SetSize(), 7);
EXPECT_EQ(item3->GetSetSize(), 7);
}
// Tests PosInSet and SetSize on a list containing various roles.
// Roles for items and associated container should match up.
TEST(AXTreeTest, TestSetSizePosInSetDiverseList) {
AXTreeUpdate tree_update;
tree_update.root_id = 1;
......@@ -1569,15 +1570,15 @@ TEST(AXTreeTest, TestSetSizePosInSetDiverseList) {
tree_update.nodes[1].id = 2;
tree_update.nodes[1].role = ax::mojom::Role::kListItem; // 1 of 3
tree_update.nodes[2].id = 3;
tree_update.nodes[2].role = ax::mojom::Role::kMenuItem; // 1 of 2
tree_update.nodes[2].role = ax::mojom::Role::kMenuItem; // 0 of 0
tree_update.nodes[3].id = 4;
tree_update.nodes[3].role = ax::mojom::Role::kListItem; // 2 of 3
tree_update.nodes[4].id = 5;
tree_update.nodes[4].role = ax::mojom::Role::kMenuItem; // 2 of 2
tree_update.nodes[4].role = ax::mojom::Role::kMenuItem; // 0 of 0
tree_update.nodes[5].id = 6;
tree_update.nodes[5].role = ax::mojom::Role::kArticle; // 1 of 2
tree_update.nodes[5].role = ax::mojom::Role::kArticle; // 0 of 0
tree_update.nodes[6].id = 7;
tree_update.nodes[6].role = ax::mojom::Role::kArticle; // 2 of 2
tree_update.nodes[6].role = ax::mojom::Role::kArticle; // 0 of 0
tree_update.nodes[7].id = 8;
tree_update.nodes[7].role = ax::mojom::Role::kListItem; // 3 of 3
tree_update.nodes[8].id = 9;
......@@ -1585,33 +1586,32 @@ TEST(AXTreeTest, TestSetSizePosInSetDiverseList) {
AXTree tree(tree_update);
AXNode* listitem1 = tree.GetFromId(2);
EXPECT_EQ(listitem1->PosInSet(), 1);
EXPECT_EQ(listitem1->SetSize(), 3);
AXNode* listitem2 = tree.GetFromId(3);
EXPECT_EQ(listitem2->PosInSet(), 1);
EXPECT_EQ(listitem2->SetSize(), 2);
AXNode* menuitem1 = tree.GetFromId(4);
EXPECT_EQ(menuitem1->PosInSet(), 2);
EXPECT_EQ(menuitem1->SetSize(), 3);
EXPECT_EQ(listitem1->GetPosInSet(), 1);
EXPECT_EQ(listitem1->GetSetSize(), 3);
AXNode* menuitem1 = tree.GetFromId(3);
EXPECT_EQ(menuitem1->GetPosInSet(), 0);
EXPECT_EQ(menuitem1->GetSetSize(), 0);
AXNode* listitem2 = tree.GetFromId(4);
EXPECT_EQ(listitem2->GetPosInSet(), 2);
EXPECT_EQ(listitem2->GetSetSize(), 3);
AXNode* menuitem2 = tree.GetFromId(5);
EXPECT_EQ(menuitem2->PosInSet(), 2);
EXPECT_EQ(menuitem2->SetSize(), 2);
EXPECT_EQ(menuitem2->GetPosInSet(), 0);
EXPECT_EQ(menuitem2->GetSetSize(), 0);
AXNode* article1 = tree.GetFromId(6);
EXPECT_EQ(article1->PosInSet(), 1);
EXPECT_EQ(article1->SetSize(), 2);
EXPECT_EQ(article1->GetPosInSet(), 0);
EXPECT_EQ(article1->GetSetSize(), 0);
AXNode* article2 = tree.GetFromId(7);
EXPECT_EQ(article2->PosInSet(), 2);
EXPECT_EQ(article2->SetSize(), 2);
EXPECT_EQ(article2->GetPosInSet(), 0);
EXPECT_EQ(article2->GetSetSize(), 0);
AXNode* listitem3 = tree.GetFromId(8);
EXPECT_EQ(listitem3->PosInSet(), 3);
EXPECT_EQ(listitem3->SetSize(), 3);
AXNode* image1 = tree.GetFromId(9);
// Roles that do not use PosInSet or SetSize should return 0
EXPECT_EQ(image1->PosInSet(), 0);
EXPECT_EQ(image1->SetSize(), 0);
EXPECT_EQ(listitem3->GetPosInSet(), 3);
EXPECT_EQ(listitem3->GetSetSize(), 3);
AXNode* image = tree.GetFromId(9);
EXPECT_EQ(image->GetPosInSet(), 0);
EXPECT_EQ(image->GetSetSize(), 0);
}
// Tests PosInSet and SetSize on a nested list
// Tests PosInSet and SetSize on a nested list.
TEST(AXTreeTest, TestSetSizePosInSetNestedList) {
AXTreeUpdate tree_update;
tree_update.root_id = 1;
......@@ -1635,28 +1635,28 @@ TEST(AXTreeTest, TestSetSizePosInSetNestedList) {
AXTree tree(tree_update);
AXNode* outer_item1 = tree.GetFromId(2);
EXPECT_EQ(outer_item1->PosInSet(), 1);
EXPECT_EQ(outer_item1->SetSize(), 3);
EXPECT_EQ(outer_item1->GetPosInSet(), 1);
EXPECT_EQ(outer_item1->GetSetSize(), 3);
AXNode* outer_item2 = tree.GetFromId(3);
EXPECT_EQ(outer_item2->PosInSet(), 2);
EXPECT_EQ(outer_item2->SetSize(), 3);
EXPECT_EQ(outer_item2->GetPosInSet(), 2);
EXPECT_EQ(outer_item2->GetSetSize(), 3);
// List object itself should not report posinset or setsize
// TODO (akihiroota): Lists should report setsize in the future
// List object itself should not report posinset or setsize.
// TODO (akihiroota): Lists should report setsize in the future.
AXNode* inner_list = tree.GetFromId(4);
EXPECT_EQ(inner_list->PosInSet(), 0);
EXPECT_EQ(inner_list->SetSize(), 0);
EXPECT_EQ(inner_list->GetPosInSet(), 0);
EXPECT_EQ(inner_list->GetSetSize(), 0);
AXNode* inner_item1 = tree.GetFromId(5);
EXPECT_EQ(inner_item1->PosInSet(), 1);
EXPECT_EQ(inner_item1->SetSize(), 2);
EXPECT_EQ(inner_item1->GetPosInSet(), 1);
EXPECT_EQ(inner_item1->GetSetSize(), 2);
AXNode* inner_item2 = tree.GetFromId(6);
EXPECT_EQ(inner_item2->PosInSet(), 2);
EXPECT_EQ(inner_item2->SetSize(), 2);
EXPECT_EQ(inner_item2->GetPosInSet(), 2);
EXPECT_EQ(inner_item2->GetSetSize(), 2);
AXNode* outer_item3 = tree.GetFromId(7);
EXPECT_EQ(outer_item3->PosInSet(), 3);
EXPECT_EQ(outer_item3->SetSize(), 3);
EXPECT_EQ(outer_item3->GetPosInSet(), 3);
EXPECT_EQ(outer_item3->GetSetSize(), 3);
}
// Tests PosInSet can be calculated if one item specifies PosInSet, but others
......@@ -1678,17 +1678,194 @@ TEST(AXTreeTest, TestPosInSetMissing) {
tree_update.nodes[3].role = ax::mojom::Role::kListItem;
AXTree tree(tree_update);
// Item1 should have pos of 12, since item2 is assigned a pos of 13
// Item1 should have pos of 12, since item2 is assigned a pos of 13.
AXNode* item1 = tree.GetFromId(2);
EXPECT_EQ(item1->PosInSet(), 12);
EXPECT_EQ(item1->SetSize(), 20);
EXPECT_EQ(item1->GetPosInSet(), 1);
EXPECT_EQ(item1->GetSetSize(), 20);
AXNode* item2 = tree.GetFromId(3);
EXPECT_EQ(item2->PosInSet(), 13);
EXPECT_EQ(item2->SetSize(), 20);
// Item2 should have pos of 14, since item2 is assigned a pos of 13
EXPECT_EQ(item2->GetPosInSet(), 13);
EXPECT_EQ(item2->GetSetSize(), 20);
// Item2 should have pos of 14, since item2 is assigned a pos of 13.
AXNode* item3 = tree.GetFromId(4);
EXPECT_EQ(item3->PosInSet(), 14);
EXPECT_EQ(item3->SetSize(), 20);
EXPECT_EQ(item3->GetPosInSet(), 14);
EXPECT_EQ(item3->GetSetSize(), 20);
}
// A more difficult test that invovles missing PosInSet and SetSize values.
TEST(AXTreeTest, TestSetSizePosInSetMissingDifficult) {
AXTreeUpdate tree_update;
tree_update.root_id = 1;
tree_update.nodes.resize(6);
tree_update.nodes[0].id = 1;
tree_update.nodes[0].role = ax::mojom::Role::kList;
tree_update.nodes[0].child_ids = {2, 3, 4, 5, 6};
tree_update.nodes[1].id = 2;
tree_update.nodes[1].role = ax::mojom::Role::kListItem; // 1 of 11
tree_update.nodes[2].id = 3;
tree_update.nodes[2].role = ax::mojom::Role::kListItem;
tree_update.nodes[2].AddIntAttribute(ax::mojom::IntAttribute::kPosInSet,
5); // 5 of 11
tree_update.nodes[3].id = 4;
tree_update.nodes[3].role = ax::mojom::Role::kListItem; // 6 of 11
tree_update.nodes[4].id = 5;
tree_update.nodes[4].role = ax::mojom::Role::kListItem;
tree_update.nodes[4].AddIntAttribute(ax::mojom::IntAttribute::kPosInSet,
10); // 10 of 11
tree_update.nodes[5].id = 6;
tree_update.nodes[5].role = ax::mojom::Role::kListItem; // 11 of 11
AXTree tree(tree_update);
AXNode* item1 = tree.GetFromId(2);
EXPECT_EQ(item1->GetPosInSet(), 1);
EXPECT_EQ(item1->GetSetSize(), 11);
AXNode* item2 = tree.GetFromId(3);
EXPECT_EQ(item2->GetPosInSet(), 5);
EXPECT_EQ(item2->GetSetSize(), 11);
AXNode* item3 = tree.GetFromId(4);
EXPECT_EQ(item3->GetPosInSet(), 6);
EXPECT_EQ(item3->GetSetSize(), 11);
AXNode* item4 = tree.GetFromId(5);
EXPECT_EQ(item4->GetPosInSet(), 10);
EXPECT_EQ(item4->GetSetSize(), 11);
AXNode* item5 = tree.GetFromId(6);
EXPECT_EQ(item5->GetPosInSet(), 11);
EXPECT_EQ(item5->GetSetSize(), 11);
}
// Tests that code overwrites decreasing SetSize assignments to largest of
// assigned values.
TEST(AXTreeTest, TestSetSizeDecreasing) {
AXTreeUpdate tree_update;
tree_update.root_id = 1;
tree_update.nodes.resize(4);
tree_update.nodes[0].id = 1;
tree_update.nodes[0].role = ax::mojom::Role::kList;
tree_update.nodes[0].child_ids = {2, 3, 4};
tree_update.nodes[1].id = 2;
tree_update.nodes[1].role = ax::mojom::Role::kListItem; // 1 of 5
tree_update.nodes[2].id = 3;
tree_update.nodes[2].role = ax::mojom::Role::kListItem; // 2 of 5
tree_update.nodes[2].AddIntAttribute(ax::mojom::IntAttribute::kSetSize, 5);
tree_update.nodes[3].id = 4;
tree_update.nodes[3].role = ax::mojom::Role::kListItem; // 3 of 5
tree_update.nodes[3].AddIntAttribute(ax::mojom::IntAttribute::kSetSize, 4);
AXTree tree(tree_update);
AXNode* item1 = tree.GetFromId(2);
EXPECT_EQ(item1->GetPosInSet(), 1);
EXPECT_EQ(item1->GetSetSize(), 5);
AXNode* item2 = tree.GetFromId(3);
EXPECT_EQ(item2->GetPosInSet(), 2);
EXPECT_EQ(item2->GetSetSize(), 5);
AXNode* item3 = tree.GetFromId(4);
EXPECT_EQ(item3->GetPosInSet(), 3);
EXPECT_EQ(item3->GetSetSize(), 5);
}
// Tests that code overwrites decreasing PosInSet values.
TEST(AXTreeTest, TestPosInSetDecreasing) {
AXTreeUpdate tree_update;
tree_update.root_id = 1;
tree_update.nodes.resize(4);
tree_update.nodes[0].id = 1;
tree_update.nodes[0].role = ax::mojom::Role::kList;
tree_update.nodes[0].child_ids = {2, 3, 4};
tree_update.nodes[1].id = 2;
tree_update.nodes[1].role = ax::mojom::Role::kListItem; // 1 of 8
tree_update.nodes[2].id = 3;
tree_update.nodes[2].role = ax::mojom::Role::kListItem; // 7 of 8
tree_update.nodes[2].AddIntAttribute(ax::mojom::IntAttribute::kPosInSet, 7);
tree_update.nodes[3].id = 4;
tree_update.nodes[3].role = ax::mojom::Role::kListItem; // 8 of 8
tree_update.nodes[3].AddIntAttribute(ax::mojom::IntAttribute::kPosInSet, 3);
AXTree tree(tree_update);
AXNode* item1 = tree.GetFromId(2);
EXPECT_EQ(item1->GetPosInSet(), 1);
EXPECT_EQ(item1->GetSetSize(), 8);
AXNode* item2 = tree.GetFromId(3);
EXPECT_EQ(item2->GetPosInSet(), 7);
EXPECT_EQ(item2->GetSetSize(), 8);
AXNode* item3 = tree.GetFromId(4);
EXPECT_EQ(item3->GetPosInSet(), 8);
EXPECT_EQ(item3->GetSetSize(), 8);
}
// Tests that code overwrites duplicate PosInSet values. Note this case is
// tricky; an update to the second element causes an update to the third
// element.
TEST(AXTreeTest, TestPosInSetDuplicates) {
AXTreeUpdate tree_update;
tree_update.root_id = 1;
tree_update.nodes.resize(4);
tree_update.nodes[0].id = 1;
tree_update.nodes[0].role = ax::mojom::Role::kList;
tree_update.nodes[0].child_ids = {2, 3, 4};
tree_update.nodes[1].id = 2;
tree_update.nodes[1].role = ax::mojom::Role::kListItem; // 6 of 8
tree_update.nodes[1].AddIntAttribute(ax::mojom::IntAttribute::kPosInSet, 6);
tree_update.nodes[2].id = 3;
tree_update.nodes[2].role = ax::mojom::Role::kListItem; // 7 of 8
tree_update.nodes[2].AddIntAttribute(ax::mojom::IntAttribute::kPosInSet, 6);
tree_update.nodes[3].id = 4;
tree_update.nodes[3].role = ax::mojom::Role::kListItem; // 8 of 8
tree_update.nodes[3].AddIntAttribute(ax::mojom::IntAttribute::kPosInSet, 7);
AXTree tree(tree_update);
AXNode* item1 = tree.GetFromId(2);
EXPECT_EQ(item1->GetPosInSet(), 6);
EXPECT_EQ(item1->GetSetSize(), 8);
AXNode* item2 = tree.GetFromId(3);
EXPECT_EQ(item2->GetPosInSet(), 7);
EXPECT_EQ(item2->GetSetSize(), 8);
AXNode* item3 = tree.GetFromId(4);
EXPECT_EQ(item3->GetPosInSet(), 8);
EXPECT_EQ(item3->GetSetSize(), 8);
}
// Tests PosInSet and SetSize when some list items are nested in a generic
// container.
TEST(AXTreeTest, TestSetSizePosInSetNestedContainer) {
AXTreeUpdate tree_update;
tree_update.root_id = 1;
tree_update.nodes.resize(7);
tree_update.nodes[0].id = 1;
tree_update.nodes[0].role = ax::mojom::Role::kList;
tree_update.nodes[0].child_ids = {2, 3, 7};
tree_update.nodes[1].id = 2;
tree_update.nodes[1].role = ax::mojom::Role::kListItem; // 1 of 4
tree_update.nodes[2].id = 3;
tree_update.nodes[2].role = ax::mojom::Role::kGenericContainer;
tree_update.nodes[2].child_ids = {4, 5};
tree_update.nodes[3].id = 4;
tree_update.nodes[3].role = ax::mojom::Role::kListItem; // 2 of 4
tree_update.nodes[4].id = 5;
tree_update.nodes[4].role = ax::mojom::Role::kIgnored;
tree_update.nodes[4].child_ids = {6};
tree_update.nodes[5].id = 6;
tree_update.nodes[5].role = ax::mojom::Role::kListItem; // 3 of 4
tree_update.nodes[6].id = 7;
tree_update.nodes[6].role = ax::mojom::Role::kListItem; // 4 of 4
AXTree tree(tree_update);
AXNode* item1 = tree.GetFromId(2);
EXPECT_EQ(item1->GetPosInSet(), 1);
EXPECT_EQ(item1->GetSetSize(), 4);
AXNode* g_container = tree.GetFromId(3);
EXPECT_EQ(g_container->GetPosInSet(), 0);
EXPECT_EQ(g_container->GetSetSize(), 0);
AXNode* item2 = tree.GetFromId(4);
EXPECT_EQ(item2->GetPosInSet(), 2);
EXPECT_EQ(item2->GetSetSize(), 4);
AXNode* ignored = tree.GetFromId(5);
EXPECT_EQ(ignored->GetPosInSet(), 0);
EXPECT_EQ(ignored->GetSetSize(), 0);
AXNode* item3 = tree.GetFromId(6);
EXPECT_EQ(item3->GetPosInSet(), 3);
EXPECT_EQ(item3->GetSetSize(), 4);
AXNode* item4 = tree.GetFromId(7);
EXPECT_EQ(item4->GetPosInSet(), 4);
EXPECT_EQ(item4->GetSetSize(), 4);
}
} // namespace ui
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment