未验证 提交 d1b0c466 编写于 作者: G gaaclarke 提交者: GitHub

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

上级 dc7e24e1
......@@ -903,6 +903,7 @@ FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/SemanticsObjec
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
......
......@@ -152,6 +152,28 @@ source_set("flutter_framework_source") {
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 @@ shared_library("ios_test_flutter") {
]
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" ]
......
......@@ -155,4 +155,50 @@ constexpr int32_t kRootNodeId = 0;
- (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_
......@@ -103,50 +103,6 @@ flutter::SemanticsAction GetSemanticsActionForScrollDirection(
}
@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;
......
// 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
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册