| 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)testSetBinaryMessengerToSameBinaryMessenger {
193 FakeBinaryMessengerRelay* fakeBinaryMessenger = [[FakeBinaryMessengerRelay alloc] init];
194
195 FlutterEngine* engine = [[FlutterEngine alloc] init];
196 [engine setBinaryMessenger:fakeBinaryMessenger];
197
198 // Verify that the setter doesn't free the old messenger before setting the new messenger.
199 fakeBinaryMessenger.failOnDealloc = YES;
200 [engine setBinaryMessenger:fakeBinaryMessenger];
201
202 // Don't fail when ARC releases the binary messenger.
203 fakeBinaryMessenger.failOnDealloc = NO;
204 }
205
206 - (void)testRunningInitialRouteSendsNavigationMessage {
207 id mockBinaryMessenger = OCMClassMock([FlutterBinaryMessengerRelay class]);
208
209 FlutterEngine* engine = [[FlutterEngine alloc] init];
210 [engine setBinaryMessenger:mockBinaryMessenger];
211
212 // Run with an initial route.
213 [engine runWithEntrypoint:FlutterDefaultDartEntrypoint initialRoute:@"test"];
214
215 // Now check that an encoded method call has been made on the binary messenger to set the
216 // initial route to "test".
217 FlutterMethodCall* setInitialRouteMethodCall =
218 [FlutterMethodCall methodCallWithMethodName:@"setInitialRoute" arguments:@"test"];
219 NSData* encodedSetInitialRouteMethod =
220 [[FlutterJSONMethodCodec sharedInstance] encodeMethodCall:setInitialRouteMethodCall];
221 OCMVerify([mockBinaryMessenger sendOnChannel:@"flutter/navigation"
222 message:encodedSetInitialRouteMethod]);
223 }
224
225 - (void)testInitialRouteSettingsSendsNavigationMessage {
226 id mockBinaryMessenger = OCMClassMock([FlutterBinaryMessengerRelay class]);
227
228 auto settings = FLTDefaultSettingsForBundle();
229 settings.route = "test";
230 FlutterDartProject* project = [[FlutterDartProject alloc] initWithSettings:settings];
231 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
232 [engine setBinaryMessenger:mockBinaryMessenger];
233 [engine run];
234
235 // Now check that an encoded method call has been made on the binary messenger to set the
236 // initial route to "test".
237 FlutterMethodCall* setInitialRouteMethodCall =
238 [FlutterMethodCall methodCallWithMethodName:@"setInitialRoute" arguments:@"test"];
239 NSData* encodedSetInitialRouteMethod =
240 [[FlutterJSONMethodCodec sharedInstance] encodeMethodCall:setInitialRouteMethodCall];
241 OCMVerify([mockBinaryMessenger sendOnChannel:@"flutter/navigation"
242 message:encodedSetInitialRouteMethod]);
243 }
244
245 - (void)testPlatformViewsControllerRenderingMetalBackend {
246 FlutterEngine* engine = [[FlutterEngine alloc] init];
247 [engine run];
248 flutter::IOSRenderingAPI renderingApi = [engine platformViewsRenderingAPI];
249
250 XCTAssertEqual(renderingApi, flutter::IOSRenderingAPI::kMetal);
251 }
252
253 - (void)testWaitForFirstFrameTimeout {
254 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar"];
255 [engine run];
256 XCTestExpectation* timeoutFirstFrame = [self expectationWithDescription:@"timeoutFirstFrame"];
257 [engine waitForFirstFrame:0.1
258 callback:^(BOOL didTimeout) {
259 if (timeoutFirstFrame) {
260 [timeoutFirstFrame fulfill];
261 }
262 }];
263 [self waitForExpectations:@[ timeoutFirstFrame ]];
264 }
265
266 - (void)testSpawn {
267 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar"];
268 [engine run];
269 FlutterEngine* spawn = [engine spawnWithEntrypoint:nil
270 libraryURI:nil
271 initialRoute:nil
272 entrypointArgs:nil];
273 XCTAssertNotNil(spawn);
274 }
275
276 - (void)testEngineId {
277 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar"];
278 [engine run];
279 int64_t id1 = engine.engineIdentifier;
280 XCTAssertTrue(id1 != 0);
281 FlutterEngine* spawn = [engine spawnWithEntrypoint:nil
282 libraryURI:nil
283 initialRoute:nil
284 entrypointArgs:nil];
285 int64_t id2 = spawn.engineIdentifier;
286 XCTAssertEqual([FlutterEngine engineForIdentifier:id1], engine);
287 XCTAssertEqual([FlutterEngine engineForIdentifier:id2], spawn);
288 }
289
290 - (void)testSetHandlerAfterRun {
291 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar"];
292 XCTestExpectation* gotMessage = [self expectationWithDescription:@"gotMessage"];
293 dispatch_async(dispatch_get_main_queue(), ^{
294 NSObject<FlutterPluginRegistrar>* registrar = [engine registrarForPlugin:@"foo"];
295 fml::AutoResetWaitableEvent latch;
296 [engine run];
297 flutter::Shell& shell = engine.shell;
298 fml::TaskRunner::RunNowOrPostTask(
299 engine.shell.GetTaskRunners().GetUITaskRunner(), [&latch, &shell] {
300 flutter::Engine::Delegate& delegate = shell;
301 auto message = std::make_unique<flutter::PlatformMessage>("foo", nullptr);
302 delegate.OnEngineHandlePlatformMessage(std::move(message));
303 latch.Signal();
304 });
305 latch.Wait();
306 [registrar.messenger setMessageHandlerOnChannel:@"foo"
307 binaryMessageHandler:^(NSData* message, FlutterBinaryReply reply) {
308 [gotMessage fulfill];
309 }];
310 });
311 [self waitForExpectations:@[ gotMessage ]];
312 }
313
314 - (void)testThreadPrioritySetCorrectly {
315 XCTestExpectation* prioritiesSet = [self expectationWithDescription:@"prioritiesSet"];
316 prioritiesSet.expectedFulfillmentCount = 2;
317
318 IMP mockSetThreadPriority =
319 imp_implementationWithBlock(^(NSThread* thread, double threadPriority) {
320 if ([thread.name hasSuffix:@".raster"]) {
321 XCTAssertEqual(threadPriority, 1.0);
322 [prioritiesSet fulfill];
323 } else if ([thread.name hasSuffix:@".io"]) {
324 XCTAssertEqual(threadPriority, 0.5);
325 [prioritiesSet fulfill];
326 }
327 });
328 Method method = class_getInstanceMethod([NSThread class], @selector(setThreadPriority:));
329 IMP originalSetThreadPriority = method_getImplementation(method);
330 method_setImplementation(method, mockSetThreadPriority);
331
332 FlutterEngine* engine = [[FlutterEngine alloc] init];
333 [engine run];
334 [self waitForExpectations:@[ prioritiesSet ]];
335
336 method_setImplementation(method, originalSetThreadPriority);
337 }
338
339 - (void)testCanEnableDisableEmbedderAPIThroughInfoPlist {
340 {
341 // Not enable embedder API by default
342 auto settings = FLTDefaultSettingsForBundle();
343 settings.enable_software_rendering = true;
344 FlutterDartProject* project = [[FlutterDartProject alloc] initWithSettings:settings];
345 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
346 XCTAssertFalse(engine.enableEmbedderAPI);
347 }
348 {
349 // Enable embedder api
350 id mockMainBundle = OCMPartialMock([NSBundle mainBundle]);
351 OCMStub([mockMainBundle objectForInfoDictionaryKey:@"FLTEnableIOSEmbedderAPI"])
352 .andReturn(@"YES");
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 XCTAssertTrue(engine.enableEmbedderAPI);
358 }
359 }
360
361 - (void)testFlutterTextInputViewDidResignFirstResponderWillCallTextInputClientConnectionClosed {
362 id mockBinaryMessenger = OCMClassMock([FlutterBinaryMessengerRelay class]);
363 FlutterEngine* engine = [[FlutterEngine alloc] init];
364 [engine setBinaryMessenger:mockBinaryMessenger];
365 [engine runWithEntrypoint:FlutterDefaultDartEntrypoint initialRoute:@"test"];
366 [engine flutterTextInputView:nil didResignFirstResponderWithTextInputClient:1];
367 FlutterMethodCall* methodCall =
368 [FlutterMethodCall methodCallWithMethodName:@"TextInputClient.onConnectionClosed"
369 arguments:@[ @(1) ]];
370 NSData* encodedMethodCall = [[FlutterJSONMethodCodec sharedInstance] encodeMethodCall:methodCall];
371 OCMVerify([mockBinaryMessenger sendOnChannel:@"flutter/textinput" message:encodedMethodCall]);
372 }
373
374 - (void)testFlutterEngineUpdatesDisplays {
375 FlutterEngine* engine = [[FlutterEngine alloc] init];
376 id mockEngine = OCMPartialMock(engine);
377
378 [engine run];
379 OCMVerify(times(1), [mockEngine updateDisplays]);
380 engine.viewController = nil;
381 OCMVerify(times(2), [mockEngine updateDisplays]);
382 }
383
384 - (void)testLifeCycleNotificationDidEnterBackgroundForApplication {
385 FlutterDartProject* project = [[FlutterDartProject alloc] init];
386 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
387 [engine run];
388 NSNotification* sceneNotification =
389 [NSNotification notificationWithName:UISceneDidEnterBackgroundNotification
390 object:nil
391 userInfo:nil];
392 NSNotification* applicationNotification =
393 [NSNotification notificationWithName:UIApplicationDidEnterBackgroundNotification
394 object:nil
395 userInfo:nil];
396 id mockEngine = OCMPartialMock(engine);
397 [NSNotificationCenter.defaultCenter postNotification:sceneNotification];
398 [NSNotificationCenter.defaultCenter postNotification:applicationNotification];
399 OCMVerify(times(1), [mockEngine applicationDidEnterBackground:[OCMArg any]]);
400 XCTAssertTrue(engine.isGpuDisabled);
401 BOOL gpuDisabled = NO;
402 [engine shell].GetIsGpuDisabledSyncSwitch()->Execute(
403 fml::SyncSwitch::Handlers().SetIfTrue([&] { gpuDisabled = YES; }).SetIfFalse([&] {
404 gpuDisabled = NO;
405 }));
406 XCTAssertTrue(gpuDisabled);
407 }
408
409 - (void)testLifeCycleNotificationDidEnterBackgroundForScene {
410 id mockBundle = OCMPartialMock([NSBundle mainBundle]);
411 OCMStub([mockBundle objectForInfoDictionaryKey:@"NSExtension"]).andReturn(@{
412 @"NSExtensionPointIdentifier" : @"com.apple.share-services"
413 });
414 FlutterDartProject* project = [[FlutterDartProject alloc] init];
415 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
416 [engine run];
417 NSNotification* sceneNotification =
418 [NSNotification notificationWithName:UISceneDidEnterBackgroundNotification
419 object:nil
420 userInfo:nil];
421 NSNotification* applicationNotification =
422 [NSNotification notificationWithName:UIApplicationDidEnterBackgroundNotification
423 object:nil
424 userInfo:nil];
425 id mockEngine = OCMPartialMock(engine);
426 [NSNotificationCenter.defaultCenter postNotification:sceneNotification];
427 [NSNotificationCenter.defaultCenter postNotification:applicationNotification];
428 OCMVerify(times(1), [mockEngine sceneDidEnterBackground:[OCMArg any]]);
429 XCTAssertTrue(engine.isGpuDisabled);
430 BOOL gpuDisabled = NO;
431 [engine shell].GetIsGpuDisabledSyncSwitch()->Execute(
432 fml::SyncSwitch::Handlers().SetIfTrue([&] { gpuDisabled = YES; }).SetIfFalse([&] {
433 gpuDisabled = NO;
434 }));
435 XCTAssertTrue(gpuDisabled);
436 [mockBundle stopMocking];
437 }
438
439 - (void)testLifeCycleNotificationWillEnterForegroundForApplication {
440 FlutterDartProject* project = [[FlutterDartProject alloc] init];
441 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
442 [engine run];
443 NSNotification* sceneNotification =
444 [NSNotification notificationWithName:UISceneWillEnterForegroundNotification
445 object:nil
446 userInfo:nil];
447 NSNotification* applicationNotification =
448 [NSNotification notificationWithName:UIApplicationWillEnterForegroundNotification
449 object:nil
450 userInfo:nil];
451 id mockEngine = OCMPartialMock(engine);
452 [NSNotificationCenter.defaultCenter postNotification:sceneNotification];
453 [NSNotificationCenter.defaultCenter postNotification:applicationNotification];
454 OCMVerify(times(1), [mockEngine applicationWillEnterForeground:[OCMArg any]]);
455 XCTAssertFalse(engine.isGpuDisabled);
456 BOOL gpuDisabled = YES;
457 [engine shell].GetIsGpuDisabledSyncSwitch()->Execute(
458 fml::SyncSwitch::Handlers().SetIfTrue([&] { gpuDisabled = YES; }).SetIfFalse([&] {
459 gpuDisabled = NO;
460 }));
461 XCTAssertFalse(gpuDisabled);
462 }
463
464 - (void)testLifeCycleNotificationWillEnterForegroundForScene {
465 id mockBundle = OCMPartialMock([NSBundle mainBundle]);
466 OCMStub([mockBundle objectForInfoDictionaryKey:@"NSExtension"]).andReturn(@{
467 @"NSExtensionPointIdentifier" : @"com.apple.share-services"
468 });
469 FlutterDartProject* project = [[FlutterDartProject alloc] init];
470 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
471 [engine run];
472 NSNotification* sceneNotification =
473 [NSNotification notificationWithName:UISceneWillEnterForegroundNotification
474 object:nil
475 userInfo:nil];
476 NSNotification* applicationNotification =
477 [NSNotification notificationWithName:UIApplicationWillEnterForegroundNotification
478 object:nil
479 userInfo:nil];
480 id mockEngine = OCMPartialMock(engine);
481 [NSNotificationCenter.defaultCenter postNotification:sceneNotification];
482 [NSNotificationCenter.defaultCenter postNotification:applicationNotification];
483 OCMVerify(times(1), [mockEngine sceneWillEnterForeground:[OCMArg any]]);
484 XCTAssertFalse(engine.isGpuDisabled);
485 BOOL gpuDisabled = YES;
486 [engine shell].GetIsGpuDisabledSyncSwitch()->Execute(
487 fml::SyncSwitch::Handlers().SetIfTrue([&] { gpuDisabled = YES; }).SetIfFalse([&] {
488 gpuDisabled = NO;
489 }));
490 XCTAssertFalse(gpuDisabled);
491 [mockBundle stopMocking];
492 }
493
494 - (void)testSpawnsShareGpuContext {
495 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar"];
496 [engine run];
497 FlutterEngine* spawn = [engine spawnWithEntrypoint:nil
498 libraryURI:nil
499 initialRoute:nil
500 entrypointArgs:nil];
501 XCTAssertNotNil(spawn);
502 XCTAssertTrue(engine.platformView != nullptr);
503 XCTAssertTrue(spawn.platformView != nullptr);
504 std::shared_ptr<flutter::IOSContext> engine_context = engine.platformView->GetIosContext();
505 std::shared_ptr<flutter::IOSContext> spawn_context = spawn.platformView->GetIosContext();
506 XCTAssertEqual(engine_context, spawn_context);
507 }
508
509 - (void)testEnableSemanticsWhenFlutterViewAccessibilityDidCall {
510 FlutterEngineSpy* engine = [[FlutterEngineSpy alloc] initWithName:@"foobar"];
511 engine.ensureSemanticsEnabledCalled = NO;
512 [engine flutterViewAccessibilityDidCall];
513 XCTAssertTrue(engine.ensureSemanticsEnabledCalled);
514 }
515
516 - (void)testCanMergePlatformAndUIThread {
517 #if defined(TARGET_IPHONE_SIMULATOR) && TARGET_IPHONE_SIMULATOR
518 auto settings = FLTDefaultSettingsForBundle();
519 FlutterDartProject* project = [[FlutterDartProject alloc] initWithSettings:settings];
520 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
521 [engine run];
522
523 XCTAssertEqual(engine.shell.GetTaskRunners().GetUITaskRunner(),
524 engine.shell.GetTaskRunners().GetPlatformTaskRunner());
525 #endif // defined(TARGET_IPHONE_SIMULATOR) && TARGET_IPHONE_SIMULATOR
526 }
527
528 - (void)testCanUnMergePlatformAndUIThread {
529 #if defined(TARGET_IPHONE_SIMULATOR) && TARGET_IPHONE_SIMULATOR
530 auto settings = FLTDefaultSettingsForBundle();
531 settings.merged_platform_ui_thread = flutter::Settings::MergedPlatformUIThread::kDisabled;
532 FlutterDartProject* project = [[FlutterDartProject alloc] initWithSettings:settings];
533 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
534 [engine run];
535
536 XCTAssertNotEqual(engine.shell.GetTaskRunners().GetUITaskRunner(),
537 engine.shell.GetTaskRunners().GetPlatformTaskRunner());
538 #endif // defined(TARGET_IPHONE_SIMULATOR) && TARGET_IPHONE_SIMULATOR
539 }
540
541 @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:263
flutter::PlatformViewIOS::GetIosContext
const std::shared_ptr< IOSContext > & GetIosContext()
Definition: platform_view_ios.h:121
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:896
-[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 runWithEntrypoint:initialRoute:]
BOOL runWithEntrypoint:initialRoute:(nullable NSString *entrypoint,[initialRoute] nullable NSString *initialRoute)
-[FlutterEngine ensureSemanticsEnabled]
void ensureSemanticsEnabled()
Definition: FlutterEngine.mm:442
-[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