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 = std::make_shared<flutter::PlatformViewsController>();
283 flutterPlatformViewsController->SetTaskRunner(thread_task_runner);
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 = std::make_shared<flutter::PlatformViewsController>();
344 flutterPlatformViewsController->SetTaskRunner(thread_task_runner);
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::PlatformViewsController>();
391 flutterPlatformViewsController->SetTaskRunner(thread_task_runner);
392 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
394 mock_delegate.settings_.enable_impeller
397 flutterPlatformViewsController,
400 std::make_shared<fml::SyncSwitch>());
406 OCMStub([mockFlutterViewController view]).andReturn(flutterView);
407 std::string label =
"some label";
408 auto bridge = std::make_unique<flutter::AccessibilityBridge>(
409 mockFlutterViewController,
411 flutterPlatformViewsController);
413 flutter::SemanticsNodeUpdates nodes;
414 flutter::SemanticsNode parent;
416 parent.rect = SkRect::MakeXYWH(0, 0, 100, 200);
417 parent.label =
"label";
418 parent.value =
"value";
419 parent.hint =
"hint";
421 flutter::SemanticsNode node;
423 node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
424 node.label =
"label";
425 node.value =
"value";
427 node.scrollExtentMax = 100.0;
428 node.scrollPosition = 0.0;
429 parent.childrenInTraversalOrder.push_back(1);
430 parent.childrenInHitTestOrder.push_back(1);
432 flutter::SemanticsNode child;
434 child.rect = SkRect::MakeXYWH(0, 0, 100, 200);
435 child.label =
"label";
436 child.value =
"value";
438 node.childrenInTraversalOrder.push_back(2);
439 node.childrenInHitTestOrder.push_back(2);
444 flutter::CustomAccessibilityActionUpdates actions;
445 bridge->UpdateSemantics(nodes, actions);
448 flutter::SemanticsNodeUpdates new_nodes;
449 flutter::SemanticsNode new_node;
451 new_node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
452 new_node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kHasImplicitScrolling);
453 new_node.actions = flutter::kHorizontalScrollSemanticsActions;
454 new_node.label =
"label";
455 new_node.value =
"value";
456 new_node.hint =
"hint";
457 new_node.scrollExtentMax = 100.0;
458 new_node.scrollPosition = 0.0;
459 new_node.childrenInTraversalOrder.push_back(2);
460 new_node.childrenInHitTestOrder.push_back(2);
462 new_nodes[1] = new_node;
463 bridge->UpdateSemantics(new_nodes, actions);
467 id rootContainer = flutterView.accessibilityElements[0];
468 XCTAssertTrue([rootContainer accessibilityElementCount] ==
470 id scrollableContainer = [rootContainer accessibilityElementAtIndex:1];
471 XCTAssertTrue([scrollableContainer accessibilityElementCount] ==
473 id child = [scrollableContainer accessibilityElementAtIndex:1];
475 XCTAssertNotNil([child accessibilityContainer]);
478 - (void)testScrollableSemanticsDeallocated {
479 flutter::MockDelegate mock_delegate;
481 flutter::TaskRunners runners(
self.name.UTF8String,
487 auto flutterPlatformViewsController = std::make_shared<flutter::PlatformViewsController>();
488 flutterPlatformViewsController->SetTaskRunner(thread_task_runner);
489 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
491 mock_delegate.settings_.enable_impeller
494 flutterPlatformViewsController,
497 std::make_shared<fml::SyncSwitch>());
503 OCMStub([mockFlutterViewController view]).andReturn(flutterView);
504 std::string label =
"some label";
506 auto bridge = std::make_unique<flutter::AccessibilityBridge>(
507 mockFlutterViewController,
509 flutterPlatformViewsController);
511 flutter::SemanticsNodeUpdates nodes;
512 flutter::SemanticsNode parent;
514 parent.rect = SkRect::MakeXYWH(0, 0, 100, 200);
515 parent.label =
"label";
516 parent.value =
"value";
517 parent.hint =
"hint";
519 flutter::SemanticsNode node;
521 node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kHasImplicitScrolling);
522 node.actions = flutter::kHorizontalScrollSemanticsActions;
523 node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
524 node.label =
"label";
525 node.value =
"value";
527 node.scrollExtentMax = 100.0;
528 node.scrollPosition = 0.0;
529 parent.childrenInTraversalOrder.push_back(1);
530 parent.childrenInHitTestOrder.push_back(1);
533 flutter::CustomAccessibilityActionUpdates actions;
534 bridge->UpdateSemantics(nodes, actions);
535 XCTAssertTrue([flutterView.subviews count] == 1);
537 XCTAssertTrue([flutterView.subviews[0].accessibilityLabel isEqualToString:
@"label"]);
540 flutter::SemanticsNodeUpdates new_nodes;
541 flutter::SemanticsNode new_parent;
543 new_parent.rect = SkRect::MakeXYWH(0, 0, 100, 200);
544 new_parent.label =
"label";
545 new_parent.value =
"value";
546 new_parent.hint =
"hint";
547 new_nodes[0] = new_parent;
548 bridge->UpdateSemantics(new_nodes, actions);
550 XCTAssertTrue([flutterView.subviews count] == 0);
553 - (void)testBridgeReplacesSemanticsNode {
554 flutter::MockDelegate mock_delegate;
556 flutter::TaskRunners runners(
self.name.UTF8String,
562 auto flutterPlatformViewsController = std::make_shared<flutter::PlatformViewsController>();
563 flutterPlatformViewsController->SetTaskRunner(thread_task_runner);
564 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
566 mock_delegate.settings_.enable_impeller
569 flutterPlatformViewsController,
572 std::make_shared<fml::SyncSwitch>());
578 OCMStub([mockFlutterViewController view]).andReturn(flutterView);
579 std::string label =
"some label";
581 auto bridge = std::make_unique<flutter::AccessibilityBridge>(
582 mockFlutterViewController,
584 flutterPlatformViewsController);
586 flutter::SemanticsNodeUpdates nodes;
587 flutter::SemanticsNode parent;
589 parent.rect = SkRect::MakeXYWH(0, 0, 100, 200);
590 parent.label =
"label";
591 parent.value =
"value";
592 parent.hint =
"hint";
594 flutter::SemanticsNode node;
596 node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kHasImplicitScrolling);
597 node.actions = flutter::kHorizontalScrollSemanticsActions;
598 node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
599 node.label =
"label";
600 node.value =
"value";
602 node.scrollExtentMax = 100.0;
603 node.scrollPosition = 0.0;
604 parent.childrenInTraversalOrder.push_back(1);
605 parent.childrenInHitTestOrder.push_back(1);
608 flutter::CustomAccessibilityActionUpdates actions;
609 bridge->UpdateSemantics(nodes, actions);
610 XCTAssertTrue([flutterView.subviews count] == 1);
612 XCTAssertTrue([flutterView.subviews[0].accessibilityLabel isEqualToString:
@"label"]);
615 flutter::SemanticsNodeUpdates new_nodes;
616 flutter::SemanticsNode new_node;
618 new_node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
619 new_node.label =
"label";
620 new_node.value =
"value";
621 new_node.hint =
"hint";
622 new_node.scrollExtentMax = 100.0;
623 new_node.scrollPosition = 0.0;
624 new_nodes[1] = new_node;
625 bridge->UpdateSemantics(new_nodes, actions);
627 XCTAssertTrue([flutterView.subviews count] == 0);
630 - (void)testAnnouncesRouteChanges {
631 flutter::MockDelegate mock_delegate;
633 flutter::TaskRunners runners(
self.name.UTF8String,
638 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
640 mock_delegate.settings_.enable_impeller
646 std::make_shared<fml::SyncSwitch>());
647 id mockFlutterView = OCMClassMock([
FlutterView class]);
649 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
651 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
652 [[NSMutableArray alloc] init];
653 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
654 ios_delegate->on_PostAccessibilityNotification_ =
655 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
656 [accessibility_notifications addObject:@{
657 @"notification" : @(notification),
658 @"argument" : argument ? argument : [NSNull null],
661 __block
auto bridge =
662 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
665 std::move(ios_delegate));
667 flutter::CustomAccessibilityActionUpdates actions;
668 flutter::SemanticsNodeUpdates nodes;
670 flutter::SemanticsNode node1;
672 node1.label =
"node1";
673 node1.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute);
674 node1.childrenInTraversalOrder = {2, 3};
675 node1.childrenInHitTestOrder = {2, 3};
676 nodes[node1.id] = node1;
677 flutter::SemanticsNode node2;
679 node2.label =
"node2";
680 nodes[node2.id] = node2;
681 flutter::SemanticsNode node3;
683 node3.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kNamesRoute);
684 node3.label =
"node3";
685 nodes[node3.id] = node3;
686 flutter::SemanticsNode root_node;
688 root_node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute);
689 root_node.childrenInTraversalOrder = {1};
690 root_node.childrenInHitTestOrder = {1};
691 nodes[root_node.id] = root_node;
692 bridge->UpdateSemantics(nodes, actions);
694 XCTAssertEqual([accessibility_notifications count], 1ul);
695 XCTAssertEqualObjects(accessibility_notifications[0][
@"argument"],
@"node3");
696 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
697 UIAccessibilityScreenChangedNotification);
700 - (void)testRadioButtonIsNotSwitchButton {
701 flutter::MockDelegate mock_delegate;
703 flutter::TaskRunners runners(
self.name.UTF8String,
708 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
710 mock_delegate.settings_.enable_impeller
716 std::make_shared<fml::SyncSwitch>());
722 OCMStub([mockFlutterViewController view]).andReturn(flutterView);
723 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
724 __block
auto bridge =
725 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
728 std::move(ios_delegate));
730 flutter::CustomAccessibilityActionUpdates actions;
731 flutter::SemanticsNodeUpdates nodes;
733 flutter::SemanticsNode root_node;
735 root_node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kIsInMutuallyExclusiveGroup) |
736 static_cast<int32_t
>(flutter::SemanticsFlags::kIsEnabled) |
737 static_cast<int32_t
>(flutter::SemanticsFlags::kHasCheckedState) |
738 static_cast<int32_t
>(flutter::SemanticsFlags::kHasEnabledState);
739 nodes[root_node.id] = root_node;
740 bridge->UpdateSemantics(nodes, actions);
745 XCTAssertTrue((rootNode.accessibilityTraits & UIAccessibilityTraitButton) > 0);
746 XCTAssertNil(rootNode.accessibilityValue);
749 - (void)testLayoutChangeWithNonAccessibilityElement {
750 flutter::MockDelegate mock_delegate;
752 flutter::TaskRunners runners(
self.name.UTF8String,
757 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
759 mock_delegate.settings_.enable_impeller
765 std::make_shared<fml::SyncSwitch>());
766 id mockFlutterView = OCMClassMock([
FlutterView class]);
768 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
770 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
771 [[NSMutableArray alloc] init];
772 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
773 ios_delegate->on_PostAccessibilityNotification_ =
774 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
775 [accessibility_notifications addObject:@{
776 @"notification" : @(notification),
777 @"argument" : argument ? argument : [NSNull null],
780 __block
auto bridge =
781 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
784 std::move(ios_delegate));
786 flutter::CustomAccessibilityActionUpdates actions;
787 flutter::SemanticsNodeUpdates nodes;
789 flutter::SemanticsNode node1;
791 node1.label =
"node1";
792 node1.childrenInTraversalOrder = {2, 3};
793 node1.childrenInHitTestOrder = {2, 3};
794 nodes[node1.id] = node1;
795 flutter::SemanticsNode node2;
797 node2.label =
"node2";
798 nodes[node2.id] = node2;
799 flutter::SemanticsNode node3;
801 node3.label =
"node3";
802 nodes[node3.id] = node3;
803 flutter::SemanticsNode root_node;
805 root_node.label =
"root";
806 root_node.childrenInTraversalOrder = {1};
807 root_node.childrenInHitTestOrder = {1};
808 nodes[root_node.id] = root_node;
809 bridge->UpdateSemantics(nodes, actions);
812 bridge->AccessibilityObjectDidBecomeFocused(1);
817 flutter::CustomAccessibilityActionUpdates new_actions;
818 flutter::SemanticsNodeUpdates new_nodes;
820 flutter::SemanticsNode new_node1;
822 new_node1.childrenInTraversalOrder = {2};
823 new_node1.childrenInHitTestOrder = {2};
824 new_nodes[new_node1.id] = new_node1;
825 bridge->UpdateSemantics(new_nodes, new_actions);
827 XCTAssertEqual([accessibility_notifications count], 1ul);
828 SemanticsObject* focusObject = accessibility_notifications[0][@"argument"];
830 XCTAssertEqual([focusObject uid], 2);
831 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
832 UIAccessibilityLayoutChangedNotification);
835 - (void)testLayoutChangeDoesCallNativeAccessibility {
836 flutter::MockDelegate mock_delegate;
838 flutter::TaskRunners runners(
self.name.UTF8String,
843 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
845 mock_delegate.settings_.enable_impeller
851 std::make_shared<fml::SyncSwitch>());
852 id mockFlutterView = OCMClassMock([
FlutterView class]);
854 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
856 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
857 [[NSMutableArray alloc] init];
858 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
859 ios_delegate->on_PostAccessibilityNotification_ =
860 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
861 [accessibility_notifications addObject:@{
862 @"notification" : @(notification),
863 @"argument" : argument ? argument : [NSNull null],
866 __block
auto bridge =
867 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
870 std::move(ios_delegate));
872 flutter::CustomAccessibilityActionUpdates actions;
873 flutter::SemanticsNodeUpdates nodes;
875 flutter::SemanticsNode node1;
877 node1.label =
"node1";
878 nodes[node1.id] = node1;
879 flutter::SemanticsNode root_node;
881 root_node.label =
"root";
882 root_node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kHasImplicitScrolling);
883 root_node.childrenInTraversalOrder = {1};
884 root_node.childrenInHitTestOrder = {1};
885 nodes[root_node.id] = root_node;
886 bridge->UpdateSemantics(nodes, actions);
889 bridge->AccessibilityObjectDidBecomeFocused(0);
892 flutter::CustomAccessibilityActionUpdates new_actions;
893 flutter::SemanticsNodeUpdates new_nodes;
895 flutter::SemanticsNode new_root_node;
897 new_root_node.label =
"root";
898 new_root_node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kHasImplicitScrolling);
899 new_nodes[new_root_node.id] = new_root_node;
900 bridge->UpdateSemantics(new_nodes, new_actions);
902 XCTAssertEqual([accessibility_notifications count], 1ul);
903 id focusObject = accessibility_notifications[0][@"argument"];
907 XCTAssertEqualObjects(focusObject, [NSNull
null]);
908 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
909 UIAccessibilityLayoutChangedNotification);
912 - (void)testLayoutChangeDoesCallNativeAccessibilityWhenFocusChanged {
913 flutter::MockDelegate mock_delegate;
915 flutter::TaskRunners runners(
self.name.UTF8String,
920 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
922 mock_delegate.settings_.enable_impeller
928 std::make_shared<fml::SyncSwitch>());
929 id mockFlutterView = OCMClassMock([
FlutterView class]);
931 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
933 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
934 [[NSMutableArray alloc] init];
935 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
936 ios_delegate->on_PostAccessibilityNotification_ =
937 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
938 [accessibility_notifications addObject:@{
939 @"notification" : @(notification),
940 @"argument" : argument ? argument : [NSNull null],
943 __block
auto bridge =
944 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
947 std::move(ios_delegate));
949 flutter::CustomAccessibilityActionUpdates actions;
950 flutter::SemanticsNodeUpdates nodes;
952 flutter::SemanticsNode node1;
954 node1.label =
"node1";
955 nodes[node1.id] = node1;
956 flutter::SemanticsNode root_node;
958 root_node.label =
"root";
959 root_node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kHasImplicitScrolling);
960 root_node.childrenInTraversalOrder = {1};
961 root_node.childrenInHitTestOrder = {1};
962 nodes[root_node.id] = root_node;
963 bridge->UpdateSemantics(nodes, actions);
966 bridge->AccessibilityObjectDidBecomeFocused(1);
969 flutter::CustomAccessibilityActionUpdates new_actions;
970 flutter::SemanticsNodeUpdates new_nodes;
972 flutter::SemanticsNode new_root_node;
974 new_root_node.label =
"root";
975 new_root_node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kHasImplicitScrolling);
976 new_nodes[new_root_node.id] = new_root_node;
977 bridge->UpdateSemantics(new_nodes, new_actions);
979 XCTAssertEqual([accessibility_notifications count], 1ul);
980 SemanticsObject* focusObject2 = accessibility_notifications[0][@"argument"];
984 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
985 UIAccessibilityLayoutChangedNotification);
988 - (void)testScrollableSemanticsContainerReturnsCorrectChildren {
989 flutter::MockDelegate mock_delegate;
991 flutter::TaskRunners runners(
self.name.UTF8String,
996 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
998 mock_delegate.settings_.enable_impeller
1004 std::make_shared<fml::SyncSwitch>());
1005 id mockFlutterView = OCMClassMock([
FlutterView class]);
1007 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1009 OCMExpect([mockFlutterView
1010 setAccessibilityElements:[OCMArg checkWithBlock:^BOOL(NSArray* value) {
1011 if ([value count] != 1) {
1020 return [scrollableContainer indexOfAccessibilityElement:nativeScrollable] == 1;
1022 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1023 __block
auto bridge =
1024 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
1027 std::move(ios_delegate));
1029 flutter::CustomAccessibilityActionUpdates actions;
1030 flutter::SemanticsNodeUpdates nodes;
1032 flutter::SemanticsNode node1;
1034 node1.label =
"node1";
1035 node1.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kHasImplicitScrolling);
1036 nodes[node1.id] = node1;
1037 flutter::SemanticsNode root_node;
1039 root_node.label =
"root";
1040 root_node.childrenInTraversalOrder = {1};
1041 root_node.childrenInHitTestOrder = {1};
1042 nodes[root_node.id] = root_node;
1043 bridge->UpdateSemantics(nodes, actions);
1044 OCMVerifyAll(mockFlutterView);
1047 - (void)testAnnouncesRouteChangesAndLayoutChangeInOneUpdate {
1048 flutter::MockDelegate mock_delegate;
1050 flutter::TaskRunners runners(
self.name.UTF8String,
1054 thread_task_runner);
1055 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1057 mock_delegate.settings_.enable_impeller
1063 std::make_shared<fml::SyncSwitch>());
1064 id mockFlutterView = OCMClassMock([
FlutterView class]);
1066 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1068 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1069 [[NSMutableArray alloc] init];
1070 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1071 ios_delegate->on_PostAccessibilityNotification_ =
1072 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
1073 [accessibility_notifications addObject:@{
1074 @"notification" : @(notification),
1075 @"argument" : argument ? argument : [NSNull null],
1078 __block
auto bridge =
1079 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
1082 std::move(ios_delegate));
1084 flutter::CustomAccessibilityActionUpdates actions;
1085 flutter::SemanticsNodeUpdates nodes;
1087 flutter::SemanticsNode node1;
1089 node1.label =
"node1";
1090 node1.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute) |
1091 static_cast<int32_t
>(flutter::SemanticsFlags::kNamesRoute);
1092 nodes[node1.id] = node1;
1093 flutter::SemanticsNode node3;
1095 node3.label =
"node3";
1096 nodes[node3.id] = node3;
1097 flutter::SemanticsNode root_node;
1099 root_node.label =
"root";
1100 root_node.childrenInTraversalOrder = {1, 3};
1101 root_node.childrenInHitTestOrder = {1, 3};
1102 nodes[root_node.id] = root_node;
1103 bridge->UpdateSemantics(nodes, actions);
1105 XCTAssertEqual([accessibility_notifications count], 1ul);
1106 XCTAssertEqualObjects(accessibility_notifications[0][
@"argument"],
@"node1");
1107 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
1108 UIAccessibilityScreenChangedNotification);
1111 bridge->AccessibilityObjectDidBecomeFocused(0);
1113 flutter::SemanticsNodeUpdates new_nodes;
1115 flutter::SemanticsNode new_node1;
1117 new_node1.label =
"new_node1";
1118 new_node1.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute) |
1119 static_cast<int32_t
>(flutter::SemanticsFlags::kNamesRoute);
1120 new_node1.childrenInTraversalOrder = {2};
1121 new_node1.childrenInHitTestOrder = {2};
1122 new_nodes[new_node1.id] = new_node1;
1123 flutter::SemanticsNode new_node2;
1125 new_node2.label =
"new_node2";
1126 new_node2.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute) |
1127 static_cast<int32_t
>(flutter::SemanticsFlags::kNamesRoute);
1128 new_nodes[new_node2.id] = new_node2;
1129 flutter::SemanticsNode new_root_node;
1131 new_root_node.label =
"root";
1132 new_root_node.childrenInTraversalOrder = {1};
1133 new_root_node.childrenInHitTestOrder = {1};
1134 new_nodes[new_root_node.id] = new_root_node;
1135 bridge->UpdateSemantics(new_nodes, actions);
1136 XCTAssertEqual([accessibility_notifications count], 3ul);
1137 XCTAssertEqualObjects(accessibility_notifications[1][
@"argument"],
@"new_node2");
1138 XCTAssertEqual([accessibility_notifications[1][
@"notification"] unsignedIntValue],
1139 UIAccessibilityScreenChangedNotification);
1140 SemanticsObject* focusObject = accessibility_notifications[2][@"argument"];
1141 XCTAssertEqual([focusObject uid], 0);
1142 XCTAssertEqual([accessibility_notifications[2][
@"notification"] unsignedIntValue],
1143 UIAccessibilityLayoutChangedNotification);
1146 - (void)testAnnouncesRouteChangesWhenAddAdditionalRoute {
1147 flutter::MockDelegate mock_delegate;
1149 flutter::TaskRunners runners(
self.name.UTF8String,
1153 thread_task_runner);
1154 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1156 mock_delegate.settings_.enable_impeller
1162 std::make_shared<fml::SyncSwitch>());
1163 id mockFlutterView = OCMClassMock([
FlutterView class]);
1165 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1167 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1168 [[NSMutableArray alloc] init];
1169 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1170 ios_delegate->on_PostAccessibilityNotification_ =
1171 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
1172 [accessibility_notifications addObject:@{
1173 @"notification" : @(notification),
1174 @"argument" : argument ? argument : [NSNull null],
1177 __block
auto bridge =
1178 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
1181 std::move(ios_delegate));
1183 flutter::CustomAccessibilityActionUpdates actions;
1184 flutter::SemanticsNodeUpdates nodes;
1186 flutter::SemanticsNode node1;
1188 node1.label =
"node1";
1189 node1.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute) |
1190 static_cast<int32_t
>(flutter::SemanticsFlags::kNamesRoute);
1191 nodes[node1.id] = node1;
1192 flutter::SemanticsNode root_node;
1194 root_node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute);
1195 root_node.childrenInTraversalOrder = {1};
1196 root_node.childrenInHitTestOrder = {1};
1197 nodes[root_node.id] = root_node;
1198 bridge->UpdateSemantics(nodes, actions);
1200 XCTAssertEqual([accessibility_notifications count], 1ul);
1201 XCTAssertEqualObjects(accessibility_notifications[0][
@"argument"],
@"node1");
1202 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
1203 UIAccessibilityScreenChangedNotification);
1205 flutter::SemanticsNodeUpdates new_nodes;
1207 flutter::SemanticsNode new_node1;
1209 new_node1.label =
"new_node1";
1210 new_node1.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute) |
1211 static_cast<int32_t
>(flutter::SemanticsFlags::kNamesRoute);
1212 new_node1.childrenInTraversalOrder = {2};
1213 new_node1.childrenInHitTestOrder = {2};
1214 new_nodes[new_node1.id] = new_node1;
1215 flutter::SemanticsNode new_node2;
1217 new_node2.label =
"new_node2";
1218 new_node2.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute) |
1219 static_cast<int32_t
>(flutter::SemanticsFlags::kNamesRoute);
1220 new_nodes[new_node2.id] = new_node2;
1221 flutter::SemanticsNode new_root_node;
1223 new_root_node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute);
1224 new_root_node.childrenInTraversalOrder = {1};
1225 new_root_node.childrenInHitTestOrder = {1};
1226 new_nodes[new_root_node.id] = new_root_node;
1227 bridge->UpdateSemantics(new_nodes, actions);
1228 XCTAssertEqual([accessibility_notifications count], 2ul);
1229 XCTAssertEqualObjects(accessibility_notifications[1][
@"argument"],
@"new_node2");
1230 XCTAssertEqual([accessibility_notifications[1][
@"notification"] unsignedIntValue],
1231 UIAccessibilityScreenChangedNotification);
1234 - (void)testAnnouncesRouteChangesRemoveRouteInMiddle {
1235 flutter::MockDelegate mock_delegate;
1237 flutter::TaskRunners runners(
self.name.UTF8String,
1241 thread_task_runner);
1242 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1244 mock_delegate.settings_.enable_impeller
1250 std::make_shared<fml::SyncSwitch>());
1251 id mockFlutterView = OCMClassMock([
FlutterView class]);
1253 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1255 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1256 [[NSMutableArray alloc] init];
1257 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1258 ios_delegate->on_PostAccessibilityNotification_ =
1259 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
1260 [accessibility_notifications addObject:@{
1261 @"notification" : @(notification),
1262 @"argument" : argument ? argument : [NSNull null],
1265 __block
auto bridge =
1266 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
1269 std::move(ios_delegate));
1271 flutter::CustomAccessibilityActionUpdates actions;
1272 flutter::SemanticsNodeUpdates nodes;
1274 flutter::SemanticsNode node1;
1276 node1.label =
"node1";
1277 node1.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute) |
1278 static_cast<int32_t
>(flutter::SemanticsFlags::kNamesRoute);
1279 node1.childrenInTraversalOrder = {2};
1280 node1.childrenInHitTestOrder = {2};
1281 nodes[node1.id] = node1;
1282 flutter::SemanticsNode node2;
1284 node2.label =
"node2";
1285 node2.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute) |
1286 static_cast<int32_t
>(flutter::SemanticsFlags::kNamesRoute);
1287 nodes[node2.id] = node2;
1288 flutter::SemanticsNode root_node;
1290 root_node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute);
1291 root_node.childrenInTraversalOrder = {1};
1292 root_node.childrenInHitTestOrder = {1};
1293 nodes[root_node.id] = root_node;
1294 bridge->UpdateSemantics(nodes, actions);
1296 XCTAssertEqual([accessibility_notifications count], 1ul);
1297 XCTAssertEqualObjects(accessibility_notifications[0][
@"argument"],
@"node2");
1298 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
1299 UIAccessibilityScreenChangedNotification);
1301 flutter::SemanticsNodeUpdates new_nodes;
1303 flutter::SemanticsNode new_node1;
1305 new_node1.label =
"new_node1";
1306 new_node1.childrenInTraversalOrder = {2};
1307 new_node1.childrenInHitTestOrder = {2};
1308 new_nodes[new_node1.id] = new_node1;
1309 flutter::SemanticsNode new_node2;
1311 new_node2.label =
"new_node2";
1312 new_node2.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute) |
1313 static_cast<int32_t
>(flutter::SemanticsFlags::kNamesRoute);
1314 new_nodes[new_node2.id] = new_node2;
1315 flutter::SemanticsNode new_root_node;
1317 new_root_node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute);
1318 new_root_node.childrenInTraversalOrder = {1};
1319 new_root_node.childrenInHitTestOrder = {1};
1320 new_nodes[new_root_node.id] = new_root_node;
1321 bridge->UpdateSemantics(new_nodes, actions);
1322 XCTAssertEqual([accessibility_notifications count], 2ul);
1323 XCTAssertEqualObjects(accessibility_notifications[1][
@"argument"],
@"new_node2");
1324 XCTAssertEqual([accessibility_notifications[1][
@"notification"] unsignedIntValue],
1325 UIAccessibilityScreenChangedNotification);
1328 - (void)testHandleEvent {
1329 flutter::MockDelegate mock_delegate;
1331 flutter::TaskRunners runners(
self.name.UTF8String,
1335 thread_task_runner);
1336 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1338 mock_delegate.settings_.enable_impeller
1344 std::make_shared<fml::SyncSwitch>());
1345 id mockFlutterView = OCMClassMock([
FlutterView class]);
1347 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1349 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1350 [[NSMutableArray alloc] init];
1351 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1352 ios_delegate->on_PostAccessibilityNotification_ =
1353 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
1354 [accessibility_notifications addObject:@{
1355 @"notification" : @(notification),
1356 @"argument" : argument ? argument : [NSNull null],
1359 __block
auto bridge =
1360 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
1363 std::move(ios_delegate));
1365 NSDictionary<NSString*, id>* annotatedEvent = @{
@"type" :
@"focus",
@"nodeId" : @123};
1367 bridge->HandleEvent(annotatedEvent);
1369 XCTAssertEqual([accessibility_notifications count], 1ul);
1370 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
1371 UIAccessibilityLayoutChangedNotification);
1374 - (void)testAccessibilityObjectDidBecomeFocused {
1375 flutter::MockDelegate mock_delegate;
1376 auto thread = std::make_unique<fml::Thread>(
"AccessibilityBridgeTest");
1377 auto thread_task_runner = thread->GetTaskRunner();
1378 flutter::TaskRunners runners(
self.name.UTF8String,
1382 thread_task_runner);
1387 OCMStub([flutterViewController
engine]).andReturn(
engine);
1388 OCMStub([
engine binaryMessenger]).andReturn(messenger);
1390 OCMStub([messenger setMessageHandlerOnChannel:
@"flutter/accessibility"
1391 binaryMessageHandler:[OCMArg any]])
1392 .andReturn(connection);
1394 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1396 mock_delegate.settings_.enable_impeller
1402 std::make_shared<fml::SyncSwitch>());
1403 fml::AutoResetWaitableEvent latch;
1404 thread_task_runner->PostTask([&] {
1406 std::make_unique<fml::WeakNSObjectFactory<FlutterViewController>>(flutterViewController);
1407 platform_view->SetOwnerViewController(weakFactory->GetWeakNSObject());
1409 std::make_unique<flutter::AccessibilityBridge>(nil,
1412 XCTAssertTrue(bridge.get());
1413 OCMVerify([messenger setMessageHandlerOnChannel:
@"flutter/accessibility"
1414 binaryMessageHandler:[OCMArg isNotNil]]);
1416 bridge->AccessibilityObjectDidBecomeFocused(123);
1418 NSDictionary<NSString*, id>* annotatedEvent = @{
@"type" :
@"didGainFocus",
@"nodeId" : @123};
1421 OCMVerify([messenger sendOnChannel:
@"flutter/accessibility" message:encodedMessage]);
1426 [engine stopMocking];
1429 - (void)testAnnouncesRouteChangesWhenNoNamesRoute {
1430 flutter::MockDelegate mock_delegate;
1432 flutter::TaskRunners runners(
self.name.UTF8String,
1436 thread_task_runner);
1437 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1439 mock_delegate.settings_.enable_impeller
1445 std::make_shared<fml::SyncSwitch>());
1446 id mockFlutterView = OCMClassMock([
FlutterView class]);
1448 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1450 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1451 [[NSMutableArray alloc] init];
1452 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1453 ios_delegate->on_PostAccessibilityNotification_ =
1454 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
1455 [accessibility_notifications addObject:@{
1456 @"notification" : @(notification),
1457 @"argument" : argument ? argument : [NSNull null],
1460 __block
auto bridge =
1461 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
1464 std::move(ios_delegate));
1466 flutter::CustomAccessibilityActionUpdates actions;
1467 flutter::SemanticsNodeUpdates nodes;
1469 flutter::SemanticsNode node1;
1471 node1.label =
"node1";
1472 node1.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute) |
1473 static_cast<int32_t
>(flutter::SemanticsFlags::kNamesRoute);
1474 node1.childrenInTraversalOrder = {2, 3};
1475 node1.childrenInHitTestOrder = {2, 3};
1476 nodes[node1.id] = node1;
1477 flutter::SemanticsNode node2;
1479 node2.label =
"node2";
1480 nodes[node2.id] = node2;
1481 flutter::SemanticsNode node3;
1483 node3.label =
"node3";
1484 nodes[node3.id] = node3;
1485 flutter::SemanticsNode root_node;
1487 root_node.childrenInTraversalOrder = {1};
1488 root_node.childrenInHitTestOrder = {1};
1489 nodes[root_node.id] = root_node;
1490 bridge->UpdateSemantics(nodes, actions);
1493 XCTAssertEqual([accessibility_notifications count], 1ul);
1494 id focusObject = accessibility_notifications[0][@"argument"];
1495 XCTAssertTrue([focusObject isKindOfClass:[NSString
class]]);
1496 XCTAssertEqualObjects(focusObject,
@"node1");
1497 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
1498 UIAccessibilityScreenChangedNotification);
1501 - (void)testAnnouncesLayoutChangeWithNilIfLastFocusIsRemoved {
1502 flutter::MockDelegate mock_delegate;
1504 flutter::TaskRunners runners(
self.name.UTF8String,
1508 thread_task_runner);
1509 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1511 mock_delegate.settings_.enable_impeller
1517 std::make_shared<fml::SyncSwitch>());
1519 id mockFlutterView = OCMClassMock([
FlutterView class]);
1520 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1522 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1523 [[NSMutableArray alloc] init];
1524 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1525 ios_delegate->on_PostAccessibilityNotification_ =
1526 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
1527 [accessibility_notifications addObject:@{
1528 @"notification" : @(notification),
1529 @"argument" : argument ? argument : [NSNull null],
1532 __block
auto bridge =
1533 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
1536 std::move(ios_delegate));
1538 flutter::CustomAccessibilityActionUpdates actions;
1539 flutter::SemanticsNodeUpdates first_update;
1541 flutter::SemanticsNode route_node;
1543 route_node.label =
"route";
1544 first_update[route_node.id] = route_node;
1545 flutter::SemanticsNode root_node;
1547 root_node.label =
"root";
1548 root_node.childrenInTraversalOrder = {1};
1549 root_node.childrenInHitTestOrder = {1};
1550 first_update[root_node.id] = root_node;
1551 bridge->UpdateSemantics(first_update, actions);
1553 XCTAssertEqual([accessibility_notifications count], 0ul);
1555 bridge->AccessibilityObjectDidBecomeFocused(1);
1557 flutter::SemanticsNodeUpdates second_update;
1559 flutter::SemanticsNode new_root_node;
1561 new_root_node.label =
"root";
1562 second_update[root_node.id] = new_root_node;
1563 bridge->UpdateSemantics(second_update, actions);
1564 SemanticsObject* focusObject = accessibility_notifications[0][@"argument"];
1566 XCTAssertEqual([focusObject uid], 0);
1567 XCTAssertEqualObjects([focusObject accessibilityLabel],
@"root");
1568 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
1569 UIAccessibilityLayoutChangedNotification);
1572 - (void)testAnnouncesLayoutChangeWithTheSameItemFocused {
1573 flutter::MockDelegate mock_delegate;
1575 flutter::TaskRunners runners(
self.name.UTF8String,
1579 thread_task_runner);
1580 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1582 mock_delegate.settings_.enable_impeller
1588 std::make_shared<fml::SyncSwitch>());
1590 id mockFlutterView = OCMClassMock([
FlutterView class]);
1591 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1593 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1594 [[NSMutableArray alloc] init];
1595 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1596 ios_delegate->on_PostAccessibilityNotification_ =
1597 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
1598 [accessibility_notifications addObject:@{
1599 @"notification" : @(notification),
1600 @"argument" : argument ? argument : [NSNull null],
1603 __block
auto bridge =
1604 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
1607 std::move(ios_delegate));
1609 flutter::CustomAccessibilityActionUpdates actions;
1610 flutter::SemanticsNodeUpdates first_update;
1612 flutter::SemanticsNode node_one;
1614 node_one.label =
"route1";
1615 first_update[node_one.id] = node_one;
1616 flutter::SemanticsNode node_two;
1618 node_two.label =
"route2";
1619 first_update[node_two.id] = node_two;
1620 flutter::SemanticsNode root_node;
1622 root_node.label =
"root";
1623 root_node.childrenInTraversalOrder = {1, 2};
1624 root_node.childrenInHitTestOrder = {1, 2};
1625 first_update[root_node.id] = root_node;
1626 bridge->UpdateSemantics(first_update, actions);
1628 XCTAssertEqual([accessibility_notifications count], 0ul);
1630 bridge->AccessibilityObjectDidBecomeFocused(1);
1632 flutter::SemanticsNodeUpdates second_update;
1634 flutter::SemanticsNode new_root_node;
1636 new_root_node.label =
"root";
1637 new_root_node.childrenInTraversalOrder = {1};
1638 new_root_node.childrenInHitTestOrder = {1};
1639 second_update[root_node.id] = new_root_node;
1640 bridge->UpdateSemantics(second_update, actions);
1641 id focusObject = accessibility_notifications[0][@"argument"];
1644 XCTAssertEqualObjects(focusObject, [NSNull
null]);
1645 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
1646 UIAccessibilityLayoutChangedNotification);
1649 - (void)testAnnouncesLayoutChangeWhenFocusMovedOutside {
1650 flutter::MockDelegate mock_delegate;
1652 flutter::TaskRunners runners(
self.name.UTF8String,
1656 thread_task_runner);
1657 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1659 mock_delegate.settings_.enable_impeller
1665 std::make_shared<fml::SyncSwitch>());
1667 id mockFlutterView = OCMClassMock([
FlutterView class]);
1668 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1670 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1671 [[NSMutableArray alloc] init];
1672 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1673 ios_delegate->on_PostAccessibilityNotification_ =
1674 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
1675 [accessibility_notifications addObject:@{
1676 @"notification" : @(notification),
1677 @"argument" : argument ? argument : [NSNull null],
1680 __block
auto bridge =
1681 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
1684 std::move(ios_delegate));
1686 flutter::CustomAccessibilityActionUpdates actions;
1687 flutter::SemanticsNodeUpdates first_update;
1689 flutter::SemanticsNode node_one;
1691 node_one.label =
"route1";
1692 first_update[node_one.id] = node_one;
1693 flutter::SemanticsNode node_two;
1695 node_two.label =
"route2";
1696 first_update[node_two.id] = node_two;
1697 flutter::SemanticsNode root_node;
1699 root_node.label =
"root";
1700 root_node.childrenInTraversalOrder = {1, 2};
1701 root_node.childrenInHitTestOrder = {1, 2};
1702 first_update[root_node.id] = root_node;
1703 bridge->UpdateSemantics(first_update, actions);
1705 XCTAssertEqual([accessibility_notifications count], 0ul);
1707 bridge->AccessibilityObjectDidBecomeFocused(1);
1709 bridge->AccessibilityObjectDidLoseFocus(1);
1711 flutter::SemanticsNodeUpdates second_update;
1713 flutter::SemanticsNode new_root_node;
1715 new_root_node.label =
"root";
1716 new_root_node.childrenInTraversalOrder = {1};
1717 new_root_node.childrenInHitTestOrder = {1};
1718 second_update[root_node.id] = new_root_node;
1719 bridge->UpdateSemantics(second_update, actions);
1720 NSNull* focusObject = accessibility_notifications[0][@"argument"];
1723 XCTAssertEqual(focusObject, [NSNull
null]);
1724 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
1725 UIAccessibilityLayoutChangedNotification);
1728 - (void)testAnnouncesScrollChangeWithLastFocused {
1729 flutter::MockDelegate mock_delegate;
1731 flutter::TaskRunners runners(
self.name.UTF8String,
1735 thread_task_runner);
1736 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1738 mock_delegate.settings_.enable_impeller
1744 std::make_shared<fml::SyncSwitch>());
1746 id mockFlutterView = OCMClassMock([
FlutterView class]);
1747 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1749 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1750 [[NSMutableArray alloc] init];
1751 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1752 ios_delegate->on_PostAccessibilityNotification_ =
1753 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
1754 [accessibility_notifications addObject:@{
1755 @"notification" : @(notification),
1756 @"argument" : argument ? argument : [NSNull null],
1759 __block
auto bridge =
1760 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
1763 std::move(ios_delegate));
1765 flutter::CustomAccessibilityActionUpdates actions;
1766 flutter::SemanticsNodeUpdates first_update;
1768 flutter::SemanticsNode node_one;
1770 node_one.label =
"route1";
1771 node_one.scrollPosition = 0.0;
1772 first_update[node_one.id] = node_one;
1773 flutter::SemanticsNode root_node;
1775 root_node.label =
"root";
1776 root_node.childrenInTraversalOrder = {1};
1777 root_node.childrenInHitTestOrder = {1};
1778 first_update[root_node.id] = root_node;
1779 bridge->UpdateSemantics(first_update, actions);
1782 [accessibility_notifications removeAllObjects];
1785 bridge->AccessibilityObjectDidBecomeFocused(1);
1787 flutter::SemanticsNodeUpdates second_update;
1789 flutter::SemanticsNode new_node_one;
1790 new_node_one.id = 1;
1791 new_node_one.label =
"route1";
1792 new_node_one.scrollPosition = 1.0;
1793 second_update[new_node_one.id] = new_node_one;
1794 bridge->UpdateSemantics(second_update, actions);
1795 SemanticsObject* focusObject = accessibility_notifications[0][@"argument"];
1798 XCTAssertEqual([focusObject uid], 1);
1799 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
1800 UIAccessibilityPageScrolledNotification);
1803 - (void)testAnnouncesScrollChangeDoesCallNativeAccessibility {
1804 flutter::MockDelegate mock_delegate;
1806 flutter::TaskRunners runners(
self.name.UTF8String,
1810 thread_task_runner);
1811 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1813 mock_delegate.settings_.enable_impeller
1819 std::make_shared<fml::SyncSwitch>());
1821 id mockFlutterView = OCMClassMock([
FlutterView class]);
1822 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1824 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1825 [[NSMutableArray alloc] init];
1826 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1827 ios_delegate->on_PostAccessibilityNotification_ =
1828 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
1829 [accessibility_notifications addObject:@{
1830 @"notification" : @(notification),
1831 @"argument" : argument ? argument : [NSNull null],
1834 __block
auto bridge =
1835 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
1838 std::move(ios_delegate));
1840 flutter::CustomAccessibilityActionUpdates actions;
1841 flutter::SemanticsNodeUpdates first_update;
1843 flutter::SemanticsNode node_one;
1845 node_one.label =
"route1";
1846 node_one.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kHasImplicitScrolling);
1847 node_one.scrollPosition = 0.0;
1848 first_update[node_one.id] = node_one;
1849 flutter::SemanticsNode root_node;
1851 root_node.label =
"root";
1852 root_node.childrenInTraversalOrder = {1};
1853 root_node.childrenInHitTestOrder = {1};
1854 first_update[root_node.id] = root_node;
1855 bridge->UpdateSemantics(first_update, actions);
1858 [accessibility_notifications removeAllObjects];
1861 bridge->AccessibilityObjectDidBecomeFocused(1);
1863 flutter::SemanticsNodeUpdates second_update;
1865 flutter::SemanticsNode new_node_one;
1866 new_node_one.id = 1;
1867 new_node_one.label =
"route1";
1868 new_node_one.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kHasImplicitScrolling);
1869 new_node_one.scrollPosition = 1.0;
1870 second_update[new_node_one.id] = new_node_one;
1871 bridge->UpdateSemantics(second_update, actions);
1872 SemanticsObject* focusObject = accessibility_notifications[0][@"argument"];
1876 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
1877 UIAccessibilityPageScrolledNotification);
1880 - (void)testAnnouncesIgnoresRouteChangesWhenModal {
1881 flutter::MockDelegate mock_delegate;
1883 flutter::TaskRunners runners(
self.name.UTF8String,
1887 thread_task_runner);
1888 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1890 mock_delegate.settings_.enable_impeller
1896 std::make_shared<fml::SyncSwitch>());
1897 id mockFlutterView = OCMClassMock([
FlutterView class]);
1899 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1900 std::string label =
"some label";
1902 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1903 [[NSMutableArray alloc] init];
1904 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1905 ios_delegate->on_PostAccessibilityNotification_ =
1906 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
1907 [accessibility_notifications addObject:@{
1908 @"notification" : @(notification),
1909 @"argument" : argument ? argument : [NSNull null],
1912 ios_delegate->result_IsFlutterViewControllerPresentingModalViewController_ =
true;
1913 __block
auto bridge =
1914 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
1917 std::move(ios_delegate));
1919 flutter::CustomAccessibilityActionUpdates actions;
1920 flutter::SemanticsNodeUpdates nodes;
1922 flutter::SemanticsNode route_node;
1924 route_node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute) |
1925 static_cast<int32_t
>(flutter::SemanticsFlags::kNamesRoute);
1926 route_node.label =
"route";
1927 nodes[route_node.id] = route_node;
1928 flutter::SemanticsNode root_node;
1930 root_node.label = label;
1931 root_node.childrenInTraversalOrder = {1};
1932 root_node.childrenInHitTestOrder = {1};
1933 nodes[root_node.id] = root_node;
1934 bridge->UpdateSemantics(nodes, actions);
1936 XCTAssertEqual([accessibility_notifications count], 0ul);
1939 - (void)testAnnouncesIgnoresLayoutChangeWhenModal {
1940 flutter::MockDelegate mock_delegate;
1942 flutter::TaskRunners runners(
self.name.UTF8String,
1946 thread_task_runner);
1947 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1949 mock_delegate.settings_.enable_impeller
1955 std::make_shared<fml::SyncSwitch>());
1956 id mockFlutterView = OCMClassMock([
FlutterView class]);
1958 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1960 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1961 [[NSMutableArray alloc] init];
1962 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1963 ios_delegate->on_PostAccessibilityNotification_ =
1964 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
1965 [accessibility_notifications addObject:@{
1966 @"notification" : @(notification),
1967 @"argument" : argument ? argument : [NSNull null],
1970 ios_delegate->result_IsFlutterViewControllerPresentingModalViewController_ =
true;
1971 __block
auto bridge =
1972 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
1975 std::move(ios_delegate));
1977 flutter::CustomAccessibilityActionUpdates actions;
1978 flutter::SemanticsNodeUpdates nodes;
1980 flutter::SemanticsNode child_node;
1982 child_node.label =
"child_node";
1983 nodes[child_node.id] = child_node;
1984 flutter::SemanticsNode root_node;
1986 root_node.label =
"root";
1987 root_node.childrenInTraversalOrder = {1};
1988 root_node.childrenInHitTestOrder = {1};
1989 nodes[root_node.id] = root_node;
1990 bridge->UpdateSemantics(nodes, actions);
1993 flutter::SemanticsNodeUpdates new_nodes;
1994 flutter::SemanticsNode new_root_node;
1996 new_root_node.label =
"root";
1997 new_nodes[new_root_node.id] = new_root_node;
1998 bridge->UpdateSemantics(new_nodes, actions);
2000 XCTAssertEqual([accessibility_notifications count], 0ul);
2003 - (void)testAnnouncesIgnoresScrollChangeWhenModal {
2004 flutter::MockDelegate mock_delegate;
2006 flutter::TaskRunners runners(
self.name.UTF8String,
2010 thread_task_runner);
2011 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2013 mock_delegate.settings_.enable_impeller
2019 std::make_shared<fml::SyncSwitch>());
2020 id mockFlutterView = OCMClassMock([
FlutterView class]);
2022 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
2024 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
2025 [[NSMutableArray alloc] init];
2026 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
2027 ios_delegate->on_PostAccessibilityNotification_ =
2028 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
2029 [accessibility_notifications addObject:@{
2030 @"notification" : @(notification),
2031 @"argument" : argument ? argument : [NSNull null],
2034 ios_delegate->result_IsFlutterViewControllerPresentingModalViewController_ =
true;
2035 __block
auto bridge =
2036 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
2039 std::move(ios_delegate));
2041 flutter::CustomAccessibilityActionUpdates actions;
2042 flutter::SemanticsNodeUpdates nodes;
2044 flutter::SemanticsNode root_node;
2046 root_node.label =
"root";
2047 root_node.scrollPosition = 1;
2048 nodes[root_node.id] = root_node;
2049 bridge->UpdateSemantics(nodes, actions);
2052 flutter::SemanticsNodeUpdates new_nodes;
2053 flutter::SemanticsNode new_root_node;
2055 new_root_node.label =
"root";
2056 new_root_node.scrollPosition = 2;
2057 new_nodes[new_root_node.id] = new_root_node;
2058 bridge->UpdateSemantics(new_nodes, actions);
2060 XCTAssertEqual([accessibility_notifications count], 0ul);
2063 - (void)testAccessibilityMessageAfterDeletion {
2064 flutter::MockDelegate mock_delegate;
2065 auto thread = std::make_unique<fml::Thread>(
"AccessibilityBridgeTest");
2066 auto thread_task_runner = thread->GetTaskRunner();
2067 flutter::TaskRunners runners(
self.name.UTF8String,
2071 thread_task_runner);
2076 OCMStub([flutterViewController
engine]).andReturn(
engine);
2077 OCMStub([
engine binaryMessenger]).andReturn(messenger);
2079 OCMStub([messenger setMessageHandlerOnChannel:
@"flutter/accessibility"
2080 binaryMessageHandler:[OCMArg any]])
2081 .andReturn(connection);
2083 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2085 mock_delegate.settings_.enable_impeller
2091 std::make_shared<fml::SyncSwitch>());
2092 fml::AutoResetWaitableEvent latch;
2093 thread_task_runner->PostTask([&] {
2095 std::make_unique<fml::WeakNSObjectFactory<FlutterViewController>>(flutterViewController);
2096 platform_view->SetOwnerViewController(weakFactory->GetWeakNSObject());
2098 std::make_unique<flutter::AccessibilityBridge>(nil,
2101 XCTAssertTrue(bridge.get());
2102 OCMVerify([messenger setMessageHandlerOnChannel:
@"flutter/accessibility"
2103 binaryMessageHandler:[OCMArg isNotNil]]);
2108 OCMVerify([messenger cleanUpConnection:connection]);
2109 [engine stopMocking];
2112 - (void)testFlutterSemanticsScrollViewManagedObjectLifecycleCorrectly {
2113 flutter::MockDelegate mock_delegate;
2115 flutter::TaskRunners runners(
self.name.UTF8String,
2119 thread_task_runner);
2120 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2122 mock_delegate.settings_.enable_impeller
2128 std::make_shared<fml::SyncSwitch>());
2129 id mockFlutterView = OCMClassMock([
FlutterView class]);
2131 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
2133 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
2134 __block
auto bridge =
2135 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
2138 std::move(ios_delegate));
2147 XCTAssertTrue(flutterSemanticsScrollView);
2150 XCTAssertFalse([flutterSemanticsScrollView isAccessibilityElement]);
2153 - (void)testPlatformViewDestructorDoesNotCallSemanticsAPIs {
2154 class TestDelegate :
public flutter::MockDelegate {
2156 void OnPlatformViewSetSemanticsEnabled(
bool enabled)
override { set_semantics_enabled_calls++; }
2157 int set_semantics_enabled_calls = 0;
2160 TestDelegate test_delegate;
2161 auto thread = std::make_unique<fml::Thread>(
"AccessibilityBridgeTest");
2162 auto thread_task_runner = thread->GetTaskRunner();
2163 flutter::TaskRunners runners(
self.name.UTF8String,
2167 thread_task_runner);
2169 fml::AutoResetWaitableEvent latch;
2170 thread_task_runner->PostTask([&] {
2171 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2173 test_delegate.settings_.enable_impeller
2179 std::make_shared<fml::SyncSwitch>());
2182 auto flutterPlatformViewsController = std::make_shared<flutter::PlatformViewsController>();
2183 flutterPlatformViewsController->SetTaskRunner(thread_task_runner);
2185 OCMStub([mockFlutterViewController platformViewsController])
2186 .andReturn(flutterPlatformViewsController.get());
2187 auto weakFactory = std::make_unique<fml::WeakNSObjectFactory<FlutterViewController>>(
2188 mockFlutterViewController);
2189 platform_view->SetOwnerViewController(weakFactory->GetWeakNSObject());
2192 XCTAssertNotEqual(test_delegate.set_semantics_enabled_calls, 0);
2195 test_delegate.set_semantics_enabled_calls = 0;
2197 XCTAssertEqual(test_delegate.set_semantics_enabled_calls, 0);