| 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
8 #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterBinaryMessenger.h"
9 #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h"
10 #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h"
11 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h"
12 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.h"
13 #import "flutter/shell/platform/darwin/ios/platform_view_ios.h"
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