| Flutter macOS 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"
11 #include "test_accessibility_bridge.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
45 TEST(AccessibilityBridgeTest, BasicTest) {
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.
116 TEST(AccessibilityBridgeTest, AddOrder) {
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
accessibility_bridge.h
flutter::testing::CreateSemanticsNode
FlutterSemanticsNode2 CreateSemanticsNode(int32_t id, const char *label, const std::vector< int32_t > *children=nullptr)
Definition: accessibility_bridge_unittests.cc:20
flutter::testing::kEmptyFlags
FlutterSemanticsFlags kEmptyFlags
Definition: accessibility_bridge_unittests.cc:18
flutter::testing::TEST
TEST(FlutterAppDelegateTest, DoesNotCallDelegatesWithoutHandler)
Definition: FlutterAppDelegateTest.mm:32
flutter
Definition: AccessibilityBridgeMac.h:16
test_accessibility_bridge.h