Flutter Engine
 
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
23
24@protocol TestFlutterPluginWithSceneEvents <NSObject, FlutterPlugin, FlutterSceneLifeCycleDelegate>
25@end
26
29@end
30
31@implementation FlutterEngineSpy
32
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
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];
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"];
310 [engine run];
311 flutter::Shell& shell = engine.shell;
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]);
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(),
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(),
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:
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:
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
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:911
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 gchar * channel
G_BEGIN_DECLS GBytes * message
HWND(* FlutterPlatformViewFactory)(const FlutterPlatformViewCreationParameters *)
FlutterDesktopBinaryReply callback
FlutterEngine * spawnWithEntrypoint:libraryURI:initialRoute:entrypointArgs:(nullable NSString *entrypoint,[libraryURI] nullable NSString *libraryURI,[initialRoute] nullable NSString *initialRoute,[entrypointArgs] nullable NSArray< NSString * > *entrypointArgs)
NSObject< FlutterBinaryMessenger > * binaryMessenger
flutter::PlatformViewIOS * platformView()
void setBinaryMessenger:(FlutterBinaryMessengerRelay *binaryMessenger)
flutter::Shell & shell()
flutter::IOSRenderingAPI platformViewsRenderingAPI()
BOOL runWithEntrypoint:initialRoute:(nullable NSString *entrypoint,[initialRoute] nullable NSString *initialRoute)
NSMutableDictionary * pluginPublications
void ensureSemanticsEnabled()
FlutterViewController * viewController
void waitForFirstFrame:callback:(NSTimeInterval timeout,[callback] void(^ callback)(BOOL didTimeout))
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