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"
30 _ensureSemanticsEnabledCalled = YES;
44 @property(nonatomic, assign) BOOL failOnDealloc;
50 XCTFail(
"FakeBinaryMessageRelay should not be deallocated");
72 - (void)testShellGetters {
78 XCTAssertEqual(
engine.platformTaskRunner.get(),
nullptr);
79 XCTAssertEqual(
engine.uiTaskRunner.get(),
nullptr);
80 XCTAssertEqual(
engine.rasterTaskRunner.get(),
nullptr);
83 - (void)testInfoPlist {
85 NSURL* flutterFrameworkURL =
86 [NSBundle.mainBundle.privateFrameworksURL URLByAppendingPathComponent:@"Flutter.framework"];
87 NSBundle* flutterBundle = [NSBundle bundleWithURL:flutterFrameworkURL];
88 XCTAssertEqualObjects(flutterBundle.bundleIdentifier,
@"io.flutter.flutter");
90 NSDictionary<NSString*, id>* infoDictionary = flutterBundle.infoDictionary;
93 NSError* regexError = NULL;
94 NSRegularExpression* osVersionRegex =
95 [NSRegularExpression regularExpressionWithPattern:@"((0|[1-9]\\d*)\\.)*(0|[1-9]\\d*)"
96 options:NSRegularExpressionCaseInsensitive
98 XCTAssertNil(regexError);
101 NSString* testString =
@"9";
102 NSUInteger versionMatches =
103 [osVersionRegex numberOfMatchesInString:testString
104 options:NSMatchingAnchored
105 range:NSMakeRange(0, testString.length)];
106 XCTAssertEqual(versionMatches, 1UL);
108 versionMatches = [osVersionRegex numberOfMatchesInString:testString
109 options:NSMatchingAnchored
110 range:NSMakeRange(0, testString.length)];
111 XCTAssertEqual(versionMatches, 1UL);
112 testString =
@"9.0.1";
113 versionMatches = [osVersionRegex numberOfMatchesInString:testString
114 options:NSMatchingAnchored
115 range:NSMakeRange(0, testString.length)];
116 XCTAssertEqual(versionMatches, 1UL);
117 testString =
@".0.1";
118 versionMatches = [osVersionRegex numberOfMatchesInString:testString
119 options:NSMatchingAnchored
120 range:NSMakeRange(0, testString.length)];
121 XCTAssertEqual(versionMatches, 0UL);
124 NSString* minimumOSVersion = infoDictionary[@"MinimumOSVersion"];
125 versionMatches = [osVersionRegex numberOfMatchesInString:minimumOSVersion
126 options:NSMatchingAnchored
127 range:NSMakeRange(0, minimumOSVersion.length)];
128 XCTAssertEqual(versionMatches, 1UL);
131 XCTAssertEqual(((NSString*)infoDictionary[
@"FlutterEngine"]).length, 40UL);
136 XCTAssertTrue(((NSString*)infoDictionary[
@"ClangVersion"]).length > 15UL);
139 - (void)testDeallocated {
145 XCTAssertNotNil(weakEngine);
147 XCTAssertNil(weakEngine);
150 - (void)testSendMessageBeforeRun {
154 XCTAssertThrows([
engine.binaryMessenger
156 message:[
@"bar" dataUsingEncoding:NSUTF8StringEncoding]
160 - (void)testSetMessageHandlerBeforeRun {
164 XCTAssertThrows([
engine.binaryMessenger
165 setMessageHandlerOnChannel:
@"foo"
171 - (void)testNilSetMessageHandlerBeforeRun {
175 XCTAssertNoThrow([
engine.binaryMessenger setMessageHandlerOnChannel:
@"foo"
176 binaryMessageHandler:nil]);
179 - (void)testNotifyPluginOfDealloc {
181 OCMStub([plugin detachFromEngineForRegistrar:[OCMArg any]]);
186 [registrar publish:plugin];
189 OCMVerify([plugin detachFromEngineForRegistrar:[OCMArg any]]);
192 - (void)testGetViewControllerFromRegistrar {
195 id mockEngine = OCMPartialMock(
engine);
196 NSObject<FlutterPluginRegistrar>* registrar = [mockEngine registrarForPlugin:@"plugin"];
203 - (void)testSetBinaryMessengerToSameBinaryMessenger {
217 - (void)testRunningInitialRouteSendsNavigationMessage {
230 NSData* encodedSetInitialRouteMethod =
232 OCMVerify([mockBinaryMessenger sendOnChannel:
@"flutter/navigation"
233 message:encodedSetInitialRouteMethod]);
236 - (void)testInitialRouteSettingsSendsNavigationMessage {
240 settings.route =
"test";
250 NSData* encodedSetInitialRouteMethod =
252 OCMVerify([mockBinaryMessenger sendOnChannel:
@"flutter/navigation"
253 message:encodedSetInitialRouteMethod]);
256 - (void)testPlatformViewsControllerRenderingMetalBackend {
264 - (void)testWaitForFirstFrameTimeout {
267 XCTestExpectation* timeoutFirstFrame = [
self expectationWithDescription:@"timeoutFirstFrame"];
270 if (timeoutFirstFrame) {
271 [timeoutFirstFrame fulfill];
274 [
self waitForExpectations:@[ timeoutFirstFrame ]];
284 XCTAssertNotNil(spawn);
287 - (void)testEngineId {
290 int64_t id1 =
engine.engineIdentifier;
291 XCTAssertTrue(id1 != 0);
296 int64_t id2 = spawn.engineIdentifier;
298 XCTAssertEqual([
FlutterEngine engineForIdentifier:id2], spawn);
301 - (void)testSetHandlerAfterRun {
303 XCTestExpectation* gotMessage = [
self expectationWithDescription:@"gotMessage"];
304 dispatch_async(dispatch_get_main_queue(), ^{
306 fml::AutoResetWaitableEvent latch;
309 fml::TaskRunner::RunNowOrPostTask(
310 engine.shell.GetTaskRunners().GetUITaskRunner(), [&latch, &
shell] {
311 flutter::Engine::Delegate& delegate = shell;
312 auto message = std::make_unique<flutter::PlatformMessage>(
"foo", nullptr);
313 delegate.OnEngineHandlePlatformMessage(std::move(message));
317 [registrar.messenger setMessageHandlerOnChannel:@"foo"
318 binaryMessageHandler:^(NSData* message, FlutterBinaryReply reply) {
319 [gotMessage fulfill];
322 [
self waitForExpectations:@[ gotMessage ]];
325 - (void)testThreadPrioritySetCorrectly {
326 XCTestExpectation* prioritiesSet = [
self expectationWithDescription:@"prioritiesSet"];
327 prioritiesSet.expectedFulfillmentCount = 2;
329 IMP mockSetThreadPriority =
330 imp_implementationWithBlock(^(NSThread* thread,
double threadPriority) {
331 if ([thread.name hasSuffix:
@".raster"]) {
332 XCTAssertEqual(threadPriority, 1.0);
333 [prioritiesSet fulfill];
334 }
else if ([thread.name hasSuffix:
@".io"]) {
335 XCTAssertEqual(threadPriority, 0.5);
336 [prioritiesSet fulfill];
339 Method method = class_getInstanceMethod([NSThread
class],
@selector(setThreadPriority:));
340 IMP originalSetThreadPriority = method_getImplementation(method);
341 method_setImplementation(method, mockSetThreadPriority);
345 [
self waitForExpectations:@[ prioritiesSet ]];
347 method_setImplementation(method, originalSetThreadPriority);
350 - (void)testCanEnableDisableEmbedderAPIThroughInfoPlist {
354 settings.enable_software_rendering =
true;
357 XCTAssertFalse(
engine.enableEmbedderAPI);
361 id mockMainBundle = OCMPartialMock([NSBundle mainBundle]);
362 OCMStub([mockMainBundle objectForInfoDictionaryKey:
@"FLTEnableIOSEmbedderAPI"])
365 settings.enable_software_rendering =
true;
368 XCTAssertTrue(
engine.enableEmbedderAPI);
372 - (void)testFlutterTextInputViewDidResignFirstResponderWillCallTextInputClientConnectionClosed {
377 [engine flutterTextInputView:nil didResignFirstResponderWithTextInputClient:1];
382 OCMVerify([mockBinaryMessenger sendOnChannel:
@"flutter/textinput" message:encodedMethodCall]);
385 - (void)testFlutterEngineUpdatesDisplays {
387 id mockEngine = OCMPartialMock(
engine);
391 engine.viewController = nil;
395 - (void)testLifeCycleNotificationDidEnterBackgroundForApplication {
399 NSNotification* sceneNotification =
400 [NSNotification notificationWithName:UISceneDidEnterBackgroundNotification
403 NSNotification* applicationNotification =
404 [NSNotification notificationWithName:UIApplicationDidEnterBackgroundNotification
407 id mockEngine = OCMPartialMock(
engine);
408 [NSNotificationCenter.defaultCenter postNotification:sceneNotification];
409 [NSNotificationCenter.defaultCenter postNotification:applicationNotification];
410 OCMVerify(times(1), [mockEngine applicationDidEnterBackground:[OCMArg any]]);
411 XCTAssertTrue(
engine.isGpuDisabled);
412 BOOL gpuDisabled = NO;
413 [engine
shell].GetIsGpuDisabledSyncSwitch()->Execute(
414 fml::SyncSwitch::Handlers().SetIfTrue([&] { gpuDisabled = YES; }).SetIfFalse([&] {
417 XCTAssertTrue(gpuDisabled);
420 - (void)testLifeCycleNotificationDidEnterBackgroundForScene {
421 id mockBundle = OCMPartialMock([NSBundle mainBundle]);
422 OCMStub([mockBundle objectForInfoDictionaryKey:
@"NSExtension"]).andReturn(@{
423 @"NSExtensionPointIdentifier" :
@"com.apple.share-services"
428 NSNotification* sceneNotification =
429 [NSNotification notificationWithName:UISceneDidEnterBackgroundNotification
432 NSNotification* applicationNotification =
433 [NSNotification notificationWithName:UIApplicationDidEnterBackgroundNotification
436 id mockEngine = OCMPartialMock(
engine);
437 [NSNotificationCenter.defaultCenter postNotification:sceneNotification];
438 [NSNotificationCenter.defaultCenter postNotification:applicationNotification];
439 OCMVerify(times(1), [mockEngine sceneDidEnterBackground:[OCMArg any]]);
440 XCTAssertTrue(
engine.isGpuDisabled);
441 BOOL gpuDisabled = NO;
442 [engine
shell].GetIsGpuDisabledSyncSwitch()->Execute(
443 fml::SyncSwitch::Handlers().SetIfTrue([&] { gpuDisabled = YES; }).SetIfFalse([&] {
446 XCTAssertTrue(gpuDisabled);
447 [mockBundle stopMocking];
450 - (void)testLifeCycleNotificationWillEnterForegroundForApplication {
454 NSNotification* sceneNotification =
455 [NSNotification notificationWithName:UISceneWillEnterForegroundNotification
458 NSNotification* applicationNotification =
459 [NSNotification notificationWithName:UIApplicationWillEnterForegroundNotification
462 id mockEngine = OCMPartialMock(
engine);
463 [NSNotificationCenter.defaultCenter postNotification:sceneNotification];
464 [NSNotificationCenter.defaultCenter postNotification:applicationNotification];
465 OCMVerify(times(1), [mockEngine applicationWillEnterForeground:[OCMArg any]]);
466 XCTAssertFalse(
engine.isGpuDisabled);
467 BOOL gpuDisabled = YES;
468 [engine
shell].GetIsGpuDisabledSyncSwitch()->Execute(
469 fml::SyncSwitch::Handlers().SetIfTrue([&] { gpuDisabled = YES; }).SetIfFalse([&] {
472 XCTAssertFalse(gpuDisabled);
475 - (void)testLifeCycleNotificationWillEnterForegroundForScene {
476 id mockBundle = OCMPartialMock([NSBundle mainBundle]);
477 OCMStub([mockBundle objectForInfoDictionaryKey:
@"NSExtension"]).andReturn(@{
478 @"NSExtensionPointIdentifier" :
@"com.apple.share-services"
483 NSNotification* sceneNotification =
484 [NSNotification notificationWithName:UISceneWillEnterForegroundNotification
487 NSNotification* applicationNotification =
488 [NSNotification notificationWithName:UIApplicationWillEnterForegroundNotification
491 id mockEngine = OCMPartialMock(
engine);
492 [NSNotificationCenter.defaultCenter postNotification:sceneNotification];
493 [NSNotificationCenter.defaultCenter postNotification:applicationNotification];
494 OCMVerify(times(1), [mockEngine sceneWillEnterForeground:[OCMArg any]]);
495 XCTAssertFalse(
engine.isGpuDisabled);
496 BOOL gpuDisabled = YES;
497 [engine
shell].GetIsGpuDisabledSyncSwitch()->Execute(
498 fml::SyncSwitch::Handlers().SetIfTrue([&] { gpuDisabled = YES; }).SetIfFalse([&] {
501 XCTAssertFalse(gpuDisabled);
502 [mockBundle stopMocking];
505 - (void)testSpawnsShareGpuContext {
512 XCTAssertNotNil(spawn);
513 XCTAssertTrue(
engine.platformView !=
nullptr);
515 std::shared_ptr<flutter::IOSContext> engine_context =
engine.platformView->GetIosContext();
517 XCTAssertEqual(engine_context, spawn_context);
520 - (void)testEnableSemanticsWhenFlutterViewAccessibilityDidCall {
522 engine.ensureSemanticsEnabledCalled = NO;
523 [engine flutterViewAccessibilityDidCall];
524 XCTAssertTrue(
engine.ensureSemanticsEnabledCalled);
527 - (void)testCanMergePlatformAndUIThread {
528 #if defined(TARGET_IPHONE_SIMULATOR) && TARGET_IPHONE_SIMULATOR
534 XCTAssertEqual(
engine.shell.GetTaskRunners().GetUITaskRunner(),
535 engine.shell.GetTaskRunners().GetPlatformTaskRunner());
539 - (void)testCanUnMergePlatformAndUIThread {
540 #if defined(TARGET_IPHONE_SIMULATOR) && TARGET_IPHONE_SIMULATOR
542 settings.merged_platform_ui_thread = flutter::Settings::MergedPlatformUIThread::kDisabled;
547 XCTAssertNotEqual(
engine.shell.GetTaskRunners().GetUITaskRunner(),
548 engine.shell.GetTaskRunners().GetPlatformTaskRunner());
NS_ASSUME_NONNULL_BEGIN typedef void(^ FlutterBinaryReply)(NSData *_Nullable reply)
flutter::Settings FLTDefaultSettingsForBundle(NSBundle *bundle, NSProcessInfo *processInfoOrNil)
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)
flutter::IOSRenderingAPI platformViewsRenderingAPI()
FlutterViewController * viewController
BOOL runWithEntrypoint:initialRoute:(nullable NSString *entrypoint,[initialRoute] nullable NSString *initialRoute)
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)