Flutter iOS Embedder
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros
FlutterPlatformPluginTest.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 FlutterPlatformPluginTest : XCTestCase
18 @end
19 
20 @interface FlutterPlatformPlugin ()
21 - (BOOL)isLiveTextInputAvailable;
22 - (void)searchWeb:(NSString*)searchTerm;
23 - (void)showLookUpViewController:(NSString*)term;
24 - (void)showShareViewController:(NSString*)content;
25 @end
26 
27 @interface UIViewController ()
28 - (void)presentViewController:(UIViewController*)viewControllerToPresent
29  animated:(BOOL)flag
30  completion:(void (^)(void))completion;
31 @end
32 
33 @implementation FlutterPlatformPluginTest
34 - (void)testSearchWebInvokedWithEscapedTerm {
35  id mockApplication = OCMClassMock([UIApplication class]);
36  OCMStub([mockApplication sharedApplication]).andReturn(mockApplication);
37 
38  FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"test" project:nil];
39  [engine runWithEntrypoint:nil];
40 
41  XCTestExpectation* invokeExpectation =
42  [self expectationWithDescription:@"Web search launched with escaped search term"];
43 
44  FlutterPlatformPlugin* plugin = [[FlutterPlatformPlugin alloc] initWithEngine:engine];
45  FlutterPlatformPlugin* mockPlugin = OCMPartialMock(plugin);
46 
47  FlutterMethodCall* methodCall = [FlutterMethodCall methodCallWithMethodName:@"SearchWeb.invoke"
48  arguments:@"Testing Word!"];
49 
50  FlutterResult result = ^(id result) {
51  OCMVerify([mockPlugin searchWeb:@"Testing Word!"]);
52 #if not APPLICATION_EXTENSION_API_ONLY
53  OCMVerify([mockApplication openURL:[NSURL URLWithString:@"x-web-search://?Testing%20Word!"]
54  options:@{}
55  completionHandler:nil]);
56 #endif
57  [invokeExpectation fulfill];
58  };
59 
60  [mockPlugin handleMethodCall:methodCall result:result];
61  [self waitForExpectationsWithTimeout:1 handler:nil];
62  [mockApplication stopMocking];
63 }
64 
65 - (void)testSearchWebInvokedWithNonEscapedTerm {
66  id mockApplication = OCMClassMock([UIApplication class]);
67  OCMStub([mockApplication sharedApplication]).andReturn(mockApplication);
68 
69  FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"test" project:nil];
70  [engine runWithEntrypoint:nil];
71 
72  XCTestExpectation* invokeExpectation =
73  [self expectationWithDescription:@"Web search launched with non escaped search term"];
74 
75  FlutterPlatformPlugin* plugin = [[FlutterPlatformPlugin alloc] initWithEngine:engine];
76  FlutterPlatformPlugin* mockPlugin = OCMPartialMock(plugin);
77 
78  FlutterMethodCall* methodCall = [FlutterMethodCall methodCallWithMethodName:@"SearchWeb.invoke"
79  arguments:@"Test"];
80 
81  FlutterResult result = ^(id result) {
82  OCMVerify([mockPlugin searchWeb:@"Test"]);
83 #if not APPLICATION_EXTENSION_API_ONLY
84  OCMVerify([mockApplication openURL:[NSURL URLWithString:@"x-web-search://?Test"]
85  options:@{}
86  completionHandler:nil]);
87 #endif
88  [invokeExpectation fulfill];
89  };
90 
91  [mockPlugin handleMethodCall:methodCall result:result];
92  [self waitForExpectationsWithTimeout:1 handler:nil];
93  [mockApplication stopMocking];
94 }
95 
96 - (void)testLookUpCallInitiated {
97  FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"test" project:nil];
98  [engine runWithEntrypoint:nil];
99 
100  XCTestExpectation* presentExpectation =
101  [self expectationWithDescription:@"Look Up view controller presented"];
102 
103  FlutterViewController* engineViewController = [[FlutterViewController alloc] initWithEngine:engine
104  nibName:nil
105  bundle:nil];
106  FlutterViewController* mockEngineViewController = OCMPartialMock(engineViewController);
107 
108  FlutterPlatformPlugin* plugin = [[FlutterPlatformPlugin alloc] initWithEngine:engine];
109  FlutterPlatformPlugin* mockPlugin = OCMPartialMock(plugin);
110 
111  FlutterMethodCall* methodCall = [FlutterMethodCall methodCallWithMethodName:@"LookUp.invoke"
112  arguments:@"Test"];
113  FlutterResult result = ^(id result) {
114  OCMVerify([mockEngineViewController
115  presentViewController:[OCMArg isKindOfClass:[UIReferenceLibraryViewController class]]
116  animated:YES
117  completion:nil]);
118  [presentExpectation fulfill];
119  };
120  [mockPlugin handleMethodCall:methodCall result:result];
121  [self waitForExpectationsWithTimeout:2 handler:nil];
122 }
123 
124 - (void)testShareScreenInvoked {
125  FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"test" project:nil];
126  [engine runWithEntrypoint:nil];
127 
128  XCTestExpectation* presentExpectation =
129  [self expectationWithDescription:@"Share view controller presented"];
130 
131  FlutterViewController* engineViewController = [[FlutterViewController alloc] initWithEngine:engine
132  nibName:nil
133  bundle:nil];
134  FlutterViewController* mockEngineViewController = OCMPartialMock(engineViewController);
135  OCMStub([mockEngineViewController
136  presentViewController:[OCMArg isKindOfClass:[UIActivityViewController class]]
137  animated:YES
138  completion:nil]);
139 
140  FlutterPlatformPlugin* plugin = [[FlutterPlatformPlugin alloc] initWithEngine:engine];
141  FlutterPlatformPlugin* mockPlugin = OCMPartialMock(plugin);
142 
143  FlutterMethodCall* methodCall = [FlutterMethodCall methodCallWithMethodName:@"Share.invoke"
144  arguments:@"Test"];
145  FlutterResult result = ^(id result) {
146  OCMVerify([mockEngineViewController
147  presentViewController:[OCMArg isKindOfClass:[UIActivityViewController class]]
148  animated:YES
149  completion:nil]);
150  [presentExpectation fulfill];
151  };
152  [mockPlugin handleMethodCall:methodCall result:result];
153  [self waitForExpectationsWithTimeout:1 handler:nil];
154 }
155 
156 - (void)testShareScreenInvokedOnIPad {
157  FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"test" project:nil];
158  [engine runWithEntrypoint:nil];
159 
160  XCTestExpectation* presentExpectation =
161  [self expectationWithDescription:@"Share view controller presented on iPad"];
162 
163  FlutterViewController* engineViewController = [[FlutterViewController alloc] initWithEngine:engine
164  nibName:nil
165  bundle:nil];
166  FlutterViewController* mockEngineViewController = OCMPartialMock(engineViewController);
167  OCMStub([mockEngineViewController
168  presentViewController:[OCMArg isKindOfClass:[UIActivityViewController class]]
169  animated:YES
170  completion:nil]);
171 
172  id mockTraitCollection = OCMClassMock([UITraitCollection class]);
173  OCMStub([mockTraitCollection userInterfaceIdiom]).andReturn(UIUserInterfaceIdiomPad);
174 
175  FlutterPlatformPlugin* plugin = [[FlutterPlatformPlugin alloc] initWithEngine:engine];
176  FlutterPlatformPlugin* mockPlugin = OCMPartialMock(plugin);
177 
178  FlutterMethodCall* methodCall = [FlutterMethodCall methodCallWithMethodName:@"Share.invoke"
179  arguments:@"Test"];
180  FlutterResult result = ^(id result) {
181  OCMVerify([mockEngineViewController
182  presentViewController:[OCMArg isKindOfClass:[UIActivityViewController class]]
183  animated:YES
184  completion:nil]);
185  [presentExpectation fulfill];
186  };
187  [mockPlugin handleMethodCall:methodCall result:result];
188  [self waitForExpectationsWithTimeout:1 handler:nil];
189 }
190 
191 - (void)testClipboardHasCorrectStrings {
192  [UIPasteboard generalPasteboard].string = nil;
193  FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"test" project:nil];
194  FlutterPlatformPlugin* plugin = [[FlutterPlatformPlugin alloc] initWithEngine:engine];
195 
196  XCTestExpectation* setStringExpectation = [self expectationWithDescription:@"setString"];
197  FlutterResult resultSet = ^(id result) {
198  [setStringExpectation fulfill];
199  };
200  FlutterMethodCall* methodCallSet =
201  [FlutterMethodCall methodCallWithMethodName:@"Clipboard.setData"
202  arguments:@{@"text" : @"some string"}];
203  [plugin handleMethodCall:methodCallSet result:resultSet];
204  [self waitForExpectationsWithTimeout:1 handler:nil];
205 
206  XCTestExpectation* hasStringsExpectation = [self expectationWithDescription:@"hasStrings"];
207  FlutterResult result = ^(id result) {
208  XCTAssertTrue([result[@"value"] boolValue]);
209  [hasStringsExpectation fulfill];
210  };
211  FlutterMethodCall* methodCall =
212  [FlutterMethodCall methodCallWithMethodName:@"Clipboard.hasStrings" arguments:nil];
213  [plugin handleMethodCall:methodCall result:result];
214  [self waitForExpectationsWithTimeout:1 handler:nil];
215 
216  XCTestExpectation* getDataExpectation = [self expectationWithDescription:@"getData"];
217  FlutterResult getDataResult = ^(id result) {
218  XCTAssertEqualObjects(result[@"text"], @"some string");
219  [getDataExpectation fulfill];
220  };
221  FlutterMethodCall* methodCallGetData =
222  [FlutterMethodCall methodCallWithMethodName:@"Clipboard.getData" arguments:@"text/plain"];
223  [plugin handleMethodCall:methodCallGetData result:getDataResult];
224  [self waitForExpectationsWithTimeout:1 handler:nil];
225 }
226 
227 - (void)testClipboardSetDataToNullDoNotCrash {
228  [UIPasteboard generalPasteboard].string = nil;
229  FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"test" project:nil];
230  FlutterPlatformPlugin* plugin = [[FlutterPlatformPlugin alloc] initWithEngine:engine];
231 
232  XCTestExpectation* setStringExpectation = [self expectationWithDescription:@"setData"];
233  FlutterResult resultSet = ^(id result) {
234  [setStringExpectation fulfill];
235  };
236  FlutterMethodCall* methodCallSet =
237  [FlutterMethodCall methodCallWithMethodName:@"Clipboard.setData"
238  arguments:@{@"text" : [NSNull null]}];
239  [plugin handleMethodCall:methodCallSet result:resultSet];
240 
241  XCTestExpectation* getDataExpectation = [self expectationWithDescription:@"getData"];
242  FlutterResult result = ^(id result) {
243  XCTAssertEqualObjects(result[@"text"], @"null");
244  [getDataExpectation fulfill];
245  };
246  FlutterMethodCall* methodCall = [FlutterMethodCall methodCallWithMethodName:@"Clipboard.getData"
247  arguments:@"text/plain"];
248  [plugin handleMethodCall:methodCall result:result];
249  [self waitForExpectationsWithTimeout:1 handler:nil];
250 }
251 
252 - (void)testPopSystemNavigator {
253  FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"test" project:nil];
254  [engine runWithEntrypoint:nil];
255  FlutterViewController* flutterViewController =
256  [[FlutterViewController alloc] initWithEngine:engine nibName:nil bundle:nil];
257  UINavigationController* navigationController =
258  [[UINavigationController alloc] initWithRootViewController:flutterViewController];
259  UITabBarController* tabBarController = [[UITabBarController alloc] init];
260  tabBarController.viewControllers = @[ navigationController ];
261  FlutterPlatformPlugin* plugin = [[FlutterPlatformPlugin alloc] initWithEngine:engine];
262 
263  id navigationControllerMock = OCMPartialMock(navigationController);
264  OCMStub([navigationControllerMock popViewControllerAnimated:YES]);
265  // Set some string to the pasteboard.
266  XCTestExpectation* navigationPopCalled = [self expectationWithDescription:@"SystemNavigator.pop"];
267  FlutterResult resultSet = ^(id result) {
268  [navigationPopCalled fulfill];
269  };
270  FlutterMethodCall* methodCallSet =
271  [FlutterMethodCall methodCallWithMethodName:@"SystemNavigator.pop" arguments:@(YES)];
272  [plugin handleMethodCall:methodCallSet result:resultSet];
273  [self waitForExpectationsWithTimeout:1 handler:nil];
274  OCMVerify([navigationControllerMock popViewControllerAnimated:YES]);
275 
276  [flutterViewController deregisterNotifications];
277 }
278 
279 - (void)testWhetherDeviceHasLiveTextInputInvokeCorrectly {
280  FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"test" project:nil];
281  XCTestExpectation* invokeExpectation =
282  [self expectationWithDescription:@"isLiveTextInputAvailableInvoke"];
283  FlutterPlatformPlugin* plugin = [[FlutterPlatformPlugin alloc] initWithEngine:engine];
284  FlutterPlatformPlugin* mockPlugin = OCMPartialMock(plugin);
285  FlutterMethodCall* methodCall =
286  [FlutterMethodCall methodCallWithMethodName:@"LiveText.isLiveTextInputAvailable"
287  arguments:nil];
288  FlutterResult result = ^(id result) {
289  OCMVerify([mockPlugin isLiveTextInputAvailable]);
290  [invokeExpectation fulfill];
291  };
292  [mockPlugin handleMethodCall:methodCall result:result];
293  [self waitForExpectationsWithTimeout:1 handler:nil];
294 }
295 
296 - (void)testViewControllerBasedStatusBarHiddenUpdate {
297  id bundleMock = OCMPartialMock([NSBundle mainBundle]);
298  OCMStub([bundleMock objectForInfoDictionaryKey:@"UIViewControllerBasedStatusBarAppearance"])
299  .andReturn(@YES);
300  {
301  // Enabling system UI overlays to update status bar.
302  FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"test" project:nil];
303  [engine runWithEntrypoint:nil];
304  FlutterViewController* flutterViewController =
305  [[FlutterViewController alloc] initWithEngine:engine nibName:nil bundle:nil];
306  XCTAssertFalse(flutterViewController.prefersStatusBarHidden);
307 
308  // Update to hidden.
309  FlutterPlatformPlugin* plugin = [engine platformPlugin];
310 
311  XCTestExpectation* enableSystemUIOverlaysCalled =
312  [self expectationWithDescription:@"setEnabledSystemUIOverlays"];
313  FlutterResult resultSet = ^(id result) {
314  [enableSystemUIOverlaysCalled fulfill];
315  };
316  FlutterMethodCall* methodCallSet =
317  [FlutterMethodCall methodCallWithMethodName:@"SystemChrome.setEnabledSystemUIOverlays"
318  arguments:@[ @"SystemUiOverlay.bottom" ]];
319  [plugin handleMethodCall:methodCallSet result:resultSet];
320  [self waitForExpectationsWithTimeout:1 handler:nil];
321  XCTAssertTrue(flutterViewController.prefersStatusBarHidden);
322 
323  // Update to shown.
324  XCTestExpectation* enableSystemUIOverlaysCalled2 =
325  [self expectationWithDescription:@"setEnabledSystemUIOverlays"];
326  FlutterResult resultSet2 = ^(id result) {
327  [enableSystemUIOverlaysCalled2 fulfill];
328  };
329  FlutterMethodCall* methodCallSet2 =
330  [FlutterMethodCall methodCallWithMethodName:@"SystemChrome.setEnabledSystemUIOverlays"
331  arguments:@[ @"SystemUiOverlay.top" ]];
332  [plugin handleMethodCall:methodCallSet2 result:resultSet2];
333  [self waitForExpectationsWithTimeout:1 handler:nil];
334  XCTAssertFalse(flutterViewController.prefersStatusBarHidden);
335 
336  [flutterViewController deregisterNotifications];
337  }
338  {
339  // Enable system UI mode to update status bar.
340  FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"test" project:nil];
341  [engine runWithEntrypoint:nil];
342  FlutterViewController* flutterViewController =
343  [[FlutterViewController alloc] initWithEngine:engine nibName:nil bundle:nil];
344  XCTAssertFalse(flutterViewController.prefersStatusBarHidden);
345 
346  // Update to hidden.
347  FlutterPlatformPlugin* plugin = [engine platformPlugin];
348 
349  XCTestExpectation* enableSystemUIModeCalled =
350  [self expectationWithDescription:@"setEnabledSystemUIMode"];
351  FlutterResult resultSet = ^(id result) {
352  [enableSystemUIModeCalled fulfill];
353  };
354  FlutterMethodCall* methodCallSet =
355  [FlutterMethodCall methodCallWithMethodName:@"SystemChrome.setEnabledSystemUIMode"
356  arguments:@"SystemUiMode.immersive"];
357  [plugin handleMethodCall:methodCallSet result:resultSet];
358  [self waitForExpectationsWithTimeout:1 handler:nil];
359  XCTAssertTrue(flutterViewController.prefersStatusBarHidden);
360 
361  // Update to shown.
362  XCTestExpectation* enableSystemUIModeCalled2 =
363  [self expectationWithDescription:@"setEnabledSystemUIMode"];
364  FlutterResult resultSet2 = ^(id result) {
365  [enableSystemUIModeCalled2 fulfill];
366  };
367  FlutterMethodCall* methodCallSet2 =
368  [FlutterMethodCall methodCallWithMethodName:@"SystemChrome.setEnabledSystemUIMode"
369  arguments:@"SystemUiMode.edgeToEdge"];
370  [plugin handleMethodCall:methodCallSet2 result:resultSet2];
371  [self waitForExpectationsWithTimeout:1 handler:nil];
372  XCTAssertFalse(flutterViewController.prefersStatusBarHidden);
373 
374  [flutterViewController deregisterNotifications];
375  }
376  [bundleMock stopMocking];
377 }
378 
379 - (void)testStatusBarHiddenUpdate {
380  id bundleMock = OCMPartialMock([NSBundle mainBundle]);
381  OCMStub([bundleMock objectForInfoDictionaryKey:@"UIViewControllerBasedStatusBarAppearance"])
382  .andReturn(@NO);
383  id mockApplication = OCMClassMock([UIApplication class]);
384  OCMStub([mockApplication sharedApplication]).andReturn(mockApplication);
385 
386  // Enabling system UI overlays to update status bar.
387  FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"test" project:nil];
388  [engine runWithEntrypoint:nil];
389  FlutterViewController* flutterViewController =
390  [[FlutterViewController alloc] initWithEngine:engine nibName:nil bundle:nil];
391 
392  // Update to hidden.
393  FlutterPlatformPlugin* plugin = [engine platformPlugin];
394 
395  XCTestExpectation* enableSystemUIOverlaysCalled =
396  [self expectationWithDescription:@"setEnabledSystemUIOverlays"];
397  FlutterResult resultSet = ^(id result) {
398  [enableSystemUIOverlaysCalled fulfill];
399  };
400  FlutterMethodCall* methodCallSet =
401  [FlutterMethodCall methodCallWithMethodName:@"SystemChrome.setEnabledSystemUIOverlays"
402  arguments:@[ @"SystemUiOverlay.bottom" ]];
403  [plugin handleMethodCall:methodCallSet result:resultSet];
404  [self waitForExpectationsWithTimeout:1 handler:nil];
405 #if not APPLICATION_EXTENSION_API_ONLY
406  OCMVerify([mockApplication setStatusBarHidden:YES]);
407 #endif
408 
409  // Update to shown.
410  XCTestExpectation* enableSystemUIOverlaysCalled2 =
411  [self expectationWithDescription:@"setEnabledSystemUIOverlays"];
412  FlutterResult resultSet2 = ^(id result) {
413  [enableSystemUIOverlaysCalled2 fulfill];
414  };
415  FlutterMethodCall* methodCallSet2 =
416  [FlutterMethodCall methodCallWithMethodName:@"SystemChrome.setEnabledSystemUIOverlays"
417  arguments:@[ @"SystemUiOverlay.top" ]];
418  [plugin handleMethodCall:methodCallSet2 result:resultSet2];
419  [self waitForExpectationsWithTimeout:1 handler:nil];
420 #if not APPLICATION_EXTENSION_API_ONLY
421  OCMVerify([mockApplication setStatusBarHidden:NO]);
422 #endif
423 
424  [flutterViewController deregisterNotifications];
425  [mockApplication stopMocking];
426  [bundleMock stopMocking];
427 }
428 
429 - (void)testStatusBarStyle {
430  id bundleMock = OCMPartialMock([NSBundle mainBundle]);
431  OCMStub([bundleMock objectForInfoDictionaryKey:@"UIViewControllerBasedStatusBarAppearance"])
432  .andReturn(@NO);
433  id mockApplication = OCMClassMock([UIApplication class]);
434  OCMStub([mockApplication sharedApplication]).andReturn(mockApplication);
435 
436  FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"test" project:nil];
437  [engine runWithEntrypoint:nil];
438  FlutterViewController* flutterViewController =
439  [[FlutterViewController alloc] initWithEngine:engine nibName:nil bundle:nil];
440  XCTAssertFalse(flutterViewController.prefersStatusBarHidden);
441 
442  FlutterPlatformPlugin* plugin = [engine platformPlugin];
443 
444  XCTestExpectation* enableSystemUIModeCalled =
445  [self expectationWithDescription:@"setSystemUIOverlayStyle"];
446  FlutterResult resultSet = ^(id result) {
447  [enableSystemUIModeCalled fulfill];
448  };
449  FlutterMethodCall* methodCallSet =
450  [FlutterMethodCall methodCallWithMethodName:@"SystemChrome.setSystemUIOverlayStyle"
451  arguments:@{@"statusBarBrightness" : @"Brightness.dark"}];
452  [plugin handleMethodCall:methodCallSet result:resultSet];
453  [self waitForExpectationsWithTimeout:1 handler:nil];
454 
455 #if not APPLICATION_EXTENSION_API_ONLY
456  OCMVerify([mockApplication setStatusBarStyle:UIStatusBarStyleLightContent]);
457 #endif
458 
459  [flutterViewController deregisterNotifications];
460  [mockApplication stopMocking];
461  [bundleMock stopMocking];
462 }
463 
464 @end
FlutterEngine
Definition: FlutterEngine.h:61
+[FlutterMethodCall methodCallWithMethodName:arguments:]
instancetype methodCallWithMethodName:arguments:(NSString *method,[arguments] id _Nullable arguments)
FlutterViewController
Definition: FlutterViewController.h:57
FlutterPlatformPluginTest
Definition: FlutterPlatformPluginTest.mm:17
-[FlutterEngine runWithEntrypoint:]
BOOL runWithEntrypoint:(nullable NSString *entrypoint)
FlutterEngine_Internal.h
-[FlutterPlatformPlugin handleMethodCall:result:]
void handleMethodCall:result:(FlutterMethodCall *call,[result] FlutterResult result)
Definition: FlutterPlatformPlugin.mm:106
FlutterMacros.h
FlutterMethodCall
Definition: FlutterCodecs.h:220
FlutterBinaryMessenger.h
FlutterResult
void(^ FlutterResult)(id _Nullable result)
Definition: FlutterChannels.h:194
FlutterPlatformPlugin.h
engine
id engine
Definition: FlutterTextInputPluginTest.mm:89
FlutterPlatformPlugin
Definition: FlutterPlatformPlugin.h:11
platform_view_ios.h
FLUTTER_ASSERT_ARC
Definition: FlutterChannelKeyResponder.mm:13
FlutterViewController.h