Flutter iOS Embedder
FlutterPluginAppLifeCycleDelegateTest.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 <OCMock/OCMock.h>
6 #import <XCTest/XCTest.h>
7 
13 
15 
16 // --- Test Category to avoid modifying the original production source code ---
18 - (void)removeDelegate:(NSObject<FlutterApplicationLifeCycleDelegate>*)delegate;
19 @end
20 
22 - (void)removeDelegate:(NSObject<FlutterApplicationLifeCycleDelegate>*)delegate {
23  // Access the private _delegates member via Key-Value Coding (KVC)
24  NSPointerArray* delegates = [self valueForKey:@"_delegates"];
25  for (NSUInteger i = 0; i < delegates.count; i++) {
26  if ([delegates pointerAtIndex:i] == (__bridge void*)delegate) {
27  [delegates removePointerAtIndex:i];
28  break;
29  }
30  }
31 }
32 @end
33 // -----------------------------------------------------------------------
34 
35 @protocol TestFlutterPluginWithSceneEvents <NSObject,
38 @end
39 
41 @end
42 
44 - (BOOL)application:(UIApplication*)application
45  didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
46  return NO;
47 }
48 
49 - (BOOL)application:(UIApplication*)application
50  openURL:(NSURL*)url
51  options:(NSDictionary<UIApplicationOpenURLOptionsKey, id>*)options {
52  return YES;
53 }
54 
55 - (BOOL)application:(UIApplication*)application
56  continueUserActivity:(NSUserActivity*)userActivity
57  restorationHandler:(void (^)(NSArray*))restorationHandler {
58  return YES;
59 }
60 
61 - (BOOL)application:(UIApplication*)application
62  performActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem
63  completionHandler:(void (^)(BOOL succeeded))completionHandler
64  API_AVAILABLE(ios(9.0)) {
65  return YES;
66 }
67 @end
68 
70 @end
71 
72 @implementation FakePlugin
73 - (BOOL)application:(UIApplication*)application
74  openURL:(NSURL*)url
75  options:(NSDictionary<UIApplicationOpenURLOptionsKey, id>*)options {
76  return YES;
77 }
78 
79 - (BOOL)application:(UIApplication*)application
80  continueUserActivity:(NSUserActivity*)userActivity
81  restorationHandler:(void (^)(NSArray*))restorationHandler {
82  return YES;
83 }
84 
85 - (BOOL)application:(UIApplication*)application
86  performActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem
87  completionHandler:(void (^)(BOOL succeeded))completionHandler
88  API_AVAILABLE(ios(9.0)) {
89  return YES;
90 }
91 
92 - (BOOL)application:(UIApplication*)application
93  didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
94  return YES;
95 }
96 
97 - (BOOL)application:(UIApplication*)application
98  willFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
99  return YES;
100 }
101 @end
102 
103 /**
104  * A mock plugin that simulates behavior causing a mutation crash.
105  * This represents a "downstream" mutation where a plugin adds or removes
106  * delegates during a lifecycle notification loop.
107  */
109 @property(nonatomic, weak) FlutterPluginAppLifeCycleDelegate* lifecycleDelegate;
110 @property(nonatomic, assign) BOOL shouldAdd; // YES = Add, NO = Remove
111 @end
112 
113 @implementation MutatingPlugin
114 - (BOOL)application:(UIApplication*)application
115  didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
116  if (self.shouldAdd) {
117  // Case 1: Add a new delegate during the loop over _delegates
118  [self.lifecycleDelegate addDelegate:[[FakePlugin alloc] init]];
119  } else {
120  // Case 2: Remove itself during the loop over _delegates via TestUtils category
121  [(id)self.lifecycleDelegate removeDelegate:self];
122  }
123  return YES;
124 }
125 @end
126 
128 @end
129 
131 
132 - (void)testCreate {
134  XCTAssertNotNil(delegate);
135 }
136 
137 - (void)testSceneWillConnectFallback {
139  id plugin = [[FakePlugin alloc] init];
140  id mockPlugin = OCMPartialMock(plugin);
141  [delegate addDelegate:mockPlugin];
142 
143  id mockOptions = OCMClassMock([UISceneConnectionOptions class]);
144  id mockShortcutItem = OCMClassMock([UIApplicationShortcutItem class]);
145  OCMStub([mockOptions shortcutItem]).andReturn(mockShortcutItem);
146  OCMStub([mockOptions sourceApplication]).andReturn(@"bundle_id");
147  id urlContext = OCMClassMock([UIOpenURLContext class]);
148  NSURL* url = [NSURL URLWithString:@"http://example.com"];
149  OCMStub([urlContext URL]).andReturn(url);
150  NSSet<UIOpenURLContext*>* urlContexts = [NSSet setWithObjects:urlContext, nil];
151  OCMStub([mockOptions URLContexts]).andReturn(urlContexts);
152 
153  NSDictionary<UIApplicationOpenURLOptionsKey, id>* expectedApplicationOptions = @{
154  UIApplicationLaunchOptionsShortcutItemKey : mockShortcutItem,
155  UIApplicationLaunchOptionsSourceApplicationKey : @"bundle_id",
156  UIApplicationLaunchOptionsURLKey : url,
157  };
158 
159  [delegate sceneWillConnectFallback:mockOptions];
160  OCMVerify([mockPlugin application:[UIApplication sharedApplication]
161  didFinishLaunchingWithOptions:expectedApplicationOptions]);
162 }
163 
164 - (void)testSceneWillConnectFallbackSkippedSupportsScenes {
166  id plugin = [[FakeTestFlutterPluginWithSceneEvents alloc] init];
167  id mockPlugin = OCMPartialMock(plugin);
168  [delegate addDelegate:mockPlugin];
169 
170  id mockOptions = OCMClassMock([UISceneConnectionOptions class]);
171  id mockShortcutItem = OCMClassMock([UIApplicationShortcutItem class]);
172  OCMStub([mockOptions shortcutItem]).andReturn(mockShortcutItem);
173  OCMStub([mockOptions sourceApplication]).andReturn(@"bundle_id");
174  id urlContext = OCMClassMock([UIOpenURLContext class]);
175  NSURL* url = [NSURL URLWithString:@"http://example.com"];
176  OCMStub([urlContext URL]).andReturn(url);
177  NSSet<UIOpenURLContext*>* urlContexts = [NSSet setWithObjects:urlContext, nil];
178  OCMStub([mockOptions URLContexts]).andReturn(urlContexts);
179 
180  [delegate sceneWillConnectFallback:mockOptions];
181  OCMReject([mockPlugin application:[OCMArg any] didFinishLaunchingWithOptions:[OCMArg any]]);
182 }
183 
184 - (void)testSceneWillConnectFallbackSkippedNoOptions {
186  id plugin = [[FakePlugin alloc] init];
187  id mockPlugin = OCMPartialMock(plugin);
188  [delegate addDelegate:mockPlugin];
189 
190  id mockOptions = OCMClassMock([UISceneConnectionOptions class]);
191 
192  [delegate sceneWillConnectFallback:mockOptions];
193  OCMReject([mockPlugin application:[OCMArg any] didFinishLaunchingWithOptions:[OCMArg any]]);
194 }
195 
196 - (void)testDidEnterBackground {
197  XCTNSNotificationExpectation* expectation = [[XCTNSNotificationExpectation alloc]
198  initWithName:UIApplicationDidEnterBackgroundNotification];
200  id plugin = OCMProtocolMock(@protocol(FlutterPlugin));
201  [delegate addDelegate:plugin];
202  [[NSNotificationCenter defaultCenter]
203  postNotificationName:UIApplicationDidEnterBackgroundNotification
204  object:nil];
205 
206  [self waitForExpectations:@[ expectation ] timeout:5.0];
207  OCMVerify([plugin applicationDidEnterBackground:[UIApplication sharedApplication]]);
208 }
209 
210 - (void)testDidEnterBackgroundWithUIScene {
211  XCTNSNotificationExpectation* expectation = [[XCTNSNotificationExpectation alloc]
212  initWithName:UIApplicationDidEnterBackgroundNotification];
214  id mockApplication = OCMClassMock([FlutterSharedApplication class]);
215  OCMStub([mockApplication hasSceneDelegate]).andReturn(YES);
216  id plugin = OCMProtocolMock(@protocol(FlutterPlugin));
217  [delegate addDelegate:plugin];
218  [[NSNotificationCenter defaultCenter]
219  postNotificationName:UIApplicationDidEnterBackgroundNotification
220  object:nil];
221 
222  [self waitForExpectations:@[ expectation ] timeout:5.0];
223  OCMReject([plugin applicationDidEnterBackground:[OCMArg any]]);
224 }
225 
226 - (void)testSceneDidEnterBackgroundFallback {
228  id plugin = OCMProtocolMock(@protocol(FlutterPlugin));
229  [delegate addDelegate:plugin];
230 
231  [delegate sceneDidEnterBackgroundFallback];
232  OCMVerify([plugin applicationDidEnterBackground:[UIApplication sharedApplication]]);
233 }
234 
235 - (void)testUnnecessarySceneDidEnterBackgroundFallback {
237  id plugin = OCMProtocolMock(@protocol(TestFlutterPluginWithSceneEvents));
238  [delegate addDelegate:plugin];
239 
240  [delegate sceneDidEnterBackgroundFallback];
241  OCMReject([plugin applicationDidEnterBackground:[OCMArg any]]);
242 }
243 
244 - (void)testWillEnterForeground {
245  XCTNSNotificationExpectation* expectation = [[XCTNSNotificationExpectation alloc]
246  initWithName:UIApplicationWillEnterForegroundNotification];
247 
249  id plugin = OCMProtocolMock(@protocol(FlutterPlugin));
250  [delegate addDelegate:plugin];
251  [[NSNotificationCenter defaultCenter]
252  postNotificationName:UIApplicationWillEnterForegroundNotification
253  object:nil];
254  [self waitForExpectations:@[ expectation ] timeout:5.0];
255  OCMVerify([plugin applicationWillEnterForeground:[UIApplication sharedApplication]]);
256 }
257 
258 - (void)testWillEnterForegroundWithUIScene {
259  XCTNSNotificationExpectation* expectation = [[XCTNSNotificationExpectation alloc]
260  initWithName:UIApplicationWillEnterForegroundNotification];
262  id mockApplication = OCMClassMock([FlutterSharedApplication class]);
263  OCMStub([mockApplication hasSceneDelegate]).andReturn(YES);
264  id plugin = OCMProtocolMock(@protocol(FlutterPlugin));
265  [delegate addDelegate:plugin];
266  [[NSNotificationCenter defaultCenter]
267  postNotificationName:UIApplicationWillEnterForegroundNotification
268  object:nil];
269 
270  [self waitForExpectations:@[ expectation ] timeout:5.0];
271  OCMReject([plugin applicationWillEnterForeground:[OCMArg any]]);
272 }
273 
274 - (void)testSceneWillEnterForegroundFallback {
276  id plugin = OCMProtocolMock(@protocol(FlutterPlugin));
277  [delegate addDelegate:plugin];
278 
279  [delegate sceneWillEnterForegroundFallback];
280  OCMVerify([plugin applicationWillEnterForeground:[UIApplication sharedApplication]]);
281 }
282 
283 - (void)testUnnecessarySceneWillEnterForegroundFallback {
285  id plugin = OCMProtocolMock(@protocol(TestFlutterPluginWithSceneEvents));
286  [delegate addDelegate:plugin];
287 
288  [delegate sceneWillEnterForegroundFallback];
289  OCMReject([plugin applicationWillEnterForeground:[OCMArg any]]);
290 }
291 
292 - (void)testWillResignActive {
293  XCTNSNotificationExpectation* expectation =
294  [[XCTNSNotificationExpectation alloc] initWithName:UIApplicationWillResignActiveNotification];
295 
297  id plugin = OCMProtocolMock(@protocol(FlutterPlugin));
298  [delegate addDelegate:plugin];
299  [[NSNotificationCenter defaultCenter]
300  postNotificationName:UIApplicationWillResignActiveNotification
301  object:nil];
302  [self waitForExpectations:@[ expectation ] timeout:5.0];
303  OCMVerify([plugin applicationWillResignActive:[UIApplication sharedApplication]]);
304 }
305 
306 - (void)testWillResignActiveWithUIScene {
307  XCTNSNotificationExpectation* expectation =
308  [[XCTNSNotificationExpectation alloc] initWithName:UIApplicationWillResignActiveNotification];
310  id mockApplication = OCMClassMock([FlutterSharedApplication class]);
311  OCMStub([mockApplication hasSceneDelegate]).andReturn(YES);
312  id plugin = OCMProtocolMock(@protocol(FlutterPlugin));
313  [delegate addDelegate:plugin];
314  [[NSNotificationCenter defaultCenter]
315  postNotificationName:UIApplicationWillResignActiveNotification
316  object:nil];
317 
318  [self waitForExpectations:@[ expectation ] timeout:5.0];
319  OCMReject([plugin applicationWillResignActive:[OCMArg any]]);
320 }
321 
322 - (void)testSceneWillResignActiveFallback {
324  id plugin = OCMProtocolMock(@protocol(FlutterPlugin));
325  [delegate addDelegate:plugin];
326 
327  [delegate sceneWillResignActiveFallback];
328  OCMVerify([plugin applicationWillResignActive:[UIApplication sharedApplication]]);
329 }
330 
331 - (void)testUnnecessarySceneWillResignActiveFallback {
333  id plugin = OCMProtocolMock(@protocol(TestFlutterPluginWithSceneEvents));
334  [delegate addDelegate:plugin];
335 
336  [delegate sceneWillResignActiveFallback];
337  OCMReject([plugin applicationWillResignActive:[OCMArg any]]);
338 }
339 
340 - (void)testDidBecomeActive {
341  XCTNSNotificationExpectation* expectation =
342  [[XCTNSNotificationExpectation alloc] initWithName:UIApplicationDidBecomeActiveNotification];
343 
345  id plugin = OCMProtocolMock(@protocol(FlutterPlugin));
346  [delegate addDelegate:plugin];
347  [[NSNotificationCenter defaultCenter]
348  postNotificationName:UIApplicationDidBecomeActiveNotification
349  object:nil];
350  [self waitForExpectations:@[ expectation ] timeout:5.0];
351  OCMVerify([plugin applicationDidBecomeActive:[UIApplication sharedApplication]]);
352 }
353 
354 - (void)testDidBecomeActiveWithUIScene {
355  XCTNSNotificationExpectation* expectation =
356  [[XCTNSNotificationExpectation alloc] initWithName:UIApplicationDidBecomeActiveNotification];
358  id mockApplication = OCMClassMock([FlutterSharedApplication class]);
359  OCMStub([mockApplication hasSceneDelegate]).andReturn(YES);
360  id plugin = OCMProtocolMock(@protocol(FlutterPlugin));
361  [delegate addDelegate:plugin];
362  [[NSNotificationCenter defaultCenter]
363  postNotificationName:UIApplicationDidBecomeActiveNotification
364  object:nil];
365 
366  [self waitForExpectations:@[ expectation ] timeout:5.0];
367  OCMReject([plugin applicationDidBecomeActive:[OCMArg any]]);
368 }
369 
370 - (void)testSceneDidBecomeActiveFallback {
372  id plugin = OCMProtocolMock(@protocol(FlutterPlugin));
373  [delegate addDelegate:plugin];
374 
375  [delegate sceneDidBecomeActiveFallback];
376  OCMVerify([plugin applicationDidBecomeActive:[UIApplication sharedApplication]]);
377 }
378 
379 - (void)testUnnecessarySceneDidBecomeActiveFallback {
381  id plugin = OCMProtocolMock(@protocol(TestFlutterPluginWithSceneEvents));
382  [delegate addDelegate:plugin];
383 
384  [delegate sceneDidBecomeActiveFallback];
385  OCMReject([plugin applicationDidBecomeActive:[OCMArg any]]);
386 }
387 
388 - (void)testSceneFallbackOpenURLContexts {
390  id plugin = [[FakePlugin alloc] init];
391  id mockPlugin = OCMPartialMock(plugin);
392  [delegate addDelegate:mockPlugin];
393 
394  id urlContext = OCMClassMock([UIOpenURLContext class]);
395  NSURL* url = [NSURL URLWithString:@"http://example.com"];
396  OCMStub([urlContext URL]).andReturn(url);
397  NSSet<UIOpenURLContext*>* urlContexts = [NSSet setWithObjects:urlContext, nil];
398 
399  NSDictionary<UIApplicationOpenURLOptionsKey, id>* expectedApplicationOptions = @{
400  UIApplicationOpenURLOptionsOpenInPlaceKey : @(NO),
401  };
402 
403  [delegate sceneFallbackOpenURLContexts:urlContexts];
404  OCMVerify([mockPlugin application:[UIApplication sharedApplication]
405  openURL:url
406  options:expectedApplicationOptions]);
407 }
408 
409 - (void)testConvertURLOptions {
411  id plugin = [[FakePlugin alloc] init];
412  id mockPlugin = OCMPartialMock(plugin);
413  [delegate addDelegate:mockPlugin];
414 
415  NSString* bundleId = @"app.bundle.id";
416  id annotation = @{@"key" : @"value"};
417  id eventAttribution = OCMClassMock([UIEventAttribution class]);
418 
419  UIOpenURLContext* urlContext = OCMClassMock([UIOpenURLContext class]);
420  NSURL* url = [NSURL URLWithString:@"http://example.com"];
421  OCMStub([urlContext URL]).andReturn(url);
422  id sceneOptions = OCMClassMock([UISceneOpenURLOptions class]);
423  OCMStub([sceneOptions sourceApplication]).andReturn(bundleId);
424  OCMStub([sceneOptions annotation]).andReturn(annotation);
425  OCMStub([sceneOptions openInPlace]).andReturn(YES);
426  OCMStub([sceneOptions eventAttribution]).andReturn(eventAttribution);
427 
428  OCMStub([urlContext options]).andReturn(sceneOptions);
429  NSSet<UIOpenURLContext*>* urlContexts = [NSSet setWithObjects:urlContext, nil];
430 
431  [delegate sceneFallbackOpenURLContexts:urlContexts];
432 
433  NSDictionary<UIApplicationOpenURLOptionsKey, id>* expectedApplicationOptions = @{
434  UIApplicationOpenURLOptionsSourceApplicationKey : bundleId,
435  UIApplicationOpenURLOptionsAnnotationKey : annotation,
436  UIApplicationOpenURLOptionsOpenInPlaceKey : @(YES),
437  UIApplicationOpenURLOptionsEventAttributionKey : eventAttribution,
438  };
439 
440  OCMVerify([mockPlugin application:[UIApplication sharedApplication]
441  openURL:url
442  options:expectedApplicationOptions]);
443 }
444 
445 - (void)testUnnecessarySceneFallbackOpenURLContexts {
447  id plugin = [[FakeTestFlutterPluginWithSceneEvents alloc] init];
448  id mockPlugin = OCMPartialMock(plugin);
449  [delegate addDelegate:mockPlugin];
450 
451  id urlContext = OCMClassMock([UIOpenURLContext class]);
452  NSSet<UIOpenURLContext*>* urlContexts = [NSSet setWithObjects:urlContext, nil];
453 
454  [delegate sceneFallbackOpenURLContexts:urlContexts];
455  OCMReject([mockPlugin application:[OCMArg any] openURL:[OCMArg any] options:[OCMArg any]]);
456 }
457 
458 - (void)testSceneFallbackContinueUserActivity {
460  id plugin = [[FakePlugin alloc] init];
461  id mockPlugin = OCMPartialMock(plugin);
462  [delegate addDelegate:mockPlugin];
463 
464  id userActivity = OCMClassMock([NSUserActivity class]);
465 
466  [delegate sceneFallbackContinueUserActivity:userActivity];
467  OCMVerify([mockPlugin application:[UIApplication sharedApplication]
468  continueUserActivity:userActivity
469  restorationHandler:[OCMArg any]]);
470 }
471 
472 - (void)testUnnecessarySceneFallbackContinueUserActivity {
474  id plugin = [[FakeTestFlutterPluginWithSceneEvents alloc] init];
475  id mockPlugin = OCMPartialMock(plugin);
476  [delegate addDelegate:mockPlugin];
477 
478  id userActivity = OCMClassMock([NSUserActivity class]);
479 
480  [delegate sceneFallbackContinueUserActivity:userActivity];
481  OCMReject([mockPlugin application:[UIApplication sharedApplication]
482  continueUserActivity:userActivity
483  restorationHandler:[OCMArg any]]);
484 }
485 
486 - (void)testSceneFallbackPerformActionForShortcutItem {
488  FakePlugin* plugin = [[FakePlugin alloc] init];
489  FakePlugin* mockPlugin = OCMPartialMock(plugin);
490  [delegate addDelegate:mockPlugin];
491 
492  id shortcut = OCMClassMock([UIApplicationShortcutItem class]);
493  id handler = ^(BOOL succeeded) {
494  };
495 
496  [delegate sceneFallbackPerformActionForShortcutItem:shortcut completionHandler:handler];
497  OCMVerify([mockPlugin application:[UIApplication sharedApplication]
498  performActionForShortcutItem:shortcut
499  completionHandler:handler]);
500 }
501 
502 - (void)testUnnecessarySceneFallbackPerformActionForShortcutItem {
506  FakeTestFlutterPluginWithSceneEvents* mockPlugin = OCMPartialMock(plugin);
507  [delegate addDelegate:mockPlugin];
508 
509  id shortcut = OCMClassMock([UIApplicationShortcutItem class]);
510  [delegate sceneFallbackPerformActionForShortcutItem:shortcut
511  completionHandler:^(BOOL succeeded){
512  }];
513  OCMReject([mockPlugin application:[OCMArg any]
514  performActionForShortcutItem:[OCMArg any]
515  completionHandler:[OCMArg any]]);
516 }
517 
518 - (void)testWillTerminate {
519  XCTNSNotificationExpectation* expectation =
520  [[XCTNSNotificationExpectation alloc] initWithName:UIApplicationWillTerminateNotification];
521 
523  id plugin = OCMProtocolMock(@protocol(FlutterPlugin));
524  [delegate addDelegate:plugin];
525  [[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationWillTerminateNotification
526  object:nil];
527  [self waitForExpectations:@[ expectation ] timeout:5.0];
528  OCMVerify([plugin applicationWillTerminate:[UIApplication sharedApplication]]);
529 }
530 
531 - (void)testReleasesPluginOnDealloc {
532  __weak id<FlutterApplicationLifeCycleDelegate> weakPlugin;
533  __weak FlutterPluginAppLifeCycleDelegate* weakDelegate;
534  @autoreleasepool {
535  FakePlugin* fakePlugin = [[FakePlugin alloc] init];
536  weakPlugin = fakePlugin;
538  [delegate addDelegate:fakePlugin];
539  weakDelegate = delegate;
540  }
541  XCTAssertNil(weakPlugin);
542  XCTAssertNil(weakDelegate);
543 }
544 
545 - (void)testApplicationWillFinishLaunchingSceneFallbackForwards {
547  id plugin = [[FakePlugin alloc] init];
548  id mockPlugin = OCMPartialMock(plugin);
549  [delegate addDelegate:mockPlugin];
550  id mockApplication = OCMClassMock([UIApplication class]);
551  NSDictionary* options = @{};
552 
553  [delegate sceneFallbackWillFinishLaunchingApplication:mockApplication];
554  OCMVerify(times(1), [mockPlugin application:mockApplication
555  willFinishLaunchingWithOptions:options]);
556 }
557 
558 - (void)testApplicationWillFinishLaunchingSceneFallbackNoForwardAfterWillLaunch {
560  id plugin = [[FakePlugin alloc] init];
561  id mockPlugin = OCMPartialMock(plugin);
562  [delegate addDelegate:mockPlugin];
563  id mockApplication = OCMClassMock([UIApplication class]);
564  NSDictionary* options = @{@"key" : @"value"};
565 
566  [delegate application:mockApplication willFinishLaunchingWithOptions:options];
567  [delegate sceneFallbackWillFinishLaunchingApplication:mockApplication];
568  OCMVerify(times(1), [mockPlugin application:mockApplication
569  willFinishLaunchingWithOptions:options]);
570 }
571 
572 - (void)testApplicationWillFinishLaunchingSceneFallbackNoForwardAfterDidLaunch {
574  id plugin = [[FakePlugin alloc] init];
575  id mockPlugin = OCMPartialMock(plugin);
576  [delegate addDelegate:mockPlugin];
577  id mockApplication = OCMClassMock([UIApplication class]);
578  NSDictionary* options = @{@"key" : @"value"};
579 
580  [delegate application:mockApplication didFinishLaunchingWithOptions:options];
581  [delegate sceneFallbackWillFinishLaunchingApplication:mockApplication];
582  OCMVerify(times(0), [mockPlugin application:mockApplication
583  willFinishLaunchingWithOptions:options]);
584 }
585 
586 - (void)testApplicationDidFinishLaunchingSceneFallbackForwards {
588  id plugin = [[FakePlugin alloc] init];
589  id mockPlugin = OCMPartialMock(plugin);
590  [delegate addDelegate:mockPlugin];
591  id mockApplication = OCMClassMock([UIApplication class]);
592  NSDictionary* options = @{};
593 
594  [delegate sceneFallbackDidFinishLaunchingApplication:mockApplication];
595  OCMVerify(times(1), [mockPlugin application:mockApplication
596  didFinishLaunchingWithOptions:options]);
597 }
598 
599 - (void)testApplicationDidFinishLaunchingSceneFallbackNoForward {
601  id plugin = [[FakePlugin alloc] init];
602  id mockPlugin = OCMPartialMock(plugin);
603  [delegate addDelegate:mockPlugin];
604  id mockApplication = OCMClassMock([UIApplication class]);
605  NSDictionary* options = @{@"key" : @"value"};
606 
607  [delegate application:mockApplication didFinishLaunchingWithOptions:options];
608  [delegate sceneFallbackDidFinishLaunchingApplication:mockApplication];
609  OCMVerify(times(1), [mockPlugin application:mockApplication
610  didFinishLaunchingWithOptions:options]);
611 }
612 
613 - (void)testCanAddDelegateDuringEnumeration {
615  MutatingPlugin* mutatingPlugin = [[MutatingPlugin alloc] init];
616  mutatingPlugin.lifecycleDelegate = delegate;
617  mutatingPlugin.shouldAdd = YES; // Add Mode
618 
619  [delegate addDelegate:mutatingPlugin];
620 
621  // Validation that [_delegates allObjects] (snapshotting) prevents NSGenericException crash
622  // when a plugin adds another plugin during the dispatch loop.
623  BOOL result = [delegate application:[UIApplication sharedApplication]
625 
626  XCTAssertTrue(result);
627 }
628 
629 - (void)testCanRemoveSelfDuringEnumeration {
631  MutatingPlugin* mutatingPlugin = [[MutatingPlugin alloc] init];
632  mutatingPlugin.lifecycleDelegate = delegate;
633  mutatingPlugin.shouldAdd = NO; // Delete Mode
634 
635  [delegate addDelegate:mutatingPlugin];
636 
637  // Validation that [_delegates allObjects] (snapshotting) prevents crash
638  // when a plugin removes itself via removeDelegate during the dispatch loop.
639  BOOL result = [delegate application:[UIApplication sharedApplication]
641 
642  XCTAssertTrue(result);
643 }
644 
645 @end
UITextSmartQuotesType smartQuotesType API_AVAILABLE(ios(11.0))
BOOL application:didFinishLaunchingWithOptions:(UIApplication *application,[didFinishLaunchingWithOptions] NSDictionary *launchOptions)
BOOL application:willFinishLaunchingWithOptions:(UIApplication *application,[willFinishLaunchingWithOptions] NSDictionary *launchOptions)
void addDelegate:(NSObject< FlutterApplicationLifeCycleDelegate > *delegate)
FlutterPluginAppLifeCycleDelegate * lifecycleDelegate