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