Flutter iOS Embedder
FlutterAppDelegateTest.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 
15 
17 
18 @interface FlutterAppDelegateTest : XCTestCase
19 @property(strong) FlutterAppDelegate* appDelegate;
21 @property(strong) id mockMainBundle;
22 @property(strong) id mockNavigationChannel;
23 @property(strong) FlutterEngine* engine;
24 
25 // Retain callback until the tests are done.
26 // https://github.com/flutter/flutter/issues/74267
27 @property(strong) id mockEngineFirstFrameCallback;
28 @end
29 
30 @implementation FlutterAppDelegateTest
31 
32 - (void)setUp {
33  [super setUp];
34 
35  id mockMainBundle = OCMClassMock([NSBundle class]);
36  OCMStub([mockMainBundle mainBundle]).andReturn(mockMainBundle);
37  self.mockMainBundle = mockMainBundle;
38 
40  self.appDelegate = appDelegate;
41 
43  self.viewController = viewController;
44 
45  FlutterMethodChannel* navigationChannel = OCMClassMock([FlutterMethodChannel class]);
46  self.mockNavigationChannel = navigationChannel;
47 
48  FlutterEngine* engine = OCMClassMock([FlutterEngine class]);
49  OCMStub([engine navigationChannel]).andReturn(navigationChannel);
50  OCMStub([viewController engine]).andReturn(engine);
51  self.engine = engine;
52 
53  id mockEngineFirstFrameCallback = [OCMArg invokeBlockWithArgs:@NO, nil];
54  self.mockEngineFirstFrameCallback = mockEngineFirstFrameCallback;
55  OCMStub([engine waitForFirstFrame:3.0 callback:mockEngineFirstFrameCallback]);
57  return viewController;
58  };
59 }
60 
61 - (void)tearDown {
62  // Explicitly stop mocking the NSBundle class property.
63  [self.mockMainBundle stopMocking];
64  [super tearDown];
65 }
66 
67 - (void)testLaunchUrl {
68  OCMStub([self.mockMainBundle objectForInfoDictionaryKey:@"FlutterDeepLinkingEnabled"])
69  .andReturn(@YES);
70 
71  OCMStub([self.mockNavigationChannel
72  invokeMethod:@"pushRouteInformation"
73  arguments:@{@"location" : @"http://myApp/custom/route?query=test"}])
74  .andReturn(@YES);
75 
76  BOOL result =
77  [self.appDelegate application:[UIApplication sharedApplication]
78  openURL:[NSURL URLWithString:@"http://myApp/custom/route?query=test"]
79  options:@{}];
80 
81  XCTAssertTrue(result);
82  OCMVerifyAll(self.mockNavigationChannel);
83 }
84 
85 - (void)testLaunchUrlWithDeepLinkingNotSet {
86  OCMStub([self.mockMainBundle objectForInfoDictionaryKey:@"FlutterDeepLinkingEnabled"])
87  .andReturn(nil);
88 
89  OCMStub([self.mockNavigationChannel
90  invokeMethod:@"pushRouteInformation"
91  arguments:@{@"location" : @"http://myApp/custom/route?query=test"}])
92  .andReturn(@YES);
93 
94  BOOL result =
95  [self.appDelegate application:[UIApplication sharedApplication]
96  openURL:[NSURL URLWithString:@"http://myApp/custom/route?query=test"]
97  options:@{}];
98 
99  XCTAssertTrue(result);
100  OCMVerifyAll(self.mockNavigationChannel);
101 }
102 
103 - (void)testLaunchUrlWithDeepLinkingDisabled {
104  OCMStub([self.mockMainBundle objectForInfoDictionaryKey:@"FlutterDeepLinkingEnabled"])
105  .andReturn(@NO);
106 
107  BOOL result =
108  [self.appDelegate application:[UIApplication sharedApplication]
109  openURL:[NSURL URLWithString:@"http://myApp/custom/route?query=test"]
110  options:@{}];
111  XCTAssertFalse(result);
112  OCMReject([self.mockNavigationChannel invokeMethod:OCMOCK_ANY arguments:OCMOCK_ANY]);
113 }
114 
115 - (void)testLaunchUrlWithQueryParameterAndFragment {
116  OCMStub([self.mockMainBundle objectForInfoDictionaryKey:@"FlutterDeepLinkingEnabled"])
117  .andReturn(@YES);
118  OCMStub([self.mockNavigationChannel
119  invokeMethod:@"pushRouteInformation"
120  arguments:@{@"location" : @"http://myApp/custom/route?query=test#fragment"}])
121  .andReturn(@YES);
122  BOOL result = [self.appDelegate
123  application:[UIApplication sharedApplication]
124  openURL:[NSURL URLWithString:@"http://myApp/custom/route?query=test#fragment"]
125  options:@{}];
126  XCTAssertTrue(result);
127  OCMVerifyAll(self.mockNavigationChannel);
128 }
129 
130 - (void)testLaunchUrlWithFragmentNoQueryParameter {
131  OCMStub([self.mockMainBundle objectForInfoDictionaryKey:@"FlutterDeepLinkingEnabled"])
132  .andReturn(@YES);
133  OCMStub([self.mockNavigationChannel
134  invokeMethod:@"pushRouteInformation"
135  arguments:@{@"location" : @"http://myApp/custom/route#fragment"}])
136  .andReturn(@YES);
137  BOOL result =
138  [self.appDelegate application:[UIApplication sharedApplication]
139  openURL:[NSURL URLWithString:@"http://myApp/custom/route#fragment"]
140  options:@{}];
141  XCTAssertTrue(result);
142  OCMVerifyAll(self.mockNavigationChannel);
143 }
144 
145 - (void)testReleasesWindowOnDealloc {
146  __weak UIWindow* weakWindow;
147  @autoreleasepool {
148  id mockWindow = OCMClassMock([UIWindow class]);
150  appDelegate.window = mockWindow;
151  weakWindow = mockWindow;
152  XCTAssertNotNil(weakWindow);
153  [mockWindow stopMocking];
154  mockWindow = nil;
155  appDelegate = nil;
156  }
157  // App delegate has released the window.
158  XCTAssertNil(weakWindow);
159 }
160 
161 - (void)testGrabLaunchEngine {
162  // Clear out the mocking of the root view controller.
163  [self.mockMainBundle stopMocking];
164  self.appDelegate.rootFlutterViewControllerGetter = nil;
165  // Working with plugins forces the creation of an engine.
166  XCTAssertFalse([self.appDelegate hasPlugin:@"hello"]);
167  XCTAssertNotNil([self.appDelegate takeLaunchEngine]);
168  XCTAssertNil([self.appDelegate takeLaunchEngine]);
169 }
170 
171 - (void)testGrabLaunchEngineWithoutPlugins {
172  XCTAssertNil([self.appDelegate takeLaunchEngine]);
173 }
174 
175 #pragma mark - Deep linking
176 
177 - (void)testUniversalLinkPushRouteInformation {
178  OCMStub([self.mockMainBundle objectForInfoDictionaryKey:@"FlutterDeepLinkingEnabled"])
179  .andReturn(@YES);
180  OCMStub([self.mockNavigationChannel
181  invokeMethod:@"pushRouteInformation"
182  arguments:@{@"location" : @"http://myApp/custom/route?query=test"}])
183  .andReturn(@YES);
184  NSUserActivity* userActivity = [[NSUserActivity alloc] initWithActivityType:@"com.example.test"];
185  userActivity.webpageURL = [NSURL URLWithString:@"http://myApp/custom/route?query=test"];
186  BOOL result = [self.appDelegate
187  application:[UIApplication sharedApplication]
188  continueUserActivity:userActivity
189  restorationHandler:^(NSArray<id<UIUserActivityRestoring>>* __nullable restorableObjects){
190  }];
191  XCTAssertTrue(result);
192  OCMVerifyAll(self.mockNavigationChannel);
193 }
194 
195 - (void)testUseNonDeprecatedOpenURLAPI {
196  OCMStub([self.mockMainBundle objectForInfoDictionaryKey:@"FlutterDeepLinkingEnabled"])
197  .andReturn(@YES);
198  NSUserActivity* userActivity = [[NSUserActivity alloc] initWithActivityType:@"com.example.test"];
199  userActivity.webpageURL = [NSURL URLWithString:@"http://myApp/custom/route?query=nonexist"];
200  OCMStub([self.engine sendDeepLinkToFramework:[OCMArg any] completionHandler:[OCMArg any]])
201  .andDo(^(NSInvocation* invocation) {
202  void (^handler)(BOOL success);
203  [invocation getArgument:&handler atIndex:3];
204  handler(NO);
205  });
206  id mockApplication = OCMClassMock([UIApplication class]);
207  OCMStub([mockApplication sharedApplication]).andReturn(mockApplication);
208  BOOL result = [self.appDelegate
209  application:[UIApplication sharedApplication]
210  continueUserActivity:userActivity
211  restorationHandler:^(NSArray<id<UIUserActivityRestoring>>* __nullable restorableObjects){
212  }];
213  XCTAssertTrue(result);
214  OCMVerify([mockApplication openURL:[OCMArg any]
215  options:[OCMArg any]
216  completionHandler:[OCMArg any]]);
217 }
218 
219 - (void)testSetGetPluginRegistrant {
220  id mockRegistrant = OCMProtocolMock(@protocol(FlutterPluginRegistrant));
221  self.appDelegate.pluginRegistrant = mockRegistrant;
222  XCTAssertEqual(self.appDelegate.pluginRegistrant, mockRegistrant);
223 }
224 
225 - (void)testSetGetPluginRegistrantSelf {
226  __weak FlutterAppDelegate* appDelegate = self.appDelegate;
227  @autoreleasepool {
229  self.appDelegate = nil;
230  }
231  // A retain cycle would keep this alive.
232  XCTAssertNil(appDelegate);
233 }
234 
235 @end
FlutterAppLifeCycleProvider UIWindow * window
NSObject< FlutterPluginRegistrant > * pluginRegistrant
FlutterViewController *(^ rootFlutterViewControllerGetter)(void)
FlutterAppDelegate * appDelegate
FlutterViewController * viewController