blob: a16787b737016e03fcb9d41fd91ef2933929ab0a [file] [log] [blame]
// Copyright 2019 The Fuchsia 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 <fuchsia/accessibility/cpp/fidl.h>
#include <fuchsia/sys/cpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/async/cpp/executor.h>
#include <lib/fdio/fd.h>
#include <lib/gtest/real_loop_fixture.h>
#include <lib/inspect/cpp/inspect.h>
#include <lib/inspect/testing/cpp/inspect.h>
#include <lib/sys/cpp/testing/component_context_provider.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/ui/scenic/cpp/commands.h>
#include <lib/vfs/cpp/pseudo_dir.h>
#include <lib/zx/event.h>
#include <algorithm>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "src/lib/files/file.h"
#include "src/ui/a11y/bin/a11y_manager/tests/util/util.h"
#include "src/ui/a11y/lib/semantics/semantic_tree.h"
#include "src/ui/a11y/lib/semantics/tests/semantic_tree_parser.h"
#include "src/ui/a11y/lib/util/util.h"
namespace accessibility_test {
namespace {
using ::a11y::SemanticTree;
using fuchsia::accessibility::semantics::Node;
using fuchsia::accessibility::semantics::Role;
using ::inspect::Inspector;
using ::testing::HasSubstr;
// Valid tree paths.
const std::string kSemanticTreeSingleNodePath = "/pkg/data/semantic_tree_single_node.json";
const std::string kSemanticTreeOddNodesPath = "/pkg/data/semantic_tree_odd_nodes.json";
const std::string kSemanticTreeEvenNodesPath = "/pkg/data/semantic_tree_even_nodes.json";
// Invalid tree paths.
const std::string kSemanticTreeWithCyclePath = "/pkg/data/cyclic_semantic_tree.json";
const std::string kSemanticTreeWithMissingChildrenPath =
"/pkg/data/semantic_tree_not_parseable.json";
constexpr char kInspectNodeName[] = "test_inspect_node";
class SemanticTreeTest : public gtest::RealLoopFixture {
public:
SemanticTreeTest() : executor_(dispatcher()) {}
protected:
void SetUp() override {
RealLoopFixture::SetUp();
inspector_ = std::make_unique<inspect::Inspector>();
tree_ = std::make_unique<SemanticTree>(inspector_->GetRoot().CreateChild(kInspectNodeName));
tree_->set_action_handler([this](uint32_t node_id,
fuchsia::accessibility::semantics::Action action,
fuchsia::accessibility::semantics::SemanticListener::
OnAccessibilityActionRequestedCallback callback) {
this->action_handler_called_ = true;
});
tree_->set_hit_testing_handler(
[this](fuchsia::math::PointF local_point,
fuchsia::accessibility::semantics::SemanticListener::HitTestCallback callback) {
this->hit_testing_called_ = true;
});
}
// Helper function to ensure that a promise completes.
void RunPromiseToCompletion(fit::promise<> promise) {
bool done = false;
executor_.schedule_task(std::move(promise).and_then([&]() { done = true; }));
RunLoopUntil([&] { return done; });
}
// Checks if the tree contains all nodes in |ids|.
void TreeContainsNodes(const std::vector<uint32_t>& ids) {
for (const auto id : ids) {
auto node = tree_->GetNode(id);
EXPECT_TRUE(node);
EXPECT_EQ(node->node_id(), id);
}
}
SemanticTree::TreeUpdates BuildUpdatesFromFile(const std::string& file_path) {
SemanticTree::TreeUpdates updates;
std::vector<Node> nodes;
EXPECT_TRUE(semantic_tree_parser_.ParseSemanticTree(file_path, &nodes));
for (auto& node : nodes) {
updates.emplace_back(std::move(node));
}
return updates;
}
SemanticTreeParser semantic_tree_parser_;
// Whether the action handler was called.
bool action_handler_called_ = false;
// Whether the hit testing handler was called.
bool hit_testing_called_ = false;
// Required to verify inspect metrics.
std::unique_ptr<inspect::Inspector> inspector_;
// Our test subject.
std::unique_ptr<SemanticTree> tree_;
// Required to retrieve inspect metrics.
async::Executor executor_;
};
TEST_F(SemanticTreeTest, GetNodesById) {
SemanticTree::TreeUpdates updates = BuildUpdatesFromFile(kSemanticTreeSingleNodePath);
EXPECT_TRUE(tree_->Update(std::move(updates)));
// Attempt to retrieve node with id not present in tree.
auto invalid_node = tree_->GetNode(1u);
auto root = tree_->GetNode(SemanticTree::kRootNodeId);
EXPECT_EQ(invalid_node, nullptr);
EXPECT_EQ(root->node_id(), SemanticTree::kRootNodeId);
}
TEST_F(SemanticTreeTest, ClearsTheTree) {
SemanticTree::TreeUpdates updates;
updates.emplace_back(CreateTestNode(SemanticTree::kRootNodeId, "node0", {1, 2}));
updates.emplace_back(CreateTestNode(1u, "node1"));
updates.emplace_back(CreateTestNode(2u, "node2"));
EXPECT_TRUE(tree_->Update(std::move(updates)));
EXPECT_EQ(tree_->Size(), 3u);
// Set event callback to verify that callback was called with the correct
// event type.
bool semantics_event_callback_called = false;
tree_->set_semantics_event_callback(
[&semantics_event_callback_called](a11y::SemanticsEventInfo event_info) {
semantics_event_callback_called = true;
EXPECT_EQ(event_info.event_type, a11y::SemanticsEventType::kSemanticTreeUpdated);
});
tree_->Clear();
EXPECT_EQ(tree_->Size(), 0u);
EXPECT_TRUE(semantics_event_callback_called);
}
TEST_F(SemanticTreeTest, SemanticsEventCallbackInvokedOnSuccessfulUpdate) {
// Set event callback to verify that callback was called with the correct
// event type.
bool semantics_event_callback_called = false;
tree_->set_semantics_event_callback(
[&semantics_event_callback_called](a11y::SemanticsEventInfo event_info) {
semantics_event_callback_called = true;
EXPECT_EQ(event_info.event_type, a11y::SemanticsEventType::kSemanticTreeUpdated);
});
SemanticTree::TreeUpdates updates = BuildUpdatesFromFile(kSemanticTreeOddNodesPath);
EXPECT_TRUE(tree_->Update(std::move(updates)));
EXPECT_TRUE(semantics_event_callback_called);
}
TEST_F(SemanticTreeTest, ReceivesTreeInOneSingleUpdate) {
SemanticTree::TreeUpdates updates = BuildUpdatesFromFile(kSemanticTreeOddNodesPath);
std::vector<uint32_t> added_ids;
for (const auto& update : updates) {
added_ids.push_back(update.node().node_id());
}
EXPECT_TRUE(tree_->Update(std::move(updates)));
TreeContainsNodes(added_ids);
}
TEST_F(SemanticTreeTest, BuildsTreeFromTheLeaves) {
SemanticTree::TreeUpdates updates = BuildUpdatesFromFile(kSemanticTreeOddNodesPath);
// Updates is in ascending order. Sort it in descending order to send the
// updates from the leaves.
std::sort(updates.begin(), updates.end(),
[](const auto& a, const auto& b) { return a.node().node_id() > b.node().node_id(); });
std::vector<uint32_t> added_ids;
for (const auto& update : updates) {
added_ids.push_back(update.node().node_id());
}
EXPECT_TRUE(tree_->Update(std::move(updates)));
TreeContainsNodes(added_ids);
}
TEST_F(SemanticTreeTest, InvalidTreeWithoutParent) {
SemanticTree::TreeUpdates updates = BuildUpdatesFromFile(kSemanticTreeOddNodesPath);
// Remove the root (first node).
updates.erase(updates.begin());
EXPECT_FALSE(tree_->Update(std::move(updates)));
}
TEST_F(SemanticTreeTest, InvalidTreeWithCycle) {
SemanticTree::TreeUpdates updates = BuildUpdatesFromFile(kSemanticTreeWithCyclePath);
EXPECT_FALSE(tree_->Update(std::move(updates)));
EXPECT_EQ(tree_->Size(), 0u);
}
TEST_F(SemanticTreeTest, DeletingNodesByUpdatingTheParent) {
SemanticTree::TreeUpdates updates = BuildUpdatesFromFile(kSemanticTreeOddNodesPath);
std::vector<uint32_t> added_ids;
for (const auto& update : updates) {
added_ids.push_back(update.node().node_id());
}
EXPECT_TRUE(tree_->Update(std::move(updates)));
{
auto root = tree_->GetNode(SemanticTree::kRootNodeId);
EXPECT_EQ(root->attributes().label(), "Node-0");
EXPECT_EQ(root->child_ids().size(), 2u);
}
// Update the root to point to nobody else.
auto new_root = CreateTestNode(SemanticTree::kRootNodeId, "node1");
new_root.set_child_ids(std::vector<uint32_t>()); // Points to no children.
new_root.mutable_attributes()->set_label("new node");
EXPECT_TRUE(new_root.has_child_ids());
SemanticTree::TreeUpdates new_updates;
new_updates.emplace_back(std::move(new_root));
EXPECT_TRUE(tree_->Update(std::move(new_updates)));
{
auto root = tree_->GetNode(0);
EXPECT_TRUE(root->child_ids().empty());
EXPECT_EQ(root->attributes().label(), "new node");
}
EXPECT_EQ(tree_->Size(), 1u);
for (const auto id : added_ids) {
auto node = tree_->GetNode(id);
if (id == SemanticTree::kRootNodeId) {
EXPECT_TRUE(node);
EXPECT_EQ(node->node_id(), id);
} else {
EXPECT_FALSE(node);
}
}
}
TEST_F(SemanticTreeTest, ExplicitlyDeletingNodes) {
SemanticTree::TreeUpdates updates = BuildUpdatesFromFile(kSemanticTreeOddNodesPath);
std::vector<uint32_t> added_ids;
for (const auto& update : updates) {
added_ids.push_back(update.node().node_id());
}
EXPECT_TRUE(tree_->Update(std::move(updates)));
SemanticTree::TreeUpdates delete_updates;
delete_updates.emplace_back(5);
delete_updates.emplace_back(6);
// Update the parent.
auto updated_parent = CreateTestNode(2, "updated parent");
*updated_parent.mutable_child_ids() = std::vector<uint32_t>();
delete_updates.push_back(std::move(updated_parent));
// Remove 5 and 6 from |added_ids|.
auto it_5 = std::find(added_ids.begin(), added_ids.end(), 5);
EXPECT_NE(it_5, added_ids.end());
added_ids.erase(it_5);
auto it_6 = std::find(added_ids.begin(), added_ids.end(), 6);
EXPECT_NE(it_6, added_ids.end());
added_ids.erase(it_6);
EXPECT_TRUE(tree_->Update(std::move(delete_updates)));
EXPECT_EQ(tree_->Size(), 5u);
TreeContainsNodes(added_ids);
}
TEST_F(SemanticTreeTest, DeletingRootNodeClearsTheTree) {
SemanticTree::TreeUpdates updates = BuildUpdatesFromFile(kSemanticTreeOddNodesPath);
EXPECT_TRUE(tree_->Update(std::move(updates)));
SemanticTree::TreeUpdates delete_updates;
delete_updates.emplace_back(SemanticTree::kRootNodeId);
EXPECT_TRUE(tree_->Update(std::move(delete_updates)));
EXPECT_EQ(tree_->Size(), 0u);
}
TEST_F(SemanticTreeTest, ReplaceNodeWithADeletion) {
SemanticTree::TreeUpdates updates = BuildUpdatesFromFile(kSemanticTreeOddNodesPath);
EXPECT_TRUE(tree_->Update(std::move(updates)));
SemanticTree::TreeUpdates delete_updates;
delete_updates.emplace_back(2);
delete_updates.emplace_back(CreateTestNode(2, "new node 2", {5, 6}));
EXPECT_TRUE(tree_->Update(std::move(delete_updates)));
EXPECT_EQ(tree_->Size(), 7u);
auto node = tree_->GetNode(2);
EXPECT_TRUE(node);
EXPECT_THAT(node->attributes().label(), "new node 2");
EXPECT_THAT(node->child_ids(), testing::ElementsAre(5, 6));
}
TEST_F(SemanticTreeTest, SemanticTreeWithMissingChildren) {
SemanticTree::TreeUpdates updates;
updates.emplace_back(CreateTestNode(SemanticTree::kRootNodeId, "node0", {1, 2}));
updates.emplace_back(CreateTestNode(1u, "node1"));
updates.emplace_back(CreateTestNode(2u, "node2", {3}));
EXPECT_FALSE(tree_->Update(std::move(updates)));
EXPECT_EQ(tree_->Size(), 0u);
}
TEST_F(SemanticTreeTest, PartialUpdateCopiesNewInfo) {
{
SemanticTree::TreeUpdates updates;
updates.emplace_back(CreateTestNode(SemanticTree::kRootNodeId, "node0", {1, 2}));
updates.emplace_back(CreateTestNode(1u, "node1"));
updates.emplace_back(CreateTestNode(2u, "node2"));
EXPECT_TRUE(tree_->Update(std::move(updates)));
}
EXPECT_EQ(tree_->Size(), 3u);
SemanticTree::TreeUpdates updates;
// Partial update of the root node with a new label.
// Please note that there are three partial updates on the root node, and the
// partial update must always be applied on top of the existing one.
auto first_root_update = CreateTestNode(SemanticTree::kRootNodeId, "root", {1, 2, 10});
first_root_update.set_role(fuchsia::accessibility::semantics::Role::UNKNOWN);
first_root_update.mutable_states()->set_selected(true);
updates.emplace_back(std::move(first_root_update));
auto second_root_update = CreateTestNode(SemanticTree::kRootNodeId, "root");
second_root_update.mutable_states()->set_selected(false);
second_root_update.set_actions({fuchsia::accessibility::semantics::Action::DEFAULT,
fuchsia::accessibility::semantics::Action::SHOW_ON_SCREEN});
updates.emplace_back(std::move(second_root_update));
auto third_root_update = CreateTestNode(SemanticTree::kRootNodeId, "updated label");
fuchsia::ui::gfx::BoundingBox box;
box.max.z = 10.f;
third_root_update.set_location(box);
third_root_update.set_transform(
scenic::NewMatrix4Value({2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1}).value);
third_root_update.set_container_id(2u);
updates.emplace_back(std::move(third_root_update));
updates.emplace_back(CreateTestNode(10, "node 10"));
EXPECT_TRUE(tree_->Update(std::move(updates)));
EXPECT_EQ(tree_->Size(), 4u);
// Verify that the contents of the root node represent a merge of the three
// updates.
auto root = tree_->GetNode(SemanticTree::kRootNodeId);
EXPECT_EQ(root->attributes().label(), "updated label");
EXPECT_EQ(root->actions().size(), 2u);
EXPECT_EQ(root->location().max.z, 10.f);
EXPECT_EQ(root->transform().matrix[0], 2);
EXPECT_EQ(root->container_id(), 2u);
EXPECT_THAT(root->child_ids(), testing::ElementsAre(1, 2, 10));
EXPECT_EQ(root->role(), fuchsia::accessibility::semantics::Role::UNKNOWN);
EXPECT_FALSE(root->states().selected());
}
TEST_F(SemanticTreeTest, ReparentsNodes) {
// A common use case of semantic trees is to reparent a node. Within an
// update, reparenting would look like as a removal of a child node ID of one
// node and the addition of that same child node ID to another node (new
// parent).
SemanticTree::TreeUpdates updates = BuildUpdatesFromFile(kSemanticTreeOddNodesPath);
EXPECT_TRUE(tree_->Update(std::move(updates)));
SemanticTree::TreeUpdates reparenting_updates;
reparenting_updates.push_back(
CreateTestNode(SemanticTree::kRootNodeId, "root", {1})); // 2 removed.
reparenting_updates.push_back(
CreateTestNode(1, "new parent", {3, 4, 2})); // 2 will have 1 as new parent.
EXPECT_TRUE(tree_->Update(std::move(reparenting_updates)));
EXPECT_EQ(tree_->Size(), 7u);
auto root = tree_->GetNode(SemanticTree::kRootNodeId);
EXPECT_TRUE(root);
EXPECT_THAT(root->child_ids(), testing::ElementsAre(1));
auto new_parent = tree_->GetNode(1);
EXPECT_TRUE(new_parent);
EXPECT_THAT(new_parent->child_ids(), testing::ElementsAre(3, 4, 2));
}
TEST_F(SemanticTreeTest, GetParentNodeTest) {
SemanticTree::TreeUpdates updates = BuildUpdatesFromFile(kSemanticTreeOddNodesPath);
EXPECT_TRUE(tree_->Update(std::move(updates)));
auto parent = tree_->GetParentNode(1);
auto missing_parent = tree_->GetParentNode(SemanticTree::kRootNodeId);
EXPECT_TRUE(parent);
EXPECT_FALSE(missing_parent);
EXPECT_EQ(parent->node_id(), SemanticTree::kRootNodeId);
}
TEST_F(SemanticTreeTest, PerformAccessibilityActionRequested) {
tree_->PerformAccessibilityAction(1, fuchsia::accessibility::semantics::Action::DEFAULT,
[](auto...) {});
EXPECT_TRUE(action_handler_called_);
}
TEST_F(SemanticTreeTest, PerformHitTestingRequested) {
tree_->PerformHitTesting({1, 1}, [](auto...) {});
EXPECT_TRUE(hit_testing_called_);
}
TEST_F(SemanticTreeTest, NextNodeExists) {
// Tests the case where semantic tree is not balanced, and GetNextNode is called on a node which
// is the leaf node, without any sibling. This will fail in case of a level order traversal.
SemanticTree::TreeUpdates updates = BuildUpdatesFromFile(kSemanticTreeEvenNodesPath);
EXPECT_TRUE(tree_->Update(std::move(updates)));
auto next_node = tree_->GetNextNode(
7u, [](const fuchsia::accessibility::semantics::Node* node) { return true; });
EXPECT_NE(next_node, nullptr);
EXPECT_EQ(next_node->node_id(), 4u);
}
TEST_F(SemanticTreeTest, GetNextNodeFilterReturnsFalse) {
// Test case where intermediate nodes which are not describable are skipped. This will fail in
// case of level order traversal.
SemanticTree::TreeUpdates updates = BuildUpdatesFromFile(kSemanticTreeOddNodesPath);
EXPECT_TRUE(tree_->Update(std::move(updates)));
auto next_node = tree_->GetNextNode(
2u, [](const fuchsia::accessibility::semantics::Node* node) { return false; });
EXPECT_EQ(next_node, nullptr);
}
TEST_F(SemanticTreeTest, NoNextNode) {
// Tests case where next node doesn't exist.This will fail in case of level order traversal.
SemanticTree::TreeUpdates updates = BuildUpdatesFromFile(kSemanticTreeEvenNodesPath);
EXPECT_TRUE(tree_->Update(std::move(updates)));
auto next_node = tree_->GetNextNode(
6u, [](const fuchsia::accessibility::semantics::Node* node) { return true; });
EXPECT_EQ(next_node, nullptr);
}
TEST_F(SemanticTreeTest, GetNextNodeForNonexistentId) {
// Tests case where input node doesn't exist.
SemanticTree::TreeUpdates updates = BuildUpdatesFromFile(kSemanticTreeOddNodesPath);
EXPECT_TRUE(tree_->Update(std::move(updates)));
auto next_node = tree_->GetNextNode(
10u, [](const fuchsia::accessibility::semantics::Node* node) { return true; });
EXPECT_EQ(next_node, nullptr);
}
TEST_F(SemanticTreeTest, PreviousNodeExists) {
// Tests the case where semantic tree is not balanced, and GetPreviousNode is called on a non leaf
// which should return a leaf node. This will fail in case of a level order traversal.
SemanticTree::TreeUpdates updates = BuildUpdatesFromFile(kSemanticTreeEvenNodesPath);
EXPECT_TRUE(tree_->Update(std::move(updates)));
auto next_node = tree_->GetPreviousNode(
4u, [](const fuchsia::accessibility::semantics::Node* node) { return true; });
EXPECT_NE(next_node, nullptr);
EXPECT_EQ(next_node->node_id(), 7u);
}
TEST_F(SemanticTreeTest, GetPreviousNodeFilterReturnsFalse) {
// Test case where intermediate nodes which are not describable are skipped.
SemanticTree::TreeUpdates updates = BuildUpdatesFromFile(kSemanticTreeOddNodesPath);
EXPECT_TRUE(tree_->Update(std::move(updates)));
updates.clear();
auto previous_node = tree_->GetPreviousNode(
6u, [](const fuchsia::accessibility::semantics::Node* node) { return false; });
EXPECT_EQ(previous_node, nullptr);
}
TEST_F(SemanticTreeTest, NoPreviousNode) {
// Tests case where previous node doesn't exist.
SemanticTree::TreeUpdates updates = BuildUpdatesFromFile(kSemanticTreeOddNodesPath);
EXPECT_TRUE(tree_->Update(std::move(updates)));
auto next_node = tree_->GetPreviousNode(
0u, [](const fuchsia::accessibility::semantics::Node* node) { return true; });
EXPECT_EQ(next_node, nullptr);
}
TEST_F(SemanticTreeTest, GetPreviousNodeForNonexistentId) {
// Tests case where input node doesn't exist.
SemanticTree::TreeUpdates updates = BuildUpdatesFromFile(kSemanticTreeOddNodesPath);
EXPECT_TRUE(tree_->Update(std::move(updates)));
auto next_node = tree_->GetPreviousNode(
10u, [](const fuchsia::accessibility::semantics::Node* node) { return true; });
EXPECT_EQ(next_node, nullptr);
}
TEST_F(SemanticTreeTest, InspectOutput) {
SemanticTree::TreeUpdates updates = BuildUpdatesFromFile(kSemanticTreeOddNodesPath);
EXPECT_TRUE(tree_->Update(std::move(updates)));
fit::result<inspect::Hierarchy> hierarchy;
ASSERT_FALSE(hierarchy.is_ok());
RunPromiseToCompletion(
inspect::ReadFromInspector(*inspector_).then([&](fit::result<inspect::Hierarchy>& result) {
hierarchy = std::move(result);
}));
ASSERT_TRUE(hierarchy.is_ok());
using namespace inspect::testing;
using testing::UnorderedElementsAre;
auto node3 = AllOf(
NodeMatches(AllOf(NameMatches("node_3"), PropertyList(UnorderedElementsAre(
UintIs("id", 3), StringIs("label", "Node-3"),
UintIs("label_length", 6))))),
ChildrenMatch(UnorderedElementsAre()));
auto node4 = AllOf(
NodeMatches(AllOf(NameMatches("node_4"), PropertyList(UnorderedElementsAre(
UintIs("id", 4), StringIs("label", "Node-4"),
UintIs("label_length", 6))))),
ChildrenMatch(UnorderedElementsAre()));
auto node5 = AllOf(
NodeMatches(AllOf(NameMatches("node_5"), PropertyList(UnorderedElementsAre(
UintIs("id", 5), StringIs("label", "Node-5"),
UintIs("label_length", 6))))),
ChildrenMatch(UnorderedElementsAre()));
auto node6 = AllOf(
NodeMatches(AllOf(NameMatches("node_6"), PropertyList(UnorderedElementsAre(
UintIs("id", 6), StringIs("label", "Node-6"),
UintIs("label_length", 6))))),
ChildrenMatch(UnorderedElementsAre()));
auto node1 = AllOf(
NodeMatches(AllOf(NameMatches("node_1"), PropertyList(UnorderedElementsAre(
UintIs("id", 1), StringIs("label", "Node-1"),
UintIs("label_length", 6))))),
ChildrenMatch(UnorderedElementsAre(node3, node4)));
auto node2 = AllOf(
NodeMatches(AllOf(NameMatches("node_2"), PropertyList(UnorderedElementsAre(
UintIs("id", 2), StringIs("label", "Node-2"),
UintIs("label_length", 6))))),
ChildrenMatch(UnorderedElementsAre(node5, node6)));
auto root_node =
AllOf(NodeMatches(AllOf(
NameMatches("semantic_tree_root"),
PropertyList(UnorderedElementsAre(UintIs("id", 0), StringIs("label", "Node-0"),
UintIs("label_length", 6))))),
ChildrenMatch(UnorderedElementsAre(node1, node2)));
auto tree_inspect_hierarchy = hierarchy.value().GetByPath({kInspectNodeName});
ASSERT_NE(tree_inspect_hierarchy, nullptr);
EXPECT_THAT(
*tree_inspect_hierarchy,
AllOf(NodeMatches(PropertyList(UnorderedElementsAre(UintIs("tree_update_count", 7u)))),
ChildrenMatch(UnorderedElementsAre(root_node))));
}
} // namespace
} // namespace accessibility_test