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"
22 
24 @property(nonatomic) BOOL ensureSemanticsEnabledCalled;
25 @end
26 
27 @implementation FlutterEngineSpy
28 
29 - (void)ensureSemanticsEnabled {
30  _ensureSemanticsEnabledCalled = YES;
31 }
32 
33 @end
34 
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
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
NS_ASSUME_NONNULL_BEGIN typedef void(^ FlutterBinaryReply)(NSData *_Nullable reply)
flutter::Settings FLTDefaultSettingsForBundle(NSBundle *bundle, NSProcessInfo *processInfoOrNil)
flutter::Shell & shell()
const std::shared_ptr< IOSContext > & GetIosContext()
flutter::PlatformViewIOS * platformView()
FlutterEngine * spawnWithEntrypoint:libraryURI:initialRoute:entrypointArgs:(/*nullable */NSString *entrypoint,[libraryURI]/*nullable */NSString *libraryURI,[initialRoute]/*nullable */NSString *initialRoute,[entrypointArgs]/*nullable */NSArray< NSString * > *entrypointArgs)
flutter::Shell & shell()
void setBinaryMessenger:(FlutterBinaryMessengerRelay *binaryMessenger)
flutter::IOSRenderingAPI platformViewsRenderingAPI()
FlutterViewController * viewController
BOOL runWithEntrypoint:initialRoute:(nullable NSString *entrypoint,[initialRoute] nullable NSString *initialRoute)
void ensureSemanticsEnabled()
void waitForFirstFrame:callback:(NSTimeInterval timeout,[callback] void(^ callback)(BOOL didTimeout))
instancetype methodCallWithMethodName:arguments:(NSString *method,[arguments] id _Nullable arguments)
nullable NSObject< FlutterPluginRegistrar > * registrarForPlugin:(NSString *pluginKey)