| 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 = static_cast<int32_t>(flutter::SemanticsFlags::kHasImplicitScrolling);
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 = static_cast<int32_t>(flutter::SemanticsFlags::kHasImplicitScrolling);
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 = static_cast<int32_t>(flutter::SemanticsFlags::kHasImplicitScrolling);
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 = static_cast<int32_t>(flutter::SemanticsFlags::kScopesRoute);
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 = static_cast<int32_t>(flutter::SemanticsFlags::kNamesRoute);
696 node3.label = "node3";
697 nodes[node3.id] = node3;
698 flutter::SemanticsNode root_node;
699 root_node.id = kRootNodeId;
700 root_node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kScopesRoute);
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 = static_cast<int32_t>(flutter::SemanticsFlags::kIsInMutuallyExclusiveGroup) |
748 static_cast<int32_t>(flutter::SemanticsFlags::kIsEnabled) |
749 static_cast<int32_t>(flutter::SemanticsFlags::kHasCheckedState) |
750 static_cast<int32_t>(flutter::SemanticsFlags::kHasEnabledState);
751 nodes[root_node.id] = root_node;
752 bridge->UpdateSemantics(/*nodes=*/nodes, /*actions=*/actions);
753
754 SemanticsObjectContainer* rootContainer = flutterView.accessibilityElements[0];
755 FlutterSemanticsObject* rootNode = [rootContainer accessibilityElementAtIndex:0];
756
757 XCTAssertTrue((rootNode.accessibilityTraits & UIAccessibilityTraitButton) > 0);
758 XCTAssertNil(rootNode.accessibilityValue);
759 }
760
761 - (void)testSemanticObjectWithNoAccessibilityFlagNotMarkedAsResponsiveToUserInteraction {
762 flutter::MockDelegate mock_delegate;
763 auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
764 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
765 /*platform=*/thread_task_runner,
766 /*raster=*/thread_task_runner,
767 /*ui=*/thread_task_runner,
768 /*io=*/thread_task_runner);
769 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
770 /*delegate=*/mock_delegate,
771 /*rendering_api=*/mock_delegate.settings_.enable_impeller
772 ? flutter::IOSRenderingAPI::kMetal
773 : flutter::IOSRenderingAPI::kSoftware,
774 /*platform_views_controller=*/nil,
775 /*task_runners=*/runners,
776 /*worker_task_runner=*/nil,
777 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
778 id engine = OCMClassMock([FlutterEngine class]);
779 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
780 FlutterView* flutterView = [[FlutterView alloc] initWithDelegate:engine
781 opaque:YES
782 enableWideGamut:NO];
783 OCMStub([mockFlutterViewController view]).andReturn(flutterView);
784 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
785 __block auto bridge =
786 std::make_unique<flutter::AccessibilityBridge>(/*view_controller=*/mockFlutterViewController,
787 /*platform_view=*/platform_view.get(),
788 /*platform_views_controller=*/nil,
789 /*ios_delegate=*/std::move(ios_delegate));
790
791 flutter::CustomAccessibilityActionUpdates actions;
792 flutter::SemanticsNodeUpdates nodes;
793
794 flutter::SemanticsNode root_node;
795 root_node.id = kRootNodeId;
796
797 nodes[root_node.id] = root_node;
798 bridge->UpdateSemantics(/*nodes=*/nodes, /*actions=*/actions);
799
800 SemanticsObjectContainer* rootContainer = flutterView.accessibilityElements[0];
801 FlutterSemanticsObject* rootNode = [rootContainer accessibilityElementAtIndex:0];
802
803 XCTAssertFalse(rootNode.accessibilityRespondsToUserInteraction);
804 }
805
806 - (void)testSemanticObjectWithAccessibilityFlagsMarkedAsResponsiveToUserInteraction {
807 flutter::MockDelegate mock_delegate;
808 auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
809 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
810 /*platform=*/thread_task_runner,
811 /*raster=*/thread_task_runner,
812 /*ui=*/thread_task_runner,
813 /*io=*/thread_task_runner);
814 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
815 /*delegate=*/mock_delegate,
816 /*rendering_api=*/mock_delegate.settings_.enable_impeller
817 ? flutter::IOSRenderingAPI::kMetal
818 : flutter::IOSRenderingAPI::kSoftware,
819 /*platform_views_controller=*/nil,
820 /*task_runners=*/runners,
821 /*worker_task_runner=*/nil,
822 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
823 id engine = OCMClassMock([FlutterEngine class]);
824 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
825 FlutterView* flutterView = [[FlutterView alloc] initWithDelegate:engine
826 opaque:YES
827 enableWideGamut:NO];
828 OCMStub([mockFlutterViewController view]).andReturn(flutterView);
829 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
830 __block auto bridge =
831 std::make_unique<flutter::AccessibilityBridge>(/*view_controller=*/mockFlutterViewController,
832 /*platform_view=*/platform_view.get(),
833 /*platform_views_controller=*/nil,
834 /*ios_delegate=*/std::move(ios_delegate));
835
836 flutter::CustomAccessibilityActionUpdates actions;
837 flutter::SemanticsNodeUpdates nodes;
838
839 flutter::SemanticsNode root_node;
840 root_node.id = kRootNodeId;
841 root_node.actions = static_cast<int32_t>(flutter::SemanticsAction::kTap);
842
843 nodes[root_node.id] = root_node;
844 bridge->UpdateSemantics(/*nodes=*/nodes, /*actions=*/actions);
845
846 SemanticsObjectContainer* rootContainer = flutterView.accessibilityElements[0];
847 FlutterSemanticsObject* rootNode = [rootContainer accessibilityElementAtIndex:0];
848
849 XCTAssertTrue(rootNode.accessibilityRespondsToUserInteraction);
850 }
851
852 // Regression test for:
853 // https://github.com/flutter/flutter/issues/158477
854 - (void)testLabeledParentAndChildNotInteractive {
855 flutter::MockDelegate mock_delegate;
856 auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
857 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
858 /*platform=*/thread_task_runner,
859 /*raster=*/thread_task_runner,
860 /*ui=*/thread_task_runner,
861 /*io=*/thread_task_runner);
862
863 FlutterPlatformViewsController* flutterPlatformViewsController =
864 [[FlutterPlatformViewsController alloc] init];
865 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
866 /*delegate=*/mock_delegate,
867 /*rendering_api=*/mock_delegate.settings_.enable_impeller
868 ? flutter::IOSRenderingAPI::kMetal
869 : flutter::IOSRenderingAPI::kSoftware,
870 /*platform_views_controller=*/flutterPlatformViewsController,
871 /*task_runners=*/runners,
872 /*worker_task_runner=*/nil,
873 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
874 id engine = OCMClassMock([FlutterEngine class]);
875 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
876 FlutterView* flutterView = [[FlutterView alloc] initWithDelegate:engine
877 opaque:YES
878 enableWideGamut:NO];
879 OCMStub([mockFlutterViewController view]).andReturn(flutterView);
880
881 @autoreleasepool {
882 auto bridge = std::make_unique<flutter::AccessibilityBridge>(
883 /*view_controller=*/mockFlutterViewController,
884 /*platform_view=*/platform_view.get(),
885 /*platform_views_controller=*/flutterPlatformViewsController);
886
887 flutter::SemanticsNodeUpdates nodes;
888
889 flutter::SemanticsNode parent;
890 parent.id = 0;
891 parent.rect = SkRect::MakeXYWH(0, 0, 100, 200);
892 parent.label = "parent_label";
893
894 flutter::SemanticsNode node;
895 node.id = 1;
896 node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
897 node.label = "child_label";
898
899 parent.childrenInTraversalOrder.push_back(1);
900 parent.childrenInHitTestOrder.push_back(1);
901 nodes[0] = parent;
902 nodes[1] = node;
903 flutter::CustomAccessibilityActionUpdates actions;
904 bridge->UpdateSemantics(/*nodes=*/nodes, /*actions=*/actions);
905
906 SemanticsObjectContainer* parentContainer = flutterView.accessibilityElements[0];
907 FlutterSemanticsObject* parentNode = [parentContainer accessibilityElementAtIndex:0];
908 FlutterSemanticsObject* childNode = [parentContainer accessibilityElementAtIndex:1];
909
910 XCTAssertTrue([parentNode.accessibilityLabel isEqualToString:@"parent_label"]);
911 XCTAssertTrue([childNode.accessibilityLabel isEqualToString:@"child_label"]);
912 XCTAssertFalse(parentNode.accessibilityRespondsToUserInteraction);
913 XCTAssertFalse(childNode.accessibilityRespondsToUserInteraction);
914 }
915 }
916
917 - (void)testLayoutChangeWithNonAccessibilityElement {
918 flutter::MockDelegate mock_delegate;
919 auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
920 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
921 /*platform=*/thread_task_runner,
922 /*raster=*/thread_task_runner,
923 /*ui=*/thread_task_runner,
924 /*io=*/thread_task_runner);
925 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
926 /*delegate=*/mock_delegate,
927 /*rendering_api=*/mock_delegate.settings_.enable_impeller
928 ? flutter::IOSRenderingAPI::kMetal
929 : flutter::IOSRenderingAPI::kSoftware,
930 /*platform_views_controller=*/nil,
931 /*task_runners=*/runners,
932 /*worker_task_runner=*/nil,
933 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
934 id mockFlutterView = OCMClassMock([FlutterView class]);
935 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
936 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
937
938 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
939 [[NSMutableArray alloc] init];
940 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
941 ios_delegate->on_PostAccessibilityNotification_ =
942 [accessibility_notifications](UIAccessibilityNotifications notification, id argument) {
943 [accessibility_notifications addObject:@{
944 @"notification" : @(notification),
945 @"argument" : argument ? argument : [NSNull null],
946 }];
947 };
948 __block auto bridge =
949 std::make_unique<flutter::AccessibilityBridge>(/*view_controller=*/mockFlutterViewController,
950 /*platform_view=*/platform_view.get(),
951 /*platform_views_controller=*/nil,
952 /*ios_delegate=*/std::move(ios_delegate));
953
954 flutter::CustomAccessibilityActionUpdates actions;
955 flutter::SemanticsNodeUpdates nodes;
956
957 flutter::SemanticsNode node1;
958 node1.id = 1;
959 node1.label = "node1";
960 node1.childrenInTraversalOrder = {2, 3};
961 node1.childrenInHitTestOrder = {2, 3};
962 nodes[node1.id] = node1;
963 flutter::SemanticsNode node2;
964 node2.id = 2;
965 node2.label = "node2";
966 nodes[node2.id] = node2;
967 flutter::SemanticsNode node3;
968 node3.id = 3;
969 node3.label = "node3";
970 nodes[node3.id] = node3;
971 flutter::SemanticsNode root_node;
972 root_node.id = kRootNodeId;
973 root_node.label = "root";
974 root_node.childrenInTraversalOrder = {1};
975 root_node.childrenInHitTestOrder = {1};
976 nodes[root_node.id] = root_node;
977 bridge->UpdateSemantics(/*nodes=*/nodes, /*actions=*/actions);
978
979 // Simulates the focusing on the node 1.
980 bridge->AccessibilityObjectDidBecomeFocused(1);
981
982 // In this update, we make node 1 unfocusable and trigger the
983 // layout change. The accessibility bridge should send layoutchange
984 // notification with the first focusable node under node 1
985 flutter::CustomAccessibilityActionUpdates new_actions;
986 flutter::SemanticsNodeUpdates new_nodes;
987
988 flutter::SemanticsNode new_node1;
989 new_node1.id = 1;
990 new_node1.childrenInTraversalOrder = {2};
991 new_node1.childrenInHitTestOrder = {2};
992 new_nodes[new_node1.id] = new_node1;
993 bridge->UpdateSemantics(/*nodes=*/new_nodes, /*actions=*/new_actions);
994
995 XCTAssertEqual([accessibility_notifications count], 1ul);
996 SemanticsObject* focusObject = accessibility_notifications[0][@"argument"];
997 // Since node 1 is no longer focusable (no label), it will focus node 2 instead.
998 XCTAssertEqual([focusObject uid], 2);
999 XCTAssertEqual([accessibility_notifications[0][@"notification"] unsignedIntValue],
1000 UIAccessibilityLayoutChangedNotification);
1001 }
1002
1003 - (void)testLayoutChangeDoesCallNativeAccessibility {
1004 flutter::MockDelegate mock_delegate;
1005 auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
1006 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
1007 /*platform=*/thread_task_runner,
1008 /*raster=*/thread_task_runner,
1009 /*ui=*/thread_task_runner,
1010 /*io=*/thread_task_runner);
1011 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1012 /*delegate=*/mock_delegate,
1013 /*rendering_api=*/mock_delegate.settings_.enable_impeller
1014 ? flutter::IOSRenderingAPI::kMetal
1015 : flutter::IOSRenderingAPI::kSoftware,
1016 /*platform_views_controller=*/nil,
1017 /*task_runners=*/runners,
1018 /*worker_task_runner=*/nil,
1019 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
1020 id mockFlutterView = OCMClassMock([FlutterView class]);
1021 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
1022 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1023
1024 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1025 [[NSMutableArray alloc] init];
1026 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1027 ios_delegate->on_PostAccessibilityNotification_ =
1028 [accessibility_notifications](UIAccessibilityNotifications notification, id argument) {
1029 [accessibility_notifications addObject:@{
1030 @"notification" : @(notification),
1031 @"argument" : argument ? argument : [NSNull null],
1032 }];
1033 };
1034 __block auto bridge =
1035 std::make_unique<flutter::AccessibilityBridge>(/*view_controller=*/mockFlutterViewController,
1036 /*platform_view=*/platform_view.get(),
1037 /*platform_views_controller=*/nil,
1038 /*ios_delegate=*/std::move(ios_delegate));
1039
1040 flutter::CustomAccessibilityActionUpdates actions;
1041 flutter::SemanticsNodeUpdates nodes;
1042
1043 flutter::SemanticsNode node1;
1044 node1.id = 1;
1045 node1.label = "node1";
1046 nodes[node1.id] = node1;
1047 flutter::SemanticsNode root_node;
1048 root_node.id = kRootNodeId;
1049 root_node.label = "root";
1050 root_node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasImplicitScrolling);
1051 root_node.childrenInTraversalOrder = {1};
1052 root_node.childrenInHitTestOrder = {1};
1053 nodes[root_node.id] = root_node;
1054 bridge->UpdateSemantics(/*nodes=*/nodes, /*actions=*/actions);
1055
1056 // Simulates the focusing on the node 0.
1057 bridge->AccessibilityObjectDidBecomeFocused(0);
1058
1059 // Remove node 1 to trigger a layout change notification
1060 flutter::CustomAccessibilityActionUpdates new_actions;
1061 flutter::SemanticsNodeUpdates new_nodes;
1062
1063 flutter::SemanticsNode new_root_node;
1064 new_root_node.id = kRootNodeId;
1065 new_root_node.label = "root";
1066 new_root_node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasImplicitScrolling);
1067 new_nodes[new_root_node.id] = new_root_node;
1068 bridge->UpdateSemantics(/*nodes=*/new_nodes, /*actions=*/new_actions);
1069
1070 XCTAssertEqual([accessibility_notifications count], 1ul);
1071 id focusObject = accessibility_notifications[0][@"argument"];
1072
1073 // Make sure the focused item is not specificed when it stays the same.
1074 // See: https://github.com/flutter/flutter/issues/104176
1075 XCTAssertEqualObjects(focusObject, [NSNull null]);
1076 XCTAssertEqual([accessibility_notifications[0][@"notification"] unsignedIntValue],
1077 UIAccessibilityLayoutChangedNotification);
1078 }
1079
1080 - (void)testLayoutChangeDoesCallNativeAccessibilityWhenFocusChanged {
1081 flutter::MockDelegate mock_delegate;
1082 auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
1083 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
1084 /*platform=*/thread_task_runner,
1085 /*raster=*/thread_task_runner,
1086 /*ui=*/thread_task_runner,
1087 /*io=*/thread_task_runner);
1088 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1089 /*delegate=*/mock_delegate,
1090 /*rendering_api=*/mock_delegate.settings_.enable_impeller
1091 ? flutter::IOSRenderingAPI::kMetal
1092 : flutter::IOSRenderingAPI::kSoftware,
1093 /*platform_views_controller=*/nil,
1094 /*task_runners=*/runners,
1095 /*worker_task_runner=*/nil,
1096 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
1097 id mockFlutterView = OCMClassMock([FlutterView class]);
1098 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
1099 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1100
1101 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1102 [[NSMutableArray alloc] init];
1103 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1104 ios_delegate->on_PostAccessibilityNotification_ =
1105 [accessibility_notifications](UIAccessibilityNotifications notification, id argument) {
1106 [accessibility_notifications addObject:@{
1107 @"notification" : @(notification),
1108 @"argument" : argument ? argument : [NSNull null],
1109 }];
1110 };
1111 __block auto bridge =
1112 std::make_unique<flutter::AccessibilityBridge>(/*view_controller=*/mockFlutterViewController,
1113 /*platform_view=*/platform_view.get(),
1114 /*platform_views_controller=*/nil,
1115 /*ios_delegate=*/std::move(ios_delegate));
1116
1117 flutter::CustomAccessibilityActionUpdates actions;
1118 flutter::SemanticsNodeUpdates nodes;
1119
1120 flutter::SemanticsNode node1;
1121 node1.id = 1;
1122 node1.label = "node1";
1123 nodes[node1.id] = node1;
1124 flutter::SemanticsNode root_node;
1125 root_node.id = kRootNodeId;
1126 root_node.label = "root";
1127 root_node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasImplicitScrolling);
1128 root_node.childrenInTraversalOrder = {1};
1129 root_node.childrenInHitTestOrder = {1};
1130 nodes[root_node.id] = root_node;
1131 bridge->UpdateSemantics(/*nodes=*/nodes, /*actions=*/actions);
1132
1133 // Simulates the focusing on the node 1.
1134 bridge->AccessibilityObjectDidBecomeFocused(1);
1135
1136 // Remove node 1 to trigger a layout change notification, and focus should be one root
1137 flutter::CustomAccessibilityActionUpdates new_actions;
1138 flutter::SemanticsNodeUpdates new_nodes;
1139
1140 flutter::SemanticsNode new_root_node;
1141 new_root_node.id = kRootNodeId;
1142 new_root_node.label = "root";
1143 new_root_node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasImplicitScrolling);
1144 new_nodes[new_root_node.id] = new_root_node;
1145 bridge->UpdateSemantics(/*nodes=*/new_nodes, /*actions=*/new_actions);
1146
1147 XCTAssertEqual([accessibility_notifications count], 1ul);
1148 SemanticsObject* focusObject2 = accessibility_notifications[0][@"argument"];
1149
1150 // Bridge should ask accessibility to focus on root because node 1 is moved from screen.
1151 XCTAssertTrue([focusObject2 isKindOfClass:[FlutterSemanticsScrollView class]]);
1152 XCTAssertEqual([accessibility_notifications[0][@"notification"] unsignedIntValue],
1153 UIAccessibilityLayoutChangedNotification);
1154 }
1155
1156 - (void)testScrollableSemanticsContainerReturnsCorrectChildren {
1157 flutter::MockDelegate mock_delegate;
1158 auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
1159 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
1160 /*platform=*/thread_task_runner,
1161 /*raster=*/thread_task_runner,
1162 /*ui=*/thread_task_runner,
1163 /*io=*/thread_task_runner);
1164 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1165 /*delegate=*/mock_delegate,
1166 /*rendering_api=*/mock_delegate.settings_.enable_impeller
1167 ? flutter::IOSRenderingAPI::kMetal
1168 : flutter::IOSRenderingAPI::kSoftware,
1169 /*platform_views_controller=*/nil,
1170 /*task_runners=*/runners,
1171 /*worker_task_runner=*/nil,
1172 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
1173 id mockFlutterView = OCMClassMock([FlutterView class]);
1174 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
1175 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1176
1177 OCMExpect([mockFlutterView
1178 setAccessibilityElements:[OCMArg checkWithBlock:^BOOL(NSArray* value) {
1179 if ([value count] != 1) {
1180 return NO;
1181 }
1182 SemanticsObjectContainer* container = value[0];
1183 SemanticsObject* object = container.semanticsObject;
1184 FlutterScrollableSemanticsObject* scrollable =
1185 (FlutterScrollableSemanticsObject*)object.children[0];
1186 id nativeScrollable = scrollable.nativeAccessibility;
1187 SemanticsObjectContainer* scrollableContainer = [nativeScrollable accessibilityContainer];
1188 return [scrollableContainer indexOfAccessibilityElement:nativeScrollable] == 1;
1189 }]]);
1190 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1191 __block auto bridge =
1192 std::make_unique<flutter::AccessibilityBridge>(/*view_controller=*/mockFlutterViewController,
1193 /*platform_view=*/platform_view.get(),
1194 /*platform_views_controller=*/nil,
1195 /*ios_delegate=*/std::move(ios_delegate));
1196
1197 flutter::CustomAccessibilityActionUpdates actions;
1198 flutter::SemanticsNodeUpdates nodes;
1199
1200 flutter::SemanticsNode node1;
1201 node1.id = 1;
1202 node1.label = "node1";
1203 node1.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasImplicitScrolling);
1204 nodes[node1.id] = node1;
1205 flutter::SemanticsNode root_node;
1206 root_node.id = kRootNodeId;
1207 root_node.label = "root";
1208 root_node.childrenInTraversalOrder = {1};
1209 root_node.childrenInHitTestOrder = {1};
1210 nodes[root_node.id] = root_node;
1211 bridge->UpdateSemantics(/*nodes=*/nodes, /*actions=*/actions);
1212 OCMVerifyAll(mockFlutterView);
1213 }
1214
1215 - (void)testAnnouncesRouteChangesAndLayoutChangeInOneUpdate {
1216 flutter::MockDelegate mock_delegate;
1217 auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
1218 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
1219 /*platform=*/thread_task_runner,
1220 /*raster=*/thread_task_runner,
1221 /*ui=*/thread_task_runner,
1222 /*io=*/thread_task_runner);
1223 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1224 /*delegate=*/mock_delegate,
1225 /*rendering_api=*/mock_delegate.settings_.enable_impeller
1226 ? flutter::IOSRenderingAPI::kMetal
1227 : flutter::IOSRenderingAPI::kSoftware,
1228 /*platform_views_controller=*/nil,
1229 /*task_runners=*/runners,
1230 /*worker_task_runner=*/nil,
1231 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
1232 id mockFlutterView = OCMClassMock([FlutterView class]);
1233 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
1234 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1235
1236 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1237 [[NSMutableArray alloc] init];
1238 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1239 ios_delegate->on_PostAccessibilityNotification_ =
1240 [accessibility_notifications](UIAccessibilityNotifications notification, id argument) {
1241 [accessibility_notifications addObject:@{
1242 @"notification" : @(notification),
1243 @"argument" : argument ? argument : [NSNull null],
1244 }];
1245 };
1246 __block auto bridge =
1247 std::make_unique<flutter::AccessibilityBridge>(/*view_controller=*/mockFlutterViewController,
1248 /*platform_view=*/platform_view.get(),
1249 /*platform_views_controller=*/nil,
1250 /*ios_delegate=*/std::move(ios_delegate));
1251
1252 flutter::CustomAccessibilityActionUpdates actions;
1253 flutter::SemanticsNodeUpdates nodes;
1254
1255 flutter::SemanticsNode node1;
1256 node1.id = 1;
1257 node1.label = "node1";
1258 node1.flags = static_cast<int32_t>(flutter::SemanticsFlags::kScopesRoute) |
1259 static_cast<int32_t>(flutter::SemanticsFlags::kNamesRoute);
1260 nodes[node1.id] = node1;
1261 flutter::SemanticsNode node3;
1262 node3.id = 3;
1263 node3.label = "node3";
1264 nodes[node3.id] = node3;
1265 flutter::SemanticsNode root_node;
1266 root_node.id = kRootNodeId;
1267 root_node.label = "root";
1268 root_node.childrenInTraversalOrder = {1, 3};
1269 root_node.childrenInHitTestOrder = {1, 3};
1270 nodes[root_node.id] = root_node;
1271 bridge->UpdateSemantics(/*nodes=*/nodes, /*actions=*/actions);
1272
1273 XCTAssertEqual([accessibility_notifications count], 1ul);
1274 XCTAssertEqualObjects(accessibility_notifications[0][@"argument"], @"node1");
1275 XCTAssertEqual([accessibility_notifications[0][@"notification"] unsignedIntValue],
1276 UIAccessibilityScreenChangedNotification);
1277
1278 // Simulates the focusing on the node 0.
1279 bridge->AccessibilityObjectDidBecomeFocused(0);
1280
1281 flutter::SemanticsNodeUpdates new_nodes;
1282
1283 flutter::SemanticsNode new_node1;
1284 new_node1.id = 1;
1285 new_node1.label = "new_node1";
1286 new_node1.flags = static_cast<int32_t>(flutter::SemanticsFlags::kScopesRoute) |
1287 static_cast<int32_t>(flutter::SemanticsFlags::kNamesRoute);
1288 new_node1.childrenInTraversalOrder = {2};
1289 new_node1.childrenInHitTestOrder = {2};
1290 new_nodes[new_node1.id] = new_node1;
1291 flutter::SemanticsNode new_node2;
1292 new_node2.id = 2;
1293 new_node2.label = "new_node2";
1294 new_node2.flags = static_cast<int32_t>(flutter::SemanticsFlags::kScopesRoute) |
1295 static_cast<int32_t>(flutter::SemanticsFlags::kNamesRoute);
1296 new_nodes[new_node2.id] = new_node2;
1297 flutter::SemanticsNode new_root_node;
1298 new_root_node.id = kRootNodeId;
1299 new_root_node.label = "root";
1300 new_root_node.childrenInTraversalOrder = {1};
1301 new_root_node.childrenInHitTestOrder = {1};
1302 new_nodes[new_root_node.id] = new_root_node;
1303 bridge->UpdateSemantics(/*nodes=*/new_nodes, /*actions=*/actions);
1304 XCTAssertEqual([accessibility_notifications count], 3ul);
1305 XCTAssertEqualObjects(accessibility_notifications[1][@"argument"], @"new_node2");
1306 XCTAssertEqual([accessibility_notifications[1][@"notification"] unsignedIntValue],
1307 UIAccessibilityScreenChangedNotification);
1308 SemanticsObject* focusObject = accessibility_notifications[2][@"argument"];
1309 XCTAssertEqual([focusObject uid], 0);
1310 XCTAssertEqual([accessibility_notifications[2][@"notification"] unsignedIntValue],
1311 UIAccessibilityLayoutChangedNotification);
1312 }
1313
1314 - (void)testAnnouncesRouteChangesWhenAddAdditionalRoute {
1315 flutter::MockDelegate mock_delegate;
1316 auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
1317 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
1318 /*platform=*/thread_task_runner,
1319 /*raster=*/thread_task_runner,
1320 /*ui=*/thread_task_runner,
1321 /*io=*/thread_task_runner);
1322 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1323 /*delegate=*/mock_delegate,
1324 /*rendering_api=*/mock_delegate.settings_.enable_impeller
1325 ? flutter::IOSRenderingAPI::kMetal
1326 : flutter::IOSRenderingAPI::kSoftware,
1327 /*platform_views_controller=*/nil,
1328 /*task_runners=*/runners,
1329 /*worker_task_runner=*/nil,
1330 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
1331 id mockFlutterView = OCMClassMock([FlutterView class]);
1332 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
1333 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1334
1335 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1336 [[NSMutableArray alloc] init];
1337 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1338 ios_delegate->on_PostAccessibilityNotification_ =
1339 [accessibility_notifications](UIAccessibilityNotifications notification, id argument) {
1340 [accessibility_notifications addObject:@{
1341 @"notification" : @(notification),
1342 @"argument" : argument ? argument : [NSNull null],
1343 }];
1344 };
1345 __block auto bridge =
1346 std::make_unique<flutter::AccessibilityBridge>(/*view_controller=*/mockFlutterViewController,
1347 /*platform_view=*/platform_view.get(),
1348 /*platform_views_controller=*/nil,
1349 /*ios_delegate=*/std::move(ios_delegate));
1350
1351 flutter::CustomAccessibilityActionUpdates actions;
1352 flutter::SemanticsNodeUpdates nodes;
1353
1354 flutter::SemanticsNode node1;
1355 node1.id = 1;
1356 node1.label = "node1";
1357 node1.flags = static_cast<int32_t>(flutter::SemanticsFlags::kScopesRoute) |
1358 static_cast<int32_t>(flutter::SemanticsFlags::kNamesRoute);
1359 nodes[node1.id] = node1;
1360 flutter::SemanticsNode root_node;
1361 root_node.id = kRootNodeId;
1362 root_node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kScopesRoute);
1363 root_node.childrenInTraversalOrder = {1};
1364 root_node.childrenInHitTestOrder = {1};
1365 nodes[root_node.id] = root_node;
1366 bridge->UpdateSemantics(/*nodes=*/nodes, /*actions=*/actions);
1367
1368 XCTAssertEqual([accessibility_notifications count], 1ul);
1369 XCTAssertEqualObjects(accessibility_notifications[0][@"argument"], @"node1");
1370 XCTAssertEqual([accessibility_notifications[0][@"notification"] unsignedIntValue],
1371 UIAccessibilityScreenChangedNotification);
1372
1373 flutter::SemanticsNodeUpdates new_nodes;
1374
1375 flutter::SemanticsNode new_node1;
1376 new_node1.id = 1;
1377 new_node1.label = "new_node1";
1378 new_node1.flags = static_cast<int32_t>(flutter::SemanticsFlags::kScopesRoute) |
1379 static_cast<int32_t>(flutter::SemanticsFlags::kNamesRoute);
1380 new_node1.childrenInTraversalOrder = {2};
1381 new_node1.childrenInHitTestOrder = {2};
1382 new_nodes[new_node1.id] = new_node1;
1383 flutter::SemanticsNode new_node2;
1384 new_node2.id = 2;
1385 new_node2.label = "new_node2";
1386 new_node2.flags = static_cast<int32_t>(flutter::SemanticsFlags::kScopesRoute) |
1387 static_cast<int32_t>(flutter::SemanticsFlags::kNamesRoute);
1388 new_nodes[new_node2.id] = new_node2;
1389 flutter::SemanticsNode new_root_node;
1390 new_root_node.id = kRootNodeId;
1391 new_root_node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kScopesRoute);
1392 new_root_node.childrenInTraversalOrder = {1};
1393 new_root_node.childrenInHitTestOrder = {1};
1394 new_nodes[new_root_node.id] = new_root_node;
1395 bridge->UpdateSemantics(/*nodes=*/new_nodes, /*actions=*/actions);
1396 XCTAssertEqual([accessibility_notifications count], 2ul);
1397 XCTAssertEqualObjects(accessibility_notifications[1][@"argument"], @"new_node2");
1398 XCTAssertEqual([accessibility_notifications[1][@"notification"] unsignedIntValue],
1399 UIAccessibilityScreenChangedNotification);
1400 }
1401
1402 - (void)testAnnouncesRouteChangesRemoveRouteInMiddle {
1403 flutter::MockDelegate mock_delegate;
1404 auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
1405 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
1406 /*platform=*/thread_task_runner,
1407 /*raster=*/thread_task_runner,
1408 /*ui=*/thread_task_runner,
1409 /*io=*/thread_task_runner);
1410 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1411 /*delegate=*/mock_delegate,
1412 /*rendering_api=*/mock_delegate.settings_.enable_impeller
1413 ? flutter::IOSRenderingAPI::kMetal
1414 : flutter::IOSRenderingAPI::kSoftware,
1415 /*platform_views_controller=*/nil,
1416 /*task_runners=*/runners,
1417 /*worker_task_runner=*/nil,
1418 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
1419 id mockFlutterView = OCMClassMock([FlutterView class]);
1420 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
1421 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1422
1423 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1424 [[NSMutableArray alloc] init];
1425 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1426 ios_delegate->on_PostAccessibilityNotification_ =
1427 [accessibility_notifications](UIAccessibilityNotifications notification, id argument) {
1428 [accessibility_notifications addObject:@{
1429 @"notification" : @(notification),
1430 @"argument" : argument ? argument : [NSNull null],
1431 }];
1432 };
1433 __block auto bridge =
1434 std::make_unique<flutter::AccessibilityBridge>(/*view_controller=*/mockFlutterViewController,
1435 /*platform_view=*/platform_view.get(),
1436 /*platform_views_controller=*/nil,
1437 /*ios_delegate=*/std::move(ios_delegate));
1438
1439 flutter::CustomAccessibilityActionUpdates actions;
1440 flutter::SemanticsNodeUpdates nodes;
1441
1442 flutter::SemanticsNode node1;
1443 node1.id = 1;
1444 node1.label = "node1";
1445 node1.flags = static_cast<int32_t>(flutter::SemanticsFlags::kScopesRoute) |
1446 static_cast<int32_t>(flutter::SemanticsFlags::kNamesRoute);
1447 node1.childrenInTraversalOrder = {2};
1448 node1.childrenInHitTestOrder = {2};
1449 nodes[node1.id] = node1;
1450 flutter::SemanticsNode node2;
1451 node2.id = 2;
1452 node2.label = "node2";
1453 node2.flags = static_cast<int32_t>(flutter::SemanticsFlags::kScopesRoute) |
1454 static_cast<int32_t>(flutter::SemanticsFlags::kNamesRoute);
1455 nodes[node2.id] = node2;
1456 flutter::SemanticsNode root_node;
1457 root_node.id = kRootNodeId;
1458 root_node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kScopesRoute);
1459 root_node.childrenInTraversalOrder = {1};
1460 root_node.childrenInHitTestOrder = {1};
1461 nodes[root_node.id] = root_node;
1462 bridge->UpdateSemantics(/*nodes=*/nodes, /*actions=*/actions);
1463
1464 XCTAssertEqual([accessibility_notifications count], 1ul);
1465 XCTAssertEqualObjects(accessibility_notifications[0][@"argument"], @"node2");
1466 XCTAssertEqual([accessibility_notifications[0][@"notification"] unsignedIntValue],
1467 UIAccessibilityScreenChangedNotification);
1468
1469 flutter::SemanticsNodeUpdates new_nodes;
1470
1471 flutter::SemanticsNode new_node1;
1472 new_node1.id = 1;
1473 new_node1.label = "new_node1";
1474 new_node1.childrenInTraversalOrder = {2};
1475 new_node1.childrenInHitTestOrder = {2};
1476 new_nodes[new_node1.id] = new_node1;
1477 flutter::SemanticsNode new_node2;
1478 new_node2.id = 2;
1479 new_node2.label = "new_node2";
1480 new_node2.flags = static_cast<int32_t>(flutter::SemanticsFlags::kScopesRoute) |
1481 static_cast<int32_t>(flutter::SemanticsFlags::kNamesRoute);
1482 new_nodes[new_node2.id] = new_node2;
1483 flutter::SemanticsNode new_root_node;
1484 new_root_node.id = kRootNodeId;
1485 new_root_node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kScopesRoute);
1486 new_root_node.childrenInTraversalOrder = {1};
1487 new_root_node.childrenInHitTestOrder = {1};
1488 new_nodes[new_root_node.id] = new_root_node;
1489 bridge->UpdateSemantics(/*nodes=*/new_nodes, /*actions=*/actions);
1490 XCTAssertEqual([accessibility_notifications count], 2ul);
1491 XCTAssertEqualObjects(accessibility_notifications[1][@"argument"], @"new_node2");
1492 XCTAssertEqual([accessibility_notifications[1][@"notification"] unsignedIntValue],
1493 UIAccessibilityScreenChangedNotification);
1494 }
1495
1496 - (void)testHandleEvent {
1497 flutter::MockDelegate mock_delegate;
1498 auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
1499 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
1500 /*platform=*/thread_task_runner,
1501 /*raster=*/thread_task_runner,
1502 /*ui=*/thread_task_runner,
1503 /*io=*/thread_task_runner);
1504 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1505 /*delegate=*/mock_delegate,
1506 /*rendering_api=*/mock_delegate.settings_.enable_impeller
1507 ? flutter::IOSRenderingAPI::kMetal
1508 : flutter::IOSRenderingAPI::kSoftware,
1509 /*platform_views_controller=*/nil,
1510 /*task_runners=*/runners,
1511 /*worker_task_runner=*/nil,
1512 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
1513 id mockFlutterView = OCMClassMock([FlutterView class]);
1514 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
1515 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1516
1517 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1518 [[NSMutableArray alloc] init];
1519 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1520 ios_delegate->on_PostAccessibilityNotification_ =
1521 [accessibility_notifications](UIAccessibilityNotifications notification, id argument) {
1522 [accessibility_notifications addObject:@{
1523 @"notification" : @(notification),
1524 @"argument" : argument ? argument : [NSNull null],
1525 }];
1526 };
1527 __block auto bridge =
1528 std::make_unique<flutter::AccessibilityBridge>(/*view_controller=*/mockFlutterViewController,
1529 /*platform_view=*/platform_view.get(),
1530 /*platform_views_controller=*/nil,
1531 /*ios_delegate=*/std::move(ios_delegate));
1532
1533 NSDictionary<NSString*, id>* annotatedEvent = @{@"type" : @"focus", @"nodeId" : @123};
1534
1535 bridge->HandleEvent(annotatedEvent);
1536
1537 XCTAssertEqual([accessibility_notifications count], 1ul);
1538 XCTAssertEqual([accessibility_notifications[0][@"notification"] unsignedIntValue],
1539 UIAccessibilityLayoutChangedNotification);
1540 }
1541
1542 - (void)testAccessibilityObjectDidBecomeFocused {
1543 flutter::MockDelegate mock_delegate;
1544 auto thread = std::make_unique<fml::Thread>("AccessibilityBridgeTest");
1545 auto thread_task_runner = thread->GetTaskRunner();
1546 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
1547 /*platform=*/thread_task_runner,
1548 /*raster=*/thread_task_runner,
1549 /*ui=*/thread_task_runner,
1550 /*io=*/thread_task_runner);
1551 id messenger = OCMProtocolMock(@protocol(FlutterBinaryMessenger));
1552 id engine = OCMClassMock([FlutterEngine class]);
1553 id flutterViewController = OCMClassMock([FlutterViewController class]);
1554
1555 OCMStub([flutterViewController engine]).andReturn(engine);
1556 OCMStub([engine binaryMessenger]).andReturn(messenger);
1557 FlutterBinaryMessengerConnection connection = 123;
1558 OCMStub([messenger setMessageHandlerOnChannel:@"flutter/accessibility"
1559 binaryMessageHandler:[OCMArg any]])
1560 .andReturn(connection);
1561
1562 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1563 /*delegate=*/mock_delegate,
1564 /*rendering_api=*/mock_delegate.settings_.enable_impeller
1565 ? flutter::IOSRenderingAPI::kMetal
1566 : flutter::IOSRenderingAPI::kSoftware,
1567 /*platform_views_controller=*/nil,
1568 /*task_runners=*/runners,
1569 /*worker_task_runner=*/nil,
1570 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
1571 fml::AutoResetWaitableEvent latch;
1572 thread_task_runner->PostTask([&] {
1573 platform_view->SetOwnerViewController(flutterViewController);
1574 auto bridge =
1575 std::make_unique<flutter::AccessibilityBridge>(/*view=*/nil,
1576 /*platform_view=*/platform_view.get(),
1577 /*platform_views_controller=*/nil);
1578 XCTAssertTrue(bridge.get());
1579 OCMVerify([messenger setMessageHandlerOnChannel:@"flutter/accessibility"
1580 binaryMessageHandler:[OCMArg isNotNil]]);
1581
1582 bridge->AccessibilityObjectDidBecomeFocused(123);
1583
1584 NSDictionary<NSString*, id>* annotatedEvent = @{@"type" : @"didGainFocus", @"nodeId" : @123};
1585 NSData* encodedMessage = [[FlutterStandardMessageCodec sharedInstance] encode:annotatedEvent];
1586
1587 OCMVerify([messenger sendOnChannel:@"flutter/accessibility" message:encodedMessage]);
1588 latch.Signal();
1589 });
1590 latch.Wait();
1591
1592 [engine stopMocking];
1593 }
1594
1595 - (void)testAnnouncesRouteChangesWhenNoNamesRoute {
1596 flutter::MockDelegate mock_delegate;
1597 auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
1598 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
1599 /*platform=*/thread_task_runner,
1600 /*raster=*/thread_task_runner,
1601 /*ui=*/thread_task_runner,
1602 /*io=*/thread_task_runner);
1603 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1604 /*delegate=*/mock_delegate,
1605 /*rendering_api=*/mock_delegate.settings_.enable_impeller
1606 ? flutter::IOSRenderingAPI::kMetal
1607 : flutter::IOSRenderingAPI::kSoftware,
1608 /*platform_views_controller=*/nil,
1609 /*task_runners=*/runners,
1610 /*worker_task_runner=*/nil,
1611 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
1612 id mockFlutterView = OCMClassMock([FlutterView class]);
1613 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
1614 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1615
1616 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1617 [[NSMutableArray alloc] init];
1618 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1619 ios_delegate->on_PostAccessibilityNotification_ =
1620 [accessibility_notifications](UIAccessibilityNotifications notification, id argument) {
1621 [accessibility_notifications addObject:@{
1622 @"notification" : @(notification),
1623 @"argument" : argument ? argument : [NSNull null],
1624 }];
1625 };
1626 __block auto bridge =
1627 std::make_unique<flutter::AccessibilityBridge>(/*view_controller=*/mockFlutterViewController,
1628 /*platform_view=*/platform_view.get(),
1629 /*platform_views_controller=*/nil,
1630 /*ios_delegate=*/std::move(ios_delegate));
1631
1632 flutter::CustomAccessibilityActionUpdates actions;
1633 flutter::SemanticsNodeUpdates nodes;
1634
1635 flutter::SemanticsNode node1;
1636 node1.id = 1;
1637 node1.label = "node1";
1638 node1.flags = static_cast<int32_t>(flutter::SemanticsFlags::kScopesRoute) |
1639 static_cast<int32_t>(flutter::SemanticsFlags::kNamesRoute);
1640 node1.childrenInTraversalOrder = {2, 3};
1641 node1.childrenInHitTestOrder = {2, 3};
1642 nodes[node1.id] = node1;
1643 flutter::SemanticsNode node2;
1644 node2.id = 2;
1645 node2.label = "node2";
1646 nodes[node2.id] = node2;
1647 flutter::SemanticsNode node3;
1648 node3.id = 3;
1649 node3.label = "node3";
1650 nodes[node3.id] = node3;
1651 flutter::SemanticsNode root_node;
1652 root_node.id = kRootNodeId;
1653 root_node.childrenInTraversalOrder = {1};
1654 root_node.childrenInHitTestOrder = {1};
1655 nodes[root_node.id] = root_node;
1656 bridge->UpdateSemantics(/*nodes=*/nodes, /*actions=*/actions);
1657
1658 // Notification should focus first focusable node, which is node1.
1659 XCTAssertEqual([accessibility_notifications count], 1ul);
1660 id focusObject = accessibility_notifications[0][@"argument"];
1661 XCTAssertTrue([focusObject isKindOfClass:[NSString class]]);
1662 XCTAssertEqualObjects(focusObject, @"node1");
1663 XCTAssertEqual([accessibility_notifications[0][@"notification"] unsignedIntValue],
1664 UIAccessibilityScreenChangedNotification);
1665 }
1666
1667 - (void)testAnnouncesLayoutChangeWithNilIfLastFocusIsRemoved {
1668 flutter::MockDelegate mock_delegate;
1669 auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
1670 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
1671 /*platform=*/thread_task_runner,
1672 /*raster=*/thread_task_runner,
1673 /*ui=*/thread_task_runner,
1674 /*io=*/thread_task_runner);
1675 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1676 /*delegate=*/mock_delegate,
1677 /*rendering_api=*/mock_delegate.settings_.enable_impeller
1678 ? flutter::IOSRenderingAPI::kMetal
1679 : flutter::IOSRenderingAPI::kSoftware,
1680 /*platform_views_controller=*/nil,
1681 /*task_runners=*/runners,
1682 /*worker_task_runner=*/nil,
1683 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
1684 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
1685 id mockFlutterView = OCMClassMock([FlutterView class]);
1686 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1687
1688 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1689 [[NSMutableArray alloc] init];
1690 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1691 ios_delegate->on_PostAccessibilityNotification_ =
1692 [accessibility_notifications](UIAccessibilityNotifications notification, id argument) {
1693 [accessibility_notifications addObject:@{
1694 @"notification" : @(notification),
1695 @"argument" : argument ? argument : [NSNull null],
1696 }];
1697 };
1698 __block auto bridge =
1699 std::make_unique<flutter::AccessibilityBridge>(/*view_controller=*/mockFlutterViewController,
1700 /*platform_view=*/platform_view.get(),
1701 /*platform_views_controller=*/nil,
1702 /*ios_delegate=*/std::move(ios_delegate));
1703
1704 flutter::CustomAccessibilityActionUpdates actions;
1705 flutter::SemanticsNodeUpdates first_update;
1706
1707 flutter::SemanticsNode route_node;
1708 route_node.id = 1;
1709 route_node.label = "route";
1710 first_update[route_node.id] = route_node;
1711 flutter::SemanticsNode root_node;
1712 root_node.id = kRootNodeId;
1713 root_node.label = "root";
1714 root_node.childrenInTraversalOrder = {1};
1715 root_node.childrenInHitTestOrder = {1};
1716 first_update[root_node.id] = root_node;
1717 bridge->UpdateSemantics(/*nodes=*/first_update, /*actions=*/actions);
1718
1719 XCTAssertEqual([accessibility_notifications count], 0ul);
1720 // Simulates the focusing on the node 1.
1721 bridge->AccessibilityObjectDidBecomeFocused(1);
1722
1723 flutter::SemanticsNodeUpdates second_update;
1724 // Simulates the removal of the node 1
1725 flutter::SemanticsNode new_root_node;
1726 new_root_node.id = kRootNodeId;
1727 new_root_node.label = "root";
1728 second_update[root_node.id] = new_root_node;
1729 bridge->UpdateSemantics(/*nodes=*/second_update, /*actions=*/actions);
1730 SemanticsObject* focusObject = accessibility_notifications[0][@"argument"];
1731 // The node 1 was removed, so the bridge will set the focus object to root.
1732 XCTAssertEqual([focusObject uid], 0);
1733 XCTAssertEqualObjects([focusObject accessibilityLabel], @"root");
1734 XCTAssertEqual([accessibility_notifications[0][@"notification"] unsignedIntValue],
1735 UIAccessibilityLayoutChangedNotification);
1736 }
1737
1738 - (void)testAnnouncesLayoutChangeWithTheSameItemFocused {
1739 flutter::MockDelegate mock_delegate;
1740 auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
1741 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
1742 /*platform=*/thread_task_runner,
1743 /*raster=*/thread_task_runner,
1744 /*ui=*/thread_task_runner,
1745 /*io=*/thread_task_runner);
1746 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1747 /*delegate=*/mock_delegate,
1748 /*rendering_api=*/mock_delegate.settings_.enable_impeller
1749 ? flutter::IOSRenderingAPI::kMetal
1750 : flutter::IOSRenderingAPI::kSoftware,
1751 /*platform_views_controller=*/nil,
1752 /*task_runners=*/runners,
1753 /*worker_task_runner=*/nil,
1754 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
1755 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
1756 id mockFlutterView = OCMClassMock([FlutterView class]);
1757 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1758
1759 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1760 [[NSMutableArray alloc] init];
1761 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1762 ios_delegate->on_PostAccessibilityNotification_ =
1763 [accessibility_notifications](UIAccessibilityNotifications notification, id argument) {
1764 [accessibility_notifications addObject:@{
1765 @"notification" : @(notification),
1766 @"argument" : argument ? argument : [NSNull null],
1767 }];
1768 };
1769 __block auto bridge =
1770 std::make_unique<flutter::AccessibilityBridge>(/*view_controller=*/mockFlutterViewController,
1771 /*platform_view=*/platform_view.get(),
1772 /*platform_views_controller=*/nil,
1773 /*ios_delegate=*/std::move(ios_delegate));
1774
1775 flutter::CustomAccessibilityActionUpdates actions;
1776 flutter::SemanticsNodeUpdates first_update;
1777
1778 flutter::SemanticsNode node_one;
1779 node_one.id = 1;
1780 node_one.label = "route1";
1781 first_update[node_one.id] = node_one;
1782 flutter::SemanticsNode node_two;
1783 node_two.id = 2;
1784 node_two.label = "route2";
1785 first_update[node_two.id] = node_two;
1786 flutter::SemanticsNode root_node;
1787 root_node.id = kRootNodeId;
1788 root_node.label = "root";
1789 root_node.childrenInTraversalOrder = {1, 2};
1790 root_node.childrenInHitTestOrder = {1, 2};
1791 first_update[root_node.id] = root_node;
1792 bridge->UpdateSemantics(/*nodes=*/first_update, /*actions=*/actions);
1793
1794 XCTAssertEqual([accessibility_notifications count], 0ul);
1795 // Simulates the focusing on the node 1.
1796 bridge->AccessibilityObjectDidBecomeFocused(1);
1797
1798 flutter::SemanticsNodeUpdates second_update;
1799 // Simulates the removal of the node 2.
1800 flutter::SemanticsNode new_root_node;
1801 new_root_node.id = kRootNodeId;
1802 new_root_node.label = "root";
1803 new_root_node.childrenInTraversalOrder = {1};
1804 new_root_node.childrenInHitTestOrder = {1};
1805 second_update[root_node.id] = new_root_node;
1806 bridge->UpdateSemantics(/*nodes=*/second_update, /*actions=*/actions);
1807 id focusObject = accessibility_notifications[0][@"argument"];
1808 // Since we have focused on the node 1 right before the layout changed, the bridge should not ask
1809 // to refocus again on the same node.
1810 XCTAssertEqualObjects(focusObject, [NSNull null]);
1811 XCTAssertEqual([accessibility_notifications[0][@"notification"] unsignedIntValue],
1812 UIAccessibilityLayoutChangedNotification);
1813 }
1814
1815 - (void)testAnnouncesLayoutChangeWhenFocusMovedOutside {
1816 flutter::MockDelegate mock_delegate;
1817 auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
1818 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
1819 /*platform=*/thread_task_runner,
1820 /*raster=*/thread_task_runner,
1821 /*ui=*/thread_task_runner,
1822 /*io=*/thread_task_runner);
1823 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1824 /*delegate=*/mock_delegate,
1825 /*rendering_api=*/mock_delegate.settings_.enable_impeller
1826 ? flutter::IOSRenderingAPI::kMetal
1827 : flutter::IOSRenderingAPI::kSoftware,
1828 /*platform_views_controller=*/nil,
1829 /*task_runners=*/runners,
1830 /*worker_task_runner=*/nil,
1831 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
1832 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
1833 id mockFlutterView = OCMClassMock([FlutterView class]);
1834 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1835
1836 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1837 [[NSMutableArray alloc] init];
1838 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1839 ios_delegate->on_PostAccessibilityNotification_ =
1840 [accessibility_notifications](UIAccessibilityNotifications notification, id argument) {
1841 [accessibility_notifications addObject:@{
1842 @"notification" : @(notification),
1843 @"argument" : argument ? argument : [NSNull null],
1844 }];
1845 };
1846 __block auto bridge =
1847 std::make_unique<flutter::AccessibilityBridge>(/*view_controller=*/mockFlutterViewController,
1848 /*platform_view=*/platform_view.get(),
1849 /*platform_views_controller=*/nil,
1850 /*ios_delegate=*/std::move(ios_delegate));
1851
1852 flutter::CustomAccessibilityActionUpdates actions;
1853 flutter::SemanticsNodeUpdates first_update;
1854
1855 flutter::SemanticsNode node_one;
1856 node_one.id = 1;
1857 node_one.label = "route1";
1858 first_update[node_one.id] = node_one;
1859 flutter::SemanticsNode node_two;
1860 node_two.id = 2;
1861 node_two.label = "route2";
1862 first_update[node_two.id] = node_two;
1863 flutter::SemanticsNode root_node;
1864 root_node.id = kRootNodeId;
1865 root_node.label = "root";
1866 root_node.childrenInTraversalOrder = {1, 2};
1867 root_node.childrenInHitTestOrder = {1, 2};
1868 first_update[root_node.id] = root_node;
1869 bridge->UpdateSemantics(/*nodes=*/first_update, /*actions=*/actions);
1870
1871 XCTAssertEqual([accessibility_notifications count], 0ul);
1872 // Simulates the focusing on the node 1.
1873 bridge->AccessibilityObjectDidBecomeFocused(1);
1874 // Simulates that the focus move outside of flutter.
1875 bridge->AccessibilityObjectDidLoseFocus(1);
1876
1877 flutter::SemanticsNodeUpdates second_update;
1878 // Simulates the removal of the node 2.
1879 flutter::SemanticsNode new_root_node;
1880 new_root_node.id = kRootNodeId;
1881 new_root_node.label = "root";
1882 new_root_node.childrenInTraversalOrder = {1};
1883 new_root_node.childrenInHitTestOrder = {1};
1884 second_update[root_node.id] = new_root_node;
1885 bridge->UpdateSemantics(/*nodes=*/second_update, /*actions=*/actions);
1886 NSNull* focusObject = accessibility_notifications[0][@"argument"];
1887 // Since the focus is moved outside of the app right before the layout
1888 // changed, the bridge should not try to refocus anything .
1889 XCTAssertEqual(focusObject, [NSNull null]);
1890 XCTAssertEqual([accessibility_notifications[0][@"notification"] unsignedIntValue],
1891 UIAccessibilityLayoutChangedNotification);
1892 }
1893
1894 - (void)testAnnouncesScrollChangeWithLastFocused {
1895 flutter::MockDelegate mock_delegate;
1896 auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
1897 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
1898 /*platform=*/thread_task_runner,
1899 /*raster=*/thread_task_runner,
1900 /*ui=*/thread_task_runner,
1901 /*io=*/thread_task_runner);
1902 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1903 /*delegate=*/mock_delegate,
1904 /*rendering_api=*/mock_delegate.settings_.enable_impeller
1905 ? flutter::IOSRenderingAPI::kMetal
1906 : flutter::IOSRenderingAPI::kSoftware,
1907 /*platform_views_controller=*/nil,
1908 /*task_runners=*/runners,
1909 /*worker_task_runner=*/nil,
1910 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
1911 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
1912 id mockFlutterView = OCMClassMock([FlutterView class]);
1913 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1914
1915 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1916 [[NSMutableArray alloc] init];
1917 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1918 ios_delegate->on_PostAccessibilityNotification_ =
1919 [accessibility_notifications](UIAccessibilityNotifications notification, id argument) {
1920 [accessibility_notifications addObject:@{
1921 @"notification" : @(notification),
1922 @"argument" : argument ? argument : [NSNull null],
1923 }];
1924 };
1925 __block auto bridge =
1926 std::make_unique<flutter::AccessibilityBridge>(/*view_controller=*/mockFlutterViewController,
1927 /*platform_view=*/platform_view.get(),
1928 /*platform_views_controller=*/nil,
1929 /*ios_delegate=*/std::move(ios_delegate));
1930
1931 flutter::CustomAccessibilityActionUpdates actions;
1932 flutter::SemanticsNodeUpdates first_update;
1933
1934 flutter::SemanticsNode node_one;
1935 node_one.id = 1;
1936 node_one.label = "route1";
1937 node_one.scrollPosition = 0.0;
1938 first_update[node_one.id] = node_one;
1939 flutter::SemanticsNode root_node;
1940 root_node.id = kRootNodeId;
1941 root_node.label = "root";
1942 root_node.childrenInTraversalOrder = {1};
1943 root_node.childrenInHitTestOrder = {1};
1944 first_update[root_node.id] = root_node;
1945 bridge->UpdateSemantics(/*nodes=*/first_update, /*actions=*/actions);
1946
1947 // The first update will trigger a scroll announcement, but we are not interested in it.
1948 [accessibility_notifications removeAllObjects];
1949
1950 // Simulates the focusing on the node 1.
1951 bridge->AccessibilityObjectDidBecomeFocused(1);
1952
1953 flutter::SemanticsNodeUpdates second_update;
1954 // Simulates the scrolling on the node 1.
1955 flutter::SemanticsNode new_node_one;
1956 new_node_one.id = 1;
1957 new_node_one.label = "route1";
1958 new_node_one.scrollPosition = 1.0;
1959 second_update[new_node_one.id] = new_node_one;
1960 bridge->UpdateSemantics(/*nodes=*/second_update, /*actions=*/actions);
1961 SemanticsObject* focusObject = accessibility_notifications[0][@"argument"];
1962 // Since we have focused on the node 1 right before the scrolling, the bridge should refocus the
1963 // node 1.
1964 XCTAssertEqual([focusObject uid], 1);
1965 XCTAssertEqual([accessibility_notifications[0][@"notification"] unsignedIntValue],
1966 UIAccessibilityPageScrolledNotification);
1967 }
1968
1969 - (void)testAnnouncesScrollChangeDoesCallNativeAccessibility {
1970 flutter::MockDelegate mock_delegate;
1971 auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
1972 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
1973 /*platform=*/thread_task_runner,
1974 /*raster=*/thread_task_runner,
1975 /*ui=*/thread_task_runner,
1976 /*io=*/thread_task_runner);
1977 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1978 /*delegate=*/mock_delegate,
1979 /*rendering_api=*/mock_delegate.settings_.enable_impeller
1980 ? flutter::IOSRenderingAPI::kMetal
1981 : flutter::IOSRenderingAPI::kSoftware,
1982 /*platform_views_controller=*/nil,
1983 /*task_runners=*/runners,
1984 /*worker_task_runner=*/nil,
1985 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
1986 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
1987 id mockFlutterView = OCMClassMock([FlutterView class]);
1988 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1989
1990 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1991 [[NSMutableArray alloc] init];
1992 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1993 ios_delegate->on_PostAccessibilityNotification_ =
1994 [accessibility_notifications](UIAccessibilityNotifications notification, id argument) {
1995 [accessibility_notifications addObject:@{
1996 @"notification" : @(notification),
1997 @"argument" : argument ? argument : [NSNull null],
1998 }];
1999 };
2000 __block auto bridge =
2001 std::make_unique<flutter::AccessibilityBridge>(/*view_controller=*/mockFlutterViewController,
2002 /*platform_view=*/platform_view.get(),
2003 /*platform_views_controller=*/nil,
2004 /*ios_delegate=*/std::move(ios_delegate));
2005
2006 flutter::CustomAccessibilityActionUpdates actions;
2007 flutter::SemanticsNodeUpdates first_update;
2008
2009 flutter::SemanticsNode node_one;
2010 node_one.id = 1;
2011 node_one.label = "route1";
2012 node_one.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasImplicitScrolling);
2013 node_one.scrollPosition = 0.0;
2014 first_update[node_one.id] = node_one;
2015 flutter::SemanticsNode root_node;
2016 root_node.id = kRootNodeId;
2017 root_node.label = "root";
2018 root_node.childrenInTraversalOrder = {1};
2019 root_node.childrenInHitTestOrder = {1};
2020 first_update[root_node.id] = root_node;
2021 bridge->UpdateSemantics(/*nodes=*/first_update, /*actions=*/actions);
2022
2023 // The first update will trigger a scroll announcement, but we are not interested in it.
2024 [accessibility_notifications removeAllObjects];
2025
2026 // Simulates the focusing on the node 1.
2027 bridge->AccessibilityObjectDidBecomeFocused(1);
2028
2029 flutter::SemanticsNodeUpdates second_update;
2030 // Simulates the scrolling on the node 1.
2031 flutter::SemanticsNode new_node_one;
2032 new_node_one.id = 1;
2033 new_node_one.label = "route1";
2034 new_node_one.flags = static_cast<int32_t>(flutter::SemanticsFlags::kHasImplicitScrolling);
2035 new_node_one.scrollPosition = 1.0;
2036 second_update[new_node_one.id] = new_node_one;
2037 bridge->UpdateSemantics(/*nodes=*/second_update, /*actions=*/actions);
2038 SemanticsObject* focusObject = accessibility_notifications[0][@"argument"];
2039 // Make sure refocus event is sent with the nativeAccessibility of node_one
2040 // which is a FlutterSemanticsScrollView.
2041 XCTAssertTrue([focusObject isKindOfClass:[FlutterSemanticsScrollView class]]);
2042 XCTAssertEqual([accessibility_notifications[0][@"notification"] unsignedIntValue],
2043 UIAccessibilityPageScrolledNotification);
2044 }
2045
2046 - (void)testAnnouncesIgnoresRouteChangesWhenModal {
2047 flutter::MockDelegate mock_delegate;
2048 auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
2049 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
2050 /*platform=*/thread_task_runner,
2051 /*raster=*/thread_task_runner,
2052 /*ui=*/thread_task_runner,
2053 /*io=*/thread_task_runner);
2054 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2055 /*delegate=*/mock_delegate,
2056 /*rendering_api=*/mock_delegate.settings_.enable_impeller
2057 ? flutter::IOSRenderingAPI::kMetal
2058 : flutter::IOSRenderingAPI::kSoftware,
2059 /*platform_views_controller=*/nil,
2060 /*task_runners=*/runners,
2061 /*worker_task_runner=*/nil,
2062 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
2063 id mockFlutterView = OCMClassMock([FlutterView class]);
2064 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
2065 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
2066 std::string label = "some label";
2067
2068 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
2069 [[NSMutableArray alloc] init];
2070 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
2071 ios_delegate->on_PostAccessibilityNotification_ =
2072 [accessibility_notifications](UIAccessibilityNotifications notification, id argument) {
2073 [accessibility_notifications addObject:@{
2074 @"notification" : @(notification),
2075 @"argument" : argument ? argument : [NSNull null],
2076 }];
2077 };
2078 ios_delegate->result_IsFlutterViewControllerPresentingModalViewController_ = true;
2079 __block auto bridge =
2080 std::make_unique<flutter::AccessibilityBridge>(/*view_controller=*/mockFlutterViewController,
2081 /*platform_view=*/platform_view.get(),
2082 /*platform_views_controller=*/nil,
2083 /*ios_delegate=*/std::move(ios_delegate));
2084
2085 flutter::CustomAccessibilityActionUpdates actions;
2086 flutter::SemanticsNodeUpdates nodes;
2087
2088 flutter::SemanticsNode route_node;
2089 route_node.id = 1;
2090 route_node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kScopesRoute) |
2091 static_cast<int32_t>(flutter::SemanticsFlags::kNamesRoute);
2092 route_node.label = "route";
2093 nodes[route_node.id] = route_node;
2094 flutter::SemanticsNode root_node;
2095 root_node.id = kRootNodeId;
2096 root_node.label = label;
2097 root_node.childrenInTraversalOrder = {1};
2098 root_node.childrenInHitTestOrder = {1};
2099 nodes[root_node.id] = root_node;
2100 bridge->UpdateSemantics(/*nodes=*/nodes, /*actions=*/actions);
2101
2102 XCTAssertEqual([accessibility_notifications count], 0ul);
2103 }
2104
2105 - (void)testAnnouncesIgnoresLayoutChangeWhenModal {
2106 flutter::MockDelegate mock_delegate;
2107 auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
2108 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
2109 /*platform=*/thread_task_runner,
2110 /*raster=*/thread_task_runner,
2111 /*ui=*/thread_task_runner,
2112 /*io=*/thread_task_runner);
2113 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2114 /*delegate=*/mock_delegate,
2115 /*rendering_api=*/mock_delegate.settings_.enable_impeller
2116 ? flutter::IOSRenderingAPI::kMetal
2117 : flutter::IOSRenderingAPI::kSoftware,
2118 /*platform_views_controller=*/nil,
2119 /*task_runners=*/runners,
2120 /*worker_task_runner=*/nil,
2121 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
2122 id mockFlutterView = OCMClassMock([FlutterView class]);
2123 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
2124 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
2125
2126 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
2127 [[NSMutableArray alloc] init];
2128 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
2129 ios_delegate->on_PostAccessibilityNotification_ =
2130 [accessibility_notifications](UIAccessibilityNotifications notification, id argument) {
2131 [accessibility_notifications addObject:@{
2132 @"notification" : @(notification),
2133 @"argument" : argument ? argument : [NSNull null],
2134 }];
2135 };
2136 ios_delegate->result_IsFlutterViewControllerPresentingModalViewController_ = true;
2137 __block auto bridge =
2138 std::make_unique<flutter::AccessibilityBridge>(/*view_controller=*/mockFlutterViewController,
2139 /*platform_view=*/platform_view.get(),
2140 /*platform_views_controller=*/nil,
2141 /*ios_delegate=*/std::move(ios_delegate));
2142
2143 flutter::CustomAccessibilityActionUpdates actions;
2144 flutter::SemanticsNodeUpdates nodes;
2145
2146 flutter::SemanticsNode child_node;
2147 child_node.id = 1;
2148 child_node.label = "child_node";
2149 nodes[child_node.id] = child_node;
2150 flutter::SemanticsNode root_node;
2151 root_node.id = kRootNodeId;
2152 root_node.label = "root";
2153 root_node.childrenInTraversalOrder = {1};
2154 root_node.childrenInHitTestOrder = {1};
2155 nodes[root_node.id] = root_node;
2156 bridge->UpdateSemantics(/*nodes=*/nodes, /*actions=*/actions);
2157
2158 // Removes child_node to simulate a layout change.
2159 flutter::SemanticsNodeUpdates new_nodes;
2160 flutter::SemanticsNode new_root_node;
2161 new_root_node.id = kRootNodeId;
2162 new_root_node.label = "root";
2163 new_nodes[new_root_node.id] = new_root_node;
2164 bridge->UpdateSemantics(/*nodes=*/new_nodes, /*actions=*/actions);
2165
2166 XCTAssertEqual([accessibility_notifications count], 0ul);
2167 }
2168
2169 - (void)testAnnouncesIgnoresScrollChangeWhenModal {
2170 flutter::MockDelegate mock_delegate;
2171 auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
2172 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
2173 /*platform=*/thread_task_runner,
2174 /*raster=*/thread_task_runner,
2175 /*ui=*/thread_task_runner,
2176 /*io=*/thread_task_runner);
2177 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2178 /*delegate=*/mock_delegate,
2179 /*rendering_api=*/mock_delegate.settings_.enable_impeller
2180 ? flutter::IOSRenderingAPI::kMetal
2181 : flutter::IOSRenderingAPI::kSoftware,
2182 /*platform_views_controller=*/nil,
2183 /*task_runners=*/runners,
2184 /*worker_task_runner=*/nil,
2185 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
2186 id mockFlutterView = OCMClassMock([FlutterView class]);
2187 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
2188 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
2189
2190 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
2191 [[NSMutableArray alloc] init];
2192 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
2193 ios_delegate->on_PostAccessibilityNotification_ =
2194 [accessibility_notifications](UIAccessibilityNotifications notification, id argument) {
2195 [accessibility_notifications addObject:@{
2196 @"notification" : @(notification),
2197 @"argument" : argument ? argument : [NSNull null],
2198 }];
2199 };
2200 ios_delegate->result_IsFlutterViewControllerPresentingModalViewController_ = true;
2201 __block auto bridge =
2202 std::make_unique<flutter::AccessibilityBridge>(/*view_controller=*/mockFlutterViewController,
2203 /*platform_view=*/platform_view.get(),
2204 /*platform_views_controller=*/nil,
2205 /*ios_delegate=*/std::move(ios_delegate));
2206
2207 flutter::CustomAccessibilityActionUpdates actions;
2208 flutter::SemanticsNodeUpdates nodes;
2209
2210 flutter::SemanticsNode root_node;
2211 root_node.id = kRootNodeId;
2212 root_node.label = "root";
2213 root_node.scrollPosition = 1;
2214 nodes[root_node.id] = root_node;
2215 bridge->UpdateSemantics(/*nodes=*/nodes, /*actions=*/actions);
2216
2217 // Removes child_node to simulate a layout change.
2218 flutter::SemanticsNodeUpdates new_nodes;
2219 flutter::SemanticsNode new_root_node;
2220 new_root_node.id = kRootNodeId;
2221 new_root_node.label = "root";
2222 new_root_node.scrollPosition = 2;
2223 new_nodes[new_root_node.id] = new_root_node;
2224 bridge->UpdateSemantics(/*nodes=*/new_nodes, /*actions=*/actions);
2225
2226 XCTAssertEqual([accessibility_notifications count], 0ul);
2227 }
2228
2229 - (void)testAccessibilityMessageAfterDeletion {
2230 flutter::MockDelegate mock_delegate;
2231 auto thread = std::make_unique<fml::Thread>("AccessibilityBridgeTest");
2232 auto thread_task_runner = thread->GetTaskRunner();
2233 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
2234 /*platform=*/thread_task_runner,
2235 /*raster=*/thread_task_runner,
2236 /*ui=*/thread_task_runner,
2237 /*io=*/thread_task_runner);
2238 id messenger = OCMProtocolMock(@protocol(FlutterBinaryMessenger));
2239 id engine = OCMClassMock([FlutterEngine class]);
2240 id flutterViewController = OCMClassMock([FlutterViewController class]);
2241
2242 OCMStub([flutterViewController engine]).andReturn(engine);
2243 OCMStub([engine binaryMessenger]).andReturn(messenger);
2244 FlutterBinaryMessengerConnection connection = 123;
2245 OCMStub([messenger setMessageHandlerOnChannel:@"flutter/accessibility"
2246 binaryMessageHandler:[OCMArg any]])
2247 .andReturn(connection);
2248
2249 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2250 /*delegate=*/mock_delegate,
2251 /*rendering_api=*/mock_delegate.settings_.enable_impeller
2252 ? flutter::IOSRenderingAPI::kMetal
2253 : flutter::IOSRenderingAPI::kSoftware,
2254 /*platform_views_controller=*/nil,
2255 /*task_runners=*/runners,
2256 /*worker_task_runner=*/nil,
2257 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
2258 fml::AutoResetWaitableEvent latch;
2259 thread_task_runner->PostTask([&] {
2260 platform_view->SetOwnerViewController(flutterViewController);
2261 auto bridge =
2262 std::make_unique<flutter::AccessibilityBridge>(/*view=*/nil,
2263 /*platform_view=*/platform_view.get(),
2264 /*platform_views_controller=*/nil);
2265 XCTAssertTrue(bridge.get());
2266 OCMVerify([messenger setMessageHandlerOnChannel:@"flutter/accessibility"
2267 binaryMessageHandler:[OCMArg isNotNil]]);
2268 bridge.reset();
2269 latch.Signal();
2270 });
2271 latch.Wait();
2272 OCMVerify([messenger cleanUpConnection:connection]);
2273 [engine stopMocking];
2274 }
2275
2276 - (void)testFlutterSemanticsScrollViewManagedObjectLifecycleCorrectly {
2277 flutter::MockDelegate mock_delegate;
2278 auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
2279 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
2280 /*platform=*/thread_task_runner,
2281 /*raster=*/thread_task_runner,
2282 /*ui=*/thread_task_runner,
2283 /*io=*/thread_task_runner);
2284 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2285 /*delegate=*/mock_delegate,
2286 /*rendering_api=*/mock_delegate.settings_.enable_impeller
2287 ? flutter::IOSRenderingAPI::kMetal
2288 : flutter::IOSRenderingAPI::kSoftware,
2289 /*platform_views_controller=*/nil,
2290 /*task_runners=*/runners,
2291 /*worker_task_runner=*/nil,
2292 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
2293 id mockFlutterView = OCMClassMock([FlutterView class]);
2294 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
2295 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
2296
2297 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
2298 __block auto bridge =
2299 std::make_unique<flutter::AccessibilityBridge>(/*view_controller=*/mockFlutterViewController,
2300 /*platform_view=*/platform_view.get(),
2301 /*platform_views_controller=*/nil,
2302 /*ios_delegate=*/std::move(ios_delegate));
2303
2304 FlutterSemanticsScrollView* flutterSemanticsScrollView;
2305 @autoreleasepool {
2306 FlutterScrollableSemanticsObject* semanticsObject =
2307 [[FlutterScrollableSemanticsObject alloc] initWithBridge:bridge->GetWeakPtr() uid:1234];
2308
2309 flutterSemanticsScrollView = semanticsObject.nativeAccessibility;
2310 }
2311 XCTAssertTrue(flutterSemanticsScrollView);
2312 // If the _semanticsObject is not a weak pointer this (or any other method on
2313 // flutterSemanticsScrollView) will cause an EXC_BAD_ACCESS.
2314 XCTAssertFalse([flutterSemanticsScrollView isAccessibilityElement]);
2315 }
2316
2317 - (void)testPlatformViewDestructorDoesNotCallSemanticsAPIs {
2318 class TestDelegate : public flutter::MockDelegate {
2319 public:
2320 void OnPlatformViewSetSemanticsEnabled(bool enabled) override { set_semantics_enabled_calls++; }
2321 int set_semantics_enabled_calls = 0;
2322 };
2323
2324 TestDelegate test_delegate;
2325 auto thread = std::make_unique<fml::Thread>("AccessibilityBridgeTest");
2326 auto thread_task_runner = thread->GetTaskRunner();
2327 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
2328 /*platform=*/thread_task_runner,
2329 /*raster=*/thread_task_runner,
2330 /*ui=*/thread_task_runner,
2331 /*io=*/thread_task_runner);
2332
2333 fml::AutoResetWaitableEvent latch;
2334 thread_task_runner->PostTask([&] {
2335 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2336 /*delegate=*/test_delegate,
2337 /*rendering_api=*/test_delegate.settings_.enable_impeller
2338 ? flutter::IOSRenderingAPI::kMetal
2339 : flutter::IOSRenderingAPI::kSoftware,
2340 /*platform_views_controller=*/nil,
2341 /*task_runners=*/runners,
2342 /*worker_task_runner=*/nil,
2343 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
2344
2345 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
2346 FlutterPlatformViewsController* flutterPlatformViewsController =
2347 [[FlutterPlatformViewsController alloc] init];
2348 flutterPlatformViewsController.taskRunner = thread_task_runner;
2349
2350 OCMStub([mockFlutterViewController platformViewsController])
2351 .andReturn(flutterPlatformViewsController);
2352 platform_view->SetOwnerViewController(mockFlutterViewController);
2353
2354 platform_view->SetSemanticsEnabled(true);
2355 XCTAssertNotEqual(test_delegate.set_semantics_enabled_calls, 0);
2356
2357 // Deleting PlatformViewIOS should not call OnPlatformViewSetSemanticsEnabled
2358 test_delegate.set_semantics_enabled_calls = 0;
2359 platform_view.reset();
2360 XCTAssertEqual(test_delegate.set_semantics_enabled_calls, 0);
2361
2362 latch.Signal();
2363 });
2364 latch.Wait();
2365 }
2366
2367 - (void)testResetsAccessibilityElementsOnHotRestart {
2368 flutter::MockDelegate mock_delegate;
2369 auto thread = std::make_unique<fml::Thread>("AccessibilityBridgeTest");
2370 auto thread_task_runner = thread->GetTaskRunner();
2371 flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
2372 /*platform=*/thread_task_runner,
2373 /*raster=*/thread_task_runner,
2374 /*ui=*/thread_task_runner,
2375 /*io=*/thread_task_runner);
2376 id mockFlutterView = OCMClassMock([FlutterView class]);
2377 id mockFlutterViewController = OCMClassMock([FlutterViewController class]);
2378 OCMStub([mockFlutterViewController viewIfLoaded]).andReturn(mockFlutterView);
2379
2380 fml::AutoResetWaitableEvent latch;
2381 thread_task_runner->PostTask([&] {
2382 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2383 /*delegate=*/mock_delegate,
2384 /*rendering_api=*/mock_delegate.settings_.enable_impeller
2385 ? flutter::IOSRenderingAPI::kMetal
2386 : flutter::IOSRenderingAPI::kSoftware,
2387 /*platform_views_controller=*/nil,
2388 /*task_runners=*/runners,
2389 /*worker_task_runner=*/nil,
2390 /*is_gpu_disabled_sync_switch=*/std::make_shared<fml::SyncSwitch>());
2391
2392 platform_view->SetOwnerViewController(mockFlutterViewController);
2393 platform_view->SetSemanticsEnabled(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 @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:92
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:646
-[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:397
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:270
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