Added a unit test for the iOS AccessibilityBridge. (#18281)

diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter
index cd0ba04..c891c35 100755
--- a/ci/licenses_golden/licenses_flutter
+++ b/ci/licenses_golden/licenses_flutter
@@ -903,6 +903,7 @@
 FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/accessibility_bridge.h
 FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/accessibility_bridge.mm
 FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/accessibility_bridge_ios.h
+FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm
 FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/accessibility_text_entry.h
 FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/accessibility_text_entry.mm
 FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/platform_message_response_darwin.h
diff --git a/shell/platform/darwin/ios/BUILD.gn b/shell/platform/darwin/ios/BUILD.gn
index f2eead7..a71cd79 100644
--- a/shell/platform/darwin/ios/BUILD.gn
+++ b/shell/platform/darwin/ios/BUILD.gn
@@ -152,6 +152,28 @@
 ios_test_flutter_path = rebase_path("$root_out_dir/libios_test_flutter.dylib")
 platform_frameworks_path = "$ios_sdk_path/../../Library/Frameworks/"
 
+# For tests that rely on manual reference counting.
+source_set("ios_test_flutter_mrc") {
+  visibility = [ ":*" ]
+  cflags = [
+    "-fvisibility=default",
+    "-F$platform_frameworks_path",
+    "-mios-simulator-version-min=$ios_testing_deployment_target",
+  ]
+  sources = [
+    "framework/Source/accessibility_bridge_test.mm",
+  ]
+  deps = [
+    ":flutter_framework_source",
+    "//flutter/shell/platform/darwin/common:framework_shared",
+    "//flutter/third_party/tonic",
+    "//flutter/third_party/txt",
+    "//third_party/ocmock:ocmock",
+    "//third_party/rapidjson",
+    "//third_party/skia",
+  ]
+}
+
 # NOTE: This currently only supports simulator targets because of the install_name.
 # TODO(54504): Switch the install_name and make the test runner copy the dynamic
 #              library into the testing bundle.
@@ -182,8 +204,12 @@
   ]
   deps = [
     ":flutter_framework_source",
+    ":ios_test_flutter_mrc",
     "//flutter/shell/platform/darwin/common:framework_shared",
+    "//flutter/third_party/tonic",
+    "//flutter/third_party/txt",
     "//third_party/ocmock:ocmock",
+    "//third_party/rapidjson",
     "//third_party/skia",
   ]
   public_configs = [ "//flutter:config" ]
diff --git a/shell/platform/darwin/ios/framework/Source/SemanticsObject.h b/shell/platform/darwin/ios/framework/Source/SemanticsObject.h
index ba7c468..ba8591e 100644
--- a/shell/platform/darwin/ios/framework/Source/SemanticsObject.h
+++ b/shell/platform/darwin/ios/framework/Source/SemanticsObject.h
@@ -155,4 +155,50 @@
 - (instancetype)initWithSemanticsObject:(SemanticsObject*)semanticsObject;
 @end
 
+/**
+ * Represents a semantics object that has children and hence has to be presented to the OS as a
+ * UIAccessibilityContainer.
+ *
+ * The SemanticsObject class cannot implement the UIAccessibilityContainer protocol because an
+ * object that returns YES for isAccessibilityElement cannot also implement
+ * UIAccessibilityContainer.
+ *
+ * With the help of SemanticsObjectContainer, the hierarchy of semantic objects received from
+ * the framework, such as:
+ *
+ * SemanticsObject1
+ *     SemanticsObject2
+ *         SemanticsObject3
+ *         SemanticsObject4
+ *
+ * is translated into the following hierarchy, which is understood by iOS:
+ *
+ * SemanticsObjectContainer1
+ *     SemanticsObject1
+ *     SemanticsObjectContainer2
+ *         SemanticsObject2
+ *         SemanticsObject3
+ *         SemanticsObject4
+ *
+ * From Flutter's view of the world (the first tree seen above), we construct iOS's view of the
+ * world (second tree) as follows: We replace each SemanticsObjects that has children with a
+ * SemanticsObjectContainer, which has the original SemanticsObject and its children as children.
+ *
+ * SemanticsObjects have semantic information attached to them which is interpreted by
+ * VoiceOver (they return YES for isAccessibilityElement). The SemanticsObjectContainers are just
+ * there for structure and they don't provide any semantic information to VoiceOver (they return
+ * NO for isAccessibilityElement).
+ */
+@interface SemanticsObjectContainer : UIAccessibilityElement
+- (instancetype)init NS_UNAVAILABLE;
++ (instancetype)new NS_UNAVAILABLE;
+- (instancetype)initWithAccessibilityContainer:(id)container NS_UNAVAILABLE;
+- (instancetype)initWithSemanticsObject:(SemanticsObject*)semanticsObject
+                                 bridge:(fml::WeakPtr<flutter::AccessibilityBridgeIos>)bridge
+    NS_DESIGNATED_INITIALIZER;
+
+@property(nonatomic, weak) SemanticsObject* semanticsObject;
+
+@end
+
 #endif  // SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_SEMANTICS_OBJECT_H_
