Flutter Engine
accessibility_bridge_unittest.cc
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "flutter/shell/platform/fuchsia/flutter/accessibility_bridge.h"
6 
7 #include <gtest/gtest.h>
8 #include <lib/async-loop/cpp/loop.h>
9 #include <lib/async-loop/default.h>
10 #include <lib/fidl/cpp/binding_set.h>
11 #include <lib/fidl/cpp/interface_request.h>
12 #include <lib/sys/cpp/testing/service_directory_provider.h>
13 #include <zircon/types.h>
14 
15 #include <memory>
16 
17 #include "flutter/lib/ui/semantics/semantics_node.h"
18 #include "flutter/shell/platform/fuchsia/flutter/flutter_runner_fakes.h"
19 
21 
22 namespace {
23 
24 void ExpectNodeHasRole(
25  const fuchsia::accessibility::semantics::Node& node,
26  const std::unordered_map<uint32_t, fuchsia::accessibility::semantics::Role>
27  roles_by_node_id) {
28  ASSERT_TRUE(node.has_node_id());
29  ASSERT_NE(roles_by_node_id.find(node.node_id()), roles_by_node_id.end());
30  EXPECT_TRUE(node.has_role());
31  EXPECT_EQ(node.role(), roles_by_node_id.at(node.node_id()));
32 }
33 
34 } // namespace
35 
38  public:
39  void SetSemanticsEnabled(bool enabled) override { enabled_ = enabled; }
40  void DispatchSemanticsAction(int32_t node_id,
42  actions.push_back(std::make_pair(node_id, action));
43  }
44 
45  bool enabled() { return enabled_; }
46  std::vector<std::pair<int32_t, flutter::SemanticsAction>> actions;
47 
48  private:
49  bool enabled_;
50 };
51 
52 class AccessibilityBridgeTest : public testing::Test {
53  public:
55  : loop_(&kAsyncLoopConfigAttachToCurrentThread),
56  services_provider_(loop_.dispatcher()) {
57  services_provider_.AddService(
58  semantics_manager_.GetHandler(loop_.dispatcher()),
59  SemanticsManager::Name_);
60  }
61 
63  loop_.RunUntilIdle();
64  loop_.ResetQuit();
65  }
66 
67  protected:
68  void SetUp() override {
69  zx_status_t status = zx::eventpair::create(
70  /*flags*/ 0u, &view_ref_control_.reference, &view_ref_.reference);
71  EXPECT_EQ(status, ZX_OK);
72 
73  accessibility_delegate_.actions.clear();
74  accessibility_bridge_ =
75  std::make_unique<flutter_runner::AccessibilityBridge>(
76  accessibility_delegate_, services_provider_.service_directory(),
77  std::move(view_ref_));
78  RunLoopUntilIdle();
79  }
80 
81  void TearDown() override { semantics_manager_.ResetTree(); }
82 
83  fuchsia::ui::views::ViewRefControl view_ref_control_;
84  fuchsia::ui::views::ViewRef view_ref_;
87  std::unique_ptr<flutter_runner::AccessibilityBridge> accessibility_bridge_;
88 
89  private:
90  async::Loop loop_;
91  sys::testing::ServiceDirectoryProvider services_provider_;
92 };
93 
94 TEST_F(AccessibilityBridgeTest, RegistersViewRef) {
95  EXPECT_TRUE(semantics_manager_.RegisterViewCalled());
96 }
97 
99  EXPECT_FALSE(accessibility_delegate_.enabled());
100  std::unique_ptr<fuchsia::accessibility::semantics::SemanticListener> listener(
101  accessibility_bridge_.release());
102  listener->OnSemanticsModeChanged(true, nullptr);
103  EXPECT_TRUE(accessibility_delegate_.enabled());
104 }
105 
106 TEST_F(AccessibilityBridgeTest, UpdatesNodeRoles) {
108 
110  node0.id = 0;
111  node0.flags |= static_cast<int>(flutter::SemanticsFlags::kIsButton);
112  node0.childrenInTraversalOrder = {1, 2, 3, 4};
113  node0.childrenInHitTestOrder = {1, 2, 3, 4};
114  updates.emplace(0, node0);
115 
117  node1.id = 1;
118  node1.flags |= static_cast<int>(flutter::SemanticsFlags::kIsHeader);
119  node1.childrenInTraversalOrder = {};
120  node1.childrenInHitTestOrder = {};
121  updates.emplace(1, node1);
122 
124  node2.id = 2;
125  node2.flags |= static_cast<int>(flutter::SemanticsFlags::kIsImage);
126  node2.childrenInTraversalOrder = {};
127  node2.childrenInHitTestOrder = {};
128  updates.emplace(2, node2);
129 
131  node3.id = 3;
132  node3.flags |= static_cast<int>(flutter::SemanticsFlags::kIsTextField);
133  node3.childrenInTraversalOrder = {};
134  node3.childrenInHitTestOrder = {};
135  updates.emplace(3, node3);
136 
138  node4.childrenInTraversalOrder = {};
139  node4.childrenInHitTestOrder = {};
140  node4.id = 4;
141  node4.flags |= static_cast<int>(flutter::SemanticsFlags::kIsSlider);
142  updates.emplace(4, node4);
143 
144  accessibility_bridge_->AddSemanticsNodeUpdate(std::move(updates), 1.f);
145  RunLoopUntilIdle();
146 
147  std::unordered_map<uint32_t, fuchsia::accessibility::semantics::Role>
148  roles_by_node_id = {
149  {0u, fuchsia::accessibility::semantics::Role::BUTTON},
150  {1u, fuchsia::accessibility::semantics::Role::HEADER},
151  {2u, fuchsia::accessibility::semantics::Role::IMAGE},
152  {3u, fuchsia::accessibility::semantics::Role::TEXT_FIELD},
153  {4u, fuchsia::accessibility::semantics::Role::SLIDER}};
154 
155  EXPECT_EQ(0, semantics_manager_.DeleteCount());
156  EXPECT_EQ(1, semantics_manager_.UpdateCount());
157  EXPECT_EQ(1, semantics_manager_.CommitCount());
158  EXPECT_EQ(5U, semantics_manager_.LastUpdatedNodes().size());
159  for (const auto& node : semantics_manager_.LastUpdatedNodes()) {
160  ExpectNodeHasRole(node, roles_by_node_id);
161  }
162 
163  EXPECT_FALSE(semantics_manager_.DeleteOverflowed());
164  EXPECT_FALSE(semantics_manager_.UpdateOverflowed());
165 }
166 
167 TEST_F(AccessibilityBridgeTest, DeletesChildrenTransitively) {
168  // Test that when a node is deleted, so are its transitive children.
170  node2.id = 2;
171 
173  node1.id = 1;
174  node1.childrenInTraversalOrder = {2};
175  node1.childrenInHitTestOrder = {2};
176 
178  node0.id = 0;
179  node0.childrenInTraversalOrder = {1};
180  node0.childrenInHitTestOrder = {1};
181 
182  accessibility_bridge_->AddSemanticsNodeUpdate(
183  {
184  {0, node0},
185  {1, node1},
186  {2, node2},
187  },
188  1.f);
189  RunLoopUntilIdle();
190 
191  EXPECT_EQ(0, semantics_manager_.DeleteCount());
192  EXPECT_EQ(1, semantics_manager_.UpdateCount());
193  EXPECT_EQ(1, semantics_manager_.CommitCount());
194  EXPECT_EQ(3U, semantics_manager_.LastUpdatedNodes().size());
195  EXPECT_EQ(0U, semantics_manager_.LastDeletedNodeIds().size());
196  EXPECT_FALSE(semantics_manager_.DeleteOverflowed());
197  EXPECT_FALSE(semantics_manager_.UpdateOverflowed());
198 
199  // Remove the children
200  node0.childrenInTraversalOrder.clear();
201  node0.childrenInHitTestOrder.clear();
202  accessibility_bridge_->AddSemanticsNodeUpdate(
203  {
204  {0, node0},
205  },
206  1.f);
207  RunLoopUntilIdle();
208 
209  EXPECT_EQ(1, semantics_manager_.DeleteCount());
210  EXPECT_EQ(2, semantics_manager_.UpdateCount());
211  EXPECT_EQ(2, semantics_manager_.CommitCount());
212  EXPECT_EQ(1U, semantics_manager_.LastUpdatedNodes().size());
213  ASSERT_EQ(std::vector<uint32_t>({1, 2}),
214  semantics_manager_.LastDeletedNodeIds());
215  EXPECT_FALSE(semantics_manager_.DeleteOverflowed());
216  EXPECT_FALSE(semantics_manager_.UpdateOverflowed());
217 }
218 
219 TEST_F(AccessibilityBridgeTest, PopulatesRoleButton) {
221  node0.id = 0;
222  node0.flags = static_cast<int>(flutter::SemanticsFlags::kIsButton);
223 
224  accessibility_bridge_->AddSemanticsNodeUpdate({{0, node0}}, 1.f);
225  RunLoopUntilIdle();
226 
227  EXPECT_EQ(1U, semantics_manager_.LastUpdatedNodes().size());
228  const auto& fuchsia_node = semantics_manager_.LastUpdatedNodes().at(0u);
229  EXPECT_EQ(fuchsia_node.node_id(), static_cast<unsigned int>(node0.id));
230  EXPECT_TRUE(fuchsia_node.has_role());
231  EXPECT_EQ(fuchsia_node.role(),
232  fuchsia::accessibility::semantics::Role::BUTTON);
233 }
234 
235 TEST_F(AccessibilityBridgeTest, PopulatesRoleImage) {
237  node0.id = 0;
238  node0.flags = static_cast<int>(flutter::SemanticsFlags::kIsImage);
239 
240  accessibility_bridge_->AddSemanticsNodeUpdate({{0, node0}}, 1.f);
241  RunLoopUntilIdle();
242 
243  EXPECT_EQ(1U, semantics_manager_.LastUpdatedNodes().size());
244  const auto& fuchsia_node = semantics_manager_.LastUpdatedNodes().at(0u);
245  EXPECT_EQ(fuchsia_node.node_id(), static_cast<unsigned int>(node0.id));
246  EXPECT_TRUE(fuchsia_node.has_role());
247  EXPECT_EQ(fuchsia_node.role(),
248  fuchsia::accessibility::semantics::Role::IMAGE);
249 }
250 
251 TEST_F(AccessibilityBridgeTest, PopulatesRoleSlider) {
253  node0.id = 0;
254  node0.actions |= static_cast<int>(flutter::SemanticsAction::kIncrease);
255 
256  accessibility_bridge_->AddSemanticsNodeUpdate({{0, node0}}, 1.f);
257  RunLoopUntilIdle();
258 
259  EXPECT_EQ(1U, semantics_manager_.LastUpdatedNodes().size());
260  const auto& fuchsia_node = semantics_manager_.LastUpdatedNodes().at(0u);
261  EXPECT_EQ(fuchsia_node.node_id(), static_cast<unsigned int>(node0.id));
262  EXPECT_TRUE(fuchsia_node.has_role());
263  EXPECT_EQ(fuchsia_node.role(),
264  fuchsia::accessibility::semantics::Role::SLIDER);
265 }
266 
267 TEST_F(AccessibilityBridgeTest, PopulatesRoleHeader) {
269  node0.id = 0;
270  node0.flags = static_cast<int>(flutter::SemanticsFlags::kIsHeader);
271 
272  accessibility_bridge_->AddSemanticsNodeUpdate({{0, node0}}, 1.f);
273  RunLoopUntilIdle();
274 
275  EXPECT_EQ(1U, semantics_manager_.LastUpdatedNodes().size());
276  const auto& fuchsia_node = semantics_manager_.LastUpdatedNodes().at(0u);
277  EXPECT_EQ(fuchsia_node.node_id(), static_cast<unsigned int>(node0.id));
278  EXPECT_TRUE(fuchsia_node.has_role());
279  EXPECT_EQ(fuchsia_node.role(),
280  fuchsia::accessibility::semantics::Role::HEADER);
281 }
282 
283 TEST_F(AccessibilityBridgeTest, PopulatesCheckedState) {
285  node0.id = 0;
286  // HasCheckedState = true
287  // IsChecked = true
288  // IsSelected = false
289  node0.flags |= static_cast<int>(flutter::SemanticsFlags::kHasCheckedState);
290  node0.flags |= static_cast<int>(flutter::SemanticsFlags::kIsChecked);
291  node0.value = "value";
292 
293  accessibility_bridge_->AddSemanticsNodeUpdate({{0, node0}}, 1.f);
294  RunLoopUntilIdle();
295 
296  EXPECT_EQ(0, semantics_manager_.DeleteCount());
297  EXPECT_EQ(1, semantics_manager_.UpdateCount());
298  EXPECT_EQ(1, semantics_manager_.CommitCount());
299  EXPECT_EQ(1U, semantics_manager_.LastUpdatedNodes().size());
300  const auto& fuchsia_node = semantics_manager_.LastUpdatedNodes().at(0u);
301  EXPECT_EQ(fuchsia_node.node_id(), static_cast<unsigned int>(node0.id));
302  EXPECT_TRUE(fuchsia_node.has_states());
303  const auto& states = fuchsia_node.states();
304  EXPECT_TRUE(states.has_checked_state());
305  EXPECT_EQ(states.checked_state(),
306  fuchsia::accessibility::semantics::CheckedState::CHECKED);
307  EXPECT_TRUE(states.has_selected());
308  EXPECT_FALSE(states.selected());
309  EXPECT_TRUE(states.has_value());
310  EXPECT_EQ(states.value(), node0.value);
311 
312  EXPECT_FALSE(semantics_manager_.DeleteOverflowed());
313  EXPECT_FALSE(semantics_manager_.UpdateOverflowed());
314 }
315 
316 TEST_F(AccessibilityBridgeTest, PopulatesSelectedState) {
318  node0.id = 0;
319  // HasCheckedState = false
320  // IsChecked = false
321  // IsSelected = true
322  node0.flags = static_cast<int>(flutter::SemanticsFlags::kIsSelected);
323 
324  accessibility_bridge_->AddSemanticsNodeUpdate({{0, node0}}, 1.f);
325  RunLoopUntilIdle();
326 
327  EXPECT_EQ(0, semantics_manager_.DeleteCount());
328  EXPECT_EQ(1, semantics_manager_.UpdateCount());
329  EXPECT_EQ(1, semantics_manager_.CommitCount());
330  EXPECT_EQ(1U, semantics_manager_.LastUpdatedNodes().size());
331  const auto& fuchsia_node = semantics_manager_.LastUpdatedNodes().at(0u);
332  EXPECT_EQ(fuchsia_node.node_id(), static_cast<unsigned int>(node0.id));
333  EXPECT_TRUE(fuchsia_node.has_states());
334  const auto& states = fuchsia_node.states();
335  EXPECT_TRUE(states.has_checked_state());
336  EXPECT_EQ(states.checked_state(),
337  fuchsia::accessibility::semantics::CheckedState::NONE);
338  EXPECT_TRUE(states.has_selected());
339  EXPECT_TRUE(states.selected());
340 
341  EXPECT_FALSE(semantics_manager_.DeleteOverflowed());
342  EXPECT_FALSE(semantics_manager_.UpdateOverflowed());
343 }
344 
345 TEST_F(AccessibilityBridgeTest, ApplyViewPixelRatioToRoot) {
347  node0.id = 0;
348  node0.flags = static_cast<int>(flutter::SemanticsFlags::kIsSelected);
349 
350  accessibility_bridge_->AddSemanticsNodeUpdate({{0, node0}}, 1.25f);
351  RunLoopUntilIdle();
352  const auto& fuchsia_node = semantics_manager_.LastUpdatedNodes().at(0u);
353  EXPECT_EQ(fuchsia_node.node_id(), static_cast<unsigned int>(node0.id));
354  EXPECT_EQ(fuchsia_node.transform().matrix[0], 0.8f);
355  EXPECT_EQ(fuchsia_node.transform().matrix[5], 0.8f);
356  EXPECT_EQ(fuchsia_node.transform().matrix[10], 1.f);
357 }
358 
359 TEST_F(AccessibilityBridgeTest, DoesNotPopulatesHiddenState) {
360  // Flutter's notion of a hidden node is different from Fuchsia's hidden node.
361  // This test make sures that this state does not get sent.
363  node0.id = 0;
364  // HasCheckedState = false
365  // IsChecked = false
366  // IsSelected = false
367  // IsHidden = true
368  node0.flags = static_cast<int>(flutter::SemanticsFlags::kIsHidden);
369 
370  accessibility_bridge_->AddSemanticsNodeUpdate({{0, node0}}, 1.f);
371  RunLoopUntilIdle();
372 
373  EXPECT_EQ(0, semantics_manager_.DeleteCount());
374  EXPECT_EQ(1, semantics_manager_.UpdateCount());
375  EXPECT_EQ(1, semantics_manager_.CommitCount());
376  EXPECT_EQ(1u, semantics_manager_.LastUpdatedNodes().size());
377  const auto& fuchsia_node = semantics_manager_.LastUpdatedNodes().at(0u);
378  EXPECT_EQ(fuchsia_node.node_id(), static_cast<unsigned int>(node0.id));
379  EXPECT_TRUE(fuchsia_node.has_states());
380  const auto& states = fuchsia_node.states();
381  EXPECT_TRUE(states.has_checked_state());
382  EXPECT_EQ(states.checked_state(),
383  fuchsia::accessibility::semantics::CheckedState::NONE);
384  EXPECT_TRUE(states.has_selected());
385  EXPECT_FALSE(states.selected());
386  EXPECT_FALSE(states.has_hidden());
387 
388  EXPECT_FALSE(semantics_manager_.DeleteOverflowed());
389  EXPECT_FALSE(semantics_manager_.UpdateOverflowed());
390 }
391 
392 TEST_F(AccessibilityBridgeTest, PopulatesActions) {
394  node0.id = 0;
395  node0.actions |= static_cast<int>(flutter::SemanticsAction::kTap);
396  node0.actions |= static_cast<int>(flutter::SemanticsAction::kLongPress);
397  node0.actions |= static_cast<int>(flutter::SemanticsAction::kShowOnScreen);
398  node0.actions |= static_cast<int>(flutter::SemanticsAction::kIncrease);
399  node0.actions |= static_cast<int>(flutter::SemanticsAction::kDecrease);
400 
401  accessibility_bridge_->AddSemanticsNodeUpdate({{0, node0}}, 1.f);
402  RunLoopUntilIdle();
403 
404  EXPECT_EQ(0, semantics_manager_.DeleteCount());
405  EXPECT_EQ(1, semantics_manager_.UpdateCount());
406  EXPECT_EQ(1, semantics_manager_.CommitCount());
407  EXPECT_EQ(1u, semantics_manager_.LastUpdatedNodes().size());
408  const auto& fuchsia_node = semantics_manager_.LastUpdatedNodes().at(0u);
409  EXPECT_EQ(fuchsia_node.actions().size(), 5u);
410  EXPECT_EQ(fuchsia_node.actions().at(0u),
411  fuchsia::accessibility::semantics::Action::DEFAULT);
412  EXPECT_EQ(fuchsia_node.actions().at(1u),
413  fuchsia::accessibility::semantics::Action::SECONDARY);
414  EXPECT_EQ(fuchsia_node.actions().at(2u),
415  fuchsia::accessibility::semantics::Action::SHOW_ON_SCREEN);
416  EXPECT_EQ(fuchsia_node.actions().at(3u),
417  fuchsia::accessibility::semantics::Action::INCREMENT);
418  EXPECT_EQ(fuchsia_node.actions().at(4u),
419  fuchsia::accessibility::semantics::Action::DECREMENT);
420 }
421 
422 TEST_F(AccessibilityBridgeTest, TruncatesLargeLabel) {
423  // Test that labels which are too long are truncated.
425  node0.id = 0;
426 
428  node1.id = 1;
429 
430  flutter::SemanticsNode bad_node;
431  bad_node.id = 2;
432  bad_node.label =
433  std::string(fuchsia::accessibility::semantics::MAX_LABEL_SIZE + 1, '2');
434 
435  node0.childrenInTraversalOrder = {1, 2};
436  node0.childrenInHitTestOrder = {1, 2};
437 
438  accessibility_bridge_->AddSemanticsNodeUpdate(
439  {
440  {0, node0},
441  {1, node1},
442  {2, bad_node},
443  },
444  1.f);
445  RunLoopUntilIdle();
446 
447  // Nothing to delete, but we should have broken
448  EXPECT_EQ(0, semantics_manager_.DeleteCount());
449  EXPECT_EQ(1, semantics_manager_.UpdateCount());
450  EXPECT_EQ(1, semantics_manager_.CommitCount());
451  EXPECT_EQ(3U, semantics_manager_.LastUpdatedNodes().size());
452  auto trimmed_node =
453  std::find_if(semantics_manager_.LastUpdatedNodes().begin(),
454  semantics_manager_.LastUpdatedNodes().end(),
455  [id = static_cast<uint32_t>(bad_node.id)](
456  fuchsia::accessibility::semantics::Node const& node) {
457  return node.node_id() == id;
458  });
459  ASSERT_NE(trimmed_node, semantics_manager_.LastUpdatedNodes().end());
460  ASSERT_TRUE(trimmed_node->has_attributes());
461  EXPECT_EQ(
462  trimmed_node->attributes().label(),
463  std::string(fuchsia::accessibility::semantics::MAX_LABEL_SIZE, '2'));
464  EXPECT_FALSE(semantics_manager_.DeleteOverflowed());
465  EXPECT_FALSE(semantics_manager_.UpdateOverflowed());
466 }
467 
468 TEST_F(AccessibilityBridgeTest, TruncatesLargeValue) {
469  // Test that values which are too long are truncated.
471  node0.id = 0;
472 
474  node1.id = 1;
475 
476  flutter::SemanticsNode bad_node;
477  bad_node.id = 2;
478  bad_node.value =
479  std::string(fuchsia::accessibility::semantics::MAX_VALUE_SIZE + 1, '2');
480 
481  node0.childrenInTraversalOrder = {1, 2};
482  node0.childrenInHitTestOrder = {1, 2};
483 
484  accessibility_bridge_->AddSemanticsNodeUpdate(
485  {
486  {0, node0},
487  {1, node1},
488  {2, bad_node},
489  },
490  1.f);
491  RunLoopUntilIdle();
492 
493  EXPECT_EQ(0, semantics_manager_.DeleteCount());
494  EXPECT_EQ(1, semantics_manager_.UpdateCount());
495  EXPECT_EQ(1, semantics_manager_.CommitCount());
496  EXPECT_EQ(3U, semantics_manager_.LastUpdatedNodes().size());
497  auto trimmed_node =
498  std::find_if(semantics_manager_.LastUpdatedNodes().begin(),
499  semantics_manager_.LastUpdatedNodes().end(),
500  [id = static_cast<uint32_t>(bad_node.id)](
501  fuchsia::accessibility::semantics::Node const& node) {
502  return node.node_id() == id;
503  });
504  ASSERT_NE(trimmed_node, semantics_manager_.LastUpdatedNodes().end());
505  ASSERT_TRUE(trimmed_node->has_states());
506  EXPECT_EQ(
507  trimmed_node->states().value(),
508  std::string(fuchsia::accessibility::semantics::MAX_VALUE_SIZE, '2'));
509  EXPECT_FALSE(semantics_manager_.DeleteOverflowed());
510  EXPECT_FALSE(semantics_manager_.UpdateOverflowed());
511 }
512 
513 TEST_F(AccessibilityBridgeTest, SplitsLargeUpdates) {
514  // Test that labels which are too long are truncated.
516  node0.id = 0;
517 
519  node1.id = 1;
520  node1.label =
521  std::string(fuchsia::accessibility::semantics::MAX_LABEL_SIZE, '1');
522 
524  node2.id = 2;
525  node2.label = "2";
526 
528  node3.id = 3;
529  node3.label = "3";
530 
532  node4.id = 4;
533  node4.value =
534  std::string(fuchsia::accessibility::semantics::MAX_VALUE_SIZE, '4');
535 
536  node0.childrenInTraversalOrder = {1, 2};
537  node0.childrenInHitTestOrder = {1, 2};
538  node1.childrenInTraversalOrder = {3, 4};
539  node1.childrenInHitTestOrder = {3, 4};
540 
541  accessibility_bridge_->AddSemanticsNodeUpdate(
542  {
543  {0, node0},
544  {1, node1},
545  {2, node2},
546  {3, node3},
547  {4, node4},
548  },
549  1.f);
550  RunLoopUntilIdle();
551 
552  // Nothing to delete, but we should have broken into groups (4, 3, 2), (1, 0)
553  EXPECT_EQ(0, semantics_manager_.DeleteCount());
554  EXPECT_EQ(2, semantics_manager_.UpdateCount());
555  EXPECT_EQ(1, semantics_manager_.CommitCount());
556  EXPECT_EQ(2U, semantics_manager_.LastUpdatedNodes().size());
557  EXPECT_FALSE(semantics_manager_.DeleteOverflowed());
558  EXPECT_FALSE(semantics_manager_.UpdateOverflowed());
559 }
560 
562  // Test that cycles don't cause fatal error.
564  node0.id = 0;
565  node0.childrenInTraversalOrder.push_back(0);
566  node0.childrenInHitTestOrder.push_back(0);
567  accessibility_bridge_->AddSemanticsNodeUpdate(
568  {
569  {0, node0},
570  },
571  1.f);
572  RunLoopUntilIdle();
573 
574  EXPECT_EQ(0, semantics_manager_.DeleteCount());
575  EXPECT_EQ(1, semantics_manager_.UpdateCount());
576  EXPECT_EQ(1, semantics_manager_.CommitCount());
577  EXPECT_FALSE(semantics_manager_.DeleteOverflowed());
578  EXPECT_FALSE(semantics_manager_.UpdateOverflowed());
579 
580  node0.childrenInTraversalOrder = {0, 1};
581  node0.childrenInHitTestOrder = {0, 1};
583  node1.id = 1;
584  node1.childrenInTraversalOrder = {0};
585  node1.childrenInHitTestOrder = {0};
586  accessibility_bridge_->AddSemanticsNodeUpdate(
587  {
588  {0, node0},
589  {1, node1},
590  },
591  1.f);
592  RunLoopUntilIdle();
593 
594  EXPECT_EQ(0, semantics_manager_.DeleteCount());
595  EXPECT_EQ(2, semantics_manager_.UpdateCount());
596  EXPECT_EQ(2, semantics_manager_.CommitCount());
597  EXPECT_FALSE(semantics_manager_.DeleteOverflowed());
598  EXPECT_FALSE(semantics_manager_.UpdateOverflowed());
599 }
600 
601 TEST_F(AccessibilityBridgeTest, BatchesLargeMessages) {
602  // Tests that messages get batched appropriately.
604  node0.id = 0;
605 
607 
608  const int32_t child_nodes = 100;
609  const int32_t leaf_nodes = 100;
610  for (int32_t i = 1; i < child_nodes + 1; i++) {
612  node.id = i;
613  node0.childrenInTraversalOrder.push_back(i);
614  node0.childrenInHitTestOrder.push_back(i);
615  for (int32_t j = 0; j < leaf_nodes; j++) {
616  flutter::SemanticsNode leaf_node;
617  int id = (i * child_nodes) + ((j + 1) * leaf_nodes);
618  leaf_node.id = id;
619  leaf_node.label = "A relatively simple label";
620  node.childrenInTraversalOrder.push_back(id);
621  node.childrenInHitTestOrder.push_back(id);
622  update.insert(std::make_pair(id, std::move(leaf_node)));
623  }
624  update.insert(std::make_pair(i, std::move(node)));
625  }
626 
627  update.insert(std::make_pair(0, std::move(node0)));
628  accessibility_bridge_->AddSemanticsNodeUpdate(update, 1.f);
629  RunLoopUntilIdle();
630 
631  EXPECT_EQ(0, semantics_manager_.DeleteCount());
632  EXPECT_TRUE(6 <= semantics_manager_.UpdateCount() &&
633  semantics_manager_.UpdateCount() <= 10);
634  EXPECT_EQ(1, semantics_manager_.CommitCount());
635  EXPECT_FALSE(semantics_manager_.DeleteOverflowed());
636  EXPECT_FALSE(semantics_manager_.UpdateOverflowed());
637 
638  int next_update_count = semantics_manager_.UpdateCount() + 1;
639  // Remove the children
640  node0.childrenInTraversalOrder.clear();
641  node0.childrenInHitTestOrder.clear();
642  accessibility_bridge_->AddSemanticsNodeUpdate(
643  {
644  {0, node0},
645  },
646  1.f);
647  RunLoopUntilIdle();
648 
649  EXPECT_EQ(1, semantics_manager_.DeleteCount());
650  EXPECT_EQ(next_update_count, semantics_manager_.UpdateCount());
651  EXPECT_EQ(2, semantics_manager_.CommitCount());
652  EXPECT_FALSE(semantics_manager_.DeleteOverflowed());
653  EXPECT_FALSE(semantics_manager_.UpdateOverflowed());
654 }
655 
658  node0.id = 0;
659  node0.rect.setLTRB(0, 0, 100, 100);
660 
662  node1.id = 1;
663  node1.rect.setLTRB(10, 10, 20, 20);
664 
666  node2.id = 2;
667  node2.rect.setLTRB(25, 10, 45, 20);
668 
670  node3.id = 3;
671  node3.rect.setLTRB(10, 25, 20, 45);
672 
674  node4.id = 4;
675  node4.rect.setLTRB(10, 10, 20, 20);
676  node4.transform.setTranslate(20, 20, 0);
677 
678  node0.childrenInTraversalOrder = {1, 2, 3, 4};
679  node0.childrenInHitTestOrder = {1, 2, 3, 4};
680 
681  accessibility_bridge_->AddSemanticsNodeUpdate(
682  {
683  {0, node0},
684  {1, node1},
685  {2, node2},
686  {3, node3},
687  {4, node4},
688  },
689  1.f);
690  RunLoopUntilIdle();
691 
692  uint32_t hit_node_id;
693  auto callback = [&hit_node_id](fuchsia::accessibility::semantics::Hit hit) {
694  EXPECT_TRUE(hit.has_node_id());
695  hit_node_id = hit.node_id();
696  };
697 
698  // Nodes are:
699  // ----------
700  // | 1 2 |
701  // | 3 4 |
702  // ----------
703 
704  accessibility_bridge_->HitTest({1, 1}, callback);
705  EXPECT_EQ(hit_node_id, 0u);
706  accessibility_bridge_->HitTest({15, 15}, callback);
707  EXPECT_EQ(hit_node_id, 1u);
708  accessibility_bridge_->HitTest({30, 15}, callback);
709  EXPECT_EQ(hit_node_id, 2u);
710  accessibility_bridge_->HitTest({15, 30}, callback);
711  EXPECT_EQ(hit_node_id, 3u);
712  accessibility_bridge_->HitTest({30, 30}, callback);
713  EXPECT_EQ(hit_node_id, 4u);
714 }
715 
716 TEST_F(AccessibilityBridgeTest, HitTestOverlapping) {
717  // Tests that the first node in hit test order wins, even if a later node
718  // would be able to recieve the hit.
720  node0.id = 0;
721  node0.rect.setLTRB(0, 0, 100, 100);
722 
724  node1.id = 1;
725  node1.rect.setLTRB(0, 0, 100, 100);
726 
728  node2.id = 2;
729  node2.rect.setLTRB(25, 10, 45, 20);
730 
731  node0.childrenInTraversalOrder = {1, 2};
732  node0.childrenInHitTestOrder = {2, 1};
733 
734  accessibility_bridge_->AddSemanticsNodeUpdate(
735  {
736  {0, node0},
737  {1, node1},
738  {2, node2},
739  },
740  1.f);
741  RunLoopUntilIdle();
742 
743  uint32_t hit_node_id;
744  auto callback = [&hit_node_id](fuchsia::accessibility::semantics::Hit hit) {
745  EXPECT_TRUE(hit.has_node_id());
746  hit_node_id = hit.node_id();
747  };
748 
749  accessibility_bridge_->HitTest({30, 15}, callback);
750  EXPECT_EQ(hit_node_id, 2u);
751 }
752 
755  node0.id = 0;
756 
758  node1.id = 1;
759 
760  node0.childrenInTraversalOrder = {1};
761  node0.childrenInHitTestOrder = {1};
762 
763  accessibility_bridge_->AddSemanticsNodeUpdate(
764  {
765  {0, node0},
766  {1, node1},
767  },
768  1.f);
769  RunLoopUntilIdle();
770 
771  auto handled_callback = [](bool handled) { EXPECT_TRUE(handled); };
772  auto unhandled_callback = [](bool handled) { EXPECT_FALSE(handled); };
773 
774  accessibility_bridge_->OnAccessibilityActionRequested(
775  0u, fuchsia::accessibility::semantics::Action::DEFAULT, handled_callback);
776  EXPECT_EQ(accessibility_delegate_.actions.size(), 1u);
777  EXPECT_EQ(accessibility_delegate_.actions.back(),
778  std::make_pair(0, flutter::SemanticsAction::kTap));
779 
780  accessibility_bridge_->OnAccessibilityActionRequested(
781  0u, fuchsia::accessibility::semantics::Action::SECONDARY,
782  handled_callback);
783  EXPECT_EQ(accessibility_delegate_.actions.size(), 2u);
784  EXPECT_EQ(accessibility_delegate_.actions.back(),
785  std::make_pair(0, flutter::SemanticsAction::kLongPress));
786 
787  accessibility_bridge_->OnAccessibilityActionRequested(
788  0u, fuchsia::accessibility::semantics::Action::SET_FOCUS,
789  unhandled_callback);
790  EXPECT_EQ(accessibility_delegate_.actions.size(), 2u);
791 
792  accessibility_bridge_->OnAccessibilityActionRequested(
793  0u, fuchsia::accessibility::semantics::Action::SET_VALUE,
794  unhandled_callback);
795  EXPECT_EQ(accessibility_delegate_.actions.size(), 2u);
796 
797  accessibility_bridge_->OnAccessibilityActionRequested(
798  0u, fuchsia::accessibility::semantics::Action::SHOW_ON_SCREEN,
799  handled_callback);
800  EXPECT_EQ(accessibility_delegate_.actions.size(), 3u);
801  EXPECT_EQ(accessibility_delegate_.actions.back(),
802  std::make_pair(0, flutter::SemanticsAction::kShowOnScreen));
803 
804  accessibility_bridge_->OnAccessibilityActionRequested(
805  2u, fuchsia::accessibility::semantics::Action::DEFAULT,
806  unhandled_callback);
807  EXPECT_EQ(accessibility_delegate_.actions.size(), 3u);
808 
809  accessibility_bridge_->OnAccessibilityActionRequested(
810  0u, fuchsia::accessibility::semantics::Action::INCREMENT,
811  handled_callback);
812  EXPECT_EQ(accessibility_delegate_.actions.size(), 4u);
813  EXPECT_EQ(accessibility_delegate_.actions.back(),
814  std::make_pair(0, flutter::SemanticsAction::kIncrease));
815 
816  accessibility_bridge_->OnAccessibilityActionRequested(
817  0u, fuchsia::accessibility::semantics::Action::DECREMENT,
818  handled_callback);
819  EXPECT_EQ(accessibility_delegate_.actions.size(), 5u);
820  EXPECT_EQ(accessibility_delegate_.actions.back(),
821  std::make_pair(0, flutter::SemanticsAction::kDecrease));
822 }
823 } // namespace flutter_runner_test
std::vector< int32_t > childrenInHitTestOrder
TEST_F(AccessibilityBridgeTest, RegistersViewRef)
void DispatchSemanticsAction(int32_t node_id, flutter::SemanticsAction action) override
std::unordered_map< int32_t, SemanticsNode > SemanticsNodeUpdates
std::vector< int32_t > childrenInTraversalOrder
std::unique_ptr< flutter_runner::AccessibilityBridge > accessibility_bridge_
SemanticsAction action
int32_t id
std::vector< std::pair< int32_t, flutter::SemanticsAction > > actions