| Flutter iOS Embedder |

accessibility_bridge_test.mm
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 #import <OCMock/OCMock.h>
6 #import <XCTest/XCTest.h>
7
8 #import "flutter/fml/thread.h"
9 #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h"
10 #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterPlatformViews.h"
11 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h"
12 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterSemanticsScrollView.h"
13 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h"
14 #import "flutter/shell/platform/darwin/ios/framework/Source/accessibility_bridge.h"
15 #import "flutter/shell/platform/darwin/ios/platform_view_ios.h"
16
17 FLUTTER_ASSERT_ARC
18
19 @class MockPlatformView;
20 __weak static MockPlatformView* gMockPlatformView = nil;
21
22 @interface MockPlatformView : UIView
23 @end
24 @implementation MockPlatformView
25
26 - (instancetype)init {
27 self = [super init];
28 if (self) {
29 gMockPlatformView = self;
30 }
31 return self;
32 }
33
34 - (void)dealloc {
35 gMockPlatformView = nil;
36 }
37
38 @end
39
40 @interface MockFlutterPlatformView : NSObject <FlutterPlatformView>
41 @property(nonatomic, strong) UIView* view;
42 @end
43
44 @implementation MockFlutterPlatformView
45
46 - (instancetype)init {
47 if (self = [super init]) {
48 _view = [[MockPlatformView alloc] init];
49 }
50 return self;
51 }
52
53 @end
54
55 @interface MockFlutterPlatformFactory : NSObject <FlutterPlatformViewFactory>
56 @end
57
58 @implementation MockFlutterPlatformFactory
59 - (NSObject<FlutterPlatformView>*)createWithFrame:(CGRect)frame
60 viewIdentifier:(int64_t)viewId
61 arguments:(id _Nullable)args {
62 return [[MockFlutterPlatformView alloc] init];
63 }
64
65 @end
66
67 namespace flutter {
68 namespace {
69 class MockDelegate : public PlatformView::Delegate {
70 public:
71 void OnPlatformViewCreated(std::unique_ptr<Surface> surface) override {}
72 void OnPlatformViewDestroyed() override {}
73 void OnPlatformViewScheduleFrame() override {}
74 void OnPlatformViewAddView(int64_t view_id,
75 const ViewportMetrics& viewport_metrics,
76 AddViewCallback callback) override {}
77 void OnPlatformViewRemoveView(int64_t view_id, RemoveViewCallback callback) override {}
78 void OnPlatformViewSendViewFocusEvent(const ViewFocusEvent& event) override {};
79 void OnPlatformViewSetNextFrameCallback(const fml::closure& closure) override {}
80 void OnPlatformViewSetViewportMetrics(int64_t view_id, const ViewportMetrics& metrics) override {}
81 const flutter::Settings& OnPlatformViewGetSettings() const override { return settings_; }
82 void OnPlatformViewDispatchPlatformMessage(std::unique_ptr<PlatformMessage> message) override {}
83 void OnPlatformViewDispatchPointerDataPacket(std::unique_ptr<PointerDataPacket> packet) override {
84 }
85 void OnPlatformViewDispatchSemanticsAction(int64_t view_id,
86 int32_t node_id,
87 SemanticsAction action,
88 fml::MallocMapping args) override {}
89 void OnPlatformViewSetSemanticsEnabled(bool enabled) override {}
90 void OnPlatformViewSetAccessibilityFeatures(int32_t flags) override {}
91 void OnPlatformViewRegisterTexture(std::shared_ptr<Texture> texture) override {}
92 void OnPlatformViewUnregisterTexture(int64_t texture_id) override {}
93 void OnPlatformViewMarkTextureFrameAvailable(int64_t texture_id) override {}
94
95 void LoadDartDeferredLibrary(intptr_t loading_unit_id,
96 std::unique_ptr<const fml::Mapping> snapshot_data,
97 std::unique_ptr<const fml::Mapping> snapshot_instructions) override {
98 }
99 void LoadDartDeferredLibraryError(intptr_t loading_unit_id,
100 const std::string error_message,
101 bool transient) override {}
102 void UpdateAssetResolverByType(std::unique_ptr<flutter::AssetResolver> updated_asset_resolver,
103 flutter::AssetResolver::AssetResolverType type) override {}
104
105 flutter::Settings settings_;
106 };
107
108 class MockIosDelegate : public AccessibilityBridge::IosDelegate {
109 public:
110 bool IsFlutterViewControllerPresentingModalViewController(
111 FlutterViewController* view_controller) override {
112 return result_IsFlutterViewControllerPresentingModalViewController_;
113 };
114
115 void PostAccessibilityNotification(UIAccessibilityNotifications notification,
116 id argument) override {
117 if (on_PostAccessibilityNotification_) {
118 on_PostAccessibilityNotification_(notification, argument);
119 }
120 }
121 std::function<void(UIAccessibilityNotifications, id)> on_PostAccessibilityNotification_;
122 bool result_IsFlutterViewControllerPresentingModalViewController_ = false;
123 };
124 } // namespace
125 } // namespace flutter
126
127 namespace {
128 fml::RefPtr<fml::TaskRunner> CreateNewThread(const std::string& name) {
129 auto thread = std::make_unique<fml::Thread>(name);
130 auto runner = thread->GetTaskRunner();
131 return runner;
132 }
133 } // namespace
134
135 @interface AccessibilityBridgeTest : XCTestCase
136 @end
137
138 @implementation AccessibilityBridgeTest
139
140 - (void)testCreate {
141 flutter::MockDelegate mock_delegate;
142 auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
143 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
144 /*platform=*/thread_task_runner,
145 /*raster=*/thread_task_runner,
146 /*ui=*/thread_task_runner,
147 /*io=*/thread_task_runner);
148 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
149 /*delegate=*/mock_delegate,
150 /*rendering_api=*/mock_delegate.settings_.enable_impeller
151 ? flutter::IOSRenderingAPI::kMetal
152 : flutter::IOSRenderingAPI::kSoftware,
153 /*platform_views_controller=*/nil,
154 /*task_runners=*/runners,
155 /*worker_task_runner=*/nil,
156 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
157 auto bridge =
158 std::make_unique<flutter::AccessibilityBridge>(/*view=*/nil,
159 /*platform_view=*/platform_view.get(),
160 /*platform_views_controller=*/nil);
161 XCTAssertTrue(bridge.get());
162 }
163
164 - (void)testUpdateSemanticsEmpty {
165 flutter::MockDelegate mock_delegate;
166 auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
167 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
168 /*platform=*/thread_task_runner,
169 /*raster=*/thread_task_runner,
170 /*ui=*/thread_task_runner,
171 /*io=*/thread_task_runner);
172 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
173 /*delegate=*/mock_delegate,
174 /*rendering_api=*/mock_delegate.settings_.enable_impeller
175 ? flutter::IOSRenderingAPI::kMetal
176 : flutter::IOSRenderingAPI::kSoftware,
177 /*platform_views_controller=*/nil,
178 /*task_runners=*/runners,
179 /*worker_task_runner=*/nil,
180 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
181 id mockFlutterView = OCMClassMock([FlutterView class]);
182 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
183 OCMStub([mockFlutterViewController viewIfLoaded]).andReturn(mockFlutterView);
184 OCMExpect([mockFlutterView setAccessibilityElements:[OCMArg isNil]]);
185 auto bridge =
186 std::make_unique<flutter::AccessibilityBridge>(/*view_controller=*/mockFlutterViewController,
187 /*platform_view=*/platform_view.get(),
188 /*platform_views_controller=*/nil);
189 flutter::SemanticsNodeUpdates nodes;
190 flutter::CustomAccessibilityActionUpdates actions;
191 bridge->UpdateSemantics(/*nodes=*/nodes, /*actions=*/actions);
192 OCMVerifyAll(mockFlutterView);
193 }
194
195 - (void)testUpdateSemanticsOneNode {
196 flutter::MockDelegate mock_delegate;
197 auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
198 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
199 /*platform=*/thread_task_runner,
200 /*raster=*/thread_task_runner,
201 /*ui=*/thread_task_runner,
202 /*io=*/thread_task_runner);
203 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
204 /*delegate=*/mock_delegate,
205 /*rendering_api=*/mock_delegate.settings_.enable_impeller
206 ? flutter::IOSRenderingAPI::kMetal
207 : flutter::IOSRenderingAPI::kSoftware,
208 /*platform_views_controller=*/nil,
209 /*task_runners=*/runners,
210 /*worker_task_runner=*/nil,
211 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
212 id mockFlutterView = OCMClassMock([FlutterView class]);
213 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
214 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
215 std::string label = "some label";
216
217 __block auto bridge =
218 std::make_unique<flutter::AccessibilityBridge>(/*view_controller=*/mockFlutterViewController,
219 /*platform_view=*/platform_view.get(),
220 /*platform_views_controller=*/nil);
221
222 OCMExpect([mockFlutterView setAccessibilityElements:[OCMArg checkWithBlock:^BOOL(NSArray* value) {
223 if ([value count] != 1) {
224 return NO;
225 } else {
226 SemanticsObjectContainer* container = value[0];
227 SemanticsObject* object = container.semanticsObject;
228 return object.uid == kRootNodeId &&
229 object.bridge.get() == bridge.get() &&
230 object.node.label == label;
231 }
232 }]]);
233
234 flutter::SemanticsNodeUpdates nodes;
235 flutter::SemanticsNode semantics_node;
236 semantics_node.id = kRootNodeId;
237 semantics_node.label = label;
238 nodes[kRootNodeId] = semantics_node;
239 flutter::CustomAccessibilityActionUpdates actions;
240 bridge->UpdateSemantics(/*nodes=*/nodes, /*actions=*/actions);
241 OCMVerifyAll(mockFlutterView);
242 }
243
244 - (void)testIsVoiceOverRunning {
245 flutter::MockDelegate mock_delegate;
246 auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
247 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
248 /*platform=*/thread_task_runner,
249 /*raster=*/thread_task_runner,
250 /*ui=*/thread_task_runner,
251 /*io=*/thread_task_runner);
252 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
253 /*delegate=*/mock_delegate,
254 /*rendering_api=*/mock_delegate.settings_.enable_impeller
255 ? flutter::IOSRenderingAPI::kMetal
256 : flutter::IOSRenderingAPI::kSoftware,
257 /*platform_views_controller=*/nil,
258 /*task_runners=*/runners,
259 /*worker_task_runner=*/nil,
260 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
261 id mockFlutterView = OCMClassMock([FlutterView class]);
262 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
263 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
264 OCMStub([mockFlutterViewController isVoiceOverRunning]).andReturn(YES);
265
266 __block auto bridge =
267 std::make_unique<flutter::AccessibilityBridge>(/*view_controller=*/mockFlutterViewController,
268 /*platform_view=*/platform_view.get(),
269 /*platform_views_controller=*/nil);
270
271 XCTAssertTrue(bridge->isVoiceOverRunning());
272 }
273
274 - (void)testSemanticsDeallocated {
275 @autoreleasepool {
276 flutter::MockDelegate mock_delegate;
277 auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
278 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
279 /*platform=*/thread_task_runner,
280 /*raster=*/thread_task_runner,
281 /*ui=*/thread_task_runner,
282 /*io=*/thread_task_runner);
283
284 FlutterPlatformViewsController* flutterPlatformViewsController =
285 [[FlutterPlatformViewsController alloc] init];
286 flutterPlatformViewsController.taskRunner = thread_task_runner;
287 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
288 /*delegate=*/mock_delegate,
289 /*rendering_api=*/mock_delegate.settings_.enable_impeller
290 ? flutter::IOSRenderingAPI::kMetal
291 : flutter::IOSRenderingAPI::kSoftware,
292 /*platform_views_controller=*/flutterPlatformViewsController,
293 /*task_runners=*/runners,
294 /*worker_task_runner=*/nil,
295 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
296 id mockFlutterView = OCMClassMock([FlutterView class]);
297 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
298 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
299 std::string label = "some label";
300 flutterPlatformViewsController.flutterView = mockFlutterView;
301
302 MockFlutterPlatformFactory* factory = [[MockFlutterPlatformFactory alloc] init];
303 [flutterPlatformViewsController
304 registerViewFactory:factory
305 withId:@"MockFlutterPlatformView"
306 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
307 FlutterResult result = ^(id result) {
308 };
309 [flutterPlatformViewsController
310 onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"create"
311 arguments:@{
312 @"id" : @2,
313 @"viewType" : @"MockFlutterPlatformView"
314 }]
315 result:result];
316
317 auto bridge = std::make_unique<flutter::AccessibilityBridge>(
318 /*view_controller=*/mockFlutterViewController,
319 /*platform_view=*/platform_view.get(),
320 /*platform_views_controller=*/flutterPlatformViewsController);
321
322 flutter::SemanticsNodeUpdates nodes;
323 flutter::SemanticsNode semantics_node;
324 semantics_node.id = 2;
325 semantics_node.platformViewId = 2;
326 semantics_node.label = label;
327 nodes[kRootNodeId] = semantics_node;
328 flutter::CustomAccessibilityActionUpdates actions;
329 bridge->UpdateSemantics(/*nodes=*/nodes, /*actions=*/actions);
330 XCTAssertNotNil(gMockPlatformView);
331 [flutterPlatformViewsController reset];
332 }
333 XCTAssertNil(gMockPlatformView);
334 }
335
336 - (void)testSemanticsDeallocatedWithoutLoadingView {
337 id engine = OCMClassMock([FlutterEngine class]);
338 FlutterViewController* flutterViewController =
339 [[FlutterViewController alloc] initWithEngine:engine nibName:nil bundle:nil];
340 @autoreleasepool {
341 flutter::MockDelegate mock_delegate;
342 auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
343 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
344 /*platform=*/thread_task_runner,
345 /*raster=*/thread_task_runner,
346 /*ui=*/thread_task_runner,
347 /*io=*/thread_task_runner);
348
349 FlutterPlatformViewsController* flutterPlatformViewsController =
350 [[FlutterPlatformViewsController alloc] init];
351 flutterPlatformViewsController.taskRunner = thread_task_runner;
352 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
353 /*delegate=*/mock_delegate,
354 /*rendering_api=*/mock_delegate.settings_.enable_impeller
355 ? flutter::IOSRenderingAPI::kMetal
356 : flutter::IOSRenderingAPI::kSoftware,
357 /*platform_views_controller=*/flutterPlatformViewsController,
358 /*task_runners=*/runners,
359 /*worker_task_runner=*/nil,
360 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
361
362 MockFlutterPlatformFactory* factory = [[MockFlutterPlatformFactory alloc] init];
363 [flutterPlatformViewsController
364 registerViewFactory:factory
365 withId:@"MockFlutterPlatformView"
366 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
367 FlutterResult result = ^(id result) {
368 };
369 [flutterPlatformViewsController
370 onMethodCall:[FlutterMethodCall methodCallWithMethodName:@"create"
371 arguments:@{
372 @"id" : @2,
373 @"viewType" : @"MockFlutterPlatformView"
374 }]
375 result:result];
376
377 auto bridge = std::make_unique<flutter::AccessibilityBridge>(
378 /*view_controller=*/flutterViewController,
379 /*platform_view=*/platform_view.get(),
380 /*platform_views_controller=*/flutterPlatformViewsController);
381
382 XCTAssertNotNil(gMockPlatformView);
383 [flutterPlatformViewsController reset];
384 platform_view->NotifyDestroyed();
385 }
386 XCTAssertNil(gMockPlatformView);
387 XCTAssertNil(flutterViewController.viewIfLoaded);
388 [flutterViewController deregisterNotifications];
389 }
390
391 - (void)testReplacedSemanticsDoesNotCleanupChildren {
392 flutter::MockDelegate mock_delegate;
393 auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
394 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
395 /*platform=*/thread_task_runner,
396 /*raster=*/thread_task_runner,
397 /*ui=*/thread_task_runner,
398 /*io=*/thread_task_runner);
399
400 FlutterPlatformViewsController* flutterPlatformViewsController =
401 [[FlutterPlatformViewsController alloc] init];
402 flutterPlatformViewsController.taskRunner = thread_task_runner;
403 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
404 /*delegate=*/mock_delegate,
405 /*rendering_api=*/mock_delegate.settings_.enable_impeller
406 ? flutter::IOSRenderingAPI::kMetal
407 : flutter::IOSRenderingAPI::kSoftware,
408 /*platform_views_controller=*/flutterPlatformViewsController,
409 /*task_runners=*/runners,
410 /*worker_task_runner=*/nil,
411 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
412 id engine = OCMClassMock([FlutterEngine class]);
413 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
414 FlutterView* flutterView = [[FlutterView alloc] initWithDelegate:engine
415 opaque:YES
416 enableWideGamut:NO];
417 OCMStub([mockFlutterViewController view]).andReturn(flutterView);
418 std::string label = "some label";
419 auto bridge = std::make_unique<flutter::AccessibilityBridge>(
420 /*view_controller=*/mockFlutterViewController,
421 /*platform_view=*/platform_view.get(),
422 /*platform_views_controller=*/flutterPlatformViewsController);
423 @autoreleasepool {
424 flutter::SemanticsNodeUpdates nodes;
425 flutter::SemanticsNode parent;
426 parent.id = 0;
427 parent.rect = SkRect::MakeXYWH(0, 0, 100, 200);
428 parent.label = "label";
429 parent.value = "value";
430 parent.hint = "hint";
431
432 flutter::SemanticsNode node;
433 node.id = 1;
434 node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
435 node.label = "label";
436 node.value = "value";
437 node.hint = "hint";
438 node.scrollExtentMax = 100.0;
439 node.scrollPosition = 0.0;
440 parent.childrenInTraversalOrder.push_back(1);
441 parent.childrenInHitTestOrder.push_back(1);
442
443 flutter::SemanticsNode child;
444 child.id = 2;
445 child.rect = SkRect::MakeXYWH(0, 0, 100, 200);
446 child.label = "label";
447 child.value = "value";
448 child.hint = "hint";
449 node.childrenInTraversalOrder.push_back(2);
450 node.childrenInHitTestOrder.push_back(2);
451
452 nodes[0] = parent;
453 nodes[1] = node;
454 nodes[2] = child;
455 flutter::CustomAccessibilityActionUpdates actions;
456 bridge->UpdateSemantics(/*nodes=*/nodes, /*actions=*/actions);
457
458 // Add implicit scroll from node 1 to cause replacement.
459 flutter::SemanticsNodeUpdates new_nodes;
460 flutter::SemanticsNode new_node;
461 new_node.id = 1;
462 new_node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
463 new_node.flags.hasImplicitScrolling = true;
464 new_node.actions = flutter::kHorizontalScrollSemanticsActions;
465 new_node.label = "label";
466 new_node.value = "value";
467 new_node.hint = "hint";
468 new_node.scrollExtentMax = 100.0;
469 new_node.scrollPosition = 0.0;
470 new_node.childrenInTraversalOrder.push_back(2);
471 new_node.childrenInHitTestOrder.push_back(2);
472
473 new_nodes[1] = new_node;
474 bridge->UpdateSemantics(/*nodes=*/new_nodes, /*actions=*/actions);
475 }
476 /// The old node should be deallocated at this moment. Procced to check
477 /// accessibility tree integrity.
478 id rootContainer = flutterView.accessibilityElements[0];
479 XCTAssertTrue([rootContainer accessibilityElementCount] ==
480 2); // one for root, one for scrollable.
481 id scrollableContainer = [rootContainer accessibilityElementAtIndex:1];
482 XCTAssertTrue([scrollableContainer accessibilityElementCount] ==
483 2); // one for scrollable, one for scrollable child.
484 id child = [scrollableContainer accessibilityElementAtIndex:1];
485 /// Replacing node 1 should not accidentally clean up its child's container.
486 XCTAssertNotNil([child accessibilityContainer]);
487 }
488
489 - (void)testScrollableSemanticsDeallocated {
490 flutter::MockDelegate mock_delegate;
491 auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
492 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
493 /*platform=*/thread_task_runner,
494 /*raster=*/thread_task_runner,
495 /*ui=*/thread_task_runner,
496 /*io=*/thread_task_runner);
497
498 FlutterPlatformViewsController* flutterPlatformViewsController =
499 [[FlutterPlatformViewsController alloc] init];
500 flutterPlatformViewsController.taskRunner = thread_task_runner;
501 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
502 /*delegate=*/mock_delegate,
503 /*rendering_api=*/mock_delegate.settings_.enable_impeller
504 ? flutter::IOSRenderingAPI::kMetal
505 : flutter::IOSRenderingAPI::kSoftware,
506 /*platform_views_controller=*/flutterPlatformViewsController,
507 /*task_runners=*/runners,
508 /*worker_task_runner=*/nil,
509 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
510 id engine = OCMClassMock([FlutterEngine class]);
511 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
512 FlutterView* flutterView = [[FlutterView alloc] initWithDelegate:engine
513 opaque:YES
514 enableWideGamut:NO];
515 OCMStub([mockFlutterViewController view]).andReturn(flutterView);
516 std::string label = "some label";
517 @autoreleasepool {
518 auto bridge = std::make_unique<flutter::AccessibilityBridge>(
519 /*view_controller=*/mockFlutterViewController,
520 /*platform_view=*/platform_view.get(),
521 /*platform_views_controller=*/flutterPlatformViewsController);
522
523 flutter::SemanticsNodeUpdates nodes;
524 flutter::SemanticsNode parent;
525 parent.id = 0;
526 parent.rect = SkRect::MakeXYWH(0, 0, 100, 200);
527 parent.label = "label";
528 parent.value = "value";
529 parent.hint = "hint";
530
531 flutter::SemanticsNode node;
532 node.id = 1;
533 node.flags.hasImplicitScrolling = true;
534 node.actions = flutter::kHorizontalScrollSemanticsActions;
535 node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
536 node.label = "label";
537 node.value = "value";
538 node.hint = "hint";
539 node.scrollExtentMax = 100.0;
540 node.scrollPosition = 0.0;
541 parent.childrenInTraversalOrder.push_back(1);
542 parent.childrenInHitTestOrder.push_back(1);
543 nodes[0] = parent;
544 nodes[1] = node;
545 flutter::CustomAccessibilityActionUpdates actions;
546 bridge->UpdateSemantics(/*nodes=*/nodes, /*actions=*/actions);
547 XCTAssertTrue([flutterView.subviews count] == 1);
548 XCTAssertTrue([flutterView.subviews[0] isKindOfClass:[FlutterSemanticsScrollView class]]);
549 XCTAssertTrue([flutterView.subviews[0].accessibilityLabel isEqualToString:@"label"]);
550
551 // Remove the scrollable from the tree.
552 flutter::SemanticsNodeUpdates new_nodes;
553 flutter::SemanticsNode new_parent;
554 new_parent.id = 0;
555 new_parent.rect = SkRect::MakeXYWH(0, 0, 100, 200);
556 new_parent.label = "label";
557 new_parent.value = "value";
558 new_parent.hint = "hint";
559 new_nodes[0] = new_parent;
560 bridge->UpdateSemantics(/*nodes=*/new_nodes, /*actions=*/actions);
561 }
562 XCTAssertTrue([flutterView.subviews count] == 0);
563 }
564
565 - (void)testBridgeReplacesSemanticsNode {
566 flutter::MockDelegate mock_delegate;
567 auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
568 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
569 /*platform=*/thread_task_runner,
570 /*raster=*/thread_task_runner,
571 /*ui=*/thread_task_runner,
572 /*io=*/thread_task_runner);
573
574 FlutterPlatformViewsController* flutterPlatformViewsController =
575 [[FlutterPlatformViewsController alloc] init];
576 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
577 /*delegate=*/mock_delegate,
578 /*rendering_api=*/mock_delegate.settings_.enable_impeller
579 ? flutter::IOSRenderingAPI::kMetal
580 : flutter::IOSRenderingAPI::kSoftware,
581 /*platform_views_controller=*/flutterPlatformViewsController,
582 /*task_runners=*/runners,
583 /*worker_task_runner=*/nil,
584 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
585 id engine = OCMClassMock([FlutterEngine class]);
586 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
587 FlutterView* flutterView = [[FlutterView alloc] initWithDelegate:engine
588 opaque:YES
589 enableWideGamut:NO];
590 OCMStub([mockFlutterViewController view]).andReturn(flutterView);
591 std::string label = "some label";
592 @autoreleasepool {
593 auto bridge = std::make_unique<flutter::AccessibilityBridge>(
594 /*view_controller=*/mockFlutterViewController,
595 /*platform_view=*/platform_view.get(),
596 /*platform_views_controller=*/flutterPlatformViewsController);
597
598 flutter::SemanticsNodeUpdates nodes;
599 flutter::SemanticsNode parent;
600 parent.id = 0;
601 parent.rect = SkRect::MakeXYWH(0, 0, 100, 200);
602 parent.label = "label";
603 parent.value = "value";
604 parent.hint = "hint";
605
606 flutter::SemanticsNode node;
607 node.id = 1;
608 node.flags.hasImplicitScrolling = true;
609 node.actions = flutter::kHorizontalScrollSemanticsActions;
610 node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
611 node.label = "label";
612 node.value = "value";
613 node.hint = "hint";
614 node.scrollExtentMax = 100.0;
615 node.scrollPosition = 0.0;
616 parent.childrenInTraversalOrder.push_back(1);
617 parent.childrenInHitTestOrder.push_back(1);
618 nodes[0] = parent;
619 nodes[1] = node;
620 flutter::CustomAccessibilityActionUpdates actions;
621 bridge->UpdateSemantics(/*nodes=*/nodes, /*actions=*/actions);
622 XCTAssertTrue([flutterView.subviews count] == 1);
623 XCTAssertTrue([flutterView.subviews[0] isKindOfClass:[FlutterSemanticsScrollView class]]);
624 XCTAssertTrue([flutterView.subviews[0].accessibilityLabel isEqualToString:@"label"]);
625
626 // Remove implicit scroll from node 1.
627 flutter::SemanticsNodeUpdates new_nodes;
628 flutter::SemanticsNode new_node;
629 new_node.id = 1;
630 new_node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
631 new_node.label = "label";
632 new_node.value = "value";
633 new_node.hint = "hint";
634 new_node.scrollExtentMax = 100.0;
635 new_node.scrollPosition = 0.0;
636 new_nodes[1] = new_node;
637 bridge->UpdateSemantics(/*nodes=*/new_nodes, /*actions=*/actions);
638 }
639 XCTAssertTrue([flutterView.subviews count] == 0);
640 }
641
642 - (void)testAnnouncesRouteChanges {
643 flutter::MockDelegate mock_delegate;
644 auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
645 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
646 /*platform=*/thread_task_runner,
647 /*raster=*/thread_task_runner,
648 /*ui=*/thread_task_runner,
649 /*io=*/thread_task_runner);
650 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
651 /*delegate=*/mock_delegate,
652 /*rendering_api=*/mock_delegate.settings_.enable_impeller
653 ? flutter::IOSRenderingAPI::kMetal
654 : flutter::IOSRenderingAPI::kSoftware,
655 /*platform_views_controller=*/nil,
656 /*task_runners=*/runners,
657 /*worker_task_runner=*/nil,
658 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
659 id mockFlutterView = OCMClassMock([FlutterView class]);
660 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
661 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
662
663 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
664 [[NSMutableArray alloc] init];
665 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
666 ios_delegate->on_PostAccessibilityNotification_ =
667 [accessibility_notifications](UIAccessibilityNotifications notification, id argument) {
668 [accessibility_notifications addObject:@{
669 @"notification" : @(notification),
670 @"argument" : argument ? argument : [NSNull null],
671 }];
672 };
673 __block auto bridge =
674 std::make_unique<flutter::AccessibilityBridge>(/*view_controller=*/mockFlutterViewController,
675 /*platform_view=*/platform_view.get(),
676 /*platform_views_controller=*/nil,
677 /*ios_delegate=*/std::move(ios_delegate));
678
679 flutter::CustomAccessibilityActionUpdates actions;
680 flutter::SemanticsNodeUpdates nodes;
681
682 flutter::SemanticsNode node1;
683 node1.id = 1;
684 node1.label = "node1";
685 node1.flags.scopesRoute = true;
686 node1.childrenInTraversalOrder = {2, 3};
687 node1.childrenInHitTestOrder = {2, 3};
688 nodes[node1.id] = node1;
689 flutter::SemanticsNode node2;
690 node2.id = 2;
691 node2.label = "node2";
692 nodes[node2.id] = node2;
693 flutter::SemanticsNode node3;
694 node3.id = 3;
695 node3.flags.namesRoute = true;
696 node3.label = "node3";
697 nodes[node3.id] = node3;
698 flutter::SemanticsNode root_node;
699 root_node.id = kRootNodeId;
700 root_node.flags.scopesRoute = true;
701 root_node.childrenInTraversalOrder = {1};
702 root_node.childrenInHitTestOrder = {1};
703 nodes[root_node.id] = root_node;
704 bridge->UpdateSemantics(/*nodes=*/nodes, /*actions=*/actions);
705
706 XCTAssertEqual([accessibility_notifications count], 1ul);
707 XCTAssertEqualObjects(accessibility_notifications[0][@"argument"], @"node3");
708 XCTAssertEqual([accessibility_notifications[0][@"notification"] unsignedIntValue],
709 UIAccessibilityScreenChangedNotification);
710 }
711
712 - (void)testRadioButtonIsNotSwitchButton {
713 flutter::MockDelegate mock_delegate;
714 auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
715 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
716 /*platform=*/thread_task_runner,
717 /*raster=*/thread_task_runner,
718 /*ui=*/thread_task_runner,
719 /*io=*/thread_task_runner);
720 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
721 /*delegate=*/mock_delegate,
722 /*rendering_api=*/mock_delegate.settings_.enable_impeller
723 ? flutter::IOSRenderingAPI::kMetal
724 : flutter::IOSRenderingAPI::kSoftware,
725 /*platform_views_controller=*/nil,
726 /*task_runners=*/runners,
727 /*worker_task_runner=*/nil,
728 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
729 id engine = OCMClassMock([FlutterEngine class]);
730 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
731 FlutterView* flutterView = [[FlutterView alloc] initWithDelegate:engine
732 opaque:YES
733 enableWideGamut:NO];
734 OCMStub([mockFlutterViewController view]).andReturn(flutterView);
735 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
736 __block auto bridge =
737 std::make_unique<flutter::AccessibilityBridge>(/*view_controller=*/mockFlutterViewController,
738 /*platform_view=*/platform_view.get(),
739 /*platform_views_controller=*/nil,
740 /*ios_delegate=*/std::move(ios_delegate));
741
742 flutter::CustomAccessibilityActionUpdates actions;
743 flutter::SemanticsNodeUpdates nodes;
744
745 flutter::SemanticsNode root_node;
746 root_node.id = kRootNodeId;
747 root_node.flags.isInMutuallyExclusiveGroup = true;
748 root_node.flags.isEnabled = flutter::SemanticsTristate::kTrue;
749 root_node.flags.isChecked = flutter::SemanticsCheckState::kFalse;
750 nodes[root_node.id] = root_node;
751 bridge->UpdateSemantics(/*nodes=*/nodes, /*actions=*/actions);
752
753 SemanticsObjectContainer* rootContainer = flutterView.accessibilityElements[0];
754 FlutterSemanticsObject* rootNode = [rootContainer accessibilityElementAtIndex:0];
755
756 XCTAssertTrue((rootNode.accessibilityTraits & UIAccessibilityTraitButton) > 0);
757 XCTAssertNil(rootNode.accessibilityValue);
758 }
759
760 - (void)testSemanticObjectWithNoAccessibilityFlagNotMarkedAsResponsiveToUserInteraction {
761 flutter::MockDelegate mock_delegate;
762 auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
763 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
764 /*platform=*/thread_task_runner,
765 /*raster=*/thread_task_runner,
766 /*ui=*/thread_task_runner,
767 /*io=*/thread_task_runner);
768 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
769 /*delegate=*/mock_delegate,
770 /*rendering_api=*/mock_delegate.settings_.enable_impeller
771 ? flutter::IOSRenderingAPI::kMetal
772 : flutter::IOSRenderingAPI::kSoftware,
773 /*platform_views_controller=*/nil,
774 /*task_runners=*/runners,
775 /*worker_task_runner=*/nil,
776 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
777 id engine = OCMClassMock([FlutterEngine class]);
778 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
779 FlutterView* flutterView = [[FlutterView alloc] initWithDelegate:engine
780 opaque:YES
781 enableWideGamut:NO];
782 OCMStub([mockFlutterViewController view]).andReturn(flutterView);
783 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
784 __block auto bridge =
785 std::make_unique<flutter::AccessibilityBridge>(/*view_controller=*/mockFlutterViewController,
786 /*platform_view=*/platform_view.get(),
787 /*platform_views_controller=*/nil,
788 /*ios_delegate=*/std::move(ios_delegate));
789
790 flutter::CustomAccessibilityActionUpdates actions;
791 flutter::SemanticsNodeUpdates nodes;
792
793 flutter::SemanticsNode root_node;
794 root_node.id = kRootNodeId;
795
796 nodes[root_node.id] = root_node;
797 bridge->UpdateSemantics(/*nodes=*/nodes, /*actions=*/actions);
798
799 SemanticsObjectContainer* rootContainer = flutterView.accessibilityElements[0];
800 FlutterSemanticsObject* rootNode = [rootContainer accessibilityElementAtIndex:0];
801
802 XCTAssertFalse(rootNode.accessibilityRespondsToUserInteraction);
803 }
804
805 - (void)testSemanticObjectWithAccessibilityFlagsMarkedAsResponsiveToUserInteraction {
806 flutter::MockDelegate mock_delegate;
807 auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
808 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
809 /*platform=*/thread_task_runner,
810 /*raster=*/thread_task_runner,
811 /*ui=*/thread_task_runner,
812 /*io=*/thread_task_runner);
813 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
814 /*delegate=*/mock_delegate,
815 /*rendering_api=*/mock_delegate.settings_.enable_impeller
816 ? flutter::IOSRenderingAPI::kMetal
817 : flutter::IOSRenderingAPI::kSoftware,
818 /*platform_views_controller=*/nil,
819 /*task_runners=*/runners,
820 /*worker_task_runner=*/nil,
821 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
822 id engine = OCMClassMock([FlutterEngine class]);
823 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
824 FlutterView* flutterView = [[FlutterView alloc] initWithDelegate:engine
825 opaque:YES
826 enableWideGamut:NO];
827 OCMStub([mockFlutterViewController view]).andReturn(flutterView);
828 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
829 __block auto bridge =
830 std::make_unique<flutter::AccessibilityBridge>(/*view_controller=*/mockFlutterViewController,
831 /*platform_view=*/platform_view.get(),
832 /*platform_views_controller=*/nil,
833 /*ios_delegate=*/std::move(ios_delegate));
834
835 flutter::CustomAccessibilityActionUpdates actions;
836 flutter::SemanticsNodeUpdates nodes;
837
838 flutter::SemanticsNode root_node;
839 root_node.id = kRootNodeId;
840 root_node.actions = static_cast<int32_t>(flutter::SemanticsAction::kTap);
841
842 nodes[root_node.id] = root_node;
843 bridge->UpdateSemantics(/*nodes=*/nodes, /*actions=*/actions);
844
845 SemanticsObjectContainer* rootContainer = flutterView.accessibilityElements[0];
846 FlutterSemanticsObject* rootNode = [rootContainer accessibilityElementAtIndex:0];
847
848 XCTAssertTrue(rootNode.accessibilityRespondsToUserInteraction);
849 }
850
851 // Regression test for:
852 // https://github.com/flutter/flutter/issues/158477
853 - (void)testLabeledParentAndChildNotInteractive {
854 flutter::MockDelegate mock_delegate;
855 auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
856 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
857 /*platform=*/thread_task_runner,
858 /*raster=*/thread_task_runner,
859 /*ui=*/thread_task_runner,
860 /*io=*/thread_task_runner);
861
862 FlutterPlatformViewsController* flutterPlatformViewsController =
863 [[FlutterPlatformViewsController alloc] init];
864 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
865 /*delegate=*/mock_delegate,
866 /*rendering_api=*/mock_delegate.settings_.enable_impeller
867 ? flutter::IOSRenderingAPI::kMetal
868 : flutter::IOSRenderingAPI::kSoftware,
869 /*platform_views_controller=*/flutterPlatformViewsController,
870 /*task_runners=*/runners,
871 /*worker_task_runner=*/nil,
872 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
873 id engine = OCMClassMock([FlutterEngine class]);
874 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
875 FlutterView* flutterView = [[FlutterView alloc] initWithDelegate:engine
876 opaque:YES
877 enableWideGamut:NO];
878 OCMStub([mockFlutterViewController view]).andReturn(flutterView);
879
880 @autoreleasepool {
881 auto bridge = std::make_unique<flutter::AccessibilityBridge>(
882 /*view_controller=*/mockFlutterViewController,
883 /*platform_view=*/platform_view.get(),
884 /*platform_views_controller=*/flutterPlatformViewsController);
885
886 flutter::SemanticsNodeUpdates nodes;
887
888 flutter::SemanticsNode parent;
889 parent.id = 0;
890 parent.rect = SkRect::MakeXYWH(0, 0, 100, 200);
891 parent.label = "parent_label";
892
893 flutter::SemanticsNode node;
894 node.id = 1;
895 node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
896 node.label = "child_label";
897
898 parent.childrenInTraversalOrder.push_back(1);
899 parent.childrenInHitTestOrder.push_back(1);
900 nodes[0] = parent;
901 nodes[1] = node;
902 flutter::CustomAccessibilityActionUpdates actions;
903 bridge->UpdateSemantics(/*nodes=*/nodes, /*actions=*/actions);
904
905 SemanticsObjectContainer* parentContainer = flutterView.accessibilityElements[0];
906 FlutterSemanticsObject* parentNode = [parentContainer accessibilityElementAtIndex:0];
907 FlutterSemanticsObject* childNode = [parentContainer accessibilityElementAtIndex:1];
908
909 XCTAssertTrue([parentNode.accessibilityLabel isEqualToString:@"parent_label"]);
910 XCTAssertTrue([childNode.accessibilityLabel isEqualToString:@"child_label"]);
911 XCTAssertFalse(parentNode.accessibilityRespondsToUserInteraction);
912 XCTAssertFalse(childNode.accessibilityRespondsToUserInteraction);
913 }
914 }
915
916 - (void)testLayoutChangeWithNonAccessibilityElement {
917 flutter::MockDelegate mock_delegate;
918 auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
919 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
920 /*platform=*/thread_task_runner,
921 /*raster=*/thread_task_runner,
922 /*ui=*/thread_task_runner,
923 /*io=*/thread_task_runner);
924 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
925 /*delegate=*/mock_delegate,
926 /*rendering_api=*/mock_delegate.settings_.enable_impeller
927 ? flutter::IOSRenderingAPI::kMetal
928 : flutter::IOSRenderingAPI::kSoftware,
929 /*platform_views_controller=*/nil,
930 /*task_runners=*/runners,
931 /*worker_task_runner=*/nil,
932 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
933 id mockFlutterView = OCMClassMock([FlutterView class]);
934 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
935 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
936
937 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
938 [[NSMutableArray alloc] init];
939 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
940 ios_delegate->on_PostAccessibilityNotification_ =
941 [accessibility_notifications](UIAccessibilityNotifications notification, id argument) {
942 [accessibility_notifications addObject:@{
943 @"notification" : @(notification),
944 @"argument" : argument ? argument : [NSNull null],
945 }];
946 };
947 __block auto bridge =
948 std::make_unique<flutter::AccessibilityBridge>(/*view_controller=*/mockFlutterViewController,
949 /*platform_view=*/platform_view.get(),
950 /*platform_views_controller=*/nil,
951 /*ios_delegate=*/std::move(ios_delegate));
952
953 flutter::CustomAccessibilityActionUpdates actions;
954 flutter::SemanticsNodeUpdates nodes;
955
956 flutter::SemanticsNode node1;
957 node1.id = 1;
958 node1.label = "node1";
959 node1.childrenInTraversalOrder = {2, 3};
960 node1.childrenInHitTestOrder = {2, 3};
961 nodes[node1.id] = node1;
962 flutter::SemanticsNode node2;
963 node2.id = 2;
964 node2.label = "node2";
965 nodes[node2.id] = node2;
966 flutter::SemanticsNode node3;
967 node3.id = 3;
968 node3.label = "node3";
969 nodes[node3.id] = node3;
970 flutter::SemanticsNode root_node;
971 root_node.id = kRootNodeId;
972 root_node.label = "root";
973 root_node.childrenInTraversalOrder = {1};
974 root_node.childrenInHitTestOrder = {1};
975 nodes[root_node.id] = root_node;
976 bridge->UpdateSemantics(/*nodes=*/nodes, /*actions=*/actions);
977
978 // Simulates the focusing on the node 1.
979 bridge->AccessibilityObjectDidBecomeFocused(1);
980
981 // In this update, we make node 1 unfocusable and trigger the
982 // layout change. The accessibility bridge should send layoutchange
983 // notification with the first focusable node under node 1
984 flutter::CustomAccessibilityActionUpdates new_actions;
985 flutter::SemanticsNodeUpdates new_nodes;
986
987 flutter::SemanticsNode new_node1;
988 new_node1.id = 1;
989 new_node1.childrenInTraversalOrder = {2};
990 new_node1.childrenInHitTestOrder = {2};
991 new_nodes[new_node1.id] = new_node1;
992 bridge->UpdateSemantics(/*nodes=*/new_nodes, /*actions=*/new_actions);
993
994 XCTAssertEqual([accessibility_notifications count], 1ul);
995 SemanticsObject* focusObject = accessibility_notifications[0][@"argument"];
996 // Since node 1 is no longer focusable (no label), it will focus node 2 instead.
997 XCTAssertEqual([focusObject uid], 2);
998 XCTAssertEqual([accessibility_notifications[0][@"notification"] unsignedIntValue],
999 UIAccessibilityLayoutChangedNotification);
1000 }
1001
1002 - (void)testLayoutChangeDoesCallNativeAccessibility {
1003 flutter::MockDelegate mock_delegate;
1004 auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
1005 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
1006 /*platform=*/thread_task_runner,
1007 /*raster=*/thread_task_runner,
1008 /*ui=*/thread_task_runner,
1009 /*io=*/thread_task_runner);
1010 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1011 /*delegate=*/mock_delegate,
1012 /*rendering_api=*/mock_delegate.settings_.enable_impeller
1013 ? flutter::IOSRenderingAPI::kMetal
1014 : flutter::IOSRenderingAPI::kSoftware,
1015 /*platform_views_controller=*/nil,
1016 /*task_runners=*/runners,
1017 /*worker_task_runner=*/nil,
1018 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
1019 id mockFlutterView = OCMClassMock([FlutterView class]);
1020 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
1021 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1022
1023 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1024 [[NSMutableArray alloc] init];
1025 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1026 ios_delegate->on_PostAccessibilityNotification_ =
1027 [accessibility_notifications](UIAccessibilityNotifications notification, id argument) {
1028 [accessibility_notifications addObject:@{
1029 @"notification" : @(notification),
1030 @"argument" : argument ? argument : [NSNull null],
1031 }];
1032 };
1033 __block auto bridge =
1034 std::make_unique<flutter::AccessibilityBridge>(/*view_controller=*/mockFlutterViewController,
1035 /*platform_view=*/platform_view.get(),
1036 /*platform_views_controller=*/nil,
1037 /*ios_delegate=*/std::move(ios_delegate));
1038
1039 flutter::CustomAccessibilityActionUpdates actions;
1040 flutter::SemanticsNodeUpdates nodes;
1041
1042 flutter::SemanticsNode node1;
1043 node1.id = 1;
1044 node1.label = "node1";
1045 nodes[node1.id] = node1;
1046 flutter::SemanticsNode root_node;
1047 root_node.id = kRootNodeId;
1048 root_node.label = "root";
1049 root_node.flags.hasImplicitScrolling = true;
1050 root_node.childrenInTraversalOrder = {1};
1051 root_node.childrenInHitTestOrder = {1};
1052 nodes[root_node.id] = root_node;
1053 bridge->UpdateSemantics(/*nodes=*/nodes, /*actions=*/actions);
1054
1055 // Simulates the focusing on the node 0.
1056 bridge->AccessibilityObjectDidBecomeFocused(0);
1057
1058 // Remove node 1 to trigger a layout change notification
1059 flutter::CustomAccessibilityActionUpdates new_actions;
1060 flutter::SemanticsNodeUpdates new_nodes;
1061
1062 flutter::SemanticsNode new_root_node;
1063 new_root_node.id = kRootNodeId;
1064 new_root_node.label = "root";
1065 new_root_node.flags.hasImplicitScrolling = true;
1066 new_nodes[new_root_node.id] = new_root_node;
1067 bridge->UpdateSemantics(/*nodes=*/new_nodes, /*actions=*/new_actions);
1068
1069 XCTAssertEqual([accessibility_notifications count], 1ul);
1070 id focusObject = accessibility_notifications[0][@"argument"];
1071
1072 // Make sure the focused item is not specificed when it stays the same.
1073 // See: https://github.com/flutter/flutter/issues/104176
1074 XCTAssertEqualObjects(focusObject, [NSNull null]);
1075 XCTAssertEqual([accessibility_notifications[0][@"notification"] unsignedIntValue],
1076 UIAccessibilityLayoutChangedNotification);
1077 }
1078
1079 - (void)testLayoutChangeDoesCallNativeAccessibilityWhenFocusChanged {
1080 flutter::MockDelegate mock_delegate;
1081 auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
1082 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
1083 /*platform=*/thread_task_runner,
1084 /*raster=*/thread_task_runner,
1085 /*ui=*/thread_task_runner,
1086 /*io=*/thread_task_runner);
1087 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1088 /*delegate=*/mock_delegate,
1089 /*rendering_api=*/mock_delegate.settings_.enable_impeller
1090 ? flutter::IOSRenderingAPI::kMetal
1091 : flutter::IOSRenderingAPI::kSoftware,
1092 /*platform_views_controller=*/nil,
1093 /*task_runners=*/runners,
1094 /*worker_task_runner=*/nil,
1095 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
1096 id mockFlutterView = OCMClassMock([FlutterView class]);
1097 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
1098 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1099
1100 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1101 [[NSMutableArray alloc] init];
1102 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1103 ios_delegate->on_PostAccessibilityNotification_ =
1104 [accessibility_notifications](UIAccessibilityNotifications notification, id argument) {
1105 [accessibility_notifications addObject:@{
1106 @"notification" : @(notification),
1107 @"argument" : argument ? argument : [NSNull null],
1108 }];
1109 };
1110 __block auto bridge =
1111 std::make_unique<flutter::AccessibilityBridge>(/*view_controller=*/mockFlutterViewController,
1112 /*platform_view=*/platform_view.get(),
1113 /*platform_views_controller=*/nil,
1114 /*ios_delegate=*/std::move(ios_delegate));
1115
1116 flutter::CustomAccessibilityActionUpdates actions;
1117 flutter::SemanticsNodeUpdates nodes;
1118
1119 flutter::SemanticsNode node1;
1120 node1.id = 1;
1121 node1.label = "node1";
1122 nodes[node1.id] = node1;
1123 flutter::SemanticsNode root_node;
1124 root_node.id = kRootNodeId;
1125 root_node.label = "root";
1126 root_node.flags.hasImplicitScrolling = true;
1127 root_node.childrenInTraversalOrder = {1};
1128 root_node.childrenInHitTestOrder = {1};
1129 nodes[root_node.id] = root_node;
1130 bridge->UpdateSemantics(/*nodes=*/nodes, /*actions=*/actions);
1131
1132 // Simulates the focusing on the node 1.
1133 bridge->AccessibilityObjectDidBecomeFocused(1);
1134
1135 // Remove node 1 to trigger a layout change notification, and focus should be one root
1136 flutter::CustomAccessibilityActionUpdates new_actions;
1137 flutter::SemanticsNodeUpdates new_nodes;
1138
1139 flutter::SemanticsNode new_root_node;
1140 new_root_node.id = kRootNodeId;
1141 new_root_node.label = "root";
1142 new_root_node.flags.hasImplicitScrolling = true;
1143 new_nodes[new_root_node.id] = new_root_node;
1144 bridge->UpdateSemantics(/*nodes=*/new_nodes, /*actions=*/new_actions);
1145
1146 XCTAssertEqual([accessibility_notifications count], 1ul);
1147 SemanticsObject* focusObject2 = accessibility_notifications[0][@"argument"];
1148
1149 // Bridge should ask accessibility to focus on root because node 1 is moved from screen.
1150 XCTAssertTrue([focusObject2 isKindOfClass:[FlutterSemanticsScrollView class]]);
1151 XCTAssertEqual([accessibility_notifications[0][@"notification"] unsignedIntValue],
1152 UIAccessibilityLayoutChangedNotification);
1153 }
1154
1155 - (void)testScrollableSemanticsContainerReturnsCorrectChildren {
1156 flutter::MockDelegate mock_delegate;
1157 auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
1158 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
1159 /*platform=*/thread_task_runner,
1160 /*raster=*/thread_task_runner,
1161 /*ui=*/thread_task_runner,
1162 /*io=*/thread_task_runner);
1163 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1164 /*delegate=*/mock_delegate,
1165 /*rendering_api=*/mock_delegate.settings_.enable_impeller
1166 ? flutter::IOSRenderingAPI::kMetal
1167 : flutter::IOSRenderingAPI::kSoftware,
1168 /*platform_views_controller=*/nil,
1169 /*task_runners=*/runners,
1170 /*worker_task_runner=*/nil,
1171 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
1172 id mockFlutterView = OCMClassMock([FlutterView class]);
1173 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
1174 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1175
1176 OCMExpect([mockFlutterView
1177 setAccessibilityElements:[OCMArg checkWithBlock:^BOOL(NSArray* value) {
1178 if ([value count] != 1) {
1179 return NO;
1180 }
1181 SemanticsObjectContainer* container = value[0];
1182 SemanticsObject* object = container.semanticsObject;
1183 FlutterScrollableSemanticsObject* scrollable =
1184 (FlutterScrollableSemanticsObject*)object.children[0];
1185 id nativeScrollable = scrollable.nativeAccessibility;
1186 SemanticsObjectContainer* scrollableContainer = [nativeScrollable accessibilityContainer];
1187 return [scrollableContainer indexOfAccessibilityElement:nativeScrollable] == 1;
1188 }]]);
1189 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1190 __block auto bridge =
1191 std::make_unique<flutter::AccessibilityBridge>(/*view_controller=*/mockFlutterViewController,
1192 /*platform_view=*/platform_view.get(),
1193 /*platform_views_controller=*/nil,
1194 /*ios_delegate=*/std::move(ios_delegate));
1195
1196 flutter::CustomAccessibilityActionUpdates actions;
1197 flutter::SemanticsNodeUpdates nodes;
1198
1199 flutter::SemanticsNode node1;
1200 node1.id = 1;
1201 node1.label = "node1";
1202 node1.flags.hasImplicitScrolling = true;
1203 nodes[node1.id] = node1;
1204 flutter::SemanticsNode root_node;
1205 root_node.id = kRootNodeId;
1206 root_node.label = "root";
1207 root_node.childrenInTraversalOrder = {1};
1208 root_node.childrenInHitTestOrder = {1};
1209 nodes[root_node.id] = root_node;
1210 bridge->UpdateSemantics(/*nodes=*/nodes, /*actions=*/actions);
1211 OCMVerifyAll(mockFlutterView);
1212 }
1213
1214 - (void)testAnnouncesRouteChangesAndLayoutChangeInOneUpdate {
1215 flutter::MockDelegate mock_delegate;
1216 auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
1217 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
1218 /*platform=*/thread_task_runner,
1219 /*raster=*/thread_task_runner,
1220 /*ui=*/thread_task_runner,
1221 /*io=*/thread_task_runner);
1222 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1223 /*delegate=*/mock_delegate,
1224 /*rendering_api=*/mock_delegate.settings_.enable_impeller
1225 ? flutter::IOSRenderingAPI::kMetal
1226 : flutter::IOSRenderingAPI::kSoftware,
1227 /*platform_views_controller=*/nil,
1228 /*task_runners=*/runners,
1229 /*worker_task_runner=*/nil,
1230 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
1231 id mockFlutterView = OCMClassMock([FlutterView class]);
1232 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
1233 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1234
1235 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1236 [[NSMutableArray alloc] init];
1237 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1238 ios_delegate->on_PostAccessibilityNotification_ =
1239 [accessibility_notifications](UIAccessibilityNotifications notification, id argument) {
1240 [accessibility_notifications addObject:@{
1241 @"notification" : @(notification),
1242 @"argument" : argument ? argument : [NSNull null],
1243 }];
1244 };
1245 __block auto bridge =
1246 std::make_unique<flutter::AccessibilityBridge>(/*view_controller=*/mockFlutterViewController,
1247 /*platform_view=*/platform_view.get(),
1248 /*platform_views_controller=*/nil,
1249 /*ios_delegate=*/std::move(ios_delegate));
1250
1251 flutter::CustomAccessibilityActionUpdates actions;
1252 flutter::SemanticsNodeUpdates nodes;
1253
1254 flutter::SemanticsNode node1;
1255 node1.id = 1;
1256 node1.label = "node1";
1257 node1.flags.scopesRoute = true;
1258 node1.flags.namesRoute = true;
1259 nodes[node1.id] = node1;
1260 flutter::SemanticsNode node3;
1261 node3.id = 3;
1262 node3.label = "node3";
1263 nodes[node3.id] = node3;
1264 flutter::SemanticsNode root_node;
1265 root_node.id = kRootNodeId;
1266 root_node.label = "root";
1267 root_node.childrenInTraversalOrder = {1, 3};
1268 root_node.childrenInHitTestOrder = {1, 3};
1269 nodes[root_node.id] = root_node;
1270 bridge->UpdateSemantics(/*nodes=*/nodes, /*actions=*/actions);
1271
1272 XCTAssertEqual([accessibility_notifications count], 1ul);
1273 XCTAssertEqualObjects(accessibility_notifications[0][@"argument"], @"node1");
1274 XCTAssertEqual([accessibility_notifications[0][@"notification"] unsignedIntValue],
1275 UIAccessibilityScreenChangedNotification);
1276
1277 // Simulates the focusing on the node 0.
1278 bridge->AccessibilityObjectDidBecomeFocused(0);
1279
1280 flutter::SemanticsNodeUpdates new_nodes;
1281
1282 flutter::SemanticsNode new_node1;
1283 new_node1.id = 1;
1284 new_node1.label = "new_node1";
1285 new_node1.flags.scopesRoute = true;
1286 new_node1.flags.namesRoute = true;
1287 new_node1.childrenInTraversalOrder = {2};
1288 new_node1.childrenInHitTestOrder = {2};
1289 new_nodes[new_node1.id] = new_node1;
1290 flutter::SemanticsNode new_node2;
1291 new_node2.id = 2;
1292 new_node2.label = "new_node2";
1293 new_node2.flags.scopesRoute = true;
1294 new_node2.flags.namesRoute = true;
1295 new_nodes[new_node2.id] = new_node2;
1296 flutter::SemanticsNode new_root_node;
1297 new_root_node.id = kRootNodeId;
1298 new_root_node.label = "root";
1299 new_root_node.childrenInTraversalOrder = {1};
1300 new_root_node.childrenInHitTestOrder = {1};
1301 new_nodes[new_root_node.id] = new_root_node;
1302 bridge->UpdateSemantics(/*nodes=*/new_nodes, /*actions=*/actions);
1303 XCTAssertEqual([accessibility_notifications count], 3ul);
1304 XCTAssertEqualObjects(accessibility_notifications[1][@"argument"], @"new_node2");
1305 XCTAssertEqual([accessibility_notifications[1][@"notification"] unsignedIntValue],
1306 UIAccessibilityScreenChangedNotification);
1307 SemanticsObject* focusObject = accessibility_notifications[2][@"argument"];
1308 XCTAssertEqual([focusObject uid], 0);
1309 XCTAssertEqual([accessibility_notifications[2][@"notification"] unsignedIntValue],
1310 UIAccessibilityLayoutChangedNotification);
1311 }
1312
1313 - (void)testAnnouncesRouteChangesWhenAddAdditionalRoute {
1314 flutter::MockDelegate mock_delegate;
1315 auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
1316 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
1317 /*platform=*/thread_task_runner,
1318 /*raster=*/thread_task_runner,
1319 /*ui=*/thread_task_runner,
1320 /*io=*/thread_task_runner);
1321 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1322 /*delegate=*/mock_delegate,
1323 /*rendering_api=*/mock_delegate.settings_.enable_impeller
1324 ? flutter::IOSRenderingAPI::kMetal
1325 : flutter::IOSRenderingAPI::kSoftware,
1326 /*platform_views_controller=*/nil,
1327 /*task_runners=*/runners,
1328 /*worker_task_runner=*/nil,
1329 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
1330 id mockFlutterView = OCMClassMock([FlutterView class]);
1331 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
1332 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1333
1334 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1335 [[NSMutableArray alloc] init];
1336 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1337 ios_delegate->on_PostAccessibilityNotification_ =
1338 [accessibility_notifications](UIAccessibilityNotifications notification, id argument) {
1339 [accessibility_notifications addObject:@{
1340 @"notification" : @(notification),
1341 @"argument" : argument ? argument : [NSNull null],
1342 }];
1343 };
1344 __block auto bridge =
1345 std::make_unique<flutter::AccessibilityBridge>(/*view_controller=*/mockFlutterViewController,
1346 /*platform_view=*/platform_view.get(),
1347 /*platform_views_controller=*/nil,
1348 /*ios_delegate=*/std::move(ios_delegate));
1349
1350 flutter::CustomAccessibilityActionUpdates actions;
1351 flutter::SemanticsNodeUpdates nodes;
1352
1353 flutter::SemanticsNode node1;
1354 node1.id = 1;
1355 node1.label = "node1";
1356 node1.flags.scopesRoute = true;
1357 node1.flags.namesRoute = true;
1358 nodes[node1.id] = node1;
1359 flutter::SemanticsNode root_node;
1360 root_node.id = kRootNodeId;
1361 root_node.flags.scopesRoute = true;
1362 root_node.childrenInTraversalOrder = {1};
1363 root_node.childrenInHitTestOrder = {1};
1364 nodes[root_node.id] = root_node;
1365 bridge->UpdateSemantics(/*nodes=*/nodes, /*actions=*/actions);
1366
1367 XCTAssertEqual([accessibility_notifications count], 1ul);
1368 XCTAssertEqualObjects(accessibility_notifications[0][@"argument"], @"node1");
1369 XCTAssertEqual([accessibility_notifications[0][@"notification"] unsignedIntValue],
1370 UIAccessibilityScreenChangedNotification);
1371
1372 flutter::SemanticsNodeUpdates new_nodes;
1373
1374 flutter::SemanticsNode new_node1;
1375 new_node1.id = 1;
1376 new_node1.label = "new_node1";
1377 new_node1.flags.scopesRoute = true;
1378 new_node1.flags.namesRoute = true;
1379 new_node1.childrenInTraversalOrder = {2};
1380 new_node1.childrenInHitTestOrder = {2};
1381 new_nodes[new_node1.id] = new_node1;
1382 flutter::SemanticsNode new_node2;
1383 new_node2.id = 2;
1384 new_node2.label = "new_node2";
1385 new_node2.flags.scopesRoute = true;
1386 new_node2.flags.namesRoute = true;
1387 new_nodes[new_node2.id] = new_node2;
1388 flutter::SemanticsNode new_root_node;
1389 new_root_node.id = kRootNodeId;
1390 new_root_node.flags.scopesRoute = true;
1391 new_root_node.childrenInTraversalOrder = {1};
1392 new_root_node.childrenInHitTestOrder = {1};
1393 new_nodes[new_root_node.id] = new_root_node;
1394 bridge->UpdateSemantics(/*nodes=*/new_nodes, /*actions=*/actions);
1395 XCTAssertEqual([accessibility_notifications count], 2ul);
1396 XCTAssertEqualObjects(accessibility_notifications[1][@"argument"], @"new_node2");
1397 XCTAssertEqual([accessibility_notifications[1][@"notification"] unsignedIntValue],
1398 UIAccessibilityScreenChangedNotification);
1399 }
1400
1401 - (void)testAnnouncesRouteChangesRemoveRouteInMiddle {
1402 flutter::MockDelegate mock_delegate;
1403 auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
1404 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
1405 /*platform=*/thread_task_runner,
1406 /*raster=*/thread_task_runner,
1407 /*ui=*/thread_task_runner,
1408 /*io=*/thread_task_runner);
1409 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1410 /*delegate=*/mock_delegate,
1411 /*rendering_api=*/mock_delegate.settings_.enable_impeller
1412 ? flutter::IOSRenderingAPI::kMetal
1413 : flutter::IOSRenderingAPI::kSoftware,
1414 /*platform_views_controller=*/nil,
1415 /*task_runners=*/runners,
1416 /*worker_task_runner=*/nil,
1417 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
1418 id mockFlutterView = OCMClassMock([FlutterView class]);
1419 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
1420 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1421
1422 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1423 [[NSMutableArray alloc] init];
1424 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1425 ios_delegate->on_PostAccessibilityNotification_ =
1426 [accessibility_notifications](UIAccessibilityNotifications notification, id argument) {
1427 [accessibility_notifications addObject:@{
1428 @"notification" : @(notification),
1429 @"argument" : argument ? argument : [NSNull null],
1430 }];
1431 };
1432 __block auto bridge =
1433 std::make_unique<flutter::AccessibilityBridge>(/*view_controller=*/mockFlutterViewController,
1434 /*platform_view=*/platform_view.get(),
1435 /*platform_views_controller=*/nil,
1436 /*ios_delegate=*/std::move(ios_delegate));
1437
1438 flutter::CustomAccessibilityActionUpdates actions;
1439 flutter::SemanticsNodeUpdates nodes;
1440
1441 flutter::SemanticsNode node1;
1442 node1.id = 1;
1443 node1.label = "node1";
1444 node1.flags.scopesRoute = true;
1445 node1.flags.namesRoute = true;
1446 node1.childrenInTraversalOrder = {2};
1447 node1.childrenInHitTestOrder = {2};
1448 nodes[node1.id] = node1;
1449 flutter::SemanticsNode node2;
1450 node2.id = 2;
1451 node2.label = "node2";
1452 node2.flags.scopesRoute = true;
1453 node2.flags.namesRoute = true;
1454 nodes[node2.id] = node2;
1455 flutter::SemanticsNode root_node;
1456 root_node.id = kRootNodeId;
1457 root_node.flags.scopesRoute = true;
1458 root_node.childrenInTraversalOrder = {1};
1459 root_node.childrenInHitTestOrder = {1};
1460 nodes[root_node.id] = root_node;
1461 bridge->UpdateSemantics(/*nodes=*/nodes, /*actions=*/actions);
1462
1463 XCTAssertEqual([accessibility_notifications count], 1ul);
1464 XCTAssertEqualObjects(accessibility_notifications[0][@"argument"], @"node2");
1465 XCTAssertEqual([accessibility_notifications[0][@"notification"] unsignedIntValue],
1466 UIAccessibilityScreenChangedNotification);
1467
1468 flutter::SemanticsNodeUpdates new_nodes;
1469
1470 flutter::SemanticsNode new_node1;
1471 new_node1.id = 1;
1472 new_node1.label = "new_node1";
1473 new_node1.childrenInTraversalOrder = {2};
1474 new_node1.childrenInHitTestOrder = {2};
1475 new_nodes[new_node1.id] = new_node1;
1476 flutter::SemanticsNode new_node2;
1477 new_node2.id = 2;
1478 new_node2.label = "new_node2";
1479 new_node2.flags.scopesRoute = true;
1480 new_node2.flags.namesRoute = true;
1481 new_nodes[new_node2.id] = new_node2;
1482 flutter::SemanticsNode new_root_node;
1483 new_root_node.id = kRootNodeId;
1484 new_root_node.flags.scopesRoute = true;
1485 new_root_node.childrenInTraversalOrder = {1};
1486 new_root_node.childrenInHitTestOrder = {1};
1487 new_nodes[new_root_node.id] = new_root_node;
1488 bridge->UpdateSemantics(/*nodes=*/new_nodes, /*actions=*/actions);
1489 XCTAssertEqual([accessibility_notifications count], 2ul);
1490 XCTAssertEqualObjects(accessibility_notifications[1][@"argument"], @"new_node2");
1491 XCTAssertEqual([accessibility_notifications[1][@"notification"] unsignedIntValue],
1492 UIAccessibilityScreenChangedNotification);
1493 }
1494
1495 - (void)testHandleEvent {
1496 flutter::MockDelegate mock_delegate;
1497 auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
1498 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
1499 /*platform=*/thread_task_runner,
1500 /*raster=*/thread_task_runner,
1501 /*ui=*/thread_task_runner,
1502 /*io=*/thread_task_runner);
1503 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1504 /*delegate=*/mock_delegate,
1505 /*rendering_api=*/mock_delegate.settings_.enable_impeller
1506 ? flutter::IOSRenderingAPI::kMetal
1507 : flutter::IOSRenderingAPI::kSoftware,
1508 /*platform_views_controller=*/nil,
1509 /*task_runners=*/runners,
1510 /*worker_task_runner=*/nil,
1511 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
1512 id mockFlutterView = OCMClassMock([FlutterView class]);
1513 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
1514 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1515
1516 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1517 [[NSMutableArray alloc] init];
1518 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1519 ios_delegate->on_PostAccessibilityNotification_ =
1520 [accessibility_notifications](UIAccessibilityNotifications notification, id argument) {
1521 [accessibility_notifications addObject:@{
1522 @"notification" : @(notification),
1523 @"argument" : argument ? argument : [NSNull null],
1524 }];
1525 };
1526 __block auto bridge =
1527 std::make_unique<flutter::AccessibilityBridge>(/*view_controller=*/mockFlutterViewController,
1528 /*platform_view=*/platform_view.get(),
1529 /*platform_views_controller=*/nil,
1530 /*ios_delegate=*/std::move(ios_delegate));
1531
1532 NSDictionary<NSString*, id>* annotatedEvent = @{@"type" : @"focus", @"nodeId" : @123};
1533
1534 bridge->HandleEvent(annotatedEvent);
1535
1536 XCTAssertEqual([accessibility_notifications count], 1ul);
1537 XCTAssertEqual([accessibility_notifications[0][@"notification"] unsignedIntValue],
1538 UIAccessibilityLayoutChangedNotification);
1539 }
1540
1541 - (void)testAccessibilityObjectDidBecomeFocused {
1542 flutter::MockDelegate mock_delegate;
1543 auto thread = std::make_unique<fml::Thread>("AccessibilityBridgeTest");
1544 auto thread_task_runner = thread->GetTaskRunner();
1545 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
1546 /*platform=*/thread_task_runner,
1547 /*raster=*/thread_task_runner,
1548 /*ui=*/thread_task_runner,
1549 /*io=*/thread_task_runner);
1550 id messenger = OCMProtocolMock(@protocol(FlutterBinaryMessenger));
1551 id engine = OCMClassMock([FlutterEngine class]);
1552 id flutterViewController = OCMClassMock([FlutterViewController class]);
1553
1554 OCMStub([flutterViewController engine]).andReturn(engine);
1555 OCMStub([engine binaryMessenger]).andReturn(messenger);
1556 FlutterBinaryMessengerConnection connection = 123;
1557 OCMStub([messenger setMessageHandlerOnChannel:@"flutter/accessibility"
1558 binaryMessageHandler:[OCMArg any]])
1559 .andReturn(connection);
1560
1561 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1562 /*delegate=*/mock_delegate,
1563 /*rendering_api=*/mock_delegate.settings_.enable_impeller
1564 ? flutter::IOSRenderingAPI::kMetal
1565 : flutter::IOSRenderingAPI::kSoftware,
1566 /*platform_views_controller=*/nil,
1567 /*task_runners=*/runners,
1568 /*worker_task_runner=*/nil,
1569 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
1570 fml::AutoResetWaitableEvent latch;
1571 thread_task_runner->PostTask([&] {
1572 platform_view->SetOwnerViewController(flutterViewController);
1573 auto bridge =
1574 std::make_unique<flutter::AccessibilityBridge>(/*view=*/nil,
1575 /*platform_view=*/platform_view.get(),
1576 /*platform_views_controller=*/nil);
1577 XCTAssertTrue(bridge.get());
1578 OCMVerify([messenger setMessageHandlerOnChannel:@"flutter/accessibility"
1579 binaryMessageHandler:[OCMArg isNotNil]]);
1580
1581 bridge->AccessibilityObjectDidBecomeFocused(123);
1582
1583 NSDictionary<NSString*, id>* annotatedEvent = @{@"type" : @"didGainFocus", @"nodeId" : @123};
1584 NSData* encodedMessage = [[FlutterStandardMessageCodec sharedInstance] encode:annotatedEvent];
1585
1586 OCMVerify([messenger sendOnChannel:@"flutter/accessibility" message:encodedMessage]);
1587 latch.Signal();
1588 });
1589 latch.Wait();
1590
1591 [engine stopMocking];
1592 }
1593
1594 - (void)testAnnouncesRouteChangesWhenNoNamesRoute {
1595 flutter::MockDelegate mock_delegate;
1596 auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
1597 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
1598 /*platform=*/thread_task_runner,
1599 /*raster=*/thread_task_runner,
1600 /*ui=*/thread_task_runner,
1601 /*io=*/thread_task_runner);
1602 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1603 /*delegate=*/mock_delegate,
1604 /*rendering_api=*/mock_delegate.settings_.enable_impeller
1605 ? flutter::IOSRenderingAPI::kMetal
1606 : flutter::IOSRenderingAPI::kSoftware,
1607 /*platform_views_controller=*/nil,
1608 /*task_runners=*/runners,
1609 /*worker_task_runner=*/nil,
1610 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
1611 id mockFlutterView = OCMClassMock([FlutterView class]);
1612 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
1613 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1614
1615 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1616 [[NSMutableArray alloc] init];
1617 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1618 ios_delegate->on_PostAccessibilityNotification_ =
1619 [accessibility_notifications](UIAccessibilityNotifications notification, id argument) {
1620 [accessibility_notifications addObject:@{
1621 @"notification" : @(notification),
1622 @"argument" : argument ? argument : [NSNull null],
1623 }];
1624 };
1625 __block auto bridge =
1626 std::make_unique<flutter::AccessibilityBridge>(/*view_controller=*/mockFlutterViewController,
1627 /*platform_view=*/platform_view.get(),
1628 /*platform_views_controller=*/nil,
1629 /*ios_delegate=*/std::move(ios_delegate));
1630
1631 flutter::CustomAccessibilityActionUpdates actions;
1632 flutter::SemanticsNodeUpdates nodes;
1633
1634 flutter::SemanticsNode node1;
1635 node1.id = 1;
1636 node1.label = "node1";
1637 node1.flags.scopesRoute = true;
1638 node1.flags.namesRoute = true;
1639 node1.childrenInTraversalOrder = {2, 3};
1640 node1.childrenInHitTestOrder = {2, 3};
1641 nodes[node1.id] = node1;
1642 flutter::SemanticsNode node2;
1643 node2.id = 2;
1644 node2.label = "node2";
1645 nodes[node2.id] = node2;
1646 flutter::SemanticsNode node3;
1647 node3.id = 3;
1648 node3.label = "node3";
1649 nodes[node3.id] = node3;
1650 flutter::SemanticsNode root_node;
1651 root_node.id = kRootNodeId;
1652 root_node.childrenInTraversalOrder = {1};
1653 root_node.childrenInHitTestOrder = {1};
1654 nodes[root_node.id] = root_node;
1655 bridge->UpdateSemantics(/*nodes=*/nodes, /*actions=*/actions);
1656
1657 // Notification should focus first focusable node, which is node1.
1658 XCTAssertEqual([accessibility_notifications count], 1ul);
1659 id focusObject = accessibility_notifications[0][@"argument"];
1660 XCTAssertTrue([focusObject isKindOfClass:[NSString class]]);
1661 XCTAssertEqualObjects(focusObject, @"node1");
1662 XCTAssertEqual([accessibility_notifications[0][@"notification"] unsignedIntValue],
1663 UIAccessibilityScreenChangedNotification);
1664 }
1665
1666 - (void)testAnnouncesLayoutChangeWithNilIfLastFocusIsRemoved {
1667 flutter::MockDelegate mock_delegate;
1668 auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
1669 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
1670 /*platform=*/thread_task_runner,
1671 /*raster=*/thread_task_runner,
1672 /*ui=*/thread_task_runner,
1673 /*io=*/thread_task_runner);
1674 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1675 /*delegate=*/mock_delegate,
1676 /*rendering_api=*/mock_delegate.settings_.enable_impeller
1677 ? flutter::IOSRenderingAPI::kMetal
1678 : flutter::IOSRenderingAPI::kSoftware,
1679 /*platform_views_controller=*/nil,
1680 /*task_runners=*/runners,
1681 /*worker_task_runner=*/nil,
1682 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
1683 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
1684 id mockFlutterView = OCMClassMock([FlutterView class]);
1685 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1686
1687 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1688 [[NSMutableArray alloc] init];
1689 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1690 ios_delegate->on_PostAccessibilityNotification_ =
1691 [accessibility_notifications](UIAccessibilityNotifications notification, id argument) {
1692 [accessibility_notifications addObject:@{
1693 @"notification" : @(notification),
1694 @"argument" : argument ? argument : [NSNull null],
1695 }];
1696 };
1697 __block auto bridge =
1698 std::make_unique<flutter::AccessibilityBridge>(/*view_controller=*/mockFlutterViewController,
1699 /*platform_view=*/platform_view.get(),
1700 /*platform_views_controller=*/nil,
1701 /*ios_delegate=*/std::move(ios_delegate));
1702
1703 flutter::CustomAccessibilityActionUpdates actions;
1704 flutter::SemanticsNodeUpdates first_update;
1705
1706 flutter::SemanticsNode route_node;
1707 route_node.id = 1;
1708 route_node.label = "route";
1709 first_update[route_node.id] = route_node;
1710 flutter::SemanticsNode root_node;
1711 root_node.id = kRootNodeId;
1712 root_node.label = "root";
1713 root_node.childrenInTraversalOrder = {1};
1714 root_node.childrenInHitTestOrder = {1};
1715 first_update[root_node.id] = root_node;
1716 bridge->UpdateSemantics(/*nodes=*/first_update, /*actions=*/actions);
1717
1718 XCTAssertEqual([accessibility_notifications count], 0ul);
1719 // Simulates the focusing on the node 1.
1720 bridge->AccessibilityObjectDidBecomeFocused(1);
1721
1722 flutter::SemanticsNodeUpdates second_update;
1723 // Simulates the removal of the node 1
1724 flutter::SemanticsNode new_root_node;
1725 new_root_node.id = kRootNodeId;
1726 new_root_node.label = "root";
1727 second_update[root_node.id] = new_root_node;
1728 bridge->UpdateSemantics(/*nodes=*/second_update, /*actions=*/actions);
1729 SemanticsObject* focusObject = accessibility_notifications[0][@"argument"];
1730 // The node 1 was removed, so the bridge will set the focus object to root.
1731 XCTAssertEqual([focusObject uid], 0);
1732 XCTAssertEqualObjects([focusObject accessibilityLabel], @"root");
1733 XCTAssertEqual([accessibility_notifications[0][@"notification"] unsignedIntValue],
1734 UIAccessibilityLayoutChangedNotification);
1735 }
1736
1737 - (void)testAnnouncesLayoutChangeWithTheSameItemFocused {
1738 flutter::MockDelegate mock_delegate;
1739 auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
1740 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
1741 /*platform=*/thread_task_runner,
1742 /*raster=*/thread_task_runner,
1743 /*ui=*/thread_task_runner,
1744 /*io=*/thread_task_runner);
1745 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1746 /*delegate=*/mock_delegate,
1747 /*rendering_api=*/mock_delegate.settings_.enable_impeller
1748 ? flutter::IOSRenderingAPI::kMetal
1749 : flutter::IOSRenderingAPI::kSoftware,
1750 /*platform_views_controller=*/nil,
1751 /*task_runners=*/runners,
1752 /*worker_task_runner=*/nil,
1753 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
1754 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
1755 id mockFlutterView = OCMClassMock([FlutterView class]);
1756 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1757
1758 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1759 [[NSMutableArray alloc] init];
1760 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1761 ios_delegate->on_PostAccessibilityNotification_ =
1762 [accessibility_notifications](UIAccessibilityNotifications notification, id argument) {
1763 [accessibility_notifications addObject:@{
1764 @"notification" : @(notification),
1765 @"argument" : argument ? argument : [NSNull null],
1766 }];
1767 };
1768 __block auto bridge =
1769 std::make_unique<flutter::AccessibilityBridge>(/*view_controller=*/mockFlutterViewController,
1770 /*platform_view=*/platform_view.get(),
1771 /*platform_views_controller=*/nil,
1772 /*ios_delegate=*/std::move(ios_delegate));
1773
1774 flutter::CustomAccessibilityActionUpdates actions;
1775 flutter::SemanticsNodeUpdates first_update;
1776
1777 flutter::SemanticsNode node_one;
1778 node_one.id = 1;
1779 node_one.label = "route1";
1780 first_update[node_one.id] = node_one;
1781 flutter::SemanticsNode node_two;
1782 node_two.id = 2;
1783 node_two.label = "route2";
1784 first_update[node_two.id] = node_two;
1785 flutter::SemanticsNode root_node;
1786 root_node.id = kRootNodeId;
1787 root_node.label = "root";
1788 root_node.childrenInTraversalOrder = {1, 2};
1789 root_node.childrenInHitTestOrder = {1, 2};
1790 first_update[root_node.id] = root_node;
1791 bridge->UpdateSemantics(/*nodes=*/first_update, /*actions=*/actions);
1792
1793 XCTAssertEqual([accessibility_notifications count], 0ul);
1794 // Simulates the focusing on the node 1.
1795 bridge->AccessibilityObjectDidBecomeFocused(1);
1796
1797 flutter::SemanticsNodeUpdates second_update;
1798 // Simulates the removal of the node 2.
1799 flutter::SemanticsNode new_root_node;
1800 new_root_node.id = kRootNodeId;
1801 new_root_node.label = "root";
1802 new_root_node.childrenInTraversalOrder = {1};
1803 new_root_node.childrenInHitTestOrder = {1};
1804 second_update[root_node.id] = new_root_node;
1805 bridge->UpdateSemantics(/*nodes=*/second_update, /*actions=*/actions);
1806 id focusObject = accessibility_notifications[0][@"argument"];
1807 // Since we have focused on the node 1 right before the layout changed, the bridge should not ask
1808 // to refocus again on the same node.
1809 XCTAssertEqualObjects(focusObject, [NSNull null]);
1810 XCTAssertEqual([accessibility_notifications[0][@"notification"] unsignedIntValue],
1811 UIAccessibilityLayoutChangedNotification);
1812 }
1813
1814 - (void)testAnnouncesLayoutChangeWhenFocusMovedOutside {
1815 flutter::MockDelegate mock_delegate;
1816 auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
1817 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
1818 /*platform=*/thread_task_runner,
1819 /*raster=*/thread_task_runner,
1820 /*ui=*/thread_task_runner,
1821 /*io=*/thread_task_runner);
1822 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1823 /*delegate=*/mock_delegate,
1824 /*rendering_api=*/mock_delegate.settings_.enable_impeller
1825 ? flutter::IOSRenderingAPI::kMetal
1826 : flutter::IOSRenderingAPI::kSoftware,
1827 /*platform_views_controller=*/nil,
1828 /*task_runners=*/runners,
1829 /*worker_task_runner=*/nil,
1830 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
1831 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
1832 id mockFlutterView = OCMClassMock([FlutterView class]);
1833 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1834
1835 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1836 [[NSMutableArray alloc] init];
1837 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1838 ios_delegate->on_PostAccessibilityNotification_ =
1839 [accessibility_notifications](UIAccessibilityNotifications notification, id argument) {
1840 [accessibility_notifications addObject:@{
1841 @"notification" : @(notification),
1842 @"argument" : argument ? argument : [NSNull null],
1843 }];
1844 };
1845 __block auto bridge =
1846 std::make_unique<flutter::AccessibilityBridge>(/*view_controller=*/mockFlutterViewController,
1847 /*platform_view=*/platform_view.get(),
1848 /*platform_views_controller=*/nil,
1849 /*ios_delegate=*/std::move(ios_delegate));
1850
1851 flutter::CustomAccessibilityActionUpdates actions;
1852 flutter::SemanticsNodeUpdates first_update;
1853
1854 flutter::SemanticsNode node_one;
1855 node_one.id = 1;
1856 node_one.label = "route1";
1857 first_update[node_one.id] = node_one;
1858 flutter::SemanticsNode node_two;
1859 node_two.id = 2;
1860 node_two.label = "route2";
1861 first_update[node_two.id] = node_two;
1862 flutter::SemanticsNode root_node;
1863 root_node.id = kRootNodeId;
1864 root_node.label = "root";
1865 root_node.childrenInTraversalOrder = {1, 2};
1866 root_node.childrenInHitTestOrder = {1, 2};
1867 first_update[root_node.id] = root_node;
1868 bridge->UpdateSemantics(/*nodes=*/first_update, /*actions=*/actions);
1869
1870 XCTAssertEqual([accessibility_notifications count], 0ul);
1871 // Simulates the focusing on the node 1.
1872 bridge->AccessibilityObjectDidBecomeFocused(1);
1873 // Simulates that the focus move outside of flutter.
1874 bridge->AccessibilityObjectDidLoseFocus(1);
1875
1876 flutter::SemanticsNodeUpdates second_update;
1877 // Simulates the removal of the node 2.
1878 flutter::SemanticsNode new_root_node;
1879 new_root_node.id = kRootNodeId;
1880 new_root_node.label = "root";
1881 new_root_node.childrenInTraversalOrder = {1};
1882 new_root_node.childrenInHitTestOrder = {1};
1883 second_update[root_node.id] = new_root_node;
1884 bridge->UpdateSemantics(/*nodes=*/second_update, /*actions=*/actions);
1885 NSNull* focusObject = accessibility_notifications[0][@"argument"];
1886 // Since the focus is moved outside of the app right before the layout
1887 // changed, the bridge should not try to refocus anything .
1888 XCTAssertEqual(focusObject, [NSNull null]);
1889 XCTAssertEqual([accessibility_notifications[0][@"notification"] unsignedIntValue],
1890 UIAccessibilityLayoutChangedNotification);
1891 }
1892
1893 - (void)testAnnouncesScrollChangeWithLastFocused {
1894 flutter::MockDelegate mock_delegate;
1895 auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
1896 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
1897 /*platform=*/thread_task_runner,
1898 /*raster=*/thread_task_runner,
1899 /*ui=*/thread_task_runner,
1900 /*io=*/thread_task_runner);
1901 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1902 /*delegate=*/mock_delegate,
1903 /*rendering_api=*/mock_delegate.settings_.enable_impeller
1904 ? flutter::IOSRenderingAPI::kMetal
1905 : flutter::IOSRenderingAPI::kSoftware,
1906 /*platform_views_controller=*/nil,
1907 /*task_runners=*/runners,
1908 /*worker_task_runner=*/nil,
1909 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
1910 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
1911 id mockFlutterView = OCMClassMock([FlutterView class]);
1912 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1913
1914 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1915 [[NSMutableArray alloc] init];
1916 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1917 ios_delegate->on_PostAccessibilityNotification_ =
1918 [accessibility_notifications](UIAccessibilityNotifications notification, id argument) {
1919 [accessibility_notifications addObject:@{
1920 @"notification" : @(notification),
1921 @"argument" : argument ? argument : [NSNull null],
1922 }];
1923 };
1924 __block auto bridge =
1925 std::make_unique<flutter::AccessibilityBridge>(/*view_controller=*/mockFlutterViewController,
1926 /*platform_view=*/platform_view.get(),
1927 /*platform_views_controller=*/nil,
1928 /*ios_delegate=*/std::move(ios_delegate));
1929
1930 flutter::CustomAccessibilityActionUpdates actions;
1931 flutter::SemanticsNodeUpdates first_update;
1932
1933 flutter::SemanticsNode node_one;
1934 node_one.id = 1;
1935 node_one.label = "route1";
1936 node_one.scrollPosition = 0.0;
1937 first_update[node_one.id] = node_one;
1938 flutter::SemanticsNode root_node;
1939 root_node.id = kRootNodeId;
1940 root_node.label = "root";
1941 root_node.childrenInTraversalOrder = {1};
1942 root_node.childrenInHitTestOrder = {1};
1943 first_update[root_node.id] = root_node;
1944 bridge->UpdateSemantics(/*nodes=*/first_update, /*actions=*/actions);
1945
1946 // The first update will trigger a scroll announcement, but we are not interested in it.
1947 [accessibility_notifications removeAllObjects];
1948
1949 // Simulates the focusing on the node 1.
1950 bridge->AccessibilityObjectDidBecomeFocused(1);
1951
1952 flutter::SemanticsNodeUpdates second_update;
1953 // Simulates the scrolling on the node 1.
1954 flutter::SemanticsNode new_node_one;
1955 new_node_one.id = 1;
1956 new_node_one.label = "route1";
1957 new_node_one.scrollPosition = 1.0;
1958 second_update[new_node_one.id] = new_node_one;
1959 bridge->UpdateSemantics(/*nodes=*/second_update, /*actions=*/actions);
1960 SemanticsObject* focusObject = accessibility_notifications[0][@"argument"];
1961 // Since we have focused on the node 1 right before the scrolling, the bridge should refocus the
1962 // node 1.
1963 XCTAssertEqual([focusObject uid], 1);
1964 XCTAssertEqual([accessibility_notifications[0][@"notification"] unsignedIntValue],
1965 UIAccessibilityPageScrolledNotification);
1966 }
1967
1968 - (void)testAnnouncesScrollChangeDoesCallNativeAccessibility {
1969 flutter::MockDelegate mock_delegate;
1970 auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
1971 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
1972 /*platform=*/thread_task_runner,
1973 /*raster=*/thread_task_runner,
1974 /*ui=*/thread_task_runner,
1975 /*io=*/thread_task_runner);
1976 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1977 /*delegate=*/mock_delegate,
1978 /*rendering_api=*/mock_delegate.settings_.enable_impeller
1979 ? flutter::IOSRenderingAPI::kMetal
1980 : flutter::IOSRenderingAPI::kSoftware,
1981 /*platform_views_controller=*/nil,
1982 /*task_runners=*/runners,
1983 /*worker_task_runner=*/nil,
1984 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
1985 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
1986 id mockFlutterView = OCMClassMock([FlutterView class]);
1987 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1988
1989 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1990 [[NSMutableArray alloc] init];
1991 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1992 ios_delegate->on_PostAccessibilityNotification_ =
1993 [accessibility_notifications](UIAccessibilityNotifications notification, id argument) {
1994 [accessibility_notifications addObject:@{
1995 @"notification" : @(notification),
1996 @"argument" : argument ? argument : [NSNull null],
1997 }];
1998 };
1999 __block auto bridge =
2000 std::make_unique<flutter::AccessibilityBridge>(/*view_controller=*/mockFlutterViewController,
2001 /*platform_view=*/platform_view.get(),
2002 /*platform_views_controller=*/nil,
2003 /*ios_delegate=*/std::move(ios_delegate));
2004
2005 flutter::CustomAccessibilityActionUpdates actions;
2006 flutter::SemanticsNodeUpdates first_update;
2007
2008 flutter::SemanticsNode node_one;
2009 node_one.id = 1;
2010 node_one.label = "route1";
2011 node_one.flags.hasImplicitScrolling = true;
2012 node_one.scrollPosition = 0.0;
2013 first_update[node_one.id] = node_one;
2014 flutter::SemanticsNode root_node;
2015 root_node.id = kRootNodeId;
2016 root_node.label = "root";
2017 root_node.childrenInTraversalOrder = {1};
2018 root_node.childrenInHitTestOrder = {1};
2019 first_update[root_node.id] = root_node;
2020 bridge->UpdateSemantics(/*nodes=*/first_update, /*actions=*/actions);
2021
2022 // The first update will trigger a scroll announcement, but we are not interested in it.
2023 [accessibility_notifications removeAllObjects];
2024
2025 // Simulates the focusing on the node 1.
2026 bridge->AccessibilityObjectDidBecomeFocused(1);
2027
2028 flutter::SemanticsNodeUpdates second_update;
2029 // Simulates the scrolling on the node 1.
2030 flutter::SemanticsNode new_node_one;
2031 new_node_one.id = 1;
2032 new_node_one.label = "route1";
2033 new_node_one.flags.hasImplicitScrolling = true;
2034 new_node_one.scrollPosition = 1.0;
2035 second_update[new_node_one.id] = new_node_one;
2036 bridge->UpdateSemantics(/*nodes=*/second_update, /*actions=*/actions);
2037 SemanticsObject* focusObject = accessibility_notifications[0][@"argument"];
2038 // Make sure refocus event is sent with the nativeAccessibility of node_one
2039 // which is a FlutterSemanticsScrollView.
2040 XCTAssertTrue([focusObject isKindOfClass:[FlutterSemanticsScrollView class]]);
2041 XCTAssertEqual([accessibility_notifications[0][@"notification"] unsignedIntValue],
2042 UIAccessibilityPageScrolledNotification);
2043 }
2044
2045 - (void)testAnnouncesIgnoresRouteChangesWhenModal {
2046 flutter::MockDelegate mock_delegate;
2047 auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
2048 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
2049 /*platform=*/thread_task_runner,
2050 /*raster=*/thread_task_runner,
2051 /*ui=*/thread_task_runner,
2052 /*io=*/thread_task_runner);
2053 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2054 /*delegate=*/mock_delegate,
2055 /*rendering_api=*/mock_delegate.settings_.enable_impeller
2056 ? flutter::IOSRenderingAPI::kMetal
2057 : flutter::IOSRenderingAPI::kSoftware,
2058 /*platform_views_controller=*/nil,
2059 /*task_runners=*/runners,
2060 /*worker_task_runner=*/nil,
2061 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
2062 id mockFlutterView = OCMClassMock([FlutterView class]);
2063 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
2064 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
2065 std::string label = "some label";
2066
2067 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
2068 [[NSMutableArray alloc] init];
2069 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
2070 ios_delegate->on_PostAccessibilityNotification_ =
2071 [accessibility_notifications](UIAccessibilityNotifications notification, id argument) {
2072 [accessibility_notifications addObject:@{
2073 @"notification" : @(notification),
2074 @"argument" : argument ? argument : [NSNull null],
2075 }];
2076 };
2077 ios_delegate->result_IsFlutterViewControllerPresentingModalViewController_ = true;
2078 __block auto bridge =
2079 std::make_unique<flutter::AccessibilityBridge>(/*view_controller=*/mockFlutterViewController,
2080 /*platform_view=*/platform_view.get(),
2081 /*platform_views_controller=*/nil,
2082 /*ios_delegate=*/std::move(ios_delegate));
2083
2084 flutter::CustomAccessibilityActionUpdates actions;
2085 flutter::SemanticsNodeUpdates nodes;
2086
2087 flutter::SemanticsNode route_node;
2088 route_node.id = 1;
2089 route_node.flags.scopesRoute = true;
2090 route_node.flags.namesRoute = true;
2091 route_node.label = "route";
2092 nodes[route_node.id] = route_node;
2093 flutter::SemanticsNode root_node;
2094 root_node.id = kRootNodeId;
2095 root_node.label = label;
2096 root_node.childrenInTraversalOrder = {1};
2097 root_node.childrenInHitTestOrder = {1};
2098 nodes[root_node.id] = root_node;
2099 bridge->UpdateSemantics(/*nodes=*/nodes, /*actions=*/actions);
2100
2101 XCTAssertEqual([accessibility_notifications count], 0ul);
2102 }
2103
2104 - (void)testAnnouncesIgnoresLayoutChangeWhenModal {
2105 flutter::MockDelegate mock_delegate;
2106 auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
2107 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
2108 /*platform=*/thread_task_runner,
2109 /*raster=*/thread_task_runner,
2110 /*ui=*/thread_task_runner,
2111 /*io=*/thread_task_runner);
2112 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2113 /*delegate=*/mock_delegate,
2114 /*rendering_api=*/mock_delegate.settings_.enable_impeller
2115 ? flutter::IOSRenderingAPI::kMetal
2116 : flutter::IOSRenderingAPI::kSoftware,
2117 /*platform_views_controller=*/nil,
2118 /*task_runners=*/runners,
2119 /*worker_task_runner=*/nil,
2120 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
2121 id mockFlutterView = OCMClassMock([FlutterView class]);
2122 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
2123 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
2124
2125 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
2126 [[NSMutableArray alloc] init];
2127 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
2128 ios_delegate->on_PostAccessibilityNotification_ =
2129 [accessibility_notifications](UIAccessibilityNotifications notification, id argument) {
2130 [accessibility_notifications addObject:@{
2131 @"notification" : @(notification),
2132 @"argument" : argument ? argument : [NSNull null],
2133 }];
2134 };
2135 ios_delegate->result_IsFlutterViewControllerPresentingModalViewController_ = true;
2136 __block auto bridge =
2137 std::make_unique<flutter::AccessibilityBridge>(/*view_controller=*/mockFlutterViewController,
2138 /*platform_view=*/platform_view.get(),
2139 /*platform_views_controller=*/nil,
2140 /*ios_delegate=*/std::move(ios_delegate));
2141
2142 flutter::CustomAccessibilityActionUpdates actions;
2143 flutter::SemanticsNodeUpdates nodes;
2144
2145 flutter::SemanticsNode child_node;
2146 child_node.id = 1;
2147 child_node.label = "child_node";
2148 nodes[child_node.id] = child_node;
2149 flutter::SemanticsNode root_node;
2150 root_node.id = kRootNodeId;
2151 root_node.label = "root";
2152 root_node.childrenInTraversalOrder = {1};
2153 root_node.childrenInHitTestOrder = {1};
2154 nodes[root_node.id] = root_node;
2155 bridge->UpdateSemantics(/*nodes=*/nodes, /*actions=*/actions);
2156
2157 // Removes child_node to simulate a layout change.
2158 flutter::SemanticsNodeUpdates new_nodes;
2159 flutter::SemanticsNode new_root_node;
2160 new_root_node.id = kRootNodeId;
2161 new_root_node.label = "root";
2162 new_nodes[new_root_node.id] = new_root_node;
2163 bridge->UpdateSemantics(/*nodes=*/new_nodes, /*actions=*/actions);
2164
2165 XCTAssertEqual([accessibility_notifications count], 0ul);
2166 }
2167
2168 - (void)testAnnouncesIgnoresScrollChangeWhenModal {
2169 flutter::MockDelegate mock_delegate;
2170 auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
2171 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
2172 /*platform=*/thread_task_runner,
2173 /*raster=*/thread_task_runner,
2174 /*ui=*/thread_task_runner,
2175 /*io=*/thread_task_runner);
2176 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2177 /*delegate=*/mock_delegate,
2178 /*rendering_api=*/mock_delegate.settings_.enable_impeller
2179 ? flutter::IOSRenderingAPI::kMetal
2180 : flutter::IOSRenderingAPI::kSoftware,
2181 /*platform_views_controller=*/nil,
2182 /*task_runners=*/runners,
2183 /*worker_task_runner=*/nil,
2184 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
2185 id mockFlutterView = OCMClassMock([FlutterView class]);
2186 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
2187 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
2188
2189 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
2190 [[NSMutableArray alloc] init];
2191 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
2192 ios_delegate->on_PostAccessibilityNotification_ =
2193 [accessibility_notifications](UIAccessibilityNotifications notification, id argument) {
2194 [accessibility_notifications addObject:@{
2195 @"notification" : @(notification),
2196 @"argument" : argument ? argument : [NSNull null],
2197 }];
2198 };
2199 ios_delegate->result_IsFlutterViewControllerPresentingModalViewController_ = true;
2200 __block auto bridge =
2201 std::make_unique<flutter::AccessibilityBridge>(/*view_controller=*/mockFlutterViewController,
2202 /*platform_view=*/platform_view.get(),
2203 /*platform_views_controller=*/nil,
2204 /*ios_delegate=*/std::move(ios_delegate));
2205
2206 flutter::CustomAccessibilityActionUpdates actions;
2207 flutter::SemanticsNodeUpdates nodes;
2208
2209 flutter::SemanticsNode root_node;
2210 root_node.id = kRootNodeId;
2211 root_node.label = "root";
2212 root_node.scrollPosition = 1;
2213 nodes[root_node.id] = root_node;
2214 bridge->UpdateSemantics(/*nodes=*/nodes, /*actions=*/actions);
2215
2216 // Removes child_node to simulate a layout change.
2217 flutter::SemanticsNodeUpdates new_nodes;
2218 flutter::SemanticsNode new_root_node;
2219 new_root_node.id = kRootNodeId;
2220 new_root_node.label = "root";
2221 new_root_node.scrollPosition = 2;
2222 new_nodes[new_root_node.id] = new_root_node;
2223 bridge->UpdateSemantics(/*nodes=*/new_nodes, /*actions=*/actions);
2224
2225 XCTAssertEqual([accessibility_notifications count], 0ul);
2226 }
2227
2228 - (void)testAccessibilityMessageAfterDeletion {
2229 flutter::MockDelegate mock_delegate;
2230 auto thread = std::make_unique<fml::Thread>("AccessibilityBridgeTest");
2231 auto thread_task_runner = thread->GetTaskRunner();
2232 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
2233 /*platform=*/thread_task_runner,
2234 /*raster=*/thread_task_runner,
2235 /*ui=*/thread_task_runner,
2236 /*io=*/thread_task_runner);
2237 id messenger = OCMProtocolMock(@protocol(FlutterBinaryMessenger));
2238 id engine = OCMClassMock([FlutterEngine class]);
2239 id flutterViewController = OCMClassMock([FlutterViewController class]);
2240
2241 OCMStub([flutterViewController engine]).andReturn(engine);
2242 OCMStub([engine binaryMessenger]).andReturn(messenger);
2243 FlutterBinaryMessengerConnection connection = 123;
2244 OCMStub([messenger setMessageHandlerOnChannel:@"flutter/accessibility"
2245 binaryMessageHandler:[OCMArg any]])
2246 .andReturn(connection);
2247
2248 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2249 /*delegate=*/mock_delegate,
2250 /*rendering_api=*/mock_delegate.settings_.enable_impeller
2251 ? flutter::IOSRenderingAPI::kMetal
2252 : flutter::IOSRenderingAPI::kSoftware,
2253 /*platform_views_controller=*/nil,
2254 /*task_runners=*/runners,
2255 /*worker_task_runner=*/nil,
2256 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
2257 fml::AutoResetWaitableEvent latch;
2258 thread_task_runner->PostTask([&] {
2259 platform_view->SetOwnerViewController(flutterViewController);
2260 auto bridge =
2261 std::make_unique<flutter::AccessibilityBridge>(/*view=*/nil,
2262 /*platform_view=*/platform_view.get(),
2263 /*platform_views_controller=*/nil);
2264 XCTAssertTrue(bridge.get());
2265 OCMVerify([messenger setMessageHandlerOnChannel:@"flutter/accessibility"
2266 binaryMessageHandler:[OCMArg isNotNil]]);
2267 bridge.reset();
2268 latch.Signal();
2269 });
2270 latch.Wait();
2271 OCMVerify([messenger cleanUpConnection:connection]);
2272 [engine stopMocking];
2273 }
2274
2275 - (void)testFlutterSemanticsScrollViewManagedObjectLifecycleCorrectly {
2276 flutter::MockDelegate mock_delegate;
2277 auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
2278 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
2279 /*platform=*/thread_task_runner,
2280 /*raster=*/thread_task_runner,
2281 /*ui=*/thread_task_runner,
2282 /*io=*/thread_task_runner);
2283 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2284 /*delegate=*/mock_delegate,
2285 /*rendering_api=*/mock_delegate.settings_.enable_impeller
2286 ? flutter::IOSRenderingAPI::kMetal
2287 : flutter::IOSRenderingAPI::kSoftware,
2288 /*platform_views_controller=*/nil,
2289 /*task_runners=*/runners,
2290 /*worker_task_runner=*/nil,
2291 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
2292 id mockFlutterView = OCMClassMock([FlutterView class]);
2293 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
2294 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
2295
2296 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
2297 __block auto bridge =
2298 std::make_unique<flutter::AccessibilityBridge>(/*view_controller=*/mockFlutterViewController,
2299 /*platform_view=*/platform_view.get(),
2300 /*platform_views_controller=*/nil,
2301 /*ios_delegate=*/std::move(ios_delegate));
2302
2303 FlutterSemanticsScrollView* flutterSemanticsScrollView;
2304 @autoreleasepool {
2305 FlutterScrollableSemanticsObject* semanticsObject =
2306 [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge->GetWeakPtr() uid:1234];
2307
2308 flutterSemanticsScrollView = semanticsObject.nativeAccessibility;
2309 }
2310 XCTAssertTrue(flutterSemanticsScrollView);
2311 // If the _semanticsObject is not a weak pointer this (or any other method on
2312 // flutterSemanticsScrollView) will cause an EXC_BAD_ACCESS.
2313 XCTAssertFalse([flutterSemanticsScrollView isAccessibilityElement]);
2314 }
2315
2316 - (void)testPlatformViewDestructorDoesNotCallSemanticsAPIs {
2317 class TestDelegate : public flutter::MockDelegate {
2318 public:
2319 void OnPlatformViewSetSemanticsEnabled(bool enabled) override { set_semantics_enabled_calls++; }
2320 int set_semantics_enabled_calls = 0;
2321 };
2322
2323 TestDelegate test_delegate;
2324 auto thread = std::make_unique<fml::Thread>("AccessibilityBridgeTest");
2325 auto thread_task_runner = thread->GetTaskRunner();
2326 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
2327 /*platform=*/thread_task_runner,
2328 /*raster=*/thread_task_runner,
2329 /*ui=*/thread_task_runner,
2330 /*io=*/thread_task_runner);
2331
2332 fml::AutoResetWaitableEvent latch;
2333 thread_task_runner->PostTask([&] {
2334 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2335 /*delegate=*/test_delegate,
2336 /*rendering_api=*/test_delegate.settings_.enable_impeller
2337 ? flutter::IOSRenderingAPI::kMetal
2338 : flutter::IOSRenderingAPI::kSoftware,
2339 /*platform_views_controller=*/nil,
2340 /*task_runners=*/runners,
2341 /*worker_task_runner=*/nil,
2342 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
2343
2344 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
2345 FlutterPlatformViewsController* flutterPlatformViewsController =
2346 [[FlutterPlatformViewsController alloc] init];
2347 flutterPlatformViewsController.taskRunner = thread_task_runner;
2348
2349 OCMStub([mockFlutterViewController platformViewsController])
2350 .andReturn(flutterPlatformViewsController);
2351 platform_view->SetOwnerViewController(mockFlutterViewController);
2352
2353 platform_view->SetSemanticsEnabled(true);
2354 XCTAssertNotEqual(test_delegate.set_semantics_enabled_calls, 0);
2355
2356 // Deleting PlatformViewIOS should not call OnPlatformViewSetSemanticsEnabled
2357 test_delegate.set_semantics_enabled_calls = 0;
2358 platform_view.reset();
2359 XCTAssertEqual(test_delegate.set_semantics_enabled_calls, 0);
2360
2361 latch.Signal();
2362 });
2363 latch.Wait();
2364 }
2365
2366 - (void)testResetsAccessibilityElementsOnHotRestart {
2367 flutter::MockDelegate mock_delegate;
2368 auto thread = std::make_unique<fml::Thread>("AccessibilityBridgeTest");
2369 auto thread_task_runner = thread->GetTaskRunner();
2370 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
2371 /*platform=*/thread_task_runner,
2372 /*raster=*/thread_task_runner,
2373 /*ui=*/thread_task_runner,
2374 /*io=*/thread_task_runner);
2375 id mockFlutterView = OCMClassMock([FlutterView class]);
2376 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
2377 OCMStub([mockFlutterViewController viewIfLoaded]).andReturn(mockFlutterView);
2378
2379 fml::AutoResetWaitableEvent latch;
2380 thread_task_runner->PostTask([&] {
2381 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2382 /*delegate=*/mock_delegate,
2383 /*rendering_api=*/mock_delegate.settings_.enable_impeller
2384 ? flutter::IOSRenderingAPI::kMetal
2385 : flutter::IOSRenderingAPI::kSoftware,
2386 /*platform_views_controller=*/nil,
2387 /*task_runners=*/runners,
2388 /*worker_task_runner=*/nil,
2389 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
2390
2391 platform_view->SetOwnerViewController(mockFlutterViewController);
2392 platform_view->SetSemanticsEnabled(true);
2393 platform_view->SetSemanticsTreeEnabled(true);
2394
2395 OCMExpect([mockFlutterView setAccessibilityElements:[OCMArg isNil]]);
2396 platform_view->OnPreEngineRestart();
2397 OCMVerifyAll(mockFlutterView);
2398
2399 latch.Signal();
2400 });
2401 latch.Wait();
2402 }
2403
2404 - (void)testWeakViewController {
2405 flutter::MockDelegate mock_delegate;
2406 auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
2407 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
2408 /*platform=*/thread_task_runner,
2409 /*raster=*/thread_task_runner,
2410 /*ui=*/thread_task_runner,
2411 /*io=*/thread_task_runner);
2412 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2413 /*delegate=*/mock_delegate,
2414 /*rendering_api=*/mock_delegate.settings_.enable_impeller
2415 ? flutter::IOSRenderingAPI::kMetal
2416 : flutter::IOSRenderingAPI::kSoftware,
2417 /*platform_views_controller=*/nil,
2418 /*task_runners=*/runners,
2419 /*worker_task_runner=*/nil,
2420 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
2421
2422 std::unique_ptr<flutter::AccessibilityBridge> bridge;
2423 @autoreleasepool {
2424 id mockFlutterView = OCMClassMock([FlutterView class]);
2425 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
2426 OCMStub([mockFlutterViewController viewIfLoaded]).andReturn(mockFlutterView);
2427 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
2428
2429 bridge = std::make_unique<flutter::AccessibilityBridge>(
2430 /*view_controller=*/mockFlutterViewController,
2431 /*platform_view=*/platform_view.get(),
2432 /*platform_views_controller=*/nil);
2433 XCTAssertTrue(bridge.get());
2434 XCTAssertNotNil(bridge->view());
2435 }
2436 XCTAssertNil(bridge->view());
2437 }
2438
2439 @end
FlutterBinaryMessengerConnection
int64_t FlutterBinaryMessengerConnection
Definition: FlutterBinaryMessenger.h:32
FlutterResult
void(^ FlutterResult)(id _Nullable result)
Definition: FlutterChannels.h:194
settings_
flutter::Settings settings_
Definition: FlutterEnginePlatformViewTest.mm:58
platform_view
std::unique_ptr< flutter::PlatformViewIOS > platform_view
Definition: FlutterEnginePlatformViewTest.mm:68
FlutterMacros.h
FlutterPlatformViews.h
FlutterPlatformViews_Internal.h
FlutterSemanticsScrollView.h
engine
id engine
Definition: FlutterTextInputPluginTest.mm:137
FlutterViewController_Internal.h
kRootNodeId
constexpr int32_t kRootNodeId
Definition: SemanticsObject.h:16
gMockPlatformView
static __weak MockPlatformView * gMockPlatformView
Definition: accessibility_bridge_test.mm:20
accessibility_bridge.h
AccessibilityBridgeTest
Definition: accessibility_bridge_test.mm:136
FlutterEngine
Definition: FlutterEngine.h:61
FlutterMethodCall
Definition: FlutterCodecs.h:221
+[FlutterMethodCall methodCallWithMethodName:arguments:]
instancetype methodCallWithMethodName:arguments:(NSString *method,[arguments] id _Nullable arguments)
FlutterPlatformViewsController
Definition: FlutterPlatformViewsController.h:31
-[FlutterPlatformViewsController reset]
void reset()
Discards all platform views instances and auxiliary resources.
Definition: FlutterPlatformViewsController.mm:644
-[FlutterPlatformViewsController registerViewFactory:withId:gestureRecognizersBlockingPolicy:]
void registerViewFactory:withId:gestureRecognizersBlockingPolicy:(NSObject< FlutterPlatformViewFactory > *factory,[withId] NSString *factoryId,[gestureRecognizersBlockingPolicy] FlutterPlatformViewGestureRecognizersBlockingPolicy gestureRecognizerBlockingPolicy)
set the factory used to construct embedded UI Views.
Definition: FlutterPlatformViewsController.mm:396
FlutterPlatformViewsController::taskRunner
const fml::RefPtr< fml::TaskRunner > & taskRunner
The task runner used to post rendering tasks to the platform thread.
Definition: FlutterPlatformViewsController.h:35
FlutterPlatformViewsController::flutterView
UIView *_Nullable flutterView
The flutter view.
Definition: FlutterPlatformViewsController.h:38
-[FlutterPlatformViewsController onMethodCall:result:]
void onMethodCall:result:(FlutterMethodCall *call,[result] FlutterResult result)
Handler for platform view message channels.
Definition: FlutterPlatformViewsController.mm:269
FlutterScrollableSemanticsObject
Definition: SemanticsObject.h:190
FlutterSemanticsObject
Definition: SemanticsObject.h:156
FlutterSemanticsScrollView
Definition: FlutterSemanticsScrollView.h:21
FlutterStandardMessageCodec
Definition: FlutterCodecs.h:209
FlutterViewController
Definition: FlutterViewController.h:57
FlutterView
Definition: FlutterView.h:33
MockFlutterPlatformFactory
Definition: accessibility_bridge_test.mm:55
MockFlutterPlatformView
Definition: accessibility_bridge_test.mm:40
MockPlatformView
Definition: accessibility_bridge_test.mm:23
SemanticsObjectContainer
Definition: SemanticsObject.h:228
SemanticsObjectContainer::semanticsObject
SemanticsObject * semanticsObject
Definition: SemanticsObject.h:235
SemanticsObject
Definition: SemanticsObject.h:32
SemanticsObject::nativeAccessibility
id nativeAccessibility
Definition: SemanticsObject.h:83
SemanticsObject::uid
int32_t uid
Definition: SemanticsObject.h:36
FLUTTER_ASSERT_ARC
Definition: FlutterChannelKeyResponder.mm:13
FLUTTER_ASSERT_ARC::CreateNewThread
fml::RefPtr< fml::TaskRunner > CreateNewThread(const std::string &name)
Definition: VsyncWaiterIosTest.mm:16
flutter
Definition: accessibility_bridge.h:26
flutter::IOSRenderingAPI::kMetal
@ kMetal
flutter::IOSRenderingAPI::kSoftware
@ kSoftware
platform_view_ios.h
FlutterBinaryMessenger-p
Definition: FlutterBinaryMessenger.h:49
+[FlutterMessageCodec-p sharedInstance]
instancetype sharedInstance()
FlutterPlatformView-p
Definition: FlutterPlatformViews.h:18
FlutterPlatformViewFactory-p
Definition: FlutterPlatformViews.h:26
texture_id
int64_t texture_id
Definition: texture_registrar_unittests.cc:24