diff --git a/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm b/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm
index 9f8f6b6..229e398 100644
--- a/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm
+++ b/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm
@@ -103,50 +103,6 @@
 }
 @end
 
-/**
- * Represents a semantics object that has children and hence has to be presented to the OS as a
- * UIAccessibilityContainer.
- *
- * The SemanticsObject class cannot implement the UIAccessibilityContainer protocol because an
- * object that returns YES for isAccessibilityElement cannot also implement
- * UIAccessibilityContainer.
- *
- * With the help of SemanticsObjectContainer, the hierarchy of semantic objects received from
- * the framework, such as:
- *
- * SemanticsObject1
- *     SemanticsObject2
- *         SemanticsObject3
- *         SemanticsObject4
- *
- * is translated into the following hierarchy, which is understood by iOS:
- *
- * SemanticsObjectContainer1
- *     SemanticsObject1
- *     SemanticsObjectContainer2
- *         SemanticsObject2
- *         SemanticsObject3
- *         SemanticsObject4
- *
- * From Flutter's view of the world (the first tree seen above), we construct iOS's view of the
- * world (second tree) as follows: We replace each SemanticsObjects that has children with a
- * SemanticsObjectContainer, which has the original SemanticsObject and its children as children.
- *
- * SemanticsObjects have semantic information attached to them which is interpreted by
- * VoiceOver (they return YES for isAccessibilityElement). The SemanticsObjectContainers are just
- * there for structure and they don't provide any semantic information to VoiceOver (they return
- * NO for isAccessibilityElement).
- */
-@interface SemanticsObjectContainer : UIAccessibilityElement
-- (instancetype)init __attribute__((unavailable("Use initWithSemanticsObject instead")));
-- (instancetype)initWithSemanticsObject:(SemanticsObject*)semanticsObject
-                                 bridge:(fml::WeakPtr<flutter::AccessibilityBridgeIos>)bridge
-    NS_DESIGNATED_INITIALIZER;
-
-@property(nonatomic, weak) SemanticsObject* semanticsObject;
-
-@end
-
 @interface SemanticsObject ()
 /** Should only be called in conjunction with setting child/parent relationship. */
 - (void)privateSetParent:(SemanticsObject*)parent;
