| Flutter iOS Embedder |

FlutterEngineTest.mm
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #import <Foundation/Foundation.h>
6 #import <OCMock/OCMock.h>
7 #import <XCTest/XCTest.h>
8
9 #import <objc/runtime.h>
10
11 #import "flutter/common/settings.h"
12 #include "flutter/fml/synchronization/sync_switch.h"
13 #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h"
14 #import "flutter/shell/platform/darwin/common/framework/Source/FlutterBinaryMessengerRelay.h"
15 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterDartProject_Internal.h"
16 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h"
17 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Test.h"
18 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycle_Internal.h"
19 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterSharedApplication.h"
20 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h"
21 #import "flutter/shell/platform/darwin/ios/platform_view_ios.h"
22 FLUTTER_ASSERT_ARC
23
24 @protocol TestFlutterPluginWithSceneEvents <NSObject, FlutterPlugin, FlutterSceneLifeCycleDelegate>
25 @end
26
27 @interface FlutterEngineSpy : FlutterEngine
28 @property(nonatomic) BOOL ensureSemanticsEnabledCalled;
29 @end
30
31 @implementation FlutterEngineSpy
32
33 - (void)ensureSemanticsEnabled {
34 _ensureSemanticsEnabledCalled = YES;
35 }
36
37 @end
38
39 @interface FlutterEngine () <FlutterTextInputDelegate>
40
41 @end
42
43 /// FlutterBinaryMessengerRelay used for testing that setting FlutterEngine.binaryMessenger to
44 /// the current instance doesn't trigger a use-after-free bug.
45 ///
46 /// See: testSetBinaryMessengerToSameBinaryMessenger
47 @interface FakeBinaryMessengerRelay : FlutterBinaryMessengerRelay
48 @property(nonatomic, assign) BOOL failOnDealloc;
49 @end
50
51 @implementation FakeBinaryMessengerRelay
52 - (void)dealloc {
53 if (_failOnDealloc) {
54 XCTFail("FakeBinaryMessageRelay should not be deallocated");
55 }
56 }
57 @end
58
59 @interface FlutterEngineTest : XCTestCase
60 @end
61
62 @implementation FlutterEngineTest
63
64 - (void)setUp {
65 }
66
67 - (void)tearDown {
68 }
69
70 - (void)testCreate {
71 FlutterDartProject* project = [[FlutterDartProject alloc] init];
72 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
73 XCTAssertNotNil(engine);
74 }
75
76 - (void)testShellGetters {
77 FlutterDartProject* project = [[FlutterDartProject alloc] init];
78 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
79 XCTAssertNotNil(engine);
80
81 // Ensure getters don't deref _shell when it's null, and instead return nullptr.
82 XCTAssertEqual(engine.platformTaskRunner.get(), nullptr);
83 XCTAssertEqual(engine.uiTaskRunner.get(), nullptr);
84 XCTAssertEqual(engine.rasterTaskRunner.get(), nullptr);
85 }
86
87 - (void)testInfoPlist {
88 // Check the embedded Flutter.framework Info.plist, not the linked dylib.
89 NSURL* flutterFrameworkURL =
90 [NSBundle.mainBundle.privateFrameworksURL URLByAppendingPathComponent:@"Flutter.framework"];
91 NSBundle* flutterBundle = [NSBundle bundleWithURL:flutterFrameworkURL];
92 XCTAssertEqualObjects(flutterBundle.bundleIdentifier, @"io.flutter.flutter");
93
94 NSDictionary<NSString*, id>* infoDictionary = flutterBundle.infoDictionary;
95
96 // OS version can have one, two, or three digits: "8", "8.0", "8.0.0"
97 NSError* regexError = NULL;
98 NSRegularExpression* osVersionRegex =
99 [NSRegularExpression regularExpressionWithPattern:@"((0|[1-9]\\d*)\\.)*(0|[1-9]\\d*)"
100 options:NSRegularExpressionCaseInsensitive
101 error:&regexError];
102 XCTAssertNil(regexError);
103
104 // Smoke test the test regex.
105 NSString* testString = @"9";
106 NSUInteger versionMatches =
107 [osVersionRegex numberOfMatchesInString:testString
108 options:NSMatchingAnchored
109 range:NSMakeRange(0, testString.length)];
110 XCTAssertEqual(versionMatches, 1UL);
111 testString = @"9.1";
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);
126
127 // Test Info.plist values.
128 NSString* minimumOSVersion = infoDictionary[@"MinimumOSVersion"];
129 versionMatches = [osVersionRegex numberOfMatchesInString:minimumOSVersion
130 options:NSMatchingAnchored
131 range:NSMakeRange(0, minimumOSVersion.length)];
132 XCTAssertEqual(versionMatches, 1UL);
133
134 // SHA length is 40.
135 XCTAssertEqual(((NSString*)infoDictionary[@"FlutterEngine"]).length, 40UL);
136
137 // {clang_version} placeholder is 15 characters. The clang string version
138 // is longer than that, so check if the placeholder has been replaced, without
139 // actually checking a literal string, which could be different on various machines.
140 XCTAssertTrue(((NSString*)infoDictionary[@"ClangVersion"]).length > 15UL);
141 }
142
143 - (void)testDeallocated {
144 __weak FlutterEngine* weakEngine = nil;
145 @autoreleasepool {
146 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar"];
147 weakEngine = engine;
148 [engine run];
149 XCTAssertNotNil(weakEngine);
150 }
151 XCTAssertNil(weakEngine);
152 }
153
154 - (void)testSendMessageBeforeRun {
155 FlutterDartProject* project = [[FlutterDartProject alloc] init];
156 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
157 XCTAssertNotNil(engine);
158 XCTAssertThrows([engine.binaryMessenger
159 sendOnChannel:@"foo"
160 message:[@"bar" dataUsingEncoding:NSUTF8StringEncoding]
161 binaryReply:nil]);
162 }
163
164 - (void)testSetMessageHandlerBeforeRun {
165 FlutterDartProject* project = [[FlutterDartProject alloc] init];
166 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
167 XCTAssertNotNil(engine);
168 XCTAssertThrows([engine.binaryMessenger
169 setMessageHandlerOnChannel:@"foo"
170 binaryMessageHandler:^(NSData* _Nullable message, FlutterBinaryReply _Nonnull reply){
171
172 }]);
173 }
174
175 - (void)testNilSetMessageHandlerBeforeRun {
176 FlutterDartProject* project = [[FlutterDartProject alloc] init];
177 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
178 XCTAssertNotNil(engine);
179 XCTAssertNoThrow([engine.binaryMessenger setMessageHandlerOnChannel:@"foo"
180 binaryMessageHandler:nil]);
181 }
182
183 - (void)testNotifyPluginOfDealloc {
184 id plugin = OCMProtocolMock(@protocol(FlutterPlugin));
185 OCMStub([plugin detachFromEngineForRegistrar:[OCMArg any]]);
186 @autoreleasepool {
187 FlutterDartProject* project = [[FlutterDartProject alloc] init];
188 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"engine" project:project];
189 NSObject<FlutterPluginRegistrar>* registrar = [engine registrarForPlugin:@"plugin"];
190 [registrar publish:plugin];
191 }
192 OCMVerify([plugin detachFromEngineForRegistrar:[OCMArg any]]);
193 }
194
195 - (void)testGetViewControllerFromRegistrar {
196 FlutterDartProject* project = [[FlutterDartProject alloc] init];
197 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"engine" project:project];
198 id mockEngine = OCMPartialMock(engine);
199 NSObject<FlutterPluginRegistrar>* registrar = [mockEngine registrarForPlugin:@"plugin"];
200
201 // Verify accessing the viewController getter calls FlutterEngine.viewController.
202 (void)[registrar viewController];
203 OCMVerify(times(1), [mockEngine viewController]);
204 }
205
206 - (void)testSetBinaryMessengerToSameBinaryMessenger {
207 FakeBinaryMessengerRelay* fakeBinaryMessenger = [[FakeBinaryMessengerRelay alloc] init];
208
209 FlutterEngine* engine = [[FlutterEngine alloc] init];
210 [engine setBinaryMessenger:fakeBinaryMessenger];
211
212 // Verify that the setter doesn't free the old messenger before setting the new messenger.
213 fakeBinaryMessenger.failOnDealloc = YES;
214 [engine setBinaryMessenger:fakeBinaryMessenger];
215
216 // Don't fail when ARC releases the binary messenger.
217 fakeBinaryMessenger.failOnDealloc = NO;
218 }
219
220 - (void)testRunningInitialRouteSendsNavigationMessage {
221 id mockBinaryMessenger = OCMClassMock([FlutterBinaryMessengerRelay class]);
222
223 FlutterEngine* engine = [[FlutterEngine alloc] init];
224 [engine setBinaryMessenger:mockBinaryMessenger];
225
226 // Run with an initial route.
227 [engine runWithEntrypoint:FlutterDefaultDartEntrypoint initialRoute:@"test"];
228
229 // Now check that an encoded method call has been made on the binary messenger to set the
230 // initial route to "test".
231 FlutterMethodCall* setInitialRouteMethodCall =
232 [FlutterMethodCall methodCallWithMethodName:@"setInitialRoute" arguments:@"test"];
233 NSData* encodedSetInitialRouteMethod =
234 [[FlutterJSONMethodCodec sharedInstance] encodeMethodCall:setInitialRouteMethodCall];
235 OCMVerify([mockBinaryMessenger sendOnChannel:@"flutter/navigation"
236 message:encodedSetInitialRouteMethod]);
237 }
238
239 - (void)testInitialRouteSettingsSendsNavigationMessage {
240 id mockBinaryMessenger = OCMClassMock([FlutterBinaryMessengerRelay class]);
241
242 auto settings = FLTDefaultSettingsForBundle();
243 settings.route = "test";
244 FlutterDartProject* project = [[FlutterDartProject alloc] initWithSettings:settings];
245 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
246 [engine setBinaryMessenger:mockBinaryMessenger];
247 [engine run];
248
249 // Now check that an encoded method call has been made on the binary messenger to set the
250 // initial route to "test".
251 FlutterMethodCall* setInitialRouteMethodCall =
252 [FlutterMethodCall methodCallWithMethodName:@"setInitialRoute" arguments:@"test"];
253 NSData* encodedSetInitialRouteMethod =
254 [[FlutterJSONMethodCodec sharedInstance] encodeMethodCall:setInitialRouteMethodCall];
255 OCMVerify([mockBinaryMessenger sendOnChannel:@"flutter/navigation"
256 message:encodedSetInitialRouteMethod]);
257 }
258
259 - (void)testPlatformViewsControllerRenderingMetalBackend {
260 FlutterEngine* engine = [[FlutterEngine alloc] init];
261 [engine run];
262 flutter::IOSRenderingAPI renderingApi = [engine platformViewsRenderingAPI];
263
264 XCTAssertEqual(renderingApi, flutter::IOSRenderingAPI::kMetal);
265 }
266
267 - (void)testWaitForFirstFrameTimeout {
268 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar"];
269 [engine run];
270 XCTestExpectation* timeoutFirstFrame = [self expectationWithDescription:@"timeoutFirstFrame"];
271 [engine waitForFirstFrame:0.1
272 callback:^(BOOL didTimeout) {
273 if (timeoutFirstFrame) {
274 [timeoutFirstFrame fulfill];
275 }
276 }];
277 [self waitForExpectations:@[ timeoutFirstFrame ]];
278 }
279
280 - (void)testSpawn {
281 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar"];
282 [engine run];
283 FlutterEngine* spawn = [engine spawnWithEntrypoint:nil
284 libraryURI:nil
285 initialRoute:nil
286 entrypointArgs:nil];
287 XCTAssertNotNil(spawn);
288 }
289
290 - (void)testEngineId {
291 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar"];
292 [engine run];
293 int64_t id1 = engine.engineIdentifier;
294 XCTAssertTrue(id1 != 0);
295 FlutterEngine* spawn = [engine spawnWithEntrypoint:nil
296 libraryURI:nil
297 initialRoute:nil
298 entrypointArgs:nil];
299 int64_t id2 = spawn.engineIdentifier;
300 XCTAssertEqual([FlutterEngine engineForIdentifier:id1], engine);
301 XCTAssertEqual([FlutterEngine engineForIdentifier:id2], spawn);
302 }
303
304 - (void)testSetHandlerAfterRun {
305 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar"];
306 XCTestExpectation* gotMessage = [self expectationWithDescription:@"gotMessage"];
307 dispatch_async(dispatch_get_main_queue(), ^{
308 NSObject<FlutterPluginRegistrar>* registrar = [engine registrarForPlugin:@"foo"];
309 fml::AutoResetWaitableEvent latch;
310 [engine run];
311 flutter::Shell& shell = engine.shell;
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));
317 latch.Signal();
318 });
319 latch.Wait();
320 [registrar.messenger setMessageHandlerOnChannel:@"foo"
321 binaryMessageHandler:^(NSData* message, FlutterBinaryReply reply) {
322 [gotMessage fulfill];
323 }];
324 });
325 [self waitForExpectations:@[ gotMessage ]];
326 }
327
328 - (void)testThreadPrioritySetCorrectly {
329 XCTestExpectation* prioritiesSet = [self expectationWithDescription:@"prioritiesSet"];
330 prioritiesSet.expectedFulfillmentCount = 2;
331
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];
340 }
341 });
342 Method method = class_getInstanceMethod([NSThread class], @selector(setThreadPriority:));
343 IMP originalSetThreadPriority = method_getImplementation(method);
344 method_setImplementation(method, mockSetThreadPriority);
345
346 FlutterEngine* engine = [[FlutterEngine alloc] init];
347 [engine run];
348 [self waitForExpectations:@[ prioritiesSet ]];
349
350 method_setImplementation(method, originalSetThreadPriority);
351 }
352
353 - (void)testCanEnableDisableEmbedderAPIThroughInfoPlist {
354 {
355 // Not enable embedder API by default
356 auto settings = FLTDefaultSettingsForBundle();
357 settings.enable_software_rendering = true;
358 FlutterDartProject* project = [[FlutterDartProject alloc] initWithSettings:settings];
359 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
360 XCTAssertFalse(engine.enableEmbedderAPI);
361 }
362 {
363 // Enable embedder api
364 id mockMainBundle = OCMPartialMock([NSBundle mainBundle]);
365 OCMStub([mockMainBundle objectForInfoDictionaryKey:@"FLTEnableIOSEmbedderAPI"])
366 .andReturn(@"YES");
367 auto settings = FLTDefaultSettingsForBundle();
368 settings.enable_software_rendering = true;
369 FlutterDartProject* project = [[FlutterDartProject alloc] initWithSettings:settings];
370 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
371 XCTAssertTrue(engine.enableEmbedderAPI);
372 }
373 }
374
375 - (void)testFlutterTextInputViewDidResignFirstResponderWillCallTextInputClientConnectionClosed {
376 id mockBinaryMessenger = OCMClassMock([FlutterBinaryMessengerRelay class]);
377 FlutterEngine* engine = [[FlutterEngine alloc] init];
378 [engine setBinaryMessenger:mockBinaryMessenger];
379 [engine runWithEntrypoint:FlutterDefaultDartEntrypoint initialRoute:@"test"];
380 [engine flutterTextInputView:nil didResignFirstResponderWithTextInputClient:1];
381 FlutterMethodCall* methodCall =
382 [FlutterMethodCall methodCallWithMethodName:@"TextInputClient.onConnectionClosed"
383 arguments:@[ @(1) ]];
384 NSData* encodedMethodCall = [[FlutterJSONMethodCodec sharedInstance] encodeMethodCall:methodCall];
385 OCMVerify([mockBinaryMessenger sendOnChannel:@"flutter/textinput" message:encodedMethodCall]);
386 }
387
388 - (void)testFlutterEngineUpdatesDisplays {
389 FlutterEngine* engine = [[FlutterEngine alloc] init];
390 id mockEngine = OCMPartialMock(engine);
391
392 [engine run];
393 OCMVerify(times(1), [mockEngine updateDisplays]);
394 engine.viewController = nil;
395 OCMVerify(times(2), [mockEngine updateDisplays]);
396 }
397
398 - (void)testLifeCycleNotificationDidEnterBackgroundForApplication {
399 FlutterDartProject* project = [[FlutterDartProject alloc] init];
400 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
401 [engine run];
402 NSNotification* sceneNotification =
403 [NSNotification notificationWithName:UISceneDidEnterBackgroundNotification
404 object:nil
405 userInfo:nil];
406 NSNotification* applicationNotification =
407 [NSNotification notificationWithName:UIApplicationDidEnterBackgroundNotification
408 object:nil
409 userInfo:nil];
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([&] {
418 gpuDisabled = NO;
419 }));
420 XCTAssertTrue(gpuDisabled);
421 }
422
423 - (void)testLifeCycleNotificationDidEnterBackgroundForScene {
424 id mockBundle = OCMPartialMock([NSBundle mainBundle]);
425 OCMStub([mockBundle objectForInfoDictionaryKey:@"NSExtension"]).andReturn(@{
426 @"NSExtensionPointIdentifier" : @"com.apple.share-services"
427 });
428 FlutterDartProject* project = [[FlutterDartProject alloc] init];
429 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
430 [engine run];
431 NSNotification* sceneNotification =
432 [NSNotification notificationWithName:UISceneDidEnterBackgroundNotification
433 object:nil
434 userInfo:nil];
435 NSNotification* applicationNotification =
436 [NSNotification notificationWithName:UIApplicationDidEnterBackgroundNotification
437 object:nil
438 userInfo:nil];
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([&] {
447 gpuDisabled = NO;
448 }));
449 XCTAssertTrue(gpuDisabled);
450 [mockBundle stopMocking];
451 }
452
453 - (void)testLifeCycleNotificationWillEnterForegroundForApplication {
454 FlutterDartProject* project = [[FlutterDartProject alloc] init];
455 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
456 [engine run];
457 NSNotification* sceneNotification =
458 [NSNotification notificationWithName:UISceneWillEnterForegroundNotification
459 object:nil
460 userInfo:nil];
461 NSNotification* applicationNotification =
462 [NSNotification notificationWithName:UIApplicationWillEnterForegroundNotification
463 object:nil
464 userInfo:nil];
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([&] {
473 gpuDisabled = NO;
474 }));
475 XCTAssertFalse(gpuDisabled);
476 }
477
478 - (void)testLifeCycleNotificationWillEnterForegroundForScene {
479 id mockBundle = OCMPartialMock([NSBundle mainBundle]);
480 OCMStub([mockBundle objectForInfoDictionaryKey:@"NSExtension"]).andReturn(@{
481 @"NSExtensionPointIdentifier" : @"com.apple.share-services"
482 });
483 FlutterDartProject* project = [[FlutterDartProject alloc] init];
484 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
485 [engine run];
486 NSNotification* sceneNotification =
487 [NSNotification notificationWithName:UISceneWillEnterForegroundNotification
488 object:nil
489 userInfo:nil];
490 NSNotification* applicationNotification =
491 [NSNotification notificationWithName:UIApplicationWillEnterForegroundNotification
492 object:nil
493 userInfo:nil];
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([&] {
502 gpuDisabled = NO;
503 }));
504 XCTAssertFalse(gpuDisabled);
505 [mockBundle stopMocking];
506 }
507
508 - (void)testLifeCycleNotificationSceneWillConnect {
509 FlutterDartProject* project = [[FlutterDartProject alloc] init];
510 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
511 [engine run];
512 id mockScene = OCMClassMock([UIWindowScene class]);
513 id mockLifecycleProvider = OCMProtocolMock(@protocol(FlutterSceneLifeCycleProvider));
514 id mockLifecycleDelegate = OCMClassMock([FlutterPluginSceneLifeCycleDelegate class]);
515 OCMStub([mockScene delegate]).andReturn(mockLifecycleProvider);
516 OCMStub([mockLifecycleProvider sceneLifeCycleDelegate]).andReturn(mockLifecycleDelegate);
517
518 NSNotification* sceneNotification =
519 [NSNotification notificationWithName:UISceneWillConnectNotification
520 object:mockScene
521 userInfo:nil];
522
523 [NSNotificationCenter.defaultCenter postNotification:sceneNotification];
524 OCMVerify(times(1), [mockLifecycleDelegate engine:engine
525 receivedConnectNotificationFor:mockScene]);
526 }
527
528 - (void)testSpawnsShareGpuContext {
529 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar"];
530 [engine run];
531 FlutterEngine* spawn = [engine spawnWithEntrypoint:nil
532 libraryURI:nil
533 initialRoute:nil
534 entrypointArgs:nil];
535 XCTAssertNotNil(spawn);
536 XCTAssertTrue(engine.platformView != nullptr);
537 XCTAssertTrue(spawn.platformView != nullptr);
538 std::shared_ptr<flutter::IOSContext> engine_context = engine.platformView->GetIosContext();
539 std::shared_ptr<flutter::IOSContext> spawn_context = spawn.platformView->GetIosContext();
540 XCTAssertEqual(engine_context, spawn_context);
541 }
542
543 - (void)testEnableSemanticsWhenFlutterViewAccessibilityDidCall {
544 FlutterEngineSpy* engine = [[FlutterEngineSpy alloc] initWithName:@"foobar"];
545 engine.ensureSemanticsEnabledCalled = NO;
546 [engine flutterViewAccessibilityDidCall];
547 XCTAssertTrue(engine.ensureSemanticsEnabledCalled);
548 }
549
550 - (void)testCanMergePlatformAndUIThread {
551 #if defined(TARGET_IPHONE_SIMULATOR) && TARGET_IPHONE_SIMULATOR
552 auto settings = FLTDefaultSettingsForBundle();
553 FlutterDartProject* project = [[FlutterDartProject alloc] initWithSettings:settings];
554 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
555 [engine run];
556
557 XCTAssertEqual(engine.shell.GetTaskRunners().GetUITaskRunner(),
558 engine.shell.GetTaskRunners().GetPlatformTaskRunner());
559 #endif // defined(TARGET_IPHONE_SIMULATOR) && TARGET_IPHONE_SIMULATOR
560 }
561
562 - (void)testCanUnMergePlatformAndUIThread {
563 #if defined(TARGET_IPHONE_SIMULATOR) && TARGET_IPHONE_SIMULATOR
564 auto settings = FLTDefaultSettingsForBundle();
565 settings.merged_platform_ui_thread = flutter::Settings::MergedPlatformUIThread::kDisabled;
566 FlutterDartProject* project = [[FlutterDartProject alloc] initWithSettings:settings];
567 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
568 [engine run];
569
570 XCTAssertNotEqual(engine.shell.GetTaskRunners().GetUITaskRunner(),
571 engine.shell.GetTaskRunners().GetPlatformTaskRunner());
572 #endif // defined(TARGET_IPHONE_SIMULATOR) && TARGET_IPHONE_SIMULATOR
573 }
574
575 - (void)testAddSceneDelegateToRegistrar {
576 FlutterDartProject* project = [[FlutterDartProject alloc] init];
577 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"engine" project:project];
578 id mockEngine = OCMPartialMock(engine);
579 NSObject<FlutterPluginRegistrar>* registrar = [mockEngine registrarForPlugin:@"plugin"];
580 id mockPlugin = OCMProtocolMock(@protocol(TestFlutterPluginWithSceneEvents));
581 [registrar addSceneDelegate:mockPlugin];
582
583 OCMVerify(times(1), [mockEngine addSceneLifeCycleDelegate:[OCMArg any]]);
584 }
585
586 - (void)testNotifyAppDelegateOfEngineInitialization {
587 FlutterDartProject* project = [[FlutterDartProject alloc] init];
588 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"engine" project:project];
589
590 id mockApplication = OCMClassMock([UIApplication class]);
591 OCMStub([mockApplication sharedApplication]).andReturn(mockApplication);
592 id mockAppDelegate = OCMProtocolMock(@protocol(FlutterImplicitEngineDelegate));
593 OCMStub([mockApplication delegate]).andReturn(mockAppDelegate);
594
595 [engine performImplicitEngineCallback];
596 OCMVerify(times(1), [mockAppDelegate didInitializeImplicitFlutterEngine:[OCMArg any]]);
597 }
598
599 - (void)testRegistrarForPlugin {
600 FlutterDartProject* project = [[FlutterDartProject alloc] init];
601 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"engine" project:project];
602 FlutterEngine* mockEngine = OCMPartialMock(engine);
603 id mockViewController = OCMClassMock([FlutterViewController class]);
604 id mockBinaryMessenger = OCMProtocolMock(@protocol(FlutterBinaryMessenger));
605 id mockTextureRegistry = OCMProtocolMock(@protocol(FlutterTextureRegistry));
606 id mockPlatformViewController = OCMClassMock([FlutterPlatformViewsController class]);
607 OCMStub([mockEngine viewController]).andReturn(mockViewController);
608 OCMStub([mockEngine binaryMessenger]).andReturn(mockBinaryMessenger);
609 OCMStub([mockEngine textureRegistry]).andReturn(mockTextureRegistry);
610 OCMStub([mockEngine platformViewsController]).andReturn(mockPlatformViewController);
611
612 NSString* pluginKey = @"plugin";
613 NSString* assetKey = @"asset";
614 NSString* factoryKey = @"platform_view_factory";
615
616 NSObject<FlutterPluginRegistrar>* registrar = [mockEngine registrarForPlugin:pluginKey];
617
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:)]);
629
630 // Verify messenger, textures, and viewController forwards to FlutterEngine
631 XCTAssertEqual(registrar.messenger, mockBinaryMessenger);
632 XCTAssertEqual(registrar.textures, mockTextureRegistry);
633 XCTAssertEqual(registrar.viewController, mockViewController);
634
635 // Verify registerViewFactory:withId:, registerViewFactory:withId:gestureRecognizersBlockingPolicy
636 // forwards to FlutterEngine
637 id mockPlatformViewFactory = OCMProtocolMock(@protocol(FlutterPlatformViewFactory));
638 [registrar registerViewFactory:mockPlatformViewFactory withId:factoryKey];
639 [registrar registerViewFactory:mockPlatformViewFactory
640 withId:factoryKey
641 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
642 OCMVerify(times(2), [mockPlatformViewController registerViewFactory:mockPlatformViewFactory
643 withId:factoryKey
644 gestureRecognizersBlockingPolicy:
645 FlutterPlatformViewGestureRecognizersBlockingPolicyEager]);
646
647 // Verify publish forwards to FlutterEngine
648 id plugin = OCMProtocolMock(@protocol(FlutterPlugin));
649 [registrar publish:plugin];
650 XCTAssertEqual(mockEngine.pluginPublications[pluginKey], plugin);
651
652 // Verify lookupKeyForAsset:, lookupKeyForAsset:fromPackage forward to engine
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]);
657 }
658
659 - (void)testRegistrarForApplication {
660 FlutterDartProject* project = [[FlutterDartProject alloc] init];
661 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"engine" project:project];
662 FlutterEngine* mockEngine = OCMPartialMock(engine);
663 id mockViewController = OCMClassMock([FlutterViewController class]);
664 id mockBinaryMessenger = OCMProtocolMock(@protocol(FlutterBinaryMessenger));
665 id mockTextureRegistry = OCMProtocolMock(@protocol(FlutterTextureRegistry));
666 id mockPlatformViewController = OCMClassMock([FlutterPlatformViewsController class]);
667 OCMStub([mockEngine viewController]).andReturn(mockViewController);
668 OCMStub([mockEngine binaryMessenger]).andReturn(mockBinaryMessenger);
669 OCMStub([mockEngine textureRegistry]).andReturn(mockTextureRegistry);
670 OCMStub([mockEngine platformViewsController]).andReturn(mockPlatformViewController);
671
672 NSString* pluginKey = @"plugin";
673 NSString* factoryKey = @"platform_view_factory";
674
675 NSObject<FlutterApplicationRegistrar>* registrar = [mockEngine registrarForApplication:pluginKey];
676
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:)]);
688
689 // Verify messenger and textures forwards to FlutterEngine
690 XCTAssertEqual(registrar.messenger, mockBinaryMessenger);
691 XCTAssertEqual(registrar.textures, mockTextureRegistry);
692
693 // Verify registerViewFactory:withId:, registerViewFactory:withId:gestureRecognizersBlockingPolicy
694 // forwards to FlutterEngine
695 id mockPlatformViewFactory = OCMProtocolMock(@protocol(FlutterPlatformViewFactory));
696 [registrar registerViewFactory:mockPlatformViewFactory withId:factoryKey];
697 [registrar registerViewFactory:mockPlatformViewFactory
698 withId:factoryKey
699 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
700 OCMVerify(times(2), [mockPlatformViewController registerViewFactory:mockPlatformViewFactory
701 withId:factoryKey
702 gestureRecognizersBlockingPolicy:
703 FlutterPlatformViewGestureRecognizersBlockingPolicyEager]);
704 }
705
706 - (void)testSendDeepLinkToFrameworkTimesOut {
707 FlutterDartProject* project = [[FlutterDartProject alloc] init];
708 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"engine" project:project];
709 id mockEngine = OCMPartialMock(engine);
710 id mockEngineFirstFrameCallback = [OCMArg invokeBlockWithArgs:@YES, nil];
711 OCMStub([mockEngine waitForFirstFrame:3.0 callback:mockEngineFirstFrameCallback]);
712
713 NSURL* url = [NSURL URLWithString:@"example.com"];
714
715 [mockEngine sendDeepLinkToFramework:url
716 completionHandler:^(BOOL success) {
717 XCTAssertFalse(success);
718 }];
719 }
720
721 - (void)testSendDeepLinkToFrameworkUsingNavigationChannel {
722 NSString* urlString = @"example.com";
723 NSURL* url = [NSURL URLWithString:urlString];
724 FlutterDartProject* project = [[FlutterDartProject alloc] init];
725 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"engine" project:project];
726 id mockEngine = OCMPartialMock(engine);
727 id mockEngineFirstFrameCallback = [OCMArg invokeBlockWithArgs:@NO, nil];
728 OCMStub([mockEngine waitForFirstFrame:3.0 callback:mockEngineFirstFrameCallback]);
729 id mockNavigationChannel = OCMClassMock([FlutterMethodChannel class]);
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]);
735
736 [mockEngine sendDeepLinkToFramework:url
737 completionHandler:^(BOOL success) {
738 XCTAssertTrue(success);
739 }];
740 }
741
742 - (void)testSendDeepLinkToFrameworkUsingNavigationChannelFails {
743 NSString* urlString = @"example.com";
744 NSURL* url = [NSURL URLWithString:urlString];
745 FlutterDartProject* project = [[FlutterDartProject alloc] init];
746 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"engine" project:project];
747 id mockEngine = OCMPartialMock(engine);
748 id mockEngineFirstFrameCallback = [OCMArg invokeBlockWithArgs:@NO, nil];
749 OCMStub([mockEngine waitForFirstFrame:3.0 callback:mockEngineFirstFrameCallback]);
750 id mockNavigationChannel = OCMClassMock([FlutterMethodChannel class]);
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]);
756
757 [mockEngine sendDeepLinkToFramework:url
758 completionHandler:^(BOOL success) {
759 XCTAssertFalse(success);
760 }];
761 }
762 @end
FlutterBinaryReply
NS_ASSUME_NONNULL_BEGIN typedef void(^ FlutterBinaryReply)(NSData *_Nullable reply)
FlutterBinaryMessengerRelay.h
FLTDefaultSettingsForBundle
flutter::Settings FLTDefaultSettingsForBundle(NSBundle *bundle, NSProcessInfo *processInfoOrNil)
Definition: FlutterDartProject.mm:42
FlutterDartProject_Internal.h
FlutterEngine_Internal.h
FlutterEngine_Test.h
FlutterMacros.h
FlutterPlatformViewGestureRecognizersBlockingPolicyEager
@ FlutterPlatformViewGestureRecognizersBlockingPolicyEager
Definition: FlutterPlugin.h:262
FlutterSceneLifeCycle_Internal.h
FlutterSharedApplication.h
FlutterTextInputPlugin.h
engine
id engine
Definition: FlutterTextInputPluginTest.mm:136
-[FlutterEngine(Test) shell]
flutter::Shell & shell()
-[FlutterEngine(Test) updateDisplays]
void updateDisplays()
FlutterDartProject
Definition: FlutterDartProject.mm:268
flutter::PlatformViewIOS::GetIosContext
const std::shared_ptr< IOSContext > & GetIosContext()
Definition: platform_view_ios.h:128
FakeBinaryMessengerRelay
Definition: FlutterEngineTest.mm:48
FakeBinaryMessengerRelay::failOnDealloc
BOOL failOnDealloc
Definition: FlutterEngineTest.mm:48
FlutterBinaryMessengerRelay
Definition: FlutterBinaryMessengerRelay.h:14
FlutterEngine
Definition: FlutterEngine.h:61
-[FlutterEngine run]
BOOL run()
Definition: FlutterEngine.mm:970
-[FlutterEngine platformView]
flutter::PlatformViewIOS * platformView()
-[FlutterEngine spawnWithEntrypoint:libraryURI:initialRoute:entrypointArgs:]
FlutterEngine * spawnWithEntrypoint:libraryURI:initialRoute:entrypointArgs:(/*nullable */NSString *entrypoint,[libraryURI]/*nullable */NSString *libraryURI,[initialRoute]/*nullable */NSString *initialRoute,[entrypointArgs]/*nullable */NSArray< NSString * > *entrypointArgs)
-[FlutterEngine shell]
flutter::Shell & shell()
-[FlutterEngine setBinaryMessenger:]
void setBinaryMessenger:(FlutterBinaryMessengerRelay *binaryMessenger)
FlutterEngine::navigationChannel
FlutterMethodChannel * navigationChannel
Definition: FlutterEngine.h:345
-[FlutterEngine platformViewsRenderingAPI]
flutter::IOSRenderingAPI platformViewsRenderingAPI()
FlutterEngine::viewController
FlutterViewController * viewController
Definition: FlutterEngine.h:327
-[FlutterEngine runWithEntrypoint:initialRoute:]
BOOL runWithEntrypoint:initialRoute:(nullable NSString *entrypoint,[initialRoute] nullable NSString *initialRoute)
FlutterEngine::textureRegistry
NSObject< FlutterTextureRegistry > * textureRegistry
Definition: FlutterEngine.h:432
FlutterEngine::pluginPublications
NSMutableDictionary * pluginPublications
Definition: FlutterEngine_Test.h:26
FlutterEngine::binaryMessenger
NSObject< FlutterBinaryMessenger > * binaryMessenger
Definition: FlutterEngine.h:427
-[FlutterEngine ensureSemanticsEnabled]
void ensureSemanticsEnabled()
Definition: FlutterEngine.mm:505
-[FlutterEngine waitForFirstFrame:callback:]
void waitForFirstFrame:callback:(NSTimeInterval timeout,[callback] void(^ callback)(BOOL didTimeout))
FlutterEngineSpy
Definition: FlutterEngineTest.mm:28
FlutterEngineSpy::ensureSemanticsEnabledCalled
BOOL ensureSemanticsEnabledCalled
Definition: FlutterEngineTest.mm:28
FlutterEngineTest
Definition: FlutterEngineTest.mm:60
FlutterJSONMethodCodec
Definition: FlutterCodecs.h:455
FlutterMethodCall
Definition: FlutterCodecs.h:221
+[FlutterMethodCall methodCallWithMethodName:arguments:]
instancetype methodCallWithMethodName:arguments:(NSString *method,[arguments] id _Nullable arguments)
FlutterMethodChannel
Definition: FlutterChannels.h:221
FlutterPlatformViewsController
Definition: FlutterPlatformViewsController.h:31
FlutterPluginSceneLifeCycleDelegate
Definition: FlutterSceneLifeCycle.h:149
FlutterViewController
Definition: FlutterViewController.h:57
FLUTTER_ASSERT_ARC
Definition: FlutterChannelKeyResponder.mm:13
flutter::IOSRenderingAPI
IOSRenderingAPI
Definition: rendering_api_selection.h:14
flutter::IOSRenderingAPI::kMetal
@ kMetal
platform_view_ios.h
FlutterBinaryMessenger-p
Definition: FlutterBinaryMessenger.h:49
FlutterImplicitEngineDelegate-p
Definition: FlutterEngine.h:481
FlutterPlatformViewFactory-p
Definition: FlutterPlatformViews.h:26
FlutterPlugin-p
Definition: FlutterPlugin.h:190
-[FlutterPluginRegistry-p registrarForPlugin:]
nullable NSObject< FlutterPluginRegistrar > * registrarForPlugin:(NSString *pluginKey)
FlutterSceneLifeCycleDelegate-p
Definition: FlutterSceneLifeCycle.h:31
FlutterSceneLifeCycleProvider-p
Definition: FlutterSceneLifeCycle.h:213
FlutterTextInputDelegate-p
Definition: FlutterTextInputDelegate.h:37
FlutterTextureRegistry-p
Definition: FlutterTexture.h:45
TestFlutterPluginWithSceneEvents-p
Definition: FlutterEngineTest.mm:24