blob: 47a0b0c0272b7c7231b46df813ba31ab0b1040ea [file] [log] [blame]
// 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/FlutterBinaryMessenger.h"
#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h"
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterPlatformViews.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.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
@class MockPlatformView;
static MockPlatformView* gMockPlatformView = nil;
@interface MockPlatformView : UIView
@end
@implementation MockPlatformView
- (instancetype)init {
self = [super init];
if (self) {
gMockPlatformView = self;
}
return self;
}
- (void)dealloc {
gMockPlatformView = nil;
[super dealloc];
}
@end
@interface MockFlutterPlatformView : NSObject <FlutterPlatformView>
@property(nonatomic, strong) UIView* view;
@end
@implementation MockFlutterPlatformView
- (instancetype)init {
if (self = [super init]) {
_view = [[MockPlatformView alloc] init];
}
return self;
}
- (void)dealloc {
[_view release];
_view = nil;
[super dealloc];
}
@end
@interface MockFlutterPlatformFactory : NSObject <FlutterPlatformViewFactory>
@end
@implementation MockFlutterPlatformFactory
- (NSObject<FlutterPlatformView>*)createWithFrame:(CGRect)frame
viewIdentifier:(int64_t)viewId
arguments:(id _Nullable)args {
return [[[MockFlutterPlatformView alloc] init] autorelease];
}
@end
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 {}
std::unique_ptr<std::vector<std::string>> ComputePlatformViewResolvedLocale(
const std::vector<std::string>& supported_locale_data) override {
std::unique_ptr<std::vector<std::string>> out = std::make_unique<std::vector<std::string>>();
return out;
}
};
class MockIosDelegate : public AccessibilityBridge::IosDelegate {
public:
bool IsFlutterViewControllerPresentingModalViewController(UIView* view) override {
return result_IsFlutterViewControllerPresentingModalViewController_;
};
void PostAccessibilityNotification(UIAccessibilityNotifications notification,
id argument) override {
if (on_PostAccessibilityNotification_) {
on_PostAccessibilityNotification_(notification, argument);
}
}
std::function<void(UIAccessibilityNotifications, id)> on_PostAccessibilityNotification_;
bool result_IsFlutterViewControllerPresentingModalViewController_ = false;
};
} // 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);
}
- (void)testSemanticsDeallocated {
@autoreleasepool {
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";
auto flutterPlatformViewsController =
std::make_unique<flutter::FlutterPlatformViewsController>();
flutterPlatformViewsController->SetFlutterView(mockFlutterView);
MockFlutterPlatformFactory* factory = [[MockFlutterPlatformFactory new] autorelease];
flutterPlatformViewsController->RegisterViewFactory(
factory, @"MockFlutterPlatformView",
FlutterPlatformViewGestureRecognizersBlockingPolicyEager);
FlutterResult result = ^(id result) {
};
flutterPlatformViewsController->OnMethodCall(
[FlutterMethodCall
methodCallWithMethodName:@"create"
arguments:@{@"id" : @2, @"viewType" : @"MockFlutterPlatformView"}],
result);
auto bridge = std::make_unique<flutter::AccessibilityBridge>(
/*view=*/mockFlutterView,
/*platform_view=*/platform_view.get(),
/*platform_views_controller=*/flutterPlatformViewsController.get());
flutter::SemanticsNodeUpdates nodes;
flutter::SemanticsNode semantics_node;
semantics_node.id = 2;
semantics_node.platformViewId = 2;
semantics_node.label = label;
nodes[kRootNodeId] = semantics_node;
flutter::CustomAccessibilityActionUpdates actions;
bridge->UpdateSemantics(/*nodes=*/nodes, /*actions=*/actions);
XCTAssertNotNil(gMockPlatformView);
flutterPlatformViewsController->Reset();
}
XCTAssertNil(gMockPlatformView);
}
- (void)testAnnouncesRouteChanges {
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";
NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
[[[NSMutableArray alloc] init] autorelease];
auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
ios_delegate->on_PostAccessibilityNotification_ =
[accessibility_notifications](UIAccessibilityNotifications notification, id argument) {
[accessibility_notifications addObject:@{
@"notification" : @(notification),
@"argument" : argument ? argument : [NSNull null],
}];
};
__block auto bridge =
std::make_unique<flutter::AccessibilityBridge>(/*view=*/mockFlutterView,
/*platform_view=*/platform_view.get(),
/*platform_views_controller=*/nil,
/*ios_delegate=*/std::move(ios_delegate));
flutter::CustomAccessibilityActionUpdates actions;
flutter::SemanticsNodeUpdates nodes;
flutter::SemanticsNode route_node;
route_node.id = 1;
route_node.label = label;
route_node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kScopesRoute) |
static_cast<int32_t>(flutter::SemanticsFlags::kNamesRoute);
route_node.label = "route";
nodes[route_node.id] = route_node;
flutter::SemanticsNode root_node;
root_node.id = kRootNodeId;
root_node.label = label;
root_node.childrenInTraversalOrder = {1};
root_node.childrenInHitTestOrder = {1};
nodes[root_node.id] = root_node;
bridge->UpdateSemantics(/*nodes=*/nodes, /*actions=*/actions);
XCTAssertEqual([accessibility_notifications count], 1ul);
XCTAssertEqualObjects(accessibility_notifications[0][@"argument"], @"route");
XCTAssertEqual([accessibility_notifications[0][@"notification"] unsignedIntValue],
UIAccessibilityScreenChangedNotification);
}
- (void)testAnnouncesIgnoresRouteChangesWhenModal {
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";
NSMutableArray<NSDictionary<NSString*, id>*>* accessibility_notifications =
[[[NSMutableArray alloc] init] autorelease];
auto ios_delegate = std::make_unique<flutter::MockIosDelegate>();
ios_delegate->on_PostAccessibilityNotification_ =
[accessibility_notifications](UIAccessibilityNotifications notification, id argument) {
[accessibility_notifications addObject:@{
@"notification" : @(notification),
@"argument" : argument ? argument : [NSNull null],
}];
};
ios_delegate->result_IsFlutterViewControllerPresentingModalViewController_ = true;
__block auto bridge =
std::make_unique<flutter::AccessibilityBridge>(/*view=*/mockFlutterView,
/*platform_view=*/platform_view.get(),
/*platform_views_controller=*/nil,
/*ios_delegate=*/std::move(ios_delegate));
flutter::CustomAccessibilityActionUpdates actions;
flutter::SemanticsNodeUpdates nodes;
flutter::SemanticsNode route_node;
route_node.id = 1;
route_node.label = label;
route_node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kScopesRoute) |
static_cast<int32_t>(flutter::SemanticsFlags::kNamesRoute);
route_node.label = "route";
nodes[route_node.id] = route_node;
flutter::SemanticsNode root_node;
root_node.id = kRootNodeId;
root_node.label = label;
root_node.childrenInTraversalOrder = {1};
root_node.childrenInHitTestOrder = {1};
nodes[root_node.id] = root_node;
bridge->UpdateSemantics(/*nodes=*/nodes, /*actions=*/actions);
XCTAssertEqual([accessibility_notifications count], 0ul);
}
- (void)testAccessibilityMessageAfterDeletion {
flutter::MockDelegate mock_delegate;
auto thread = std::make_unique<fml::Thread>("AccessibilityBridgeTest");
auto thread_task_runner = thread->GetTaskRunner();
flutter::TaskRunners runners(/*label=*/self.name.UTF8String,
/*platform=*/thread_task_runner,
/*raster=*/thread_task_runner,
/*ui=*/thread_task_runner,
/*io=*/thread_task_runner);
id messenger = OCMProtocolMock(@protocol(FlutterBinaryMessenger));
id engine = OCMClassMock([FlutterEngine class]);
id flutterViewController = OCMClassMock([FlutterViewController class]);
OCMStub([flutterViewController engine]).andReturn(engine);
OCMStub([engine binaryMessenger]).andReturn(messenger);
FlutterBinaryMessengerConnection connection = 123;
OCMStub([messenger setMessageHandlerOnChannel:@"flutter/accessibility"
binaryMessageHandler:[OCMArg any]])
.andReturn(connection);
auto platform_view = std::make_unique<flutter::PlatformViewIOS>(
/*delegate=*/mock_delegate,
/*rendering_api=*/flutter::IOSRenderingAPI::kSoftware,
/*task_runners=*/runners);
fml::AutoResetWaitableEvent latch;
thread_task_runner->PostTask([&] {
auto weakFactory =
std::make_unique<fml::WeakPtrFactory<FlutterViewController>>(flutterViewController);
platform_view->SetOwnerViewController(weakFactory->GetWeakPtr());
auto bridge =
std::make_unique<flutter::AccessibilityBridge>(/*view=*/nil,
/*platform_view=*/platform_view.get(),
/*platform_views_controller=*/nil);
XCTAssertTrue(bridge.get());
OCMVerify([messenger setMessageHandlerOnChannel:@"flutter/accessibility"
binaryMessageHandler:[OCMArg isNotNil]]);
bridge.reset();
latch.Signal();
});
latch.Wait();
OCMVerify([messenger cleanupConnection:connection]);
}
@end