diff --git a/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm b/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm
new file mode 100644
index 0000000..6f17747
--- /dev/null
+++ b/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm
@@ -0,0 +1,134 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import <XCTest/XCTest.h>
+
+#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h"
+#import "flutter/shell/platform/darwin/ios/framework/Source/accessibility_bridge.h"
+#import "flutter/shell/platform/darwin/ios/platform_view_ios.h"
+#import "third_party/ocmock/Source/OCMock/OCMock.h"
+
+FLUTTER_ASSERT_NOT_ARC
+
+namespace flutter {
+namespace {
+class MockDelegate : public PlatformView::Delegate {
+  void OnPlatformViewCreated(std::unique_ptr<Surface> surface) override {}
+  void OnPlatformViewDestroyed() override {}
+  void OnPlatformViewSetNextFrameCallback(const fml::closure& closure) override {}
+  void OnPlatformViewSetViewportMetrics(const ViewportMetrics& metrics) override {}
+  void OnPlatformViewDispatchPlatformMessage(fml::RefPtr<PlatformMessage> message) override {}
+  void OnPlatformViewDispatchPointerDataPacket(std::unique_ptr<PointerDataPacket> packet) override {
+  }
+  void OnPlatformViewDispatchSemanticsAction(int32_t id,
+                                             SemanticsAction action,
+                                             std::vector<uint8_t> args) override {}
+  void OnPlatformViewSetSemanticsEnabled(bool enabled) override {}
+  void OnPlatformViewSetAccessibilityFeatures(int32_t flags) override {}
+  void OnPlatformViewRegisterTexture(std::shared_ptr<Texture> texture) override {}
+  void OnPlatformViewUnregisterTexture(int64_t texture_id) override {}
+  void OnPlatformViewMarkTextureFrameAvailable(int64_t texture_id) override {}
+};
+}  // namespace
+}  // namespace flutter
+
+namespace {
+fml::RefPtr<fml::TaskRunner> CreateNewThread(std::string name) {
+  auto thread = std::make_unique<fml::Thread>(name);
+  auto runner = thread->GetTaskRunner();
+  return runner;
+}
+}  // namespace
+
+@interface AccessibilityBridgeTest : XCTestCase
+@end
+
+@implementation AccessibilityBridgeTest
+
+- (void)testCreate {
+  flutter::MockDelegate mock_delegate;
+  auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
+  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
+                               /*platform=*/thread_task_runner,
+                               /*raster=*/thread_task_runner,
+                               /*ui=*/thread_task_runner,
+                               /*io=*/thread_task_runner);
+  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
+      /*delegate=*/mock_delegate,
+      /*rendering_api=*/flutter::IOSRenderingAPI::kSoftware,
+      /*task_runners=*/runners);
+  auto bridge =
+      std::make_unique<flutter::AccessibilityBridge>(/*view=*/nil,
+                                                     /*platform_view=*/platform_view.get(),
+                                                     /*platform_views_controller=*/nil);
+  XCTAssertTrue(bridge.get());
+}
+
+- (void)testUpdateSemanticsEmpty {
+  flutter::MockDelegate mock_delegate;
+  auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
+  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
+                               /*platform=*/thread_task_runner,
+                               /*raster=*/thread_task_runner,
+                               /*ui=*/thread_task_runner,
+                               /*io=*/thread_task_runner);
+  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
+      /*delegate=*/mock_delegate,
+      /*rendering_api=*/flutter::IOSRenderingAPI::kSoftware,
+      /*task_runners=*/runners);
+  id mockFlutterView = OCMClassMock([FlutterView class]);
+  OCMExpect([mockFlutterView setAccessibilityElements:[OCMArg isNil]]);
+  auto bridge =
+      std::make_unique<flutter::AccessibilityBridge>(/*view=*/mockFlutterView,
+                                                     /*platform_view=*/platform_view.get(),
+                                                     /*platform_views_controller=*/nil);
+  flutter::SemanticsNodeUpdates nodes;
+  flutter::CustomAccessibilityActionUpdates actions;
+  bridge->UpdateSemantics(/*nodes=*/nodes, /*actions=*/actions);
+  OCMVerifyAll(mockFlutterView);
+}
+
+- (void)testUpdateSemanticsOneNode {
+  flutter::MockDelegate mock_delegate;
+  auto thread_task_runner = CreateNewThread("AccessibilityBridgeTest");
+  flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
+                               /*platform=*/thread_task_runner,
+                               /*raster=*/thread_task_runner,
+                               /*ui=*/thread_task_runner,
+                               /*io=*/thread_task_runner);
+  auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
+      /*delegate=*/mock_delegate,
+      /*rendering_api=*/flutter::IOSRenderingAPI::kSoftware,
+      /*task_runners=*/runners);
+  id mockFlutterView = OCMClassMock([FlutterView class]);
+  std::string label = "some label";
+
+  __block auto bridge =
+      std::make_unique<flutter::AccessibilityBridge>(/*view=*/mockFlutterView,
+                                                     /*platform_view=*/platform_view.get(),
+                                                     /*platform_views_controller=*/nil);
+
+  OCMExpect([mockFlutterView setAccessibilityElements:[OCMArg checkWithBlock:^BOOL(NSArray* value) {
+                               if ([value count] != 1) {
+                                 return NO;
+                               } else {
+                                 SemanticsObjectContainer* container = value[0];
+                                 SemanticsObject* object = container.semanticsObject;
+                                 return object.uid == kRootNodeId &&
+                                        object.bridge.get() == bridge.get() &&
+                                        object.node.label == label;
+                               }
+                             }]]);
+
+  flutter::SemanticsNodeUpdates nodes;
+  flutter::SemanticsNode semantics_node;
+  semantics_node.id = kRootNodeId;
+  semantics_node.label = label;
+  nodes[kRootNodeId] = semantics_node;
+  flutter::CustomAccessibilityActionUpdates actions;
+  bridge->UpdateSemantics(/*nodes=*/nodes, /*actions=*/actions);
+  OCMVerifyAll(mockFlutterView);
+}
+
+@end