| 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/FlutterSharedApplication.h"
19 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h"
20 #import "flutter/shell/platform/darwin/ios/platform_view_ios.h"
21 FLUTTER_ASSERT_ARC
22
23 @interface FlutterEngineSpy : FlutterEngine
24 @property(nonatomic) BOOL ensureSemanticsEnabledCalled;
25 @end
26
27 @implementation FlutterEngineSpy
28
29 - (void)ensureSemanticsEnabled {
30 _ensureSemanticsEnabledCalled = YES;
31 }
32
33 @end
34
35 @interface FlutterEngine () <FlutterTextInputDelegate>
36
37 @end
38
39 /// FlutterBinaryMessengerRelay used for testing that setting FlutterEngine.binaryMessenger to
40 /// the current instance doesn't trigger a use-after-free bug.
41 ///
42 /// See: testSetBinaryMessengerToSameBinaryMessenger
43 @interface FakeBinaryMessengerRelay : FlutterBinaryMessengerRelay
44 @property(nonatomic, assign) BOOL failOnDealloc;
45 @end
46
47 @implementation FakeBinaryMessengerRelay
48 - (void)dealloc {
49 if (_failOnDealloc) {
50 XCTFail("FakeBinaryMessageRelay should not be deallocated");
51 }
52 }
53 @end
54
55 @interface FlutterEngineTest : XCTestCase
56 @end
57
58 @implementation FlutterEngineTest
59
60 - (void)setUp {
61 }
62
63 - (void)tearDown {
64 }
65
66 - (void)testCreate {
67 FlutterDartProject* project = [[FlutterDartProject alloc] init];
68 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
69 XCTAssertNotNil(engine);
70 }
71
72 - (void)testShellGetters {
73 FlutterDartProject* project = [[FlutterDartProject alloc] init];
74 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
75 XCTAssertNotNil(engine);
76
77 // Ensure getters don't deref _shell when it's null, and instead return nullptr.
78 XCTAssertEqual(engine.platformTaskRunner.get(), nullptr);
79 XCTAssertEqual(engine.uiTaskRunner.get(), nullptr);
80 XCTAssertEqual(engine.rasterTaskRunner.get(), nullptr);
81 }
82
83 - (void)testInfoPlist {
84 // Check the embedded Flutter.framework Info.plist, not the linked dylib.
85 NSURL* flutterFrameworkURL =
86 [NSBundle.mainBundle.privateFrameworksURL URLByAppendingPathComponent:@"Flutter.framework"];
87 NSBundle* flutterBundle = [NSBundle bundleWithURL:flutterFrameworkURL];
88 XCTAssertEqualObjects(flutterBundle.bundleIdentifier, @"io.flutter.flutter");
89
90 NSDictionary<NSString*, id>* infoDictionary = flutterBundle.infoDictionary;
91
92 // OS version can have one, two, or three digits: "8", "8.0", "8.0.0"
93 NSError* regexError = NULL;
94 NSRegularExpression* osVersionRegex =
95 [NSRegularExpression regularExpressionWithPattern:@"((0|[1-9]\\d*)\\.)*(0|[1-9]\\d*)"
96 options:NSRegularExpressionCaseInsensitive
97 error:&regexError];
98 XCTAssertNil(regexError);
99
100 // Smoke test the test regex.
101 NSString* testString = @"9";
102 NSUInteger versionMatches =
103 [osVersionRegex numberOfMatchesInString:testString
104 options:NSMatchingAnchored
105 range:NSMakeRange(0, testString.length)];
106 XCTAssertEqual(versionMatches, 1UL);
107 testString = @"9.1";
108 versionMatches = [osVersionRegex numberOfMatchesInString:testString
109 options:NSMatchingAnchored
110 range:NSMakeRange(0, testString.length)];
111 XCTAssertEqual(versionMatches, 1UL);
112 testString = @"9.0.1";
113 versionMatches = [osVersionRegex numberOfMatchesInString:testString
114 options:NSMatchingAnchored
115 range:NSMakeRange(0, testString.length)];
116 XCTAssertEqual(versionMatches, 1UL);
117 testString = @".0.1";
118 versionMatches = [osVersionRegex numberOfMatchesInString:testString
119 options:NSMatchingAnchored
120 range:NSMakeRange(0, testString.length)];
121 XCTAssertEqual(versionMatches, 0UL);
122
123 // Test Info.plist values.
124 NSString* minimumOSVersion = infoDictionary[@"MinimumOSVersion"];
125 versionMatches = [osVersionRegex numberOfMatchesInString:minimumOSVersion
126 options:NSMatchingAnchored
127 range:NSMakeRange(0, minimumOSVersion.length)];
128 XCTAssertEqual(versionMatches, 1UL);
129
130 // SHA length is 40.
131 XCTAssertEqual(((NSString*)infoDictionary[@"FlutterEngine"]).length, 40UL);
132
133 // {clang_version} placeholder is 15 characters. The clang string version
134 // is longer than that, so check if the placeholder has been replaced, without
135 // actually checking a literal string, which could be different on various machines.
136 XCTAssertTrue(((NSString*)infoDictionary[@"ClangVersion"]).length > 15UL);
137 }
138
139 - (void)testDeallocated {
140 __weak FlutterEngine* weakEngine = nil;
141 @autoreleasepool {
142 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar"];
143 weakEngine = engine;
144 [engine run];
145 XCTAssertNotNil(weakEngine);
146 }
147 XCTAssertNil(weakEngine);
148 }
149
150 - (void)testSendMessageBeforeRun {
151 FlutterDartProject* project = [[FlutterDartProject alloc] init];
152 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
153 XCTAssertNotNil(engine);
154 XCTAssertThrows([engine.binaryMessenger
155 sendOnChannel:@"foo"
156 message:[@"bar" dataUsingEncoding:NSUTF8StringEncoding]
157 binaryReply:nil]);
158 }
159
160 - (void)testSetMessageHandlerBeforeRun {
161 FlutterDartProject* project = [[FlutterDartProject alloc] init];
162 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
163 XCTAssertNotNil(engine);
164 XCTAssertThrows([engine.binaryMessenger
165 setMessageHandlerOnChannel:@"foo"
166 binaryMessageHandler:^(NSData* _Nullable message, FlutterBinaryReply _Nonnull reply){
167
168 }]);
169 }
170
171 - (void)testNilSetMessageHandlerBeforeRun {
172 FlutterDartProject* project = [[FlutterDartProject alloc] init];
173 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
174 XCTAssertNotNil(engine);
175 XCTAssertNoThrow([engine.binaryMessenger setMessageHandlerOnChannel:@"foo"
176 binaryMessageHandler:nil]);
177 }
178
179 - (void)testNotifyPluginOfDealloc {
180 id plugin = OCMProtocolMock(@protocol(FlutterPlugin));
181 OCMStub([plugin detachFromEngineForRegistrar:[OCMArg any]]);
182 {
183 FlutterDartProject* project = [[FlutterDartProject alloc] init];
184 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"engine" project:project];
185 NSObject<FlutterPluginRegistrar>* registrar = [engine registrarForPlugin:@"plugin"];
186 [registrar publish:plugin];
187 engine = nil;
188 }
189 OCMVerify([plugin detachFromEngineForRegistrar:[OCMArg any]]);
190 }
191
192 - (void)testGetViewControllerFromRegistrar {
193 FlutterDartProject* project = [[FlutterDartProject alloc] init];
194 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"engine" project:project];
195 id mockEngine = OCMPartialMock(engine);
196 NSObject<FlutterPluginRegistrar>* registrar = [mockEngine registrarForPlugin:@"plugin"];
197
198 // Verify accessing the viewController getter calls FlutterEngine.viewController.
199 (void)[registrar viewController];
200 OCMVerify(times(1), [mockEngine viewController]);
201 }
202
203 - (void)testSetBinaryMessengerToSameBinaryMessenger {
204 FakeBinaryMessengerRelay* fakeBinaryMessenger = [[FakeBinaryMessengerRelay alloc] init];
205
206 FlutterEngine* engine = [[FlutterEngine alloc] init];
207 [engine setBinaryMessenger:fakeBinaryMessenger];
208
209 // Verify that the setter doesn't free the old messenger before setting the new messenger.
210 fakeBinaryMessenger.failOnDealloc = YES;
211 [engine setBinaryMessenger:fakeBinaryMessenger];
212
213 // Don't fail when ARC releases the binary messenger.
214 fakeBinaryMessenger.failOnDealloc = NO;
215 }
216
217 - (void)testRunningInitialRouteSendsNavigationMessage {
218 id mockBinaryMessenger = OCMClassMock([FlutterBinaryMessengerRelay class]);
219
220 FlutterEngine* engine = [[FlutterEngine alloc] init];
221 [engine setBinaryMessenger:mockBinaryMessenger];
222
223 // Run with an initial route.
224 [engine runWithEntrypoint:FlutterDefaultDartEntrypoint initialRoute:@"test"];
225
226 // Now check that an encoded method call has been made on the binary messenger to set the
227 // initial route to "test".
228 FlutterMethodCall* setInitialRouteMethodCall =
229 [FlutterMethodCall methodCallWithMethodName:@"setInitialRoute" arguments:@"test"];
230 NSData* encodedSetInitialRouteMethod =
231 [[FlutterJSONMethodCodec sharedInstance] encodeMethodCall:setInitialRouteMethodCall];
232 OCMVerify([mockBinaryMessenger sendOnChannel:@"flutter/navigation"
233 message:encodedSetInitialRouteMethod]);
234 }
235
236 - (void)testInitialRouteSettingsSendsNavigationMessage {
237 id mockBinaryMessenger = OCMClassMock([FlutterBinaryMessengerRelay class]);
238
239 auto settings = FLTDefaultSettingsForBundle();
240 settings.route = "test";
241 FlutterDartProject* project = [[FlutterDartProject alloc] initWithSettings:settings];
242 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
243 [engine setBinaryMessenger:mockBinaryMessenger];
244 [engine run];
245
246 // Now check that an encoded method call has been made on the binary messenger to set the
247 // initial route to "test".
248 FlutterMethodCall* setInitialRouteMethodCall =
249 [FlutterMethodCall methodCallWithMethodName:@"setInitialRoute" arguments:@"test"];
250 NSData* encodedSetInitialRouteMethod =
251 [[FlutterJSONMethodCodec sharedInstance] encodeMethodCall:setInitialRouteMethodCall];
252 OCMVerify([mockBinaryMessenger sendOnChannel:@"flutter/navigation"
253 message:encodedSetInitialRouteMethod]);
254 }
255
256 - (void)testPlatformViewsControllerRenderingMetalBackend {
257 FlutterEngine* engine = [[FlutterEngine alloc] init];
258 [engine run];
259 flutter::IOSRenderingAPI renderingApi = [engine platformViewsRenderingAPI];
260
261 XCTAssertEqual(renderingApi, flutter::IOSRenderingAPI::kMetal);
262 }
263
264 - (void)testWaitForFirstFrameTimeout {
265 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar"];
266 [engine run];
267 XCTestExpectation* timeoutFirstFrame = [self expectationWithDescription:@"timeoutFirstFrame"];
268 [engine waitForFirstFrame:0.1
269 callback:^(BOOL didTimeout) {
270 if (timeoutFirstFrame) {
271 [timeoutFirstFrame fulfill];
272 }
273 }];
274 [self waitForExpectations:@[ timeoutFirstFrame ]];
275 }
276
277 - (void)testSpawn {
278 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar"];
279 [engine run];
280 FlutterEngine* spawn = [engine spawnWithEntrypoint:nil
281 libraryURI:nil
282 initialRoute:nil
283 entrypointArgs:nil];
284 XCTAssertNotNil(spawn);
285 }
286
287 - (void)testEngineId {
288 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar"];
289 [engine run];
290 int64_t id1 = engine.engineIdentifier;
291 XCTAssertTrue(id1 != 0);
292 FlutterEngine* spawn = [engine spawnWithEntrypoint:nil
293 libraryURI:nil
294 initialRoute:nil
295 entrypointArgs:nil];
296 int64_t id2 = spawn.engineIdentifier;
297 XCTAssertEqual([FlutterEngine engineForIdentifier:id1], engine);
298 XCTAssertEqual([FlutterEngine engineForIdentifier:id2], spawn);
299 }
300
301 - (void)testSetHandlerAfterRun {
302 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar"];
303 XCTestExpectation* gotMessage = [self expectationWithDescription:@"gotMessage"];
304 dispatch_async(dispatch_get_main_queue(), ^{
305 NSObject<FlutterPluginRegistrar>* registrar = [engine registrarForPlugin:@"foo"];
306 fml::AutoResetWaitableEvent latch;
307 [engine run];
308 flutter::Shell& shell = engine.shell;
309 fml::TaskRunner::RunNowOrPostTask(
310 engine.shell.GetTaskRunners().GetUITaskRunner(), [&latch, &shell] {
311 flutter::Engine::Delegate& delegate = shell;
312 auto message = std::make_unique<flutter::PlatformMessage>("foo", nullptr);
313 delegate.OnEngineHandlePlatformMessage(std::move(message));
314 latch.Signal();
315 });
316 latch.Wait();
317 [registrar.messenger setMessageHandlerOnChannel:@"foo"
318 binaryMessageHandler:^(NSData* message, FlutterBinaryReply reply) {
319 [gotMessage fulfill];
320 }];
321 });
322 [self waitForExpectations:@[ gotMessage ]];
323 }
324
325 - (void)testThreadPrioritySetCorrectly {
326 XCTestExpectation* prioritiesSet = [self expectationWithDescription:@"prioritiesSet"];
327 prioritiesSet.expectedFulfillmentCount = 2;
328
329 IMP mockSetThreadPriority =
330 imp_implementationWithBlock(^(NSThread* thread, double threadPriority) {
331 if ([thread.name hasSuffix:@".raster"]) {
332 XCTAssertEqual(threadPriority, 1.0);
333 [prioritiesSet fulfill];
334 } else if ([thread.name hasSuffix:@".io"]) {
335 XCTAssertEqual(threadPriority, 0.5);
336 [prioritiesSet fulfill];
337 }
338 });
339 Method method = class_getInstanceMethod([NSThread class], @selector(setThreadPriority:));
340 IMP originalSetThreadPriority = method_getImplementation(method);
341 method_setImplementation(method, mockSetThreadPriority);
342
343 FlutterEngine* engine = [[FlutterEngine alloc] init];
344 [engine run];
345 [self waitForExpectations:@[ prioritiesSet ]];
346
347 method_setImplementation(method, originalSetThreadPriority);
348 }
349
350 - (void)testCanEnableDisableEmbedderAPIThroughInfoPlist {
351 {
352 // Not enable embedder API by default
353 auto settings = FLTDefaultSettingsForBundle();
354 settings.enable_software_rendering = true;
355 FlutterDartProject* project = [[FlutterDartProject alloc] initWithSettings:settings];
356 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
357 XCTAssertFalse(engine.enableEmbedderAPI);
358 }
359 {
360 // Enable embedder api
361 id mockMainBundle = OCMPartialMock([NSBundle mainBundle]);
362 OCMStub([mockMainBundle objectForInfoDictionaryKey:@"FLTEnableIOSEmbedderAPI"])
363 .andReturn(@"YES");
364 auto settings = FLTDefaultSettingsForBundle();
365 settings.enable_software_rendering = true;
366 FlutterDartProject* project = [[FlutterDartProject alloc] initWithSettings:settings];
367 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
368 XCTAssertTrue(engine.enableEmbedderAPI);
369 }
370 }
371
372 - (void)testFlutterTextInputViewDidResignFirstResponderWillCallTextInputClientConnectionClosed {
373 id mockBinaryMessenger = OCMClassMock([FlutterBinaryMessengerRelay class]);
374 FlutterEngine* engine = [[FlutterEngine alloc] init];
375 [engine setBinaryMessenger:mockBinaryMessenger];
376 [engine runWithEntrypoint:FlutterDefaultDartEntrypoint initialRoute:@"test"];
377 [engine flutterTextInputView:nil didResignFirstResponderWithTextInputClient:1];
378 FlutterMethodCall* methodCall =
379 [FlutterMethodCall methodCallWithMethodName:@"TextInputClient.onConnectionClosed"
380 arguments:@[ @(1) ]];
381 NSData* encodedMethodCall = [[FlutterJSONMethodCodec sharedInstance] encodeMethodCall:methodCall];
382 OCMVerify([mockBinaryMessenger sendOnChannel:@"flutter/textinput" message:encodedMethodCall]);
383 }
384
385 - (void)testFlutterEngineUpdatesDisplays {
386 FlutterEngine* engine = [[FlutterEngine alloc] init];
387 id mockEngine = OCMPartialMock(engine);
388
389 [engine run];
390 OCMVerify(times(1), [mockEngine updateDisplays]);
391 engine.viewController = nil;
392 OCMVerify(times(2), [mockEngine updateDisplays]);
393 }
394
395 - (void)testLifeCycleNotificationDidEnterBackgroundForApplication {
396 FlutterDartProject* project = [[FlutterDartProject alloc] init];
397 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
398 [engine run];
399 NSNotification* sceneNotification =
400 [NSNotification notificationWithName:UISceneDidEnterBackgroundNotification
401 object:nil
402 userInfo:nil];
403 NSNotification* applicationNotification =
404 [NSNotification notificationWithName:UIApplicationDidEnterBackgroundNotification
405 object:nil
406 userInfo:nil];
407 id mockEngine = OCMPartialMock(engine);
408 [NSNotificationCenter.defaultCenter postNotification:sceneNotification];
409 [NSNotificationCenter.defaultCenter postNotification:applicationNotification];
410 OCMVerify(times(1), [mockEngine applicationDidEnterBackground:[OCMArg any]]);
411 XCTAssertTrue(engine.isGpuDisabled);
412 BOOL gpuDisabled = NO;
413 [engine shell].GetIsGpuDisabledSyncSwitch()->Execute(
414 fml::SyncSwitch::Handlers().SetIfTrue([&] { gpuDisabled = YES; }).SetIfFalse([&] {
415 gpuDisabled = NO;
416 }));
417 XCTAssertTrue(gpuDisabled);
418 }
419
420 - (void)testLifeCycleNotificationDidEnterBackgroundForScene {
421 id mockBundle = OCMPartialMock([NSBundle mainBundle]);
422 OCMStub([mockBundle objectForInfoDictionaryKey:@"NSExtension"]).andReturn(@{
423 @"NSExtensionPointIdentifier" : @"com.apple.share-services"
424 });
425 FlutterDartProject* project = [[FlutterDartProject alloc] init];
426 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
427 [engine run];
428 NSNotification* sceneNotification =
429 [NSNotification notificationWithName:UISceneDidEnterBackgroundNotification
430 object:nil
431 userInfo:nil];
432 NSNotification* applicationNotification =
433 [NSNotification notificationWithName:UIApplicationDidEnterBackgroundNotification
434 object:nil
435 userInfo:nil];
436 id mockEngine = OCMPartialMock(engine);
437 [NSNotificationCenter.defaultCenter postNotification:sceneNotification];
438 [NSNotificationCenter.defaultCenter postNotification:applicationNotification];
439 OCMVerify(times(1), [mockEngine sceneDidEnterBackground:[OCMArg any]]);
440 XCTAssertTrue(engine.isGpuDisabled);
441 BOOL gpuDisabled = NO;
442 [engine shell].GetIsGpuDisabledSyncSwitch()->Execute(
443 fml::SyncSwitch::Handlers().SetIfTrue([&] { gpuDisabled = YES; }).SetIfFalse([&] {
444 gpuDisabled = NO;
445 }));
446 XCTAssertTrue(gpuDisabled);
447 [mockBundle stopMocking];
448 }
449
450 - (void)testLifeCycleNotificationWillEnterForegroundForApplication {
451 FlutterDartProject* project = [[FlutterDartProject alloc] init];
452 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
453 [engine run];
454 NSNotification* sceneNotification =
455 [NSNotification notificationWithName:UISceneWillEnterForegroundNotification
456 object:nil
457 userInfo:nil];
458 NSNotification* applicationNotification =
459 [NSNotification notificationWithName:UIApplicationWillEnterForegroundNotification
460 object:nil
461 userInfo:nil];
462 id mockEngine = OCMPartialMock(engine);
463 [NSNotificationCenter.defaultCenter postNotification:sceneNotification];
464 [NSNotificationCenter.defaultCenter postNotification:applicationNotification];
465 OCMVerify(times(1), [mockEngine applicationWillEnterForeground:[OCMArg any]]);
466 XCTAssertFalse(engine.isGpuDisabled);
467 BOOL gpuDisabled = YES;
468 [engine shell].GetIsGpuDisabledSyncSwitch()->Execute(
469 fml::SyncSwitch::Handlers().SetIfTrue([&] { gpuDisabled = YES; }).SetIfFalse([&] {
470 gpuDisabled = NO;
471 }));
472 XCTAssertFalse(gpuDisabled);
473 }
474
475 - (void)testLifeCycleNotificationWillEnterForegroundForScene {
476 id mockBundle = OCMPartialMock([NSBundle mainBundle]);
477 OCMStub([mockBundle objectForInfoDictionaryKey:@"NSExtension"]).andReturn(@{
478 @"NSExtensionPointIdentifier" : @"com.apple.share-services"
479 });
480 FlutterDartProject* project = [[FlutterDartProject alloc] init];
481 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
482 [engine run];
483 NSNotification* sceneNotification =
484 [NSNotification notificationWithName:UISceneWillEnterForegroundNotification
485 object:nil
486 userInfo:nil];
487 NSNotification* applicationNotification =
488 [NSNotification notificationWithName:UIApplicationWillEnterForegroundNotification
489 object:nil
490 userInfo:nil];
491 id mockEngine = OCMPartialMock(engine);
492 [NSNotificationCenter.defaultCenter postNotification:sceneNotification];
493 [NSNotificationCenter.defaultCenter postNotification:applicationNotification];
494 OCMVerify(times(1), [mockEngine sceneWillEnterForeground:[OCMArg any]]);
495 XCTAssertFalse(engine.isGpuDisabled);
496 BOOL gpuDisabled = YES;
497 [engine shell].GetIsGpuDisabledSyncSwitch()->Execute(
498 fml::SyncSwitch::Handlers().SetIfTrue([&] { gpuDisabled = YES; }).SetIfFalse([&] {
499 gpuDisabled = NO;
500 }));
501 XCTAssertFalse(gpuDisabled);
502 [mockBundle stopMocking];
503 }
504
505 - (void)testSpawnsShareGpuContext {
506 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar"];
507 [engine run];
508 FlutterEngine* spawn = [engine spawnWithEntrypoint:nil
509 libraryURI:nil
510 initialRoute:nil
511 entrypointArgs:nil];
512 XCTAssertNotNil(spawn);
513 XCTAssertTrue(engine.platformView != nullptr);
514 XCTAssertTrue(spawn.platformView != nullptr);
515 std::shared_ptr<flutter::IOSContext> engine_context = engine.platformView->GetIosContext();
516 std::shared_ptr<flutter::IOSContext> spawn_context = spawn.platformView->GetIosContext();
517 XCTAssertEqual(engine_context, spawn_context);
518 }
519
520 - (void)testEnableSemanticsWhenFlutterViewAccessibilityDidCall {
521 FlutterEngineSpy* engine = [[FlutterEngineSpy alloc] initWithName:@"foobar"];
522 engine.ensureSemanticsEnabledCalled = NO;
523 [engine flutterViewAccessibilityDidCall];
524 XCTAssertTrue(engine.ensureSemanticsEnabledCalled);
525 }
526
527 - (void)testCanMergePlatformAndUIThread {
528 #if defined(TARGET_IPHONE_SIMULATOR) && TARGET_IPHONE_SIMULATOR
529 auto settings = FLTDefaultSettingsForBundle();
530 FlutterDartProject* project = [[FlutterDartProject alloc] initWithSettings:settings];
531 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
532 [engine run];
533
534 XCTAssertEqual(engine.shell.GetTaskRunners().GetUITaskRunner(),
535 engine.shell.GetTaskRunners().GetPlatformTaskRunner());
536 #endif // defined(TARGET_IPHONE_SIMULATOR) && TARGET_IPHONE_SIMULATOR
537 }
538
539 - (void)testCanUnMergePlatformAndUIThread {
540 #if defined(TARGET_IPHONE_SIMULATOR) && TARGET_IPHONE_SIMULATOR
541 auto settings = FLTDefaultSettingsForBundle();
542 settings.merged_platform_ui_thread = flutter::Settings::MergedPlatformUIThread::kDisabled;
543 FlutterDartProject* project = [[FlutterDartProject alloc] initWithSettings:settings];
544 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
545 [engine run];
546
547 XCTAssertNotEqual(engine.shell.GetTaskRunners().GetUITaskRunner(),
548 engine.shell.GetTaskRunners().GetPlatformTaskRunner());
549 #endif // defined(TARGET_IPHONE_SIMULATOR) && TARGET_IPHONE_SIMULATOR
550 }
551
552 @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
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:124
FakeBinaryMessengerRelay
Definition: FlutterEngineTest.mm:44
FakeBinaryMessengerRelay::failOnDealloc
BOOL failOnDealloc
Definition: FlutterEngineTest.mm:44
FlutterBinaryMessengerRelay
Definition: FlutterBinaryMessengerRelay.h:14
FlutterEngine
Definition: FlutterEngine.h:61
-[FlutterEngine run]
BOOL run()
Definition: FlutterEngine.mm:897
-[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 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 ensureSemanticsEnabled]
void ensureSemanticsEnabled()
Definition: FlutterEngine.mm:443
-[FlutterEngine waitForFirstFrame:callback:]
void waitForFirstFrame:callback:(NSTimeInterval timeout,[callback] void(^ callback)(BOOL didTimeout))
FlutterEngineSpy
Definition: FlutterEngineTest.mm:24
FlutterEngineSpy::ensureSemanticsEnabledCalled
BOOL ensureSemanticsEnabledCalled
Definition: FlutterEngineTest.mm:24
FlutterEngineTest
Definition: FlutterEngineTest.mm:56
FlutterJSONMethodCodec
Definition: FlutterCodecs.h:455
FlutterMethodCall
Definition: FlutterCodecs.h:221
+[FlutterMethodCall methodCallWithMethodName:arguments:]
instancetype methodCallWithMethodName:arguments:(NSString *method,[arguments] id _Nullable arguments)
FLUTTER_ASSERT_ARC
Definition: FlutterChannelKeyResponder.mm:13
flutter::IOSRenderingAPI
IOSRenderingAPI
Definition: rendering_api_selection.h:14
flutter::IOSRenderingAPI::kMetal
@ kMetal
platform_view_ios.h
FlutterPlugin-p
Definition: FlutterPlugin.h:189
-[FlutterPluginRegistry-p registrarForPlugin:]
nullable NSObject< FlutterPluginRegistrar > * registrarForPlugin:(NSString *pluginKey)
FlutterTextInputDelegate-p
Definition: FlutterTextInputDelegate.h:37