Flutter Engine Uber Docs
Docs for the entire Flutter Engine repo.
 
Loading...
Searching...
No Matches
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
15#import "flutter/shell/platform/darwin/common/test_utils_swift/test_utils_swift.h"
16#import "flutter/shell/platform/darwin/ios/InternalFlutterSwift/InternalFlutterSwift.h"
26
27@protocol TestFlutterPluginWithSceneEvents <NSObject, FlutterPlugin, FlutterSceneLifeCycleDelegate>
28@end
29
30/// A minimal FlutterPlugin that does not implement any lifecycle methods.
31/// Used to verify that plugins not using lifecycle events do not trigger a warning.
33@end
34
35@implementation TestMinimalFlutterPlugin
36+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
37}
38@end
39
41@property(nonatomic) BOOL ensureSemanticsEnabledCalled;
42@end
43
44@implementation FlutterEngineSpy
45
46- (void)ensureSemanticsEnabled {
47 _ensureSemanticsEnabledCalled = YES;
48}
49
50@end
51
52@interface FlutterEngine () <FlutterTextInputDelegate>
53
54@end
55
56/// FlutterBinaryMessengerRelay used for testing that setting FlutterEngine.binaryMessenger to
57/// the current instance doesn't trigger a use-after-free bug.
58///
59/// See: testSetBinaryMessengerToSameBinaryMessenger
61@property(nonatomic, assign) BOOL failOnDealloc;
62@end
63
64@implementation FakeBinaryMessengerRelay
65- (void)dealloc {
66 if (_failOnDealloc) {
67 XCTFail("FakeBinaryMessageRelay should not be deallocated");
68 }
69}
70@end
71
72@interface FlutterEngineTest : XCTestCase
73@end
74
75@implementation FlutterEngineTest
76
77- (void)setUp {
78}
79
80- (void)tearDown {
81}
82
83- (void)testCreate {
84 FlutterDartProject* project = [[FlutterDartProject alloc] init];
85 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
86 XCTAssertNotNil(engine);
87}
88
89- (void)testShellGetters {
90 FlutterDartProject* project = [[FlutterDartProject alloc] init];
91 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
92 XCTAssertNotNil(engine);
93
94 // Ensure getters don't deref _shell when it's null, and instead return nullptr.
95 XCTAssertNil(engine.platformTaskRunner);
96 XCTAssertNil(engine.uiTaskRunner);
97 XCTAssertNil(engine.rasterTaskRunner);
98}
99
100- (void)testTaskRunnerPropertyStability {
101 FlutterDartProject* project = [[FlutterDartProject alloc] init];
102 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
103 [engine run];
104
105 // Since the taskRunners are wrappers, ensure that we generate them once and they keep the same
106 // identity over time. This makes identity checks on the runners safe/possible.
107 XCTAssertNotNil(engine.platformTaskRunner);
109 XCTAssertNotNil(engine.uiTaskRunner);
110 XCTAssertEqual(engine.uiTaskRunner, engine.uiTaskRunner);
111 XCTAssertNotNil(engine.rasterTaskRunner);
113}
114
115- (void)testInfoPlist {
116 // Check the embedded Flutter.framework Info.plist, not the linked dylib.
117 NSURL* flutterFrameworkURL =
118 [NSBundle.mainBundle.privateFrameworksURL URLByAppendingPathComponent:@"Flutter.framework"];
119 NSBundle* flutterBundle = [NSBundle bundleWithURL:flutterFrameworkURL];
120 XCTAssertEqualObjects(flutterBundle.bundleIdentifier, @"io.flutter.flutter");
121
122 NSDictionary<NSString*, id>* infoDictionary = flutterBundle.infoDictionary;
123
124 // OS version can have one, two, or three digits: "8", "8.0", "8.0.0"
125 NSError* regexError = NULL;
126 NSRegularExpression* osVersionRegex =
127 [NSRegularExpression regularExpressionWithPattern:@"((0|[1-9]\\d*)\\.)*(0|[1-9]\\d*)"
128 options:NSRegularExpressionCaseInsensitive
129 error:&regexError];
130 XCTAssertNil(regexError);
131
132 // Smoke test the test regex.
133 NSString* testString = @"9";
134 NSUInteger versionMatches =
135 [osVersionRegex numberOfMatchesInString:testString
136 options:NSMatchingAnchored
137 range:NSMakeRange(0, testString.length)];
138 XCTAssertEqual(versionMatches, 1UL);
139 testString = @"9.1";
140 versionMatches = [osVersionRegex numberOfMatchesInString:testString
141 options:NSMatchingAnchored
142 range:NSMakeRange(0, testString.length)];
143 XCTAssertEqual(versionMatches, 1UL);
144 testString = @"9.0.1";
145 versionMatches = [osVersionRegex numberOfMatchesInString:testString
146 options:NSMatchingAnchored
147 range:NSMakeRange(0, testString.length)];
148 XCTAssertEqual(versionMatches, 1UL);
149 testString = @".0.1";
150 versionMatches = [osVersionRegex numberOfMatchesInString:testString
151 options:NSMatchingAnchored
152 range:NSMakeRange(0, testString.length)];
153 XCTAssertEqual(versionMatches, 0UL);
154
155 // Test Info.plist values.
156 NSString* minimumOSVersion = infoDictionary[@"MinimumOSVersion"];
157 versionMatches = [osVersionRegex numberOfMatchesInString:minimumOSVersion
158 options:NSMatchingAnchored
159 range:NSMakeRange(0, minimumOSVersion.length)];
160 XCTAssertEqual(versionMatches, 1UL);
161
162 // SHA length is 40.
163 XCTAssertEqual(((NSString*)infoDictionary[@"FlutterEngine"]).length, 40UL);
164
165 // {clang_version} placeholder is 15 characters. The clang string version
166 // is longer than that, so check if the placeholder has been replaced, without
167 // actually checking a literal string, which could be different on various machines.
168 XCTAssertTrue(((NSString*)infoDictionary[@"ClangVersion"]).length > 15UL);
169}
170
171- (void)testDeallocated {
172 __weak FlutterEngine* weakEngine = nil;
173 @autoreleasepool {
174 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar"];
175 weakEngine = engine;
176 [engine run];
177 XCTAssertNotNil(weakEngine);
178 }
179 XCTAssertNil(weakEngine);
180}
181
182- (void)testSendMessageBeforeRun {
183 FlutterDartProject* project = [[FlutterDartProject alloc] init];
184 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
185 XCTAssertNotNil(engine);
186 XCTAssertThrows([engine.binaryMessenger
187 sendOnChannel:@"foo"
188 message:[@"bar" dataUsingEncoding:NSUTF8StringEncoding]
189 binaryReply:nil]);
190}
191
192- (void)testSetMessageHandlerBeforeRun {
193 FlutterDartProject* project = [[FlutterDartProject alloc] init];
194 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
195 XCTAssertNotNil(engine);
196 XCTAssertThrows([engine.binaryMessenger
197 setMessageHandlerOnChannel:@"foo"
198 binaryMessageHandler:^(NSData* _Nullable message, FlutterBinaryReply _Nonnull reply){
199
200 }]);
201}
202
203- (void)testNilSetMessageHandlerBeforeRun {
204 FlutterDartProject* project = [[FlutterDartProject alloc] init];
205 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
206 XCTAssertNotNil(engine);
207 XCTAssertNoThrow([engine.binaryMessenger setMessageHandlerOnChannel:@"foo"
208 binaryMessageHandler:nil]);
209}
210
211- (void)testNotifyPluginOfDealloc {
212 id plugin = OCMProtocolMock(@protocol(FlutterPlugin));
213 OCMStub([plugin detachFromEngineForRegistrar:[OCMArg any]]);
214 @autoreleasepool {
215 FlutterDartProject* project = [[FlutterDartProject alloc] init];
216 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"engine" project:project];
217 NSObject<FlutterPluginRegistrar>* registrar = [engine registrarForPlugin:@"plugin"];
218 [registrar publish:plugin];
219 }
220 OCMVerify([plugin detachFromEngineForRegistrar:[OCMArg any]]);
221}
222
223- (void)testGetViewControllerFromRegistrar {
224 FlutterDartProject* project = [[FlutterDartProject alloc] init];
225 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"engine" project:project];
226 id mockEngine = OCMPartialMock(engine);
227 NSObject<FlutterPluginRegistrar>* registrar = [mockEngine registrarForPlugin:@"plugin"];
228
229 // Verify accessing the viewController getter calls FlutterEngine.viewController.
230 (void)[registrar viewController];
231 OCMVerify(times(1), [mockEngine viewController]);
232}
233
234- (void)testSetBinaryMessengerToSameBinaryMessenger {
235 FakeBinaryMessengerRelay* fakeBinaryMessenger = [[FakeBinaryMessengerRelay alloc] init];
236
237 FlutterEngine* engine = [[FlutterEngine alloc] init];
238 [engine setBinaryMessenger:fakeBinaryMessenger];
239
240 // Verify that the setter doesn't free the old messenger before setting the new messenger.
241 fakeBinaryMessenger.failOnDealloc = YES;
242 [engine setBinaryMessenger:fakeBinaryMessenger];
243
244 // Don't fail when ARC releases the binary messenger.
245 fakeBinaryMessenger.failOnDealloc = NO;
246}
247
248- (void)testRunningInitialRouteSendsNavigationMessage {
249 id mockBinaryMessenger = OCMClassMock([FlutterBinaryMessengerRelay class]);
250
251 FlutterEngine* engine = [[FlutterEngine alloc] init];
252 [engine setBinaryMessenger:mockBinaryMessenger];
253
254 // Run with an initial route.
255 [engine runWithEntrypoint:FlutterDefaultDartEntrypoint initialRoute:@"test"];
256
257 // Now check that an encoded method call has been made on the binary messenger to set the
258 // initial route to "test".
259 FlutterMethodCall* setInitialRouteMethodCall =
260 [FlutterMethodCall methodCallWithMethodName:@"setInitialRoute" arguments:@"test"];
261 NSData* encodedSetInitialRouteMethod =
262 [[FlutterJSONMethodCodec sharedInstance] encodeMethodCall:setInitialRouteMethodCall];
263 OCMVerify([mockBinaryMessenger sendOnChannel:@"flutter/navigation"
264 message:encodedSetInitialRouteMethod]);
265}
266
267- (void)testInitialRouteSettingsSendsNavigationMessage {
268 id mockBinaryMessenger = OCMClassMock([FlutterBinaryMessengerRelay class]);
269
270 auto settings = FLTDefaultSettingsForBundle();
271 settings.route = "test";
272 FlutterDartProject* project = [[FlutterDartProject alloc] initWithSettings:settings];
273 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
274 [engine setBinaryMessenger:mockBinaryMessenger];
275 [engine run];
276
277 // Now check that an encoded method call has been made on the binary messenger to set the
278 // initial route to "test".
279 FlutterMethodCall* setInitialRouteMethodCall =
280 [FlutterMethodCall methodCallWithMethodName:@"setInitialRoute" arguments:@"test"];
281 NSData* encodedSetInitialRouteMethod =
282 [[FlutterJSONMethodCodec sharedInstance] encodeMethodCall:setInitialRouteMethodCall];
283 OCMVerify([mockBinaryMessenger sendOnChannel:@"flutter/navigation"
284 message:encodedSetInitialRouteMethod]);
285}
286
287- (void)testPlatformViewsControllerRenderingMetalBackend {
288 FlutterEngine* engine = [[FlutterEngine alloc] init];
289 [engine run];
291
292 XCTAssertEqual(renderingApi, flutter::IOSRenderingAPI::kMetal);
293}
294
295- (void)testWaitForFirstFrameTimeout {
296 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar"];
297 [engine run];
298 XCTestExpectation* timeoutFirstFrame = [self expectationWithDescription:@"timeoutFirstFrame"];
299 [engine waitForFirstFrame:0.1
300 callback:^(BOOL didTimeout) {
301 if (timeoutFirstFrame) {
302 [timeoutFirstFrame fulfill];
303 }
304 }];
305 [self waitForExpectations:@[ timeoutFirstFrame ]];
306}
307
308- (void)testSpawn {
309 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar"];
310 [engine run];
311 FlutterEngine* spawn = [engine spawnWithEntrypoint:nil
312 libraryURI:nil
313 initialRoute:nil
314 entrypointArgs:nil];
315 XCTAssertNotNil(spawn);
316}
317
318- (void)testEngineId {
319 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar"];
320 [engine run];
321 int64_t id1 = engine.engineIdentifier;
322 XCTAssertTrue(id1 != 0);
323 FlutterEngine* spawn = [engine spawnWithEntrypoint:nil
324 libraryURI:nil
325 initialRoute:nil
326 entrypointArgs:nil];
327 int64_t id2 = spawn.engineIdentifier;
328 XCTAssertEqual([FlutterEngine engineForIdentifier:id1], engine);
329 XCTAssertEqual([FlutterEngine engineForIdentifier:id2], spawn);
330}
331
332- (void)testSetHandlerAfterRun {
333 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar"];
334 XCTestExpectation* gotMessage = [self expectationWithDescription:@"gotMessage"];
335 dispatch_async(dispatch_get_main_queue(), ^{
336 NSObject<FlutterPluginRegistrar>* registrar = [engine registrarForPlugin:@"foo"];
338 [engine run];
339 flutter::Shell& shell = engine.shell;
341 engine.shell.GetTaskRunners().GetUITaskRunner(), [&latch, &shell] {
342 flutter::Engine::Delegate& delegate = shell;
343 auto message = std::make_unique<flutter::PlatformMessage>("foo", nullptr);
344 delegate.OnEngineHandlePlatformMessage(std::move(message));
345 latch.Signal();
346 });
347 latch.Wait();
348 [registrar.messenger setMessageHandlerOnChannel:@"foo"
349 binaryMessageHandler:^(NSData* message, FlutterBinaryReply reply) {
350 [gotMessage fulfill];
351 }];
352 });
353 [self waitForExpectations:@[ gotMessage ]];
354}
355
356- (void)testThreadPrioritySetCorrectly {
357 XCTestExpectation* prioritiesSet = [self expectationWithDescription:@"prioritiesSet"];
358 prioritiesSet.expectedFulfillmentCount = 2;
359
360 IMP mockSetThreadPriority =
361 imp_implementationWithBlock(^(NSThread* thread, double threadPriority) {
362 if ([thread.name hasSuffix:@".raster"]) {
363 XCTAssertEqual(threadPriority, 1.0);
364 [prioritiesSet fulfill];
365 } else if ([thread.name hasSuffix:@".io"]) {
366 XCTAssertEqual(threadPriority, 0.5);
367 [prioritiesSet fulfill];
368 }
369 });
370 Method method = class_getInstanceMethod([NSThread class], @selector(setThreadPriority:));
371 IMP originalSetThreadPriority = method_getImplementation(method);
372 method_setImplementation(method, mockSetThreadPriority);
373
374 FlutterEngine* engine = [[FlutterEngine alloc] init];
375 [engine run];
376 [self waitForExpectations:@[ prioritiesSet ]];
377
378 method_setImplementation(method, originalSetThreadPriority);
379}
380
381- (void)testCanEnableDisableEmbedderAPIThroughInfoPlist {
382 {
383 // Not enable embedder API by default
384 auto settings = FLTDefaultSettingsForBundle();
385 settings.enable_software_rendering = true;
386 FlutterDartProject* project = [[FlutterDartProject alloc] initWithSettings:settings];
387 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
388 XCTAssertFalse(engine.enableEmbedderAPI);
389 }
390 {
391 // Enable embedder api
392 id mockMainBundle = OCMPartialMock([NSBundle mainBundle]);
393 OCMStub([mockMainBundle objectForInfoDictionaryKey:@"FLTEnableIOSEmbedderAPI"])
394 .andReturn(@"YES");
395 auto settings = FLTDefaultSettingsForBundle();
396 settings.enable_software_rendering = true;
397 FlutterDartProject* project = [[FlutterDartProject alloc] initWithSettings:settings];
398 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
399 XCTAssertTrue(engine.enableEmbedderAPI);
400 }
401}
402
403- (void)testFlutterTextInputViewDidResignFirstResponderWillCallTextInputClientConnectionClosed {
404 id mockBinaryMessenger = OCMClassMock([FlutterBinaryMessengerRelay class]);
405 FlutterEngine* engine = [[FlutterEngine alloc] init];
406 [engine setBinaryMessenger:mockBinaryMessenger];
407 [engine runWithEntrypoint:FlutterDefaultDartEntrypoint initialRoute:@"test"];
408 [engine flutterTextInputView:nil didResignFirstResponderWithTextInputClient:1];
409 FlutterMethodCall* methodCall =
410 [FlutterMethodCall methodCallWithMethodName:@"TextInputClient.onConnectionClosed"
411 arguments:@[ @(1) ]];
412 NSData* encodedMethodCall = [[FlutterJSONMethodCodec sharedInstance] encodeMethodCall:methodCall];
413 OCMVerify([mockBinaryMessenger sendOnChannel:@"flutter/textinput" message:encodedMethodCall]);
414}
415
416- (void)testFlutterEngineUpdatesDisplays {
417 FlutterEngine* engine = [[FlutterEngine alloc] init];
418 id mockEngine = OCMPartialMock(engine);
419
420 [engine run];
421 OCMVerify(times(1), [mockEngine updateDisplays]);
423 OCMVerify(times(2), [mockEngine updateDisplays]);
424}
425
426- (void)testLifeCycleNotificationDidEnterBackgroundForApplication {
427 FlutterDartProject* project = [[FlutterDartProject alloc] init];
428 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
429 [engine run];
430 NSNotification* sceneNotification =
431 [NSNotification notificationWithName:UISceneDidEnterBackgroundNotification
432 object:nil
433 userInfo:nil];
434 NSNotification* applicationNotification =
435 [NSNotification notificationWithName:UIApplicationDidEnterBackgroundNotification
436 object:nil
437 userInfo:nil];
438 id mockEngine = OCMPartialMock(engine);
439 [NSNotificationCenter.defaultCenter postNotification:sceneNotification];
440 [NSNotificationCenter.defaultCenter postNotification:applicationNotification];
441 OCMVerify(times(1), [mockEngine applicationDidEnterBackground:[OCMArg any]]);
442 XCTAssertTrue(engine.isGpuDisabled);
443 BOOL gpuDisabled = NO;
444 [engine shell].GetIsGpuDisabledSyncSwitch()->Execute(
445 fml::SyncSwitch::Handlers().SetIfTrue([&] { gpuDisabled = YES; }).SetIfFalse([&] {
446 gpuDisabled = NO;
447 }));
448 XCTAssertTrue(gpuDisabled);
449}
450
451- (void)testLifeCycleNotificationDidEnterBackgroundForScene {
452 id mockBundle = OCMPartialMock([NSBundle mainBundle]);
453 OCMStub([mockBundle objectForInfoDictionaryKey:@"NSExtension"]).andReturn(@{
454 @"NSExtensionPointIdentifier" : @"com.apple.share-services"
455 });
456 FlutterDartProject* project = [[FlutterDartProject alloc] init];
457 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
458 [engine run];
459 NSNotification* sceneNotification =
460 [NSNotification notificationWithName:UISceneDidEnterBackgroundNotification
461 object:nil
462 userInfo:nil];
463 NSNotification* applicationNotification =
464 [NSNotification notificationWithName:UIApplicationDidEnterBackgroundNotification
465 object:nil
466 userInfo:nil];
467 id mockEngine = OCMPartialMock(engine);
468 [NSNotificationCenter.defaultCenter postNotification:sceneNotification];
469 [NSNotificationCenter.defaultCenter postNotification:applicationNotification];
470 OCMVerify(times(1), [mockEngine sceneDidEnterBackground:[OCMArg any]]);
471 XCTAssertTrue(engine.isGpuDisabled);
472 BOOL gpuDisabled = NO;
473 [engine shell].GetIsGpuDisabledSyncSwitch()->Execute(
474 fml::SyncSwitch::Handlers().SetIfTrue([&] { gpuDisabled = YES; }).SetIfFalse([&] {
475 gpuDisabled = NO;
476 }));
477 XCTAssertTrue(gpuDisabled);
478 [mockBundle stopMocking];
479}
480
481- (void)testLifeCycleNotificationWillEnterForegroundForApplication {
482 FlutterDartProject* project = [[FlutterDartProject alloc] init];
483 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
484 [engine run];
485 NSNotification* sceneNotification =
486 [NSNotification notificationWithName:UISceneWillEnterForegroundNotification
487 object:nil
488 userInfo:nil];
489 NSNotification* applicationNotification =
490 [NSNotification notificationWithName:UIApplicationWillEnterForegroundNotification
491 object:nil
492 userInfo:nil];
493 id mockEngine = OCMPartialMock(engine);
494 [NSNotificationCenter.defaultCenter postNotification:sceneNotification];
495 [NSNotificationCenter.defaultCenter postNotification:applicationNotification];
496 OCMVerify(times(1), [mockEngine applicationWillEnterForeground:[OCMArg any]]);
497 XCTAssertFalse(engine.isGpuDisabled);
498 BOOL gpuDisabled = YES;
499 [engine shell].GetIsGpuDisabledSyncSwitch()->Execute(
500 fml::SyncSwitch::Handlers().SetIfTrue([&] { gpuDisabled = YES; }).SetIfFalse([&] {
501 gpuDisabled = NO;
502 }));
503 XCTAssertFalse(gpuDisabled);
504}
505
506- (void)testLifeCycleNotificationWillEnterForegroundForScene {
507 id mockBundle = OCMPartialMock([NSBundle mainBundle]);
508 OCMStub([mockBundle objectForInfoDictionaryKey:@"NSExtension"]).andReturn(@{
509 @"NSExtensionPointIdentifier" : @"com.apple.share-services"
510 });
511 FlutterDartProject* project = [[FlutterDartProject alloc] init];
512 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
513 [engine run];
514 NSNotification* sceneNotification =
515 [NSNotification notificationWithName:UISceneWillEnterForegroundNotification
516 object:nil
517 userInfo:nil];
518 NSNotification* applicationNotification =
519 [NSNotification notificationWithName:UIApplicationWillEnterForegroundNotification
520 object:nil
521 userInfo:nil];
522 id mockEngine = OCMPartialMock(engine);
523 [NSNotificationCenter.defaultCenter postNotification:sceneNotification];
524 [NSNotificationCenter.defaultCenter postNotification:applicationNotification];
525 OCMVerify(times(1), [mockEngine sceneWillEnterForeground:[OCMArg any]]);
526 XCTAssertFalse(engine.isGpuDisabled);
527 BOOL gpuDisabled = YES;
528 [engine shell].GetIsGpuDisabledSyncSwitch()->Execute(
529 fml::SyncSwitch::Handlers().SetIfTrue([&] { gpuDisabled = YES; }).SetIfFalse([&] {
530 gpuDisabled = NO;
531 }));
532 XCTAssertFalse(gpuDisabled);
533 [mockBundle stopMocking];
534}
535
536- (void)testLifeCycleNotificationSceneWillConnect {
537 FlutterDartProject* project = [[FlutterDartProject alloc] init];
538 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
539 [engine run];
540 id mockScene = OCMClassMock([UIWindowScene class]);
541 id mockLifecycleProvider = OCMProtocolMock(@protocol(FlutterSceneLifeCycleProvider));
542 id mockLifecycleDelegate = OCMClassMock([FlutterPluginSceneLifeCycleDelegate class]);
543 OCMStub([mockScene delegate]).andReturn(mockLifecycleProvider);
544 OCMStub([mockLifecycleProvider sceneLifeCycleDelegate]).andReturn(mockLifecycleDelegate);
545
546 NSNotification* sceneNotification =
547 [NSNotification notificationWithName:UISceneWillConnectNotification
548 object:mockScene
549 userInfo:nil];
550
551 [NSNotificationCenter.defaultCenter postNotification:sceneNotification];
552 OCMVerify(times(1), [mockLifecycleDelegate engine:engine
553 receivedConnectNotificationFor:mockScene]);
554}
555
556- (void)testSpawnsShareGpuContext {
557 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar"];
558 [engine run];
559 FlutterEngine* spawn = [engine spawnWithEntrypoint:nil
560 libraryURI:nil
561 initialRoute:nil
562 entrypointArgs:nil];
563 XCTAssertNotNil(spawn);
564 XCTAssertTrue(engine.platformView != nullptr);
565 XCTAssertTrue(spawn.platformView != nullptr);
566 std::shared_ptr<flutter::IOSContext> engine_context = engine.platformView->GetIosContext();
567 std::shared_ptr<flutter::IOSContext> spawn_context = spawn.platformView->GetIosContext();
568 XCTAssertEqual(engine_context, spawn_context);
569}
570
571- (void)testEnableSemanticsWhenFlutterViewAccessibilityDidCall {
572 FlutterEngineSpy* engine = [[FlutterEngineSpy alloc] initWithName:@"foobar"];
573 engine.ensureSemanticsEnabledCalled = NO;
574 [engine flutterViewAccessibilityDidCall];
575 XCTAssertTrue(engine.ensureSemanticsEnabledCalled);
576}
577
578- (void)testCanMergePlatformAndUIThread {
579#if defined(TARGET_IPHONE_SIMULATOR) && TARGET_IPHONE_SIMULATOR
580 auto settings = FLTDefaultSettingsForBundle();
581 FlutterDartProject* project = [[FlutterDartProject alloc] initWithSettings:settings];
582 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
583 [engine run];
584
585 XCTAssertEqual(engine.shell.GetTaskRunners().GetUITaskRunner(),
587#endif // defined(TARGET_IPHONE_SIMULATOR) && TARGET_IPHONE_SIMULATOR
588}
589
590- (void)testCanUnMergePlatformAndUIThread {
591#if defined(TARGET_IPHONE_SIMULATOR) && TARGET_IPHONE_SIMULATOR
592 auto settings = FLTDefaultSettingsForBundle();
593 settings.merged_platform_ui_thread = flutter::Settings::MergedPlatformUIThread::kDisabled;
594 FlutterDartProject* project = [[FlutterDartProject alloc] initWithSettings:settings];
595 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
596 [engine run];
597
598 XCTAssertNotEqual(engine.shell.GetTaskRunners().GetUITaskRunner(),
600#endif // defined(TARGET_IPHONE_SIMULATOR) && TARGET_IPHONE_SIMULATOR
601}
602
603- (void)testAddSceneDelegateToRegistrar {
604 FlutterDartProject* project = [[FlutterDartProject alloc] init];
605 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"engine" project:project];
606 id mockEngine = OCMPartialMock(engine);
607 NSObject<FlutterPluginRegistrar>* registrar = [mockEngine registrarForPlugin:@"plugin"];
608 id mockPlugin = OCMProtocolMock(@protocol(TestFlutterPluginWithSceneEvents));
609 [registrar addSceneDelegate:mockPlugin];
610
611 OCMVerify(times(1), [mockEngine addSceneLifeCycleDelegate:[OCMArg any]]);
612}
613
614- (void)testNotifyAppDelegateOfEngineInitialization {
615 FlutterDartProject* project = [[FlutterDartProject alloc] init];
616 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"engine" project:project];
617
618 id mockApplication = OCMClassMock([UIApplication class]);
619 OCMStub([mockApplication sharedApplication]).andReturn(mockApplication);
620 id mockAppDelegate = OCMProtocolMock(@protocol(FlutterImplicitEngineDelegate));
621 OCMStub([mockApplication delegate]).andReturn(mockAppDelegate);
622
623 [engine performImplicitEngineCallback];
624 OCMVerify(times(1), [mockAppDelegate didInitializeImplicitFlutterEngine:[OCMArg any]]);
625}
626
627- (void)testRegistrarForPlugin {
628 FlutterDartProject* project = [[FlutterDartProject alloc] init];
629 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"engine" project:project];
630 FlutterEngine* mockEngine = OCMPartialMock(engine);
631 id mockViewController = OCMClassMock([FlutterViewController class]);
632 id mockBinaryMessenger = OCMProtocolMock(@protocol(FlutterBinaryMessenger));
633 id mockTextureRegistry = OCMProtocolMock(@protocol(FlutterTextureRegistry));
634 id mockPlatformViewController = OCMClassMock([FlutterPlatformViewsController class]);
635 OCMStub([mockEngine viewController]).andReturn(mockViewController);
636 OCMStub([mockEngine binaryMessenger]).andReturn(mockBinaryMessenger);
637 OCMStub([mockEngine textureRegistry]).andReturn(mockTextureRegistry);
638 OCMStub([mockEngine platformViewsController]).andReturn(mockPlatformViewController);
639
640 NSString* pluginKey = @"plugin";
641 NSString* assetKey = @"asset";
642 NSString* factoryKey = @"platform_view_factory";
643
644 NSObject<FlutterPluginRegistrar>* registrar = [mockEngine registrarForPlugin:pluginKey];
645
646 XCTAssertTrue([registrar respondsToSelector:@selector(messenger)]);
647 XCTAssertTrue([registrar respondsToSelector:@selector(textures)]);
648 XCTAssertTrue([registrar respondsToSelector:@selector(registerViewFactory:withId:)]);
649 XCTAssertTrue([registrar
650 respondsToSelector:@selector(registerViewFactory:withId:gestureRecognizersBlockingPolicy:)]);
651 XCTAssertTrue([registrar respondsToSelector:@selector(viewController)]);
652 XCTAssertTrue([registrar respondsToSelector:@selector(publish:)]);
653 XCTAssertTrue([registrar respondsToSelector:@selector(valuePublishedByPlugin:)]);
654 XCTAssertTrue([registrar respondsToSelector:@selector(addMethodCallDelegate:channel:)]);
655 XCTAssertTrue([registrar respondsToSelector:@selector(addApplicationDelegate:)]);
656 XCTAssertTrue([registrar respondsToSelector:@selector(lookupKeyForAsset:)]);
657 XCTAssertTrue([registrar respondsToSelector:@selector(lookupKeyForAsset:fromPackage:)]);
658
659 // Verify messenger, textures, and viewController forwards to FlutterEngine
660 XCTAssertEqual(registrar.messenger, mockBinaryMessenger);
661 XCTAssertEqual(registrar.textures, mockTextureRegistry);
662 XCTAssertEqual(registrar.viewController, mockViewController);
663
664 // Verify registerViewFactory:withId:, registerViewFactory:withId:gestureRecognizersBlockingPolicy
665 // forwards to FlutterEngine
666 id mockPlatformViewFactory = OCMProtocolMock(@protocol(FlutterPlatformViewFactory));
667 [registrar registerViewFactory:mockPlatformViewFactory withId:factoryKey];
668 [registrar registerViewFactory:mockPlatformViewFactory
669 withId:factoryKey
670 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
671 OCMVerify(times(2), [mockPlatformViewController registerViewFactory:mockPlatformViewFactory
672 withId:factoryKey
673 gestureRecognizersBlockingPolicy:
675
676 // Verify publish forwards to FlutterEngine
677 id plugin = OCMProtocolMock(@protocol(FlutterPlugin));
678 [registrar publish:plugin];
679 XCTAssertEqual(mockEngine.pluginPublications[pluginKey], plugin);
680
681 // Verify lookup forwards to FlutterEngine by fetching the published plugin
682 id published = [registrar valuePublishedByPlugin:pluginKey];
683 XCTAssertEqual(plugin, published);
684
685 // Verify lookupKeyForAsset:, lookupKeyForAsset:fromPackage forward to engine
686 [registrar lookupKeyForAsset:assetKey];
687 OCMVerify(times(1), [mockEngine lookupKeyForAsset:assetKey]);
688 [registrar lookupKeyForAsset:assetKey fromPackage:pluginKey];
689 OCMVerify(times(1), [mockEngine lookupKeyForAsset:assetKey fromPackage:pluginKey]);
690}
691
692- (void)testRegistrarForApplication {
693 FlutterDartProject* project = [[FlutterDartProject alloc] init];
694 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"engine" project:project];
695 FlutterEngine* mockEngine = OCMPartialMock(engine);
696 id mockViewController = OCMClassMock([FlutterViewController class]);
697 id mockBinaryMessenger = OCMProtocolMock(@protocol(FlutterBinaryMessenger));
698 id mockTextureRegistry = OCMProtocolMock(@protocol(FlutterTextureRegistry));
699 id mockPlatformViewController = OCMClassMock([FlutterPlatformViewsController class]);
700 OCMStub([mockEngine viewController]).andReturn(mockViewController);
701 OCMStub([mockEngine binaryMessenger]).andReturn(mockBinaryMessenger);
702 OCMStub([mockEngine textureRegistry]).andReturn(mockTextureRegistry);
703 OCMStub([mockEngine platformViewsController]).andReturn(mockPlatformViewController);
704
705 NSString* pluginKey = @"plugin";
706 NSString* factoryKey = @"platform_view_factory";
707
708 NSObject<FlutterApplicationRegistrar>* registrar = [mockEngine registrarForApplication:pluginKey];
709
710 XCTAssertTrue([registrar respondsToSelector:@selector(messenger)]);
711 XCTAssertTrue([registrar respondsToSelector:@selector(textures)]);
712 XCTAssertTrue([registrar respondsToSelector:@selector(registerViewFactory:withId:)]);
713 XCTAssertTrue([registrar
714 respondsToSelector:@selector(registerViewFactory:withId:gestureRecognizersBlockingPolicy:)]);
715 XCTAssertFalse([registrar respondsToSelector:@selector(viewController)]);
716 XCTAssertFalse([registrar respondsToSelector:@selector(publish:)]);
717 XCTAssertFalse([registrar respondsToSelector:@selector(valuePublishedByPlugin:)]);
718 XCTAssertFalse([registrar respondsToSelector:@selector(addMethodCallDelegate:channel:)]);
719 XCTAssertFalse([registrar respondsToSelector:@selector(addApplicationDelegate:)]);
720 XCTAssertFalse([registrar respondsToSelector:@selector(lookupKeyForAsset:)]);
721 XCTAssertFalse([registrar respondsToSelector:@selector(lookupKeyForAsset:fromPackage:)]);
722
723 // Verify messenger and textures forwards to FlutterEngine
724 XCTAssertEqual(registrar.messenger, mockBinaryMessenger);
725 XCTAssertEqual(registrar.textures, mockTextureRegistry);
726
727 // Verify registerViewFactory:withId:, registerViewFactory:withId:gestureRecognizersBlockingPolicy
728 // forwards to FlutterEngine
729 id mockPlatformViewFactory = OCMProtocolMock(@protocol(FlutterPlatformViewFactory));
730 [registrar registerViewFactory:mockPlatformViewFactory withId:factoryKey];
731 [registrar registerViewFactory:mockPlatformViewFactory
732 withId:factoryKey
733 gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager];
734 OCMVerify(times(2), [mockPlatformViewController registerViewFactory:mockPlatformViewFactory
735 withId:factoryKey
736 gestureRecognizersBlockingPolicy:
738}
739
740- (void)testSendDeepLinkToFrameworkTimesOut {
741 FlutterDartProject* project = [[FlutterDartProject alloc] init];
742 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"engine" project:project];
743 id mockEngine = OCMPartialMock(engine);
744 id mockEngineFirstFrameCallback = [OCMArg invokeBlockWithArgs:@YES, nil];
745 OCMStub([mockEngine waitForFirstFrame:3.0 callback:mockEngineFirstFrameCallback]);
746
747 NSURL* url = [NSURL URLWithString:@"example.com"];
748
749 [mockEngine sendDeepLinkToFramework:url
750 completionHandler:^(BOOL success) {
751 XCTAssertFalse(success);
752 }];
753}
754
755- (void)testSendDeepLinkToFrameworkUsingNavigationChannel {
756 NSString* urlString = @"example.com";
757 NSURL* url = [NSURL URLWithString:urlString];
758 FlutterDartProject* project = [[FlutterDartProject alloc] init];
759 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"engine" project:project];
760 id mockEngine = OCMPartialMock(engine);
761 id mockEngineFirstFrameCallback = [OCMArg invokeBlockWithArgs:@NO, nil];
762 OCMStub([mockEngine waitForFirstFrame:3.0 callback:mockEngineFirstFrameCallback]);
763 id mockNavigationChannel = OCMClassMock([FlutterMethodChannel class]);
764 OCMStub([mockEngine navigationChannel]).andReturn(mockNavigationChannel);
765 id mockNavigationChannelCallback = [OCMArg invokeBlockWithArgs:@1, nil];
766 OCMStub([mockNavigationChannel invokeMethod:@"pushRouteInformation"
767 arguments:@{@"location" : urlString}
768 result:mockNavigationChannelCallback]);
769
770 [mockEngine sendDeepLinkToFramework:url
771 completionHandler:^(BOOL success) {
772 XCTAssertTrue(success);
773 }];
774}
775
776- (void)testSendDeepLinkToFrameworkUsingNavigationChannelFails {
777 NSString* urlString = @"example.com";
778 NSURL* url = [NSURL URLWithString:urlString];
779 FlutterDartProject* project = [[FlutterDartProject alloc] init];
780 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"engine" project:project];
781 id mockEngine = OCMPartialMock(engine);
782 id mockEngineFirstFrameCallback = [OCMArg invokeBlockWithArgs:@NO, nil];
783 OCMStub([mockEngine waitForFirstFrame:3.0 callback:mockEngineFirstFrameCallback]);
784 id mockNavigationChannel = OCMClassMock([FlutterMethodChannel class]);
785 OCMStub([mockEngine navigationChannel]).andReturn(mockNavigationChannel);
786 id mockNavigationChannelCallback = [OCMArg invokeBlockWithArgs:@0, nil];
787 OCMStub([mockNavigationChannel invokeMethod:@"pushRouteInformation"
788 arguments:@{@"location" : urlString}
789 result:mockNavigationChannelCallback]);
790
791 [mockEngine sendDeepLinkToFramework:url
792 completionHandler:^(BOOL success) {
793 XCTAssertFalse(success);
794 }];
795}
796
797#pragma mark - Scene Lifecycle Warning Tests
798
799- (void)testAddApplicationDelegateLogsWarningWhenPluginDoesNotConformToSceneDelegate {
800 FlutterStringOutputWriter* writer = [[FlutterStringOutputWriter alloc] init];
801 writer.expectedOutput = @"uses deprecated application lifecycle events";
802 FlutterLogger.outputWriter = writer;
803
804 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"engine" project:nil];
805 id<FlutterPluginRegistrar> registrar = [engine registrarForPlugin:@"TestPlugin"];
806
807 // Create a mock plugin that does NOT conform to FlutterSceneLifeCycleDelegate.
808 id mockPlugin = OCMProtocolMock(@protocol(FlutterPlugin));
809
810 id mockAppDelegate = OCMProtocolMock(@protocol(FlutterAppLifeCycleProvider));
811 id mockApplication = OCMClassMock([UIApplication class]);
812 OCMStub([mockApplication sharedApplication]).andReturn(mockApplication);
813 OCMStub([mockApplication delegate]).andReturn(mockAppDelegate);
814
815 [registrar addApplicationDelegate:mockPlugin];
816
817 XCTAssertTrue(writer.gotExpectedOutput,
818 @"Expected warning about plugin not adopting scenes was not logged");
819}
820
821- (void)testAddApplicationDelegateDoesNotLogWarningWhenPluginConformsToSceneDelegate {
822 FlutterStringOutputWriter* writer = [[FlutterStringOutputWriter alloc] init];
823 FlutterLogger.outputWriter = writer;
824
825 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"engine" project:nil];
826 id<FlutterPluginRegistrar> registrar = [engine registrarForPlugin:@"TestPluginWithSceneEvents"];
827
828 id mockPlugin = OCMProtocolMock(@protocol(TestFlutterPluginWithSceneEvents));
829
830 id mockAppDelegate = OCMProtocolMock(@protocol(FlutterAppLifeCycleProvider));
831 id mockApplication = OCMClassMock([UIApplication class]);
832 OCMStub([mockApplication sharedApplication]).andReturn(mockApplication);
833 OCMStub([mockApplication delegate]).andReturn(mockAppDelegate);
834
835 [registrar addApplicationDelegate:mockPlugin];
836
837 XCTAssertFalse(writer.didLog, @"No warning should be logged for scene-conforming plugin");
838}
839
840- (void)testAddApplicationDelegateDoesNotLogWarningWhenPluginDoesNotUseLifecycleEvents {
841 FlutterStringOutputWriter* writer = [[FlutterStringOutputWriter alloc] init];
842 FlutterLogger.outputWriter = writer;
843
844 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"engine" project:nil];
845 id<FlutterPluginRegistrar> registrar = [engine registrarForPlugin:@"MinimalPlugin"];
846
847 // Use a concrete FlutterPlugin that does NOT implement any lifecycle methods.
848 // Even though it does not conform to FlutterSceneLifeCycleDelegate,
849 // no warning should be logged because it doesn't use any lifecycle events.
850 TestMinimalFlutterPlugin* plugin = [[TestMinimalFlutterPlugin alloc] init];
851
852 id mockAppDelegate = OCMProtocolMock(@protocol(FlutterAppLifeCycleProvider));
853 id mockApplication = OCMClassMock([UIApplication class]);
854 OCMStub([mockApplication sharedApplication]).andReturn(mockApplication);
855 OCMStub([mockApplication delegate]).andReturn(mockAppDelegate);
856
857 [registrar addApplicationDelegate:plugin];
858
859 XCTAssertFalse(writer.didLog,
860 @"No warning should be logged for a plugin that doesn't use lifecycle events");
861}
862
863@end
NS_ASSUME_NONNULL_BEGIN typedef void(^ FlutterBinaryReply)(NSData *_Nullable reply)
@ FlutterPlatformViewGestureRecognizersBlockingPolicyEager
const std::shared_ptr< IOSContext > & GetIosContext()
const TaskRunners & GetTaskRunners() const override
If callers wish to interact directly with any shell subcomponents, they must (on the platform thread)...
Definition shell.cc:916
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)
FlutterEngine engine
Definition main.cc:84
const char * message
const gchar * channel
HWND(* FlutterPlatformViewFactory)(const FlutterPlatformViewCreationParameters *)
FlutterDesktopBinaryReply callback
nullable FlutterFMLTaskRunner * uiTaskRunner()
nullable FlutterFMLTaskRunner * platformTaskRunner()
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::Shell & shell()
flutter::IOSRenderingAPI platformViewsRenderingAPI()
BOOL runWithEntrypoint:initialRoute:(nullable NSString *entrypoint,[initialRoute] nullable NSString *initialRoute)
nullable FlutterFMLTaskRunner * rasterTaskRunner()
NSMutableDictionary * pluginPublications
FlutterViewController * viewController
void waitForFirstFrame:callback:(NSTimeInterval timeout,[callback] void(^ callback)(BOOL didTimeout))
instancetype methodCallWithMethodName:arguments:(NSString *method,[arguments] id _Nullable arguments)
flutter::Settings FLTDefaultSettingsForBundle(NSBundle *bundle, NSProcessInfo *processInfoOrNil)
FlutterViewController * viewController
size_t length
nullable NSObject< FlutterPluginRegistrar > * registrarForPlugin:(NSString *pluginKey)
Represents the 2 code paths available when calling |SyncSwitchExecute|.
Definition sync_switch.h:35
int BOOL