Flutter Engine Uber Docs
Docs for the entire Flutter Engine repo.
 
Loading...
Searching...
No Matches
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 ---
17@interface FlutterPluginAppLifeCycleDelegate (TestUtils)
18- (void)removeDelegate:(NSObject<FlutterApplicationLifeCycleDelegate>*)delegate;
19@end
20
21@implementation FlutterPluginAppLifeCycleDelegate (TestUtils)
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,
36 FlutterApplicationLifeCycleDelegate,
37 FlutterSceneLifeCycleDelegate>
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
const gchar FlBinaryMessengerMessageHandler handler
BOOL application:didFinishLaunchingWithOptions:(UIApplication *application,[didFinishLaunchingWithOptions] NSDictionary *launchOptions)
BOOL application:willFinishLaunchingWithOptions:(UIApplication *application,[willFinishLaunchingWithOptions] NSDictionary *launchOptions)
void addDelegate:(NSObject< FlutterApplicationLifeCycleDelegate > *delegate)
FlutterPluginAppLifeCycleDelegate * lifecycleDelegate
UITextSmartQuotesType smartQuotesType API_AVAILABLE(ios(11.0))
int BOOL