5 #import <OCMock/OCMock.h>
6 #import <XCTest/XCTest.h>
8 #import "flutter/fml/thread.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 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 OnPlatformViewSetNextFrameCallback(
const fml::closure& closure)
override {}
79 void OnPlatformViewSetViewportMetrics(int64_t view_id,
const ViewportMetrics& metrics)
override {}
80 const flutter::Settings& OnPlatformViewGetSettings()
const override {
return settings_; }
81 void OnPlatformViewDispatchPlatformMessage(std::unique_ptr<PlatformMessage> message)
override {}
82 void OnPlatformViewDispatchPointerDataPacket(std::unique_ptr<PointerDataPacket> packet)
override {
84 void OnPlatformViewDispatchSemanticsAction(int32_t
id,
85 SemanticsAction action,
86 fml::MallocMapping args)
override {}
87 void OnPlatformViewSetSemanticsEnabled(
bool enabled)
override {}
88 void OnPlatformViewSetAccessibilityFeatures(int32_t flags)
override {}
89 void OnPlatformViewRegisterTexture(std::shared_ptr<Texture> texture)
override {}
90 void OnPlatformViewUnregisterTexture(int64_t
texture_id)
override {}
91 void OnPlatformViewMarkTextureFrameAvailable(int64_t
texture_id)
override {}
93 void LoadDartDeferredLibrary(intptr_t loading_unit_id,
94 std::unique_ptr<const fml::Mapping> snapshot_data,
95 std::unique_ptr<const fml::Mapping> snapshot_instructions)
override {
97 void LoadDartDeferredLibraryError(intptr_t loading_unit_id,
98 const std::string error_message,
99 bool transient)
override {}
100 void UpdateAssetResolverByType(std::unique_ptr<flutter::AssetResolver> updated_asset_resolver,
101 flutter::AssetResolver::AssetResolverType type)
override {}
106 class MockIosDelegate :
public AccessibilityBridge::IosDelegate {
108 bool IsFlutterViewControllerPresentingModalViewController(
110 return result_IsFlutterViewControllerPresentingModalViewController_;
113 void PostAccessibilityNotification(UIAccessibilityNotifications notification,
114 id argument)
override {
115 if (on_PostAccessibilityNotification_) {
116 on_PostAccessibilityNotification_(notification, argument);
119 std::function<void(UIAccessibilityNotifications,
id)> on_PostAccessibilityNotification_;
120 bool result_IsFlutterViewControllerPresentingModalViewController_ =
false;
126 fml::RefPtr<fml::TaskRunner>
CreateNewThread(
const std::string& name) {
127 auto thread = std::make_unique<fml::Thread>(name);
128 auto runner = thread->GetTaskRunner();
139 flutter::MockDelegate mock_delegate;
141 flutter::TaskRunners runners(
self.name.UTF8String,
146 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
148 mock_delegate.settings_.enable_impeller
154 std::make_shared<fml::SyncSwitch>());
156 std::make_unique<flutter::AccessibilityBridge>(nil,
159 XCTAssertTrue(bridge.get());
162 - (void)testUpdateSemanticsEmpty {
163 flutter::MockDelegate mock_delegate;
165 flutter::TaskRunners runners(
self.name.UTF8String,
170 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
172 mock_delegate.settings_.enable_impeller
178 std::make_shared<fml::SyncSwitch>());
179 id mockFlutterView = OCMClassMock([
FlutterView class]);
181 OCMStub([mockFlutterViewController viewIfLoaded]).andReturn(mockFlutterView);
182 OCMExpect([mockFlutterView setAccessibilityElements:[OCMArg isNil]]);
184 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
187 flutter::SemanticsNodeUpdates nodes;
188 flutter::CustomAccessibilityActionUpdates actions;
189 bridge->UpdateSemantics(nodes, actions);
190 OCMVerifyAll(mockFlutterView);
193 - (void)testUpdateSemanticsOneNode {
194 flutter::MockDelegate mock_delegate;
196 flutter::TaskRunners runners(
self.name.UTF8String,
201 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
203 mock_delegate.settings_.enable_impeller
209 std::make_shared<fml::SyncSwitch>());
210 id mockFlutterView = OCMClassMock([
FlutterView class]);
212 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
213 std::string label =
"some label";
215 __block
auto bridge =
216 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
220 OCMExpect([mockFlutterView setAccessibilityElements:[OCMArg checkWithBlock:^BOOL(NSArray* value) {
221 if ([value count] != 1) {
227 object.bridge.get() == bridge.get() &&
228 object.node.label == label;
232 flutter::SemanticsNodeUpdates nodes;
233 flutter::SemanticsNode semantics_node;
235 semantics_node.label = label;
236 nodes[kRootNodeId] = semantics_node;
237 flutter::CustomAccessibilityActionUpdates actions;
238 bridge->UpdateSemantics(nodes, actions);
239 OCMVerifyAll(mockFlutterView);
242 - (void)testIsVoiceOverRunning {
243 flutter::MockDelegate mock_delegate;
245 flutter::TaskRunners runners(
self.name.UTF8String,
250 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
252 mock_delegate.settings_.enable_impeller
258 std::make_shared<fml::SyncSwitch>());
259 id mockFlutterView = OCMClassMock([
FlutterView class]);
261 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
262 OCMStub([mockFlutterViewController isVoiceOverRunning]).andReturn(YES);
264 __block
auto bridge =
265 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
269 XCTAssertTrue(bridge->isVoiceOverRunning());
272 - (void)testSemanticsDeallocated {
274 flutter::MockDelegate mock_delegate;
276 flutter::TaskRunners runners(
self.name.UTF8String,
282 auto flutterPlatformViewsController =
283 std::make_shared<flutter::FlutterPlatformViewsController>();
284 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
286 mock_delegate.settings_.enable_impeller
289 flutterPlatformViewsController,
292 std::make_shared<fml::SyncSwitch>());
293 id mockFlutterView = OCMClassMock([
FlutterView class]);
295 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
296 std::string label =
"some label";
297 flutterPlatformViewsController->SetFlutterView(mockFlutterView);
300 flutterPlatformViewsController->RegisterViewFactory(
301 factory,
@"MockFlutterPlatformView",
305 flutterPlatformViewsController->OnMethodCall(
307 methodCallWithMethodName:
@"create"
308 arguments:@{
@"id" : @2,
@"viewType" :
@"MockFlutterPlatformView"}],
311 auto bridge = std::make_unique<flutter::AccessibilityBridge>(
312 mockFlutterViewController,
314 flutterPlatformViewsController);
316 flutter::SemanticsNodeUpdates nodes;
317 flutter::SemanticsNode semantics_node;
318 semantics_node.id = 2;
319 semantics_node.platformViewId = 2;
320 semantics_node.label = label;
321 nodes[kRootNodeId] = semantics_node;
322 flutter::CustomAccessibilityActionUpdates actions;
323 bridge->UpdateSemantics(nodes, actions);
325 flutterPlatformViewsController->Reset();
330 - (void)testSemanticsDeallocatedWithoutLoadingView {
335 flutter::MockDelegate mock_delegate;
337 flutter::TaskRunners runners(
self.name.UTF8String,
343 auto flutterPlatformViewsController =
344 std::make_shared<flutter::FlutterPlatformViewsController>();
345 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
347 mock_delegate.settings_.enable_impeller
350 flutterPlatformViewsController,
353 std::make_shared<fml::SyncSwitch>());
356 flutterPlatformViewsController->RegisterViewFactory(
357 factory,
@"MockFlutterPlatformView",
361 flutterPlatformViewsController->OnMethodCall(
363 methodCallWithMethodName:
@"create"
364 arguments:@{
@"id" : @2,
@"viewType" :
@"MockFlutterPlatformView"}],
367 auto bridge = std::make_unique<flutter::AccessibilityBridge>(
368 flutterViewController,
370 flutterPlatformViewsController);
373 flutterPlatformViewsController->Reset();
377 XCTAssertNil(flutterViewController.viewIfLoaded);
378 [flutterViewController deregisterNotifications];
381 - (void)testReplacedSemanticsDoesNotCleanupChildren {
382 flutter::MockDelegate mock_delegate;
384 flutter::TaskRunners runners(
self.name.UTF8String,
390 auto flutterPlatformViewsController = std::make_shared<flutter::FlutterPlatformViewsController>();
391 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
393 mock_delegate.settings_.enable_impeller
396 flutterPlatformViewsController,
399 std::make_shared<fml::SyncSwitch>());
405 OCMStub([mockFlutterViewController view]).andReturn(flutterView);
406 std::string label =
"some label";
407 auto bridge = std::make_unique<flutter::AccessibilityBridge>(
408 mockFlutterViewController,
410 flutterPlatformViewsController);
412 flutter::SemanticsNodeUpdates nodes;
413 flutter::SemanticsNode parent;
415 parent.rect = SkRect::MakeXYWH(0, 0, 100, 200);
416 parent.label =
"label";
417 parent.value =
"value";
418 parent.hint =
"hint";
420 flutter::SemanticsNode node;
422 node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
423 node.label =
"label";
424 node.value =
"value";
426 node.scrollExtentMax = 100.0;
427 node.scrollPosition = 0.0;
428 parent.childrenInTraversalOrder.push_back(1);
429 parent.childrenInHitTestOrder.push_back(1);
431 flutter::SemanticsNode child;
433 child.rect = SkRect::MakeXYWH(0, 0, 100, 200);
434 child.label =
"label";
435 child.value =
"value";
437 node.childrenInTraversalOrder.push_back(2);
438 node.childrenInHitTestOrder.push_back(2);
443 flutter::CustomAccessibilityActionUpdates actions;
444 bridge->UpdateSemantics(nodes, actions);
447 flutter::SemanticsNodeUpdates new_nodes;
448 flutter::SemanticsNode new_node;
450 new_node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
451 new_node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kHasImplicitScrolling);
452 new_node.actions = flutter::kHorizontalScrollSemanticsActions;
453 new_node.label =
"label";
454 new_node.value =
"value";
455 new_node.hint =
"hint";
456 new_node.scrollExtentMax = 100.0;
457 new_node.scrollPosition = 0.0;
458 new_node.childrenInTraversalOrder.push_back(2);
459 new_node.childrenInHitTestOrder.push_back(2);
461 new_nodes[1] = new_node;
462 bridge->UpdateSemantics(new_nodes, actions);
466 id rootContainer = flutterView.accessibilityElements[0];
467 XCTAssertTrue([rootContainer accessibilityElementCount] ==
469 id scrollableContainer = [rootContainer accessibilityElementAtIndex:1];
470 XCTAssertTrue([scrollableContainer accessibilityElementCount] ==
472 id child = [scrollableContainer accessibilityElementAtIndex:1];
474 XCTAssertNotNil([child accessibilityContainer]);
477 - (void)testScrollableSemanticsDeallocated {
478 flutter::MockDelegate mock_delegate;
480 flutter::TaskRunners runners(
self.name.UTF8String,
486 auto flutterPlatformViewsController = std::make_shared<flutter::FlutterPlatformViewsController>();
487 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
489 mock_delegate.settings_.enable_impeller
492 flutterPlatformViewsController,
495 std::make_shared<fml::SyncSwitch>());
501 OCMStub([mockFlutterViewController view]).andReturn(flutterView);
502 std::string label =
"some label";
504 auto bridge = std::make_unique<flutter::AccessibilityBridge>(
505 mockFlutterViewController,
507 flutterPlatformViewsController);
509 flutter::SemanticsNodeUpdates nodes;
510 flutter::SemanticsNode parent;
512 parent.rect = SkRect::MakeXYWH(0, 0, 100, 200);
513 parent.label =
"label";
514 parent.value =
"value";
515 parent.hint =
"hint";
517 flutter::SemanticsNode node;
519 node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kHasImplicitScrolling);
520 node.actions = flutter::kHorizontalScrollSemanticsActions;
521 node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
522 node.label =
"label";
523 node.value =
"value";
525 node.scrollExtentMax = 100.0;
526 node.scrollPosition = 0.0;
527 parent.childrenInTraversalOrder.push_back(1);
528 parent.childrenInHitTestOrder.push_back(1);
531 flutter::CustomAccessibilityActionUpdates actions;
532 bridge->UpdateSemantics(nodes, actions);
533 XCTAssertTrue([flutterView.subviews count] == 1);
535 XCTAssertTrue([flutterView.subviews[0].accessibilityLabel isEqualToString:
@"label"]);
538 flutter::SemanticsNodeUpdates new_nodes;
539 flutter::SemanticsNode new_parent;
541 new_parent.rect = SkRect::MakeXYWH(0, 0, 100, 200);
542 new_parent.label =
"label";
543 new_parent.value =
"value";
544 new_parent.hint =
"hint";
545 new_nodes[0] = new_parent;
546 bridge->UpdateSemantics(new_nodes, actions);
548 XCTAssertTrue([flutterView.subviews count] == 0);
551 - (void)testBridgeReplacesSemanticsNode {
552 flutter::MockDelegate mock_delegate;
554 flutter::TaskRunners runners(
self.name.UTF8String,
560 auto flutterPlatformViewsController = std::make_shared<flutter::FlutterPlatformViewsController>();
561 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
563 mock_delegate.settings_.enable_impeller
566 flutterPlatformViewsController,
569 std::make_shared<fml::SyncSwitch>());
575 OCMStub([mockFlutterViewController view]).andReturn(flutterView);
576 std::string label =
"some label";
578 auto bridge = std::make_unique<flutter::AccessibilityBridge>(
579 mockFlutterViewController,
581 flutterPlatformViewsController);
583 flutter::SemanticsNodeUpdates nodes;
584 flutter::SemanticsNode parent;
586 parent.rect = SkRect::MakeXYWH(0, 0, 100, 200);
587 parent.label =
"label";
588 parent.value =
"value";
589 parent.hint =
"hint";
591 flutter::SemanticsNode node;
593 node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kHasImplicitScrolling);
594 node.actions = flutter::kHorizontalScrollSemanticsActions;
595 node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
596 node.label =
"label";
597 node.value =
"value";
599 node.scrollExtentMax = 100.0;
600 node.scrollPosition = 0.0;
601 parent.childrenInTraversalOrder.push_back(1);
602 parent.childrenInHitTestOrder.push_back(1);
605 flutter::CustomAccessibilityActionUpdates actions;
606 bridge->UpdateSemantics(nodes, actions);
607 XCTAssertTrue([flutterView.subviews count] == 1);
609 XCTAssertTrue([flutterView.subviews[0].accessibilityLabel isEqualToString:
@"label"]);
612 flutter::SemanticsNodeUpdates new_nodes;
613 flutter::SemanticsNode new_node;
615 new_node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
616 new_node.label =
"label";
617 new_node.value =
"value";
618 new_node.hint =
"hint";
619 new_node.scrollExtentMax = 100.0;
620 new_node.scrollPosition = 0.0;
621 new_nodes[1] = new_node;
622 bridge->UpdateSemantics(new_nodes, actions);
624 XCTAssertTrue([flutterView.subviews count] == 0);
627 - (void)testAnnouncesRouteChanges {
628 flutter::MockDelegate mock_delegate;
630 flutter::TaskRunners runners(
self.name.UTF8String,
635 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
637 mock_delegate.settings_.enable_impeller
643 std::make_shared<fml::SyncSwitch>());
644 id mockFlutterView = OCMClassMock([
FlutterView class]);
646 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
648 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
649 [[NSMutableArray alloc] init];
650 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
651 ios_delegate->on_PostAccessibilityNotification_ =
652 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
653 [accessibility_notifications addObject:@{
654 @"notification" : @(notification),
655 @"argument" : argument ? argument : [NSNull null],
658 __block
auto bridge =
659 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
662 std::move(ios_delegate));
664 flutter::CustomAccessibilityActionUpdates actions;
665 flutter::SemanticsNodeUpdates nodes;
667 flutter::SemanticsNode node1;
669 node1.label =
"node1";
670 node1.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute);
671 node1.childrenInTraversalOrder = {2, 3};
672 node1.childrenInHitTestOrder = {2, 3};
673 nodes[node1.id] = node1;
674 flutter::SemanticsNode node2;
676 node2.label =
"node2";
677 nodes[node2.id] = node2;
678 flutter::SemanticsNode node3;
680 node3.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kNamesRoute);
681 node3.label =
"node3";
682 nodes[node3.id] = node3;
683 flutter::SemanticsNode root_node;
685 root_node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute);
686 root_node.childrenInTraversalOrder = {1};
687 root_node.childrenInHitTestOrder = {1};
688 nodes[root_node.id] = root_node;
689 bridge->UpdateSemantics(nodes, actions);
691 XCTAssertEqual([accessibility_notifications count], 1ul);
692 XCTAssertEqualObjects(accessibility_notifications[0][
@"argument"],
@"node3");
693 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
694 UIAccessibilityScreenChangedNotification);
697 - (void)testRadioButtonIsNotSwitchButton {
698 flutter::MockDelegate mock_delegate;
700 flutter::TaskRunners runners(
self.name.UTF8String,
705 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
707 mock_delegate.settings_.enable_impeller
713 std::make_shared<fml::SyncSwitch>());
719 OCMStub([mockFlutterViewController view]).andReturn(flutterView);
720 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
721 __block
auto bridge =
722 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
725 std::move(ios_delegate));
727 flutter::CustomAccessibilityActionUpdates actions;
728 flutter::SemanticsNodeUpdates nodes;
730 flutter::SemanticsNode root_node;
732 root_node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kIsInMutuallyExclusiveGroup) |
733 static_cast<int32_t
>(flutter::SemanticsFlags::kIsEnabled) |
734 static_cast<int32_t
>(flutter::SemanticsFlags::kHasCheckedState) |
735 static_cast<int32_t
>(flutter::SemanticsFlags::kHasEnabledState);
736 nodes[root_node.id] = root_node;
737 bridge->UpdateSemantics(nodes, actions);
742 XCTAssertTrue((rootNode.accessibilityTraits & UIAccessibilityTraitButton) > 0);
743 XCTAssertNil(rootNode.accessibilityValue);
746 - (void)testLayoutChangeWithNonAccessibilityElement {
747 flutter::MockDelegate mock_delegate;
749 flutter::TaskRunners runners(
self.name.UTF8String,
754 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
756 mock_delegate.settings_.enable_impeller
762 std::make_shared<fml::SyncSwitch>());
763 id mockFlutterView = OCMClassMock([
FlutterView class]);
765 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
767 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
768 [[NSMutableArray alloc] init];
769 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
770 ios_delegate->on_PostAccessibilityNotification_ =
771 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
772 [accessibility_notifications addObject:@{
773 @"notification" : @(notification),
774 @"argument" : argument ? argument : [NSNull null],
777 __block
auto bridge =
778 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
781 std::move(ios_delegate));
783 flutter::CustomAccessibilityActionUpdates actions;
784 flutter::SemanticsNodeUpdates nodes;
786 flutter::SemanticsNode node1;
788 node1.label =
"node1";
789 node1.childrenInTraversalOrder = {2, 3};
790 node1.childrenInHitTestOrder = {2, 3};
791 nodes[node1.id] = node1;
792 flutter::SemanticsNode node2;
794 node2.label =
"node2";
795 nodes[node2.id] = node2;
796 flutter::SemanticsNode node3;
798 node3.label =
"node3";
799 nodes[node3.id] = node3;
800 flutter::SemanticsNode root_node;
802 root_node.label =
"root";
803 root_node.childrenInTraversalOrder = {1};
804 root_node.childrenInHitTestOrder = {1};
805 nodes[root_node.id] = root_node;
806 bridge->UpdateSemantics(nodes, actions);
809 bridge->AccessibilityObjectDidBecomeFocused(1);
814 flutter::CustomAccessibilityActionUpdates new_actions;
815 flutter::SemanticsNodeUpdates new_nodes;
817 flutter::SemanticsNode new_node1;
819 new_node1.childrenInTraversalOrder = {2};
820 new_node1.childrenInHitTestOrder = {2};
821 new_nodes[new_node1.id] = new_node1;
822 bridge->UpdateSemantics(new_nodes, new_actions);
824 XCTAssertEqual([accessibility_notifications count], 1ul);
825 SemanticsObject* focusObject = accessibility_notifications[0][@"argument"];
827 XCTAssertEqual([focusObject uid], 2);
828 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
829 UIAccessibilityLayoutChangedNotification);
832 - (void)testLayoutChangeDoesCallNativeAccessibility {
833 flutter::MockDelegate mock_delegate;
835 flutter::TaskRunners runners(
self.name.UTF8String,
840 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
842 mock_delegate.settings_.enable_impeller
848 std::make_shared<fml::SyncSwitch>());
849 id mockFlutterView = OCMClassMock([
FlutterView class]);
851 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
853 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
854 [[NSMutableArray alloc] init];
855 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
856 ios_delegate->on_PostAccessibilityNotification_ =
857 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
858 [accessibility_notifications addObject:@{
859 @"notification" : @(notification),
860 @"argument" : argument ? argument : [NSNull null],
863 __block
auto bridge =
864 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
867 std::move(ios_delegate));
869 flutter::CustomAccessibilityActionUpdates actions;
870 flutter::SemanticsNodeUpdates nodes;
872 flutter::SemanticsNode node1;
874 node1.label =
"node1";
875 nodes[node1.id] = node1;
876 flutter::SemanticsNode root_node;
878 root_node.label =
"root";
879 root_node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kHasImplicitScrolling);
880 root_node.childrenInTraversalOrder = {1};
881 root_node.childrenInHitTestOrder = {1};
882 nodes[root_node.id] = root_node;
883 bridge->UpdateSemantics(nodes, actions);
886 bridge->AccessibilityObjectDidBecomeFocused(0);
889 flutter::CustomAccessibilityActionUpdates new_actions;
890 flutter::SemanticsNodeUpdates new_nodes;
892 flutter::SemanticsNode new_root_node;
894 new_root_node.label =
"root";
895 new_root_node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kHasImplicitScrolling);
896 new_nodes[new_root_node.id] = new_root_node;
897 bridge->UpdateSemantics(new_nodes, new_actions);
899 XCTAssertEqual([accessibility_notifications count], 1ul);
900 id focusObject = accessibility_notifications[0][@"argument"];
904 XCTAssertEqualObjects(focusObject, [NSNull
null]);
905 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
906 UIAccessibilityLayoutChangedNotification);
909 - (void)testLayoutChangeDoesCallNativeAccessibilityWhenFocusChanged {
910 flutter::MockDelegate mock_delegate;
912 flutter::TaskRunners runners(
self.name.UTF8String,
917 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
919 mock_delegate.settings_.enable_impeller
925 std::make_shared<fml::SyncSwitch>());
926 id mockFlutterView = OCMClassMock([
FlutterView class]);
928 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
930 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
931 [[NSMutableArray alloc] init];
932 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
933 ios_delegate->on_PostAccessibilityNotification_ =
934 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
935 [accessibility_notifications addObject:@{
936 @"notification" : @(notification),
937 @"argument" : argument ? argument : [NSNull null],
940 __block
auto bridge =
941 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
944 std::move(ios_delegate));
946 flutter::CustomAccessibilityActionUpdates actions;
947 flutter::SemanticsNodeUpdates nodes;
949 flutter::SemanticsNode node1;
951 node1.label =
"node1";
952 nodes[node1.id] = node1;
953 flutter::SemanticsNode root_node;
955 root_node.label =
"root";
956 root_node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kHasImplicitScrolling);
957 root_node.childrenInTraversalOrder = {1};
958 root_node.childrenInHitTestOrder = {1};
959 nodes[root_node.id] = root_node;
960 bridge->UpdateSemantics(nodes, actions);
963 bridge->AccessibilityObjectDidBecomeFocused(1);
966 flutter::CustomAccessibilityActionUpdates new_actions;
967 flutter::SemanticsNodeUpdates new_nodes;
969 flutter::SemanticsNode new_root_node;
971 new_root_node.label =
"root";
972 new_root_node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kHasImplicitScrolling);
973 new_nodes[new_root_node.id] = new_root_node;
974 bridge->UpdateSemantics(new_nodes, new_actions);
976 XCTAssertEqual([accessibility_notifications count], 1ul);
977 SemanticsObject* focusObject2 = accessibility_notifications[0][@"argument"];
981 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
982 UIAccessibilityLayoutChangedNotification);
985 - (void)testScrollableSemanticsContainerReturnsCorrectChildren {
986 flutter::MockDelegate mock_delegate;
988 flutter::TaskRunners runners(
self.name.UTF8String,
993 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
995 mock_delegate.settings_.enable_impeller
1001 std::make_shared<fml::SyncSwitch>());
1002 id mockFlutterView = OCMClassMock([
FlutterView class]);
1004 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1006 OCMExpect([mockFlutterView
1007 setAccessibilityElements:[OCMArg checkWithBlock:^BOOL(NSArray* value) {
1008 if ([value count] != 1) {
1017 return [scrollableContainer indexOfAccessibilityElement:nativeScrollable] == 1;
1019 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1020 __block
auto bridge =
1021 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
1024 std::move(ios_delegate));
1026 flutter::CustomAccessibilityActionUpdates actions;
1027 flutter::SemanticsNodeUpdates nodes;
1029 flutter::SemanticsNode node1;
1031 node1.label =
"node1";
1032 node1.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kHasImplicitScrolling);
1033 nodes[node1.id] = node1;
1034 flutter::SemanticsNode root_node;
1036 root_node.label =
"root";
1037 root_node.childrenInTraversalOrder = {1};
1038 root_node.childrenInHitTestOrder = {1};
1039 nodes[root_node.id] = root_node;
1040 bridge->UpdateSemantics(nodes, actions);
1041 OCMVerifyAll(mockFlutterView);
1044 - (void)testAnnouncesRouteChangesAndLayoutChangeInOneUpdate {
1045 flutter::MockDelegate mock_delegate;
1047 flutter::TaskRunners runners(
self.name.UTF8String,
1051 thread_task_runner);
1052 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1054 mock_delegate.settings_.enable_impeller
1060 std::make_shared<fml::SyncSwitch>());
1061 id mockFlutterView = OCMClassMock([
FlutterView class]);
1063 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1065 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1066 [[NSMutableArray alloc] init];
1067 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1068 ios_delegate->on_PostAccessibilityNotification_ =
1069 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
1070 [accessibility_notifications addObject:@{
1071 @"notification" : @(notification),
1072 @"argument" : argument ? argument : [NSNull null],
1075 __block
auto bridge =
1076 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
1079 std::move(ios_delegate));
1081 flutter::CustomAccessibilityActionUpdates actions;
1082 flutter::SemanticsNodeUpdates nodes;
1084 flutter::SemanticsNode node1;
1086 node1.label =
"node1";
1087 node1.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute) |
1088 static_cast<int32_t
>(flutter::SemanticsFlags::kNamesRoute);
1089 nodes[node1.id] = node1;
1090 flutter::SemanticsNode node3;
1092 node3.label =
"node3";
1093 nodes[node3.id] = node3;
1094 flutter::SemanticsNode root_node;
1096 root_node.label =
"root";
1097 root_node.childrenInTraversalOrder = {1, 3};
1098 root_node.childrenInHitTestOrder = {1, 3};
1099 nodes[root_node.id] = root_node;
1100 bridge->UpdateSemantics(nodes, actions);
1102 XCTAssertEqual([accessibility_notifications count], 1ul);
1103 XCTAssertEqualObjects(accessibility_notifications[0][
@"argument"],
@"node1");
1104 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
1105 UIAccessibilityScreenChangedNotification);
1108 bridge->AccessibilityObjectDidBecomeFocused(0);
1110 flutter::SemanticsNodeUpdates new_nodes;
1112 flutter::SemanticsNode new_node1;
1114 new_node1.label =
"new_node1";
1115 new_node1.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute) |
1116 static_cast<int32_t
>(flutter::SemanticsFlags::kNamesRoute);
1117 new_node1.childrenInTraversalOrder = {2};
1118 new_node1.childrenInHitTestOrder = {2};
1119 new_nodes[new_node1.id] = new_node1;
1120 flutter::SemanticsNode new_node2;
1122 new_node2.label =
"new_node2";
1123 new_node2.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute) |
1124 static_cast<int32_t
>(flutter::SemanticsFlags::kNamesRoute);
1125 new_nodes[new_node2.id] = new_node2;
1126 flutter::SemanticsNode new_root_node;
1128 new_root_node.label =
"root";
1129 new_root_node.childrenInTraversalOrder = {1};
1130 new_root_node.childrenInHitTestOrder = {1};
1131 new_nodes[new_root_node.id] = new_root_node;
1132 bridge->UpdateSemantics(new_nodes, actions);
1133 XCTAssertEqual([accessibility_notifications count], 3ul);
1134 XCTAssertEqualObjects(accessibility_notifications[1][
@"argument"],
@"new_node2");
1135 XCTAssertEqual([accessibility_notifications[1][
@"notification"] unsignedIntValue],
1136 UIAccessibilityScreenChangedNotification);
1137 SemanticsObject* focusObject = accessibility_notifications[2][@"argument"];
1138 XCTAssertEqual([focusObject uid], 0);
1139 XCTAssertEqual([accessibility_notifications[2][
@"notification"] unsignedIntValue],
1140 UIAccessibilityLayoutChangedNotification);
1143 - (void)testAnnouncesRouteChangesWhenAddAdditionalRoute {
1144 flutter::MockDelegate mock_delegate;
1146 flutter::TaskRunners runners(
self.name.UTF8String,
1150 thread_task_runner);
1151 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1153 mock_delegate.settings_.enable_impeller
1159 std::make_shared<fml::SyncSwitch>());
1160 id mockFlutterView = OCMClassMock([
FlutterView class]);
1162 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1164 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1165 [[NSMutableArray alloc] init];
1166 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1167 ios_delegate->on_PostAccessibilityNotification_ =
1168 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
1169 [accessibility_notifications addObject:@{
1170 @"notification" : @(notification),
1171 @"argument" : argument ? argument : [NSNull null],
1174 __block
auto bridge =
1175 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
1178 std::move(ios_delegate));
1180 flutter::CustomAccessibilityActionUpdates actions;
1181 flutter::SemanticsNodeUpdates nodes;
1183 flutter::SemanticsNode node1;
1185 node1.label =
"node1";
1186 node1.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute) |
1187 static_cast<int32_t
>(flutter::SemanticsFlags::kNamesRoute);
1188 nodes[node1.id] = node1;
1189 flutter::SemanticsNode root_node;
1191 root_node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute);
1192 root_node.childrenInTraversalOrder = {1};
1193 root_node.childrenInHitTestOrder = {1};
1194 nodes[root_node.id] = root_node;
1195 bridge->UpdateSemantics(nodes, actions);
1197 XCTAssertEqual([accessibility_notifications count], 1ul);
1198 XCTAssertEqualObjects(accessibility_notifications[0][
@"argument"],
@"node1");
1199 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
1200 UIAccessibilityScreenChangedNotification);
1202 flutter::SemanticsNodeUpdates new_nodes;
1204 flutter::SemanticsNode new_node1;
1206 new_node1.label =
"new_node1";
1207 new_node1.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute) |
1208 static_cast<int32_t
>(flutter::SemanticsFlags::kNamesRoute);
1209 new_node1.childrenInTraversalOrder = {2};
1210 new_node1.childrenInHitTestOrder = {2};
1211 new_nodes[new_node1.id] = new_node1;
1212 flutter::SemanticsNode new_node2;
1214 new_node2.label =
"new_node2";
1215 new_node2.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute) |
1216 static_cast<int32_t
>(flutter::SemanticsFlags::kNamesRoute);
1217 new_nodes[new_node2.id] = new_node2;
1218 flutter::SemanticsNode new_root_node;
1220 new_root_node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute);
1221 new_root_node.childrenInTraversalOrder = {1};
1222 new_root_node.childrenInHitTestOrder = {1};
1223 new_nodes[new_root_node.id] = new_root_node;
1224 bridge->UpdateSemantics(new_nodes, actions);
1225 XCTAssertEqual([accessibility_notifications count], 2ul);
1226 XCTAssertEqualObjects(accessibility_notifications[1][
@"argument"],
@"new_node2");
1227 XCTAssertEqual([accessibility_notifications[1][
@"notification"] unsignedIntValue],
1228 UIAccessibilityScreenChangedNotification);
1231 - (void)testAnnouncesRouteChangesRemoveRouteInMiddle {
1232 flutter::MockDelegate mock_delegate;
1234 flutter::TaskRunners runners(
self.name.UTF8String,
1238 thread_task_runner);
1239 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1241 mock_delegate.settings_.enable_impeller
1247 std::make_shared<fml::SyncSwitch>());
1248 id mockFlutterView = OCMClassMock([
FlutterView class]);
1250 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1252 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1253 [[NSMutableArray alloc] init];
1254 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1255 ios_delegate->on_PostAccessibilityNotification_ =
1256 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
1257 [accessibility_notifications addObject:@{
1258 @"notification" : @(notification),
1259 @"argument" : argument ? argument : [NSNull null],
1262 __block
auto bridge =
1263 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
1266 std::move(ios_delegate));
1268 flutter::CustomAccessibilityActionUpdates actions;
1269 flutter::SemanticsNodeUpdates nodes;
1271 flutter::SemanticsNode node1;
1273 node1.label =
"node1";
1274 node1.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute) |
1275 static_cast<int32_t
>(flutter::SemanticsFlags::kNamesRoute);
1276 node1.childrenInTraversalOrder = {2};
1277 node1.childrenInHitTestOrder = {2};
1278 nodes[node1.id] = node1;
1279 flutter::SemanticsNode node2;
1281 node2.label =
"node2";
1282 node2.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute) |
1283 static_cast<int32_t
>(flutter::SemanticsFlags::kNamesRoute);
1284 nodes[node2.id] = node2;
1285 flutter::SemanticsNode root_node;
1287 root_node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute);
1288 root_node.childrenInTraversalOrder = {1};
1289 root_node.childrenInHitTestOrder = {1};
1290 nodes[root_node.id] = root_node;
1291 bridge->UpdateSemantics(nodes, actions);
1293 XCTAssertEqual([accessibility_notifications count], 1ul);
1294 XCTAssertEqualObjects(accessibility_notifications[0][
@"argument"],
@"node2");
1295 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
1296 UIAccessibilityScreenChangedNotification);
1298 flutter::SemanticsNodeUpdates new_nodes;
1300 flutter::SemanticsNode new_node1;
1302 new_node1.label =
"new_node1";
1303 new_node1.childrenInTraversalOrder = {2};
1304 new_node1.childrenInHitTestOrder = {2};
1305 new_nodes[new_node1.id] = new_node1;
1306 flutter::SemanticsNode new_node2;
1308 new_node2.label =
"new_node2";
1309 new_node2.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute) |
1310 static_cast<int32_t
>(flutter::SemanticsFlags::kNamesRoute);
1311 new_nodes[new_node2.id] = new_node2;
1312 flutter::SemanticsNode new_root_node;
1314 new_root_node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute);
1315 new_root_node.childrenInTraversalOrder = {1};
1316 new_root_node.childrenInHitTestOrder = {1};
1317 new_nodes[new_root_node.id] = new_root_node;
1318 bridge->UpdateSemantics(new_nodes, actions);
1319 XCTAssertEqual([accessibility_notifications count], 2ul);
1320 XCTAssertEqualObjects(accessibility_notifications[1][
@"argument"],
@"new_node2");
1321 XCTAssertEqual([accessibility_notifications[1][
@"notification"] unsignedIntValue],
1322 UIAccessibilityScreenChangedNotification);
1325 - (void)testHandleEvent {
1326 flutter::MockDelegate mock_delegate;
1328 flutter::TaskRunners runners(
self.name.UTF8String,
1332 thread_task_runner);
1333 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1335 mock_delegate.settings_.enable_impeller
1341 std::make_shared<fml::SyncSwitch>());
1342 id mockFlutterView = OCMClassMock([
FlutterView class]);
1344 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1346 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1347 [[NSMutableArray alloc] init];
1348 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1349 ios_delegate->on_PostAccessibilityNotification_ =
1350 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
1351 [accessibility_notifications addObject:@{
1352 @"notification" : @(notification),
1353 @"argument" : argument ? argument : [NSNull null],
1356 __block
auto bridge =
1357 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
1360 std::move(ios_delegate));
1362 NSDictionary<NSString*, id>* annotatedEvent = @{
@"type" :
@"focus",
@"nodeId" : @123};
1364 bridge->HandleEvent(annotatedEvent);
1366 XCTAssertEqual([accessibility_notifications count], 1ul);
1367 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
1368 UIAccessibilityLayoutChangedNotification);
1371 - (void)testAccessibilityObjectDidBecomeFocused {
1372 flutter::MockDelegate mock_delegate;
1373 auto thread = std::make_unique<fml::Thread>(
"AccessibilityBridgeTest");
1374 auto thread_task_runner = thread->GetTaskRunner();
1375 flutter::TaskRunners runners(
self.name.UTF8String,
1379 thread_task_runner);
1384 OCMStub([flutterViewController
engine]).andReturn(
engine);
1385 OCMStub([
engine binaryMessenger]).andReturn(messenger);
1387 OCMStub([messenger setMessageHandlerOnChannel:
@"flutter/accessibility"
1388 binaryMessageHandler:[OCMArg any]])
1389 .andReturn(connection);
1391 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1393 mock_delegate.settings_.enable_impeller
1399 std::make_shared<fml::SyncSwitch>());
1400 fml::AutoResetWaitableEvent latch;
1401 thread_task_runner->PostTask([&] {
1403 std::make_unique<fml::WeakNSObjectFactory<FlutterViewController>>(flutterViewController);
1404 platform_view->SetOwnerViewController(weakFactory->GetWeakNSObject());
1406 std::make_unique<flutter::AccessibilityBridge>(nil,
1409 XCTAssertTrue(bridge.get());
1410 OCMVerify([messenger setMessageHandlerOnChannel:
@"flutter/accessibility"
1411 binaryMessageHandler:[OCMArg isNotNil]]);
1413 bridge->AccessibilityObjectDidBecomeFocused(123);
1415 NSDictionary<NSString*, id>* annotatedEvent = @{
@"type" :
@"didGainFocus",
@"nodeId" : @123};
1418 OCMVerify([messenger sendOnChannel:
@"flutter/accessibility" message:encodedMessage]);
1423 [engine stopMocking];
1426 - (void)testAnnouncesRouteChangesWhenNoNamesRoute {
1427 flutter::MockDelegate mock_delegate;
1429 flutter::TaskRunners runners(
self.name.UTF8String,
1433 thread_task_runner);
1434 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1436 mock_delegate.settings_.enable_impeller
1442 std::make_shared<fml::SyncSwitch>());
1443 id mockFlutterView = OCMClassMock([
FlutterView class]);
1445 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1447 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1448 [[NSMutableArray alloc] init];
1449 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1450 ios_delegate->on_PostAccessibilityNotification_ =
1451 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
1452 [accessibility_notifications addObject:@{
1453 @"notification" : @(notification),
1454 @"argument" : argument ? argument : [NSNull null],
1457 __block
auto bridge =
1458 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
1461 std::move(ios_delegate));
1463 flutter::CustomAccessibilityActionUpdates actions;
1464 flutter::SemanticsNodeUpdates nodes;
1466 flutter::SemanticsNode node1;
1468 node1.label =
"node1";
1469 node1.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute) |
1470 static_cast<int32_t
>(flutter::SemanticsFlags::kNamesRoute);
1471 node1.childrenInTraversalOrder = {2, 3};
1472 node1.childrenInHitTestOrder = {2, 3};
1473 nodes[node1.id] = node1;
1474 flutter::SemanticsNode node2;
1476 node2.label =
"node2";
1477 nodes[node2.id] = node2;
1478 flutter::SemanticsNode node3;
1480 node3.label =
"node3";
1481 nodes[node3.id] = node3;
1482 flutter::SemanticsNode root_node;
1484 root_node.childrenInTraversalOrder = {1};
1485 root_node.childrenInHitTestOrder = {1};
1486 nodes[root_node.id] = root_node;
1487 bridge->UpdateSemantics(nodes, actions);
1490 XCTAssertEqual([accessibility_notifications count], 1ul);
1491 id focusObject = accessibility_notifications[0][@"argument"];
1492 XCTAssertTrue([focusObject isKindOfClass:[NSString
class]]);
1493 XCTAssertEqualObjects(focusObject,
@"node1");
1494 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
1495 UIAccessibilityScreenChangedNotification);
1498 - (void)testAnnouncesLayoutChangeWithNilIfLastFocusIsRemoved {
1499 flutter::MockDelegate mock_delegate;
1501 flutter::TaskRunners runners(
self.name.UTF8String,
1505 thread_task_runner);
1506 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1508 mock_delegate.settings_.enable_impeller
1514 std::make_shared<fml::SyncSwitch>());
1516 id mockFlutterView = OCMClassMock([
FlutterView class]);
1517 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1519 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1520 [[NSMutableArray alloc] init];
1521 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1522 ios_delegate->on_PostAccessibilityNotification_ =
1523 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
1524 [accessibility_notifications addObject:@{
1525 @"notification" : @(notification),
1526 @"argument" : argument ? argument : [NSNull null],
1529 __block
auto bridge =
1530 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
1533 std::move(ios_delegate));
1535 flutter::CustomAccessibilityActionUpdates actions;
1536 flutter::SemanticsNodeUpdates first_update;
1538 flutter::SemanticsNode route_node;
1540 route_node.label =
"route";
1541 first_update[route_node.id] = route_node;
1542 flutter::SemanticsNode root_node;
1544 root_node.label =
"root";
1545 root_node.childrenInTraversalOrder = {1};
1546 root_node.childrenInHitTestOrder = {1};
1547 first_update[root_node.id] = root_node;
1548 bridge->UpdateSemantics(first_update, actions);
1550 XCTAssertEqual([accessibility_notifications count], 0ul);
1552 bridge->AccessibilityObjectDidBecomeFocused(1);
1554 flutter::SemanticsNodeUpdates second_update;
1556 flutter::SemanticsNode new_root_node;
1558 new_root_node.label =
"root";
1559 second_update[root_node.id] = new_root_node;
1560 bridge->UpdateSemantics(second_update, actions);
1561 SemanticsObject* focusObject = accessibility_notifications[0][@"argument"];
1563 XCTAssertEqual([focusObject uid], 0);
1564 XCTAssertEqualObjects([focusObject accessibilityLabel],
@"root");
1565 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
1566 UIAccessibilityLayoutChangedNotification);
1569 - (void)testAnnouncesLayoutChangeWithTheSameItemFocused {
1570 flutter::MockDelegate mock_delegate;
1572 flutter::TaskRunners runners(
self.name.UTF8String,
1576 thread_task_runner);
1577 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1579 mock_delegate.settings_.enable_impeller
1585 std::make_shared<fml::SyncSwitch>());
1587 id mockFlutterView = OCMClassMock([
FlutterView class]);
1588 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1590 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1591 [[NSMutableArray alloc] init];
1592 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1593 ios_delegate->on_PostAccessibilityNotification_ =
1594 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
1595 [accessibility_notifications addObject:@{
1596 @"notification" : @(notification),
1597 @"argument" : argument ? argument : [NSNull null],
1600 __block
auto bridge =
1601 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
1604 std::move(ios_delegate));
1606 flutter::CustomAccessibilityActionUpdates actions;
1607 flutter::SemanticsNodeUpdates first_update;
1609 flutter::SemanticsNode node_one;
1611 node_one.label =
"route1";
1612 first_update[node_one.id] = node_one;
1613 flutter::SemanticsNode node_two;
1615 node_two.label =
"route2";
1616 first_update[node_two.id] = node_two;
1617 flutter::SemanticsNode root_node;
1619 root_node.label =
"root";
1620 root_node.childrenInTraversalOrder = {1, 2};
1621 root_node.childrenInHitTestOrder = {1, 2};
1622 first_update[root_node.id] = root_node;
1623 bridge->UpdateSemantics(first_update, actions);
1625 XCTAssertEqual([accessibility_notifications count], 0ul);
1627 bridge->AccessibilityObjectDidBecomeFocused(1);
1629 flutter::SemanticsNodeUpdates second_update;
1631 flutter::SemanticsNode new_root_node;
1633 new_root_node.label =
"root";
1634 new_root_node.childrenInTraversalOrder = {1};
1635 new_root_node.childrenInHitTestOrder = {1};
1636 second_update[root_node.id] = new_root_node;
1637 bridge->UpdateSemantics(second_update, actions);
1638 id focusObject = accessibility_notifications[0][@"argument"];
1641 XCTAssertEqualObjects(focusObject, [NSNull
null]);
1642 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
1643 UIAccessibilityLayoutChangedNotification);
1646 - (void)testAnnouncesLayoutChangeWhenFocusMovedOutside {
1647 flutter::MockDelegate mock_delegate;
1649 flutter::TaskRunners runners(
self.name.UTF8String,
1653 thread_task_runner);
1654 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1656 mock_delegate.settings_.enable_impeller
1662 std::make_shared<fml::SyncSwitch>());
1664 id mockFlutterView = OCMClassMock([
FlutterView class]);
1665 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1667 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1668 [[NSMutableArray alloc] init];
1669 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1670 ios_delegate->on_PostAccessibilityNotification_ =
1671 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
1672 [accessibility_notifications addObject:@{
1673 @"notification" : @(notification),
1674 @"argument" : argument ? argument : [NSNull null],
1677 __block
auto bridge =
1678 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
1681 std::move(ios_delegate));
1683 flutter::CustomAccessibilityActionUpdates actions;
1684 flutter::SemanticsNodeUpdates first_update;
1686 flutter::SemanticsNode node_one;
1688 node_one.label =
"route1";
1689 first_update[node_one.id] = node_one;
1690 flutter::SemanticsNode node_two;
1692 node_two.label =
"route2";
1693 first_update[node_two.id] = node_two;
1694 flutter::SemanticsNode root_node;
1696 root_node.label =
"root";
1697 root_node.childrenInTraversalOrder = {1, 2};
1698 root_node.childrenInHitTestOrder = {1, 2};
1699 first_update[root_node.id] = root_node;
1700 bridge->UpdateSemantics(first_update, actions);
1702 XCTAssertEqual([accessibility_notifications count], 0ul);
1704 bridge->AccessibilityObjectDidBecomeFocused(1);
1706 bridge->AccessibilityObjectDidLoseFocus(1);
1708 flutter::SemanticsNodeUpdates second_update;
1710 flutter::SemanticsNode new_root_node;
1712 new_root_node.label =
"root";
1713 new_root_node.childrenInTraversalOrder = {1};
1714 new_root_node.childrenInHitTestOrder = {1};
1715 second_update[root_node.id] = new_root_node;
1716 bridge->UpdateSemantics(second_update, actions);
1717 NSNull* focusObject = accessibility_notifications[0][@"argument"];
1720 XCTAssertEqual(focusObject, [NSNull
null]);
1721 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
1722 UIAccessibilityLayoutChangedNotification);
1725 - (void)testAnnouncesScrollChangeWithLastFocused {
1726 flutter::MockDelegate mock_delegate;
1728 flutter::TaskRunners runners(
self.name.UTF8String,
1732 thread_task_runner);
1733 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1735 mock_delegate.settings_.enable_impeller
1741 std::make_shared<fml::SyncSwitch>());
1743 id mockFlutterView = OCMClassMock([
FlutterView class]);
1744 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1746 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1747 [[NSMutableArray alloc] init];
1748 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1749 ios_delegate->on_PostAccessibilityNotification_ =
1750 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
1751 [accessibility_notifications addObject:@{
1752 @"notification" : @(notification),
1753 @"argument" : argument ? argument : [NSNull null],
1756 __block
auto bridge =
1757 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
1760 std::move(ios_delegate));
1762 flutter::CustomAccessibilityActionUpdates actions;
1763 flutter::SemanticsNodeUpdates first_update;
1765 flutter::SemanticsNode node_one;
1767 node_one.label =
"route1";
1768 node_one.scrollPosition = 0.0;
1769 first_update[node_one.id] = node_one;
1770 flutter::SemanticsNode root_node;
1772 root_node.label =
"root";
1773 root_node.childrenInTraversalOrder = {1};
1774 root_node.childrenInHitTestOrder = {1};
1775 first_update[root_node.id] = root_node;
1776 bridge->UpdateSemantics(first_update, actions);
1779 [accessibility_notifications removeAllObjects];
1782 bridge->AccessibilityObjectDidBecomeFocused(1);
1784 flutter::SemanticsNodeUpdates second_update;
1786 flutter::SemanticsNode new_node_one;
1787 new_node_one.id = 1;
1788 new_node_one.label =
"route1";
1789 new_node_one.scrollPosition = 1.0;
1790 second_update[new_node_one.id] = new_node_one;
1791 bridge->UpdateSemantics(second_update, actions);
1792 SemanticsObject* focusObject = accessibility_notifications[0][@"argument"];
1795 XCTAssertEqual([focusObject uid], 1);
1796 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
1797 UIAccessibilityPageScrolledNotification);
1800 - (void)testAnnouncesScrollChangeDoesCallNativeAccessibility {
1801 flutter::MockDelegate mock_delegate;
1803 flutter::TaskRunners runners(
self.name.UTF8String,
1807 thread_task_runner);
1808 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1810 mock_delegate.settings_.enable_impeller
1816 std::make_shared<fml::SyncSwitch>());
1818 id mockFlutterView = OCMClassMock([
FlutterView class]);
1819 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1821 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1822 [[NSMutableArray alloc] init];
1823 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1824 ios_delegate->on_PostAccessibilityNotification_ =
1825 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
1826 [accessibility_notifications addObject:@{
1827 @"notification" : @(notification),
1828 @"argument" : argument ? argument : [NSNull null],
1831 __block
auto bridge =
1832 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
1835 std::move(ios_delegate));
1837 flutter::CustomAccessibilityActionUpdates actions;
1838 flutter::SemanticsNodeUpdates first_update;
1840 flutter::SemanticsNode node_one;
1842 node_one.label =
"route1";
1843 node_one.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kHasImplicitScrolling);
1844 node_one.scrollPosition = 0.0;
1845 first_update[node_one.id] = node_one;
1846 flutter::SemanticsNode root_node;
1848 root_node.label =
"root";
1849 root_node.childrenInTraversalOrder = {1};
1850 root_node.childrenInHitTestOrder = {1};
1851 first_update[root_node.id] = root_node;
1852 bridge->UpdateSemantics(first_update, actions);
1855 [accessibility_notifications removeAllObjects];
1858 bridge->AccessibilityObjectDidBecomeFocused(1);
1860 flutter::SemanticsNodeUpdates second_update;
1862 flutter::SemanticsNode new_node_one;
1863 new_node_one.id = 1;
1864 new_node_one.label =
"route1";
1865 new_node_one.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kHasImplicitScrolling);
1866 new_node_one.scrollPosition = 1.0;
1867 second_update[new_node_one.id] = new_node_one;
1868 bridge->UpdateSemantics(second_update, actions);
1869 SemanticsObject* focusObject = accessibility_notifications[0][@"argument"];
1873 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
1874 UIAccessibilityPageScrolledNotification);
1877 - (void)testAnnouncesIgnoresRouteChangesWhenModal {
1878 flutter::MockDelegate mock_delegate;
1880 flutter::TaskRunners runners(
self.name.UTF8String,
1884 thread_task_runner);
1885 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1887 mock_delegate.settings_.enable_impeller
1893 std::make_shared<fml::SyncSwitch>());
1894 id mockFlutterView = OCMClassMock([
FlutterView class]);
1896 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1897 std::string label =
"some label";
1899 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1900 [[NSMutableArray alloc] init];
1901 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1902 ios_delegate->on_PostAccessibilityNotification_ =
1903 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
1904 [accessibility_notifications addObject:@{
1905 @"notification" : @(notification),
1906 @"argument" : argument ? argument : [NSNull null],
1909 ios_delegate->result_IsFlutterViewControllerPresentingModalViewController_ =
true;
1910 __block
auto bridge =
1911 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
1914 std::move(ios_delegate));
1916 flutter::CustomAccessibilityActionUpdates actions;
1917 flutter::SemanticsNodeUpdates nodes;
1919 flutter::SemanticsNode route_node;
1921 route_node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute) |
1922 static_cast<int32_t
>(flutter::SemanticsFlags::kNamesRoute);
1923 route_node.label =
"route";
1924 nodes[route_node.id] = route_node;
1925 flutter::SemanticsNode root_node;
1927 root_node.label = label;
1928 root_node.childrenInTraversalOrder = {1};
1929 root_node.childrenInHitTestOrder = {1};
1930 nodes[root_node.id] = root_node;
1931 bridge->UpdateSemantics(nodes, actions);
1933 XCTAssertEqual([accessibility_notifications count], 0ul);
1936 - (void)testAnnouncesIgnoresLayoutChangeWhenModal {
1937 flutter::MockDelegate mock_delegate;
1939 flutter::TaskRunners runners(
self.name.UTF8String,
1943 thread_task_runner);
1944 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1946 mock_delegate.settings_.enable_impeller
1952 std::make_shared<fml::SyncSwitch>());
1953 id mockFlutterView = OCMClassMock([
FlutterView class]);
1955 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1957 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1958 [[NSMutableArray alloc] init];
1959 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1960 ios_delegate->on_PostAccessibilityNotification_ =
1961 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
1962 [accessibility_notifications addObject:@{
1963 @"notification" : @(notification),
1964 @"argument" : argument ? argument : [NSNull null],
1967 ios_delegate->result_IsFlutterViewControllerPresentingModalViewController_ =
true;
1968 __block
auto bridge =
1969 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
1972 std::move(ios_delegate));
1974 flutter::CustomAccessibilityActionUpdates actions;
1975 flutter::SemanticsNodeUpdates nodes;
1977 flutter::SemanticsNode child_node;
1979 child_node.label =
"child_node";
1980 nodes[child_node.id] = child_node;
1981 flutter::SemanticsNode root_node;
1983 root_node.label =
"root";
1984 root_node.childrenInTraversalOrder = {1};
1985 root_node.childrenInHitTestOrder = {1};
1986 nodes[root_node.id] = root_node;
1987 bridge->UpdateSemantics(nodes, actions);
1990 flutter::SemanticsNodeUpdates new_nodes;
1991 flutter::SemanticsNode new_root_node;
1993 new_root_node.label =
"root";
1994 new_nodes[new_root_node.id] = new_root_node;
1995 bridge->UpdateSemantics(new_nodes, actions);
1997 XCTAssertEqual([accessibility_notifications count], 0ul);
2000 - (void)testAnnouncesIgnoresScrollChangeWhenModal {
2001 flutter::MockDelegate mock_delegate;
2003 flutter::TaskRunners runners(
self.name.UTF8String,
2007 thread_task_runner);
2008 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2010 mock_delegate.settings_.enable_impeller
2016 std::make_shared<fml::SyncSwitch>());
2017 id mockFlutterView = OCMClassMock([
FlutterView class]);
2019 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
2021 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
2022 [[NSMutableArray alloc] init];
2023 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
2024 ios_delegate->on_PostAccessibilityNotification_ =
2025 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
2026 [accessibility_notifications addObject:@{
2027 @"notification" : @(notification),
2028 @"argument" : argument ? argument : [NSNull null],
2031 ios_delegate->result_IsFlutterViewControllerPresentingModalViewController_ =
true;
2032 __block
auto bridge =
2033 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
2036 std::move(ios_delegate));
2038 flutter::CustomAccessibilityActionUpdates actions;
2039 flutter::SemanticsNodeUpdates nodes;
2041 flutter::SemanticsNode root_node;
2043 root_node.label =
"root";
2044 root_node.scrollPosition = 1;
2045 nodes[root_node.id] = root_node;
2046 bridge->UpdateSemantics(nodes, actions);
2049 flutter::SemanticsNodeUpdates new_nodes;
2050 flutter::SemanticsNode new_root_node;
2052 new_root_node.label =
"root";
2053 new_root_node.scrollPosition = 2;
2054 new_nodes[new_root_node.id] = new_root_node;
2055 bridge->UpdateSemantics(new_nodes, actions);
2057 XCTAssertEqual([accessibility_notifications count], 0ul);
2060 - (void)testAccessibilityMessageAfterDeletion {
2061 flutter::MockDelegate mock_delegate;
2062 auto thread = std::make_unique<fml::Thread>(
"AccessibilityBridgeTest");
2063 auto thread_task_runner = thread->GetTaskRunner();
2064 flutter::TaskRunners runners(
self.name.UTF8String,
2068 thread_task_runner);
2073 OCMStub([flutterViewController
engine]).andReturn(
engine);
2074 OCMStub([
engine binaryMessenger]).andReturn(messenger);
2076 OCMStub([messenger setMessageHandlerOnChannel:
@"flutter/accessibility"
2077 binaryMessageHandler:[OCMArg any]])
2078 .andReturn(connection);
2080 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2082 mock_delegate.settings_.enable_impeller
2088 std::make_shared<fml::SyncSwitch>());
2089 fml::AutoResetWaitableEvent latch;
2090 thread_task_runner->PostTask([&] {
2092 std::make_unique<fml::WeakNSObjectFactory<FlutterViewController>>(flutterViewController);
2093 platform_view->SetOwnerViewController(weakFactory->GetWeakNSObject());
2095 std::make_unique<flutter::AccessibilityBridge>(nil,
2098 XCTAssertTrue(bridge.get());
2099 OCMVerify([messenger setMessageHandlerOnChannel:
@"flutter/accessibility"
2100 binaryMessageHandler:[OCMArg isNotNil]]);
2105 OCMVerify([messenger cleanUpConnection:connection]);
2106 [engine stopMocking];
2109 - (void)testFlutterSemanticsScrollViewManagedObjectLifecycleCorrectly {
2110 flutter::MockDelegate mock_delegate;
2112 flutter::TaskRunners runners(
self.name.UTF8String,
2116 thread_task_runner);
2117 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2119 mock_delegate.settings_.enable_impeller
2125 std::make_shared<fml::SyncSwitch>());
2126 id mockFlutterView = OCMClassMock([
FlutterView class]);
2128 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
2130 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
2131 __block
auto bridge =
2132 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
2135 std::move(ios_delegate));
2144 XCTAssertTrue(flutterSemanticsScrollView);
2147 XCTAssertFalse([flutterSemanticsScrollView isAccessibilityElement]);
2150 - (void)testPlatformViewDestructorDoesNotCallSemanticsAPIs {
2151 class TestDelegate :
public flutter::MockDelegate {
2153 void OnPlatformViewSetSemanticsEnabled(
bool enabled)
override { set_semantics_enabled_calls++; }
2154 int set_semantics_enabled_calls = 0;
2157 TestDelegate test_delegate;
2158 auto thread = std::make_unique<fml::Thread>(
"AccessibilityBridgeTest");
2159 auto thread_task_runner = thread->GetTaskRunner();
2160 flutter::TaskRunners runners(
self.name.UTF8String,
2164 thread_task_runner);
2166 fml::AutoResetWaitableEvent latch;
2167 thread_task_runner->PostTask([&] {
2168 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2170 test_delegate.settings_.enable_impeller
2176 std::make_shared<fml::SyncSwitch>());
2179 auto flutterPlatformViewsController =
2180 std::make_shared<flutter::FlutterPlatformViewsController>();
2181 OCMStub([mockFlutterViewController platformViewsController])
2182 .andReturn(flutterPlatformViewsController.get());
2183 auto weakFactory = std::make_unique<fml::WeakNSObjectFactory<FlutterViewController>>(
2184 mockFlutterViewController);
2185 platform_view->SetOwnerViewController(weakFactory->GetWeakNSObject());
2188 XCTAssertNotEqual(test_delegate.set_semantics_enabled_calls, 0);
2191 test_delegate.set_semantics_enabled_calls = 0;
2193 XCTAssertEqual(test_delegate.set_semantics_enabled_calls, 0);