Flutter Engine
 
Loading...
Searching...
No Matches
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
6
7#include <fuchsia/ui/views/cpp/fidl.h>
8#include <lib/async-loop/cpp/loop.h>
9#include <lib/async-loop/default.h>
10#include <lib/async/cpp/executor.h>
11#include <lib/fidl/cpp/binding_set.h>
12#include <lib/fidl/cpp/interface_request.h>
13#include <lib/inspect/cpp/hierarchy.h>
14#include <lib/inspect/cpp/inspector.h>
15#include <lib/inspect/cpp/reader.h>
16#include <lib/sys/cpp/testing/service_directory_provider.h>
17#include <lib/zx/eventpair.h>
18#include <zircon/status.h>
19#include <zircon/types.h>
20
21#include <memory>
22
24#include "gtest/gtest.h"
25
27
29
30namespace {
31
32void ExpectNodeHasRole(
33 const fuchsia::accessibility::semantics::Node& node,
34 const std::unordered_map<uint32_t, fuchsia::accessibility::semantics::Role>
35 roles_by_node_id) {
36 ASSERT_TRUE(node.has_node_id());
37 ASSERT_NE(roles_by_node_id.find(node.node_id()), roles_by_node_id.end());
38 EXPECT_TRUE(node.has_role());
39 EXPECT_EQ(node.role(), roles_by_node_id.at(node.node_id()));
40}
41
42} // namespace
43
45 public:
46 void SetSemanticsEnabled(bool enabled) { enabled_ = enabled; }
47 void DispatchSemanticsAction(int32_t node_id,
49 actions.push_back(std::make_pair(node_id, action));
50 }
51
52 bool enabled() { return enabled_; }
53 std::vector<std::pair<int32_t, flutter::SemanticsAction>> actions;
54
55 private:
56 bool enabled_;
57};
58
59class AccessibilityBridgeTest : public testing::Test {
60 public:
62 : loop_(&kAsyncLoopConfigAttachToCurrentThread),
63 services_provider_(loop_.dispatcher()),
64 executor_(loop_.dispatcher()) {
65 services_provider_.AddService(
66 semantics_manager_.GetHandler(loop_.dispatcher()),
67 SemanticsManager::Name_);
68 }
69
71 loop_.RunUntilIdle();
72 loop_.ResetQuit();
73 }
74
75 void RunPromiseToCompletion(fpromise::promise<> promise) {
76 bool done = false;
77 executor_.schedule_task(
78 std::move(promise).and_then([&done]() { done = true; }));
79 while (loop_.GetState() == ASYNC_LOOP_RUNNABLE) {
80 if (done) {
81 loop_.ResetQuit();
82 return;
83 }
84
85 loop_.Run(zx::deadline_after(zx::duration::infinite()), true);
86 }
87 loop_.ResetQuit();
88 }
89
90 protected:
91 void SetUp() override {
92 // Connect to SemanticsManager service.
93 fuchsia::accessibility::semantics::SemanticsManagerHandle semantics_manager;
94 zx_status_t semantics_status =
95 services_provider_.service_directory()
96 ->Connect<fuchsia::accessibility::semantics::SemanticsManager>(
97 semantics_manager.NewRequest());
98 if (semantics_status != ZX_OK) {
99 FML_LOG(WARNING)
100 << "fuchsia::accessibility::semantics::SemanticsManager connection "
101 "failed: "
102 << zx_status_get_string(semantics_status);
103 }
104
106 inspector_ = std::make_unique<inspect::Inspector>();
108 set_semantics_enabled_callback = [this](bool enabled) {
110 };
112 dispatch_semantics_action_callback =
113 [this](int32_t node_id, flutter::SemanticsAction action) {
115 };
116
117 fuchsia::ui::views::ViewRefControl view_ref_control;
118 fuchsia::ui::views::ViewRef view_ref;
119 auto status = zx::eventpair::create(
120 /*options*/ 0u, &view_ref_control.reference, &view_ref.reference);
121 ASSERT_EQ(status, ZX_OK);
122 view_ref_control.reference.replace(
123 ZX_DEFAULT_EVENTPAIR_RIGHTS & (~ZX_RIGHT_DUPLICATE),
124 &view_ref_control.reference);
125 view_ref.reference.replace(ZX_RIGHTS_BASIC, &view_ref.reference);
127 std::make_unique<flutter_runner::AccessibilityBridge>(
128 std::move(set_semantics_enabled_callback),
129 std::move(dispatch_semantics_action_callback),
130 std::move(semantics_manager), std::move(view_ref),
131 inspector_->GetRoot().CreateChild("test_node"));
132
134 }
135
136 void TearDown() override { semantics_manager_.ResetTree(); }
137
140 std::unique_ptr<flutter_runner::AccessibilityBridge> accessibility_bridge_;
141 // Required to verify inspect metrics.
142 std::unique_ptr<inspect::Inspector> inspector_;
143
144 private:
145 async::Loop loop_;
146 sys::testing::ServiceDirectoryProvider services_provider_;
147 // Required to retrieve inspect metrics.
148 async::Executor executor_;
149};
150
151TEST_F(AccessibilityBridgeTest, RegistersViewRef) {
152 EXPECT_TRUE(semantics_manager_.RegisterViewCalled());
153}
154
156 EXPECT_FALSE(accessibility_delegate_.enabled());
157 std::unique_ptr<fuchsia::accessibility::semantics::SemanticListener> listener(
158 accessibility_bridge_.release());
159 listener->OnSemanticsModeChanged(true, nullptr);
160 EXPECT_TRUE(accessibility_delegate_.enabled());
161}
162
164 accessibility_bridge_->RequestAnnounce("message");
165 RunLoopUntilIdle();
166
167 auto& last_events = semantics_manager_.GetLastEvents();
168 ASSERT_EQ(last_events.size(), 1u);
169 ASSERT_TRUE(last_events[0].is_announce());
170 EXPECT_EQ(last_events[0].announce().message(), "message");
171}
172
173TEST_F(AccessibilityBridgeTest, PopulatesIsKeyboardKeyAttribute) {
175 node0.id = 0;
176 node0.flags.isKeyboardKey = true;
177
178 accessibility_bridge_->AddSemanticsNodeUpdate({{0, node0}}, 1.f);
179 RunLoopUntilIdle();
180
181 EXPECT_EQ(1U, semantics_manager_.LastUpdatedNodes().size());
182 const auto& fuchsia_node = semantics_manager_.LastUpdatedNodes().at(0u);
183 EXPECT_EQ(fuchsia_node.node_id(), static_cast<unsigned int>(node0.id));
184 EXPECT_TRUE(fuchsia_node.has_attributes());
185 EXPECT_TRUE(fuchsia_node.attributes().is_keyboard_key());
186}
187
188TEST_F(AccessibilityBridgeTest, UpdatesNodeRoles) {
190
192 node0.id = 0;
193 node0.flags.isButton = true;
194 node0.childrenInTraversalOrder = {1, 2, 3, 4, 5, 6, 7, 8};
195 node0.childrenInHitTestOrder = {1, 2, 3, 4, 5, 6, 7, 8};
196 updates.emplace(0, node0);
197
199 node1.id = 1;
200 node1.flags.isHeader = true;
201 node1.childrenInTraversalOrder = {};
202 node1.childrenInHitTestOrder = {};
203 updates.emplace(1, node1);
204
206 node2.id = 2;
207 node2.flags.isImage = true;
208 node2.childrenInTraversalOrder = {};
209 node2.childrenInHitTestOrder = {};
210 updates.emplace(2, node2);
211
213 node3.id = 3;
214 node3.flags.isTextField = true;
215 node3.childrenInTraversalOrder = {};
216 node3.childrenInHitTestOrder = {};
217 updates.emplace(3, node3);
218
220 node4.childrenInTraversalOrder = {};
221 node4.childrenInHitTestOrder = {};
222 node4.id = 4;
223 node4.flags.isSlider = true;
224 updates.emplace(4, node4);
225
227 node5.childrenInTraversalOrder = {};
228 node5.childrenInHitTestOrder = {};
229 node5.id = 5;
230 node5.flags.isLink = true;
231 updates.emplace(5, node5);
232
234 node6.childrenInTraversalOrder = {};
235 node6.childrenInHitTestOrder = {};
236 node6.id = 6;
239 updates.emplace(6, node6);
240
242 node7.childrenInTraversalOrder = {};
243 node7.childrenInHitTestOrder = {};
244 node7.id = 7;
246 updates.emplace(7, node7);
247
249 node8.childrenInTraversalOrder = {};
250 node8.childrenInHitTestOrder = {};
251 node8.id = 8;
253 updates.emplace(7, node8);
254
255 accessibility_bridge_->AddSemanticsNodeUpdate(std::move(updates), 1.f);
256 RunLoopUntilIdle();
257
258 std::unordered_map<uint32_t, fuchsia::accessibility::semantics::Role>
259 roles_by_node_id = {
260 {0u, fuchsia::accessibility::semantics::Role::BUTTON},
261 {1u, fuchsia::accessibility::semantics::Role::HEADER},
262 {2u, fuchsia::accessibility::semantics::Role::IMAGE},
263 {3u, fuchsia::accessibility::semantics::Role::TEXT_FIELD},
264 {4u, fuchsia::accessibility::semantics::Role::SLIDER},
265 {5u, fuchsia::accessibility::semantics::Role::LINK},
266 {6u, fuchsia::accessibility::semantics::Role::RADIO_BUTTON},
267 {7u, fuchsia::accessibility::semantics::Role::CHECK_BOX},
268 {8u, fuchsia::accessibility::semantics::Role::TOGGLE_SWITCH}};
269
270 EXPECT_EQ(0, semantics_manager_.DeleteCount());
271 EXPECT_EQ(1, semantics_manager_.UpdateCount());
272 EXPECT_EQ(1, semantics_manager_.CommitCount());
273 EXPECT_EQ(8u, semantics_manager_.LastUpdatedNodes().size());
274 for (const auto& node : semantics_manager_.LastUpdatedNodes()) {
275 ExpectNodeHasRole(node, roles_by_node_id);
276 }
277
278 EXPECT_FALSE(semantics_manager_.DeleteOverflowed());
279 EXPECT_FALSE(semantics_manager_.UpdateOverflowed());
280}
281
282TEST_F(AccessibilityBridgeTest, DeletesChildrenTransitively) {
283 // Test that when a node is deleted, so are its transitive children.
285 node2.id = 2;
286
288 node1.id = 1;
289 node1.childrenInTraversalOrder = {2};
290 node1.childrenInHitTestOrder = {2};
291
293 node0.id = 0;
294 node0.childrenInTraversalOrder = {1};
295 node0.childrenInHitTestOrder = {1};
296
297 accessibility_bridge_->AddSemanticsNodeUpdate(
298 {
299 {0, node0},
300 {1, node1},
301 {2, node2},
302 },
303 1.f);
304 RunLoopUntilIdle();
305
306 EXPECT_EQ(0, semantics_manager_.DeleteCount());
307 EXPECT_EQ(1, semantics_manager_.UpdateCount());
308 EXPECT_EQ(1, semantics_manager_.CommitCount());
309 EXPECT_EQ(3U, semantics_manager_.LastUpdatedNodes().size());
310 EXPECT_EQ(0U, semantics_manager_.LastDeletedNodeIds().size());
311 EXPECT_FALSE(semantics_manager_.DeleteOverflowed());
312 EXPECT_FALSE(semantics_manager_.UpdateOverflowed());
313
314 // Remove the children
315 node0.childrenInTraversalOrder.clear();
316 node0.childrenInHitTestOrder.clear();
317 accessibility_bridge_->AddSemanticsNodeUpdate(
318 {
319 {0, node0},
320 },
321 1.f);
322 RunLoopUntilIdle();
323
324 EXPECT_EQ(1, semantics_manager_.DeleteCount());
325 EXPECT_EQ(2, semantics_manager_.UpdateCount());
326 EXPECT_EQ(2, semantics_manager_.CommitCount());
327 EXPECT_EQ(1U, semantics_manager_.LastUpdatedNodes().size());
328 ASSERT_EQ(std::vector<uint32_t>({1, 2}),
329 semantics_manager_.LastDeletedNodeIds());
330 EXPECT_FALSE(semantics_manager_.DeleteOverflowed());
331 EXPECT_FALSE(semantics_manager_.UpdateOverflowed());
332}
333
334TEST_F(AccessibilityBridgeTest, PopulatesRoleButton) {
336 node0.id = 0;
337 node0.flags.isButton = true;
338
339 accessibility_bridge_->AddSemanticsNodeUpdate({{0, node0}}, 1.f);
340 RunLoopUntilIdle();
341
342 EXPECT_EQ(1U, semantics_manager_.LastUpdatedNodes().size());
343 const auto& fuchsia_node = semantics_manager_.LastUpdatedNodes().at(0u);
344 EXPECT_EQ(fuchsia_node.node_id(), static_cast<unsigned int>(node0.id));
345 EXPECT_TRUE(fuchsia_node.has_role());
346 EXPECT_EQ(fuchsia_node.role(),
347 fuchsia::accessibility::semantics::Role::BUTTON);
348}
349
350TEST_F(AccessibilityBridgeTest, PopulatesRoleImage) {
352 node0.id = 0;
353 node0.flags.isImage = true;
354
355 accessibility_bridge_->AddSemanticsNodeUpdate({{0, node0}}, 1.f);
356 RunLoopUntilIdle();
357
358 EXPECT_EQ(1U, semantics_manager_.LastUpdatedNodes().size());
359 const auto& fuchsia_node = semantics_manager_.LastUpdatedNodes().at(0u);
360 EXPECT_EQ(fuchsia_node.node_id(), static_cast<unsigned int>(node0.id));
361 EXPECT_TRUE(fuchsia_node.has_role());
362 EXPECT_EQ(fuchsia_node.role(),
363 fuchsia::accessibility::semantics::Role::IMAGE);
364}
365
366TEST_F(AccessibilityBridgeTest, PopulatesRoleSlider) {
368 node0.id = 0;
369 node0.actions |= static_cast<int>(flutter::SemanticsAction::kIncrease);
370
371 accessibility_bridge_->AddSemanticsNodeUpdate({{0, node0}}, 1.f);
372 RunLoopUntilIdle();
373
374 EXPECT_EQ(1U, semantics_manager_.LastUpdatedNodes().size());
375 const auto& fuchsia_node = semantics_manager_.LastUpdatedNodes().at(0u);
376 EXPECT_EQ(fuchsia_node.node_id(), static_cast<unsigned int>(node0.id));
377 EXPECT_TRUE(fuchsia_node.has_role());
378 EXPECT_EQ(fuchsia_node.role(),
379 fuchsia::accessibility::semantics::Role::SLIDER);
380}
381
382TEST_F(AccessibilityBridgeTest, PopulatesRoleHeader) {
384 node0.id = 0;
385 node0.flags.isHeader = true;
386
387 accessibility_bridge_->AddSemanticsNodeUpdate({{0, node0}}, 1.f);
388 RunLoopUntilIdle();
389
390 EXPECT_EQ(1U, semantics_manager_.LastUpdatedNodes().size());
391 const auto& fuchsia_node = semantics_manager_.LastUpdatedNodes().at(0u);
392 EXPECT_EQ(fuchsia_node.node_id(), static_cast<unsigned int>(node0.id));
393 EXPECT_TRUE(fuchsia_node.has_role());
394 EXPECT_EQ(fuchsia_node.role(),
395 fuchsia::accessibility::semantics::Role::HEADER);
396}
397
398TEST_F(AccessibilityBridgeTest, PopulatesCheckedState) {
400 node0.id = 0;
401 // IsChecked = true
402 // IsSelected = false
404 node0.value = "value";
405
406 accessibility_bridge_->AddSemanticsNodeUpdate({{0, node0}}, 1.f);
407 RunLoopUntilIdle();
408
409 EXPECT_EQ(0, semantics_manager_.DeleteCount());
410 EXPECT_EQ(1, semantics_manager_.UpdateCount());
411 EXPECT_EQ(1, semantics_manager_.CommitCount());
412 EXPECT_EQ(1U, semantics_manager_.LastUpdatedNodes().size());
413 const auto& fuchsia_node = semantics_manager_.LastUpdatedNodes().at(0u);
414 EXPECT_EQ(fuchsia_node.node_id(), static_cast<unsigned int>(node0.id));
415 EXPECT_TRUE(fuchsia_node.has_states());
416 const auto& states = fuchsia_node.states();
417 EXPECT_TRUE(states.has_checked_state());
418 EXPECT_EQ(states.checked_state(),
419 fuchsia::accessibility::semantics::CheckedState::CHECKED);
420 EXPECT_TRUE(states.has_selected());
421 EXPECT_FALSE(states.selected());
422 EXPECT_TRUE(states.has_value());
423 EXPECT_EQ(states.value(), node0.value);
424
425 EXPECT_FALSE(semantics_manager_.DeleteOverflowed());
426 EXPECT_FALSE(semantics_manager_.UpdateOverflowed());
427}
428
429TEST_F(AccessibilityBridgeTest, PopulatesSelectedState) {
431 node0.id = 0;
432 // IsChecked = false
433 // IsSelected = true
435
436 accessibility_bridge_->AddSemanticsNodeUpdate({{0, node0}}, 1.f);
437 RunLoopUntilIdle();
438
439 EXPECT_EQ(0, semantics_manager_.DeleteCount());
440 EXPECT_EQ(1, semantics_manager_.UpdateCount());
441 EXPECT_EQ(1, semantics_manager_.CommitCount());
442 EXPECT_EQ(1U, semantics_manager_.LastUpdatedNodes().size());
443 const auto& fuchsia_node = semantics_manager_.LastUpdatedNodes().at(0u);
444 EXPECT_EQ(fuchsia_node.node_id(), static_cast<unsigned int>(node0.id));
445 EXPECT_TRUE(fuchsia_node.has_states());
446 const auto& states = fuchsia_node.states();
447 EXPECT_TRUE(states.has_checked_state());
448 EXPECT_EQ(states.checked_state(),
449 fuchsia::accessibility::semantics::CheckedState::NONE);
450 EXPECT_TRUE(states.has_selected());
451 EXPECT_TRUE(states.selected());
452
453 EXPECT_FALSE(semantics_manager_.DeleteOverflowed());
454 EXPECT_FALSE(semantics_manager_.UpdateOverflowed());
455}
456
457TEST_F(AccessibilityBridgeTest, PopulatesToggledState) {
459 node0.id = 0;
461
462 accessibility_bridge_->AddSemanticsNodeUpdate({{0, node0}}, 1.f);
463 RunLoopUntilIdle();
464
465 EXPECT_EQ(0, semantics_manager_.DeleteCount());
466 EXPECT_EQ(1, semantics_manager_.UpdateCount());
467 EXPECT_EQ(1, semantics_manager_.CommitCount());
468 EXPECT_EQ(1U, semantics_manager_.LastUpdatedNodes().size());
469 const auto& fuchsia_node = semantics_manager_.LastUpdatedNodes().at(0u);
470 EXPECT_EQ(fuchsia_node.node_id(), static_cast<unsigned int>(node0.id));
471 EXPECT_TRUE(fuchsia_node.has_states());
472 const auto& states = fuchsia_node.states();
473 EXPECT_TRUE(states.has_toggled_state());
474 EXPECT_EQ(states.toggled_state(),
475 fuchsia::accessibility::semantics::ToggledState::ON);
476
477 EXPECT_FALSE(semantics_manager_.DeleteOverflowed());
478 EXPECT_FALSE(semantics_manager_.UpdateOverflowed());
479}
480
481TEST_F(AccessibilityBridgeTest, PopulatesEnabledState) {
483 node0.id = 0;
485 node0.value = "value";
486
487 accessibility_bridge_->AddSemanticsNodeUpdate({{0, node0}}, 1.f);
488 RunLoopUntilIdle();
489
490 EXPECT_EQ(0, semantics_manager_.DeleteCount());
491 EXPECT_EQ(1, semantics_manager_.UpdateCount());
492 EXPECT_EQ(1, semantics_manager_.CommitCount());
493 EXPECT_EQ(1U, semantics_manager_.LastUpdatedNodes().size());
494 const auto& fuchsia_node = semantics_manager_.LastUpdatedNodes().at(0u);
495 EXPECT_EQ(fuchsia_node.node_id(), static_cast<unsigned int>(node0.id));
496 EXPECT_TRUE(fuchsia_node.has_states());
497 const auto& states = fuchsia_node.states();
498 EXPECT_TRUE(states.has_enabled_state());
499 EXPECT_EQ(states.enabled_state(),
500 fuchsia::accessibility::semantics::EnabledState::ENABLED);
501 EXPECT_TRUE(states.has_value());
502 EXPECT_EQ(states.value(), node0.value);
503
504 EXPECT_FALSE(semantics_manager_.DeleteOverflowed());
505 EXPECT_FALSE(semantics_manager_.UpdateOverflowed());
506}
507
508TEST_F(AccessibilityBridgeTest, ApplyViewPixelRatioToRoot) {
510 node0.id = 0;
512
513 accessibility_bridge_->AddSemanticsNodeUpdate({{0, node0}}, 1.25f);
514 RunLoopUntilIdle();
515 const auto& fuchsia_node = semantics_manager_.LastUpdatedNodes().at(0u);
516 EXPECT_EQ(fuchsia_node.node_id(), static_cast<unsigned int>(node0.id));
517 EXPECT_EQ(fuchsia_node.transform().matrix[0], 0.8f);
518 EXPECT_EQ(fuchsia_node.transform().matrix[5], 0.8f);
519 EXPECT_EQ(fuchsia_node.transform().matrix[10], 1.f);
520}
521
522TEST_F(AccessibilityBridgeTest, DoesNotPopulatesHiddenState) {
523 // Flutter's notion of a hidden node is different from Fuchsia's hidden node.
524 // This test make sures that this state does not get sent.
526 node0.id = 0;
527 // HasCheckedState = false
528 // IsChecked = false
529 // IsSelected = false
530 // IsHidden = true
531 node0.flags.isHidden = true;
532
533 accessibility_bridge_->AddSemanticsNodeUpdate({{0, node0}}, 1.f);
534 RunLoopUntilIdle();
535
536 EXPECT_EQ(0, semantics_manager_.DeleteCount());
537 EXPECT_EQ(1, semantics_manager_.UpdateCount());
538 EXPECT_EQ(1, semantics_manager_.CommitCount());
539 EXPECT_EQ(1u, semantics_manager_.LastUpdatedNodes().size());
540 const auto& fuchsia_node = semantics_manager_.LastUpdatedNodes().at(0u);
541 EXPECT_EQ(fuchsia_node.node_id(), static_cast<unsigned int>(node0.id));
542 EXPECT_TRUE(fuchsia_node.has_states());
543 const auto& states = fuchsia_node.states();
544 EXPECT_TRUE(states.has_checked_state());
545 EXPECT_EQ(states.checked_state(),
546 fuchsia::accessibility::semantics::CheckedState::NONE);
547 EXPECT_TRUE(states.has_selected());
548 EXPECT_FALSE(states.selected());
549 EXPECT_FALSE(states.has_hidden());
550
551 EXPECT_FALSE(semantics_manager_.DeleteOverflowed());
552 EXPECT_FALSE(semantics_manager_.UpdateOverflowed());
553}
554
555TEST_F(AccessibilityBridgeTest, PopulatesActions) {
557 node0.id = 0;
558 node0.actions |= static_cast<int>(flutter::SemanticsAction::kTap);
559 node0.actions |= static_cast<int>(flutter::SemanticsAction::kLongPress);
560 node0.actions |= static_cast<int>(flutter::SemanticsAction::kShowOnScreen);
561 node0.actions |= static_cast<int>(flutter::SemanticsAction::kIncrease);
562 node0.actions |= static_cast<int>(flutter::SemanticsAction::kDecrease);
563
564 accessibility_bridge_->AddSemanticsNodeUpdate({{0, node0}}, 1.f);
565 RunLoopUntilIdle();
566
567 EXPECT_EQ(0, semantics_manager_.DeleteCount());
568 EXPECT_EQ(1, semantics_manager_.UpdateCount());
569 EXPECT_EQ(1, semantics_manager_.CommitCount());
570 EXPECT_EQ(1u, semantics_manager_.LastUpdatedNodes().size());
571 const auto& fuchsia_node = semantics_manager_.LastUpdatedNodes().at(0u);
572 EXPECT_EQ(fuchsia_node.actions().size(), 5u);
573 EXPECT_EQ(fuchsia_node.actions().at(0u),
574 fuchsia::accessibility::semantics::Action::DEFAULT);
575 EXPECT_EQ(fuchsia_node.actions().at(1u),
576 fuchsia::accessibility::semantics::Action::SECONDARY);
577 EXPECT_EQ(fuchsia_node.actions().at(2u),
578 fuchsia::accessibility::semantics::Action::SHOW_ON_SCREEN);
579 EXPECT_EQ(fuchsia_node.actions().at(3u),
580 fuchsia::accessibility::semantics::Action::INCREMENT);
581 EXPECT_EQ(fuchsia_node.actions().at(4u),
582 fuchsia::accessibility::semantics::Action::DECREMENT);
583}
584
585TEST_F(AccessibilityBridgeTest, TruncatesLargeLabel) {
586 // Test that labels which are too long are truncated.
588 node0.id = 0;
589
591 node1.id = 1;
592
593 flutter::SemanticsNode bad_node;
594 bad_node.id = 2;
595 bad_node.label =
596 std::string(fuchsia::accessibility::semantics::MAX_LABEL_SIZE + 1, '2');
597
598 node0.childrenInTraversalOrder = {1, 2};
599 node0.childrenInHitTestOrder = {1, 2};
600
601 accessibility_bridge_->AddSemanticsNodeUpdate(
602 {
603 {0, node0},
604 {1, node1},
605 {2, bad_node},
606 },
607 1.f);
608 RunLoopUntilIdle();
609
610 // Nothing to delete, but we should have broken
611 EXPECT_EQ(0, semantics_manager_.DeleteCount());
612 EXPECT_EQ(1, semantics_manager_.UpdateCount());
613 EXPECT_EQ(1, semantics_manager_.CommitCount());
614 EXPECT_EQ(3U, semantics_manager_.LastUpdatedNodes().size());
615 auto trimmed_node =
616 std::find_if(semantics_manager_.LastUpdatedNodes().begin(),
617 semantics_manager_.LastUpdatedNodes().end(),
618 [id = static_cast<uint32_t>(bad_node.id)](
619 fuchsia::accessibility::semantics::Node const& node) {
620 return node.node_id() == id;
621 });
622 ASSERT_NE(trimmed_node, semantics_manager_.LastUpdatedNodes().end());
623 ASSERT_TRUE(trimmed_node->has_attributes());
624 EXPECT_EQ(
625 trimmed_node->attributes().label(),
626 std::string(fuchsia::accessibility::semantics::MAX_LABEL_SIZE, '2'));
627 EXPECT_FALSE(semantics_manager_.DeleteOverflowed());
628 EXPECT_FALSE(semantics_manager_.UpdateOverflowed());
629}
630
631TEST_F(AccessibilityBridgeTest, TruncatesLargeToolTip) {
632 // Test that tooltips which are too long are truncated.
634 node0.id = 0;
635
637 node1.id = 1;
638
639 flutter::SemanticsNode bad_node;
640 bad_node.id = 2;
641 bad_node.tooltip =
642 std::string(fuchsia::accessibility::semantics::MAX_LABEL_SIZE + 1, '2');
643
644 node0.childrenInTraversalOrder = {1, 2};
645 node0.childrenInHitTestOrder = {1, 2};
646
647 accessibility_bridge_->AddSemanticsNodeUpdate(
648 {
649 {0, node0},
650 {1, node1},
651 {2, bad_node},
652 },
653 1.f);
654 RunLoopUntilIdle();
655
656 // Nothing to delete, but we should have broken
657 EXPECT_EQ(0, semantics_manager_.DeleteCount());
658 EXPECT_EQ(1, semantics_manager_.UpdateCount());
659 EXPECT_EQ(1, semantics_manager_.CommitCount());
660 EXPECT_EQ(3U, semantics_manager_.LastUpdatedNodes().size());
661 auto trimmed_node =
662 std::find_if(semantics_manager_.LastUpdatedNodes().begin(),
663 semantics_manager_.LastUpdatedNodes().end(),
664 [id = static_cast<uint32_t>(bad_node.id)](
665 fuchsia::accessibility::semantics::Node const& node) {
666 return node.node_id() == id;
667 });
668 ASSERT_NE(trimmed_node, semantics_manager_.LastUpdatedNodes().end());
669 ASSERT_TRUE(trimmed_node->has_attributes());
670 EXPECT_EQ(
671 trimmed_node->attributes().secondary_label(),
672 std::string(fuchsia::accessibility::semantics::MAX_LABEL_SIZE, '2'));
673 EXPECT_FALSE(semantics_manager_.DeleteOverflowed());
674 EXPECT_FALSE(semantics_manager_.UpdateOverflowed());
675}
676
677TEST_F(AccessibilityBridgeTest, TruncatesLargeValue) {
678 // Test that values which are too long are truncated.
680 node0.id = 0;
681
683 node1.id = 1;
684
685 flutter::SemanticsNode bad_node;
686 bad_node.id = 2;
687 bad_node.value =
688 std::string(fuchsia::accessibility::semantics::MAX_VALUE_SIZE + 1, '2');
689
690 node0.childrenInTraversalOrder = {1, 2};
691 node0.childrenInHitTestOrder = {1, 2};
692
693 accessibility_bridge_->AddSemanticsNodeUpdate(
694 {
695 {0, node0},
696 {1, node1},
697 {2, bad_node},
698 },
699 1.f);
700 RunLoopUntilIdle();
701
702 EXPECT_EQ(0, semantics_manager_.DeleteCount());
703 EXPECT_EQ(1, semantics_manager_.UpdateCount());
704 EXPECT_EQ(1, semantics_manager_.CommitCount());
705 EXPECT_EQ(3U, semantics_manager_.LastUpdatedNodes().size());
706 auto trimmed_node =
707 std::find_if(semantics_manager_.LastUpdatedNodes().begin(),
708 semantics_manager_.LastUpdatedNodes().end(),
709 [id = static_cast<uint32_t>(bad_node.id)](
710 fuchsia::accessibility::semantics::Node const& node) {
711 return node.node_id() == id;
712 });
713 ASSERT_NE(trimmed_node, semantics_manager_.LastUpdatedNodes().end());
714 ASSERT_TRUE(trimmed_node->has_states());
715 EXPECT_EQ(
716 trimmed_node->states().value(),
717 std::string(fuchsia::accessibility::semantics::MAX_VALUE_SIZE, '2'));
718 EXPECT_FALSE(semantics_manager_.DeleteOverflowed());
719 EXPECT_FALSE(semantics_manager_.UpdateOverflowed());
720}
721
722TEST_F(AccessibilityBridgeTest, SplitsLargeUpdates) {
723 // Test that labels which are too long are truncated.
725 node0.id = 0;
726
728 node1.id = 1;
729 node1.label =
730 std::string(fuchsia::accessibility::semantics::MAX_LABEL_SIZE, '1');
731
733 node2.id = 2;
734 node2.label = "2";
735
737 node3.id = 3;
738 node3.label = "3";
739
741 node4.id = 4;
742 node4.value =
743 std::string(fuchsia::accessibility::semantics::MAX_VALUE_SIZE, '4');
744
745 node0.childrenInTraversalOrder = {1, 2};
746 node0.childrenInHitTestOrder = {1, 2};
747 node1.childrenInTraversalOrder = {3, 4};
748 node1.childrenInHitTestOrder = {3, 4};
749
750 accessibility_bridge_->AddSemanticsNodeUpdate(
751 {
752 {0, node0},
753 {1, node1},
754 {2, node2},
755 {3, node3},
756 {4, node4},
757 },
758 1.f);
759 RunLoopUntilIdle();
760
761 // Nothing to delete, but we should have broken into groups (4, 3, 2), (1, 0)
762 EXPECT_EQ(0, semantics_manager_.DeleteCount());
763 EXPECT_EQ(2, semantics_manager_.UpdateCount());
764 EXPECT_EQ(1, semantics_manager_.CommitCount());
765 EXPECT_EQ(2U, semantics_manager_.LastUpdatedNodes().size());
766 EXPECT_FALSE(semantics_manager_.DeleteOverflowed());
767 EXPECT_FALSE(semantics_manager_.UpdateOverflowed());
768}
769
771 // Test that cycles don't cause fatal error.
773 node0.id = 0;
774 node0.childrenInTraversalOrder.push_back(0);
775 node0.childrenInHitTestOrder.push_back(0);
776 accessibility_bridge_->AddSemanticsNodeUpdate(
777 {
778 {0, node0},
779 },
780 1.f);
781 RunLoopUntilIdle();
782
783 EXPECT_EQ(0, semantics_manager_.DeleteCount());
784 EXPECT_EQ(1, semantics_manager_.UpdateCount());
785 EXPECT_EQ(1, semantics_manager_.CommitCount());
786 EXPECT_FALSE(semantics_manager_.DeleteOverflowed());
787 EXPECT_FALSE(semantics_manager_.UpdateOverflowed());
788
789 node0.childrenInTraversalOrder = {0, 1};
790 node0.childrenInHitTestOrder = {0, 1};
792 node1.id = 1;
793 node1.childrenInTraversalOrder = {0};
794 node1.childrenInHitTestOrder = {0};
795 accessibility_bridge_->AddSemanticsNodeUpdate(
796 {
797 {0, node0},
798 {1, node1},
799 },
800 1.f);
801 RunLoopUntilIdle();
802
803 EXPECT_EQ(0, semantics_manager_.DeleteCount());
804 EXPECT_EQ(2, semantics_manager_.UpdateCount());
805 EXPECT_EQ(2, semantics_manager_.CommitCount());
806 EXPECT_FALSE(semantics_manager_.DeleteOverflowed());
807 EXPECT_FALSE(semantics_manager_.UpdateOverflowed());
808}
809
810TEST_F(AccessibilityBridgeTest, BatchesLargeMessages) {
811 // Tests that messages get batched appropriately.
813 node0.id = 0;
814
816
817 const int32_t child_nodes = 100;
818 const int32_t leaf_nodes = 100;
819 for (int32_t i = 1; i < child_nodes + 1; i++) {
821 node.id = i;
822 node0.childrenInTraversalOrder.push_back(i);
823 node0.childrenInHitTestOrder.push_back(i);
824 for (int32_t j = 0; j < leaf_nodes; j++) {
825 flutter::SemanticsNode leaf_node;
826 int id = (i * child_nodes) + ((j + 1) * leaf_nodes);
827 leaf_node.id = id;
828 leaf_node.label = "A relatively simple label";
829 node.childrenInTraversalOrder.push_back(id);
830 node.childrenInHitTestOrder.push_back(id);
831 update.insert(std::make_pair(id, std::move(leaf_node)));
832 }
833 update.insert(std::make_pair(i, std::move(node)));
834 }
835
836 update.insert(std::make_pair(0, std::move(node0)));
837
838 // Make the semantics manager hold answering to this commit to test the flow
839 // control. This means the second update will not be pushed until the first
840 // one has processed.
841 semantics_manager_.SetShouldHoldCommitResponse(true);
842 accessibility_bridge_->AddSemanticsNodeUpdate(update, 1.f);
843 RunLoopUntilIdle();
844
845 EXPECT_EQ(0, semantics_manager_.DeleteCount());
846
847 EXPECT_TRUE(6 <= semantics_manager_.UpdateCount() &&
848 semantics_manager_.UpdateCount() <= 12);
849 EXPECT_EQ(1, semantics_manager_.CommitCount());
850 EXPECT_FALSE(semantics_manager_.DeleteOverflowed());
851 EXPECT_FALSE(semantics_manager_.UpdateOverflowed());
852
853 int next_update_count = semantics_manager_.UpdateCount() + 1;
854 // Remove the children
855 node0.childrenInTraversalOrder.clear();
856 node0.childrenInHitTestOrder.clear();
857 accessibility_bridge_->AddSemanticsNodeUpdate(
858 {
859 {0, node0},
860 },
861 1.f);
862 RunLoopUntilIdle();
863
864 // Should still be 0, because the commit was not answered yet.
865 EXPECT_EQ(0, semantics_manager_.DeleteCount());
866
867 semantics_manager_.InvokeCommitCallback();
868 RunLoopUntilIdle();
869
870 EXPECT_EQ(1, semantics_manager_.DeleteCount());
871 EXPECT_EQ(next_update_count, semantics_manager_.UpdateCount());
872 EXPECT_EQ(2, semantics_manager_.CommitCount());
873 EXPECT_FALSE(semantics_manager_.DeleteOverflowed());
874 EXPECT_FALSE(semantics_manager_.UpdateOverflowed());
875}
876
879 node0.id = 0;
880 node0.rect.setLTRB(0, 0, 100, 100);
882
884 node1.id = 1;
885 node1.rect.setLTRB(10, 10, 20, 20);
886 // Setting platform view id ensures this node is considered focusable.
887 node1.platformViewId = 1u;
888
890 node2.id = 2;
891 node2.rect.setLTRB(25, 10, 45, 20);
892 // Setting label ensures this node is considered focusable.
893 node2.label = "label";
894
896 node3.id = 3;
897 node3.rect.setLTRB(10, 25, 20, 45);
898 // Setting actions to a nonzero value ensures this node is considered
899 // focusable.
900 node3.actions = 1u;
901
903 node4.id = 4;
904 node4.rect.setLTRB(10, 10, 20, 20);
905 node4.transform.setTranslate(20, 20, 0);
907
908 node0.childrenInTraversalOrder = {1, 2, 3, 4};
909 node0.childrenInHitTestOrder = {1, 2, 3, 4};
910
911 accessibility_bridge_->AddSemanticsNodeUpdate(
912 {
913 {0, node0},
914 {1, node1},
915 {2, node2},
916 {3, node3},
917 {4, node4},
918 },
919 1.f);
920 RunLoopUntilIdle();
921
922 uint32_t hit_node_id;
923 auto callback = [&hit_node_id](fuchsia::accessibility::semantics::Hit hit) {
924 EXPECT_TRUE(hit.has_node_id());
925 hit_node_id = hit.node_id();
926 };
927
928 // Nodes are:
929 // ----------
930 // | 1 2 |
931 // | 3 4 |
932 // ----------
933
934 accessibility_bridge_->HitTest({1, 1}, callback);
935 EXPECT_EQ(hit_node_id, 0u);
936 accessibility_bridge_->HitTest({15, 15}, callback);
937 EXPECT_EQ(hit_node_id, 1u);
938 accessibility_bridge_->HitTest({30, 15}, callback);
939 EXPECT_EQ(hit_node_id, 2u);
940 accessibility_bridge_->HitTest({15, 30}, callback);
941 EXPECT_EQ(hit_node_id, 3u);
942 accessibility_bridge_->HitTest({30, 30}, callback);
943 EXPECT_EQ(hit_node_id, 4u);
944}
945
946TEST_F(AccessibilityBridgeTest, HitTestWithPixelRatio) {
948 node0.id = 0;
949 node0.rect.setLTRB(0, 0, 100, 100);
952 node1.id = 1;
953 node1.rect.setLTRB(10, 10, 20, 20);
954 // Setting platform view id ensures this node is considered focusable.
955 node1.platformViewId = 1u;
956
957 node0.childrenInTraversalOrder = {1};
958 node0.childrenInHitTestOrder = {1};
959
960 accessibility_bridge_->AddSemanticsNodeUpdate(
961 {
962 {0, node0},
963 {1, node1},
964 },
965 // Pick a very small pixel ratio so that a point within the bounds of
966 // the node's root-space coordinates will be well outside the "screen"
967 // bounds of the node.
968 .1f);
969 RunLoopUntilIdle();
970
971 uint32_t hit_node_id;
972 auto callback = [&hit_node_id](fuchsia::accessibility::semantics::Hit hit) {
973 EXPECT_TRUE(hit.has_node_id());
974 hit_node_id = hit.node_id();
975 };
976 accessibility_bridge_->HitTest({15, 15}, callback);
977 EXPECT_EQ(hit_node_id, 0u);
978}
979
980TEST_F(AccessibilityBridgeTest, HitTestUnfocusableChild) {
982 node0.id = 0;
983 node0.rect.setLTRB(0, 0, 100, 100);
984
986 node1.id = 1;
987 node1.rect.setLTRB(10, 10, 60, 60);
988
990 node2.id = 2;
991 node2.rect.setLTRB(50, 50, 100, 100);
993
994 node0.childrenInTraversalOrder = {1, 2};
995 node0.childrenInHitTestOrder = {1, 2};
996
997 accessibility_bridge_->AddSemanticsNodeUpdate(
998 {
999 {0, node0},
1000 {1, node1},
1001 {2, node2},
1002 },
1003 1.f);
1004 RunLoopUntilIdle();
1005
1006 uint32_t hit_node_id;
1007 auto callback = [&hit_node_id](fuchsia::accessibility::semantics::Hit hit) {
1008 EXPECT_TRUE(hit.has_node_id());
1009 hit_node_id = hit.node_id();
1010 };
1011
1012 accessibility_bridge_->HitTest({55, 55}, callback);
1013 EXPECT_EQ(hit_node_id, 2u);
1014}
1015
1016TEST_F(AccessibilityBridgeTest, HitTestOverlapping) {
1017 // Tests that the first node in hit test order wins, even if a later node
1018 // would be able to receive the hit.
1020 node0.id = 0;
1021 node0.rect.setLTRB(0, 0, 100, 100);
1024 node1.id = 1;
1025 node1.rect.setLTRB(0, 0, 100, 100);
1027
1029 node2.id = 2;
1030 node2.rect.setLTRB(25, 10, 45, 20);
1032
1033 node0.childrenInTraversalOrder = {1, 2};
1034 node0.childrenInHitTestOrder = {2, 1};
1035
1036 accessibility_bridge_->AddSemanticsNodeUpdate(
1037 {
1038 {0, node0},
1039 {1, node1},
1040 {2, node2},
1041 },
1042 1.f);
1043 RunLoopUntilIdle();
1044
1045 uint32_t hit_node_id;
1046 auto callback = [&hit_node_id](fuchsia::accessibility::semantics::Hit hit) {
1047 EXPECT_TRUE(hit.has_node_id());
1048 hit_node_id = hit.node_id();
1049 };
1050
1051 accessibility_bridge_->HitTest({30, 15}, callback);
1052 EXPECT_EQ(hit_node_id, 2u);
1053}
1054
1057 node0.id = 0;
1058
1060 node1.id = 1;
1061
1062 node0.childrenInTraversalOrder = {1};
1063 node0.childrenInHitTestOrder = {1};
1064
1065 accessibility_bridge_->AddSemanticsNodeUpdate(
1066 {
1067 {0, node0},
1068 {1, node1},
1069 },
1070 1.f);
1071 RunLoopUntilIdle();
1072
1073 auto handled_callback = [](bool handled) { EXPECT_TRUE(handled); };
1074 auto unhandled_callback = [](bool handled) { EXPECT_FALSE(handled); };
1075
1076 accessibility_bridge_->OnAccessibilityActionRequested(
1077 0u, fuchsia::accessibility::semantics::Action::DEFAULT, handled_callback);
1078 EXPECT_EQ(accessibility_delegate_.actions.size(), 1u);
1079 EXPECT_EQ(accessibility_delegate_.actions.back(),
1080 std::make_pair(0, flutter::SemanticsAction::kTap));
1081
1082 accessibility_bridge_->OnAccessibilityActionRequested(
1083 0u, fuchsia::accessibility::semantics::Action::SECONDARY,
1084 handled_callback);
1085 EXPECT_EQ(accessibility_delegate_.actions.size(), 2u);
1086 EXPECT_EQ(accessibility_delegate_.actions.back(),
1087 std::make_pair(0, flutter::SemanticsAction::kLongPress));
1088
1089 accessibility_bridge_->OnAccessibilityActionRequested(
1090 0u, fuchsia::accessibility::semantics::Action::SET_FOCUS,
1091 unhandled_callback);
1092 EXPECT_EQ(accessibility_delegate_.actions.size(), 2u);
1093
1094 accessibility_bridge_->OnAccessibilityActionRequested(
1095 0u, fuchsia::accessibility::semantics::Action::SET_VALUE,
1096 unhandled_callback);
1097 EXPECT_EQ(accessibility_delegate_.actions.size(), 2u);
1098
1099 accessibility_bridge_->OnAccessibilityActionRequested(
1100 0u, fuchsia::accessibility::semantics::Action::SHOW_ON_SCREEN,
1101 handled_callback);
1102 EXPECT_EQ(accessibility_delegate_.actions.size(), 3u);
1103 EXPECT_EQ(accessibility_delegate_.actions.back(),
1104 std::make_pair(0, flutter::SemanticsAction::kShowOnScreen));
1105
1106 accessibility_bridge_->OnAccessibilityActionRequested(
1107 2u, fuchsia::accessibility::semantics::Action::DEFAULT,
1108 unhandled_callback);
1109 EXPECT_EQ(accessibility_delegate_.actions.size(), 3u);
1110
1111 accessibility_bridge_->OnAccessibilityActionRequested(
1112 0u, fuchsia::accessibility::semantics::Action::INCREMENT,
1113 handled_callback);
1114 EXPECT_EQ(accessibility_delegate_.actions.size(), 4u);
1115 EXPECT_EQ(accessibility_delegate_.actions.back(),
1116 std::make_pair(0, flutter::SemanticsAction::kIncrease));
1117
1118 accessibility_bridge_->OnAccessibilityActionRequested(
1119 0u, fuchsia::accessibility::semantics::Action::DECREMENT,
1120 handled_callback);
1121 EXPECT_EQ(accessibility_delegate_.actions.size(), 5u);
1122 EXPECT_EQ(accessibility_delegate_.actions.back(),
1123 std::make_pair(0, flutter::SemanticsAction::kDecrease));
1124}
1125
1126#if !FLUTTER_RELEASE
1130 node0.id = 0;
1131 node0.label = "node0";
1132 node0.hint = "node0_hint";
1133 node0.value = "value";
1134 node0.flags.isButton = true;
1135 node0.childrenInTraversalOrder = {1};
1136 node0.childrenInHitTestOrder = {1};
1137 node0.rect.setLTRB(0, 0, 100, 100);
1138 updates.emplace(0, node0);
1139
1141 node1.id = 1;
1142 node1.flags.isHeader = true;
1143 node1.childrenInTraversalOrder = {};
1144 node1.childrenInHitTestOrder = {};
1145 updates.emplace(1, node1);
1146
1147 accessibility_bridge_->AddSemanticsNodeUpdate(std::move(updates), 1.f);
1148 RunLoopUntilIdle();
1149
1150 fpromise::result<inspect::Hierarchy> hierarchy;
1151 ASSERT_FALSE(hierarchy.is_ok());
1152 RunPromiseToCompletion(
1153 inspect::ReadFromInspector(*inspector_)
1154 .then([&hierarchy](fpromise::result<inspect::Hierarchy>& result) {
1155 hierarchy = std::move(result);
1156 }));
1157 ASSERT_TRUE(hierarchy.is_ok());
1158
1159 auto tree_inspect_hierarchy = hierarchy.value().GetByPath({"test_node"});
1160 ASSERT_NE(tree_inspect_hierarchy, nullptr);
1161 // TODO(http://fxbug.dev/75841): Rewrite flutter engine accessibility bridge
1162 // tests using inspect matchers. The checks bellow verify that the tree was
1163 // built, and that it matches the format of the input tree. This will be
1164 // updated in the future when test matchers are available to verify individual
1165 // property values.
1166 const auto& root = tree_inspect_hierarchy->children();
1167 ASSERT_EQ(root.size(), 1u);
1168 EXPECT_EQ(root[0].name(), "semantic_tree_root");
1169 const auto& child = root[0].children();
1170 ASSERT_EQ(child.size(), 1u);
1171 EXPECT_EQ(child[0].name(), "node_1");
1172}
1173#endif // !FLUTTER_RELEASE
1174
1175} // namespace flutter_runner_test
std::function< void(bool)> SetSemanticsEnabledCallback
std::function< void(int32_t, flutter::SemanticsAction)> DispatchSemanticsActionCallback
std::vector< std::pair< int32_t, flutter::SemanticsAction > > actions
void DispatchSemanticsAction(int32_t node_id, flutter::SemanticsAction action)
std::unique_ptr< flutter_runner::AccessibilityBridge > accessibility_bridge_
fidl::InterfaceRequestHandler< SemanticsManager > GetHandler(async_dispatcher_t *dispatcher)
G_BEGIN_DECLS GBytes * message
FlutterDesktopBinaryReply callback
#define FML_LOG(severity)
Definition logging.h:101
const char * name
Definition fuchsia.cc:49
TEST_F(AccessibilityBridgeTest, RegistersViewRef)
std::unordered_map< int32_t, SemanticsNode > SemanticsNodeUpdates
SemanticsCheckState isChecked
SemanticsTristate isFocused
SemanticsTristate isSelected
SemanticsTristate isToggled
SemanticsTristate isEnabled
std::vector< int32_t > childrenInHitTestOrder
std::vector< int32_t > childrenInTraversalOrder
const uintptr_t id