5 #import <Foundation/Foundation.h>
6 #import <OCMock/OCMock.h>
7 #import <XCTest/XCTest.h>
9 #import <objc/runtime.h>
11 #import "flutter/common/settings.h"
12 #include "flutter/fml/synchronization/sync_switch.h"
34 _ensureSemanticsEnabledCalled = YES;
48 @property(nonatomic, assign) BOOL failOnDealloc;
54 XCTFail(
"FakeBinaryMessageRelay should not be deallocated");
76 - (void)testShellGetters {
82 XCTAssertEqual(
engine.platformTaskRunner.get(),
nullptr);
83 XCTAssertEqual(
engine.uiTaskRunner.get(),
nullptr);
84 XCTAssertEqual(
engine.rasterTaskRunner.get(),
nullptr);
87 - (void)testInfoPlist {
89 NSURL* flutterFrameworkURL =
90 [NSBundle.mainBundle.privateFrameworksURL URLByAppendingPathComponent:@"Flutter.framework"];
91 NSBundle* flutterBundle = [NSBundle bundleWithURL:flutterFrameworkURL];
92 XCTAssertEqualObjects(flutterBundle.bundleIdentifier,
@"io.flutter.flutter");
94 NSDictionary<NSString*, id>* infoDictionary = flutterBundle.infoDictionary;
97 NSError* regexError = NULL;
98 NSRegularExpression* osVersionRegex =
99 [NSRegularExpression regularExpressionWithPattern:@"((0|[1-9]\\d*)\\.)*(0|[1-9]\\d*)"
100 options:NSRegularExpressionCaseInsensitive
102 XCTAssertNil(regexError);
105 NSString* testString =
@"9";
106 NSUInteger versionMatches =
107 [osVersionRegex numberOfMatchesInString:testString
108 options:NSMatchingAnchored
109 range:NSMakeRange(0, testString.length)];
110 XCTAssertEqual(versionMatches, 1UL);
112 versionMatches = [osVersionRegex numberOfMatchesInString:testString
113 options:NSMatchingAnchored
114 range:NSMakeRange(0, testString.length)];
115 XCTAssertEqual(versionMatches, 1UL);
116 testString =
@"9.0.1";
117 versionMatches = [osVersionRegex numberOfMatchesInString:testString
118 options:NSMatchingAnchored
119 range:NSMakeRange(0, testString.length)];
120 XCTAssertEqual(versionMatches, 1UL);
121 testString =
@".0.1";
122 versionMatches = [osVersionRegex numberOfMatchesInString:testString
123 options:NSMatchingAnchored
124 range:NSMakeRange(0, testString.length)];
125 XCTAssertEqual(versionMatches, 0UL);
128 NSString* minimumOSVersion = infoDictionary[@"MinimumOSVersion"];
129 versionMatches = [osVersionRegex numberOfMatchesInString:minimumOSVersion
130 options:NSMatchingAnchored
131 range:NSMakeRange(0, minimumOSVersion.length)];
132 XCTAssertEqual(versionMatches, 1UL);
135 XCTAssertEqual(((NSString*)infoDictionary[
@"FlutterEngine"]).length, 40UL);
140 XCTAssertTrue(((NSString*)infoDictionary[
@"ClangVersion"]).length > 15UL);
143 - (void)testDeallocated {
149 XCTAssertNotNil(weakEngine);
151 XCTAssertNil(weakEngine);
154 - (void)testSendMessageBeforeRun {
158 XCTAssertThrows([
engine.binaryMessenger
160 message:[
@"bar" dataUsingEncoding:NSUTF8StringEncoding]
164 - (void)testSetMessageHandlerBeforeRun {
168 XCTAssertThrows([
engine.binaryMessenger
169 setMessageHandlerOnChannel:
@"foo"
175 - (void)testNilSetMessageHandlerBeforeRun {
179 XCTAssertNoThrow([
engine.binaryMessenger setMessageHandlerOnChannel:
@"foo"
180 binaryMessageHandler:nil]);
183 - (void)testNotifyPluginOfDealloc {
185 OCMStub([plugin detachFromEngineForRegistrar:[OCMArg any]]);
190 [registrar publish:plugin];
192 OCMVerify([plugin detachFromEngineForRegistrar:[OCMArg any]]);
195 - (void)testGetViewControllerFromRegistrar {
198 id mockEngine = OCMPartialMock(
engine);
199 NSObject<FlutterPluginRegistrar>* registrar = [mockEngine registrarForPlugin:@"plugin"];
206 - (void)testSetBinaryMessengerToSameBinaryMessenger {
220 - (void)testRunningInitialRouteSendsNavigationMessage {
233 NSData* encodedSetInitialRouteMethod =
235 OCMVerify([mockBinaryMessenger sendOnChannel:
@"flutter/navigation"
236 message:encodedSetInitialRouteMethod]);
239 - (void)testInitialRouteSettingsSendsNavigationMessage {
243 settings.route =
"test";
253 NSData* encodedSetInitialRouteMethod =
255 OCMVerify([mockBinaryMessenger sendOnChannel:
@"flutter/navigation"
256 message:encodedSetInitialRouteMethod]);
259 - (void)testPlatformViewsControllerRenderingMetalBackend {
267 - (void)testWaitForFirstFrameTimeout {
270 XCTestExpectation* timeoutFirstFrame = [
self expectationWithDescription:@"timeoutFirstFrame"];
273 if (timeoutFirstFrame) {
274 [timeoutFirstFrame fulfill];
277 [
self waitForExpectations:@[ timeoutFirstFrame ]];
287 XCTAssertNotNil(spawn);
290 - (void)testEngineId {
293 int64_t id1 =
engine.engineIdentifier;
294 XCTAssertTrue(id1 != 0);
299 int64_t id2 = spawn.engineIdentifier;
301 XCTAssertEqual([
FlutterEngine engineForIdentifier:id2], spawn);
304 - (void)testSetHandlerAfterRun {
306 XCTestExpectation* gotMessage = [
self expectationWithDescription:@"gotMessage"];
307 dispatch_async(dispatch_get_main_queue(), ^{
309 fml::AutoResetWaitableEvent latch;
312 fml::TaskRunner::RunNowOrPostTask(
313 engine.shell.GetTaskRunners().GetUITaskRunner(), [&latch, &
shell] {
314 flutter::Engine::Delegate& delegate = shell;
315 auto message = std::make_unique<flutter::PlatformMessage>(
"foo", nullptr);
316 delegate.OnEngineHandlePlatformMessage(std::move(message));
320 [registrar.messenger setMessageHandlerOnChannel:@"foo"
321 binaryMessageHandler:^(NSData* message, FlutterBinaryReply reply) {
322 [gotMessage fulfill];
325 [
self waitForExpectations:@[ gotMessage ]];
328 - (void)testThreadPrioritySetCorrectly {
329 XCTestExpectation* prioritiesSet = [
self expectationWithDescription:@"prioritiesSet"];
330 prioritiesSet.expectedFulfillmentCount = 2;
332 IMP mockSetThreadPriority =
333 imp_implementationWithBlock(^(NSThread* thread,
double threadPriority) {
334 if ([thread.name hasSuffix:
@".raster"]) {
335 XCTAssertEqual(threadPriority, 1.0);
336 [prioritiesSet fulfill];
337 }
else if ([thread.name hasSuffix:
@".io"]) {
338 XCTAssertEqual(threadPriority, 0.5);
339 [prioritiesSet fulfill];
342 Method method = class_getInstanceMethod([NSThread
class],
@selector(setThreadPriority:));
343 IMP originalSetThreadPriority = method_getImplementation(method);
344 method_setImplementation(method, mockSetThreadPriority);
348 [
self waitForExpectations:@[ prioritiesSet ]];
350 method_setImplementation(method, originalSetThreadPriority);
353 - (void)testCanEnableDisableEmbedderAPIThroughInfoPlist {
357 settings.enable_software_rendering =
true;
360 XCTAssertFalse(
engine.enableEmbedderAPI);
364 id mockMainBundle = OCMPartialMock([NSBundle mainBundle]);
365 OCMStub([mockMainBundle objectForInfoDictionaryKey:
@"FLTEnableIOSEmbedderAPI"])
368 settings.enable_software_rendering =
true;
371 XCTAssertTrue(
engine.enableEmbedderAPI);
375 - (void)testFlutterTextInputViewDidResignFirstResponderWillCallTextInputClientConnectionClosed {
380 [engine flutterTextInputView:nil didResignFirstResponderWithTextInputClient:1];
385 OCMVerify([mockBinaryMessenger sendOnChannel:
@"flutter/textinput" message:encodedMethodCall]);
388 - (void)testFlutterEngineUpdatesDisplays {
390 id mockEngine = OCMPartialMock(
engine);
394 engine.viewController = nil;
398 - (void)testLifeCycleNotificationDidEnterBackgroundForApplication {
402 NSNotification* sceneNotification =
403 [NSNotification notificationWithName:UISceneDidEnterBackgroundNotification
406 NSNotification* applicationNotification =
407 [NSNotification notificationWithName:UIApplicationDidEnterBackgroundNotification
410 id mockEngine = OCMPartialMock(
engine);
411 [NSNotificationCenter.defaultCenter postNotification:sceneNotification];
412 [NSNotificationCenter.defaultCenter postNotification:applicationNotification];
413 OCMVerify(times(1), [mockEngine applicationDidEnterBackground:[OCMArg any]]);
414 XCTAssertTrue(
engine.isGpuDisabled);
415 BOOL gpuDisabled = NO;
416 [engine
shell].GetIsGpuDisabledSyncSwitch()->Execute(
417 fml::SyncSwitch::Handlers().SetIfTrue([&] { gpuDisabled = YES; }).SetIfFalse([&] {
420 XCTAssertTrue(gpuDisabled);
423 - (void)testLifeCycleNotificationDidEnterBackgroundForScene {
424 id mockBundle = OCMPartialMock([NSBundle mainBundle]);
425 OCMStub([mockBundle objectForInfoDictionaryKey:
@"NSExtension"]).andReturn(@{
426 @"NSExtensionPointIdentifier" :
@"com.apple.share-services"
431 NSNotification* sceneNotification =
432 [NSNotification notificationWithName:UISceneDidEnterBackgroundNotification
435 NSNotification* applicationNotification =
436 [NSNotification notificationWithName:UIApplicationDidEnterBackgroundNotification
439 id mockEngine = OCMPartialMock(
engine);
440 [NSNotificationCenter.defaultCenter postNotification:sceneNotification];
441 [NSNotificationCenter.defaultCenter postNotification:applicationNotification];
442 OCMVerify(times(1), [mockEngine sceneDidEnterBackground:[OCMArg any]]);
443 XCTAssertTrue(
engine.isGpuDisabled);
444 BOOL gpuDisabled = NO;
445 [engine
shell].GetIsGpuDisabledSyncSwitch()->Execute(
446 fml::SyncSwitch::Handlers().SetIfTrue([&] { gpuDisabled = YES; }).SetIfFalse([&] {
449 XCTAssertTrue(gpuDisabled);
450 [mockBundle stopMocking];
453 - (void)testLifeCycleNotificationWillEnterForegroundForApplication {
457 NSNotification* sceneNotification =
458 [NSNotification notificationWithName:UISceneWillEnterForegroundNotification
461 NSNotification* applicationNotification =
462 [NSNotification notificationWithName:UIApplicationWillEnterForegroundNotification
465 id mockEngine = OCMPartialMock(
engine);
466 [NSNotificationCenter.defaultCenter postNotification:sceneNotification];
467 [NSNotificationCenter.defaultCenter postNotification:applicationNotification];
468 OCMVerify(times(1), [mockEngine applicationWillEnterForeground:[OCMArg any]]);
469 XCTAssertFalse(
engine.isGpuDisabled);
470 BOOL gpuDisabled = YES;
471 [engine
shell].GetIsGpuDisabledSyncSwitch()->Execute(
472 fml::SyncSwitch::Handlers().SetIfTrue([&] { gpuDisabled = YES; }).SetIfFalse([&] {
475 XCTAssertFalse(gpuDisabled);
478 - (void)testLifeCycleNotificationWillEnterForegroundForScene {
479 id mockBundle = OCMPartialMock([NSBundle mainBundle]);
480 OCMStub([mockBundle objectForInfoDictionaryKey:
@"NSExtension"]).andReturn(@{
481 @"NSExtensionPointIdentifier" :
@"com.apple.share-services"
486 NSNotification* sceneNotification =
487 [NSNotification notificationWithName:UISceneWillEnterForegroundNotification
490 NSNotification* applicationNotification =
491 [NSNotification notificationWithName:UIApplicationWillEnterForegroundNotification
494 id mockEngine = OCMPartialMock(
engine);
495 [NSNotificationCenter.defaultCenter postNotification:sceneNotification];
496 [NSNotificationCenter.defaultCenter postNotification:applicationNotification];
497 OCMVerify(times(1), [mockEngine sceneWillEnterForeground:[OCMArg any]]);
498 XCTAssertFalse(
engine.isGpuDisabled);
499 BOOL gpuDisabled = YES;
500 [engine
shell].GetIsGpuDisabledSyncSwitch()->Execute(
501 fml::SyncSwitch::Handlers().SetIfTrue([&] { gpuDisabled = YES; }).SetIfFalse([&] {
504 XCTAssertFalse(gpuDisabled);
505 [mockBundle stopMocking];
508 - (void)testLifeCycleNotificationSceneWillConnect {
512 id mockScene = OCMClassMock([UIWindowScene
class]);
515 OCMStub([mockScene delegate]).andReturn(mockLifecycleProvider);
516 OCMStub([mockLifecycleProvider sceneLifeCycleDelegate]).andReturn(mockLifecycleDelegate);
518 NSNotification* sceneNotification =
519 [NSNotification notificationWithName:UISceneWillConnectNotification
523 [NSNotificationCenter.defaultCenter postNotification:sceneNotification];
524 OCMVerify(times(1), [mockLifecycleDelegate
engine:
engine
525 receivedConnectNotificationFor:mockScene]);
528 - (void)testSpawnsShareGpuContext {
535 XCTAssertNotNil(spawn);
536 XCTAssertTrue(
engine.platformView !=
nullptr);
538 std::shared_ptr<flutter::IOSContext> engine_context =
engine.platformView->GetIosContext();
540 XCTAssertEqual(engine_context, spawn_context);
543 - (void)testEnableSemanticsWhenFlutterViewAccessibilityDidCall {
545 engine.ensureSemanticsEnabledCalled = NO;
546 [engine flutterViewAccessibilityDidCall];
547 XCTAssertTrue(
engine.ensureSemanticsEnabledCalled);
550 - (void)testCanMergePlatformAndUIThread {
551 #if defined(TARGET_IPHONE_SIMULATOR) && TARGET_IPHONE_SIMULATOR
557 XCTAssertEqual(
engine.shell.GetTaskRunners().GetUITaskRunner(),
558 engine.shell.GetTaskRunners().GetPlatformTaskRunner());
562 - (void)testCanUnMergePlatformAndUIThread {
563 #if defined(TARGET_IPHONE_SIMULATOR) && TARGET_IPHONE_SIMULATOR
565 settings.merged_platform_ui_thread = flutter::Settings::MergedPlatformUIThread::kDisabled;
570 XCTAssertNotEqual(
engine.shell.GetTaskRunners().GetUITaskRunner(),
571 engine.shell.GetTaskRunners().GetPlatformTaskRunner());
575 - (void)testAddSceneDelegateToRegistrar {
578 id mockEngine = OCMPartialMock(
engine);
579 NSObject<FlutterPluginRegistrar>* registrar = [mockEngine registrarForPlugin:@"plugin"];
581 [registrar addSceneDelegate:mockPlugin];
583 OCMVerify(times(1), [mockEngine addSceneLifeCycleDelegate:[OCMArg any]]);
586 - (void)testNotifyAppDelegateOfEngineInitialization {
590 id mockApplication = OCMClassMock([UIApplication
class]);
591 OCMStub([mockApplication sharedApplication]).andReturn(mockApplication);
593 OCMStub([mockApplication delegate]).andReturn(mockAppDelegate);
595 [engine performImplicitEngineCallback];
596 OCMVerify(times(1), [mockAppDelegate didInitializeImplicitFlutterEngine:[OCMArg any]]);
599 - (void)testRegistrarForPlugin {
607 OCMStub([mockEngine
viewController]).andReturn(mockViewController);
610 OCMStub([mockEngine platformViewsController]).andReturn(mockPlatformViewController);
612 NSString* pluginKey =
@"plugin";
613 NSString* assetKey =
@"asset";
614 NSString* factoryKey =
@"platform_view_factory";
616 NSObject<FlutterPluginRegistrar>* registrar = [mockEngine
registrarForPlugin:pluginKey];
618 XCTAssertTrue([registrar respondsToSelector:
@selector(messenger)]);
619 XCTAssertTrue([registrar respondsToSelector:
@selector(textures)]);
620 XCTAssertTrue([registrar respondsToSelector:
@selector(registerViewFactory:withId:)]);
621 XCTAssertTrue([registrar
622 respondsToSelector:
@selector(registerViewFactory:withId:gestureRecognizersBlockingPolicy:)]);
623 XCTAssertTrue([registrar respondsToSelector:
@selector(
viewController)]);
624 XCTAssertTrue([registrar respondsToSelector:
@selector(publish:)]);
625 XCTAssertTrue([registrar respondsToSelector:
@selector(addMethodCallDelegate:channel:)]);
626 XCTAssertTrue([registrar respondsToSelector:
@selector(addApplicationDelegate:)]);
627 XCTAssertTrue([registrar respondsToSelector:
@selector(lookupKeyForAsset:)]);
628 XCTAssertTrue([registrar respondsToSelector:
@selector(lookupKeyForAsset:fromPackage:)]);
631 XCTAssertEqual(registrar.messenger, mockBinaryMessenger);
632 XCTAssertEqual(registrar.textures, mockTextureRegistry);
633 XCTAssertEqual(registrar.viewController, mockViewController);
638 [registrar registerViewFactory:mockPlatformViewFactory withId:factoryKey];
639 [registrar registerViewFactory:mockPlatformViewFactory
641 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
642 OCMVerify(times(2), [mockPlatformViewController registerViewFactory:mockPlatformViewFactory
644 gestureRecognizersBlockingPolicy:
649 [registrar publish:plugin];
653 [registrar lookupKeyForAsset:assetKey];
654 OCMVerify(times(1), [mockEngine lookupKeyForAsset:assetKey]);
655 [registrar lookupKeyForAsset:assetKey fromPackage:pluginKey];
656 OCMVerify(times(1), [mockEngine lookupKeyForAsset:assetKey fromPackage:pluginKey]);
659 - (void)testRegistrarForApplication {
667 OCMStub([mockEngine
viewController]).andReturn(mockViewController);
670 OCMStub([mockEngine platformViewsController]).andReturn(mockPlatformViewController);
672 NSString* pluginKey =
@"plugin";
673 NSString* factoryKey =
@"platform_view_factory";
675 NSObject<FlutterApplicationRegistrar>* registrar = [mockEngine registrarForApplication:pluginKey];
677 XCTAssertTrue([registrar respondsToSelector:
@selector(messenger)]);
678 XCTAssertTrue([registrar respondsToSelector:
@selector(textures)]);
679 XCTAssertTrue([registrar respondsToSelector:
@selector(registerViewFactory:withId:)]);
680 XCTAssertTrue([registrar
681 respondsToSelector:
@selector(registerViewFactory:withId:gestureRecognizersBlockingPolicy:)]);
682 XCTAssertFalse([registrar respondsToSelector:
@selector(
viewController)]);
683 XCTAssertFalse([registrar respondsToSelector:
@selector(publish:)]);
684 XCTAssertFalse([registrar respondsToSelector:
@selector(addMethodCallDelegate:channel:)]);
685 XCTAssertFalse([registrar respondsToSelector:
@selector(addApplicationDelegate:)]);
686 XCTAssertFalse([registrar respondsToSelector:
@selector(lookupKeyForAsset:)]);
687 XCTAssertFalse([registrar respondsToSelector:
@selector(lookupKeyForAsset:fromPackage:)]);
690 XCTAssertEqual(registrar.messenger, mockBinaryMessenger);
691 XCTAssertEqual(registrar.textures, mockTextureRegistry);
696 [registrar registerViewFactory:mockPlatformViewFactory withId:factoryKey];
697 [registrar registerViewFactory:mockPlatformViewFactory
699 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
700 OCMVerify(times(2), [mockPlatformViewController registerViewFactory:mockPlatformViewFactory
702 gestureRecognizersBlockingPolicy:
706 - (void)testSendDeepLinkToFrameworkTimesOut {
709 id mockEngine = OCMPartialMock(
engine);
710 id mockEngineFirstFrameCallback = [OCMArg invokeBlockWithArgs:@YES, nil];
711 OCMStub([mockEngine waitForFirstFrame:3.0 callback:mockEngineFirstFrameCallback]);
713 NSURL* url = [NSURL URLWithString:@"example.com"];
715 [mockEngine sendDeepLinkToFramework:url
716 completionHandler:^(BOOL success) {
717 XCTAssertFalse(success);
721 - (void)testSendDeepLinkToFrameworkUsingNavigationChannel {
722 NSString* urlString =
@"example.com";
723 NSURL* url = [NSURL URLWithString:urlString];
726 id mockEngine = OCMPartialMock(
engine);
727 id mockEngineFirstFrameCallback = [OCMArg invokeBlockWithArgs:@NO, nil];
728 OCMStub([mockEngine waitForFirstFrame:3.0 callback:mockEngineFirstFrameCallback]);
731 id mockNavigationChannelCallback = [OCMArg invokeBlockWithArgs:@1, nil];
732 OCMStub([mockNavigationChannel invokeMethod:
@"pushRouteInformation"
733 arguments:@{
@"location" : urlString}
734 result:mockNavigationChannelCallback]);
736 [mockEngine sendDeepLinkToFramework:url
737 completionHandler:^(BOOL success) {
738 XCTAssertTrue(success);
742 - (void)testSendDeepLinkToFrameworkUsingNavigationChannelFails {
743 NSString* urlString =
@"example.com";
744 NSURL* url = [NSURL URLWithString:urlString];
747 id mockEngine = OCMPartialMock(
engine);
748 id mockEngineFirstFrameCallback = [OCMArg invokeBlockWithArgs:@NO, nil];
749 OCMStub([mockEngine waitForFirstFrame:3.0 callback:mockEngineFirstFrameCallback]);
752 id mockNavigationChannelCallback = [OCMArg invokeBlockWithArgs:@0, nil];
753 OCMStub([mockNavigationChannel invokeMethod:
@"pushRouteInformation"
754 arguments:@{
@"location" : urlString}
755 result:mockNavigationChannelCallback]);
757 [mockEngine sendDeepLinkToFramework:url
758 completionHandler:^(BOOL success) {
759 XCTAssertFalse(success);
NS_ASSUME_NONNULL_BEGIN typedef void(^ FlutterBinaryReply)(NSData *_Nullable reply)
flutter::Settings FLTDefaultSettingsForBundle(NSBundle *bundle, NSProcessInfo *processInfoOrNil)
@ FlutterPlatformViewGestureRecognizersBlockingPolicyEager
flutter::PlatformViewIOS * platformView()
FlutterEngine * spawnWithEntrypoint:libraryURI:initialRoute:entrypointArgs:(/*nullable */NSString *entrypoint,[libraryURI]/*nullable */NSString *libraryURI,[initialRoute]/*nullable */NSString *initialRoute,[entrypointArgs]/*nullable */NSArray< NSString * > *entrypointArgs)
void setBinaryMessenger:(FlutterBinaryMessengerRelay *binaryMessenger)
FlutterMethodChannel * navigationChannel
flutter::IOSRenderingAPI platformViewsRenderingAPI()
FlutterViewController * viewController
BOOL runWithEntrypoint:initialRoute:(nullable NSString *entrypoint,[initialRoute] nullable NSString *initialRoute)
NSObject< FlutterTextureRegistry > * textureRegistry
NSMutableDictionary * pluginPublications
NSObject< FlutterBinaryMessenger > * binaryMessenger
void ensureSemanticsEnabled()
void waitForFirstFrame:callback:(NSTimeInterval timeout,[callback] void(^ callback)(BOOL didTimeout))
BOOL ensureSemanticsEnabledCalled
instancetype methodCallWithMethodName:arguments:(NSString *method,[arguments] id _Nullable arguments)
nullable NSObject< FlutterPluginRegistrar > * registrarForPlugin:(NSString *pluginKey)