| // 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 "topaz/runtime/flutter_runner/accessibility_bridge.h" |
| |
| #include <gtest/gtest.h> |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/fidl/cpp/binding_set.h> |
| #include <lib/fidl/cpp/interface_request.h> |
| #include <lib/gtest/real_loop_fixture.h> |
| #include <lib/sys/cpp/testing/service_directory_provider.h> |
| #include <zircon/types.h> |
| |
| #include <memory> |
| |
| #include "flutter/lib/ui/semantics/semantics_node.h" |
| #include "topaz/runtime/flutter_runner/flutter_runner_fakes.h" |
| |
| namespace flutter_runner_test { |
| class AccessibilityBridgeTest : public gtest::RealLoopFixture { |
| public: |
| AccessibilityBridgeTest() : services_provider_(dispatcher()) { |
| services_provider_.AddService(semantics_manager_.GetHandler(dispatcher()), |
| SemanticsManager::Name_); |
| } |
| |
| protected: |
| void SetUp() override { |
| zx::eventpair a, b; |
| zx::eventpair::create(/* flags */ 0u, &a, &b); |
| auto view_ref = fuchsia::ui::views::ViewRef({ |
| .reference = std::move(a), |
| }); |
| accessibility_bridge_ = |
| std::make_unique<flutter_runner::AccessibilityBridge>( |
| services_provider_.service_directory(), std::move(view_ref)); |
| RunLoopUntilIdle(); |
| } |
| |
| void TearDown() override { semantics_manager_.ResetTree(); } |
| |
| sys::testing::ServiceDirectoryProvider services_provider_; |
| MockSemanticsManager semantics_manager_; |
| std::unique_ptr<flutter_runner::AccessibilityBridge> accessibility_bridge_; |
| }; |
| |
| TEST_F(AccessibilityBridgeTest, RegistersViewRef) { |
| EXPECT_TRUE(semantics_manager_.RegisterViewCalled()); |
| } |
| |
| TEST_F(AccessibilityBridgeTest, DeletesChildrenTransitively) { |
| // Test that when a node is deleted, so are its transitive children. |
| flutter::SemanticsNode node2; |
| node2.id = 2; |
| |
| flutter::SemanticsNode node1; |
| node1.id = 1; |
| node1.childrenInTraversalOrder = {2}; |
| |
| flutter::SemanticsNode node0; |
| node0.id = 0; |
| node0.childrenInTraversalOrder = {1}; |
| |
| accessibility_bridge_->AddSemanticsNodeUpdate({ |
| {0, node0}, |
| {1, node1}, |
| {2, node2}, |
| }); |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(0, semantics_manager_.DeleteCount()); |
| EXPECT_EQ(1, semantics_manager_.UpdateCount()); |
| EXPECT_EQ(1, semantics_manager_.CommitCount()); |
| EXPECT_EQ(3U, semantics_manager_.LastUpdatedNodes().size()); |
| EXPECT_EQ(0U, semantics_manager_.LastDeletedNodeIds().size()); |
| EXPECT_FALSE(semantics_manager_.DeleteOverflowed()); |
| EXPECT_FALSE(semantics_manager_.UpdateOverflowed()); |
| |
| // Remove the children |
| node0.childrenInTraversalOrder.clear(); |
| accessibility_bridge_->AddSemanticsNodeUpdate({ |
| {0, node0}, |
| }); |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(1, semantics_manager_.DeleteCount()); |
| EXPECT_EQ(2, semantics_manager_.UpdateCount()); |
| EXPECT_EQ(2, semantics_manager_.CommitCount()); |
| EXPECT_EQ(1U, semantics_manager_.LastUpdatedNodes().size()); |
| ASSERT_EQ(std::vector<uint32_t>({1, 2}), |
| semantics_manager_.LastDeletedNodeIds()); |
| EXPECT_FALSE(semantics_manager_.DeleteOverflowed()); |
| EXPECT_FALSE(semantics_manager_.UpdateOverflowed()); |
| } |
| |
| TEST_F(AccessibilityBridgeTest, TruncatesLargeLabel) { |
| // Test that labels which are too long are truncated. |
| flutter::SemanticsNode node0; |
| node0.id = 0; |
| |
| flutter::SemanticsNode node1; |
| node1.id = 1; |
| |
| flutter::SemanticsNode bad_node; |
| bad_node.id = 2; |
| bad_node.label = std::string( |
| fuchsia::accessibility::semantics::MAX_LABEL_SIZE + 1, '2'); |
| |
| node0.childrenInTraversalOrder = {1, 2}; |
| |
| accessibility_bridge_->AddSemanticsNodeUpdate({ |
| {0, node0}, |
| {1, node1}, |
| {2, bad_node}, |
| }); |
| RunLoopUntilIdle(); |
| |
| // Nothing to delete, but we should have broken |
| EXPECT_EQ(0, semantics_manager_.DeleteCount()); |
| EXPECT_EQ(1, semantics_manager_.UpdateCount()); |
| EXPECT_EQ(1, semantics_manager_.CommitCount()); |
| EXPECT_EQ(3U, semantics_manager_.LastUpdatedNodes().size()); |
| auto trimmed_node = |
| std::find_if(semantics_manager_.LastUpdatedNodes().begin(), |
| semantics_manager_.LastUpdatedNodes().end(), |
| [id = static_cast<uint32_t>(bad_node.id)]( |
| fuchsia::accessibility::semantics::Node const& node) { |
| return node.node_id() == id; |
| }); |
| ASSERT_NE(trimmed_node, semantics_manager_.LastUpdatedNodes().end()); |
| ASSERT_TRUE(trimmed_node->has_attributes()); |
| EXPECT_EQ( |
| trimmed_node->attributes().label(), |
| std::string(fuchsia::accessibility::semantics::MAX_LABEL_SIZE, '2')); |
| EXPECT_FALSE(semantics_manager_.DeleteOverflowed()); |
| EXPECT_FALSE(semantics_manager_.UpdateOverflowed()); |
| } |
| |
| TEST_F(AccessibilityBridgeTest, SplitsLargeUpdates) { |
| // Test that labels which are too long are truncated. |
| flutter::SemanticsNode node0; |
| node0.id = 0; |
| |
| flutter::SemanticsNode node1; |
| node1.id = 1; |
| node1.label = |
| std::string(fuchsia::accessibility::semantics::MAX_LABEL_SIZE, '1'); |
| |
| flutter::SemanticsNode node2; |
| node2.id = 2; |
| node2.label = "2"; |
| |
| flutter::SemanticsNode node3; |
| node3.id = 3; |
| node3.label = "3"; |
| |
| flutter::SemanticsNode node4; |
| node4.id = 4; |
| node4.label = |
| std::string(fuchsia::accessibility::semantics::MAX_LABEL_SIZE, '4'); |
| |
| node0.childrenInTraversalOrder = {1, 2}; |
| node1.childrenInTraversalOrder = {3, 4}; |
| |
| accessibility_bridge_->AddSemanticsNodeUpdate({ |
| {0, node0}, |
| {1, node1}, |
| {2, node2}, |
| {3, node3}, |
| {4, node4}, |
| }); |
| RunLoopUntilIdle(); |
| |
| // Nothing to delete, but we should have broken into groups (4, 3, 2), (1, 0) |
| EXPECT_EQ(0, semantics_manager_.DeleteCount()); |
| EXPECT_EQ(2, semantics_manager_.UpdateCount()); |
| EXPECT_EQ(1, semantics_manager_.CommitCount()); |
| EXPECT_EQ(2U, semantics_manager_.LastUpdatedNodes().size()); |
| EXPECT_FALSE(semantics_manager_.DeleteOverflowed()); |
| EXPECT_FALSE(semantics_manager_.UpdateOverflowed()); |
| } |
| |
| TEST_F(AccessibilityBridgeTest, HandlesCycles) { |
| // Test that cycles don't cause fatal error. |
| flutter::SemanticsNode node0; |
| node0.id = 0; |
| node0.childrenInTraversalOrder.push_back(0); |
| accessibility_bridge_->AddSemanticsNodeUpdate({ |
| {0, node0}, |
| }); |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(0, semantics_manager_.DeleteCount()); |
| EXPECT_EQ(1, semantics_manager_.UpdateCount()); |
| EXPECT_EQ(1, semantics_manager_.CommitCount()); |
| EXPECT_FALSE(semantics_manager_.DeleteOverflowed()); |
| EXPECT_FALSE(semantics_manager_.UpdateOverflowed()); |
| |
| node0.childrenInTraversalOrder = {0, 1}; |
| flutter::SemanticsNode node1; |
| node1.id = 1; |
| node1.childrenInTraversalOrder = {0}; |
| accessibility_bridge_->AddSemanticsNodeUpdate({ |
| {0, node0}, |
| {1, node1}, |
| }); |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(0, semantics_manager_.DeleteCount()); |
| EXPECT_EQ(2, semantics_manager_.UpdateCount()); |
| EXPECT_EQ(2, semantics_manager_.CommitCount()); |
| EXPECT_FALSE(semantics_manager_.DeleteOverflowed()); |
| EXPECT_FALSE(semantics_manager_.UpdateOverflowed()); |
| } |
| |
| TEST_F(AccessibilityBridgeTest, BatchesLargeMessages) { |
| // Tests that messages get batched appropriately. |
| flutter::SemanticsNode node0; |
| node0.id = 0; |
| |
| flutter::SemanticsNodeUpdates update; |
| |
| const int32_t child_nodes = fuchsia::accessibility::semantics::MAX_FAN_OUT; |
| const int32_t leaf_nodes = fuchsia::accessibility::semantics::MAX_FAN_OUT; |
| for (int32_t i = 1; i < child_nodes + 1; i++) { |
| flutter::SemanticsNode node; |
| node.id = i; |
| node0.childrenInTraversalOrder.push_back(i); |
| for (int32_t j = 0; j < leaf_nodes; j++) { |
| flutter::SemanticsNode leaf_node; |
| int id = (i * child_nodes) + ((j + 1) * leaf_nodes); |
| leaf_node.id = id; |
| leaf_node.label = "A relatively simple label"; |
| node.childrenInTraversalOrder.push_back(id); |
| update.insert(std::make_pair(id, std::move(leaf_node))); |
| } |
| update.insert(std::make_pair(i, std::move(node))); |
| } |
| |
| update.insert(std::make_pair(0, std::move(node0))); |
| accessibility_bridge_->AddSemanticsNodeUpdate(update); |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(0, semantics_manager_.DeleteCount()); |
| EXPECT_EQ(13, semantics_manager_.UpdateCount()); |
| EXPECT_EQ(1, semantics_manager_.CommitCount()); |
| EXPECT_FALSE(semantics_manager_.DeleteOverflowed()); |
| EXPECT_FALSE(semantics_manager_.UpdateOverflowed()); |
| |
| // Remove the children |
| node0.childrenInTraversalOrder.clear(); |
| accessibility_bridge_->AddSemanticsNodeUpdate({ |
| {0, node0}, |
| }); |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(1, semantics_manager_.DeleteCount()); |
| EXPECT_EQ(14, semantics_manager_.UpdateCount()); |
| EXPECT_EQ(2, semantics_manager_.CommitCount()); |
| EXPECT_FALSE(semantics_manager_.DeleteOverflowed()); |
| EXPECT_FALSE(semantics_manager_.UpdateOverflowed()); |
| } |
| } // namespace flutter_runner_test |