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,
284 flutterPlatformViewsController.
taskRunner = thread_task_runner;
285 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
287 mock_delegate.settings_.enable_impeller
290 flutterPlatformViewsController,
293 std::make_shared<fml::SyncSwitch>());
294 id mockFlutterView = OCMClassMock([
FlutterView class]);
296 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
297 std::string label =
"some label";
298 flutterPlatformViewsController.
flutterView = mockFlutterView;
301 [flutterPlatformViewsController
303 withId:@"MockFlutterPlatformView"
307 [flutterPlatformViewsController
311 @"viewType" : @"MockFlutterPlatformView"
315 auto bridge = std::make_unique<flutter::AccessibilityBridge>(
316 mockFlutterViewController,
318 flutterPlatformViewsController);
320 flutter::SemanticsNodeUpdates nodes;
321 flutter::SemanticsNode semantics_node;
322 semantics_node.id = 2;
323 semantics_node.platformViewId = 2;
324 semantics_node.label = label;
325 nodes[kRootNodeId] = semantics_node;
326 flutter::CustomAccessibilityActionUpdates actions;
327 bridge->UpdateSemantics(nodes, actions);
329 [flutterPlatformViewsController
reset];
334 - (void)testSemanticsDeallocatedWithoutLoadingView {
339 flutter::MockDelegate mock_delegate;
341 flutter::TaskRunners runners(
self.name.UTF8String,
349 flutterPlatformViewsController.
taskRunner = thread_task_runner;
350 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
352 mock_delegate.settings_.enable_impeller
355 flutterPlatformViewsController,
358 std::make_shared<fml::SyncSwitch>());
361 [flutterPlatformViewsController
363 withId:@"MockFlutterPlatformView"
367 [flutterPlatformViewsController
371 @"viewType" : @"MockFlutterPlatformView"
375 auto bridge = std::make_unique<flutter::AccessibilityBridge>(
376 flutterViewController,
378 flutterPlatformViewsController);
381 [flutterPlatformViewsController
reset];
385 XCTAssertNil(flutterViewController.viewIfLoaded);
386 [flutterViewController deregisterNotifications];
389 - (void)testReplacedSemanticsDoesNotCleanupChildren {
390 flutter::MockDelegate mock_delegate;
392 flutter::TaskRunners runners(
self.name.UTF8String,
400 flutterPlatformViewsController.
taskRunner = thread_task_runner;
401 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
403 mock_delegate.settings_.enable_impeller
406 flutterPlatformViewsController,
409 std::make_shared<fml::SyncSwitch>());
415 OCMStub([mockFlutterViewController view]).andReturn(flutterView);
416 std::string label =
"some label";
417 auto bridge = std::make_unique<flutter::AccessibilityBridge>(
418 mockFlutterViewController,
420 flutterPlatformViewsController);
422 flutter::SemanticsNodeUpdates nodes;
423 flutter::SemanticsNode parent;
425 parent.rect = SkRect::MakeXYWH(0, 0, 100, 200);
426 parent.label =
"label";
427 parent.value =
"value";
428 parent.hint =
"hint";
430 flutter::SemanticsNode node;
432 node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
433 node.label =
"label";
434 node.value =
"value";
436 node.scrollExtentMax = 100.0;
437 node.scrollPosition = 0.0;
438 parent.childrenInTraversalOrder.push_back(1);
439 parent.childrenInHitTestOrder.push_back(1);
441 flutter::SemanticsNode child;
443 child.rect = SkRect::MakeXYWH(0, 0, 100, 200);
444 child.label =
"label";
445 child.value =
"value";
447 node.childrenInTraversalOrder.push_back(2);
448 node.childrenInHitTestOrder.push_back(2);
453 flutter::CustomAccessibilityActionUpdates actions;
454 bridge->UpdateSemantics(nodes, actions);
457 flutter::SemanticsNodeUpdates new_nodes;
458 flutter::SemanticsNode new_node;
460 new_node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
461 new_node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kHasImplicitScrolling);
462 new_node.actions = flutter::kHorizontalScrollSemanticsActions;
463 new_node.label =
"label";
464 new_node.value =
"value";
465 new_node.hint =
"hint";
466 new_node.scrollExtentMax = 100.0;
467 new_node.scrollPosition = 0.0;
468 new_node.childrenInTraversalOrder.push_back(2);
469 new_node.childrenInHitTestOrder.push_back(2);
471 new_nodes[1] = new_node;
472 bridge->UpdateSemantics(new_nodes, actions);
476 id rootContainer = flutterView.accessibilityElements[0];
477 XCTAssertTrue([rootContainer accessibilityElementCount] ==
479 id scrollableContainer = [rootContainer accessibilityElementAtIndex:1];
480 XCTAssertTrue([scrollableContainer accessibilityElementCount] ==
482 id child = [scrollableContainer accessibilityElementAtIndex:1];
484 XCTAssertNotNil([child accessibilityContainer]);
487 - (void)testScrollableSemanticsDeallocated {
488 flutter::MockDelegate mock_delegate;
490 flutter::TaskRunners runners(
self.name.UTF8String,
498 flutterPlatformViewsController.
taskRunner = thread_task_runner;
499 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
501 mock_delegate.settings_.enable_impeller
504 flutterPlatformViewsController,
507 std::make_shared<fml::SyncSwitch>());
513 OCMStub([mockFlutterViewController view]).andReturn(flutterView);
514 std::string label =
"some label";
516 auto bridge = std::make_unique<flutter::AccessibilityBridge>(
517 mockFlutterViewController,
519 flutterPlatformViewsController);
521 flutter::SemanticsNodeUpdates nodes;
522 flutter::SemanticsNode parent;
524 parent.rect = SkRect::MakeXYWH(0, 0, 100, 200);
525 parent.label =
"label";
526 parent.value =
"value";
527 parent.hint =
"hint";
529 flutter::SemanticsNode node;
531 node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kHasImplicitScrolling);
532 node.actions = flutter::kHorizontalScrollSemanticsActions;
533 node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
534 node.label =
"label";
535 node.value =
"value";
537 node.scrollExtentMax = 100.0;
538 node.scrollPosition = 0.0;
539 parent.childrenInTraversalOrder.push_back(1);
540 parent.childrenInHitTestOrder.push_back(1);
543 flutter::CustomAccessibilityActionUpdates actions;
544 bridge->UpdateSemantics(nodes, actions);
545 XCTAssertTrue([flutterView.subviews count] == 1);
547 XCTAssertTrue([flutterView.subviews[0].accessibilityLabel isEqualToString:
@"label"]);
550 flutter::SemanticsNodeUpdates new_nodes;
551 flutter::SemanticsNode new_parent;
553 new_parent.rect = SkRect::MakeXYWH(0, 0, 100, 200);
554 new_parent.label =
"label";
555 new_parent.value =
"value";
556 new_parent.hint =
"hint";
557 new_nodes[0] = new_parent;
558 bridge->UpdateSemantics(new_nodes, actions);
560 XCTAssertTrue([flutterView.subviews count] == 0);
563 - (void)testBridgeReplacesSemanticsNode {
564 flutter::MockDelegate mock_delegate;
566 flutter::TaskRunners runners(
self.name.UTF8String,
574 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
576 mock_delegate.settings_.enable_impeller
579 flutterPlatformViewsController,
582 std::make_shared<fml::SyncSwitch>());
588 OCMStub([mockFlutterViewController view]).andReturn(flutterView);
589 std::string label =
"some label";
591 auto bridge = std::make_unique<flutter::AccessibilityBridge>(
592 mockFlutterViewController,
594 flutterPlatformViewsController);
596 flutter::SemanticsNodeUpdates nodes;
597 flutter::SemanticsNode parent;
599 parent.rect = SkRect::MakeXYWH(0, 0, 100, 200);
600 parent.label =
"label";
601 parent.value =
"value";
602 parent.hint =
"hint";
604 flutter::SemanticsNode node;
606 node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kHasImplicitScrolling);
607 node.actions = flutter::kHorizontalScrollSemanticsActions;
608 node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
609 node.label =
"label";
610 node.value =
"value";
612 node.scrollExtentMax = 100.0;
613 node.scrollPosition = 0.0;
614 parent.childrenInTraversalOrder.push_back(1);
615 parent.childrenInHitTestOrder.push_back(1);
618 flutter::CustomAccessibilityActionUpdates actions;
619 bridge->UpdateSemantics(nodes, actions);
620 XCTAssertTrue([flutterView.subviews count] == 1);
622 XCTAssertTrue([flutterView.subviews[0].accessibilityLabel isEqualToString:
@"label"]);
625 flutter::SemanticsNodeUpdates new_nodes;
626 flutter::SemanticsNode new_node;
628 new_node.rect = SkRect::MakeXYWH(0, 0, 100, 200);
629 new_node.label =
"label";
630 new_node.value =
"value";
631 new_node.hint =
"hint";
632 new_node.scrollExtentMax = 100.0;
633 new_node.scrollPosition = 0.0;
634 new_nodes[1] = new_node;
635 bridge->UpdateSemantics(new_nodes, actions);
637 XCTAssertTrue([flutterView.subviews count] == 0);
640 - (void)testAnnouncesRouteChanges {
641 flutter::MockDelegate mock_delegate;
643 flutter::TaskRunners runners(
self.name.UTF8String,
648 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
650 mock_delegate.settings_.enable_impeller
656 std::make_shared<fml::SyncSwitch>());
657 id mockFlutterView = OCMClassMock([
FlutterView class]);
659 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
661 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
662 [[NSMutableArray alloc] init];
663 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
664 ios_delegate->on_PostAccessibilityNotification_ =
665 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
666 [accessibility_notifications addObject:@{
667 @"notification" : @(notification),
668 @"argument" : argument ? argument : [NSNull null],
671 __block
auto bridge =
672 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
675 std::move(ios_delegate));
677 flutter::CustomAccessibilityActionUpdates actions;
678 flutter::SemanticsNodeUpdates nodes;
680 flutter::SemanticsNode node1;
682 node1.label =
"node1";
683 node1.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute);
684 node1.childrenInTraversalOrder = {2, 3};
685 node1.childrenInHitTestOrder = {2, 3};
686 nodes[node1.id] = node1;
687 flutter::SemanticsNode node2;
689 node2.label =
"node2";
690 nodes[node2.id] = node2;
691 flutter::SemanticsNode node3;
693 node3.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kNamesRoute);
694 node3.label =
"node3";
695 nodes[node3.id] = node3;
696 flutter::SemanticsNode root_node;
698 root_node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute);
699 root_node.childrenInTraversalOrder = {1};
700 root_node.childrenInHitTestOrder = {1};
701 nodes[root_node.id] = root_node;
702 bridge->UpdateSemantics(nodes, actions);
704 XCTAssertEqual([accessibility_notifications count], 1ul);
705 XCTAssertEqualObjects(accessibility_notifications[0][
@"argument"],
@"node3");
706 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
707 UIAccessibilityScreenChangedNotification);
710 - (void)testRadioButtonIsNotSwitchButton {
711 flutter::MockDelegate mock_delegate;
713 flutter::TaskRunners runners(
self.name.UTF8String,
718 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
720 mock_delegate.settings_.enable_impeller
726 std::make_shared<fml::SyncSwitch>());
732 OCMStub([mockFlutterViewController view]).andReturn(flutterView);
733 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
734 __block
auto bridge =
735 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
738 std::move(ios_delegate));
740 flutter::CustomAccessibilityActionUpdates actions;
741 flutter::SemanticsNodeUpdates nodes;
743 flutter::SemanticsNode root_node;
745 root_node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kIsInMutuallyExclusiveGroup) |
746 static_cast<int32_t
>(flutter::SemanticsFlags::kIsEnabled) |
747 static_cast<int32_t
>(flutter::SemanticsFlags::kHasCheckedState) |
748 static_cast<int32_t
>(flutter::SemanticsFlags::kHasEnabledState);
749 nodes[root_node.id] = root_node;
750 bridge->UpdateSemantics(nodes, actions);
755 XCTAssertTrue((rootNode.accessibilityTraits & UIAccessibilityTraitButton) > 0);
756 XCTAssertNil(rootNode.accessibilityValue);
759 - (void)testLayoutChangeWithNonAccessibilityElement {
760 flutter::MockDelegate mock_delegate;
762 flutter::TaskRunners runners(
self.name.UTF8String,
767 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
769 mock_delegate.settings_.enable_impeller
775 std::make_shared<fml::SyncSwitch>());
776 id mockFlutterView = OCMClassMock([
FlutterView class]);
778 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
780 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
781 [[NSMutableArray alloc] init];
782 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
783 ios_delegate->on_PostAccessibilityNotification_ =
784 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
785 [accessibility_notifications addObject:@{
786 @"notification" : @(notification),
787 @"argument" : argument ? argument : [NSNull null],
790 __block
auto bridge =
791 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
794 std::move(ios_delegate));
796 flutter::CustomAccessibilityActionUpdates actions;
797 flutter::SemanticsNodeUpdates nodes;
799 flutter::SemanticsNode node1;
801 node1.label =
"node1";
802 node1.childrenInTraversalOrder = {2, 3};
803 node1.childrenInHitTestOrder = {2, 3};
804 nodes[node1.id] = node1;
805 flutter::SemanticsNode node2;
807 node2.label =
"node2";
808 nodes[node2.id] = node2;
809 flutter::SemanticsNode node3;
811 node3.label =
"node3";
812 nodes[node3.id] = node3;
813 flutter::SemanticsNode root_node;
815 root_node.label =
"root";
816 root_node.childrenInTraversalOrder = {1};
817 root_node.childrenInHitTestOrder = {1};
818 nodes[root_node.id] = root_node;
819 bridge->UpdateSemantics(nodes, actions);
822 bridge->AccessibilityObjectDidBecomeFocused(1);
827 flutter::CustomAccessibilityActionUpdates new_actions;
828 flutter::SemanticsNodeUpdates new_nodes;
830 flutter::SemanticsNode new_node1;
832 new_node1.childrenInTraversalOrder = {2};
833 new_node1.childrenInHitTestOrder = {2};
834 new_nodes[new_node1.id] = new_node1;
835 bridge->UpdateSemantics(new_nodes, new_actions);
837 XCTAssertEqual([accessibility_notifications count], 1ul);
838 SemanticsObject* focusObject = accessibility_notifications[0][@"argument"];
840 XCTAssertEqual([focusObject uid], 2);
841 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
842 UIAccessibilityLayoutChangedNotification);
845 - (void)testLayoutChangeDoesCallNativeAccessibility {
846 flutter::MockDelegate mock_delegate;
848 flutter::TaskRunners runners(
self.name.UTF8String,
853 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
855 mock_delegate.settings_.enable_impeller
861 std::make_shared<fml::SyncSwitch>());
862 id mockFlutterView = OCMClassMock([
FlutterView class]);
864 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
866 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
867 [[NSMutableArray alloc] init];
868 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
869 ios_delegate->on_PostAccessibilityNotification_ =
870 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
871 [accessibility_notifications addObject:@{
872 @"notification" : @(notification),
873 @"argument" : argument ? argument : [NSNull null],
876 __block
auto bridge =
877 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
880 std::move(ios_delegate));
882 flutter::CustomAccessibilityActionUpdates actions;
883 flutter::SemanticsNodeUpdates nodes;
885 flutter::SemanticsNode node1;
887 node1.label =
"node1";
888 nodes[node1.id] = node1;
889 flutter::SemanticsNode root_node;
891 root_node.label =
"root";
892 root_node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kHasImplicitScrolling);
893 root_node.childrenInTraversalOrder = {1};
894 root_node.childrenInHitTestOrder = {1};
895 nodes[root_node.id] = root_node;
896 bridge->UpdateSemantics(nodes, actions);
899 bridge->AccessibilityObjectDidBecomeFocused(0);
902 flutter::CustomAccessibilityActionUpdates new_actions;
903 flutter::SemanticsNodeUpdates new_nodes;
905 flutter::SemanticsNode new_root_node;
907 new_root_node.label =
"root";
908 new_root_node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kHasImplicitScrolling);
909 new_nodes[new_root_node.id] = new_root_node;
910 bridge->UpdateSemantics(new_nodes, new_actions);
912 XCTAssertEqual([accessibility_notifications count], 1ul);
913 id focusObject = accessibility_notifications[0][@"argument"];
917 XCTAssertEqualObjects(focusObject, [NSNull
null]);
918 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
919 UIAccessibilityLayoutChangedNotification);
922 - (void)testLayoutChangeDoesCallNativeAccessibilityWhenFocusChanged {
923 flutter::MockDelegate mock_delegate;
925 flutter::TaskRunners runners(
self.name.UTF8String,
930 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
932 mock_delegate.settings_.enable_impeller
938 std::make_shared<fml::SyncSwitch>());
939 id mockFlutterView = OCMClassMock([
FlutterView class]);
941 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
943 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
944 [[NSMutableArray alloc] init];
945 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
946 ios_delegate->on_PostAccessibilityNotification_ =
947 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
948 [accessibility_notifications addObject:@{
949 @"notification" : @(notification),
950 @"argument" : argument ? argument : [NSNull null],
953 __block
auto bridge =
954 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
957 std::move(ios_delegate));
959 flutter::CustomAccessibilityActionUpdates actions;
960 flutter::SemanticsNodeUpdates nodes;
962 flutter::SemanticsNode node1;
964 node1.label =
"node1";
965 nodes[node1.id] = node1;
966 flutter::SemanticsNode root_node;
968 root_node.label =
"root";
969 root_node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kHasImplicitScrolling);
970 root_node.childrenInTraversalOrder = {1};
971 root_node.childrenInHitTestOrder = {1};
972 nodes[root_node.id] = root_node;
973 bridge->UpdateSemantics(nodes, actions);
976 bridge->AccessibilityObjectDidBecomeFocused(1);
979 flutter::CustomAccessibilityActionUpdates new_actions;
980 flutter::SemanticsNodeUpdates new_nodes;
982 flutter::SemanticsNode new_root_node;
984 new_root_node.label =
"root";
985 new_root_node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kHasImplicitScrolling);
986 new_nodes[new_root_node.id] = new_root_node;
987 bridge->UpdateSemantics(new_nodes, new_actions);
989 XCTAssertEqual([accessibility_notifications count], 1ul);
990 SemanticsObject* focusObject2 = accessibility_notifications[0][@"argument"];
994 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
995 UIAccessibilityLayoutChangedNotification);
998 - (void)testScrollableSemanticsContainerReturnsCorrectChildren {
999 flutter::MockDelegate mock_delegate;
1001 flutter::TaskRunners runners(
self.name.UTF8String,
1005 thread_task_runner);
1006 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1008 mock_delegate.settings_.enable_impeller
1014 std::make_shared<fml::SyncSwitch>());
1015 id mockFlutterView = OCMClassMock([
FlutterView class]);
1017 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1019 OCMExpect([mockFlutterView
1020 setAccessibilityElements:[OCMArg checkWithBlock:^BOOL(NSArray* value) {
1021 if ([value count] != 1) {
1030 return [scrollableContainer indexOfAccessibilityElement:nativeScrollable] == 1;
1032 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1033 __block
auto bridge =
1034 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
1037 std::move(ios_delegate));
1039 flutter::CustomAccessibilityActionUpdates actions;
1040 flutter::SemanticsNodeUpdates nodes;
1042 flutter::SemanticsNode node1;
1044 node1.label =
"node1";
1045 node1.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kHasImplicitScrolling);
1046 nodes[node1.id] = node1;
1047 flutter::SemanticsNode root_node;
1049 root_node.label =
"root";
1050 root_node.childrenInTraversalOrder = {1};
1051 root_node.childrenInHitTestOrder = {1};
1052 nodes[root_node.id] = root_node;
1053 bridge->UpdateSemantics(nodes, actions);
1054 OCMVerifyAll(mockFlutterView);
1057 - (void)testAnnouncesRouteChangesAndLayoutChangeInOneUpdate {
1058 flutter::MockDelegate mock_delegate;
1060 flutter::TaskRunners runners(
self.name.UTF8String,
1064 thread_task_runner);
1065 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1067 mock_delegate.settings_.enable_impeller
1073 std::make_shared<fml::SyncSwitch>());
1074 id mockFlutterView = OCMClassMock([
FlutterView class]);
1076 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1078 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1079 [[NSMutableArray alloc] init];
1080 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1081 ios_delegate->on_PostAccessibilityNotification_ =
1082 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
1083 [accessibility_notifications addObject:@{
1084 @"notification" : @(notification),
1085 @"argument" : argument ? argument : [NSNull null],
1088 __block
auto bridge =
1089 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
1092 std::move(ios_delegate));
1094 flutter::CustomAccessibilityActionUpdates actions;
1095 flutter::SemanticsNodeUpdates nodes;
1097 flutter::SemanticsNode node1;
1099 node1.label =
"node1";
1100 node1.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute) |
1101 static_cast<int32_t
>(flutter::SemanticsFlags::kNamesRoute);
1102 nodes[node1.id] = node1;
1103 flutter::SemanticsNode node3;
1105 node3.label =
"node3";
1106 nodes[node3.id] = node3;
1107 flutter::SemanticsNode root_node;
1109 root_node.label =
"root";
1110 root_node.childrenInTraversalOrder = {1, 3};
1111 root_node.childrenInHitTestOrder = {1, 3};
1112 nodes[root_node.id] = root_node;
1113 bridge->UpdateSemantics(nodes, actions);
1115 XCTAssertEqual([accessibility_notifications count], 1ul);
1116 XCTAssertEqualObjects(accessibility_notifications[0][
@"argument"],
@"node1");
1117 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
1118 UIAccessibilityScreenChangedNotification);
1121 bridge->AccessibilityObjectDidBecomeFocused(0);
1123 flutter::SemanticsNodeUpdates new_nodes;
1125 flutter::SemanticsNode new_node1;
1127 new_node1.label =
"new_node1";
1128 new_node1.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute) |
1129 static_cast<int32_t
>(flutter::SemanticsFlags::kNamesRoute);
1130 new_node1.childrenInTraversalOrder = {2};
1131 new_node1.childrenInHitTestOrder = {2};
1132 new_nodes[new_node1.id] = new_node1;
1133 flutter::SemanticsNode new_node2;
1135 new_node2.label =
"new_node2";
1136 new_node2.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute) |
1137 static_cast<int32_t
>(flutter::SemanticsFlags::kNamesRoute);
1138 new_nodes[new_node2.id] = new_node2;
1139 flutter::SemanticsNode new_root_node;
1141 new_root_node.label =
"root";
1142 new_root_node.childrenInTraversalOrder = {1};
1143 new_root_node.childrenInHitTestOrder = {1};
1144 new_nodes[new_root_node.id] = new_root_node;
1145 bridge->UpdateSemantics(new_nodes, actions);
1146 XCTAssertEqual([accessibility_notifications count], 3ul);
1147 XCTAssertEqualObjects(accessibility_notifications[1][
@"argument"],
@"new_node2");
1148 XCTAssertEqual([accessibility_notifications[1][
@"notification"] unsignedIntValue],
1149 UIAccessibilityScreenChangedNotification);
1150 SemanticsObject* focusObject = accessibility_notifications[2][@"argument"];
1151 XCTAssertEqual([focusObject uid], 0);
1152 XCTAssertEqual([accessibility_notifications[2][
@"notification"] unsignedIntValue],
1153 UIAccessibilityLayoutChangedNotification);
1156 - (void)testAnnouncesRouteChangesWhenAddAdditionalRoute {
1157 flutter::MockDelegate mock_delegate;
1159 flutter::TaskRunners runners(
self.name.UTF8String,
1163 thread_task_runner);
1164 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1166 mock_delegate.settings_.enable_impeller
1172 std::make_shared<fml::SyncSwitch>());
1173 id mockFlutterView = OCMClassMock([
FlutterView class]);
1175 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1177 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1178 [[NSMutableArray alloc] init];
1179 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1180 ios_delegate->on_PostAccessibilityNotification_ =
1181 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
1182 [accessibility_notifications addObject:@{
1183 @"notification" : @(notification),
1184 @"argument" : argument ? argument : [NSNull null],
1187 __block
auto bridge =
1188 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
1191 std::move(ios_delegate));
1193 flutter::CustomAccessibilityActionUpdates actions;
1194 flutter::SemanticsNodeUpdates nodes;
1196 flutter::SemanticsNode node1;
1198 node1.label =
"node1";
1199 node1.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute) |
1200 static_cast<int32_t
>(flutter::SemanticsFlags::kNamesRoute);
1201 nodes[node1.id] = node1;
1202 flutter::SemanticsNode root_node;
1204 root_node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute);
1205 root_node.childrenInTraversalOrder = {1};
1206 root_node.childrenInHitTestOrder = {1};
1207 nodes[root_node.id] = root_node;
1208 bridge->UpdateSemantics(nodes, actions);
1210 XCTAssertEqual([accessibility_notifications count], 1ul);
1211 XCTAssertEqualObjects(accessibility_notifications[0][
@"argument"],
@"node1");
1212 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
1213 UIAccessibilityScreenChangedNotification);
1215 flutter::SemanticsNodeUpdates new_nodes;
1217 flutter::SemanticsNode new_node1;
1219 new_node1.label =
"new_node1";
1220 new_node1.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute) |
1221 static_cast<int32_t
>(flutter::SemanticsFlags::kNamesRoute);
1222 new_node1.childrenInTraversalOrder = {2};
1223 new_node1.childrenInHitTestOrder = {2};
1224 new_nodes[new_node1.id] = new_node1;
1225 flutter::SemanticsNode new_node2;
1227 new_node2.label =
"new_node2";
1228 new_node2.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute) |
1229 static_cast<int32_t
>(flutter::SemanticsFlags::kNamesRoute);
1230 new_nodes[new_node2.id] = new_node2;
1231 flutter::SemanticsNode new_root_node;
1233 new_root_node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute);
1234 new_root_node.childrenInTraversalOrder = {1};
1235 new_root_node.childrenInHitTestOrder = {1};
1236 new_nodes[new_root_node.id] = new_root_node;
1237 bridge->UpdateSemantics(new_nodes, actions);
1238 XCTAssertEqual([accessibility_notifications count], 2ul);
1239 XCTAssertEqualObjects(accessibility_notifications[1][
@"argument"],
@"new_node2");
1240 XCTAssertEqual([accessibility_notifications[1][
@"notification"] unsignedIntValue],
1241 UIAccessibilityScreenChangedNotification);
1244 - (void)testAnnouncesRouteChangesRemoveRouteInMiddle {
1245 flutter::MockDelegate mock_delegate;
1247 flutter::TaskRunners runners(
self.name.UTF8String,
1251 thread_task_runner);
1252 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1254 mock_delegate.settings_.enable_impeller
1260 std::make_shared<fml::SyncSwitch>());
1261 id mockFlutterView = OCMClassMock([
FlutterView class]);
1263 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1265 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1266 [[NSMutableArray alloc] init];
1267 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1268 ios_delegate->on_PostAccessibilityNotification_ =
1269 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
1270 [accessibility_notifications addObject:@{
1271 @"notification" : @(notification),
1272 @"argument" : argument ? argument : [NSNull null],
1275 __block
auto bridge =
1276 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
1279 std::move(ios_delegate));
1281 flutter::CustomAccessibilityActionUpdates actions;
1282 flutter::SemanticsNodeUpdates nodes;
1284 flutter::SemanticsNode node1;
1286 node1.label =
"node1";
1287 node1.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute) |
1288 static_cast<int32_t
>(flutter::SemanticsFlags::kNamesRoute);
1289 node1.childrenInTraversalOrder = {2};
1290 node1.childrenInHitTestOrder = {2};
1291 nodes[node1.id] = node1;
1292 flutter::SemanticsNode node2;
1294 node2.label =
"node2";
1295 node2.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute) |
1296 static_cast<int32_t
>(flutter::SemanticsFlags::kNamesRoute);
1297 nodes[node2.id] = node2;
1298 flutter::SemanticsNode root_node;
1300 root_node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute);
1301 root_node.childrenInTraversalOrder = {1};
1302 root_node.childrenInHitTestOrder = {1};
1303 nodes[root_node.id] = root_node;
1304 bridge->UpdateSemantics(nodes, actions);
1306 XCTAssertEqual([accessibility_notifications count], 1ul);
1307 XCTAssertEqualObjects(accessibility_notifications[0][
@"argument"],
@"node2");
1308 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
1309 UIAccessibilityScreenChangedNotification);
1311 flutter::SemanticsNodeUpdates new_nodes;
1313 flutter::SemanticsNode new_node1;
1315 new_node1.label =
"new_node1";
1316 new_node1.childrenInTraversalOrder = {2};
1317 new_node1.childrenInHitTestOrder = {2};
1318 new_nodes[new_node1.id] = new_node1;
1319 flutter::SemanticsNode new_node2;
1321 new_node2.label =
"new_node2";
1322 new_node2.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute) |
1323 static_cast<int32_t
>(flutter::SemanticsFlags::kNamesRoute);
1324 new_nodes[new_node2.id] = new_node2;
1325 flutter::SemanticsNode new_root_node;
1327 new_root_node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute);
1328 new_root_node.childrenInTraversalOrder = {1};
1329 new_root_node.childrenInHitTestOrder = {1};
1330 new_nodes[new_root_node.id] = new_root_node;
1331 bridge->UpdateSemantics(new_nodes, actions);
1332 XCTAssertEqual([accessibility_notifications count], 2ul);
1333 XCTAssertEqualObjects(accessibility_notifications[1][
@"argument"],
@"new_node2");
1334 XCTAssertEqual([accessibility_notifications[1][
@"notification"] unsignedIntValue],
1335 UIAccessibilityScreenChangedNotification);
1338 - (void)testHandleEvent {
1339 flutter::MockDelegate mock_delegate;
1341 flutter::TaskRunners runners(
self.name.UTF8String,
1345 thread_task_runner);
1346 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1348 mock_delegate.settings_.enable_impeller
1354 std::make_shared<fml::SyncSwitch>());
1355 id mockFlutterView = OCMClassMock([
FlutterView class]);
1357 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1359 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1360 [[NSMutableArray alloc] init];
1361 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1362 ios_delegate->on_PostAccessibilityNotification_ =
1363 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
1364 [accessibility_notifications addObject:@{
1365 @"notification" : @(notification),
1366 @"argument" : argument ? argument : [NSNull null],
1369 __block
auto bridge =
1370 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
1373 std::move(ios_delegate));
1375 NSDictionary<NSString*, id>* annotatedEvent = @{
@"type" :
@"focus",
@"nodeId" : @123};
1377 bridge->HandleEvent(annotatedEvent);
1379 XCTAssertEqual([accessibility_notifications count], 1ul);
1380 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
1381 UIAccessibilityLayoutChangedNotification);
1384 - (void)testAccessibilityObjectDidBecomeFocused {
1385 flutter::MockDelegate mock_delegate;
1386 auto thread = std::make_unique<fml::Thread>(
"AccessibilityBridgeTest");
1387 auto thread_task_runner = thread->GetTaskRunner();
1388 flutter::TaskRunners runners(
self.name.UTF8String,
1392 thread_task_runner);
1397 OCMStub([flutterViewController
engine]).andReturn(
engine);
1398 OCMStub([
engine binaryMessenger]).andReturn(messenger);
1400 OCMStub([messenger setMessageHandlerOnChannel:
@"flutter/accessibility"
1401 binaryMessageHandler:[OCMArg any]])
1402 .andReturn(connection);
1404 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1406 mock_delegate.settings_.enable_impeller
1412 std::make_shared<fml::SyncSwitch>());
1413 fml::AutoResetWaitableEvent latch;
1414 thread_task_runner->PostTask([&] {
1415 platform_view->SetOwnerViewController(flutterViewController);
1417 std::make_unique<flutter::AccessibilityBridge>(nil,
1420 XCTAssertTrue(bridge.get());
1421 OCMVerify([messenger setMessageHandlerOnChannel:
@"flutter/accessibility"
1422 binaryMessageHandler:[OCMArg isNotNil]]);
1424 bridge->AccessibilityObjectDidBecomeFocused(123);
1426 NSDictionary<NSString*, id>* annotatedEvent = @{
@"type" :
@"didGainFocus",
@"nodeId" : @123};
1429 OCMVerify([messenger sendOnChannel:
@"flutter/accessibility" message:encodedMessage]);
1434 [engine stopMocking];
1437 - (void)testAnnouncesRouteChangesWhenNoNamesRoute {
1438 flutter::MockDelegate mock_delegate;
1440 flutter::TaskRunners runners(
self.name.UTF8String,
1444 thread_task_runner);
1445 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1447 mock_delegate.settings_.enable_impeller
1453 std::make_shared<fml::SyncSwitch>());
1454 id mockFlutterView = OCMClassMock([
FlutterView class]);
1456 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1458 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1459 [[NSMutableArray alloc] init];
1460 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1461 ios_delegate->on_PostAccessibilityNotification_ =
1462 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
1463 [accessibility_notifications addObject:@{
1464 @"notification" : @(notification),
1465 @"argument" : argument ? argument : [NSNull null],
1468 __block
auto bridge =
1469 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
1472 std::move(ios_delegate));
1474 flutter::CustomAccessibilityActionUpdates actions;
1475 flutter::SemanticsNodeUpdates nodes;
1477 flutter::SemanticsNode node1;
1479 node1.label =
"node1";
1480 node1.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute) |
1481 static_cast<int32_t
>(flutter::SemanticsFlags::kNamesRoute);
1482 node1.childrenInTraversalOrder = {2, 3};
1483 node1.childrenInHitTestOrder = {2, 3};
1484 nodes[node1.id] = node1;
1485 flutter::SemanticsNode node2;
1487 node2.label =
"node2";
1488 nodes[node2.id] = node2;
1489 flutter::SemanticsNode node3;
1491 node3.label =
"node3";
1492 nodes[node3.id] = node3;
1493 flutter::SemanticsNode root_node;
1495 root_node.childrenInTraversalOrder = {1};
1496 root_node.childrenInHitTestOrder = {1};
1497 nodes[root_node.id] = root_node;
1498 bridge->UpdateSemantics(nodes, actions);
1501 XCTAssertEqual([accessibility_notifications count], 1ul);
1502 id focusObject = accessibility_notifications[0][@"argument"];
1503 XCTAssertTrue([focusObject isKindOfClass:[NSString
class]]);
1504 XCTAssertEqualObjects(focusObject,
@"node1");
1505 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
1506 UIAccessibilityScreenChangedNotification);
1509 - (void)testAnnouncesLayoutChangeWithNilIfLastFocusIsRemoved {
1510 flutter::MockDelegate mock_delegate;
1512 flutter::TaskRunners runners(
self.name.UTF8String,
1516 thread_task_runner);
1517 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1519 mock_delegate.settings_.enable_impeller
1525 std::make_shared<fml::SyncSwitch>());
1527 id mockFlutterView = OCMClassMock([
FlutterView class]);
1528 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1530 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1531 [[NSMutableArray alloc] init];
1532 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1533 ios_delegate->on_PostAccessibilityNotification_ =
1534 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
1535 [accessibility_notifications addObject:@{
1536 @"notification" : @(notification),
1537 @"argument" : argument ? argument : [NSNull null],
1540 __block
auto bridge =
1541 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
1544 std::move(ios_delegate));
1546 flutter::CustomAccessibilityActionUpdates actions;
1547 flutter::SemanticsNodeUpdates first_update;
1549 flutter::SemanticsNode route_node;
1551 route_node.label =
"route";
1552 first_update[route_node.id] = route_node;
1553 flutter::SemanticsNode root_node;
1555 root_node.label =
"root";
1556 root_node.childrenInTraversalOrder = {1};
1557 root_node.childrenInHitTestOrder = {1};
1558 first_update[root_node.id] = root_node;
1559 bridge->UpdateSemantics(first_update, actions);
1561 XCTAssertEqual([accessibility_notifications count], 0ul);
1563 bridge->AccessibilityObjectDidBecomeFocused(1);
1565 flutter::SemanticsNodeUpdates second_update;
1567 flutter::SemanticsNode new_root_node;
1569 new_root_node.label =
"root";
1570 second_update[root_node.id] = new_root_node;
1571 bridge->UpdateSemantics(second_update, actions);
1572 SemanticsObject* focusObject = accessibility_notifications[0][@"argument"];
1574 XCTAssertEqual([focusObject uid], 0);
1575 XCTAssertEqualObjects([focusObject accessibilityLabel],
@"root");
1576 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
1577 UIAccessibilityLayoutChangedNotification);
1580 - (void)testAnnouncesLayoutChangeWithTheSameItemFocused {
1581 flutter::MockDelegate mock_delegate;
1583 flutter::TaskRunners runners(
self.name.UTF8String,
1587 thread_task_runner);
1588 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1590 mock_delegate.settings_.enable_impeller
1596 std::make_shared<fml::SyncSwitch>());
1598 id mockFlutterView = OCMClassMock([
FlutterView class]);
1599 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1601 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1602 [[NSMutableArray alloc] init];
1603 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1604 ios_delegate->on_PostAccessibilityNotification_ =
1605 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
1606 [accessibility_notifications addObject:@{
1607 @"notification" : @(notification),
1608 @"argument" : argument ? argument : [NSNull null],
1611 __block
auto bridge =
1612 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
1615 std::move(ios_delegate));
1617 flutter::CustomAccessibilityActionUpdates actions;
1618 flutter::SemanticsNodeUpdates first_update;
1620 flutter::SemanticsNode node_one;
1622 node_one.label =
"route1";
1623 first_update[node_one.id] = node_one;
1624 flutter::SemanticsNode node_two;
1626 node_two.label =
"route2";
1627 first_update[node_two.id] = node_two;
1628 flutter::SemanticsNode root_node;
1630 root_node.label =
"root";
1631 root_node.childrenInTraversalOrder = {1, 2};
1632 root_node.childrenInHitTestOrder = {1, 2};
1633 first_update[root_node.id] = root_node;
1634 bridge->UpdateSemantics(first_update, actions);
1636 XCTAssertEqual([accessibility_notifications count], 0ul);
1638 bridge->AccessibilityObjectDidBecomeFocused(1);
1640 flutter::SemanticsNodeUpdates second_update;
1642 flutter::SemanticsNode new_root_node;
1644 new_root_node.label =
"root";
1645 new_root_node.childrenInTraversalOrder = {1};
1646 new_root_node.childrenInHitTestOrder = {1};
1647 second_update[root_node.id] = new_root_node;
1648 bridge->UpdateSemantics(second_update, actions);
1649 id focusObject = accessibility_notifications[0][@"argument"];
1652 XCTAssertEqualObjects(focusObject, [NSNull
null]);
1653 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
1654 UIAccessibilityLayoutChangedNotification);
1657 - (void)testAnnouncesLayoutChangeWhenFocusMovedOutside {
1658 flutter::MockDelegate mock_delegate;
1660 flutter::TaskRunners runners(
self.name.UTF8String,
1664 thread_task_runner);
1665 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1667 mock_delegate.settings_.enable_impeller
1673 std::make_shared<fml::SyncSwitch>());
1675 id mockFlutterView = OCMClassMock([
FlutterView class]);
1676 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1678 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1679 [[NSMutableArray alloc] init];
1680 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1681 ios_delegate->on_PostAccessibilityNotification_ =
1682 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
1683 [accessibility_notifications addObject:@{
1684 @"notification" : @(notification),
1685 @"argument" : argument ? argument : [NSNull null],
1688 __block
auto bridge =
1689 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
1692 std::move(ios_delegate));
1694 flutter::CustomAccessibilityActionUpdates actions;
1695 flutter::SemanticsNodeUpdates first_update;
1697 flutter::SemanticsNode node_one;
1699 node_one.label =
"route1";
1700 first_update[node_one.id] = node_one;
1701 flutter::SemanticsNode node_two;
1703 node_two.label =
"route2";
1704 first_update[node_two.id] = node_two;
1705 flutter::SemanticsNode root_node;
1707 root_node.label =
"root";
1708 root_node.childrenInTraversalOrder = {1, 2};
1709 root_node.childrenInHitTestOrder = {1, 2};
1710 first_update[root_node.id] = root_node;
1711 bridge->UpdateSemantics(first_update, actions);
1713 XCTAssertEqual([accessibility_notifications count], 0ul);
1715 bridge->AccessibilityObjectDidBecomeFocused(1);
1717 bridge->AccessibilityObjectDidLoseFocus(1);
1719 flutter::SemanticsNodeUpdates second_update;
1721 flutter::SemanticsNode new_root_node;
1723 new_root_node.label =
"root";
1724 new_root_node.childrenInTraversalOrder = {1};
1725 new_root_node.childrenInHitTestOrder = {1};
1726 second_update[root_node.id] = new_root_node;
1727 bridge->UpdateSemantics(second_update, actions);
1728 NSNull* focusObject = accessibility_notifications[0][@"argument"];
1731 XCTAssertEqual(focusObject, [NSNull
null]);
1732 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
1733 UIAccessibilityLayoutChangedNotification);
1736 - (void)testAnnouncesScrollChangeWithLastFocused {
1737 flutter::MockDelegate mock_delegate;
1739 flutter::TaskRunners runners(
self.name.UTF8String,
1743 thread_task_runner);
1744 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1746 mock_delegate.settings_.enable_impeller
1752 std::make_shared<fml::SyncSwitch>());
1754 id mockFlutterView = OCMClassMock([
FlutterView class]);
1755 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1757 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1758 [[NSMutableArray alloc] init];
1759 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1760 ios_delegate->on_PostAccessibilityNotification_ =
1761 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
1762 [accessibility_notifications addObject:@{
1763 @"notification" : @(notification),
1764 @"argument" : argument ? argument : [NSNull null],
1767 __block
auto bridge =
1768 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
1771 std::move(ios_delegate));
1773 flutter::CustomAccessibilityActionUpdates actions;
1774 flutter::SemanticsNodeUpdates first_update;
1776 flutter::SemanticsNode node_one;
1778 node_one.label =
"route1";
1779 node_one.scrollPosition = 0.0;
1780 first_update[node_one.id] = node_one;
1781 flutter::SemanticsNode root_node;
1783 root_node.label =
"root";
1784 root_node.childrenInTraversalOrder = {1};
1785 root_node.childrenInHitTestOrder = {1};
1786 first_update[root_node.id] = root_node;
1787 bridge->UpdateSemantics(first_update, actions);
1790 [accessibility_notifications removeAllObjects];
1793 bridge->AccessibilityObjectDidBecomeFocused(1);
1795 flutter::SemanticsNodeUpdates second_update;
1797 flutter::SemanticsNode new_node_one;
1798 new_node_one.id = 1;
1799 new_node_one.label =
"route1";
1800 new_node_one.scrollPosition = 1.0;
1801 second_update[new_node_one.id] = new_node_one;
1802 bridge->UpdateSemantics(second_update, actions);
1803 SemanticsObject* focusObject = accessibility_notifications[0][@"argument"];
1806 XCTAssertEqual([focusObject uid], 1);
1807 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
1808 UIAccessibilityPageScrolledNotification);
1811 - (void)testAnnouncesScrollChangeDoesCallNativeAccessibility {
1812 flutter::MockDelegate mock_delegate;
1814 flutter::TaskRunners runners(
self.name.UTF8String,
1818 thread_task_runner);
1819 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1821 mock_delegate.settings_.enable_impeller
1827 std::make_shared<fml::SyncSwitch>());
1829 id mockFlutterView = OCMClassMock([
FlutterView class]);
1830 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1832 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1833 [[NSMutableArray alloc] init];
1834 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1835 ios_delegate->on_PostAccessibilityNotification_ =
1836 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
1837 [accessibility_notifications addObject:@{
1838 @"notification" : @(notification),
1839 @"argument" : argument ? argument : [NSNull null],
1842 __block
auto bridge =
1843 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
1846 std::move(ios_delegate));
1848 flutter::CustomAccessibilityActionUpdates actions;
1849 flutter::SemanticsNodeUpdates first_update;
1851 flutter::SemanticsNode node_one;
1853 node_one.label =
"route1";
1854 node_one.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kHasImplicitScrolling);
1855 node_one.scrollPosition = 0.0;
1856 first_update[node_one.id] = node_one;
1857 flutter::SemanticsNode root_node;
1859 root_node.label =
"root";
1860 root_node.childrenInTraversalOrder = {1};
1861 root_node.childrenInHitTestOrder = {1};
1862 first_update[root_node.id] = root_node;
1863 bridge->UpdateSemantics(first_update, actions);
1866 [accessibility_notifications removeAllObjects];
1869 bridge->AccessibilityObjectDidBecomeFocused(1);
1871 flutter::SemanticsNodeUpdates second_update;
1873 flutter::SemanticsNode new_node_one;
1874 new_node_one.id = 1;
1875 new_node_one.label =
"route1";
1876 new_node_one.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kHasImplicitScrolling);
1877 new_node_one.scrollPosition = 1.0;
1878 second_update[new_node_one.id] = new_node_one;
1879 bridge->UpdateSemantics(second_update, actions);
1880 SemanticsObject* focusObject = accessibility_notifications[0][@"argument"];
1884 XCTAssertEqual([accessibility_notifications[0][
@"notification"] unsignedIntValue],
1885 UIAccessibilityPageScrolledNotification);
1888 - (void)testAnnouncesIgnoresRouteChangesWhenModal {
1889 flutter::MockDelegate mock_delegate;
1891 flutter::TaskRunners runners(
self.name.UTF8String,
1895 thread_task_runner);
1896 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1898 mock_delegate.settings_.enable_impeller
1904 std::make_shared<fml::SyncSwitch>());
1905 id mockFlutterView = OCMClassMock([
FlutterView class]);
1907 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1908 std::string label =
"some label";
1910 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1911 [[NSMutableArray alloc] init];
1912 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1913 ios_delegate->on_PostAccessibilityNotification_ =
1914 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
1915 [accessibility_notifications addObject:@{
1916 @"notification" : @(notification),
1917 @"argument" : argument ? argument : [NSNull null],
1920 ios_delegate->result_IsFlutterViewControllerPresentingModalViewController_ =
true;
1921 __block
auto bridge =
1922 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
1925 std::move(ios_delegate));
1927 flutter::CustomAccessibilityActionUpdates actions;
1928 flutter::SemanticsNodeUpdates nodes;
1930 flutter::SemanticsNode route_node;
1932 route_node.flags =
static_cast<int32_t
>(flutter::SemanticsFlags::kScopesRoute) |
1933 static_cast<int32_t
>(flutter::SemanticsFlags::kNamesRoute);
1934 route_node.label =
"route";
1935 nodes[route_node.id] = route_node;
1936 flutter::SemanticsNode root_node;
1938 root_node.label = label;
1939 root_node.childrenInTraversalOrder = {1};
1940 root_node.childrenInHitTestOrder = {1};
1941 nodes[root_node.id] = root_node;
1942 bridge->UpdateSemantics(nodes, actions);
1944 XCTAssertEqual([accessibility_notifications count], 0ul);
1947 - (void)testAnnouncesIgnoresLayoutChangeWhenModal {
1948 flutter::MockDelegate mock_delegate;
1950 flutter::TaskRunners runners(
self.name.UTF8String,
1954 thread_task_runner);
1955 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
1957 mock_delegate.settings_.enable_impeller
1963 std::make_shared<fml::SyncSwitch>());
1964 id mockFlutterView = OCMClassMock([
FlutterView class]);
1966 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
1968 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
1969 [[NSMutableArray alloc] init];
1970 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
1971 ios_delegate->on_PostAccessibilityNotification_ =
1972 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
1973 [accessibility_notifications addObject:@{
1974 @"notification" : @(notification),
1975 @"argument" : argument ? argument : [NSNull null],
1978 ios_delegate->result_IsFlutterViewControllerPresentingModalViewController_ =
true;
1979 __block
auto bridge =
1980 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
1983 std::move(ios_delegate));
1985 flutter::CustomAccessibilityActionUpdates actions;
1986 flutter::SemanticsNodeUpdates nodes;
1988 flutter::SemanticsNode child_node;
1990 child_node.label =
"child_node";
1991 nodes[child_node.id] = child_node;
1992 flutter::SemanticsNode root_node;
1994 root_node.label =
"root";
1995 root_node.childrenInTraversalOrder = {1};
1996 root_node.childrenInHitTestOrder = {1};
1997 nodes[root_node.id] = root_node;
1998 bridge->UpdateSemantics(nodes, actions);
2001 flutter::SemanticsNodeUpdates new_nodes;
2002 flutter::SemanticsNode new_root_node;
2004 new_root_node.label =
"root";
2005 new_nodes[new_root_node.id] = new_root_node;
2006 bridge->UpdateSemantics(new_nodes, actions);
2008 XCTAssertEqual([accessibility_notifications count], 0ul);
2011 - (void)testAnnouncesIgnoresScrollChangeWhenModal {
2012 flutter::MockDelegate mock_delegate;
2014 flutter::TaskRunners runners(
self.name.UTF8String,
2018 thread_task_runner);
2019 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2021 mock_delegate.settings_.enable_impeller
2027 std::make_shared<fml::SyncSwitch>());
2028 id mockFlutterView = OCMClassMock([
FlutterView class]);
2030 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
2032 NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
2033 [[NSMutableArray alloc] init];
2034 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
2035 ios_delegate->on_PostAccessibilityNotification_ =
2036 [accessibility_notifications](UIAccessibilityNotifications notification,
id argument) {
2037 [accessibility_notifications addObject:@{
2038 @"notification" : @(notification),
2039 @"argument" : argument ? argument : [NSNull null],
2042 ios_delegate->result_IsFlutterViewControllerPresentingModalViewController_ =
true;
2043 __block
auto bridge =
2044 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
2047 std::move(ios_delegate));
2049 flutter::CustomAccessibilityActionUpdates actions;
2050 flutter::SemanticsNodeUpdates nodes;
2052 flutter::SemanticsNode root_node;
2054 root_node.label =
"root";
2055 root_node.scrollPosition = 1;
2056 nodes[root_node.id] = root_node;
2057 bridge->UpdateSemantics(nodes, actions);
2060 flutter::SemanticsNodeUpdates new_nodes;
2061 flutter::SemanticsNode new_root_node;
2063 new_root_node.label =
"root";
2064 new_root_node.scrollPosition = 2;
2065 new_nodes[new_root_node.id] = new_root_node;
2066 bridge->UpdateSemantics(new_nodes, actions);
2068 XCTAssertEqual([accessibility_notifications count], 0ul);
2071 - (void)testAccessibilityMessageAfterDeletion {
2072 flutter::MockDelegate mock_delegate;
2073 auto thread = std::make_unique<fml::Thread>(
"AccessibilityBridgeTest");
2074 auto thread_task_runner = thread->GetTaskRunner();
2075 flutter::TaskRunners runners(
self.name.UTF8String,
2079 thread_task_runner);
2084 OCMStub([flutterViewController
engine]).andReturn(
engine);
2085 OCMStub([
engine binaryMessenger]).andReturn(messenger);
2087 OCMStub([messenger setMessageHandlerOnChannel:
@"flutter/accessibility"
2088 binaryMessageHandler:[OCMArg any]])
2089 .andReturn(connection);
2091 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2093 mock_delegate.settings_.enable_impeller
2099 std::make_shared<fml::SyncSwitch>());
2100 fml::AutoResetWaitableEvent latch;
2101 thread_task_runner->PostTask([&] {
2102 platform_view->SetOwnerViewController(flutterViewController);
2104 std::make_unique<flutter::AccessibilityBridge>(nil,
2107 XCTAssertTrue(bridge.get());
2108 OCMVerify([messenger setMessageHandlerOnChannel:
@"flutter/accessibility"
2109 binaryMessageHandler:[OCMArg isNotNil]]);
2114 OCMVerify([messenger cleanUpConnection:connection]);
2115 [engine stopMocking];
2118 - (void)testFlutterSemanticsScrollViewManagedObjectLifecycleCorrectly {
2119 flutter::MockDelegate mock_delegate;
2121 flutter::TaskRunners runners(
self.name.UTF8String,
2125 thread_task_runner);
2126 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2128 mock_delegate.settings_.enable_impeller
2134 std::make_shared<fml::SyncSwitch>());
2135 id mockFlutterView = OCMClassMock([
FlutterView class]);
2137 OCMStub([mockFlutterViewController view]).andReturn(mockFlutterView);
2139 auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
2140 __block
auto bridge =
2141 std::make_unique<flutter::AccessibilityBridge>(mockFlutterViewController,
2144 std::move(ios_delegate));
2153 XCTAssertTrue(flutterSemanticsScrollView);
2156 XCTAssertFalse([flutterSemanticsScrollView isAccessibilityElement]);
2159 - (void)testPlatformViewDestructorDoesNotCallSemanticsAPIs {
2160 class TestDelegate :
public flutter::MockDelegate {
2162 void OnPlatformViewSetSemanticsEnabled(
bool enabled)
override { set_semantics_enabled_calls++; }
2163 int set_semantics_enabled_calls = 0;
2166 TestDelegate test_delegate;
2167 auto thread = std::make_unique<fml::Thread>(
"AccessibilityBridgeTest");
2168 auto thread_task_runner = thread->GetTaskRunner();
2169 flutter::TaskRunners runners(
self.name.UTF8String,
2173 thread_task_runner);
2175 fml::AutoResetWaitableEvent latch;
2176 thread_task_runner->PostTask([&] {
2177 auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
2179 test_delegate.settings_.enable_impeller
2185 std::make_shared<fml::SyncSwitch>());
2190 flutterPlatformViewsController.
taskRunner = thread_task_runner;
2192 OCMStub([mockFlutterViewController platformViewsController])
2193 .andReturn(flutterPlatformViewsController);
2194 platform_view->SetOwnerViewController(mockFlutterViewController);
2197 XCTAssertNotEqual(test_delegate.set_semantics_enabled_calls, 0);
2200 test_delegate.set_semantics_enabled_calls = 0;
2202 XCTAssertEqual(test_delegate.set_semantics_enabled_calls, 0);