5#import <Foundation/Foundation.h>
6#import <OCMock/OCMock.h>
7#import <XCTest/XCTest.h>
9#import <objc/runtime.h>
24@protocol TestFlutterPluginWithSceneEvents <NSObject, FlutterPlugin, FlutterSceneLifeCycleDelegate>
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 {
160 message:[@"bar" dataUsingEncoding:NSUTF8StringEncoding]
164- (void)testSetMessageHandlerBeforeRun {
169 setMessageHandlerOnChannel:
@"foo"
175- (void)testNilSetMessageHandlerBeforeRun {
180 binaryMessageHandler:nil]);
183- (void)testNotifyPluginOfDealloc {
184 id plugin = OCMProtocolMock(
@protocol(FlutterPlugin));
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(), ^{
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;
364 id mockMainBundle = OCMPartialMock([NSBundle mainBundle]);
365 OCMStub([mockMainBundle objectForInfoDictionaryKey:
@"FLTEnableIOSEmbedderAPI"])
368 settings.enable_software_rendering =
true;
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);
393 OCMVerify(times(1), [mockEngine updateDisplays]);
395 OCMVerify(times(2), [mockEngine updateDisplays]);
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]]);
415 BOOL gpuDisabled = NO;
416 [engine
shell].GetIsGpuDisabledSyncSwitch()->Execute(
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]]);
444 BOOL gpuDisabled = NO;
445 [engine
shell].GetIsGpuDisabledSyncSwitch()->Execute(
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]]);
470 BOOL gpuDisabled = YES;
471 [engine
shell].GetIsGpuDisabledSyncSwitch()->Execute(
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]]);
499 BOOL gpuDisabled = YES;
500 [engine
shell].GetIsGpuDisabledSyncSwitch()->Execute(
504 XCTAssertFalse(gpuDisabled);
505 [mockBundle stopMocking];
508- (void)testLifeCycleNotificationSceneWillConnect {
512 id mockScene = OCMClassMock([UIWindowScene
class]);
513 id mockLifecycleProvider = OCMProtocolMock(
@protocol(FlutterSceneLifeCycleProvider));
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);
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
562- (void)testCanUnMergePlatformAndUIThread {
563#if defined(TARGET_IPHONE_SIMULATOR) && TARGET_IPHONE_SIMULATOR
575- (void)testAddSceneDelegateToRegistrar {
578 id mockEngine = OCMPartialMock(
engine);
579 NSObject<FlutterPluginRegistrar>* registrar = [mockEngine registrarForPlugin:@"plugin"];
580 id mockPlugin = OCMProtocolMock(
@protocol(TestFlutterPluginWithSceneEvents));
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);
592 id mockAppDelegate = OCMProtocolMock(
@protocol(FlutterImplicitEngineDelegate));
593 OCMStub([mockApplication delegate]).andReturn(mockAppDelegate);
595 [engine performImplicitEngineCallback];
596 OCMVerify(times(1), [mockAppDelegate didInitializeImplicitFlutterEngine:[OCMArg any]]);
599- (void)testRegistrarForPlugin {
604 id mockBinaryMessenger = OCMProtocolMock(
@protocol(FlutterBinaryMessenger));
605 id mockTextureRegistry = OCMProtocolMock(
@protocol(FlutterTextureRegistry));
607 OCMStub([mockEngine
viewController]).andReturn(mockViewController);
608 OCMStub([mockEngine binaryMessenger]).andReturn(mockBinaryMessenger);
609 OCMStub([mockEngine textureRegistry]).andReturn(mockTextureRegistry);
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:
648 id plugin = OCMProtocolMock(
@protocol(FlutterPlugin));
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 {
664 id mockBinaryMessenger = OCMProtocolMock(
@protocol(FlutterBinaryMessenger));
665 id mockTextureRegistry = OCMProtocolMock(
@protocol(FlutterTextureRegistry));
667 OCMStub([mockEngine
viewController]).andReturn(mockViewController);
668 OCMStub([mockEngine binaryMessenger]).andReturn(mockBinaryMessenger);
669 OCMStub([mockEngine textureRegistry]).andReturn(mockTextureRegistry);
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]);
730 OCMStub([mockEngine navigationChannel]).andReturn(mockNavigationChannel);
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]);
751 OCMStub([mockEngine navigationChannel]).andReturn(mockNavigationChannel);
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)
@ FlutterPlatformViewGestureRecognizersBlockingPolicyEager
const TaskRunners & GetTaskRunners() const override
If callers wish to interact directly with any shell subcomponents, they must (on the platform thread)...
fml::RefPtr< fml::TaskRunner > GetUITaskRunner() const
fml::RefPtr< fml::TaskRunner > GetPlatformTaskRunner() const
static void RunNowOrPostTask(const fml::RefPtr< fml::TaskRunner > &runner, const fml::closure &task)
G_BEGIN_DECLS GBytes * message
HWND(* FlutterPlatformViewFactory)(const FlutterPlatformViewCreationParameters *)
FlutterDesktopBinaryReply callback
FlutterEngine * spawnWithEntrypoint:libraryURI:initialRoute:entrypointArgs:(nullable NSString *entrypoint,[libraryURI] nullable NSString *libraryURI,[initialRoute] nullable NSString *initialRoute,[entrypointArgs] nullable NSArray< NSString * > *entrypointArgs)
NSObject< FlutterBinaryMessenger > * binaryMessenger
flutter::PlatformViewIOS * platformView()
void setBinaryMessenger:(FlutterBinaryMessengerRelay *binaryMessenger)
flutter::IOSRenderingAPI platformViewsRenderingAPI()
BOOL runWithEntrypoint:initialRoute:(nullable NSString *entrypoint,[initialRoute] nullable NSString *initialRoute)
NSMutableDictionary * pluginPublications
void ensureSemanticsEnabled()
FlutterViewController * viewController
void waitForFirstFrame:callback:(NSTimeInterval timeout,[callback] void(^ callback)(BOOL didTimeout))
BOOL ensureSemanticsEnabledCalled
instancetype methodCallWithMethodName:arguments:(NSString *method,[arguments] id _Nullable arguments)
flutter::Settings FLTDefaultSettingsForBundle(NSBundle *bundle, NSProcessInfo *processInfoOrNil)
FlutterViewController * viewController
nullable NSObject< FlutterPluginRegistrar > * registrarForPlugin:(NSString *pluginKey)
Represents the 2 code paths available when calling |SyncSwitchExecute|.