Commit 18af30bc authored by Chris Hall's avatar Chris Hall Committed by Commit Bot

Refactor AXNode's GetNextUnignoredSibling and GetPreviousUnignoredSibling.

 - Refactor and simplify next/previous unignored sibling methods on AXNode.
 - Add extensive documentation, both interface and implementation.
 - Add more extensive and finer-grained unit tests.

R=aboxhall,dmazzoni,aleventhal,janewman@microsoft.com,ethavar@microsoft.com

Change-Id: Idd85a35de6a0cfa314dfba7255c22bc58005f1e6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2102269
Commit-Queue: Chris Hall <chrishall@chromium.org>
Reviewed-by: default avatarJacques Newman <janewman@microsoft.com>
Reviewed-by: default avatarEthan Jimenez <ethavar@microsoft.com>
Reviewed-by: default avatarAlice Boxhall <aboxhall@chromium.org>
Reviewed-by: default avatarDominic Mazzoni <dmazzoni@chromium.org>
Cr-Commit-Position: refs/heads/master@{#757729}
parent 11df03ba
......@@ -107,67 +107,177 @@ AXNode* AXNode::GetDeepestLastUnignoredChild() const {
return deepest_child;
}
// Search for the next sibling of this node, skipping over any ignored nodes
// encountered.
//
// In our search:
// If we find an ignored sibling, we consider its children as our siblings.
// If we run out of siblings, we consider an ignored parent's siblings as our
// own siblings.
//
// Note: this behaviour of 'skipping over' an ignored node makes this subtly
// different to finding the next (direct) sibling which is unignored.
//
// Consider a tree, where (i) marks a node as ignored:
//
// 1
// ├── 2
// ├── 3(i)
// │ └── 5
// └── 4
//
// The next sibling of node 2 is node 3, which is ignored.
// The next unignored sibling of node 2 could be either:
// 1) node 4 - next unignored sibling in the literal tree, or
// 2) node 5 - next unignored sibling in the logical document.
//
// There is no next sibling of node 5.
// The next unignored sibling of node 5 could be either:
// 1) null - no next sibling in the literal tree, or
// 2) node 4 - next unignored sibling in the logical document.
//
// In both cases, this method implements approach (2).
//
// TODO(chrishall): Can we remove this non-reflexive case by forbidding
// GetNextUnignoredSibling calls on an ignored started node?
// Note: this means that Next/Previous-UnignoredSibling are not reflexive if
// either of the nodes in question are ignored. From above we get an example:
// NextUnignoredSibling(3) is 4, but
// PreviousUnignoredSibling(4) is 5.
//
// The view of unignored siblings for node 3 includes both node 2 and node 4:
// 2 <-- [3(i)] --> 4
//
// Whereas nodes 2, 5, and 4 do not consider node 3 to be an unignored sibling:
// null <-- [2] --> 5
// 2 <-- [5] --> 4
// 5 <-- [4] --> null
AXNode* AXNode::GetNextUnignoredSibling() const {
DCHECK(!tree_->GetTreeUpdateInProgressState());
AXNode* parent_node = parent();
size_t index = index_in_parent() + 1;
while (parent_node) {
if (index < parent_node->children().size()) {
AXNode* child = parent_node->children()[index];
if (!child->IsIgnored())
return child; // valid position (unignored child)
const AXNode* current = this;
// If there are children of the |current| node still to consider.
bool considerChildren = false;
while (current) {
// A |candidate| sibling to consider.
// If it is unignored then we have found our result.
// Otherwise promote it to |current| and consider its children.
AXNode* candidate;
if (considerChildren && (candidate = current->GetFirstChild())) {
if (!candidate->IsIgnored())
return candidate;
current = candidate;
} else if ((candidate = current->GetNextSibling())) {
if (!candidate->IsIgnored())
return candidate;
current = candidate;
// Look through the ignored candidate node to consider their children as
// though they were siblings.
considerChildren = true;
// If the node is ignored, drill down to the ignored node's first child.
parent_node = child;
index = 0;
} else {
// If the parent is not ignored and we are past all of its children, there
// is no next sibling.
if (!parent_node->IsIgnored())
// Continue our search through a parent iff they are ignored.
//
// If |current| has an ignored parent, then we consider the parent's
// siblings as though they were siblings of |current|.
//
// Given a tree:
// 1
// ├── 2(?)
// │ └── [4]
// └── 3
//
// Node 4's view of siblings:
// literal tree: null <-- [4] --> null
//
// If node 2 is not ignored, then node 4's view doesn't change, and we
// have no more nodes to consider:
// unignored tree: null <-- [4] --> null
//
// If instead node 2 is ignored, then node 4's view of siblings grows to
// include node 3, and we have more nodes to consider:
// unignored tree: null <-- [4] --> 3
current = current->parent();
if (!current || !current->IsIgnored())
return nullptr;
// If the parent is ignored and we are past all of its children, continue
// on to the parent's next sibling.
index = parent_node->index_in_parent() + 1;
parent_node = parent_node->parent();
// We have already considered all relevant descendants of |current|.
considerChildren = false;
}
}
return nullptr;
}
// Search for the previous sibling of this node, skipping over any ignored nodes
// encountered.
//
// In our search for a sibling:
// If we find an ignored sibling, we may consider its children as siblings.
// If we run out of siblings, we may consider an ignored parent's siblings as
// our own.
//
// See the documentation for |GetNextUnignoredSibling| for more details.
AXNode* AXNode::GetPreviousUnignoredSibling() const {
DCHECK(!tree_->GetTreeUpdateInProgressState());
AXNode* parent_node = parent();
base::Optional<size_t> index;
if (index_in_parent() > 0)
index = index_in_parent() - 1;
while (parent_node) {
if (index.has_value()) {
AXNode* child = parent_node->children()[index.value()];
if (!child->IsIgnored())
return child; // valid position (unignored child)
// If the node is ignored, drill down to the ignored node's last child.
parent_node = child;
if (parent_node->children().empty())
index = base::nullopt;
else
index = parent_node->children().size() - 1;
const AXNode* current = this;
// If there are children of the |current| node still to consider.
bool considerChildren = false;
while (current) {
// A |candidate| sibling to consider.
// If it is unignored then we have found our result.
// Otherwise promote it to |current| and consider its children.
AXNode* candidate;
if (considerChildren && (candidate = current->GetLastChild())) {
if (!candidate->IsIgnored())
return candidate;
current = candidate;
} else if ((candidate = current->GetPreviousSibling())) {
if (!candidate->IsIgnored())
return candidate;
current = candidate;
// Look through the ignored candidate node to consider their children as
// though they were siblings.
considerChildren = true;
} else {
// If the parent is not ignored and we are past all of its children, there
// is no next sibling.
if (!parent_node->IsIgnored())
// Continue our search through a parent iff they are ignored.
//
// If |current| has an ignored parent, then we consider the parent's
// siblings as though they were siblings of |current|.
//
// Given a tree:
// 1
// ├── 2
// └── 3(?)
// └── [4]
//
// Node 4's view of siblings:
// literal tree: null <-- [4] --> null
//
// If node 3 is not ignored, then node 4's view doesn't change, and we
// have no more nodes to consider:
// unignored tree: null <-- [4] --> null
//
// If instead node 3 is ignored, then node 4's view of siblings grows to
// include node 2, and we have more nodes to consider:
// unignored tree: 2 <-- [4] --> null
current = current->parent();
if (!current || !current->IsIgnored())
return nullptr;
// If the parent is ignored and we are past all of its children, continue
// on to the parent's previous sibling.
if (parent_node->index_in_parent() == 0)
index = base::nullopt;
else
index = parent_node->index_in_parent() - 1;
parent_node = parent_node->parent();
// We have already considered all relevant descendants of |current|.
considerChildren = false;
}
}
return nullptr;
}
......@@ -208,6 +318,41 @@ AXNode::UnignoredChildIterator AXNode::UnignoredChildrenEnd() const {
return UnignoredChildIterator(this, nullptr);
}
// The first (direct) child, ignored or unignored.
AXNode* AXNode::GetFirstChild() const {
if (children().size() == 0)
return nullptr;
return children()[0];
}
// The last (direct) child, ignored or unignored.
AXNode* AXNode::GetLastChild() const {
size_t n = children().size();
if (n == 0)
return nullptr;
return children()[n - 1];
}
// The previous (direct) sibling, ignored or unignored.
AXNode* AXNode::GetPreviousSibling() const {
// Root nodes lack a parent, their index_in_parent should be 0.
DCHECK(!parent() ? index_in_parent() == 0 : true);
size_t index = index_in_parent();
if (index == 0)
return nullptr;
return parent()->children()[index - 1];
}
// The next (direct) sibling, ignored or unignored.
AXNode* AXNode::GetNextSibling() const {
if (!parent())
return nullptr;
size_t nextIndex = index_in_parent() + 1;
if (nextIndex >= parent()->children().size())
return nullptr;
return parent()->children()[nextIndex];
}
bool AXNode::IsText() const {
return data().role == ax::mojom::Role::kStaticText ||
data().role == ax::mojom::Role::kLineBreak ||
......
......@@ -132,6 +132,13 @@ class AX_EXPORT AXNode final {
UnignoredChildIterator UnignoredChildrenBegin() const;
UnignoredChildIterator UnignoredChildrenEnd() const;
// Walking the tree including both ignored and unignored nodes.
// These methods consider only the direct children or siblings of a node.
AXNode* GetFirstChild() const;
AXNode* GetLastChild() const;
AXNode* GetPreviousSibling() const;
AXNode* GetNextSibling() const;
// Returns true if the node has any of the text related roles.
bool IsText() const;
......
......@@ -2384,6 +2384,309 @@ TEST(AXTreeTest, UnignoredNextPreviousChild) {
EXPECT_EQ(nullptr, tree.GetFromId(16)->GetPreviousUnignoredSibling());
}
TEST(AXTreeTest, GetSiblingsNoIgnored) {
// Since this tree contains no ignored nodes, PreviousSibling and NextSibling
// are equivalent to their unignored counterparts.
//
// 1
// ├── 2
// │ └── 4
// └── 3
AXTreeUpdate tree_update;
tree_update.root_id = 1;
tree_update.nodes.resize(4);
tree_update.nodes[0].id = 1;
tree_update.nodes[0].child_ids = {2, 3};
tree_update.nodes[1].id = 2;
tree_update.nodes[1].child_ids = {4};
tree_update.nodes[2].id = 3;
tree_update.nodes[3].id = 4;
AXTree tree(tree_update);
EXPECT_EQ(nullptr, tree.GetFromId(1)->GetPreviousSibling());
EXPECT_EQ(nullptr, tree.GetFromId(1)->GetPreviousUnignoredSibling());
EXPECT_EQ(nullptr, tree.GetFromId(1)->GetNextSibling());
EXPECT_EQ(nullptr, tree.GetFromId(1)->GetNextUnignoredSibling());
EXPECT_EQ(nullptr, tree.GetFromId(2)->GetPreviousSibling());
EXPECT_EQ(nullptr, tree.GetFromId(2)->GetPreviousUnignoredSibling());
EXPECT_EQ(tree.GetFromId(3), tree.GetFromId(2)->GetNextSibling());
EXPECT_EQ(tree.GetFromId(3), tree.GetFromId(2)->GetNextUnignoredSibling());
EXPECT_EQ(tree.GetFromId(2), tree.GetFromId(3)->GetPreviousSibling());
EXPECT_EQ(tree.GetFromId(2),
tree.GetFromId(3)->GetPreviousUnignoredSibling());
EXPECT_EQ(nullptr, tree.GetFromId(3)->GetNextSibling());
EXPECT_EQ(nullptr, tree.GetFromId(3)->GetNextUnignoredSibling());
EXPECT_EQ(nullptr, tree.GetFromId(4)->GetPreviousSibling());
EXPECT_EQ(nullptr, tree.GetFromId(4)->GetPreviousUnignoredSibling());
EXPECT_EQ(nullptr, tree.GetFromId(4)->GetNextSibling());
EXPECT_EQ(nullptr, tree.GetFromId(4)->GetNextUnignoredSibling());
}
TEST(AXTreeTest, GetUnignoredSiblingsChildrenPromoted) {
// An ignored node has its' children considered as though they were promoted
// to their parents place.
//
// (i) => node is ignored.
//
// 1
// ├── 2(i)
// │ ├── 4
// │ └── 5
// └── 3
AXTreeUpdate tree_update;
tree_update.root_id = 1;
tree_update.nodes.resize(5);
tree_update.nodes[0].id = 1;
tree_update.nodes[0].child_ids = {2, 3};
tree_update.nodes[1].id = 2;
tree_update.nodes[1].AddState(ax::mojom::State::kIgnored);
tree_update.nodes[1].child_ids = {4, 5};
tree_update.nodes[2].id = 3;
tree_update.nodes[3].id = 4;
tree_update.nodes[4].id = 5;
AXTree tree(tree_update);
// Root node has no siblings.
EXPECT_EQ(nullptr, tree.GetFromId(1)->GetPreviousUnignoredSibling());
EXPECT_EQ(nullptr, tree.GetFromId(2)->GetPreviousUnignoredSibling());
// Node 2's view of siblings:
// literal tree: null <-- [2(i)] --> 3
// unignored tree: null <-- [2(i)] --> 3
EXPECT_EQ(nullptr, tree.GetFromId(2)->GetPreviousSibling());
EXPECT_EQ(nullptr, tree.GetFromId(2)->GetPreviousUnignoredSibling());
EXPECT_EQ(tree.GetFromId(3), tree.GetFromId(2)->GetNextSibling());
EXPECT_EQ(tree.GetFromId(3), tree.GetFromId(2)->GetNextUnignoredSibling());
// Node 3's view of siblings:
// literal tree: 2(i) <-- [3] --> null
// unignored tree: 5 <-- [4] --> null
EXPECT_EQ(tree.GetFromId(2), tree.GetFromId(3)->GetPreviousSibling());
EXPECT_EQ(tree.GetFromId(5),
tree.GetFromId(3)->GetPreviousUnignoredSibling());
EXPECT_EQ(nullptr, tree.GetFromId(3)->GetNextSibling());
EXPECT_EQ(nullptr, tree.GetFromId(3)->GetNextUnignoredSibling());
// Node 4's view of siblings:
// literal tree: null <-- [4] --> 5
// unignored tree: null <-- [4] --> 5
EXPECT_EQ(nullptr, tree.GetFromId(4)->GetPreviousSibling());
EXPECT_EQ(nullptr, tree.GetFromId(4)->GetPreviousUnignoredSibling());
EXPECT_EQ(tree.GetFromId(5), tree.GetFromId(4)->GetNextSibling());
EXPECT_EQ(tree.GetFromId(5), tree.GetFromId(4)->GetNextUnignoredSibling());
// Node 5's view of siblings:
// literal tree: 4 <-- [5] --> null
// unignored tree: 4 <-- [5] --> 3
EXPECT_EQ(tree.GetFromId(4), tree.GetFromId(5)->GetPreviousSibling());
EXPECT_EQ(tree.GetFromId(4),
tree.GetFromId(5)->GetPreviousUnignoredSibling());
EXPECT_EQ(nullptr, tree.GetFromId(5)->GetNextSibling());
EXPECT_EQ(tree.GetFromId(3), tree.GetFromId(5)->GetNextUnignoredSibling());
}
TEST(AXTreeTest, GetUnignoredSiblingsIgnoredChildSkipped) {
// Ignored children of ignored parents are skipped over.
//
// (i) => node is ignored.
//
// 1
// ├── 2(i)
// │ ├── 4
// │ └── 5(i)
// └── 3
AXTreeUpdate tree_update;
tree_update.root_id = 1;
tree_update.nodes.resize(5);
tree_update.nodes[0].id = 1;
tree_update.nodes[0].child_ids = {2, 3};
tree_update.nodes[1].id = 2;
tree_update.nodes[1].AddState(ax::mojom::State::kIgnored);
tree_update.nodes[1].child_ids = {4, 5};
tree_update.nodes[2].id = 3;
tree_update.nodes[3].id = 4;
tree_update.nodes[4].id = 5;
tree_update.nodes[4].AddState(ax::mojom::State::kIgnored);
AXTree tree(tree_update);
// Root node has no siblings.
EXPECT_EQ(nullptr, tree.GetFromId(1)->GetPreviousUnignoredSibling());
EXPECT_EQ(nullptr, tree.GetFromId(1)->GetNextUnignoredSibling());
// Node 2's view of siblings:
// literal tree: null <-- [2(i)] --> 3
// unignored tree: null <-- [2(i)] --> 3
EXPECT_EQ(nullptr, tree.GetFromId(2)->GetPreviousSibling());
EXPECT_EQ(nullptr, tree.GetFromId(2)->GetPreviousUnignoredSibling());
EXPECT_EQ(tree.GetFromId(3), tree.GetFromId(2)->GetNextSibling());
EXPECT_EQ(tree.GetFromId(3), tree.GetFromId(2)->GetNextUnignoredSibling());
// Node 3's view of siblings:
// literal tree: 2(i) <-- [3] --> null
// unignored tree: 4 <-- [3] --> null
EXPECT_EQ(tree.GetFromId(2), tree.GetFromId(3)->GetPreviousSibling());
EXPECT_EQ(tree.GetFromId(4),
tree.GetFromId(3)->GetPreviousUnignoredSibling());
EXPECT_EQ(nullptr, tree.GetFromId(3)->GetNextSibling());
EXPECT_EQ(nullptr, tree.GetFromId(3)->GetNextUnignoredSibling());
// Node 4's view of siblings:
// literal tree: null <-- [4] --> 5(i)
// unignored tree: null <-- [4] --> 3
EXPECT_EQ(nullptr, tree.GetFromId(4)->GetPreviousSibling());
EXPECT_EQ(nullptr, tree.GetFromId(4)->GetPreviousUnignoredSibling());
EXPECT_EQ(tree.GetFromId(5), tree.GetFromId(4)->GetNextSibling());
EXPECT_EQ(tree.GetFromId(3), tree.GetFromId(4)->GetNextUnignoredSibling());
// Node 5's view of siblings:
// literal tree: 4 <-- [5(i)] --> null
// unignored tree: 4 <-- [5(i)] --> 3
EXPECT_EQ(tree.GetFromId(4), tree.GetFromId(5)->GetPreviousSibling());
EXPECT_EQ(tree.GetFromId(4),
tree.GetFromId(5)->GetPreviousUnignoredSibling());
EXPECT_EQ(nullptr, tree.GetFromId(5)->GetNextSibling());
EXPECT_EQ(tree.GetFromId(3), tree.GetFromId(5)->GetNextUnignoredSibling());
}
TEST(AXTreeTest, GetUnignoredSiblingIgnoredParentIrrelevant) {
// An ignored parent is not relevant unless the search would need to continue
// up through it.
//
// (i) => node is ignored.
//
// 1(i)
// ├── 2
// └── 3
AXTreeUpdate tree_update;
tree_update.root_id = 1;
tree_update.nodes.resize(3);
tree_update.nodes[0].id = 1;
tree_update.nodes[0].AddState(ax::mojom::State::kIgnored);
tree_update.nodes[0].child_ids = {2, 3};
tree_update.nodes[1].id = 2;
tree_update.nodes[2].id = 3;
AXTree tree(tree_update);
// Node 2 and 3 are each other's unignored siblings, the parent's ignored
// status is not relevant for this search.
EXPECT_EQ(tree.GetFromId(3), tree.GetFromId(2)->GetNextUnignoredSibling());
EXPECT_EQ(tree.GetFromId(2),
tree.GetFromId(3)->GetPreviousUnignoredSibling());
}
TEST(AXTreeTest, GetUnignoredSiblingsAllIgnored) {
// Test termination when all nodes, including the root node, are ignored.
//
// (i) => node is ignored.
//
// 1(i)
// └── 2(i)
AXTreeUpdate tree_update;
tree_update.root_id = 1;
tree_update.nodes.resize(2);
tree_update.nodes[0].id = 1;
tree_update.nodes[0].AddState(ax::mojom::State::kIgnored);
tree_update.nodes[0].child_ids = {2};
tree_update.nodes[1].id = 2;
tree_update.nodes[1].AddState(ax::mojom::State::kIgnored);
AXTree tree(tree_update);
EXPECT_EQ(nullptr, tree.GetFromId(1)->GetPreviousUnignoredSibling());
EXPECT_EQ(nullptr, tree.GetFromId(1)->GetNextUnignoredSibling());
EXPECT_EQ(nullptr, tree.GetFromId(2)->GetPreviousUnignoredSibling());
EXPECT_EQ(nullptr, tree.GetFromId(2)->GetNextUnignoredSibling());
}
TEST(AXTreeTest, GetUnignoredSiblingsNestedIgnored) {
// Test promotion of children through multiple layers of ignored parents.
// (i) => node is ignored.
//
// 1
// ├── 2
// ├── 3(i)
// │ └── 5(i)
// │ └── 6
// └── 4
AXTreeUpdate tree_update;
tree_update.root_id = 1;
tree_update.nodes.resize(6);
tree_update.nodes[0].id = 1;
tree_update.nodes[0].child_ids = {2, 3, 4};
tree_update.nodes[1].id = 2;
tree_update.nodes[2].id = 3;
tree_update.nodes[2].AddState(ax::mojom::State::kIgnored);
tree_update.nodes[2].child_ids = {5};
tree_update.nodes[3].id = 4;
tree_update.nodes[4].id = 5;
tree_update.nodes[4].AddState(ax::mojom::State::kIgnored);
tree_update.nodes[4].child_ids = {6};
tree_update.nodes[5].id = 6;
AXTree tree(tree_update);
EXPECT_EQ(nullptr, tree.GetFromId(1)->GetPreviousUnignoredSibling());
const AXNode* node2 = tree.GetFromId(2);
const AXNode* node3 = tree.GetFromId(3);
const AXNode* node4 = tree.GetFromId(4);
const AXNode* node5 = tree.GetFromId(5);
const AXNode* node6 = tree.GetFromId(6);
ASSERT_NE(nullptr, node2);
ASSERT_NE(nullptr, node3);
ASSERT_NE(nullptr, node4);
ASSERT_NE(nullptr, node5);
ASSERT_NE(nullptr, node6);
// Node 2's view of siblings:
// literal tree: null <-- [2] --> 3
// unignored tree: null <-- [2] --> 6
EXPECT_EQ(nullptr, node2->GetPreviousSibling());
EXPECT_EQ(nullptr, node2->GetPreviousUnignoredSibling());
EXPECT_EQ(node3, node2->GetNextSibling());
EXPECT_EQ(node6, node2->GetNextUnignoredSibling());
// Node 3's view of siblings:
// literal tree: 2 <-- [3(i)] --> 4
// unignored tree: 2 <-- [3(i)] --> 4
EXPECT_EQ(node2, node3->GetPreviousSibling());
EXPECT_EQ(node2, node3->GetPreviousUnignoredSibling());
EXPECT_EQ(node4, node3->GetNextSibling());
EXPECT_EQ(node4, node3->GetNextUnignoredSibling());
// Node 4's view of siblings:
// literal tree: 3 <-- [4] --> null
// unignored tree: 6 <-- [4] --> null
EXPECT_EQ(node3, node4->GetPreviousSibling());
EXPECT_EQ(node6, node4->GetPreviousUnignoredSibling());
EXPECT_EQ(nullptr, node4->GetNextSibling());
EXPECT_EQ(nullptr, node4->GetNextUnignoredSibling());
// Node 5's view of siblings:
// literal tree: null <-- [5(i)] --> null
// unignored tree: 2 <-- [5(i)] --> 4
EXPECT_EQ(nullptr, node5->GetPreviousSibling());
EXPECT_EQ(node2, node5->GetPreviousUnignoredSibling());
EXPECT_EQ(nullptr, node5->GetNextSibling());
EXPECT_EQ(node4, node5->GetNextUnignoredSibling());
// Node 6's view of siblings:
// literal tree: null <-- [6] --> null
// unignored tree: 2 <-- [6] --> 4
EXPECT_EQ(nullptr, node6->GetPreviousSibling());
EXPECT_EQ(node2, node6->GetPreviousUnignoredSibling());
EXPECT_EQ(nullptr, node6->GetNextSibling());
EXPECT_EQ(node4, node6->GetNextUnignoredSibling());
}
TEST(AXTreeTest, UnignoredSelection) {
AXTreeUpdate tree_update;
// (i) => node is ignored
......@@ -2566,6 +2869,50 @@ TEST(AXTreeTest, UnignoredSelection) {
TEST_SELECTION(tree_update, test_ax_tree_manager.GetTree(), input, expected);
}
TEST(AXTreeTest, GetChildrenOrSiblings) {
// 1
// ├── 2
// │ └── 5
// ├── 3
// └── 4
AXTreeUpdate tree_update;
tree_update.root_id = 1;
tree_update.nodes.resize(5);
tree_update.nodes[0].id = 1;
tree_update.nodes[0].child_ids = {2, 3, 4};
tree_update.nodes[1].id = 2;
tree_update.nodes[1].child_ids = {5};
tree_update.nodes[2].id = 3;
tree_update.nodes[3].id = 4;
tree_update.nodes[4].id = 5;
AXTree tree(tree_update);
EXPECT_EQ(tree.GetFromId(2), tree.GetFromId(1)->GetFirstChild());
EXPECT_EQ(tree.GetFromId(5), tree.GetFromId(2)->GetFirstChild());
EXPECT_EQ(nullptr, tree.GetFromId(3)->GetFirstChild());
EXPECT_EQ(nullptr, tree.GetFromId(4)->GetFirstChild());
EXPECT_EQ(nullptr, tree.GetFromId(5)->GetFirstChild());
EXPECT_EQ(tree.GetFromId(4), tree.GetFromId(1)->GetLastChild());
EXPECT_EQ(tree.GetFromId(5), tree.GetFromId(2)->GetLastChild());
EXPECT_EQ(nullptr, tree.GetFromId(3)->GetLastChild());
EXPECT_EQ(nullptr, tree.GetFromId(4)->GetLastChild());
EXPECT_EQ(nullptr, tree.GetFromId(5)->GetLastChild());
EXPECT_EQ(nullptr, tree.GetFromId(1)->GetPreviousSibling());
EXPECT_EQ(nullptr, tree.GetFromId(2)->GetPreviousSibling());
EXPECT_EQ(tree.GetFromId(2), tree.GetFromId(3)->GetPreviousSibling());
EXPECT_EQ(tree.GetFromId(3), tree.GetFromId(4)->GetPreviousSibling());
EXPECT_EQ(nullptr, tree.GetFromId(5)->GetPreviousSibling());
EXPECT_EQ(nullptr, tree.GetFromId(1)->GetNextSibling());
EXPECT_EQ(tree.GetFromId(3), tree.GetFromId(2)->GetNextSibling());
EXPECT_EQ(tree.GetFromId(4), tree.GetFromId(3)->GetNextSibling());
EXPECT_EQ(nullptr, tree.GetFromId(4)->GetNextSibling());
EXPECT_EQ(nullptr, tree.GetFromId(5)->GetNextSibling());
}
TEST(AXTreeTest, ChildTreeIds) {
ui::AXTreeID tree_id_1 = ui::AXTreeID::CreateNewAXTreeID();
ui::AXTreeID tree_id_2 = ui::AXTreeID::CreateNewAXTreeID();
......
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