5 #import <OCMock/OCMock.h>
6 #import <XCTest/XCTest.h>
26 - (instancetype)init {
41 @property(nonatomic, strong) UIView* view;
46 - (instancetype)init {
47 if (
self = [super init]) {
59 - (NSObject<FlutterPlatformView>*)createWithFrame:(CGRect)frame
60 viewIdentifier:(int64_t)viewId
61 arguments:(
id _Nullable)args {
69 class MockDelegate :
public PlatformView::Delegate {
71 void OnPlatformViewCreated(std::unique_ptr<Surface> surface)
override {}
72 void OnPlatformViewDestroyed()
override {}
73 void OnPlatformViewScheduleFrame()
override {}
74 void OnPlatformViewSetNextFrameCallback(
const fml::closure& closure)
override {}
75 void OnPlatformViewSetViewportMetrics(int64_t view_id,
const ViewportMetrics& metrics)
override {}
76 const flutter::Settings& OnPlatformViewGetSettings()
const override {
return settings_; }
77 void OnPlatformViewDispatchPlatformMessage(std::unique_ptr<PlatformMessage> message)
override {}
78 void OnPlatformViewDispatchPointerDataPacket(std::unique_ptr<PointerDataPacket> packet)
override {
80 void OnPlatformViewDispatchSemanticsAction(int32_t
id,
81 SemanticsAction action,
82 fml::MallocMapping args)
override {}
83 void OnPlatformViewSetSemanticsEnabled(
bool enabled)
override {}
84 void OnPlatformViewSetAccessibilityFeatures(int32_t flags)
override {}
85 void OnPlatformViewRegisterTexture(std::shared_ptr<Texture> texture)
override {}
86 void OnPlatformViewUnregisterTexture(int64_t
texture_id)
override {}
87 void OnPlatformViewMarkTextureFrameAvailable(int64_t
texture_id)
override {}
89 void LoadDartDeferredLibrary(intptr_t loading_unit_id,
90 std::unique_ptr<const fml::Mapping> snapshot_data,
91 std::unique_ptr<const fml::Mapping> snapshot_instructions)
override {
93 void LoadDartDeferredLibraryError(intptr_t loading_unit_id,
94 const std::string error_message,
95 bool transient)
override {}
96 void UpdateAssetResolverByType(std::unique_ptr<flutter::AssetResolver> updated_asset_resolver,
97 flutter::AssetResolver::AssetResolverType type)
override {}
102 class MockIosDelegate :
public AccessibilityBridge::IosDelegate {
104 bool IsFlutterViewControllerPresentingModalViewController(
106 return result_IsFlutterViewControllerPresentingModalViewController_;
109 void PostAccessibilityNotification(UIAccessibilityNotifications notification,
110 id argument)
override {
111 if (on_PostAccessibilityNotification_) {
112 on_PostAccessibilityNotification_(notification, argument);
115 std::function<void(UIAccessibilityNotifications,
id)> on_PostAccessibilityNotification_;
116 bool result_IsFlutterViewControllerPresentingModalViewController_ =
false;
122 fml::RefPtr<fml::TaskRunner>
CreateNewThread(
const std::string& name) {
123 auto thread = std::make_unique<fml::Thread>(name);
124 auto runner = thread->GetTaskRunner();
135 flutter::MockDelegate mock_delegate;
137 flutter::TaskRunners runners(
self.name.UTF8String,
142 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
144 mock_delegate.settings_.enable_impeller
150 std::make_shared<fml::SyncSwitch>());
152 std::make_unique<flutter::AccessibilityBridge>(nil,
155 XCTAssertTrue(bridge.get());
158 - (void)testUpdateSemanticsEmpty {
159 flutter::MockDelegate mock_delegate;
161 flutter::TaskRunners runners(
self.name.UTF8String,
166 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
168 mock_delegate.settings_.enable_impeller
174 std::make_shared<fml::SyncSwitch>());
175 id mockFlutterView = OCMClassMock([
FlutterView class]);
177 OCMStub([mockFlutterViewController viewIfLoaded]).andReturn(mockFlutterView);
178 OCMExpect([mockFlutterView setAccessibilityElements:[OCMArg isNil]]);
180 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
183 flutter::SemanticsNodeUpdates nodes;
184 flutter::CustomAccessibilityActionUpdates actions;
185 bridge->UpdateSemantics(nodes, actions);
186 OCMVerifyAll(mockFlutterView);
189 - (void)testUpdateSemanticsOneNode {
190 flutter::MockDelegate mock_delegate;
192 flutter::TaskRunners runners(
self.name.UTF8String,
197 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
199 mock_delegate.settings_.enable_impeller
205 std::make_shared<fml::SyncSwitch>());
206 id mockFlutterView = OCMClassMock([
FlutterView class]);
208 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
209 std::string label =
"some label";
211 __block
auto bridge =
212 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
216 OCMExpect([mockFlutterView setAccessibilityElements:[OCMArg checkWithBlock:^BOOL(NSArray* value) {
217 if ([value count] != 1) {
223 object.bridge.get() == bridge.get() &&
224 object.node.label == label;
228 flutter::SemanticsNodeUpdates nodes;
229 flutter::SemanticsNode semantics_node;
231 semantics_node.label = label;
232 nodes[kRootNodeId] = semantics_node;
233 flutter::CustomAccessibilityActionUpdates actions;
234 bridge->UpdateSemantics(nodes, actions);
235 OCMVerifyAll(mockFlutterView);
238 - (void)testIsVoiceOverRunning {
239 flutter::MockDelegate mock_delegate;
241 flutter::TaskRunners runners(
self.name.UTF8String,
246 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
248 mock_delegate.settings_.enable_impeller
254 std::make_shared<fml::SyncSwitch>());
255 id mockFlutterView = OCMClassMock([
FlutterView class]);
257 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
258 OCMStub([mockFlutterViewController isVoiceOverRunning]).andReturn(YES);
260 __block
auto bridge =
261 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
265 XCTAssertTrue(bridge->isVoiceOverRunning());
268 - (void)testSemanticsDeallocated {
270 flutter::MockDelegate mock_delegate;
272 flutter::TaskRunners runners(
self.name.UTF8String,
278 auto flutterPlatformViewsController =
279 std::make_shared<flutter::FlutterPlatformViewsController>();
280 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
282 mock_delegate.settings_.enable_impeller
285 flutterPlatformViewsController,
288 std::make_shared<fml::SyncSwitch>());
289 id mockFlutterView = OCMClassMock([
FlutterView class]);
291 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
292 std::string label =
"some label";
293 flutterPlatformViewsController->SetFlutterView(mockFlutterView);
296 flutterPlatformViewsController->RegisterViewFactory(
297 factory,
@"MockFlutterPlatformView",
301 flutterPlatformViewsController->OnMethodCall(
303 methodCallWithMethodName:
@"create"
304 arguments:@{
@"id" : @2,
@"viewType" :
@"MockFlutterPlatformView"}],
307 auto bridge = std::make_unique<flutter::AccessibilityBridge>(
308 mockFlutterViewController,
310 flutterPlatformViewsController);
312 flutter::SemanticsNodeUpdates nodes;
313 flutter::SemanticsNode semantics_node;
314 semantics_node.id = 2;
315 semantics_node.platformViewId = 2;
316 semantics_node.label = label;
317 nodes[kRootNodeId] = semantics_node;
318 flutter::CustomAccessibilityActionUpdates actions;
319 bridge->UpdateSemantics(nodes, actions);
321 flutterPlatformViewsController->Reset();
326 - (void)testSemanticsDeallocatedWithoutLoadingView {
331 flutter::MockDelegate mock_delegate;
333 flutter::TaskRunners runners(
self.name.UTF8String,
339 auto flutterPlatformViewsController =
340 std::make_shared<flutter::FlutterPlatformViewsController>();
341 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
343 mock_delegate.settings_.enable_impeller
346 flutterPlatformViewsController,
349 std::make_shared<fml::SyncSwitch>());
352 flutterPlatformViewsController->RegisterViewFactory(
353 factory,
@"MockFlutterPlatformView",
357 flutterPlatformViewsController->OnMethodCall(
359 methodCallWithMethodName:
@"create"
360 arguments:@{
@"id" : @2,
@"viewType" :
@"MockFlutterPlatformView"}],
363 auto bridge = std::make_unique<flutter::AccessibilityBridge>(
364 flutterViewController,
366 flutterPlatformViewsController);
369 flutterPlatformViewsController->Reset();
373 XCTAssertNil(flutterViewController.viewIfLoaded);
374 [flutterViewController deregisterNotifications];
377 - (void)testReplacedSemanticsDoesNotCleanupChildren {
378 flutter::MockDelegate mock_delegate;
380 flutter::TaskRunners runners(
self.name.UTF8String,
386 auto flutterPlatformViewsController = std::make_shared<flutter::FlutterPlatformViewsController>();
387 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
389 mock_delegate.settings_.enable_impeller
392 flutterPlatformViewsController,
395 std::make_shared<fml::SyncSwitch>());
401 OCMStub([mockFlutterViewController view]).andReturn(flutterView);
402 std::string label =
"some label";
403 auto bridge = std::make_unique<flutter::AccessibilityBridge>(
404 mockFlutterViewController,
406 flutterPlatformViewsController);
408 flutter::SemanticsNodeUpdates nodes;
409 flutter::SemanticsNode parent;
411 parent.rect = SkRect::MakeXYWH(0, 0, 100, 200);
412 parent.label =
"label";
413 parent.value =
"value";
414 parent.hint =
"hint";
416 flutter::SemanticsNode node;
418 node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
419 node.label =
"label";
420 node.value =
"value";
422 node.scrollExtentMax = 100.0;
423 node.scrollPosition = 0.0;
424 parent.childrenInTraversalOrder.push_back(1);
425 parent.childrenInHitTestOrder.push_back(1);
427 flutter::SemanticsNode child;
429 child.rect = SkRect::MakeXYWH(0, 0, 100, 200);
430 child.label =
"label";
431 child.value =
"value";
433 node.childrenInTraversalOrder.push_back(2);
434 node.childrenInHitTestOrder.push_back(2);
439 flutter::CustomAccessibilityActionUpdates actions;
440 bridge->UpdateSemantics(nodes, actions);
443 flutter::SemanticsNodeUpdates new_nodes;
444 flutter::SemanticsNode new_node;
446 new_node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
447 new_node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kHasImplicitScrolling);
448 new_node.actions = flutter::kHorizontalScrollSemanticsActions;
449 new_node.label =
"label";
450 new_node.value =
"value";
451 new_node.hint =
"hint";
452 new_node.scrollExtentMax = 100.0;
453 new_node.scrollPosition = 0.0;
454 new_node.childrenInTraversalOrder.push_back(2);
455 new_node.childrenInHitTestOrder.push_back(2);
457 new_nodes[1] = new_node;
458 bridge->UpdateSemantics(new_nodes, actions);
462 id rootContainer = flutterView.accessibilityElements[0];
463 XCTAssertTrue([rootContainer accessibilityElementCount] ==
465 id scrollableContainer = [rootContainer accessibilityElementAtIndex:1];
466 XCTAssertTrue([scrollableContainer accessibilityElementCount] ==
468 id child = [scrollableContainer accessibilityElementAtIndex:1];
470 XCTAssertNotNil([child accessibilityContainer]);
473 - (void)testScrollableSemanticsDeallocated {
474 flutter::MockDelegate mock_delegate;
476 flutter::TaskRunners runners(
self.name.UTF8String,
482 auto flutterPlatformViewsController = std::make_shared<flutter::FlutterPlatformViewsController>();
483 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
485 mock_delegate.settings_.enable_impeller
488 flutterPlatformViewsController,
491 std::make_shared<fml::SyncSwitch>());
497 OCMStub([mockFlutterViewController view]).andReturn(flutterView);
498 std::string label =
"some label";
500 auto bridge = std::make_unique<flutter::AccessibilityBridge>(
501 mockFlutterViewController,
503 flutterPlatformViewsController);
505 flutter::SemanticsNodeUpdates nodes;
506 flutter::SemanticsNode parent;
508 parent.rect = SkRect::MakeXYWH(0, 0, 100, 200);
509 parent.label =
"label";
510 parent.value =
"value";
511 parent.hint =
"hint";
513 flutter::SemanticsNode node;
515 node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kHasImplicitScrolling);
516 node.actions = flutter::kHorizontalScrollSemanticsActions;
517 node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
518 node.label =
"label";
519 node.value =
"value";
521 node.scrollExtentMax = 100.0;
522 node.scrollPosition = 0.0;
523 parent.childrenInTraversalOrder.push_back(1);
524 parent.childrenInHitTestOrder.push_back(1);
527 flutter::CustomAccessibilityActionUpdates actions;
528 bridge->UpdateSemantics(nodes, actions);
529 XCTAssertTrue([flutterView.subviews count] == 1);
531 XCTAssertTrue([flutterView.subviews[0].accessibilityLabel isEqualToString:
@"label"]);
534 flutter::SemanticsNodeUpdates new_nodes;
535 flutter::SemanticsNode new_parent;
537 new_parent.rect = SkRect::MakeXYWH(0, 0, 100, 200);
538 new_parent.label =
"label";
539 new_parent.value =
"value";
540 new_parent.hint =
"hint";
541 new_nodes[0] = new_parent;
542 bridge->UpdateSemantics(new_nodes, actions);
544 XCTAssertTrue([flutterView.subviews count] == 0);
547 - (void)testBridgeReplacesSemanticsNode {
548 flutter::MockDelegate mock_delegate;
550 flutter::TaskRunners runners(
self.name.UTF8String,
556 auto flutterPlatformViewsController = std::make_shared<flutter::FlutterPlatformViewsController>();
557 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
559 mock_delegate.settings_.enable_impeller
562 flutterPlatformViewsController,
565 std::make_shared<fml::SyncSwitch>());
571 OCMStub([mockFlutterViewController view]).andReturn(flutterView);
572 std::string label =
"some label";
574 auto bridge = std::make_unique<flutter::AccessibilityBridge>(
575 mockFlutterViewController,
577 flutterPlatformViewsController);
579 flutter::SemanticsNodeUpdates nodes;
580 flutter::SemanticsNode parent;
582 parent.rect = SkRect::MakeXYWH(0, 0, 100, 200);
583 parent.label =
"label";
584 parent.value =
"value";
585 parent.hint =
"hint";
587 flutter::SemanticsNode node;
589 node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kHasImplicitScrolling);
590 node.actions = flutter::kHorizontalScrollSemanticsActions;
591 node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
592 node.label =
"label";
593 node.value =
"value";
595 node.scrollExtentMax = 100.0;
596 node.scrollPosition = 0.0;
597 parent.childrenInTraversalOrder.push_back(1);
598 parent.childrenInHitTestOrder.push_back(1);
601 flutter::CustomAccessibilityActionUpdates actions;
602 bridge->UpdateSemantics(nodes, actions);
603 XCTAssertTrue([flutterView.subviews count] == 1);
605 XCTAssertTrue([flutterView.subviews[0].accessibilityLabel isEqualToString:
@"label"]);
608 flutter::SemanticsNodeUpdates new_nodes;
609 flutter::SemanticsNode new_node;
611 new_node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
612 new_node.label =
"label";
613 new_node.value =
"value";
614 new_node.hint =
"hint";
615 new_node.scrollExtentMax = 100.0;
616 new_node.scrollPosition = 0.0;
617 new_nodes[1] = new_node;
618 bridge->UpdateSemantics(new_nodes, actions);
620 XCTAssertTrue([flutterView.subviews count] == 0);
623 - (void)testAnnouncesRouteChanges {
624 flutter::MockDelegate mock_delegate;
626 flutter::TaskRunners runners(
self.name.UTF8String,
631 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
633 mock_delegate.settings_.enable_impeller
639 std::make_shared<fml::SyncSwitch>());
640 id mockFlutterView = OCMClassMock([
FlutterView class]);
642 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
644 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
645 [[NSMutableArray alloc] init];
646 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
647 ios_delegate->on_PostAccessibilityNotification_ =
648 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
649 [accessibility_notifications addObject:@{
650 @"notification" : @(notification),
651 @"argument" : argument ? argument : [NSNull null],
654 __block
auto bridge =
655 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
658 std::move(ios_delegate));
660 flutter::CustomAccessibilityActionUpdates actions;
661 flutter::SemanticsNodeUpdates nodes;
663 flutter::SemanticsNode node1;
665 node1.label =
"node1";
666 node1.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute);
667 node1.childrenInTraversalOrder = {2, 3};
668 node1.childrenInHitTestOrder = {2, 3};
669 nodes[node1.id] = node1;
670 flutter::SemanticsNode node2;
672 node2.label =
"node2";
673 nodes[node2.id] = node2;
674 flutter::SemanticsNode node3;
676 node3.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kNamesRoute);
677 node3.label =
"node3";
678 nodes[node3.id] = node3;
679 flutter::SemanticsNode root_node;
681 root_node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute);
682 root_node.childrenInTraversalOrder = {1};
683 root_node.childrenInHitTestOrder = {1};
684 nodes[root_node.id] = root_node;
685 bridge->UpdateSemantics(nodes, actions);
687 XCTAssertEqual([accessibility_notifications count], 1ul);
688 XCTAssertEqualObjects(accessibility_notifications[0][
@"argument"],
@"node3");
689 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
690 UIAccessibilityScreenChangedNotification);
693 - (void)testRadioButtonIsNotSwitchButton {
694 flutter::MockDelegate mock_delegate;
696 flutter::TaskRunners runners(
self.name.UTF8String,
701 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
703 mock_delegate.settings_.enable_impeller
709 std::make_shared<fml::SyncSwitch>());
715 OCMStub([mockFlutterViewController view]).andReturn(flutterView);
716 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
717 __block
auto bridge =
718 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
721 std::move(ios_delegate));
723 flutter::CustomAccessibilityActionUpdates actions;
724 flutter::SemanticsNodeUpdates nodes;
726 flutter::SemanticsNode root_node;
728 root_node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kIsInMutuallyExclusiveGroup) |
729 static_cast<int32_t
>(flutter::SemanticsFlags::kIsEnabled) |
730 static_cast<int32_t
>(flutter::SemanticsFlags::kHasCheckedState) |
731 static_cast<int32_t
>(flutter::SemanticsFlags::kHasEnabledState);
732 nodes[root_node.id] = root_node;
733 bridge->UpdateSemantics(nodes, actions);
738 XCTAssertTrue((rootNode.accessibilityTraits & UIAccessibilityTraitButton) > 0);
739 XCTAssertNil(rootNode.accessibilityValue);
742 - (void)testLayoutChangeWithNonAccessibilityElement {
743 flutter::MockDelegate mock_delegate;
745 flutter::TaskRunners runners(
self.name.UTF8String,
750 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
752 mock_delegate.settings_.enable_impeller
758 std::make_shared<fml::SyncSwitch>());
759 id mockFlutterView = OCMClassMock([
FlutterView class]);
761 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
763 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
764 [[NSMutableArray alloc] init];
765 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
766 ios_delegate->on_PostAccessibilityNotification_ =
767 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
768 [accessibility_notifications addObject:@{
769 @"notification" : @(notification),
770 @"argument" : argument ? argument : [NSNull null],
773 __block
auto bridge =
774 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
777 std::move(ios_delegate));
779 flutter::CustomAccessibilityActionUpdates actions;
780 flutter::SemanticsNodeUpdates nodes;
782 flutter::SemanticsNode node1;
784 node1.label =
"node1";
785 node1.childrenInTraversalOrder = {2, 3};
786 node1.childrenInHitTestOrder = {2, 3};
787 nodes[node1.id] = node1;
788 flutter::SemanticsNode node2;
790 node2.label =
"node2";
791 nodes[node2.id] = node2;
792 flutter::SemanticsNode node3;
794 node3.label =
"node3";
795 nodes[node3.id] = node3;
796 flutter::SemanticsNode root_node;
798 root_node.label =
"root";
799 root_node.childrenInTraversalOrder = {1};
800 root_node.childrenInHitTestOrder = {1};
801 nodes[root_node.id] = root_node;
802 bridge->UpdateSemantics(nodes, actions);
805 bridge->AccessibilityObjectDidBecomeFocused(1);
810 flutter::CustomAccessibilityActionUpdates new_actions;
811 flutter::SemanticsNodeUpdates new_nodes;
813 flutter::SemanticsNode new_node1;
815 new_node1.childrenInTraversalOrder = {2};
816 new_node1.childrenInHitTestOrder = {2};
817 new_nodes[new_node1.id] = new_node1;
818 bridge->UpdateSemantics(new_nodes, new_actions);
820 XCTAssertEqual([accessibility_notifications count], 1ul);
821 SemanticsObject* focusObject = accessibility_notifications[0][@"argument"];
823 XCTAssertEqual([focusObject uid], 2);
824 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
825 UIAccessibilityLayoutChangedNotification);
828 - (void)testLayoutChangeDoesCallNativeAccessibility {
829 flutter::MockDelegate mock_delegate;
831 flutter::TaskRunners runners(
self.name.UTF8String,
836 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
838 mock_delegate.settings_.enable_impeller
844 std::make_shared<fml::SyncSwitch>());
845 id mockFlutterView = OCMClassMock([
FlutterView class]);
847 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
849 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
850 [[NSMutableArray alloc] init];
851 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
852 ios_delegate->on_PostAccessibilityNotification_ =
853 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
854 [accessibility_notifications addObject:@{
855 @"notification" : @(notification),
856 @"argument" : argument ? argument : [NSNull null],
859 __block
auto bridge =
860 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
863 std::move(ios_delegate));
865 flutter::CustomAccessibilityActionUpdates actions;
866 flutter::SemanticsNodeUpdates nodes;
868 flutter::SemanticsNode node1;
870 node1.label =
"node1";
871 nodes[node1.id] = node1;
872 flutter::SemanticsNode root_node;
874 root_node.label =
"root";
875 root_node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kHasImplicitScrolling);
876 root_node.childrenInTraversalOrder = {1};
877 root_node.childrenInHitTestOrder = {1};
878 nodes[root_node.id] = root_node;
879 bridge->UpdateSemantics(nodes, actions);
882 bridge->AccessibilityObjectDidBecomeFocused(0);
885 flutter::CustomAccessibilityActionUpdates new_actions;
886 flutter::SemanticsNodeUpdates new_nodes;
888 flutter::SemanticsNode new_root_node;
890 new_root_node.label =
"root";
891 new_root_node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kHasImplicitScrolling);
892 new_nodes[new_root_node.id] = new_root_node;
893 bridge->UpdateSemantics(new_nodes, new_actions);
895 XCTAssertEqual([accessibility_notifications count], 1ul);
896 id focusObject = accessibility_notifications[0][@"argument"];
900 XCTAssertEqualObjects(focusObject, [NSNull
null]);
901 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
902 UIAccessibilityLayoutChangedNotification);
905 - (void)testLayoutChangeDoesCallNativeAccessibilityWhenFocusChanged {
906 flutter::MockDelegate mock_delegate;
908 flutter::TaskRunners runners(
self.name.UTF8String,
913 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
915 mock_delegate.settings_.enable_impeller
921 std::make_shared<fml::SyncSwitch>());
922 id mockFlutterView = OCMClassMock([
FlutterView class]);
924 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
926 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
927 [[NSMutableArray alloc] init];
928 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
929 ios_delegate->on_PostAccessibilityNotification_ =
930 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
931 [accessibility_notifications addObject:@{
932 @"notification" : @(notification),
933 @"argument" : argument ? argument : [NSNull null],
936 __block
auto bridge =
937 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
940 std::move(ios_delegate));
942 flutter::CustomAccessibilityActionUpdates actions;
943 flutter::SemanticsNodeUpdates nodes;
945 flutter::SemanticsNode node1;
947 node1.label =
"node1";
948 nodes[node1.id] = node1;
949 flutter::SemanticsNode root_node;
951 root_node.label =
"root";
952 root_node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kHasImplicitScrolling);
953 root_node.childrenInTraversalOrder = {1};
954 root_node.childrenInHitTestOrder = {1};
955 nodes[root_node.id] = root_node;
956 bridge->UpdateSemantics(nodes, actions);
959 bridge->AccessibilityObjectDidBecomeFocused(1);
962 flutter::CustomAccessibilityActionUpdates new_actions;
963 flutter::SemanticsNodeUpdates new_nodes;
965 flutter::SemanticsNode new_root_node;
967 new_root_node.label =
"root";
968 new_root_node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kHasImplicitScrolling);
969 new_nodes[new_root_node.id] = new_root_node;
970 bridge->UpdateSemantics(new_nodes, new_actions);
972 XCTAssertEqual([accessibility_notifications count], 1ul);
973 SemanticsObject* focusObject2 = accessibility_notifications[0][@"argument"];
977 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
978 UIAccessibilityLayoutChangedNotification);
981 - (void)testScrollableSemanticsContainerReturnsCorrectChildren {
982 flutter::MockDelegate mock_delegate;
984 flutter::TaskRunners runners(
self.name.UTF8String,
989 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
991 mock_delegate.settings_.enable_impeller
997 std::make_shared<fml::SyncSwitch>());
998 id mockFlutterView = OCMClassMock([
FlutterView class]);
1000 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1002 OCMExpect([mockFlutterView
1003 setAccessibilityElements:[OCMArg checkWithBlock:^BOOL(NSArray* value) {
1004 if ([value count] != 1) {
1013 return [scrollableContainer indexOfAccessibilityElement:nativeScrollable] == 1;
1015 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1016 __block
auto bridge =
1017 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
1020 std::move(ios_delegate));
1022 flutter::CustomAccessibilityActionUpdates actions;
1023 flutter::SemanticsNodeUpdates nodes;
1025 flutter::SemanticsNode node1;
1027 node1.label =
"node1";
1028 node1.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kHasImplicitScrolling);
1029 nodes[node1.id] = node1;
1030 flutter::SemanticsNode root_node;
1032 root_node.label =
"root";
1033 root_node.childrenInTraversalOrder = {1};
1034 root_node.childrenInHitTestOrder = {1};
1035 nodes[root_node.id] = root_node;
1036 bridge->UpdateSemantics(nodes, actions);
1037 OCMVerifyAll(mockFlutterView);
1040 - (void)testAnnouncesRouteChangesAndLayoutChangeInOneUpdate {
1041 flutter::MockDelegate mock_delegate;
1043 flutter::TaskRunners runners(
self.name.UTF8String,
1047 thread_task_runner);
1048 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1050 mock_delegate.settings_.enable_impeller
1056 std::make_shared<fml::SyncSwitch>());
1057 id mockFlutterView = OCMClassMock([
FlutterView class]);
1059 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1061 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1062 [[NSMutableArray alloc] init];
1063 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1064 ios_delegate->on_PostAccessibilityNotification_ =
1065 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
1066 [accessibility_notifications addObject:@{
1067 @"notification" : @(notification),
1068 @"argument" : argument ? argument : [NSNull null],
1071 __block
auto bridge =
1072 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
1075 std::move(ios_delegate));
1077 flutter::CustomAccessibilityActionUpdates actions;
1078 flutter::SemanticsNodeUpdates nodes;
1080 flutter::SemanticsNode node1;
1082 node1.label =
"node1";
1083 node1.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute) |
1084 static_cast<int32_t
>(flutter::SemanticsFlags::kNamesRoute);
1085 nodes[node1.id] = node1;
1086 flutter::SemanticsNode node3;
1088 node3.label =
"node3";
1089 nodes[node3.id] = node3;
1090 flutter::SemanticsNode root_node;
1092 root_node.label =
"root";
1093 root_node.childrenInTraversalOrder = {1, 3};
1094 root_node.childrenInHitTestOrder = {1, 3};
1095 nodes[root_node.id] = root_node;
1096 bridge->UpdateSemantics(nodes, actions);
1098 XCTAssertEqual([accessibility_notifications count], 1ul);
1099 XCTAssertEqualObjects(accessibility_notifications[0][
@"argument"],
@"node1");
1100 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
1101 UIAccessibilityScreenChangedNotification);
1104 bridge->AccessibilityObjectDidBecomeFocused(0);
1106 flutter::SemanticsNodeUpdates new_nodes;
1108 flutter::SemanticsNode new_node1;
1110 new_node1.label =
"new_node1";
1111 new_node1.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute) |
1112 static_cast<int32_t
>(flutter::SemanticsFlags::kNamesRoute);
1113 new_node1.childrenInTraversalOrder = {2};
1114 new_node1.childrenInHitTestOrder = {2};
1115 new_nodes[new_node1.id] = new_node1;
1116 flutter::SemanticsNode new_node2;
1118 new_node2.label =
"new_node2";
1119 new_node2.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute) |
1120 static_cast<int32_t
>(flutter::SemanticsFlags::kNamesRoute);
1121 new_nodes[new_node2.id] = new_node2;
1122 flutter::SemanticsNode new_root_node;
1124 new_root_node.label =
"root";
1125 new_root_node.childrenInTraversalOrder = {1};
1126 new_root_node.childrenInHitTestOrder = {1};
1127 new_nodes[new_root_node.id] = new_root_node;
1128 bridge->UpdateSemantics(new_nodes, actions);
1129 XCTAssertEqual([accessibility_notifications count], 3ul);
1130 XCTAssertEqualObjects(accessibility_notifications[1][
@"argument"],
@"new_node2");
1131 XCTAssertEqual([accessibility_notifications[1][
@"notification"] unsignedIntValue],
1132 UIAccessibilityScreenChangedNotification);
1133 SemanticsObject* focusObject = accessibility_notifications[2][@"argument"];
1134 XCTAssertEqual([focusObject uid], 0);
1135 XCTAssertEqual([accessibility_notifications[2][
@"notification"] unsignedIntValue],
1136 UIAccessibilityLayoutChangedNotification);
1139 - (void)testAnnouncesRouteChangesWhenAddAdditionalRoute {
1140 flutter::MockDelegate mock_delegate;
1142 flutter::TaskRunners runners(
self.name.UTF8String,
1146 thread_task_runner);
1147 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1149 mock_delegate.settings_.enable_impeller
1155 std::make_shared<fml::SyncSwitch>());
1156 id mockFlutterView = OCMClassMock([
FlutterView class]);
1158 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1160 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1161 [[NSMutableArray alloc] init];
1162 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1163 ios_delegate->on_PostAccessibilityNotification_ =
1164 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
1165 [accessibility_notifications addObject:@{
1166 @"notification" : @(notification),
1167 @"argument" : argument ? argument : [NSNull null],
1170 __block
auto bridge =
1171 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
1174 std::move(ios_delegate));
1176 flutter::CustomAccessibilityActionUpdates actions;
1177 flutter::SemanticsNodeUpdates nodes;
1179 flutter::SemanticsNode node1;
1181 node1.label =
"node1";
1182 node1.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute) |
1183 static_cast<int32_t
>(flutter::SemanticsFlags::kNamesRoute);
1184 nodes[node1.id] = node1;
1185 flutter::SemanticsNode root_node;
1187 root_node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute);
1188 root_node.childrenInTraversalOrder = {1};
1189 root_node.childrenInHitTestOrder = {1};
1190 nodes[root_node.id] = root_node;
1191 bridge->UpdateSemantics(nodes, actions);
1193 XCTAssertEqual([accessibility_notifications count], 1ul);
1194 XCTAssertEqualObjects(accessibility_notifications[0][
@"argument"],
@"node1");
1195 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
1196 UIAccessibilityScreenChangedNotification);
1198 flutter::SemanticsNodeUpdates new_nodes;
1200 flutter::SemanticsNode new_node1;
1202 new_node1.label =
"new_node1";
1203 new_node1.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute) |
1204 static_cast<int32_t
>(flutter::SemanticsFlags::kNamesRoute);
1205 new_node1.childrenInTraversalOrder = {2};
1206 new_node1.childrenInHitTestOrder = {2};
1207 new_nodes[new_node1.id] = new_node1;
1208 flutter::SemanticsNode new_node2;
1210 new_node2.label =
"new_node2";
1211 new_node2.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute) |
1212 static_cast<int32_t
>(flutter::SemanticsFlags::kNamesRoute);
1213 new_nodes[new_node2.id] = new_node2;
1214 flutter::SemanticsNode new_root_node;
1216 new_root_node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute);
1217 new_root_node.childrenInTraversalOrder = {1};
1218 new_root_node.childrenInHitTestOrder = {1};
1219 new_nodes[new_root_node.id] = new_root_node;
1220 bridge->UpdateSemantics(new_nodes, actions);
1221 XCTAssertEqual([accessibility_notifications count], 2ul);
1222 XCTAssertEqualObjects(accessibility_notifications[1][
@"argument"],
@"new_node2");
1223 XCTAssertEqual([accessibility_notifications[1][
@"notification"] unsignedIntValue],
1224 UIAccessibilityScreenChangedNotification);
1227 - (void)testAnnouncesRouteChangesRemoveRouteInMiddle {
1228 flutter::MockDelegate mock_delegate;
1230 flutter::TaskRunners runners(
self.name.UTF8String,
1234 thread_task_runner);
1235 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1237 mock_delegate.settings_.enable_impeller
1243 std::make_shared<fml::SyncSwitch>());
1244 id mockFlutterView = OCMClassMock([
FlutterView class]);
1246 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1248 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1249 [[NSMutableArray alloc] init];
1250 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1251 ios_delegate->on_PostAccessibilityNotification_ =
1252 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
1253 [accessibility_notifications addObject:@{
1254 @"notification" : @(notification),
1255 @"argument" : argument ? argument : [NSNull null],
1258 __block
auto bridge =
1259 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
1262 std::move(ios_delegate));
1264 flutter::CustomAccessibilityActionUpdates actions;
1265 flutter::SemanticsNodeUpdates nodes;
1267 flutter::SemanticsNode node1;
1269 node1.label =
"node1";
1270 node1.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute) |
1271 static_cast<int32_t
>(flutter::SemanticsFlags::kNamesRoute);
1272 node1.childrenInTraversalOrder = {2};
1273 node1.childrenInHitTestOrder = {2};
1274 nodes[node1.id] = node1;
1275 flutter::SemanticsNode node2;
1277 node2.label =
"node2";
1278 node2.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute) |
1279 static_cast<int32_t
>(flutter::SemanticsFlags::kNamesRoute);
1280 nodes[node2.id] = node2;
1281 flutter::SemanticsNode root_node;
1283 root_node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute);
1284 root_node.childrenInTraversalOrder = {1};
1285 root_node.childrenInHitTestOrder = {1};
1286 nodes[root_node.id] = root_node;
1287 bridge->UpdateSemantics(nodes, actions);
1289 XCTAssertEqual([accessibility_notifications count], 1ul);
1290 XCTAssertEqualObjects(accessibility_notifications[0][
@"argument"],
@"node2");
1291 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
1292 UIAccessibilityScreenChangedNotification);
1294 flutter::SemanticsNodeUpdates new_nodes;
1296 flutter::SemanticsNode new_node1;
1298 new_node1.label =
"new_node1";
1299 new_node1.childrenInTraversalOrder = {2};
1300 new_node1.childrenInHitTestOrder = {2};
1301 new_nodes[new_node1.id] = new_node1;
1302 flutter::SemanticsNode new_node2;
1304 new_node2.label =
"new_node2";
1305 new_node2.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute) |
1306 static_cast<int32_t
>(flutter::SemanticsFlags::kNamesRoute);
1307 new_nodes[new_node2.id] = new_node2;
1308 flutter::SemanticsNode new_root_node;
1310 new_root_node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute);
1311 new_root_node.childrenInTraversalOrder = {1};
1312 new_root_node.childrenInHitTestOrder = {1};
1313 new_nodes[new_root_node.id] = new_root_node;
1314 bridge->UpdateSemantics(new_nodes, actions);
1315 XCTAssertEqual([accessibility_notifications count], 2ul);
1316 XCTAssertEqualObjects(accessibility_notifications[1][
@"argument"],
@"new_node2");
1317 XCTAssertEqual([accessibility_notifications[1][
@"notification"] unsignedIntValue],
1318 UIAccessibilityScreenChangedNotification);
1321 - (void)testHandleEvent {
1322 flutter::MockDelegate mock_delegate;
1324 flutter::TaskRunners runners(
self.name.UTF8String,
1328 thread_task_runner);
1329 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1331 mock_delegate.settings_.enable_impeller
1337 std::make_shared<fml::SyncSwitch>());
1338 id mockFlutterView = OCMClassMock([
FlutterView class]);
1340 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1342 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1343 [[NSMutableArray alloc] init];
1344 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1345 ios_delegate->on_PostAccessibilityNotification_ =
1346 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
1347 [accessibility_notifications addObject:@{
1348 @"notification" : @(notification),
1349 @"argument" : argument ? argument : [NSNull null],
1352 __block
auto bridge =
1353 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
1356 std::move(ios_delegate));
1358 NSDictionary<NSString*, id>* annotatedEvent = @{
@"type" :
@"focus",
@"nodeId" : @123};
1360 bridge->HandleEvent(annotatedEvent);
1362 XCTAssertEqual([accessibility_notifications count], 1ul);
1363 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
1364 UIAccessibilityLayoutChangedNotification);
1367 - (void)testAccessibilityObjectDidBecomeFocused {
1368 flutter::MockDelegate mock_delegate;
1369 auto thread = std::make_unique<fml::Thread>(
"AccessibilityBridgeTest");
1370 auto thread_task_runner = thread->GetTaskRunner();
1371 flutter::TaskRunners runners(
self.name.UTF8String,
1375 thread_task_runner);
1380 OCMStub([flutterViewController
engine]).andReturn(
engine);
1381 OCMStub([
engine binaryMessenger]).andReturn(messenger);
1383 OCMStub([messenger setMessageHandlerOnChannel:
@"flutter/accessibility"
1384 binaryMessageHandler:[OCMArg any]])
1385 .andReturn(connection);
1387 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1389 mock_delegate.settings_.enable_impeller
1395 std::make_shared<fml::SyncSwitch>());
1396 fml::AutoResetWaitableEvent latch;
1397 thread_task_runner->PostTask([&] {
1399 std::make_unique<fml::WeakNSObjectFactory<FlutterViewController>>(flutterViewController);
1400 platform_view->SetOwnerViewController(weakFactory->GetWeakNSObject());
1402 std::make_unique<flutter::AccessibilityBridge>(nil,
1405 XCTAssertTrue(bridge.get());
1406 OCMVerify([messenger setMessageHandlerOnChannel:
@"flutter/accessibility"
1407 binaryMessageHandler:[OCMArg isNotNil]]);
1409 bridge->AccessibilityObjectDidBecomeFocused(123);
1411 NSDictionary<NSString*, id>* annotatedEvent = @{
@"type" :
@"didGainFocus",
@"nodeId" : @123};
1414 OCMVerify([messenger sendOnChannel:
@"flutter/accessibility" message:encodedMessage]);
1419 [engine stopMocking];
1422 - (void)testAnnouncesRouteChangesWhenNoNamesRoute {
1423 flutter::MockDelegate mock_delegate;
1425 flutter::TaskRunners runners(
self.name.UTF8String,
1429 thread_task_runner);
1430 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1432 mock_delegate.settings_.enable_impeller
1438 std::make_shared<fml::SyncSwitch>());
1439 id mockFlutterView = OCMClassMock([
FlutterView class]);
1441 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1443 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1444 [[NSMutableArray alloc] init];
1445 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1446 ios_delegate->on_PostAccessibilityNotification_ =
1447 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
1448 [accessibility_notifications addObject:@{
1449 @"notification" : @(notification),
1450 @"argument" : argument ? argument : [NSNull null],
1453 __block
auto bridge =
1454 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
1457 std::move(ios_delegate));
1459 flutter::CustomAccessibilityActionUpdates actions;
1460 flutter::SemanticsNodeUpdates nodes;
1462 flutter::SemanticsNode node1;
1464 node1.label =
"node1";
1465 node1.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute) |
1466 static_cast<int32_t
>(flutter::SemanticsFlags::kNamesRoute);
1467 node1.childrenInTraversalOrder = {2, 3};
1468 node1.childrenInHitTestOrder = {2, 3};
1469 nodes[node1.id] = node1;
1470 flutter::SemanticsNode node2;
1472 node2.label =
"node2";
1473 nodes[node2.id] = node2;
1474 flutter::SemanticsNode node3;
1476 node3.label =
"node3";
1477 nodes[node3.id] = node3;
1478 flutter::SemanticsNode root_node;
1480 root_node.childrenInTraversalOrder = {1};
1481 root_node.childrenInHitTestOrder = {1};
1482 nodes[root_node.id] = root_node;
1483 bridge->UpdateSemantics(nodes, actions);
1486 XCTAssertEqual([accessibility_notifications count], 1ul);
1487 id focusObject = accessibility_notifications[0][@"argument"];
1488 XCTAssertTrue([focusObject isKindOfClass:[NSString
class]]);
1489 XCTAssertEqualObjects(focusObject,
@"node1");
1490 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
1491 UIAccessibilityScreenChangedNotification);
1494 - (void)testAnnouncesLayoutChangeWithNilIfLastFocusIsRemoved {
1495 flutter::MockDelegate mock_delegate;
1497 flutter::TaskRunners runners(
self.name.UTF8String,
1501 thread_task_runner);
1502 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1504 mock_delegate.settings_.enable_impeller
1510 std::make_shared<fml::SyncSwitch>());
1512 id mockFlutterView = OCMClassMock([
FlutterView class]);
1513 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1515 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1516 [[NSMutableArray alloc] init];
1517 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1518 ios_delegate->on_PostAccessibilityNotification_ =
1519 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
1520 [accessibility_notifications addObject:@{
1521 @"notification" : @(notification),
1522 @"argument" : argument ? argument : [NSNull null],
1525 __block
auto bridge =
1526 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
1529 std::move(ios_delegate));
1531 flutter::CustomAccessibilityActionUpdates actions;
1532 flutter::SemanticsNodeUpdates first_update;
1534 flutter::SemanticsNode route_node;
1536 route_node.label =
"route";
1537 first_update[route_node.id] = route_node;
1538 flutter::SemanticsNode root_node;
1540 root_node.label =
"root";
1541 root_node.childrenInTraversalOrder = {1};
1542 root_node.childrenInHitTestOrder = {1};
1543 first_update[root_node.id] = root_node;
1544 bridge->UpdateSemantics(first_update, actions);
1546 XCTAssertEqual([accessibility_notifications count], 0ul);
1548 bridge->AccessibilityObjectDidBecomeFocused(1);
1550 flutter::SemanticsNodeUpdates second_update;
1552 flutter::SemanticsNode new_root_node;
1554 new_root_node.label =
"root";
1555 second_update[root_node.id] = new_root_node;
1556 bridge->UpdateSemantics(second_update, actions);
1557 SemanticsObject* focusObject = accessibility_notifications[0][@"argument"];
1559 XCTAssertEqual([focusObject uid], 0);
1560 XCTAssertEqualObjects([focusObject accessibilityLabel],
@"root");
1561 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
1562 UIAccessibilityLayoutChangedNotification);
1565 - (void)testAnnouncesLayoutChangeWithTheSameItemFocused {
1566 flutter::MockDelegate mock_delegate;
1568 flutter::TaskRunners runners(
self.name.UTF8String,
1572 thread_task_runner);
1573 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1575 mock_delegate.settings_.enable_impeller
1581 std::make_shared<fml::SyncSwitch>());
1583 id mockFlutterView = OCMClassMock([
FlutterView class]);
1584 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1586 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1587 [[NSMutableArray alloc] init];
1588 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1589 ios_delegate->on_PostAccessibilityNotification_ =
1590 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
1591 [accessibility_notifications addObject:@{
1592 @"notification" : @(notification),
1593 @"argument" : argument ? argument : [NSNull null],
1596 __block
auto bridge =
1597 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
1600 std::move(ios_delegate));
1602 flutter::CustomAccessibilityActionUpdates actions;
1603 flutter::SemanticsNodeUpdates first_update;
1605 flutter::SemanticsNode node_one;
1607 node_one.label =
"route1";
1608 first_update[node_one.id] = node_one;
1609 flutter::SemanticsNode node_two;
1611 node_two.label =
"route2";
1612 first_update[node_two.id] = node_two;
1613 flutter::SemanticsNode root_node;
1615 root_node.label =
"root";
1616 root_node.childrenInTraversalOrder = {1, 2};
1617 root_node.childrenInHitTestOrder = {1, 2};
1618 first_update[root_node.id] = root_node;
1619 bridge->UpdateSemantics(first_update, actions);
1621 XCTAssertEqual([accessibility_notifications count], 0ul);
1623 bridge->AccessibilityObjectDidBecomeFocused(1);
1625 flutter::SemanticsNodeUpdates second_update;
1627 flutter::SemanticsNode new_root_node;
1629 new_root_node.label =
"root";
1630 new_root_node.childrenInTraversalOrder = {1};
1631 new_root_node.childrenInHitTestOrder = {1};
1632 second_update[root_node.id] = new_root_node;
1633 bridge->UpdateSemantics(second_update, actions);
1634 id focusObject = accessibility_notifications[0][@"argument"];
1637 XCTAssertEqualObjects(focusObject, [NSNull
null]);
1638 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
1639 UIAccessibilityLayoutChangedNotification);
1642 - (void)testAnnouncesLayoutChangeWhenFocusMovedOutside {
1643 flutter::MockDelegate mock_delegate;
1645 flutter::TaskRunners runners(
self.name.UTF8String,
1649 thread_task_runner);
1650 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1652 mock_delegate.settings_.enable_impeller
1658 std::make_shared<fml::SyncSwitch>());
1660 id mockFlutterView = OCMClassMock([
FlutterView class]);
1661 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1663 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1664 [[NSMutableArray alloc] init];
1665 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1666 ios_delegate->on_PostAccessibilityNotification_ =
1667 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
1668 [accessibility_notifications addObject:@{
1669 @"notification" : @(notification),
1670 @"argument" : argument ? argument : [NSNull null],
1673 __block
auto bridge =
1674 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
1677 std::move(ios_delegate));
1679 flutter::CustomAccessibilityActionUpdates actions;
1680 flutter::SemanticsNodeUpdates first_update;
1682 flutter::SemanticsNode node_one;
1684 node_one.label =
"route1";
1685 first_update[node_one.id] = node_one;
1686 flutter::SemanticsNode node_two;
1688 node_two.label =
"route2";
1689 first_update[node_two.id] = node_two;
1690 flutter::SemanticsNode root_node;
1692 root_node.label =
"root";
1693 root_node.childrenInTraversalOrder = {1, 2};
1694 root_node.childrenInHitTestOrder = {1, 2};
1695 first_update[root_node.id] = root_node;
1696 bridge->UpdateSemantics(first_update, actions);
1698 XCTAssertEqual([accessibility_notifications count], 0ul);
1700 bridge->AccessibilityObjectDidBecomeFocused(1);
1702 bridge->AccessibilityObjectDidLoseFocus(1);
1704 flutter::SemanticsNodeUpdates second_update;
1706 flutter::SemanticsNode new_root_node;
1708 new_root_node.label =
"root";
1709 new_root_node.childrenInTraversalOrder = {1};
1710 new_root_node.childrenInHitTestOrder = {1};
1711 second_update[root_node.id] = new_root_node;
1712 bridge->UpdateSemantics(second_update, actions);
1713 NSNull* focusObject = accessibility_notifications[0][@"argument"];
1716 XCTAssertEqual(focusObject, [NSNull
null]);
1717 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
1718 UIAccessibilityLayoutChangedNotification);
1721 - (void)testAnnouncesScrollChangeWithLastFocused {
1722 flutter::MockDelegate mock_delegate;
1724 flutter::TaskRunners runners(
self.name.UTF8String,
1728 thread_task_runner);
1729 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1731 mock_delegate.settings_.enable_impeller
1737 std::make_shared<fml::SyncSwitch>());
1739 id mockFlutterView = OCMClassMock([
FlutterView class]);
1740 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1742 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1743 [[NSMutableArray alloc] init];
1744 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1745 ios_delegate->on_PostAccessibilityNotification_ =
1746 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
1747 [accessibility_notifications addObject:@{
1748 @"notification" : @(notification),
1749 @"argument" : argument ? argument : [NSNull null],
1752 __block
auto bridge =
1753 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
1756 std::move(ios_delegate));
1758 flutter::CustomAccessibilityActionUpdates actions;
1759 flutter::SemanticsNodeUpdates first_update;
1761 flutter::SemanticsNode node_one;
1763 node_one.label =
"route1";
1764 node_one.scrollPosition = 0.0;
1765 first_update[node_one.id] = node_one;
1766 flutter::SemanticsNode root_node;
1768 root_node.label =
"root";
1769 root_node.childrenInTraversalOrder = {1};
1770 root_node.childrenInHitTestOrder = {1};
1771 first_update[root_node.id] = root_node;
1772 bridge->UpdateSemantics(first_update, actions);
1775 [accessibility_notifications removeAllObjects];
1778 bridge->AccessibilityObjectDidBecomeFocused(1);
1780 flutter::SemanticsNodeUpdates second_update;
1782 flutter::SemanticsNode new_node_one;
1783 new_node_one.id = 1;
1784 new_node_one.label =
"route1";
1785 new_node_one.scrollPosition = 1.0;
1786 second_update[new_node_one.id] = new_node_one;
1787 bridge->UpdateSemantics(second_update, actions);
1788 SemanticsObject* focusObject = accessibility_notifications[0][@"argument"];
1791 XCTAssertEqual([focusObject uid], 1);
1792 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
1793 UIAccessibilityPageScrolledNotification);
1796 - (void)testAnnouncesScrollChangeDoesCallNativeAccessibility {
1797 flutter::MockDelegate mock_delegate;
1799 flutter::TaskRunners runners(
self.name.UTF8String,
1803 thread_task_runner);
1804 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1806 mock_delegate.settings_.enable_impeller
1812 std::make_shared<fml::SyncSwitch>());
1814 id mockFlutterView = OCMClassMock([
FlutterView class]);
1815 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1817 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1818 [[NSMutableArray alloc] init];
1819 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1820 ios_delegate->on_PostAccessibilityNotification_ =
1821 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
1822 [accessibility_notifications addObject:@{
1823 @"notification" : @(notification),
1824 @"argument" : argument ? argument : [NSNull null],
1827 __block
auto bridge =
1828 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
1831 std::move(ios_delegate));
1833 flutter::CustomAccessibilityActionUpdates actions;
1834 flutter::SemanticsNodeUpdates first_update;
1836 flutter::SemanticsNode node_one;
1838 node_one.label =
"route1";
1839 node_one.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kHasImplicitScrolling);
1840 node_one.scrollPosition = 0.0;
1841 first_update[node_one.id] = node_one;
1842 flutter::SemanticsNode root_node;
1844 root_node.label =
"root";
1845 root_node.childrenInTraversalOrder = {1};
1846 root_node.childrenInHitTestOrder = {1};
1847 first_update[root_node.id] = root_node;
1848 bridge->UpdateSemantics(first_update, actions);
1851 [accessibility_notifications removeAllObjects];
1854 bridge->AccessibilityObjectDidBecomeFocused(1);
1856 flutter::SemanticsNodeUpdates second_update;
1858 flutter::SemanticsNode new_node_one;
1859 new_node_one.id = 1;
1860 new_node_one.label =
"route1";
1861 new_node_one.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kHasImplicitScrolling);
1862 new_node_one.scrollPosition = 1.0;
1863 second_update[new_node_one.id] = new_node_one;
1864 bridge->UpdateSemantics(second_update, actions);
1865 SemanticsObject* focusObject = accessibility_notifications[0][@"argument"];
1869 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
1870 UIAccessibilityPageScrolledNotification);
1873 - (void)testAnnouncesIgnoresRouteChangesWhenModal {
1874 flutter::MockDelegate mock_delegate;
1876 flutter::TaskRunners runners(
self.name.UTF8String,
1880 thread_task_runner);
1881 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1883 mock_delegate.settings_.enable_impeller
1889 std::make_shared<fml::SyncSwitch>());
1890 id mockFlutterView = OCMClassMock([
FlutterView class]);
1892 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1893 std::string label =
"some label";
1895 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1896 [[NSMutableArray alloc] init];
1897 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1898 ios_delegate->on_PostAccessibilityNotification_ =
1899 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
1900 [accessibility_notifications addObject:@{
1901 @"notification" : @(notification),
1902 @"argument" : argument ? argument : [NSNull null],
1905 ios_delegate->result_IsFlutterViewControllerPresentingModalViewController_ =
true;
1906 __block
auto bridge =
1907 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
1910 std::move(ios_delegate));
1912 flutter::CustomAccessibilityActionUpdates actions;
1913 flutter::SemanticsNodeUpdates nodes;
1915 flutter::SemanticsNode route_node;
1917 route_node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute) |
1918 static_cast<int32_t
>(flutter::SemanticsFlags::kNamesRoute);
1919 route_node.label =
"route";
1920 nodes[route_node.id] = route_node;
1921 flutter::SemanticsNode root_node;
1923 root_node.label = label;
1924 root_node.childrenInTraversalOrder = {1};
1925 root_node.childrenInHitTestOrder = {1};
1926 nodes[root_node.id] = root_node;
1927 bridge->UpdateSemantics(nodes, actions);
1929 XCTAssertEqual([accessibility_notifications count], 0ul);
1932 - (void)testAnnouncesIgnoresLayoutChangeWhenModal {
1933 flutter::MockDelegate mock_delegate;
1935 flutter::TaskRunners runners(
self.name.UTF8String,
1939 thread_task_runner);
1940 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1942 mock_delegate.settings_.enable_impeller
1948 std::make_shared<fml::SyncSwitch>());
1949 id mockFlutterView = OCMClassMock([
FlutterView class]);
1951 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1953 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1954 [[NSMutableArray alloc] init];
1955 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1956 ios_delegate->on_PostAccessibilityNotification_ =
1957 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
1958 [accessibility_notifications addObject:@{
1959 @"notification" : @(notification),
1960 @"argument" : argument ? argument : [NSNull null],
1963 ios_delegate->result_IsFlutterViewControllerPresentingModalViewController_ =
true;
1964 __block
auto bridge =
1965 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
1968 std::move(ios_delegate));
1970 flutter::CustomAccessibilityActionUpdates actions;
1971 flutter::SemanticsNodeUpdates nodes;
1973 flutter::SemanticsNode child_node;
1975 child_node.label =
"child_node";
1976 nodes[child_node.id] = child_node;
1977 flutter::SemanticsNode root_node;
1979 root_node.label =
"root";
1980 root_node.childrenInTraversalOrder = {1};
1981 root_node.childrenInHitTestOrder = {1};
1982 nodes[root_node.id] = root_node;
1983 bridge->UpdateSemantics(nodes, actions);
1986 flutter::SemanticsNodeUpdates new_nodes;
1987 flutter::SemanticsNode new_root_node;
1989 new_root_node.label =
"root";
1990 new_nodes[new_root_node.id] = new_root_node;
1991 bridge->UpdateSemantics(new_nodes, actions);
1993 XCTAssertEqual([accessibility_notifications count], 0ul);
1996 - (void)testAnnouncesIgnoresScrollChangeWhenModal {
1997 flutter::MockDelegate mock_delegate;
1999 flutter::TaskRunners runners(
self.name.UTF8String,
2003 thread_task_runner);
2004 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2006 mock_delegate.settings_.enable_impeller
2012 std::make_shared<fml::SyncSwitch>());
2013 id mockFlutterView = OCMClassMock([
FlutterView class]);
2015 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
2017 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
2018 [[NSMutableArray alloc] init];
2019 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
2020 ios_delegate->on_PostAccessibilityNotification_ =
2021 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
2022 [accessibility_notifications addObject:@{
2023 @"notification" : @(notification),
2024 @"argument" : argument ? argument : [NSNull null],
2027 ios_delegate->result_IsFlutterViewControllerPresentingModalViewController_ =
true;
2028 __block
auto bridge =
2029 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
2032 std::move(ios_delegate));
2034 flutter::CustomAccessibilityActionUpdates actions;
2035 flutter::SemanticsNodeUpdates nodes;
2037 flutter::SemanticsNode root_node;
2039 root_node.label =
"root";
2040 root_node.scrollPosition = 1;
2041 nodes[root_node.id] = root_node;
2042 bridge->UpdateSemantics(nodes, actions);
2045 flutter::SemanticsNodeUpdates new_nodes;
2046 flutter::SemanticsNode new_root_node;
2048 new_root_node.label =
"root";
2049 new_root_node.scrollPosition = 2;
2050 new_nodes[new_root_node.id] = new_root_node;
2051 bridge->UpdateSemantics(new_nodes, actions);
2053 XCTAssertEqual([accessibility_notifications count], 0ul);
2056 - (void)testAccessibilityMessageAfterDeletion {
2057 flutter::MockDelegate mock_delegate;
2058 auto thread = std::make_unique<fml::Thread>(
"AccessibilityBridgeTest");
2059 auto thread_task_runner = thread->GetTaskRunner();
2060 flutter::TaskRunners runners(
self.name.UTF8String,
2064 thread_task_runner);
2069 OCMStub([flutterViewController
engine]).andReturn(
engine);
2070 OCMStub([
engine binaryMessenger]).andReturn(messenger);
2072 OCMStub([messenger setMessageHandlerOnChannel:
@"flutter/accessibility"
2073 binaryMessageHandler:[OCMArg any]])
2074 .andReturn(connection);
2076 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2078 mock_delegate.settings_.enable_impeller
2084 std::make_shared<fml::SyncSwitch>());
2085 fml::AutoResetWaitableEvent latch;
2086 thread_task_runner->PostTask([&] {
2088 std::make_unique<fml::WeakNSObjectFactory<FlutterViewController>>(flutterViewController);
2089 platform_view->SetOwnerViewController(weakFactory->GetWeakNSObject());
2091 std::make_unique<flutter::AccessibilityBridge>(nil,
2094 XCTAssertTrue(bridge.get());
2095 OCMVerify([messenger setMessageHandlerOnChannel:
@"flutter/accessibility"
2096 binaryMessageHandler:[OCMArg isNotNil]]);
2101 OCMVerify([messenger cleanUpConnection:connection]);
2102 [engine stopMocking];
2105 - (void)testFlutterSemanticsScrollViewManagedObjectLifecycleCorrectly {
2106 flutter::MockDelegate mock_delegate;
2108 flutter::TaskRunners runners(
self.name.UTF8String,
2112 thread_task_runner);
2113 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2115 mock_delegate.settings_.enable_impeller
2121 std::make_shared<fml::SyncSwitch>());
2122 id mockFlutterView = OCMClassMock([
FlutterView class]);
2124 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
2126 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
2127 __block
auto bridge =
2128 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
2131 std::move(ios_delegate));
2140 XCTAssertTrue(flutterSemanticsScrollView);
2143 XCTAssertFalse([flutterSemanticsScrollView isAccessibilityElement]);
2146 - (void)testPlatformViewDestructorDoesNotCallSemanticsAPIs {
2147 class TestDelegate :
public flutter::MockDelegate {
2149 void OnPlatformViewSetSemanticsEnabled(
bool enabled)
override { set_semantics_enabled_calls++; }
2150 int set_semantics_enabled_calls = 0;
2153 TestDelegate test_delegate;
2154 auto thread = std::make_unique<fml::Thread>(
"AccessibilityBridgeTest");
2155 auto thread_task_runner = thread->GetTaskRunner();
2156 flutter::TaskRunners runners(
self.name.UTF8String,
2160 thread_task_runner);
2162 fml::AutoResetWaitableEvent latch;
2163 thread_task_runner->PostTask([&] {
2164 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2166 test_delegate.settings_.enable_impeller
2172 std::make_shared<fml::SyncSwitch>());
2175 auto flutterPlatformViewsController =
2176 std::make_shared<flutter::FlutterPlatformViewsController>();
2177 OCMStub([mockFlutterViewController platformViewsController])
2178 .andReturn(flutterPlatformViewsController.get());
2179 auto weakFactory = std::make_unique<fml::WeakNSObjectFactory<FlutterViewController>>(
2180 mockFlutterViewController);
2181 platform_view->SetOwnerViewController(weakFactory->GetWeakNSObject());
2184 XCTAssertNotEqual(test_delegate.set_semantics_enabled_calls, 0);
2187 test_delegate.set_semantics_enabled_calls = 0;
2189 XCTAssertEqual(test_delegate.set_semantics_enabled_calls, 0);