Flutter iOS Embedder
accessibility_bridge_unittests.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 "accessibility_bridge.h"
6 
7 #include "gmock/gmock.h"
8 #include "gtest/gtest.h"
9 
10 #include "flutter/third_party/accessibility/ax/ax_tree_manager_map.h"
12 
13 namespace flutter {
14 namespace testing {
15 
16 using ::testing::Contains;
17 
18 FlutterSemanticsFlags kEmptyFlags = FlutterSemanticsFlags{};
19 
20 FlutterSemanticsNode2 CreateSemanticsNode(
21  int32_t id,
22  const char* label,
23  const std::vector<int32_t>* children = nullptr) {
24  return {
25  .id = id,
26  // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange)
27  .flags__deprecated__ = static_cast<FlutterSemanticsFlag>(0),
28  // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange)
29  .actions = static_cast<FlutterSemanticsAction>(0),
30  .text_selection_base = -1,
31  .text_selection_extent = -1,
32  .label = label,
33  .hint = "",
34  .value = "",
35  .increased_value = "",
36  .decreased_value = "",
37  .child_count = children ? children->size() : 0,
38  .children_in_traversal_order = children ? children->data() : nullptr,
39  .custom_accessibility_actions_count = 0,
40  .tooltip = "",
41  .flags2 = &kEmptyFlags,
42  };
43 }
44 
46  std::shared_ptr<TestAccessibilityBridge> bridge =
47  std::make_shared<TestAccessibilityBridge>();
48 
49  std::vector<int32_t> children{1, 2};
50  FlutterSemanticsNode2 root = CreateSemanticsNode(0, "root", &children);
51  root.identifier = "identifier";
52  FlutterSemanticsNode2 child1 = CreateSemanticsNode(1, "child 1");
53  FlutterSemanticsNode2 child2 = CreateSemanticsNode(2, "child 2");
54 
55  bridge->AddFlutterSemanticsNodeUpdate(root);
56  bridge->AddFlutterSemanticsNodeUpdate(child1);
57  bridge->AddFlutterSemanticsNodeUpdate(child2);
58  bridge->CommitUpdates();
59 
60  auto root_node = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
61  auto child1_node = bridge->GetFlutterPlatformNodeDelegateFromID(1).lock();
62  auto child2_node = bridge->GetFlutterPlatformNodeDelegateFromID(2).lock();
63  EXPECT_EQ(root_node->GetChildCount(), 2);
64  EXPECT_EQ(root_node->GetData().child_ids[0], 1);
65  EXPECT_EQ(root_node->GetData().child_ids[1], 2);
66  EXPECT_EQ(root_node->GetName(), "root");
67  EXPECT_EQ(root_node->GetAuthorUniqueId(), u"identifier");
68 
69  EXPECT_EQ(child1_node->GetChildCount(), 0);
70  EXPECT_EQ(child1_node->GetName(), "child 1");
71 
72  EXPECT_EQ(child2_node->GetChildCount(), 0);
73  EXPECT_EQ(child2_node->GetName(), "child 2");
74 }
75 
76 // Flutter used to assume that the accessibility root had ID 0.
77 // In a multi-view world, each view has its own accessibility root
78 // with a globally unique node ID.
79 TEST(AccessibilityBridgeTest, AccessibilityRootId) {
80  std::shared_ptr<TestAccessibilityBridge> bridge =
81  std::make_shared<TestAccessibilityBridge>();
82 
83  std::vector<int32_t> children{456, 789};
84  FlutterSemanticsNode2 root = CreateSemanticsNode(123, "root", &children);
85  FlutterSemanticsNode2 child1 = CreateSemanticsNode(456, "child 1");
86  FlutterSemanticsNode2 child2 = CreateSemanticsNode(789, "child 2");
87 
88  bridge->AddFlutterSemanticsNodeUpdate(root);
89  bridge->AddFlutterSemanticsNodeUpdate(child1);
90  bridge->AddFlutterSemanticsNodeUpdate(child2);
91  bridge->CommitUpdates();
92 
93  auto root_node = bridge->GetFlutterPlatformNodeDelegateFromID(123).lock();
94  auto child1_node = bridge->GetFlutterPlatformNodeDelegateFromID(456).lock();
95  auto child2_node = bridge->GetFlutterPlatformNodeDelegateFromID(789).lock();
96  auto fake_delegate = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
97 
98  EXPECT_EQ(bridge->GetRootAsAXNode()->id(), 123);
99  EXPECT_EQ(bridge->RootDelegate()->GetName(), "root");
100 
101  EXPECT_EQ(root_node->GetChildCount(), 2);
102  EXPECT_EQ(root_node->GetData().child_ids[0], 456);
103  EXPECT_EQ(root_node->GetData().child_ids[1], 789);
104  EXPECT_EQ(root_node->GetName(), "root");
105 
106  EXPECT_EQ(child1_node->GetChildCount(), 0);
107  EXPECT_EQ(child1_node->GetName(), "child 1");
108 
109  EXPECT_EQ(child2_node->GetChildCount(), 0);
110  EXPECT_EQ(child2_node->GetName(), "child 2");
111 
112  ASSERT_FALSE(fake_delegate);
113 }
114 
115 // Semantic nodes can be added in any order.
117  std::shared_ptr<TestAccessibilityBridge> bridge =
118  std::make_shared<TestAccessibilityBridge>();
119 
120  std::vector<int32_t> root_children{34, 56};
121  std::vector<int32_t> child2_children{78};
122  std::vector<int32_t> child3_children{90};
123  FlutterSemanticsNode2 root = CreateSemanticsNode(12, "root", &root_children);
124  FlutterSemanticsNode2 child1 = CreateSemanticsNode(34, "child 1");
125  FlutterSemanticsNode2 child2 =
126  CreateSemanticsNode(56, "child 2", &child2_children);
127  FlutterSemanticsNode2 child3 =
128  CreateSemanticsNode(78, "child 3", &child3_children);
129  FlutterSemanticsNode2 child4 = CreateSemanticsNode(90, "child 4");
130 
131  bridge->AddFlutterSemanticsNodeUpdate(child3);
132  bridge->AddFlutterSemanticsNodeUpdate(child2);
133  bridge->AddFlutterSemanticsNodeUpdate(root);
134  bridge->AddFlutterSemanticsNodeUpdate(child1);
135  bridge->AddFlutterSemanticsNodeUpdate(child4);
136  bridge->CommitUpdates();
137 
138  auto root_node = bridge->GetFlutterPlatformNodeDelegateFromID(12).lock();
139  auto child1_node = bridge->GetFlutterPlatformNodeDelegateFromID(34).lock();
140  auto child2_node = bridge->GetFlutterPlatformNodeDelegateFromID(56).lock();
141  auto child3_node = bridge->GetFlutterPlatformNodeDelegateFromID(78).lock();
142  auto child4_node = bridge->GetFlutterPlatformNodeDelegateFromID(90).lock();
143 
144  EXPECT_EQ(bridge->GetRootAsAXNode()->id(), 12);
145  EXPECT_EQ(bridge->RootDelegate()->GetName(), "root");
146 
147  EXPECT_EQ(root_node->GetChildCount(), 2);
148  EXPECT_EQ(root_node->GetData().child_ids[0], 34);
149  EXPECT_EQ(root_node->GetData().child_ids[1], 56);
150  EXPECT_EQ(root_node->GetName(), "root");
151 
152  EXPECT_EQ(child1_node->GetChildCount(), 0);
153  EXPECT_EQ(child1_node->GetName(), "child 1");
154 
155  EXPECT_EQ(child2_node->GetChildCount(), 1);
156  EXPECT_EQ(child2_node->GetData().child_ids[0], 78);
157  EXPECT_EQ(child2_node->GetName(), "child 2");
158 
159  EXPECT_EQ(child3_node->GetChildCount(), 1);
160  EXPECT_EQ(child3_node->GetData().child_ids[0], 90);
161  EXPECT_EQ(child3_node->GetName(), "child 3");
162 
163  EXPECT_EQ(child4_node->GetChildCount(), 0);
164  EXPECT_EQ(child4_node->GetName(), "child 4");
165 }
166 
167 TEST(AccessibilityBridgeTest, CanFireChildrenChangedCorrectly) {
168  std::shared_ptr<TestAccessibilityBridge> bridge =
169  std::make_shared<TestAccessibilityBridge>();
170 
171  std::vector<int32_t> children{1};
172  FlutterSemanticsNode2 root = CreateSemanticsNode(0, "root", &children);
173  FlutterSemanticsNode2 child1 = CreateSemanticsNode(1, "child 1");
174 
175  bridge->AddFlutterSemanticsNodeUpdate(root);
176  bridge->AddFlutterSemanticsNodeUpdate(child1);
177 
178  bridge->CommitUpdates();
179 
180  auto root_node = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
181  auto child1_node = bridge->GetFlutterPlatformNodeDelegateFromID(1).lock();
182  EXPECT_EQ(root_node->GetChildCount(), 1);
183  EXPECT_EQ(root_node->GetData().child_ids[0], 1);
184  EXPECT_EQ(root_node->GetName(), "root");
185 
186  EXPECT_EQ(child1_node->GetChildCount(), 0);
187  EXPECT_EQ(child1_node->GetName(), "child 1");
188  bridge->accessibility_events.clear();
189 
190  // Add a child to root.
191  root.child_count = 2;
192  int32_t new_children[] = {1, 2};
193  root.children_in_traversal_order = new_children;
194 
195  FlutterSemanticsNode2 child2 = CreateSemanticsNode(2, "child 2");
196 
197  bridge->AddFlutterSemanticsNodeUpdate(root);
198  bridge->AddFlutterSemanticsNodeUpdate(child2);
199  bridge->CommitUpdates();
200 
201  root_node = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
202 
203  EXPECT_EQ(root_node->GetChildCount(), 2);
204  EXPECT_EQ(root_node->GetData().child_ids[0], 1);
205  EXPECT_EQ(root_node->GetData().child_ids[1], 2);
206  EXPECT_EQ(bridge->accessibility_events.size(), size_t{2});
207  std::set<ui::AXEventGenerator::Event> actual_event{
208  bridge->accessibility_events.begin(), bridge->accessibility_events.end()};
209  EXPECT_THAT(actual_event,
210  Contains(ui::AXEventGenerator::Event::CHILDREN_CHANGED));
211  EXPECT_THAT(actual_event,
212  Contains(ui::AXEventGenerator::Event::SUBTREE_CREATED));
213 }
214 
215 TEST(AccessibilityBridgeTest, CanHandleSelectionChangeCorrectly) {
216  std::shared_ptr<TestAccessibilityBridge> bridge =
217  std::make_shared<TestAccessibilityBridge>();
218  FlutterSemanticsNode2 root = CreateSemanticsNode(0, "root");
219  auto flags = FlutterSemanticsFlags{
220  .is_focused = FlutterTristate::kFlutterTristateTrue,
221  .is_text_field = true,
222  };
223  root.flags2 = &flags;
224 
225  bridge->AddFlutterSemanticsNodeUpdate(root);
226  bridge->CommitUpdates();
227 
228  const ui::AXTreeData& tree = bridge->GetAXTreeData();
229  EXPECT_EQ(tree.sel_anchor_object_id, ui::AXNode::kInvalidAXID);
230  bridge->accessibility_events.clear();
231 
232  // Update the selection.
233  root.text_selection_base = 0;
234  root.text_selection_extent = 5;
235  bridge->AddFlutterSemanticsNodeUpdate(root);
236 
237  bridge->CommitUpdates();
238 
239  EXPECT_EQ(tree.sel_anchor_object_id, 0);
240  EXPECT_EQ(tree.sel_anchor_offset, 0);
241  EXPECT_EQ(tree.sel_focus_object_id, 0);
242  EXPECT_EQ(tree.sel_focus_offset, 5);
243  ASSERT_EQ(bridge->accessibility_events.size(), size_t{2});
244  EXPECT_EQ(bridge->accessibility_events[0],
245  ui::AXEventGenerator::Event::DOCUMENT_SELECTION_CHANGED);
246  EXPECT_EQ(bridge->accessibility_events[1],
247  ui::AXEventGenerator::Event::OTHER_ATTRIBUTE_CHANGED);
248 }
249 
250 TEST(AccessibilityBridgeTest, DoesNotAssignEditableRootToSelectableText) {
251  std::shared_ptr<TestAccessibilityBridge> bridge =
252  std::make_shared<TestAccessibilityBridge>();
253  FlutterSemanticsNode2 root = CreateSemanticsNode(0, "root");
254  auto flags = FlutterSemanticsFlags{
255  .is_text_field = true,
256  .is_read_only = true,
257  };
258  root.flags2 = &flags;
259 
260  bridge->AddFlutterSemanticsNodeUpdate(root);
261  bridge->CommitUpdates();
262 
263  auto root_node = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
264 
265  EXPECT_FALSE(root_node->GetData().GetBoolAttribute(
266  ax::mojom::BoolAttribute::kEditableRoot));
267 }
268 
269 TEST(AccessibilityBridgeTest, SwitchHasSwitchRole) {
270  std::shared_ptr<TestAccessibilityBridge> bridge =
271  std::make_shared<TestAccessibilityBridge>();
272  FlutterSemanticsNode2 root = CreateSemanticsNode(0, "root");
273  auto flags = FlutterSemanticsFlags{
274  .is_enabled = FlutterTristate::kFlutterTristateTrue,
275  .is_toggled = FlutterTristate::kFlutterTristateFalse,
276  };
277 
278  root.flags2 = &flags;
279  bridge->AddFlutterSemanticsNodeUpdate(root);
280  bridge->CommitUpdates();
281 
282  auto root_node = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
283  EXPECT_EQ(root_node->GetData().role, ax::mojom::Role::kSwitch);
284 }
285 
286 TEST(AccessibilityBridgeTest, SliderHasSliderRole) {
287  std::shared_ptr<TestAccessibilityBridge> bridge =
288  std::make_shared<TestAccessibilityBridge>();
289  FlutterSemanticsNode2 root = CreateSemanticsNode(0, "root");
290  auto flags = FlutterSemanticsFlags{
291  .is_enabled = FlutterTristate::kFlutterTristateTrue,
292  .is_focused = FlutterTristate::kFlutterTristateFalse,
293  .is_slider = true,
294  };
295 
296  root.flags2 = &flags;
297 
298  bridge->AddFlutterSemanticsNodeUpdate(root);
299  bridge->CommitUpdates();
300 
301  auto root_node = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
302  EXPECT_EQ(root_node->GetData().role, ax::mojom::Role::kSlider);
303 }
304 
305 // Ensure that checkboxes have their checked status set apropriately
306 // Previously, only Radios could have this flag updated
307 // Resulted in the issue seen at
308 // https://github.com/flutter/flutter/issues/96218
309 // As this fix involved code run on all platforms, it is included here.
310 TEST(AccessibilityBridgeTest, CanSetCheckboxChecked) {
311  std::shared_ptr<TestAccessibilityBridge> bridge =
312  std::make_shared<TestAccessibilityBridge>();
313  FlutterSemanticsNode2 root = CreateSemanticsNode(0, "root");
314  auto flags = FlutterSemanticsFlags{
315  .is_checked = FlutterCheckState::kFlutterCheckStateTrue,
316  };
317 
318  root.flags2 = &flags;
319  bridge->AddFlutterSemanticsNodeUpdate(root);
320  bridge->CommitUpdates();
321 
322  auto root_node = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
323  EXPECT_EQ(root_node->GetData().role, ax::mojom::Role::kCheckBox);
324  EXPECT_EQ(root_node->GetData().GetCheckedState(),
325  ax::mojom::CheckedState::kTrue);
326 }
327 
328 // Verify that a node can be moved from one parent to another.
329 TEST(AccessibilityBridgeTest, CanReparentNode) {
330  std::shared_ptr<TestAccessibilityBridge> bridge =
331  std::make_shared<TestAccessibilityBridge>();
332 
333  std::vector<int32_t> root_children{1};
334  std::vector<int32_t> child1_children{2};
335  FlutterSemanticsNode2 root = CreateSemanticsNode(0, "root", &root_children);
336  FlutterSemanticsNode2 child1 =
337  CreateSemanticsNode(1, "child 1", &child1_children);
338  FlutterSemanticsNode2 child2 = CreateSemanticsNode(2, "child 2");
339 
340  bridge->AddFlutterSemanticsNodeUpdate(root);
341  bridge->AddFlutterSemanticsNodeUpdate(child1);
342  bridge->AddFlutterSemanticsNodeUpdate(child2);
343  bridge->CommitUpdates();
344  bridge->accessibility_events.clear();
345 
346  // Reparent child2 from child1 to the root.
347  child1.child_count = 0;
348  child1.children_in_traversal_order = nullptr;
349 
350  int32_t new_root_children[] = {1, 2};
351  root.child_count = 2;
352  root.children_in_traversal_order = new_root_children;
353 
354  bridge->AddFlutterSemanticsNodeUpdate(root);
355  bridge->AddFlutterSemanticsNodeUpdate(child1);
356  bridge->AddFlutterSemanticsNodeUpdate(child2);
357  bridge->CommitUpdates();
358 
359  auto root_node = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock();
360  auto child1_node = bridge->GetFlutterPlatformNodeDelegateFromID(1).lock();
361  auto child2_node = bridge->GetFlutterPlatformNodeDelegateFromID(2).lock();
362 
363  EXPECT_EQ(root_node->GetChildCount(), 2);
364  EXPECT_EQ(root_node->GetData().child_ids[0], 1);
365  EXPECT_EQ(root_node->GetData().child_ids[1], 2);
366  EXPECT_EQ(root_node->GetName(), "root");
367 
368  EXPECT_EQ(child1_node->GetChildCount(), 0);
369  EXPECT_EQ(child1_node->GetName(), "child 1");
370 
371  EXPECT_EQ(child2_node->GetChildCount(), 0);
372  EXPECT_EQ(child2_node->GetName(), "child 2");
373 
374  ASSERT_EQ(bridge->accessibility_events.size(), size_t{5});
375 
376  // Child2 is moved from child1 to root.
377  EXPECT_THAT(bridge->accessibility_events,
378  Contains(ui::AXEventGenerator::Event::CHILDREN_CHANGED).Times(2));
379  EXPECT_THAT(bridge->accessibility_events,
380  Contains(ui::AXEventGenerator::Event::SUBTREE_CREATED).Times(1));
381 
382  // Child1 is no longer a parent. It loses its group role and disables its
383  // 'clip children' attribute.
384  EXPECT_THAT(
385  bridge->accessibility_events,
386  Contains(ui::AXEventGenerator::Event::OTHER_ATTRIBUTE_CHANGED).Times(1));
387  EXPECT_THAT(bridge->accessibility_events,
388  Contains(ui::AXEventGenerator::Event::ROLE_CHANGED).Times(1));
389 }
390 
391 // Verify that multiple nodes can be moved to new parents.
392 TEST(AccessibilityBridgeTest, CanReparentMultipleNodes) {
393  std::shared_ptr<TestAccessibilityBridge> bridge =
394  std::make_shared<TestAccessibilityBridge>();
395 
396  int32_t root_id = 0;
397  int32_t intermediary1_id = 1;
398  int32_t intermediary2_id = 2;
399  int32_t leaf1_id = 3;
400  int32_t leaf2_id = 4;
401  int32_t leaf3_id = 5;
402 
403  std::vector<int32_t> root_children{intermediary1_id, intermediary2_id};
404  std::vector<int32_t> intermediary1_children{leaf1_id};
405  std::vector<int32_t> intermediary2_children{leaf2_id, leaf3_id};
406  FlutterSemanticsNode2 root =
407  CreateSemanticsNode(root_id, "root", &root_children);
408  FlutterSemanticsNode2 intermediary1 = CreateSemanticsNode(
409  intermediary1_id, "intermediary 1", &intermediary1_children);
410  FlutterSemanticsNode2 intermediary2 = CreateSemanticsNode(
411  intermediary2_id, "intermediary 2", &intermediary2_children);
412  FlutterSemanticsNode2 leaf1 = CreateSemanticsNode(leaf1_id, "leaf 1");
413  FlutterSemanticsNode2 leaf2 = CreateSemanticsNode(leaf2_id, "leaf 2");
414  FlutterSemanticsNode2 leaf3 = CreateSemanticsNode(leaf3_id, "leaf 3");
415 
416  bridge->AddFlutterSemanticsNodeUpdate(root);
417  bridge->AddFlutterSemanticsNodeUpdate(intermediary1);
418  bridge->AddFlutterSemanticsNodeUpdate(intermediary2);
419  bridge->AddFlutterSemanticsNodeUpdate(leaf1);
420  bridge->AddFlutterSemanticsNodeUpdate(leaf2);
421  bridge->AddFlutterSemanticsNodeUpdate(leaf3);
422  bridge->CommitUpdates();
423  bridge->accessibility_events.clear();
424 
425  // Swap intermediary 1's and intermediary2's children.
426  int32_t new_intermediary1_children[] = {leaf2_id, leaf3_id};
427  intermediary1.child_count = 2;
428  intermediary1.children_in_traversal_order = new_intermediary1_children;
429 
430  int32_t new_intermediary2_children[] = {leaf1_id};
431  intermediary2.child_count = 1;
432  intermediary2.children_in_traversal_order = new_intermediary2_children;
433 
434  bridge->AddFlutterSemanticsNodeUpdate(intermediary1);
435  bridge->AddFlutterSemanticsNodeUpdate(intermediary2);
436  bridge->AddFlutterSemanticsNodeUpdate(leaf1);
437  bridge->AddFlutterSemanticsNodeUpdate(leaf2);
438  bridge->AddFlutterSemanticsNodeUpdate(leaf3);
439  bridge->CommitUpdates();
440 
441  auto root_node = bridge->GetFlutterPlatformNodeDelegateFromID(root_id).lock();
442  auto intermediary1_node =
443  bridge->GetFlutterPlatformNodeDelegateFromID(intermediary1_id).lock();
444  auto intermediary2_node =
445  bridge->GetFlutterPlatformNodeDelegateFromID(intermediary2_id).lock();
446  auto leaf1_node =
447  bridge->GetFlutterPlatformNodeDelegateFromID(leaf1_id).lock();
448  auto leaf2_node =
449  bridge->GetFlutterPlatformNodeDelegateFromID(leaf2_id).lock();
450  auto leaf3_node =
451  bridge->GetFlutterPlatformNodeDelegateFromID(leaf3_id).lock();
452 
453  EXPECT_EQ(root_node->GetChildCount(), 2);
454  EXPECT_EQ(root_node->GetData().child_ids[0], intermediary1_id);
455  EXPECT_EQ(root_node->GetData().child_ids[1], intermediary2_id);
456  EXPECT_EQ(root_node->GetName(), "root");
457 
458  EXPECT_EQ(intermediary1_node->GetChildCount(), 2);
459  EXPECT_EQ(intermediary1_node->GetData().child_ids[0], leaf2_id);
460  EXPECT_EQ(intermediary1_node->GetData().child_ids[1], leaf3_id);
461  EXPECT_EQ(intermediary1_node->GetName(), "intermediary 1");
462 
463  EXPECT_EQ(intermediary2_node->GetChildCount(), 1);
464  EXPECT_EQ(intermediary2_node->GetData().child_ids[0], leaf1_id);
465  EXPECT_EQ(intermediary2_node->GetName(), "intermediary 2");
466 
467  EXPECT_EQ(leaf1_node->GetChildCount(), 0);
468  EXPECT_EQ(leaf1_node->GetName(), "leaf 1");
469 
470  EXPECT_EQ(leaf2_node->GetChildCount(), 0);
471  EXPECT_EQ(leaf2_node->GetName(), "leaf 2");
472 
473  EXPECT_EQ(leaf3_node->GetChildCount(), 0);
474  EXPECT_EQ(leaf3_node->GetName(), "leaf 3");
475 
476  // Intermediary 1 and intermediary 2 have new children.
477  // Leaf 1, 2, and 3 are all moved.
478  ASSERT_EQ(bridge->accessibility_events.size(), size_t{5});
479  EXPECT_THAT(bridge->accessibility_events,
480  Contains(ui::AXEventGenerator::Event::CHILDREN_CHANGED).Times(2));
481  EXPECT_THAT(bridge->accessibility_events,
482  Contains(ui::AXEventGenerator::Event::SUBTREE_CREATED).Times(3));
483 }
484 
485 // Verify that a node with a child can be moved from one parent to another.
486 TEST(AccessibilityBridgeTest, CanReparentNodeWithChild) {
487  std::shared_ptr<TestAccessibilityBridge> bridge =
488  std::make_shared<TestAccessibilityBridge>();
489 
490  int32_t root_id = 0;
491  int32_t intermediary1_id = 1;
492  int32_t intermediary2_id = 2;
493  int32_t leaf1_id = 3;
494 
495  std::vector<int32_t> root_children{intermediary1_id, intermediary2_id};
496  std::vector<int32_t> intermediary1_children{leaf1_id};
497  FlutterSemanticsNode2 root =
498  CreateSemanticsNode(root_id, "root", &root_children);
499  FlutterSemanticsNode2 intermediary1 = CreateSemanticsNode(
500  intermediary1_id, "intermediary 1", &intermediary1_children);
501  FlutterSemanticsNode2 intermediary2 =
502  CreateSemanticsNode(intermediary2_id, "intermediary 2");
503  FlutterSemanticsNode2 leaf1 = CreateSemanticsNode(leaf1_id, "leaf 1");
504 
505  bridge->AddFlutterSemanticsNodeUpdate(root);
506  bridge->AddFlutterSemanticsNodeUpdate(intermediary1);
507  bridge->AddFlutterSemanticsNodeUpdate(intermediary2);
508  bridge->AddFlutterSemanticsNodeUpdate(leaf1);
509  bridge->CommitUpdates();
510  bridge->accessibility_events.clear();
511 
512  // Move intermediary1 from root to intermediary 2.
513  int32_t new_root_children[] = {intermediary2_id};
514  root.child_count = 1;
515  root.children_in_traversal_order = new_root_children;
516 
517  int32_t new_intermediary2_children[] = {intermediary1_id};
518  intermediary2.child_count = 1;
519  intermediary2.children_in_traversal_order = new_intermediary2_children;
520 
521  bridge->AddFlutterSemanticsNodeUpdate(root);
522  bridge->AddFlutterSemanticsNodeUpdate(intermediary1);
523  bridge->AddFlutterSemanticsNodeUpdate(intermediary2);
524  bridge->AddFlutterSemanticsNodeUpdate(leaf1);
525  bridge->CommitUpdates();
526 
527  auto root_node = bridge->GetFlutterPlatformNodeDelegateFromID(root_id).lock();
528  auto intermediary1_node =
529  bridge->GetFlutterPlatformNodeDelegateFromID(intermediary1_id).lock();
530  auto intermediary2_node =
531  bridge->GetFlutterPlatformNodeDelegateFromID(intermediary2_id).lock();
532  auto leaf1_node =
533  bridge->GetFlutterPlatformNodeDelegateFromID(leaf1_id).lock();
534 
535  EXPECT_EQ(root_node->GetChildCount(), 1);
536  EXPECT_EQ(root_node->GetData().child_ids[0], intermediary2_id);
537  EXPECT_EQ(root_node->GetName(), "root");
538 
539  EXPECT_EQ(intermediary2_node->GetChildCount(), 1);
540  EXPECT_EQ(intermediary2_node->GetData().child_ids[0], intermediary1_id);
541  EXPECT_EQ(intermediary2_node->GetName(), "intermediary 2");
542 
543  EXPECT_EQ(intermediary1_node->GetChildCount(), 1);
544  EXPECT_EQ(intermediary1_node->GetData().child_ids[0], leaf1_id);
545  EXPECT_EQ(intermediary1_node->GetName(), "intermediary 1");
546 
547  EXPECT_EQ(leaf1_node->GetChildCount(), 0);
548  EXPECT_EQ(leaf1_node->GetName(), "leaf 1");
549 
550  ASSERT_EQ(bridge->accessibility_events.size(), size_t{5});
551 
552  EXPECT_THAT(bridge->accessibility_events,
553  Contains(ui::AXEventGenerator::Event::CHILDREN_CHANGED).Times(2));
554  EXPECT_THAT(bridge->accessibility_events,
555  Contains(ui::AXEventGenerator::Event::SUBTREE_CREATED).Times(1));
556 
557  // Intermediary 2 becomes a parent node. It updates to group role and enables
558  // its 'clip children' attribute.
559  EXPECT_THAT(
560  bridge->accessibility_events,
561  Contains(ui::AXEventGenerator::Event::OTHER_ATTRIBUTE_CHANGED).Times(1));
562  EXPECT_THAT(bridge->accessibility_events,
563  Contains(ui::AXEventGenerator::Event::ROLE_CHANGED).Times(1));
564 }
565 
566 TEST(AccessibilityBridgeTest, AXTreeManagerTest) {
567  std::shared_ptr<TestAccessibilityBridge> bridge =
568  std::make_shared<TestAccessibilityBridge>();
569 
570  ui::AXTreeID tree_id = bridge->GetTreeID();
571  ui::AXTreeManager* manager =
572  ui::AXTreeManagerMap::GetInstance().GetManager(tree_id);
573  ASSERT_EQ(manager, static_cast<ui::AXTreeManager*>(bridge.get()));
574 }
575 
576 TEST(AccessibilityBridgeTest, LineBreakingObjectTest) {
577  std::shared_ptr<TestAccessibilityBridge> bridge =
578  std::make_shared<TestAccessibilityBridge>();
579 
580  const int32_t root_id = 0;
581 
582  FlutterSemanticsNode2 root = CreateSemanticsNode(root_id, "root", {});
583 
584  bridge->AddFlutterSemanticsNodeUpdate(root);
585  bridge->CommitUpdates();
586 
587  auto root_node = bridge->GetFlutterPlatformNodeDelegateFromID(root_id).lock();
588  auto root_data = root_node->GetData();
589  EXPECT_TRUE(root_data.HasBoolAttribute(
590  ax::mojom::BoolAttribute::kIsLineBreakingObject));
591  EXPECT_TRUE(root_data.GetBoolAttribute(
592  ax::mojom::BoolAttribute::kIsLineBreakingObject));
593 }
594 
595 } // namespace testing
596 } // namespace flutter
flutter::FakeDelegate fake_delegate
TEST(AccessibilityBridgeTest, BasicTest)
FlutterSemanticsNode2 CreateSemanticsNode(int32_t id, const char *label, const std::vector< int32_t > *children=nullptr)
FlutterSemanticsFlags kEmptyFlags