@@ -743,10 +743,83 @@ - (void)testLayoutChangeDoesCallNativeAccessibility {
743
743
bridge->UpdateSemantics (/* nodes=*/ new_nodes, /* actions=*/ new_actions);
744
744
745
745
XCTAssertEqual ([accessibility_notifications count ], 1ul );
746
- SemanticsObject* focusObject = accessibility_notifications[0 ][@" argument" ];
747
- // Make sure refocus event is sent with the nativeAccessibility of root node
748
- // which is a FlutterSemanticsScrollView.
749
- XCTAssertTrue ([focusObject isKindOfClass: [FlutterSemanticsScrollView class ]]);
746
+ id focusObject = accessibility_notifications[0 ][@" argument" ];
747
+
748
+ // Make sure the focused item is not specificed when it stays the same.
749
+ // See: https://github.com/flutter/flutter/issues/104176
750
+ XCTAssertEqualObjects (focusObject, [NSNull null ]);
751
+ XCTAssertEqual ([accessibility_notifications[0 ][@" notification" ] unsignedIntValue ],
752
+ UIAccessibilityLayoutChangedNotification);
753
+ }
754
+
755
+ - (void )testLayoutChangeDoesCallNativeAccessibilityWhenFocusChanged {
756
+ flutter::MockDelegate mock_delegate;
757
+ auto thread_task_runner = CreateNewThread (" AccessibilityBridgeTest" );
758
+ flutter::TaskRunners runners (/* label=*/ self.name .UTF8String ,
759
+ /* platform=*/ thread_task_runner,
760
+ /* raster=*/ thread_task_runner,
761
+ /* ui=*/ thread_task_runner,
762
+ /* io=*/ thread_task_runner);
763
+ auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
764
+ /* delegate=*/ mock_delegate,
765
+ /* rendering_api=*/ flutter::IOSRenderingAPI::kSoftware ,
766
+ /* platform_views_controller=*/ nil ,
767
+ /* task_runners=*/ runners);
768
+ id mockFlutterView = OCMClassMock ([FlutterView class ]);
769
+ id mockFlutterViewController = OCMClassMock ([FlutterViewController class ]);
770
+ OCMStub ([mockFlutterViewController view ]).andReturn (mockFlutterView);
771
+
772
+ NSMutableArray <NSDictionary <NSString *, id >*>* accessibility_notifications =
773
+ [[[NSMutableArray alloc ] init ] autorelease ];
774
+ auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
775
+ ios_delegate->on_PostAccessibilityNotification_ =
776
+ [accessibility_notifications](UIAccessibilityNotifications notification, id argument) {
777
+ [accessibility_notifications addObject: @{
778
+ @" notification" : @(notification),
779
+ @" argument" : argument ? argument : [NSNull null ],
780
+ }];
781
+ };
782
+ __block auto bridge =
783
+ std::make_unique<flutter::AccessibilityBridge>(/* view_controller=*/ mockFlutterViewController,
784
+ /* platform_view=*/ platform_view.get (),
785
+ /* platform_views_controller=*/ nil ,
786
+ /* ios_delegate=*/ std::move (ios_delegate));
787
+
788
+ flutter::CustomAccessibilityActionUpdates actions;
789
+ flutter::SemanticsNodeUpdates nodes;
790
+
791
+ flutter::SemanticsNode node1;
792
+ node1.id = 1 ;
793
+ node1.label = " node1" ;
794
+ nodes[node1.id ] = node1;
795
+ flutter::SemanticsNode root_node;
796
+ root_node.id = kRootNodeId ;
797
+ root_node.label = " root" ;
798
+ root_node.flags = static_cast <int32_t >(flutter::SemanticsFlags::kHasImplicitScrolling );
799
+ root_node.childrenInTraversalOrder = {1 };
800
+ root_node.childrenInHitTestOrder = {1 };
801
+ nodes[root_node.id ] = root_node;
802
+ bridge->UpdateSemantics (/* nodes=*/ nodes, /* actions=*/ actions);
803
+
804
+ // Simulates the focusing on the node 1.
805
+ bridge->AccessibilityObjectDidBecomeFocused (1 );
806
+
807
+ // Remove node 1 to trigger a layout change notification, and focus should be one root
808
+ flutter::CustomAccessibilityActionUpdates new_actions;
809
+ flutter::SemanticsNodeUpdates new_nodes;
810
+
811
+ flutter::SemanticsNode new_root_node;
812
+ new_root_node.id = kRootNodeId ;
813
+ new_root_node.label = " root" ;
814
+ new_root_node.flags = static_cast <int32_t >(flutter::SemanticsFlags::kHasImplicitScrolling );
815
+ new_nodes[new_root_node.id ] = new_root_node;
816
+ bridge->UpdateSemantics (/* nodes=*/ new_nodes, /* actions=*/ new_actions);
817
+
818
+ XCTAssertEqual ([accessibility_notifications count ], 1ul );
819
+ SemanticsObject* focusObject2 = accessibility_notifications[0 ][@" argument" ];
820
+
821
+ // Bridge should ask accessibility to focus on root because node 1 is moved from screen.
822
+ XCTAssertTrue ([focusObject2 isKindOfClass: [FlutterSemanticsScrollView class ]]);
750
823
XCTAssertEqual ([accessibility_notifications[0 ][@" notification" ] unsignedIntValue ],
751
824
UIAccessibilityLayoutChangedNotification);
752
825
}
@@ -896,7 +969,6 @@ - (void)testAnnouncesRouteChangesAndLayoutChangeInOneUpdate {
896
969
XCTAssertEqual ([accessibility_notifications[1 ][@" notification" ] unsignedIntValue ],
897
970
UIAccessibilityScreenChangedNotification);
898
971
SemanticsObject* focusObject = accessibility_notifications[2 ][@" argument" ];
899
- // It should still focus the root.
900
972
XCTAssertEqual ([focusObject uid ], 0 );
901
973
XCTAssertEqual ([accessibility_notifications[2 ][@" notification" ] unsignedIntValue ],
902
974
UIAccessibilityLayoutChangedNotification);
@@ -1211,7 +1283,7 @@ - (void)testAnnouncesLayoutChangeWithNilIfLastFocusIsRemoved {
1211
1283
UIAccessibilityLayoutChangedNotification);
1212
1284
}
1213
1285
1214
- - (void )testAnnouncesLayoutChangeWithLastFocused {
1286
+ - (void )testAnnouncesLayoutChangeWithTheSameItemFocused {
1215
1287
flutter::MockDelegate mock_delegate;
1216
1288
auto thread_task_runner = CreateNewThread (" AccessibilityBridgeTest" );
1217
1289
flutter::TaskRunners runners (/* label=*/ self.name .UTF8String ,
@@ -1276,10 +1348,10 @@ - (void)testAnnouncesLayoutChangeWithLastFocused {
1276
1348
new_root_node.childrenInHitTestOrder = {1 };
1277
1349
second_update[root_node.id ] = new_root_node;
1278
1350
bridge->UpdateSemantics (/* nodes=*/ second_update, /* actions=*/ actions);
1279
- SemanticsObject* focusObject = accessibility_notifications[0 ][@" argument" ];
1280
- // Since we have focused on the node 1 right before the layout changed, the bridge should refocus
1281
- // the node 1 .
1282
- XCTAssertEqual ([ focusObject uid ], 1 );
1351
+ id focusObject = accessibility_notifications[0 ][@" argument" ];
1352
+ // Since we have focused on the node 1 right before the layout changed, the bridge should not ask
1353
+ // to refocus again on the same node .
1354
+ XCTAssertEqualObjects ( focusObject, [ NSNull null ] );
1283
1355
XCTAssertEqual ([accessibility_notifications[0 ][@" notification" ] unsignedIntValue ],
1284
1356
UIAccessibilityLayoutChangedNotification);
1285
1357
}
0 commit comments