| // Copyright 2013 The Flutter 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 "accessibility_bridge.h" |
| |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/async-loop/default.h> |
| #include <lib/async/cpp/executor.h> |
| #include <lib/fidl/cpp/binding_set.h> |
| #include <lib/fidl/cpp/interface_request.h> |
| #include <lib/inspect/cpp/hierarchy.h> |
| #include <lib/inspect/cpp/inspector.h> |
| #include <lib/inspect/cpp/reader.h> |
| #include <lib/sys/cpp/testing/service_directory_provider.h> |
| #include <lib/ui/scenic/cpp/view_ref_pair.h> |
| #include <zircon/status.h> |
| #include <zircon/types.h> |
| |
| #include <memory> |
| |
| #include "flutter/lib/ui/semantics/semantics_node.h" |
| #include "gtest/gtest.h" |
| |
| #include "flutter_runner_fakes.h" |
| |
| namespace flutter_runner_test { |
| |
| namespace { |
| |
| void ExpectNodeHasRole( |
| const fuchsia::accessibility::semantics::Node& node, |
| const std::unordered_map<uint32_t, fuchsia::accessibility::semantics::Role> |
| roles_by_node_id) { |
| ASSERT_TRUE(node.has_node_id()); |
| ASSERT_NE(roles_by_node_id.find(node.node_id()), roles_by_node_id.end()); |
| EXPECT_TRUE(node.has_role()); |
| EXPECT_EQ(node.role(), roles_by_node_id.at(node.node_id())); |
| } |
| |
| } // namespace |
| |
| class AccessibilityBridgeTestDelegate { |
| public: |
| void SetSemanticsEnabled(bool enabled) { enabled_ = enabled; } |
| void DispatchSemanticsAction(int32_t node_id, |
| flutter::SemanticsAction action) { |
| actions.push_back(std::make_pair(node_id, action)); |
| } |
| |
| bool enabled() { return enabled_; } |
| std::vector<std::pair<int32_t, flutter::SemanticsAction>> actions; |
| |
| private: |
| bool enabled_; |
| }; |
| |
| class AccessibilityBridgeTest : public testing::Test { |
| public: |
| AccessibilityBridgeTest() |
| : loop_(&kAsyncLoopConfigAttachToCurrentThread), |
| services_provider_(loop_.dispatcher()), |
| executor_(loop_.dispatcher()) { |
| services_provider_.AddService( |
| semantics_manager_.GetHandler(loop_.dispatcher()), |
| SemanticsManager::Name_); |
| } |
| |
| void RunLoopUntilIdle() { |
| loop_.RunUntilIdle(); |
| loop_.ResetQuit(); |
| } |
| |
| void RunPromiseToCompletion(fpromise::promise<> promise) { |
| bool done = false; |
| executor_.schedule_task( |
| std::move(promise).and_then([&done]() { done = true; })); |
| while (loop_.GetState() == ASYNC_LOOP_RUNNABLE) { |
| if (done) { |
| loop_.ResetQuit(); |
| return; |
| } |
| |
| loop_.Run(zx::deadline_after(zx::duration::infinite()), true); |
| } |
| loop_.ResetQuit(); |
| } |
| |
| protected: |
| void SetUp() override { |
| // Connect to SemanticsManager service. |
| fuchsia::accessibility::semantics::SemanticsManagerHandle semantics_manager; |
| zx_status_t semantics_status = |
| services_provider_.service_directory() |
| ->Connect<fuchsia::accessibility::semantics::SemanticsManager>( |
| semantics_manager.NewRequest()); |
| if (semantics_status != ZX_OK) { |
| FML_LOG(WARNING) |
| << "fuchsia::accessibility::semantics::SemanticsManager connection " |
| "failed: " |
| << zx_status_get_string(semantics_status); |
| } |
| |
| accessibility_delegate_.actions.clear(); |
| inspector_ = std::make_unique<inspect::Inspector>(); |
| flutter_runner::AccessibilityBridge::SetSemanticsEnabledCallback |
| set_semantics_enabled_callback = [this](bool enabled) { |
| accessibility_delegate_.SetSemanticsEnabled(enabled); |
| }; |
| flutter_runner::AccessibilityBridge::DispatchSemanticsActionCallback |
| dispatch_semantics_action_callback = |
| [this](int32_t node_id, flutter::SemanticsAction action) { |
| accessibility_delegate_.DispatchSemanticsAction(node_id, action); |
| }; |
| auto [view_ref_control, view_ref] = scenic::ViewRefPair::New(); |
| accessibility_bridge_ = |
| std::make_unique<flutter_runner::AccessibilityBridge>( |
| std::move(set_semantics_enabled_callback), |
| std::move(dispatch_semantics_action_callback), |
| std::move(semantics_manager), std::move(view_ref), |
| inspector_->GetRoot().CreateChild("test_node")); |
| |
| RunLoopUntilIdle(); |
| } |
| |
| void TearDown() override { semantics_manager_.ResetTree(); } |
| |
| MockSemanticsManager semantics_manager_; |
| AccessibilityBridgeTestDelegate accessibility_delegate_; |
| std::unique_ptr<flutter_runner::AccessibilityBridge> accessibility_bridge_; |
| // Required to verify inspect metrics. |
| std::unique_ptr<inspect::Inspector> inspector_; |
| |
| private: |
| async::Loop loop_; |
| sys::testing::ServiceDirectoryProvider services_provider_; |
| // Required to retrieve inspect metrics. |
| async::Executor executor_; |
| }; |
| |
| TEST_F(AccessibilityBridgeTest, RegistersViewRef) { |
| EXPECT_TRUE(semantics_manager_.RegisterViewCalled()); |
| } |
| |
| TEST_F(AccessibilityBridgeTest, EnableDisable) { |
| EXPECT_FALSE(accessibility_delegate_.enabled()); |
| std::unique_ptr<fuchsia::accessibility::semantics::SemanticListener> listener( |
| accessibility_bridge_.release()); |
| listener->OnSemanticsModeChanged(true, nullptr); |
| EXPECT_TRUE(accessibility_delegate_.enabled()); |
| } |
| |
| TEST_F(AccessibilityBridgeTest, RequestAnnounce) { |
| accessibility_bridge_->RequestAnnounce("message"); |
| RunLoopUntilIdle(); |
| |
| auto& last_events = semantics_manager_.GetLastEvents(); |
| ASSERT_EQ(last_events.size(), 1u); |
| ASSERT_TRUE(last_events[0].is_announce()); |
| EXPECT_EQ(last_events[0].announce().message(), "message"); |
| } |
| |
| TEST_F(AccessibilityBridgeTest, PopulatesIsKeyboardKeyAttribute) { |
| flutter::SemanticsNode node0; |
| node0.id = 0; |
| node0.flags = static_cast<int>(flutter::SemanticsFlags::kIsKeyboardKey); |
| |
| accessibility_bridge_->AddSemanticsNodeUpdate({{0, node0}}, 1.f); |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(1U, semantics_manager_.LastUpdatedNodes().size()); |
| const auto& fuchsia_node = semantics_manager_.LastUpdatedNodes().at(0u); |
| EXPECT_EQ(fuchsia_node.node_id(), static_cast<unsigned int>(node0.id)); |
| EXPECT_TRUE(fuchsia_node.has_attributes()); |
| EXPECT_TRUE(fuchsia_node.attributes().is_keyboard_key()); |
| } |
| |
| TEST_F(AccessibilityBridgeTest, UpdatesNodeRoles) { |
| flutter::SemanticsNodeUpdates updates; |
| |
| flutter::SemanticsNode node0; |
| node0.id = 0; |
| node0.flags |= static_cast<int>(flutter::SemanticsFlags::kIsButton); |
| node0.childrenInTraversalOrder = {1, 2, 3, 4, 5, 6, 7, 8}; |
| node0.childrenInHitTestOrder = {1, 2, 3, 4, 5, 6, 7, 8}; |
| updates.emplace(0, node0); |
| |
| flutter::SemanticsNode node1; |
| node1.id = 1; |
| node1.flags |= static_cast<int>(flutter::SemanticsFlags::kIsHeader); |
| node1.childrenInTraversalOrder = {}; |
| node1.childrenInHitTestOrder = {}; |
| updates.emplace(1, node1); |
| |
| flutter::SemanticsNode node2; |
| node2.id = 2; |
| node2.flags |= static_cast<int>(flutter::SemanticsFlags::kIsImage); |
| node2.childrenInTraversalOrder = {}; |
| node2.childrenInHitTestOrder = {}; |
| updates.emplace(2, node2); |
| |
| flutter::SemanticsNode node3; |
| node3.id = 3; |
| node3.flags |= static_cast<int>(flutter::SemanticsFlags::kIsTextField); |
| node3.childrenInTraversalOrder = {}; |
| node3.childrenInHitTestOrder = {}; |
| updates.emplace(3, node3); |
| |
| flutter::SemanticsNode node4; |
| node4.childrenInTraversalOrder = {}; |
| node4.childrenInHitTestOrder = {}; |
| node4.id = 4; |
| node4.flags |= static_cast<int>(flutter::SemanticsFlags::kIsSlider); |
| updates.emplace(4, node4); |
| |
| flutter::SemanticsNode node5; |
| node5.childrenInTraversalOrder = {}; |
| node5.childrenInHitTestOrder = {}; |
| node5.id = 5; |
| node5.flags |= static_cast<int>(flutter::SemanticsFlags::kIsLink); |
| updates.emplace(5, node5); |
| |
| flutter::SemanticsNode node6; |
| node6.childrenInTraversalOrder = {}; |
| node6.childrenInHitTestOrder = {}; |
| node6.id = 6; |
| node6.flags |= static_cast<int>(flutter::SemanticsFlags::kHasCheckedState); |
| node6.flags |= |
| static_cast<int>(flutter::SemanticsFlags::kIsInMutuallyExclusiveGroup); |
| updates.emplace(6, node6); |
| |
| flutter::SemanticsNode node7; |
| node7.childrenInTraversalOrder = {}; |
| node7.childrenInHitTestOrder = {}; |
| node7.id = 7; |
| node7.flags |= static_cast<int>(flutter::SemanticsFlags::kHasCheckedState); |
| updates.emplace(7, node7); |
| |
| flutter::SemanticsNode node8; |
| node8.childrenInTraversalOrder = {}; |
| node8.childrenInHitTestOrder = {}; |
| node8.id = 8; |
| node8.flags |= static_cast<int>(flutter::SemanticsFlags::kHasToggledState); |
| updates.emplace(7, node8); |
| |
| accessibility_bridge_->AddSemanticsNodeUpdate(std::move(updates), 1.f); |
| RunLoopUntilIdle(); |
| |
| std::unordered_map<uint32_t, fuchsia::accessibility::semantics::Role> |
| roles_by_node_id = { |
| {0u, fuchsia::accessibility::semantics::Role::BUTTON}, |
| {1u, fuchsia::accessibility::semantics::Role::HEADER}, |
| {2u, fuchsia::accessibility::semantics::Role::IMAGE}, |
| {3u, fuchsia::accessibility::semantics::Role::TEXT_FIELD}, |
| {4u, fuchsia::accessibility::semantics::Role::SLIDER}, |
| {5u, fuchsia::accessibility::semantics::Role::LINK}, |
| {6u, fuchsia::accessibility::semantics::Role::RADIO_BUTTON}, |
| {7u, fuchsia::accessibility::semantics::Role::CHECK_BOX}, |
| {8u, fuchsia::accessibility::semantics::Role::TOGGLE_SWITCH}}; |
| |
| EXPECT_EQ(0, semantics_manager_.DeleteCount()); |
| EXPECT_EQ(1, semantics_manager_.UpdateCount()); |
| EXPECT_EQ(1, semantics_manager_.CommitCount()); |
| EXPECT_EQ(8u, semantics_manager_.LastUpdatedNodes().size()); |
| for (const auto& node : semantics_manager_.LastUpdatedNodes()) { |
| ExpectNodeHasRole(node, roles_by_node_id); |
| } |
| |
| EXPECT_FALSE(semantics_manager_.DeleteOverflowed()); |
| EXPECT_FALSE(semantics_manager_.UpdateOverflowed()); |
| } |
| |
| 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}; |
| node1.childrenInHitTestOrder = {2}; |
| |
| flutter::SemanticsNode node0; |
| node0.id = 0; |
| node0.childrenInTraversalOrder = {1}; |
| node0.childrenInHitTestOrder = {1}; |
| |
| accessibility_bridge_->AddSemanticsNodeUpdate( |
| { |
| {0, node0}, |
| {1, node1}, |
| {2, node2}, |
| }, |
| 1.f); |
| 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(); |
| node0.childrenInHitTestOrder.clear(); |
| accessibility_bridge_->AddSemanticsNodeUpdate( |
| { |
| {0, node0}, |
| }, |
| 1.f); |
| 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, PopulatesRoleButton) { |
| flutter::SemanticsNode node0; |
| node0.id = 0; |
| node0.flags = static_cast<int>(flutter::SemanticsFlags::kIsButton); |
| |
| accessibility_bridge_->AddSemanticsNodeUpdate({{0, node0}}, 1.f); |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(1U, semantics_manager_.LastUpdatedNodes().size()); |
| const auto& fuchsia_node = semantics_manager_.LastUpdatedNodes().at(0u); |
| EXPECT_EQ(fuchsia_node.node_id(), static_cast<unsigned int>(node0.id)); |
| EXPECT_TRUE(fuchsia_node.has_role()); |
| EXPECT_EQ(fuchsia_node.role(), |
| fuchsia::accessibility::semantics::Role::BUTTON); |
| } |
| |
| TEST_F(AccessibilityBridgeTest, PopulatesRoleImage) { |
| flutter::SemanticsNode node0; |
| node0.id = 0; |
| node0.flags = static_cast<int>(flutter::SemanticsFlags::kIsImage); |
| |
| accessibility_bridge_->AddSemanticsNodeUpdate({{0, node0}}, 1.f); |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(1U, semantics_manager_.LastUpdatedNodes().size()); |
| const auto& fuchsia_node = semantics_manager_.LastUpdatedNodes().at(0u); |
| EXPECT_EQ(fuchsia_node.node_id(), static_cast<unsigned int>(node0.id)); |
| EXPECT_TRUE(fuchsia_node.has_role()); |
| EXPECT_EQ(fuchsia_node.role(), |
| fuchsia::accessibility::semantics::Role::IMAGE); |
| } |
| |
| TEST_F(AccessibilityBridgeTest, PopulatesRoleSlider) { |
| flutter::SemanticsNode node0; |
| node0.id = 0; |
| node0.actions |= static_cast<int>(flutter::SemanticsAction::kIncrease); |
| |
| accessibility_bridge_->AddSemanticsNodeUpdate({{0, node0}}, 1.f); |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(1U, semantics_manager_.LastUpdatedNodes().size()); |
| const auto& fuchsia_node = semantics_manager_.LastUpdatedNodes().at(0u); |
| EXPECT_EQ(fuchsia_node.node_id(), static_cast<unsigned int>(node0.id)); |
| EXPECT_TRUE(fuchsia_node.has_role()); |
| EXPECT_EQ(fuchsia_node.role(), |
| fuchsia::accessibility::semantics::Role::SLIDER); |
| } |
| |
| TEST_F(AccessibilityBridgeTest, PopulatesRoleHeader) { |
| flutter::SemanticsNode node0; |
| node0.id = 0; |
| node0.flags = static_cast<int>(flutter::SemanticsFlags::kIsHeader); |
| |
| accessibility_bridge_->AddSemanticsNodeUpdate({{0, node0}}, 1.f); |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(1U, semantics_manager_.LastUpdatedNodes().size()); |
| const auto& fuchsia_node = semantics_manager_.LastUpdatedNodes().at(0u); |
| EXPECT_EQ(fuchsia_node.node_id(), static_cast<unsigned int>(node0.id)); |
| EXPECT_TRUE(fuchsia_node.has_role()); |
| EXPECT_EQ(fuchsia_node.role(), |
| fuchsia::accessibility::semantics::Role::HEADER); |
| } |
| |
| TEST_F(AccessibilityBridgeTest, PopulatesCheckedState) { |
| flutter::SemanticsNode node0; |
| node0.id = 0; |
| // HasCheckedState = true |
| // IsChecked = true |
| // IsSelected = false |
| node0.flags |= static_cast<int>(flutter::SemanticsFlags::kHasCheckedState); |
| node0.flags |= static_cast<int>(flutter::SemanticsFlags::kIsChecked); |
| node0.value = "value"; |
| |
| accessibility_bridge_->AddSemanticsNodeUpdate({{0, node0}}, 1.f); |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(0, semantics_manager_.DeleteCount()); |
| EXPECT_EQ(1, semantics_manager_.UpdateCount()); |
| EXPECT_EQ(1, semantics_manager_.CommitCount()); |
| EXPECT_EQ(1U, semantics_manager_.LastUpdatedNodes().size()); |
| const auto& fuchsia_node = semantics_manager_.LastUpdatedNodes().at(0u); |
| EXPECT_EQ(fuchsia_node.node_id(), static_cast<unsigned int>(node0.id)); |
| EXPECT_TRUE(fuchsia_node.has_states()); |
| const auto& states = fuchsia_node.states(); |
| EXPECT_TRUE(states.has_checked_state()); |
| EXPECT_EQ(states.checked_state(), |
| fuchsia::accessibility::semantics::CheckedState::CHECKED); |
| EXPECT_TRUE(states.has_selected()); |
| EXPECT_FALSE(states.selected()); |
| EXPECT_TRUE(states.has_value()); |
| EXPECT_EQ(states.value(), node0.value); |
| |
| EXPECT_FALSE(semantics_manager_.DeleteOverflowed()); |
| EXPECT_FALSE(semantics_manager_.UpdateOverflowed()); |
| } |
| |
| TEST_F(AccessibilityBridgeTest, PopulatesSelectedState) { |
| flutter::SemanticsNode node0; |
| node0.id = 0; |
| // HasCheckedState = false |
| // IsChecked = false |
| // IsSelected = true |
| node0.flags = static_cast<int>(flutter::SemanticsFlags::kIsSelected); |
| |
| accessibility_bridge_->AddSemanticsNodeUpdate({{0, node0}}, 1.f); |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(0, semantics_manager_.DeleteCount()); |
| EXPECT_EQ(1, semantics_manager_.UpdateCount()); |
| EXPECT_EQ(1, semantics_manager_.CommitCount()); |
| EXPECT_EQ(1U, semantics_manager_.LastUpdatedNodes().size()); |
| const auto& fuchsia_node = semantics_manager_.LastUpdatedNodes().at(0u); |
| EXPECT_EQ(fuchsia_node.node_id(), static_cast<unsigned int>(node0.id)); |
| EXPECT_TRUE(fuchsia_node.has_states()); |
| const auto& states = fuchsia_node.states(); |
| EXPECT_TRUE(states.has_checked_state()); |
| EXPECT_EQ(states.checked_state(), |
| fuchsia::accessibility::semantics::CheckedState::NONE); |
| EXPECT_TRUE(states.has_selected()); |
| EXPECT_TRUE(states.selected()); |
| |
| EXPECT_FALSE(semantics_manager_.DeleteOverflowed()); |
| EXPECT_FALSE(semantics_manager_.UpdateOverflowed()); |
| } |
| |
| TEST_F(AccessibilityBridgeTest, PopulatesToggledState) { |
| flutter::SemanticsNode node0; |
| node0.id = 0; |
| node0.flags |= static_cast<int>(flutter::SemanticsFlags::kHasToggledState); |
| node0.flags |= static_cast<int>(flutter::SemanticsFlags::kIsToggled); |
| |
| accessibility_bridge_->AddSemanticsNodeUpdate({{0, node0}}, 1.f); |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(0, semantics_manager_.DeleteCount()); |
| EXPECT_EQ(1, semantics_manager_.UpdateCount()); |
| EXPECT_EQ(1, semantics_manager_.CommitCount()); |
| EXPECT_EQ(1U, semantics_manager_.LastUpdatedNodes().size()); |
| const auto& fuchsia_node = semantics_manager_.LastUpdatedNodes().at(0u); |
| EXPECT_EQ(fuchsia_node.node_id(), static_cast<unsigned int>(node0.id)); |
| EXPECT_TRUE(fuchsia_node.has_states()); |
| const auto& states = fuchsia_node.states(); |
| EXPECT_TRUE(states.has_toggled_state()); |
| EXPECT_EQ(states.toggled_state(), |
| fuchsia::accessibility::semantics::ToggledState::ON); |
| |
| EXPECT_FALSE(semantics_manager_.DeleteOverflowed()); |
| EXPECT_FALSE(semantics_manager_.UpdateOverflowed()); |
| } |
| |
| TEST_F(AccessibilityBridgeTest, ApplyViewPixelRatioToRoot) { |
| flutter::SemanticsNode node0; |
| node0.id = 0; |
| node0.flags = static_cast<int>(flutter::SemanticsFlags::kIsSelected); |
| |
| accessibility_bridge_->AddSemanticsNodeUpdate({{0, node0}}, 1.25f); |
| RunLoopUntilIdle(); |
| const auto& fuchsia_node = semantics_manager_.LastUpdatedNodes().at(0u); |
| EXPECT_EQ(fuchsia_node.node_id(), static_cast<unsigned int>(node0.id)); |
| EXPECT_EQ(fuchsia_node.transform().matrix[0], 0.8f); |
| EXPECT_EQ(fuchsia_node.transform().matrix[5], 0.8f); |
| EXPECT_EQ(fuchsia_node.transform().matrix[10], 1.f); |
| } |
| |
| TEST_F(AccessibilityBridgeTest, DoesNotPopulatesHiddenState) { |
| // Flutter's notion of a hidden node is different from Fuchsia's hidden node. |
| // This test make sures that this state does not get sent. |
| flutter::SemanticsNode node0; |
| node0.id = 0; |
| // HasCheckedState = false |
| // IsChecked = false |
| // IsSelected = false |
| // IsHidden = true |
| node0.flags = static_cast<int>(flutter::SemanticsFlags::kIsHidden); |
| |
| accessibility_bridge_->AddSemanticsNodeUpdate({{0, node0}}, 1.f); |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(0, semantics_manager_.DeleteCount()); |
| EXPECT_EQ(1, semantics_manager_.UpdateCount()); |
| EXPECT_EQ(1, semantics_manager_.CommitCount()); |
| EXPECT_EQ(1u, semantics_manager_.LastUpdatedNodes().size()); |
| const auto& fuchsia_node = semantics_manager_.LastUpdatedNodes().at(0u); |
| EXPECT_EQ(fuchsia_node.node_id(), static_cast<unsigned int>(node0.id)); |
| EXPECT_TRUE(fuchsia_node.has_states()); |
| const auto& states = fuchsia_node.states(); |
| EXPECT_TRUE(states.has_checked_state()); |
| EXPECT_EQ(states.checked_state(), |
| fuchsia::accessibility::semantics::CheckedState::NONE); |
| EXPECT_TRUE(states.has_selected()); |
| EXPECT_FALSE(states.selected()); |
| EXPECT_FALSE(states.has_hidden()); |
| |
| EXPECT_FALSE(semantics_manager_.DeleteOverflowed()); |
| EXPECT_FALSE(semantics_manager_.UpdateOverflowed()); |
| } |
| |
| TEST_F(AccessibilityBridgeTest, PopulatesActions) { |
| flutter::SemanticsNode node0; |
| node0.id = 0; |
| node0.actions |= static_cast<int>(flutter::SemanticsAction::kTap); |
| node0.actions |= static_cast<int>(flutter::SemanticsAction::kLongPress); |
| node0.actions |= static_cast<int>(flutter::SemanticsAction::kShowOnScreen); |
| node0.actions |= static_cast<int>(flutter::SemanticsAction::kIncrease); |
| node0.actions |= static_cast<int>(flutter::SemanticsAction::kDecrease); |
| |
| accessibility_bridge_->AddSemanticsNodeUpdate({{0, node0}}, 1.f); |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(0, semantics_manager_.DeleteCount()); |
| EXPECT_EQ(1, semantics_manager_.UpdateCount()); |
| EXPECT_EQ(1, semantics_manager_.CommitCount()); |
| EXPECT_EQ(1u, semantics_manager_.LastUpdatedNodes().size()); |
| const auto& fuchsia_node = semantics_manager_.LastUpdatedNodes().at(0u); |
| EXPECT_EQ(fuchsia_node.actions().size(), 5u); |
| EXPECT_EQ(fuchsia_node.actions().at(0u), |
| fuchsia::accessibility::semantics::Action::DEFAULT); |
| EXPECT_EQ(fuchsia_node.actions().at(1u), |
| fuchsia::accessibility::semantics::Action::SECONDARY); |
| EXPECT_EQ(fuchsia_node.actions().at(2u), |
| fuchsia::accessibility::semantics::Action::SHOW_ON_SCREEN); |
| EXPECT_EQ(fuchsia_node.actions().at(3u), |
| fuchsia::accessibility::semantics::Action::INCREMENT); |
| EXPECT_EQ(fuchsia_node.actions().at(4u), |
| fuchsia::accessibility::semantics::Action::DECREMENT); |
| } |
| |
| 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}; |
| node0.childrenInHitTestOrder = {1, 2}; |
| |
| accessibility_bridge_->AddSemanticsNodeUpdate( |
| { |
| {0, node0}, |
| {1, node1}, |
| {2, bad_node}, |
| }, |
| 1.f); |
| 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, TruncatesLargeToolTip) { |
| // Test that tooltips 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.tooltip = |
| std::string(fuchsia::accessibility::semantics::MAX_LABEL_SIZE + 1, '2'); |
| |
| node0.childrenInTraversalOrder = {1, 2}; |
| node0.childrenInHitTestOrder = {1, 2}; |
| |
| accessibility_bridge_->AddSemanticsNodeUpdate( |
| { |
| {0, node0}, |
| {1, node1}, |
| {2, bad_node}, |
| }, |
| 1.f); |
| 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().secondary_label(), |
| std::string(fuchsia::accessibility::semantics::MAX_LABEL_SIZE, '2')); |
| EXPECT_FALSE(semantics_manager_.DeleteOverflowed()); |
| EXPECT_FALSE(semantics_manager_.UpdateOverflowed()); |
| } |
| |
| TEST_F(AccessibilityBridgeTest, TruncatesLargeValue) { |
| // Test that values 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.value = |
| std::string(fuchsia::accessibility::semantics::MAX_VALUE_SIZE + 1, '2'); |
| |
| node0.childrenInTraversalOrder = {1, 2}; |
| node0.childrenInHitTestOrder = {1, 2}; |
| |
| accessibility_bridge_->AddSemanticsNodeUpdate( |
| { |
| {0, node0}, |
| {1, node1}, |
| {2, bad_node}, |
| }, |
| 1.f); |
| 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()); |
| 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_states()); |
| EXPECT_EQ( |
| trimmed_node->states().value(), |
| std::string(fuchsia::accessibility::semantics::MAX_VALUE_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.value = |
| std::string(fuchsia::accessibility::semantics::MAX_VALUE_SIZE, '4'); |
| |
| node0.childrenInTraversalOrder = {1, 2}; |
| node0.childrenInHitTestOrder = {1, 2}; |
| node1.childrenInTraversalOrder = {3, 4}; |
| node1.childrenInHitTestOrder = {3, 4}; |
| |
| accessibility_bridge_->AddSemanticsNodeUpdate( |
| { |
| {0, node0}, |
| {1, node1}, |
| {2, node2}, |
| {3, node3}, |
| {4, node4}, |
| }, |
| 1.f); |
| 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); |
| node0.childrenInHitTestOrder.push_back(0); |
| accessibility_bridge_->AddSemanticsNodeUpdate( |
| { |
| {0, node0}, |
| }, |
| 1.f); |
| 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}; |
| node0.childrenInHitTestOrder = {0, 1}; |
| flutter::SemanticsNode node1; |
| node1.id = 1; |
| node1.childrenInTraversalOrder = {0}; |
| node1.childrenInHitTestOrder = {0}; |
| accessibility_bridge_->AddSemanticsNodeUpdate( |
| { |
| {0, node0}, |
| {1, node1}, |
| }, |
| 1.f); |
| 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 = 100; |
| const int32_t leaf_nodes = 100; |
| for (int32_t i = 1; i < child_nodes + 1; i++) { |
| flutter::SemanticsNode node; |
| node.id = i; |
| node0.childrenInTraversalOrder.push_back(i); |
| node0.childrenInHitTestOrder.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); |
| node.childrenInHitTestOrder.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))); |
| |
| // Make the semantics manager hold answering to this commit to test the flow |
| // control. This means the second update will not be pushed until the first |
| // one has processed. |
| semantics_manager_.SetShouldHoldCommitResponse(true); |
| accessibility_bridge_->AddSemanticsNodeUpdate(update, 1.f); |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(0, semantics_manager_.DeleteCount()); |
| |
| EXPECT_TRUE(6 <= semantics_manager_.UpdateCount() && |
| semantics_manager_.UpdateCount() <= 12); |
| EXPECT_EQ(1, semantics_manager_.CommitCount()); |
| EXPECT_FALSE(semantics_manager_.DeleteOverflowed()); |
| EXPECT_FALSE(semantics_manager_.UpdateOverflowed()); |
| |
| int next_update_count = semantics_manager_.UpdateCount() + 1; |
| // Remove the children |
| node0.childrenInTraversalOrder.clear(); |
| node0.childrenInHitTestOrder.clear(); |
| accessibility_bridge_->AddSemanticsNodeUpdate( |
| { |
| {0, node0}, |
| }, |
| 1.f); |
| RunLoopUntilIdle(); |
| |
| // Should still be 0, because the commit was not answered yet. |
| EXPECT_EQ(0, semantics_manager_.DeleteCount()); |
| |
| semantics_manager_.InvokeCommitCallback(); |
| RunLoopUntilIdle(); |
| |
| EXPECT_EQ(1, semantics_manager_.DeleteCount()); |
| EXPECT_EQ(next_update_count, semantics_manager_.UpdateCount()); |
| EXPECT_EQ(2, semantics_manager_.CommitCount()); |
| EXPECT_FALSE(semantics_manager_.DeleteOverflowed()); |
| EXPECT_FALSE(semantics_manager_.UpdateOverflowed()); |
| } |
| |
| TEST_F(AccessibilityBridgeTest, HitTest) { |
| flutter::SemanticsNode node0; |
| node0.id = 0; |
| node0.rect.setLTRB(0, 0, 100, 100); |
| node0.flags |= static_cast<int32_t>(flutter::SemanticsFlags::kIsFocusable); |
| |
| flutter::SemanticsNode node1; |
| node1.id = 1; |
| node1.rect.setLTRB(10, 10, 20, 20); |
| // Setting platform view id ensures this node is considered focusable. |
| node1.platformViewId = 1u; |
| |
| flutter::SemanticsNode node2; |
| node2.id = 2; |
| node2.rect.setLTRB(25, 10, 45, 20); |
| // Setting label ensures this node is considered focusable. |
| node2.label = "label"; |
| |
| flutter::SemanticsNode node3; |
| node3.id = 3; |
| node3.rect.setLTRB(10, 25, 20, 45); |
| // Setting actions to a nonzero value ensures this node is considered |
| // focusable. |
| node3.actions = 1u; |
| |
| flutter::SemanticsNode node4; |
| node4.id = 4; |
| node4.rect.setLTRB(10, 10, 20, 20); |
| node4.transform.setTranslate(20, 20, 0); |
| node4.flags |= static_cast<int32_t>(flutter::SemanticsFlags::kIsFocusable); |
| |
| node0.childrenInTraversalOrder = {1, 2, 3, 4}; |
| node0.childrenInHitTestOrder = {1, 2, 3, 4}; |
| |
| accessibility_bridge_->AddSemanticsNodeUpdate( |
| { |
| {0, node0}, |
| {1, node1}, |
| {2, node2}, |
| {3, node3}, |
| {4, node4}, |
| }, |
| 1.f); |
| RunLoopUntilIdle(); |
| |
| uint32_t hit_node_id; |
| auto callback = [&hit_node_id](fuchsia::accessibility::semantics::Hit hit) { |
| EXPECT_TRUE(hit.has_node_id()); |
| hit_node_id = hit.node_id(); |
| }; |
| |
| // Nodes are: |
| // ---------- |
| // | 1 2 | |
| // | 3 4 | |
| // ---------- |
| |
| accessibility_bridge_->HitTest({1, 1}, callback); |
| EXPECT_EQ(hit_node_id, 0u); |
| accessibility_bridge_->HitTest({15, 15}, callback); |
| EXPECT_EQ(hit_node_id, 1u); |
| accessibility_bridge_->HitTest({30, 15}, callback); |
| EXPECT_EQ(hit_node_id, 2u); |
| accessibility_bridge_->HitTest({15, 30}, callback); |
| EXPECT_EQ(hit_node_id, 3u); |
| accessibility_bridge_->HitTest({30, 30}, callback); |
| EXPECT_EQ(hit_node_id, 4u); |
| } |
| |
| TEST_F(AccessibilityBridgeTest, HitTestWithPixelRatio) { |
| flutter::SemanticsNode node0; |
| node0.id = 0; |
| node0.rect.setLTRB(0, 0, 100, 100); |
| node0.flags |= static_cast<int32_t>(flutter::SemanticsFlags::kIsFocusable); |
| |
| flutter::SemanticsNode node1; |
| node1.id = 1; |
| node1.rect.setLTRB(10, 10, 20, 20); |
| // Setting platform view id ensures this node is considered focusable. |
| node1.platformViewId = 1u; |
| |
| node0.childrenInTraversalOrder = {1}; |
| node0.childrenInHitTestOrder = {1}; |
| |
| accessibility_bridge_->AddSemanticsNodeUpdate( |
| { |
| {0, node0}, |
| {1, node1}, |
| }, |
| // Pick a very small pixel ratio so that a point within the bounds of |
| // the node's root-space coordinates will be well outside the "screen" |
| // bounds of the node. |
| .1f); |
| RunLoopUntilIdle(); |
| |
| uint32_t hit_node_id; |
| auto callback = [&hit_node_id](fuchsia::accessibility::semantics::Hit hit) { |
| EXPECT_TRUE(hit.has_node_id()); |
| hit_node_id = hit.node_id(); |
| }; |
| accessibility_bridge_->HitTest({15, 15}, callback); |
| EXPECT_EQ(hit_node_id, 0u); |
| } |
| |
| TEST_F(AccessibilityBridgeTest, HitTestUnfocusableChild) { |
| flutter::SemanticsNode node0; |
| node0.id = 0; |
| node0.rect.setLTRB(0, 0, 100, 100); |
| |
| flutter::SemanticsNode node1; |
| node1.id = 1; |
| node1.rect.setLTRB(10, 10, 60, 60); |
| |
| flutter::SemanticsNode node2; |
| node2.id = 2; |
| node2.rect.setLTRB(50, 50, 100, 100); |
| node2.flags |= static_cast<int32_t>(flutter::SemanticsFlags::kIsFocusable); |
| |
| node0.childrenInTraversalOrder = {1, 2}; |
| node0.childrenInHitTestOrder = {1, 2}; |
| |
| accessibility_bridge_->AddSemanticsNodeUpdate( |
| { |
| {0, node0}, |
| {1, node1}, |
| {2, node2}, |
| }, |
| 1.f); |
| RunLoopUntilIdle(); |
| |
| uint32_t hit_node_id; |
| auto callback = [&hit_node_id](fuchsia::accessibility::semantics::Hit hit) { |
| EXPECT_TRUE(hit.has_node_id()); |
| hit_node_id = hit.node_id(); |
| }; |
| |
| accessibility_bridge_->HitTest({55, 55}, callback); |
| EXPECT_EQ(hit_node_id, 2u); |
| } |
| |
| TEST_F(AccessibilityBridgeTest, HitTestOverlapping) { |
| // Tests that the first node in hit test order wins, even if a later node |
| // would be able to receive the hit. |
| flutter::SemanticsNode node0; |
| node0.id = 0; |
| node0.rect.setLTRB(0, 0, 100, 100); |
| node0.flags |= static_cast<int32_t>(flutter::SemanticsFlags::kIsFocusable); |
| |
| flutter::SemanticsNode node1; |
| node1.id = 1; |
| node1.rect.setLTRB(0, 0, 100, 100); |
| node1.flags |= static_cast<int32_t>(flutter::SemanticsFlags::kIsFocusable); |
| |
| flutter::SemanticsNode node2; |
| node2.id = 2; |
| node2.rect.setLTRB(25, 10, 45, 20); |
| node2.flags |= static_cast<int32_t>(flutter::SemanticsFlags::kIsFocusable); |
| |
| node0.childrenInTraversalOrder = {1, 2}; |
| node0.childrenInHitTestOrder = {2, 1}; |
| |
| accessibility_bridge_->AddSemanticsNodeUpdate( |
| { |
| {0, node0}, |
| {1, node1}, |
| {2, node2}, |
| }, |
| 1.f); |
| RunLoopUntilIdle(); |
| |
| uint32_t hit_node_id; |
| auto callback = [&hit_node_id](fuchsia::accessibility::semantics::Hit hit) { |
| EXPECT_TRUE(hit.has_node_id()); |
| hit_node_id = hit.node_id(); |
| }; |
| |
| accessibility_bridge_->HitTest({30, 15}, callback); |
| EXPECT_EQ(hit_node_id, 2u); |
| } |
| |
| TEST_F(AccessibilityBridgeTest, Actions) { |
| flutter::SemanticsNode node0; |
| node0.id = 0; |
| |
| flutter::SemanticsNode node1; |
| node1.id = 1; |
| |
| node0.childrenInTraversalOrder = {1}; |
| node0.childrenInHitTestOrder = {1}; |
| |
| accessibility_bridge_->AddSemanticsNodeUpdate( |
| { |
| {0, node0}, |
| {1, node1}, |
| }, |
| 1.f); |
| RunLoopUntilIdle(); |
| |
| auto handled_callback = [](bool handled) { EXPECT_TRUE(handled); }; |
| auto unhandled_callback = [](bool handled) { EXPECT_FALSE(handled); }; |
| |
| accessibility_bridge_->OnAccessibilityActionRequested( |
| 0u, fuchsia::accessibility::semantics::Action::DEFAULT, handled_callback); |
| EXPECT_EQ(accessibility_delegate_.actions.size(), 1u); |
| EXPECT_EQ(accessibility_delegate_.actions.back(), |
| std::make_pair(0, flutter::SemanticsAction::kTap)); |
| |
| accessibility_bridge_->OnAccessibilityActionRequested( |
| 0u, fuchsia::accessibility::semantics::Action::SECONDARY, |
| handled_callback); |
| EXPECT_EQ(accessibility_delegate_.actions.size(), 2u); |
| EXPECT_EQ(accessibility_delegate_.actions.back(), |
| std::make_pair(0, flutter::SemanticsAction::kLongPress)); |
| |
| accessibility_bridge_->OnAccessibilityActionRequested( |
| 0u, fuchsia::accessibility::semantics::Action::SET_FOCUS, |
| unhandled_callback); |
| EXPECT_EQ(accessibility_delegate_.actions.size(), 2u); |
| |
| accessibility_bridge_->OnAccessibilityActionRequested( |
| 0u, fuchsia::accessibility::semantics::Action::SET_VALUE, |
| unhandled_callback); |
| EXPECT_EQ(accessibility_delegate_.actions.size(), 2u); |
| |
| accessibility_bridge_->OnAccessibilityActionRequested( |
| 0u, fuchsia::accessibility::semantics::Action::SHOW_ON_SCREEN, |
| handled_callback); |
| EXPECT_EQ(accessibility_delegate_.actions.size(), 3u); |
| EXPECT_EQ(accessibility_delegate_.actions.back(), |
| std::make_pair(0, flutter::SemanticsAction::kShowOnScreen)); |
| |
| accessibility_bridge_->OnAccessibilityActionRequested( |
| 2u, fuchsia::accessibility::semantics::Action::DEFAULT, |
| unhandled_callback); |
| EXPECT_EQ(accessibility_delegate_.actions.size(), 3u); |
| |
| accessibility_bridge_->OnAccessibilityActionRequested( |
| 0u, fuchsia::accessibility::semantics::Action::INCREMENT, |
| handled_callback); |
| EXPECT_EQ(accessibility_delegate_.actions.size(), 4u); |
| EXPECT_EQ(accessibility_delegate_.actions.back(), |
| std::make_pair(0, flutter::SemanticsAction::kIncrease)); |
| |
| accessibility_bridge_->OnAccessibilityActionRequested( |
| 0u, fuchsia::accessibility::semantics::Action::DECREMENT, |
| handled_callback); |
| EXPECT_EQ(accessibility_delegate_.actions.size(), 5u); |
| EXPECT_EQ(accessibility_delegate_.actions.back(), |
| std::make_pair(0, flutter::SemanticsAction::kDecrease)); |
| } |
| |
| #if !FLUTTER_RELEASE |
| TEST_F(AccessibilityBridgeTest, InspectData) { |
| flutter::SemanticsNodeUpdates updates; |
| flutter::SemanticsNode node0; |
| node0.id = 0; |
| node0.label = "node0"; |
| node0.hint = "node0_hint"; |
| node0.value = "value"; |
| node0.flags |= static_cast<int>(flutter::SemanticsFlags::kIsButton); |
| node0.childrenInTraversalOrder = {1}; |
| node0.childrenInHitTestOrder = {1}; |
| node0.rect.setLTRB(0, 0, 100, 100); |
| updates.emplace(0, node0); |
| |
| flutter::SemanticsNode node1; |
| node1.id = 1; |
| node1.flags |= static_cast<int>(flutter::SemanticsFlags::kIsHeader); |
| node1.childrenInTraversalOrder = {}; |
| node1.childrenInHitTestOrder = {}; |
| updates.emplace(1, node1); |
| |
| accessibility_bridge_->AddSemanticsNodeUpdate(std::move(updates), 1.f); |
| RunLoopUntilIdle(); |
| |
| fpromise::result<inspect::Hierarchy> hierarchy; |
| ASSERT_FALSE(hierarchy.is_ok()); |
| RunPromiseToCompletion( |
| inspect::ReadFromInspector(*inspector_) |
| .then([&hierarchy](fpromise::result<inspect::Hierarchy>& result) { |
| hierarchy = std::move(result); |
| })); |
| ASSERT_TRUE(hierarchy.is_ok()); |
| |
| auto tree_inspect_hierarchy = hierarchy.value().GetByPath({"test_node"}); |
| ASSERT_NE(tree_inspect_hierarchy, nullptr); |
| // TODO(http://fxbug.dev/75841): Rewrite flutter engine accessibility bridge |
| // tests using inspect matchers. The checks bellow verify that the tree was |
| // built, and that it matches the format of the input tree. This will be |
| // updated in the future when test matchers are available to verify individual |
| // property values. |
| const auto& root = tree_inspect_hierarchy->children(); |
| ASSERT_EQ(root.size(), 1u); |
| EXPECT_EQ(root[0].name(), "semantic_tree_root"); |
| const auto& child = root[0].children(); |
| ASSERT_EQ(child.size(), 1u); |
| EXPECT_EQ(child[0].name(), "node_1"); |
| } |
| #endif // !FLUTTER_RELEASE |
| |
| } // namespace flutter_runner_test |