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