| Flutter iOS Embedder |

FlutterViewControllerTest.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 #include "flutter/fml/platform/darwin/message_loop_darwin.h"
9 #import "flutter/lib/ui/window/platform_configuration.h"
10 #include "flutter/lib/ui/window/pointer_data.h"
11 #import "flutter/lib/ui/window/viewport_metrics.h"
12 #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterBinaryMessenger.h"
13 #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterHourFormat.h"
14 #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h"
15 #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h"
16 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEmbedderKeyResponder.h"
17 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterFakeKeyEvents.h"
18 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h"
19 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterView.h"
20 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h"
21 #import "flutter/shell/platform/darwin/ios/framework/Source/UIViewController+FlutterScreenAndSceneIfLoaded.h"
22 #import "flutter/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.h"
23 #import "flutter/shell/platform/embedder/embedder.h"
24 #import "flutter/third_party/spring_animation/spring_animation.h"
25
26 FLUTTER_ASSERT_ARC
27
28 using namespace flutter::testing;
29
30 @interface FlutterEngine ()
31 - (FlutterTextInputPlugin*)textInputPlugin;
32 - (void)sendKeyEvent:(const FlutterKeyEvent&)event
33 callback:(nullable FlutterKeyEventCallback)callback
34 userData:(nullable void*)userData;
35 - (fml::RefPtr<fml::TaskRunner>)uiTaskRunner;
36 @end
37
38 /// Sometimes we have to use a custom mock to avoid retain cycles in OCMock.
39 /// Used for testing low memory notification.
40 @interface FlutterEnginePartialMock : FlutterEngine
41 @property(nonatomic, strong) FlutterBasicMessageChannel* lifecycleChannel;
42 @property(nonatomic, strong) FlutterBasicMessageChannel* keyEventChannel;
43 @property(nonatomic, weak) FlutterViewController* viewController;
44 @property(nonatomic, strong) FlutterTextInputPlugin* textInputPlugin;
45 @property(nonatomic, assign) BOOL didCallNotifyLowMemory;
46 - (FlutterTextInputPlugin*)textInputPlugin;
47 - (void)sendKeyEvent:(const FlutterKeyEvent&)event
48 callback:(nullable FlutterKeyEventCallback)callback
49 userData:(nullable void*)userData;
50 @end
51
52 @implementation FlutterEnginePartialMock
53 @synthesize viewController;
54 @synthesize lifecycleChannel;
55 @synthesize keyEventChannel;
56 @synthesize textInputPlugin;
57
58 - (void)notifyLowMemory {
59 _didCallNotifyLowMemory = YES;
60 }
61
62 - (void)sendKeyEvent:(const FlutterKeyEvent&)event
63 callback:(FlutterKeyEventCallback)callback
64 userData:(void*)userData API_AVAILABLE(ios(9.0)) {
65 if (callback == nil) {
66 return;
67 }
68 // NSAssert(callback != nullptr, @"Invalid callback");
69 // Response is async, so we have to post it to the run loop instead of calling
70 // it directly.
71 CFRunLoopPerformBlock(CFRunLoopGetCurrent(), fml::MessageLoopDarwin::kMessageLoopCFRunLoopMode,
72 ^() {
73 callback(true, userData);
74 });
75 }
76 @end
77
78 @interface FlutterEngine ()
79 - (BOOL)createShell:(NSString*)entrypoint
80 libraryURI:(NSString*)libraryURI
81 initialRoute:(NSString*)initialRoute;
82 - (void)dispatchPointerDataPacket:(std::unique_ptr<flutter::PointerDataPacket>)packet;
83 - (void)updateViewportMetrics:(flutter::ViewportMetrics)viewportMetrics;
84 - (void)attachView;
85 @end
86
87 @interface FlutterEngine (TestLowMemory)
88 - (void)notifyLowMemory;
89 @end
90
91 extern NSNotificationName const FlutterViewControllerWillDealloc;
92
93 /// A simple mock class for FlutterEngine.
94 ///
95 /// OCMClassMock can't be used for FlutterEngine sometimes because OCMock retains arguments to
96 /// invocations and since the init for FlutterViewController calls a method on the
97 /// FlutterEngine it creates a retain cycle that stops us from testing behaviors related to
98 /// deleting FlutterViewControllers.
99 ///
100 /// Used for testing deallocation.
101 @interface MockEngine : NSObject
102 @property(nonatomic, strong) FlutterDartProject* project;
103 @end
104
105 @implementation MockEngine
106 - (FlutterViewController*)viewController {
107 return nil;
108 }
109 - (void)setViewController:(FlutterViewController*)viewController {
110 // noop
111 }
112 @end
113
114 @interface FlutterKeyboardManager (Tests)
115 @property(nonatomic, retain, readonly)
116 NSMutableArray<id<FlutterKeyPrimaryResponder>>* primaryResponders;
117 @end
118
119 @interface FlutterEmbedderKeyResponder (Tests)
120 @property(nonatomic, copy, readonly) FlutterSendKeyEvent sendEvent;
121 @end
122
123 @interface FlutterViewController (Tests)
124
125 @property(nonatomic, assign) double targetViewInsetBottom;
126 @property(nonatomic, assign) BOOL isKeyboardInOrTransitioningFromBackground;
127 @property(nonatomic, assign) BOOL keyboardAnimationIsShowing;
128 @property(nonatomic, strong) VSyncClient* keyboardAnimationVSyncClient;
129 @property(nonatomic, strong) VSyncClient* touchRateCorrectionVSyncClient;
130
131 - (void)createTouchRateCorrectionVSyncClientIfNeeded;
132 - (void)surfaceUpdated:(BOOL)appeared;
133 - (void)performOrientationUpdate:(UIInterfaceOrientationMask)new_preferences;
134 - (void)handlePressEvent:(FlutterUIPressProxy*)press
135 nextAction:(void (^)())next API_AVAILABLE(ios(13.4));
136 - (void)discreteScrollEvent:(UIPanGestureRecognizer*)recognizer;
137 - (void)updateViewportMetricsIfNeeded;
138 - (void)onUserSettingsChanged:(NSNotification*)notification;
139 - (void)applicationWillTerminate:(NSNotification*)notification;
140 - (void)goToApplicationLifecycle:(nonnull NSString*)state;
141 - (void)handleKeyboardNotification:(NSNotification*)notification;
142 - (CGFloat)calculateKeyboardInset:(CGRect)keyboardFrame keyboardMode:(int)keyboardMode;
143 - (BOOL)shouldIgnoreKeyboardNotification:(NSNotification*)notification;
144 - (FlutterKeyboardMode)calculateKeyboardAttachMode:(NSNotification*)notification;
145 - (CGFloat)calculateMultitaskingAdjustment:(CGRect)screenRect keyboardFrame:(CGRect)keyboardFrame;
146 - (void)startKeyBoardAnimation:(NSTimeInterval)duration;
147 - (UIView*)keyboardAnimationView;
148 - (SpringAnimation*)keyboardSpringAnimation;
149 - (void)setUpKeyboardSpringAnimationIfNeeded:(CAAnimation*)keyboardAnimation;
150 - (void)setUpKeyboardAnimationVsyncClient:
151 (FlutterKeyboardAnimationCallback)keyboardAnimationCallback;
152 - (void)ensureViewportMetricsIsCorrect;
153 - (void)invalidateKeyboardAnimationVSyncClient;
154 - (void)addInternalPlugins;
155 - (flutter::PointerData)generatePointerDataForFake;
156 - (void)sharedSetupWithProject:(nullable FlutterDartProject*)project
157 initialRoute:(nullable NSString*)initialRoute;
158 - (void)applicationBecameActive:(NSNotification*)notification;
159 - (void)applicationWillResignActive:(NSNotification*)notification;
160 - (void)applicationWillTerminate:(NSNotification*)notification;
161 - (void)applicationDidEnterBackground:(NSNotification*)notification;
162 - (void)applicationWillEnterForeground:(NSNotification*)notification;
163 - (void)sceneBecameActive:(NSNotification*)notification API_AVAILABLE(ios(13.0));
164 - (void)sceneWillResignActive:(NSNotification*)notification API_AVAILABLE(ios(13.0));
165 - (void)sceneWillDisconnect:(NSNotification*)notification API_AVAILABLE(ios(13.0));
166 - (void)sceneDidEnterBackground:(NSNotification*)notification API_AVAILABLE(ios(13.0));
167 - (void)sceneWillEnterForeground:(NSNotification*)notification API_AVAILABLE(ios(13.0));
168 - (void)triggerTouchRateCorrectionIfNeeded:(NSSet*)touches;
169 @end
170
171 @interface FlutterViewControllerTest : XCTestCase
172 @property(nonatomic, strong) id mockEngine;
173 @property(nonatomic, strong) id mockTextInputPlugin;
174 @property(nonatomic, strong) id messageSent;
175 - (void)sendMessage:(id _Nullable)message reply:(FlutterReply _Nullable)callback;
176 @end
177
178 @interface UITouch ()
179
180 @property(nonatomic, readwrite) UITouchPhase phase;
181
182 @end
183
184 @interface VSyncClient (Testing)
185
186 - (CADisplayLink*)getDisplayLink;
187
188 @end
189
190 @implementation FlutterViewControllerTest
191
192 - (void)setUp {
193 self.mockEngine = OCMClassMock([FlutterEngine class]);
194 self.mockTextInputPlugin = OCMClassMock([FlutterTextInputPlugin class]);
195 OCMStub([self.mockEngine textInputPlugin]).andReturn(self.mockTextInputPlugin);
196 self.messageSent = nil;
197 }
198
199 - (void)tearDown {
200 // We stop mocking here to avoid retain cycles that stop
201 // FlutterViewControllers from deallocing.
202 [self.mockEngine stopMocking];
203 self.mockEngine = nil;
204 self.mockTextInputPlugin = nil;
205 self.messageSent = nil;
206 }
207
208 - (id)setUpMockScreen {
209 UIScreen* mockScreen = OCMClassMock([UIScreen class]);
210 // iPhone 14 pixels
211 CGRect screenBounds = CGRectMake(0, 0, 1170, 2532);
212 OCMStub([mockScreen bounds]).andReturn(screenBounds);
213 CGFloat screenScale = 1;
214 OCMStub([mockScreen scale]).andReturn(screenScale);
215
216 return mockScreen;
217 }
218
219 - (id)setUpMockView:(FlutterViewController*)viewControllerMock
220 screen:(UIScreen*)screen
221 viewFrame:(CGRect)viewFrame
222 convertedFrame:(CGRect)convertedFrame {
223 OCMStub([viewControllerMock flutterScreenIfViewLoaded]).andReturn(screen);
224 id mockView = OCMClassMock([UIView class]);
225 OCMStub([mockView frame]).andReturn(viewFrame);
226 OCMStub([mockView convertRect:viewFrame toCoordinateSpace:[OCMArg any]])
227 .andReturn(convertedFrame);
228 OCMStub([viewControllerMock viewIfLoaded]).andReturn(mockView);
229
230 return mockView;
231 }
232
233 - (void)testViewDidLoadWillInvokeCreateTouchRateCorrectionVSyncClient {
234 FlutterEngine* engine = [[FlutterEngine alloc] init];
235 [engine runWithEntrypoint:nil];
236 FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine
237 nibName:nil
238 bundle:nil];
239 FlutterViewController* viewControllerMock = OCMPartialMock(viewController);
240 [viewControllerMock loadView];
241 [viewControllerMock viewDidLoad];
242 OCMVerify([viewControllerMock createTouchRateCorrectionVSyncClientIfNeeded]);
243 }
244
245 - (void)testStartKeyboardAnimationWillInvokeSetupKeyboardSpringAnimationIfNeeded {
246 FlutterEngine* engine = [[FlutterEngine alloc] init];
247 [engine runWithEntrypoint:nil];
248 FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine
249 nibName:nil
250 bundle:nil];
251 FlutterViewController* viewControllerMock = OCMPartialMock(viewController);
252 viewControllerMock.targetViewInsetBottom = 100;
253 [viewControllerMock startKeyBoardAnimation:0.25];
254
255 CAAnimation* keyboardAnimation =
256 [[viewControllerMock keyboardAnimationView].layer animationForKey:@"position"];
257
258 OCMVerify([viewControllerMock setUpKeyboardSpringAnimationIfNeeded:keyboardAnimation]);
259 }
260
261 - (void)testSetupKeyboardSpringAnimationIfNeeded {
262 FlutterEngine* engine = [[FlutterEngine alloc] init];
263 [engine runWithEntrypoint:nil];
264 FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine
265 nibName:nil
266 bundle:nil];
267 FlutterViewController* viewControllerMock = OCMPartialMock(viewController);
268 UIScreen* screen = [self setUpMockScreen];
269 CGRect viewFrame = screen.bounds;
270 [self setUpMockView:viewControllerMock
271 screen:screen
272 viewFrame:viewFrame
273 convertedFrame:viewFrame];
274
275 // Null check.
276 [viewControllerMock setUpKeyboardSpringAnimationIfNeeded:nil];
277 SpringAnimation* keyboardSpringAnimation = [viewControllerMock keyboardSpringAnimation];
278 XCTAssertTrue(keyboardSpringAnimation == nil);
279
280 // CAAnimation that is not a CASpringAnimation.
281 CABasicAnimation* nonSpringAnimation = [CABasicAnimation animation];
282 nonSpringAnimation.duration = 1.0;
283 nonSpringAnimation.fromValue = [NSNumber numberWithFloat:0.0];
284 nonSpringAnimation.toValue = [NSNumber numberWithFloat:1.0];
285 nonSpringAnimation.keyPath = @"position";
286 [viewControllerMock setUpKeyboardSpringAnimationIfNeeded:nonSpringAnimation];
287 keyboardSpringAnimation = [viewControllerMock keyboardSpringAnimation];
288
289 XCTAssertTrue(keyboardSpringAnimation == nil);
290
291 // CASpringAnimation.
292 CASpringAnimation* springAnimation = [CASpringAnimation animation];
293 springAnimation.mass = 1.0;
294 springAnimation.stiffness = 100.0;
295 springAnimation.damping = 10.0;
296 springAnimation.keyPath = @"position";
297 springAnimation.fromValue = [NSValue valueWithCGPoint:CGPointMake(0, 0)];
298 springAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(100, 100)];
299 [viewControllerMock setUpKeyboardSpringAnimationIfNeeded:springAnimation];
300 keyboardSpringAnimation = [viewControllerMock keyboardSpringAnimation];
301 XCTAssertTrue(keyboardSpringAnimation != nil);
302 }
303
304 - (void)testKeyboardAnimationIsShowingAndCompounding {
305 FlutterEngine* engine = [[FlutterEngine alloc] init];
306 [engine runWithEntrypoint:nil];
307 FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine
308 nibName:nil
309 bundle:nil];
310 FlutterViewController* viewControllerMock = OCMPartialMock(viewController);
311 UIScreen* screen = [self setUpMockScreen];
312 CGRect viewFrame = screen.bounds;
313 [self setUpMockView:viewControllerMock
314 screen:screen
315 viewFrame:viewFrame
316 convertedFrame:viewFrame];
317
318 BOOL isLocal = YES;
319 CGFloat screenHeight = screen.bounds.size.height;
320 CGFloat screenWidth = screen.bounds.size.height;
321
322 // Start show keyboard animation.
323 CGRect initialShowKeyboardBeginFrame = CGRectMake(0, screenHeight, screenWidth, 250);
324 CGRect initialShowKeyboardEndFrame = CGRectMake(0, screenHeight - 250, screenWidth, 500);
325 NSNotification* fakeNotification = [NSNotification
326 notificationWithName:UIKeyboardWillChangeFrameNotification
327 object:nil
328 userInfo:@{
329 @"UIKeyboardFrameBeginUserInfoKey" : @(initialShowKeyboardBeginFrame),
330 @"UIKeyboardFrameEndUserInfoKey" : @(initialShowKeyboardEndFrame),
331 @"UIKeyboardAnimationDurationUserInfoKey" : @(0.25),
332 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
333 }];
334 viewControllerMock.targetViewInsetBottom = 0;
335 [viewControllerMock handleKeyboardNotification:fakeNotification];
336 BOOL isShowingAnimation1 = viewControllerMock.keyboardAnimationIsShowing;
337 XCTAssertTrue(isShowingAnimation1);
338
339 // Start compounding show keyboard animation.
340 CGRect compoundingShowKeyboardBeginFrame = CGRectMake(0, screenHeight - 250, screenWidth, 250);
341 CGRect compoundingShowKeyboardEndFrame = CGRectMake(0, screenHeight - 500, screenWidth, 500);
342 fakeNotification = [NSNotification
343 notificationWithName:UIKeyboardWillChangeFrameNotification
344 object:nil
345 userInfo:@{
346 @"UIKeyboardFrameBeginUserInfoKey" : @(compoundingShowKeyboardBeginFrame),
347 @"UIKeyboardFrameEndUserInfoKey" : @(compoundingShowKeyboardEndFrame),
348 @"UIKeyboardAnimationDurationUserInfoKey" : @(0.25),
349 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
350 }];
351
352 [viewControllerMock handleKeyboardNotification:fakeNotification];
353 BOOL isShowingAnimation2 = viewControllerMock.keyboardAnimationIsShowing;
354 XCTAssertTrue(isShowingAnimation2);
355 XCTAssertTrue(isShowingAnimation1 == isShowingAnimation2);
356
357 // Start hide keyboard animation.
358 CGRect initialHideKeyboardBeginFrame = CGRectMake(0, screenHeight - 500, screenWidth, 250);
359 CGRect initialHideKeyboardEndFrame = CGRectMake(0, screenHeight - 250, screenWidth, 500);
360 fakeNotification = [NSNotification
361 notificationWithName:UIKeyboardWillChangeFrameNotification
362 object:nil
363 userInfo:@{
364 @"UIKeyboardFrameBeginUserInfoKey" : @(initialHideKeyboardBeginFrame),
365 @"UIKeyboardFrameEndUserInfoKey" : @(initialHideKeyboardEndFrame),
366 @"UIKeyboardAnimationDurationUserInfoKey" : @(0.25),
367 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
368 }];
369
370 [viewControllerMock handleKeyboardNotification:fakeNotification];
371 BOOL isShowingAnimation3 = viewControllerMock.keyboardAnimationIsShowing;
372 XCTAssertFalse(isShowingAnimation3);
373 XCTAssertTrue(isShowingAnimation2 != isShowingAnimation3);
374
375 // Start compounding hide keyboard animation.
376 CGRect compoundingHideKeyboardBeginFrame = CGRectMake(0, screenHeight - 250, screenWidth, 250);
377 CGRect compoundingHideKeyboardEndFrame = CGRectMake(0, screenHeight, screenWidth, 500);
378 fakeNotification = [NSNotification
379 notificationWithName:UIKeyboardWillChangeFrameNotification
380 object:nil
381 userInfo:@{
382 @"UIKeyboardFrameBeginUserInfoKey" : @(compoundingHideKeyboardBeginFrame),
383 @"UIKeyboardFrameEndUserInfoKey" : @(compoundingHideKeyboardEndFrame),
384 @"UIKeyboardAnimationDurationUserInfoKey" : @(0.25),
385 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
386 }];
387
388 [viewControllerMock handleKeyboardNotification:fakeNotification];
389 BOOL isShowingAnimation4 = viewControllerMock.keyboardAnimationIsShowing;
390 XCTAssertFalse(isShowingAnimation4);
391 XCTAssertTrue(isShowingAnimation3 == isShowingAnimation4);
392 }
393
394 - (void)testShouldIgnoreKeyboardNotification {
395 FlutterEngine* mockEngine = OCMPartialMock([[FlutterEngine alloc] init]);
396 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
397 FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:mockEngine
398 nibName:nil
399 bundle:nil];
400 FlutterViewController* viewControllerMock = OCMPartialMock(viewController);
401 UIScreen* screen = [self setUpMockScreen];
402 CGRect viewFrame = screen.bounds;
403 [self setUpMockView:viewControllerMock
404 screen:screen
405 viewFrame:viewFrame
406 convertedFrame:viewFrame];
407
408 CGFloat screenWidth = screen.bounds.size.width;
409 CGFloat screenHeight = screen.bounds.size.height;
410 CGRect emptyKeyboard = CGRectZero;
411 CGRect zeroHeightKeyboard = CGRectMake(0, 0, screenWidth, 0);
412 CGRect validKeyboardEndFrame = CGRectMake(0, screenHeight - 320, screenWidth, 320);
413 BOOL isLocal = NO;
414
415 // Hide notification, valid keyboard
416 NSNotification* notification =
417 [NSNotification notificationWithName:UIKeyboardWillHideNotification
418 object:nil
419 userInfo:@{
420 @"UIKeyboardFrameEndUserInfoKey" : @(validKeyboardEndFrame),
421 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
422 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
423 }];
424
425 BOOL shouldIgnore = [viewControllerMock shouldIgnoreKeyboardNotification:notification];
426 XCTAssertTrue(shouldIgnore == NO);
427
428 // All zero keyboard
429 isLocal = YES;
430 notification = [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
431 object:nil
432 userInfo:@{
433 @"UIKeyboardFrameEndUserInfoKey" : @(emptyKeyboard),
434 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
435 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
436 }];
437 shouldIgnore = [viewControllerMock shouldIgnoreKeyboardNotification:notification];
438 XCTAssertTrue(shouldIgnore == YES);
439
440 // Zero height keyboard
441 isLocal = NO;
442 notification =
443 [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
444 object:nil
445 userInfo:@{
446 @"UIKeyboardFrameEndUserInfoKey" : @(zeroHeightKeyboard),
447 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
448 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
449 }];
450 shouldIgnore = [viewControllerMock shouldIgnoreKeyboardNotification:notification];
451 XCTAssertTrue(shouldIgnore == NO);
452
453 // Valid keyboard, triggered from another app
454 isLocal = NO;
455 notification =
456 [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
457 object:nil
458 userInfo:@{
459 @"UIKeyboardFrameEndUserInfoKey" : @(validKeyboardEndFrame),
460 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
461 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
462 }];
463 shouldIgnore = [viewControllerMock shouldIgnoreKeyboardNotification:notification];
464 XCTAssertTrue(shouldIgnore == YES);
465
466 // Valid keyboard
467 isLocal = YES;
468 notification =
469 [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
470 object:nil
471 userInfo:@{
472 @"UIKeyboardFrameEndUserInfoKey" : @(validKeyboardEndFrame),
473 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
474 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
475 }];
476 shouldIgnore = [viewControllerMock shouldIgnoreKeyboardNotification:notification];
477 XCTAssertTrue(shouldIgnore == NO);
478
479 if (@available(iOS 13.0, *)) {
480 // noop
481 } else {
482 // Valid keyboard, keyboard is in background
483 OCMStub([viewControllerMock isKeyboardInOrTransitioningFromBackground]).andReturn(YES);
484
485 isLocal = YES;
486 notification =
487 [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
488 object:nil
489 userInfo:@{
490 @"UIKeyboardFrameEndUserInfoKey" : @(validKeyboardEndFrame),
491 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
492 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
493 }];
494 shouldIgnore = [viewControllerMock shouldIgnoreKeyboardNotification:notification];
495 XCTAssertTrue(shouldIgnore == YES);
496 }
497 }
498 - (void)testKeyboardAnimationWillNotCrashWhenEngineDestroyed {
499 FlutterEngine* engine = [[FlutterEngine alloc] init];
500 [engine runWithEntrypoint:nil];
501 FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine
502 nibName:nil
503 bundle:nil];
504 [viewController setUpKeyboardAnimationVsyncClient:^(fml::TimePoint){
505 }];
506 [engine destroyContext];
507 }
508
509 - (void)testKeyboardAnimationWillWaitUIThreadVsync {
510 // We need to make sure the new viewport metrics get sent after the
511 // begin frame event has processed. And this test is to expect that the callback
512 // will sync with UI thread. So just simulate a lot of works on UI thread and
513 // test the keyboard animation callback will execute until UI task completed.
514 // Related issue: https://github.com/flutter/flutter/issues/120555.
515
516 FlutterEngine* engine = [[FlutterEngine alloc] init];
517 [engine runWithEntrypoint:nil];
518 FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine
519 nibName:nil
520 bundle:nil];
521 // Post a task to UI thread to block the thread.
522 const int delayTime = 1;
523 [engine uiTaskRunner]->PostTask([] { sleep(delayTime); });
524 XCTestExpectation* expectation = [self expectationWithDescription:@"keyboard animation callback"];
525
526 __block CFTimeInterval fulfillTime;
527 FlutterKeyboardAnimationCallback callback = ^(fml::TimePoint targetTime) {
528 fulfillTime = CACurrentMediaTime();
529 [expectation fulfill];
530 };
531 CFTimeInterval startTime = CACurrentMediaTime();
532 [viewController setUpKeyboardAnimationVsyncClient:callback];
533 [self waitForExpectationsWithTimeout:5.0 handler:nil];
534 XCTAssertTrue(fulfillTime - startTime > delayTime);
535 }
536
537 - (void)testCalculateKeyboardAttachMode {
538 FlutterEngine* mockEngine = OCMPartialMock([[FlutterEngine alloc] init]);
539 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
540 FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:mockEngine
541 nibName:nil
542 bundle:nil];
543
544 FlutterViewController* viewControllerMock = OCMPartialMock(viewController);
545 UIScreen* screen = [self setUpMockScreen];
546 CGRect viewFrame = screen.bounds;
547 [self setUpMockView:viewControllerMock
548 screen:screen
549 viewFrame:viewFrame
550 convertedFrame:viewFrame];
551
552 CGFloat screenWidth = screen.bounds.size.width;
553 CGFloat screenHeight = screen.bounds.size.height;
554
555 // hide notification
556 CGRect keyboardFrame = CGRectZero;
557 NSNotification* notification =
558 [NSNotification notificationWithName:UIKeyboardWillHideNotification
559 object:nil
560 userInfo:@{
561 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
562 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
563 @"UIKeyboardIsLocalUserInfoKey" : @(YES)
564 }];
565 FlutterKeyboardMode keyboardMode = [viewControllerMock calculateKeyboardAttachMode:notification];
566 XCTAssertTrue(keyboardMode == FlutterKeyboardModeHidden);
567
568 // all zeros
569 keyboardFrame = CGRectZero;
570 notification = [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
571 object:nil
572 userInfo:@{
573 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
574 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
575 @"UIKeyboardIsLocalUserInfoKey" : @(YES)
576 }];
577 keyboardMode = [viewControllerMock calculateKeyboardAttachMode:notification];
578 XCTAssertTrue(keyboardMode == FlutterKeyboardModeFloating);
579
580 // 0 height
581 keyboardFrame = CGRectMake(0, 0, screenWidth, 0);
582 notification = [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
583 object:nil
584 userInfo:@{
585 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
586 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
587 @"UIKeyboardIsLocalUserInfoKey" : @(YES)
588 }];
589 keyboardMode = [viewControllerMock calculateKeyboardAttachMode:notification];
590 XCTAssertTrue(keyboardMode == FlutterKeyboardModeHidden);
591
592 // floating
593 keyboardFrame = CGRectMake(0, 0, 320, 320);
594 notification = [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
595 object:nil
596 userInfo:@{
597 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
598 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
599 @"UIKeyboardIsLocalUserInfoKey" : @(YES)
600 }];
601 keyboardMode = [viewControllerMock calculateKeyboardAttachMode:notification];
602 XCTAssertTrue(keyboardMode == FlutterKeyboardModeFloating);
603
604 // undocked
605 keyboardFrame = CGRectMake(0, 0, screenWidth, 320);
606 notification = [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
607 object:nil
608 userInfo:@{
609 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
610 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
611 @"UIKeyboardIsLocalUserInfoKey" : @(YES)
612 }];
613 keyboardMode = [viewControllerMock calculateKeyboardAttachMode:notification];
614 XCTAssertTrue(keyboardMode == FlutterKeyboardModeFloating);
615
616 // docked
617 keyboardFrame = CGRectMake(0, screenHeight - 320, screenWidth, 320);
618 notification = [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
619 object:nil
620 userInfo:@{
621 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
622 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
623 @"UIKeyboardIsLocalUserInfoKey" : @(YES)
624 }];
625 keyboardMode = [viewControllerMock calculateKeyboardAttachMode:notification];
626 XCTAssertTrue(keyboardMode == FlutterKeyboardModeDocked);
627
628 // docked - rounded values
629 CGFloat longDecimalHeight = 320.666666666666666;
630 keyboardFrame = CGRectMake(0, screenHeight - longDecimalHeight, screenWidth, longDecimalHeight);
631 notification = [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
632 object:nil
633 userInfo:@{
634 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
635 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
636 @"UIKeyboardIsLocalUserInfoKey" : @(YES)
637 }];
638 keyboardMode = [viewControllerMock calculateKeyboardAttachMode:notification];
639 XCTAssertTrue(keyboardMode == FlutterKeyboardModeDocked);
640
641 // hidden - rounded values
642 keyboardFrame = CGRectMake(0, screenHeight - .0000001, screenWidth, longDecimalHeight);
643 notification = [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
644 object:nil
645 userInfo:@{
646 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
647 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
648 @"UIKeyboardIsLocalUserInfoKey" : @(YES)
649 }];
650 keyboardMode = [viewControllerMock calculateKeyboardAttachMode:notification];
651 XCTAssertTrue(keyboardMode == FlutterKeyboardModeHidden);
652
653 // hidden
654 keyboardFrame = CGRectMake(0, screenHeight, screenWidth, 320);
655 notification = [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
656 object:nil
657 userInfo:@{
658 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
659 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
660 @"UIKeyboardIsLocalUserInfoKey" : @(YES)
661 }];
662 keyboardMode = [viewControllerMock calculateKeyboardAttachMode:notification];
663 XCTAssertTrue(keyboardMode == FlutterKeyboardModeHidden);
664 }
665
666 - (void)testCalculateMultitaskingAdjustment {
667 FlutterEngine* mockEngine = OCMPartialMock([[FlutterEngine alloc] init]);
668 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
669 FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:mockEngine
670 nibName:nil
671 bundle:nil];
672 FlutterViewController* viewControllerMock = OCMPartialMock(viewController);
673
674 UIScreen* screen = [self setUpMockScreen];
675 CGFloat screenWidth = screen.bounds.size.width;
676 CGFloat screenHeight = screen.bounds.size.height;
677 CGRect screenRect = screen.bounds;
678 CGRect viewOrigFrame = CGRectMake(0, 0, 320, screenHeight - 40);
679 CGRect convertedViewFrame = CGRectMake(20, 20, 320, screenHeight - 40);
680 CGRect keyboardFrame = CGRectMake(20, screenHeight - 320, screenWidth, 300);
681 id mockView = [self setUpMockView:viewControllerMock
682 screen:screen
683 viewFrame:viewOrigFrame
684 convertedFrame:convertedViewFrame];
685 id mockTraitCollection = OCMClassMock([UITraitCollection class]);
686 OCMStub([mockTraitCollection userInterfaceIdiom]).andReturn(UIUserInterfaceIdiomPad);
687 OCMStub([mockTraitCollection horizontalSizeClass]).andReturn(UIUserInterfaceSizeClassCompact);
688 OCMStub([mockTraitCollection verticalSizeClass]).andReturn(UIUserInterfaceSizeClassRegular);
689 OCMStub([mockView traitCollection]).andReturn(mockTraitCollection);
690
691 CGFloat adjustment = [viewControllerMock calculateMultitaskingAdjustment:screenRect
692 keyboardFrame:keyboardFrame];
693 XCTAssertTrue(adjustment == 20);
694 }
695
696 - (void)testCalculateKeyboardInset {
697 FlutterEngine* mockEngine = OCMPartialMock([[FlutterEngine alloc] init]);
698 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
699 FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:mockEngine
700 nibName:nil
701 bundle:nil];
702 FlutterViewController* viewControllerMock = OCMPartialMock(viewController);
703 UIScreen* screen = [self setUpMockScreen];
704 OCMStub([viewControllerMock flutterScreenIfViewLoaded]).andReturn(screen);
705
706 CGFloat screenWidth = screen.bounds.size.width;
707 CGFloat screenHeight = screen.bounds.size.height;
708 CGRect viewOrigFrame = CGRectMake(0, 0, 320, screenHeight - 40);
709 CGRect convertedViewFrame = CGRectMake(20, 20, 320, screenHeight - 40);
710 CGRect keyboardFrame = CGRectMake(20, screenHeight - 320, screenWidth, 300);
711
712 [self setUpMockView:viewControllerMock
713 screen:screen
714 viewFrame:viewOrigFrame
715 convertedFrame:convertedViewFrame];
716
717 CGFloat inset = [viewControllerMock calculateKeyboardInset:keyboardFrame
718 keyboardMode:FlutterKeyboardModeDocked];
719 XCTAssertTrue(inset == 300 * screen.scale);
720 }
721
722 - (void)testHandleKeyboardNotification {
723 FlutterEngine* engine = [[FlutterEngine alloc] init];
724 [engine runWithEntrypoint:nil];
725 FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine
726 nibName:nil
727 bundle:nil];
728 // keyboard is empty
729 UIScreen* screen = [self setUpMockScreen];
730 CGFloat screenWidth = screen.bounds.size.width;
731 CGFloat screenHeight = screen.bounds.size.height;
732 CGRect keyboardFrame = CGRectMake(0, screenHeight - 320, screenWidth, 320);
733 CGRect viewFrame = screen.bounds;
734 BOOL isLocal = YES;
735 NSNotification* notification =
736 [NSNotification notificationWithName:UIKeyboardWillShowNotification
737 object:nil
738 userInfo:@{
739 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
740 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
741 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
742 }];
743 FlutterViewController* viewControllerMock = OCMPartialMock(viewController);
744 [self setUpMockView:viewControllerMock
745 screen:screen
746 viewFrame:viewFrame
747 convertedFrame:viewFrame];
748 viewControllerMock.targetViewInsetBottom = 0;
749 XCTestExpectation* expectation = [self expectationWithDescription:@"update viewport"];
750 OCMStub([viewControllerMock updateViewportMetricsIfNeeded]).andDo(^(NSInvocation* invocation) {
751 [expectation fulfill];
752 });
753
754 [viewControllerMock handleKeyboardNotification:notification];
755 XCTAssertTrue(viewControllerMock.targetViewInsetBottom == 320 * screen.scale);
756 OCMVerify([viewControllerMock startKeyBoardAnimation:0.25]);
757 [self waitForExpectationsWithTimeout:5.0 handler:nil];
758 }
759
760 - (void)testEnsureBottomInsetIsZeroWhenKeyboardDismissed {
761 FlutterEngine* mockEngine = OCMPartialMock([[FlutterEngine alloc] init]);
762 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
763 FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:mockEngine
764 nibName:nil
765 bundle:nil];
766
767 FlutterViewController* viewControllerMock = OCMPartialMock(viewController);
768 CGRect keyboardFrame = CGRectZero;
769 BOOL isLocal = YES;
770 NSNotification* fakeNotification =
771 [NSNotification notificationWithName:UIKeyboardWillHideNotification
772 object:nil
773 userInfo:@{
774 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
775 @"UIKeyboardAnimationDurationUserInfoKey" : @(0.25),
776 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
777 }];
778
779 viewControllerMock.targetViewInsetBottom = 10;
780 [viewControllerMock handleKeyboardNotification:fakeNotification];
781 XCTAssertTrue(viewControllerMock.targetViewInsetBottom == 0);
782 }
783
784 - (void)testEnsureViewportMetricsWillInvokeAndDisplayLinkWillInvalidateInViewDidDisappear {
785 FlutterEngine* mockEngine = OCMPartialMock([[FlutterEngine alloc] init]);
786 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
787 FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:mockEngine
788 nibName:nil
789 bundle:nil];
790 id viewControllerMock = OCMPartialMock(viewController);
791 [viewControllerMock viewDidDisappear:YES];
792 OCMVerify([viewControllerMock ensureViewportMetricsIsCorrect]);
793 OCMVerify([viewControllerMock invalidateKeyboardAnimationVSyncClient]);
794 }
795
796 - (void)testViewDidDisappearDoesntPauseEngineWhenNotTheViewController {
797 id lifecycleChannel = OCMClassMock([FlutterBasicMessageChannel class]);
798 FlutterEnginePartialMock* mockEngine = [[FlutterEnginePartialMock alloc] init];
799 mockEngine.lifecycleChannel = lifecycleChannel;
800 FlutterViewController* viewControllerA =
801 [[FlutterViewController alloc] initWithEngine:self.mockEngine nibName:nil bundle:nil];
802 FlutterViewController* viewControllerB =
803 [[FlutterViewController alloc] initWithEngine:self.mockEngine nibName:nil bundle:nil];
804 id viewControllerMock = OCMPartialMock(viewControllerA);
805 OCMStub([viewControllerMock surfaceUpdated:NO]);
806 mockEngine.viewController = viewControllerB;
807 [viewControllerA viewDidDisappear:NO];
808 OCMReject([lifecycleChannel sendMessage:@"AppLifecycleState.paused"]);
809 OCMReject([viewControllerMock surfaceUpdated:[OCMArg any]]);
810 }
811
812 - (void)testAppWillTerminateViewDidDestroyTheEngine {
813 FlutterEngine* mockEngine = OCMPartialMock([[FlutterEngine alloc] init]);
814 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
815 FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:mockEngine
816 nibName:nil
817 bundle:nil];
818 id viewControllerMock = OCMPartialMock(viewController);
819 OCMStub([viewControllerMock goToApplicationLifecycle:@"AppLifecycleState.detached"]);
820 OCMStub([mockEngine destroyContext]);
821 [viewController applicationWillTerminate:nil];
822 OCMVerify([viewControllerMock goToApplicationLifecycle:@"AppLifecycleState.detached"]);
823 OCMVerify([mockEngine destroyContext]);
824 }
825
826 - (void)testViewDidDisappearDoesPauseEngineWhenIsTheViewController {
827 id lifecycleChannel = OCMClassMock([FlutterBasicMessageChannel class]);
828 FlutterEnginePartialMock* mockEngine = [[FlutterEnginePartialMock alloc] init];
829 mockEngine.lifecycleChannel = lifecycleChannel;
830 __weak FlutterViewController* weakViewController;
831 @autoreleasepool {
832 FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:mockEngine
833 nibName:nil
834 bundle:nil];
835 weakViewController = viewController;
836 id viewControllerMock = OCMPartialMock(viewController);
837 OCMStub([viewControllerMock surfaceUpdated:NO]);
838 [viewController viewDidDisappear:NO];
839 OCMVerify([lifecycleChannel sendMessage:@"AppLifecycleState.paused"]);
840 OCMVerify([viewControllerMock surfaceUpdated:NO]);
841 }
842 XCTAssertNil(weakViewController);
843 }
844
845 - (void)
846 testEngineConfigSyncMethodWillExecuteWhenViewControllerInEngineIsCurrentViewControllerInViewWillAppear {
847 FlutterEngine* mockEngine = OCMPartialMock([[FlutterEngine alloc] init]);
848 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
849 FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:mockEngine
850 nibName:nil
851 bundle:nil];
852 [viewController viewWillAppear:YES];
853 OCMVerify([viewController onUserSettingsChanged:nil]);
854 }
855
856 - (void)
857 testEngineConfigSyncMethodWillNotExecuteWhenViewControllerInEngineIsNotCurrentViewControllerInViewWillAppear {
858 FlutterEngine* mockEngine = OCMPartialMock([[FlutterEngine alloc] init]);
859 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
860 FlutterViewController* viewControllerA = [[FlutterViewController alloc] initWithEngine:mockEngine
861 nibName:nil
862 bundle:nil];
863 mockEngine.viewController = nil;
864 FlutterViewController* viewControllerB = [[FlutterViewController alloc] initWithEngine:mockEngine
865 nibName:nil
866 bundle:nil];
867 mockEngine.viewController = nil;
868 mockEngine.viewController = viewControllerB;
869 [viewControllerA viewWillAppear:YES];
870 OCMVerify(never(), [viewControllerA onUserSettingsChanged:nil]);
871 }
872
873 - (void)
874 testEngineConfigSyncMethodWillExecuteWhenViewControllerInEngineIsCurrentViewControllerInViewDidAppear {
875 FlutterEngine* mockEngine = OCMPartialMock([[FlutterEngine alloc] init]);
876 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
877 FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:mockEngine
878 nibName:nil
879 bundle:nil];
880 [viewController viewDidAppear:YES];
881 OCMVerify([viewController onUserSettingsChanged:nil]);
882 }
883
884 - (void)
885 testEngineConfigSyncMethodWillNotExecuteWhenViewControllerInEngineIsNotCurrentViewControllerInViewDidAppear {
886 FlutterEngine* mockEngine = OCMPartialMock([[FlutterEngine alloc] init]);
887 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
888 FlutterViewController* viewControllerA = [[FlutterViewController alloc] initWithEngine:mockEngine
889 nibName:nil
890 bundle:nil];
891 mockEngine.viewController = nil;
892 FlutterViewController* viewControllerB = [[FlutterViewController alloc] initWithEngine:mockEngine
893 nibName:nil
894 bundle:nil];
895 mockEngine.viewController = nil;
896 mockEngine.viewController = viewControllerB;
897 [viewControllerA viewDidAppear:YES];
898 OCMVerify(never(), [viewControllerA onUserSettingsChanged:nil]);
899 }
900
901 - (void)
902 testEngineConfigSyncMethodWillExecuteWhenViewControllerInEngineIsCurrentViewControllerInViewWillDisappear {
903 id lifecycleChannel = OCMClassMock([FlutterBasicMessageChannel class]);
904 FlutterEnginePartialMock* mockEngine = [[FlutterEnginePartialMock alloc] init];
905 mockEngine.lifecycleChannel = lifecycleChannel;
906 FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:mockEngine
907 nibName:nil
908 bundle:nil];
909 mockEngine.viewController = viewController;
910 [viewController viewWillDisappear:NO];
911 OCMVerify([lifecycleChannel sendMessage:@"AppLifecycleState.inactive"]);
912 }
913
914 - (void)
915 testEngineConfigSyncMethodWillNotExecuteWhenViewControllerInEngineIsNotCurrentViewControllerInViewWillDisappear {
916 id lifecycleChannel = OCMClassMock([FlutterBasicMessageChannel class]);
917 FlutterEnginePartialMock* mockEngine = [[FlutterEnginePartialMock alloc] init];
918 mockEngine.lifecycleChannel = lifecycleChannel;
919 FlutterViewController* viewControllerA = [[FlutterViewController alloc] initWithEngine:mockEngine
920 nibName:nil
921 bundle:nil];
922 FlutterViewController* viewControllerB = [[FlutterViewController alloc] initWithEngine:mockEngine
923 nibName:nil
924 bundle:nil];
925 mockEngine.viewController = viewControllerB;
926 [viewControllerA viewDidDisappear:NO];
927 OCMReject([lifecycleChannel sendMessage:@"AppLifecycleState.inactive"]);
928 }
929
930 - (void)testUpdateViewportMetricsIfNeeded_DoesntInvokeEngineWhenNotTheViewController {
931 FlutterEngine* mockEngine = OCMPartialMock([[FlutterEngine alloc] init]);
932 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
933 FlutterViewController* viewControllerA = [[FlutterViewController alloc] initWithEngine:mockEngine
934 nibName:nil
935 bundle:nil];
936 mockEngine.viewController = nil;
937 FlutterViewController* viewControllerB = [[FlutterViewController alloc] initWithEngine:mockEngine
938 nibName:nil
939 bundle:nil];
940 mockEngine.viewController = viewControllerB;
941 [viewControllerA updateViewportMetricsIfNeeded];
942 flutter::ViewportMetrics viewportMetrics;
943 OCMVerify(never(), [mockEngine updateViewportMetrics:viewportMetrics]);
944 }
945
946 - (void)testUpdateViewportMetricsIfNeeded_DoesInvokeEngineWhenIsTheViewController {
947 FlutterEngine* mockEngine = OCMPartialMock([[FlutterEngine alloc] init]);
948 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
949 FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:mockEngine
950 nibName:nil
951 bundle:nil];
952 mockEngine.viewController = viewController;
953 flutter::ViewportMetrics viewportMetrics;
954 OCMExpect([mockEngine updateViewportMetrics:viewportMetrics]).ignoringNonObjectArgs();
955 [viewController updateViewportMetricsIfNeeded];
956 OCMVerifyAll(mockEngine);
957 }
958
959 - (void)testUpdateViewportMetricsIfNeeded_DoesNotInvokeEngineWhenShouldBeIgnoredDuringRotation {
960 FlutterEngine* mockEngine = OCMPartialMock([[FlutterEngine alloc] init]);
961 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
962 FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:mockEngine
963 nibName:nil
964 bundle:nil];
965 FlutterViewController* viewControllerMock = OCMPartialMock(viewController);
966 UIScreen* screen = [self setUpMockScreen];
967 OCMStub([viewControllerMock flutterScreenIfViewLoaded]).andReturn(screen);
968 mockEngine.viewController = viewController;
969
970 id mockCoordinator = OCMProtocolMock(@protocol(UIViewControllerTransitionCoordinator));
971 OCMStub([mockCoordinator transitionDuration]).andReturn(0.5);
972
973 // Mimic the device rotation.
974 [viewController viewWillTransitionToSize:CGSizeZero withTransitionCoordinator:mockCoordinator];
975 // Should not trigger the engine call when during rotation.
976 [viewController updateViewportMetricsIfNeeded];
977
978 OCMVerify(never(), [mockEngine updateViewportMetrics:flutter::ViewportMetrics()]);
979 }
980
981 - (void)testViewWillTransitionToSize_DoesDelayEngineCallIfNonZeroDuration {
982 FlutterEngine* mockEngine = OCMPartialMock([[FlutterEngine alloc] init]);
983 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
984 FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:mockEngine
985 nibName:nil
986 bundle:nil];
987 FlutterViewController* viewControllerMock = OCMPartialMock(viewController);
988 UIScreen* screen = [self setUpMockScreen];
989 OCMStub([viewControllerMock flutterScreenIfViewLoaded]).andReturn(screen);
990 mockEngine.viewController = viewController;
991
992 // Mimic the device rotation with non-zero transition duration.
993 NSTimeInterval transitionDuration = 0.5;
994 id mockCoordinator = OCMProtocolMock(@protocol(UIViewControllerTransitionCoordinator));
995 OCMStub([mockCoordinator transitionDuration]).andReturn(transitionDuration);
996
997 flutter::ViewportMetrics viewportMetrics;
998 OCMExpect([mockEngine updateViewportMetrics:viewportMetrics]).ignoringNonObjectArgs();
999
1000 [viewController viewWillTransitionToSize:CGSizeZero withTransitionCoordinator:mockCoordinator];
1001 // Should not immediately call the engine (this request should be ignored).
1002 [viewController updateViewportMetricsIfNeeded];
1003 OCMVerify(never(), [mockEngine updateViewportMetrics:flutter::ViewportMetrics()]);
1004
1005 // Should delay the engine call for half of the transition duration.
1006 // Wait for additional transitionDuration to allow updateViewportMetrics calls if any.
1007 XCTWaiterResult result = [XCTWaiter
1008 waitForExpectations:@[ [self expectationWithDescription:@"Waiting for rotation duration"] ]
1009 timeout:transitionDuration];
1010 XCTAssertEqual(result, XCTWaiterResultTimedOut);
1011
1012 OCMVerifyAll(mockEngine);
1013 }
1014
1015 - (void)testViewWillTransitionToSize_DoesNotDelayEngineCallIfZeroDuration {
1016 FlutterEngine* mockEngine = OCMPartialMock([[FlutterEngine alloc] init]);
1017 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1018 FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:mockEngine
1019 nibName:nil
1020 bundle:nil];
1021 FlutterViewController* viewControllerMock = OCMPartialMock(viewController);
1022 UIScreen* screen = [self setUpMockScreen];
1023 OCMStub([viewControllerMock flutterScreenIfViewLoaded]).andReturn(screen);
1024 mockEngine.viewController = viewController;
1025
1026 // Mimic the device rotation with zero transition duration.
1027 id mockCoordinator = OCMProtocolMock(@protocol(UIViewControllerTransitionCoordinator));
1028 OCMStub([mockCoordinator transitionDuration]).andReturn(0);
1029
1030 flutter::ViewportMetrics viewportMetrics;
1031 OCMExpect([mockEngine updateViewportMetrics:viewportMetrics]).ignoringNonObjectArgs();
1032
1033 // Should immediately trigger the engine call, without delay.
1034 [viewController viewWillTransitionToSize:CGSizeZero withTransitionCoordinator:mockCoordinator];
1035 [viewController updateViewportMetricsIfNeeded];
1036
1037 OCMVerifyAll(mockEngine);
1038 }
1039
1040 - (void)testViewDidLoadDoesntInvokeEngineWhenNotTheViewController {
1041 FlutterEngine* mockEngine = OCMPartialMock([[FlutterEngine alloc] init]);
1042 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1043 FlutterViewController* viewControllerA = [[FlutterViewController alloc] initWithEngine:mockEngine
1044 nibName:nil
1045 bundle:nil];
1046 mockEngine.viewController = nil;
1047 FlutterViewController* viewControllerB = [[FlutterViewController alloc] initWithEngine:mockEngine
1048 nibName:nil
1049 bundle:nil];
1050 mockEngine.viewController = viewControllerB;
1051 UIView* view = viewControllerA.view;
1052 XCTAssertNotNil(view);
1053 OCMVerify(never(), [mockEngine attachView]);
1054 }
1055
1056 - (void)testViewDidLoadDoesInvokeEngineWhenIsTheViewController {
1057 FlutterEngine* mockEngine = OCMPartialMock([[FlutterEngine alloc] init]);
1058 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1059 mockEngine.viewController = nil;
1060 FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:mockEngine
1061 nibName:nil
1062 bundle:nil];
1063 mockEngine.viewController = viewController;
1064 UIView* view = viewController.view;
1065 XCTAssertNotNil(view);
1066 OCMVerify(times(1), [mockEngine attachView]);
1067 }
1068
1069 - (void)testViewDidLoadDoesntInvokeEngineAttachViewWhenEngineNeedsLaunch {
1070 FlutterEngine* mockEngine = OCMPartialMock([[FlutterEngine alloc] init]);
1071 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1072 mockEngine.viewController = nil;
1073 FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:mockEngine
1074 nibName:nil
1075 bundle:nil];
1076 // sharedSetupWithProject sets the engine needs to be launched.
1077 [viewController sharedSetupWithProject:nil initialRoute:nil];
1078 mockEngine.viewController = viewController;
1079 UIView* view = viewController.view;
1080 XCTAssertNotNil(view);
1081 OCMVerify(never(), [mockEngine attachView]);
1082 }
1083
1084 - (void)testSplashScreenViewRemoveNotCrash {
1085 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"engine" project:nil];
1086 [engine runWithEntrypoint:nil];
1087 FlutterViewController* flutterViewController =
1088 [[FlutterViewController alloc] initWithEngine:engine nibName:nil bundle:nil];
1089 [flutterViewController setSplashScreenView:[[UIView alloc] init]];
1090 [flutterViewController setSplashScreenView:nil];
1091 }
1092
1093 - (void)testInternalPluginsWeakPtrNotCrash {
1094 FlutterSendKeyEvent sendEvent;
1095 @autoreleasepool {
1096 FlutterViewController* vc = [[FlutterViewController alloc] initWithProject:nil
1097 nibName:nil
1098 bundle:nil];
1099 [vc addInternalPlugins];
1100 FlutterKeyboardManager* keyboardManager = vc.keyboardManager;
1101 FlutterEmbedderKeyResponder* keyPrimaryResponder = (FlutterEmbedderKeyResponder*)
1102 [(NSArray<id<FlutterKeyPrimaryResponder>>*)keyboardManager.primaryResponders firstObject];
1103 sendEvent = [keyPrimaryResponder sendEvent];
1104 }
1105
1106 if (sendEvent) {
1107 sendEvent({}, nil, nil);
1108 }
1109 }
1110
1111 // Regression test for https://github.com/flutter/engine/pull/32098.
1112 - (void)testInternalPluginsInvokeInViewDidLoad {
1113 FlutterEngine* mockEngine = OCMPartialMock([[FlutterEngine alloc] init]);
1114 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1115 FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:mockEngine
1116 nibName:nil
1117 bundle:nil];
1118 UIView* view = viewController.view;
1119 // The implementation in viewDidLoad requires the viewControllers.viewLoaded is true.
1120 // Accessing the view to make sure the view loads in the memory,
1121 // which makes viewControllers.viewLoaded true.
1122 XCTAssertNotNil(view);
1123 [viewController viewDidLoad];
1124 OCMVerify([viewController addInternalPlugins]);
1125 }
1126
1127 - (void)testBinaryMessenger {
1128 FlutterViewController* vc = [[FlutterViewController alloc] initWithEngine:self.mockEngine
1129 nibName:nil
1130 bundle:nil];
1131 XCTAssertNotNil(vc);
1132 id messenger = OCMProtocolMock(@protocol(FlutterBinaryMessenger));
1133 OCMStub([self.mockEngine binaryMessenger]).andReturn(messenger);
1134 XCTAssertEqual(vc.binaryMessenger, messenger);
1135 OCMVerify([self.mockEngine binaryMessenger]);
1136 }
1137
1138 - (void)testViewControllerIsReleased {
1139 __weak FlutterViewController* weakViewController;
1140 __weak UIView* weakView;
1141 @autoreleasepool {
1142 FlutterEngine* engine = [[FlutterEngine alloc] init];
1143
1144 [engine runWithEntrypoint:nil];
1145 FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine
1146 nibName:nil
1147 bundle:nil];
1148 weakViewController = viewController;
1149 [viewController viewDidLoad];
1150 weakView = viewController.view;
1151 XCTAssertTrue([viewController.view isKindOfClass:[FlutterView class]]);
1152 }
1153 XCTAssertNil(weakViewController);
1154 XCTAssertNil(weakView);
1155 }
1156
1157 #pragma mark - Platform Brightness
1158
1159 - (void)testItReportsLightPlatformBrightnessByDefault {
1160 // Setup test.
1161 id settingsChannel = OCMClassMock([FlutterBasicMessageChannel class]);
1162 OCMStub([self.mockEngine settingsChannel]).andReturn(settingsChannel);
1163
1164 FlutterViewController* vc = [[FlutterViewController alloc] initWithEngine:self.mockEngine
1165 nibName:nil
1166 bundle:nil];
1167
1168 // Exercise behavior under test.
1169 [vc traitCollectionDidChange:nil];
1170
1171 // Verify behavior.
1172 OCMVerify([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(id message) {
1173 return [message[@"platformBrightness"] isEqualToString:@"light"];
1174 }]]);
1175
1176 // Clean up mocks
1177 [settingsChannel stopMocking];
1178 }
1179
1180 - (void)testItReportsPlatformBrightnessWhenViewWillAppear {
1181 // Setup test.
1182 id settingsChannel = OCMClassMock([FlutterBasicMessageChannel class]);
1183 FlutterEngine* mockEngine = OCMPartialMock([[FlutterEngine alloc] init]);
1184 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1185 OCMStub([mockEngine settingsChannel]).andReturn(settingsChannel);
1186 FlutterViewController* vc = [[FlutterViewController alloc] initWithEngine:mockEngine
1187 nibName:nil
1188 bundle:nil];
1189
1190 // Exercise behavior under test.
1191 [vc viewWillAppear:false];
1192
1193 // Verify behavior.
1194 OCMVerify([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(id message) {
1195 return [message[@"platformBrightness"] isEqualToString:@"light"];
1196 }]]);
1197
1198 // Clean up mocks
1199 [settingsChannel stopMocking];
1200 }
1201
1202 - (void)testItReportsDarkPlatformBrightnessWhenTraitCollectionRequestsIt {
1203 if (@available(iOS 13, *)) {
1204 // noop
1205 } else {
1206 return;
1207 }
1208
1209 // Setup test.
1210 id settingsChannel = OCMClassMock([FlutterBasicMessageChannel class]);
1211 OCMStub([self.mockEngine settingsChannel]).andReturn(settingsChannel);
1212 id mockTraitCollection =
1213 [self fakeTraitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleDark];
1214
1215 // We partially mock the real FlutterViewController to act as the OS and report
1216 // the UITraitCollection of our choice. Mocking the object under test is not
1217 // desirable, but given that the OS does not offer a DI approach to providing
1218 // our own UITraitCollection, this seems to be the least bad option.
1219 id partialMockVC = OCMPartialMock([[FlutterViewController alloc] initWithEngine:self.mockEngine
1220 nibName:nil
1221 bundle:nil]);
1222 OCMStub([partialMockVC traitCollection]).andReturn(mockTraitCollection);
1223
1224 // Exercise behavior under test.
1225 [partialMockVC traitCollectionDidChange:nil];
1226
1227 // Verify behavior.
1228 OCMVerify([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(id message) {
1229 return [message[@"platformBrightness"] isEqualToString:@"dark"];
1230 }]]);
1231
1232 // Clean up mocks
1233 [partialMockVC stopMocking];
1234 [settingsChannel stopMocking];
1235 [mockTraitCollection stopMocking];
1236 }
1237
1238 // Creates a mocked UITraitCollection with nil values for everything except userInterfaceStyle,
1239 // which is set to the given "style".
1240 - (UITraitCollection*)fakeTraitCollectionWithUserInterfaceStyle:(UIUserInterfaceStyle)style {
1241 id mockTraitCollection = OCMClassMock([UITraitCollection class]);
1242 OCMStub([mockTraitCollection userInterfaceStyle]).andReturn(style);
1243 return mockTraitCollection;
1244 }
1245
1246 #pragma mark - Platform Contrast
1247
1248 - (void)testItReportsNormalPlatformContrastByDefault {
1249 if (@available(iOS 13, *)) {
1250 // noop
1251 } else {
1252 return;
1253 }
1254
1255 // Setup test.
1256 id settingsChannel = OCMClassMock([FlutterBasicMessageChannel class]);
1257 OCMStub([self.mockEngine settingsChannel]).andReturn(settingsChannel);
1258
1259 FlutterViewController* vc = [[FlutterViewController alloc] initWithEngine:self.mockEngine
1260 nibName:nil
1261 bundle:nil];
1262
1263 // Exercise behavior under test.
1264 [vc traitCollectionDidChange:nil];
1265
1266 // Verify behavior.
1267 OCMVerify([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(id message) {
1268 return [message[@"platformContrast"] isEqualToString:@"normal"];
1269 }]]);
1270
1271 // Clean up mocks
1272 [settingsChannel stopMocking];
1273 }
1274
1275 - (void)testItReportsPlatformContrastWhenViewWillAppear {
1276 if (@available(iOS 13, *)) {
1277 // noop
1278 } else {
1279 return;
1280 }
1281 FlutterEngine* mockEngine = OCMPartialMock([[FlutterEngine alloc] init]);
1282 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1283
1284 // Setup test.
1285 id settingsChannel = OCMClassMock([FlutterBasicMessageChannel class]);
1286 OCMStub([mockEngine settingsChannel]).andReturn(settingsChannel);
1287 FlutterViewController* vc = [[FlutterViewController alloc] initWithEngine:mockEngine
1288 nibName:nil
1289 bundle:nil];
1290
1291 // Exercise behavior under test.
1292 [vc viewWillAppear:false];
1293
1294 // Verify behavior.
1295 OCMVerify([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(id message) {
1296 return [message[@"platformContrast"] isEqualToString:@"normal"];
1297 }]]);
1298
1299 // Clean up mocks
1300 [settingsChannel stopMocking];
1301 }
1302
1303 - (void)testItReportsHighContrastWhenTraitCollectionRequestsIt {
1304 if (@available(iOS 13, *)) {
1305 // noop
1306 } else {
1307 return;
1308 }
1309
1310 // Setup test.
1311 id settingsChannel = OCMClassMock([FlutterBasicMessageChannel class]);
1312 OCMStub([self.mockEngine settingsChannel]).andReturn(settingsChannel);
1313
1314 id mockTraitCollection = [self fakeTraitCollectionWithContrast:UIAccessibilityContrastHigh];
1315
1316 // We partially mock the real FlutterViewController to act as the OS and report
1317 // the UITraitCollection of our choice. Mocking the object under test is not
1318 // desirable, but given that the OS does not offer a DI approach to providing
1319 // our own UITraitCollection, this seems to be the least bad option.
1320 id partialMockVC = OCMPartialMock([[FlutterViewController alloc] initWithEngine:self.mockEngine
1321 nibName:nil
1322 bundle:nil]);
1323 OCMStub([partialMockVC traitCollection]).andReturn(mockTraitCollection);
1324
1325 // Exercise behavior under test.
1326 [partialMockVC traitCollectionDidChange:mockTraitCollection];
1327
1328 // Verify behavior.
1329 OCMVerify([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(id message) {
1330 return [message[@"platformContrast"] isEqualToString:@"high"];
1331 }]]);
1332
1333 // Clean up mocks
1334 [partialMockVC stopMocking];
1335 [settingsChannel stopMocking];
1336 [mockTraitCollection stopMocking];
1337 }
1338
1339 - (void)testItReportsAlwaysUsed24HourFormat {
1340 // Setup test.
1341 id settingsChannel = OCMStrictClassMock([FlutterBasicMessageChannel class]);
1342 OCMStub([self.mockEngine settingsChannel]).andReturn(settingsChannel);
1343 FlutterViewController* vc = [[FlutterViewController alloc] initWithEngine:self.mockEngine
1344 nibName:nil
1345 bundle:nil];
1346 // Test the YES case.
1347 id mockHourFormat = OCMClassMock([FlutterHourFormat class]);
1348 OCMStub([mockHourFormat isAlwaysUse24HourFormat]).andReturn(YES);
1349 OCMExpect([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(id message) {
1350 return [message[@"alwaysUse24HourFormat"] isEqual:@(YES)];
1351 }]]);
1352 [vc onUserSettingsChanged:nil];
1353 [mockHourFormat stopMocking];
1354
1355 // Test the NO case.
1356 mockHourFormat = OCMClassMock([FlutterHourFormat class]);
1357 OCMStub([mockHourFormat isAlwaysUse24HourFormat]).andReturn(NO);
1358 OCMExpect([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(id message) {
1359 return [message[@"alwaysUse24HourFormat"] isEqual:@(NO)];
1360 }]]);
1361 [vc onUserSettingsChanged:nil];
1362 [mockHourFormat stopMocking];
1363
1364 // Clean up mocks.
1365 [settingsChannel stopMocking];
1366 }
1367
1368 - (void)testItReportsAccessibilityOnOffSwitchLabelsFlagNotSet {
1369 if (@available(iOS 13, *)) {
1370 // noop
1371 } else {
1372 return;
1373 }
1374
1375 // Setup test.
1376 FlutterViewController* viewController =
1377 [[FlutterViewController alloc] initWithEngine:self.mockEngine nibName:nil bundle:nil];
1378 id partialMockViewController = OCMPartialMock(viewController);
1379 OCMStub([partialMockViewController accessibilityIsOnOffSwitchLabelsEnabled]).andReturn(NO);
1380
1381 // Exercise behavior under test.
1382 int32_t flags = [partialMockViewController accessibilityFlags];
1383
1384 // Verify behavior.
1385 XCTAssert((flags & (int32_t)flutter::AccessibilityFeatureFlag::kOnOffSwitchLabels) == 0);
1386 }
1387
1388 - (void)testItReportsAccessibilityOnOffSwitchLabelsFlagSet {
1389 if (@available(iOS 13, *)) {
1390 // noop
1391 } else {
1392 return;
1393 }
1394
1395 // Setup test.
1396 FlutterViewController* viewController =
1397 [[FlutterViewController alloc] initWithEngine:self.mockEngine nibName:nil bundle:nil];
1398 id partialMockViewController = OCMPartialMock(viewController);
1399 OCMStub([partialMockViewController accessibilityIsOnOffSwitchLabelsEnabled]).andReturn(YES);
1400
1401 // Exercise behavior under test.
1402 int32_t flags = [partialMockViewController accessibilityFlags];
1403
1404 // Verify behavior.
1405 XCTAssert((flags & (int32_t)flutter::AccessibilityFeatureFlag::kOnOffSwitchLabels) != 0);
1406 }
1407
1408 - (void)testAccessibilityPerformEscapePopsRoute {
1409 FlutterEngine* mockEngine = OCMPartialMock([[FlutterEngine alloc] init]);
1410 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1411 id mockNavigationChannel = OCMClassMock([FlutterMethodChannel class]);
1412 OCMStub([mockEngine navigationChannel]).andReturn(mockNavigationChannel);
1413
1414 FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:mockEngine
1415 nibName:nil
1416 bundle:nil];
1417 XCTAssertTrue([viewController accessibilityPerformEscape]);
1418
1419 OCMVerify([mockNavigationChannel invokeMethod:@"popRoute" arguments:nil]);
1420
1421 [mockNavigationChannel stopMocking];
1422 }
1423
1424 - (void)testPerformOrientationUpdateForcesOrientationChange {
1425 [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortrait
1426 currentOrientation:UIInterfaceOrientationLandscapeLeft
1427 didChangeOrientation:YES
1428 resultingOrientation:UIInterfaceOrientationPortrait];
1429
1430 [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortrait
1431 currentOrientation:UIInterfaceOrientationLandscapeRight
1432 didChangeOrientation:YES
1433 resultingOrientation:UIInterfaceOrientationPortrait];
1434
1435 [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortrait
1436 currentOrientation:UIInterfaceOrientationPortraitUpsideDown
1437 didChangeOrientation:YES
1438 resultingOrientation:UIInterfaceOrientationPortrait];
1439
1440 [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortraitUpsideDown
1441 currentOrientation:UIInterfaceOrientationLandscapeLeft
1442 didChangeOrientation:YES
1443 resultingOrientation:UIInterfaceOrientationPortraitUpsideDown];
1444
1445 [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortraitUpsideDown
1446 currentOrientation:UIInterfaceOrientationLandscapeRight
1447 didChangeOrientation:YES
1448 resultingOrientation:UIInterfaceOrientationPortraitUpsideDown];
1449
1450 [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortraitUpsideDown
1451 currentOrientation:UIInterfaceOrientationPortrait
1452 didChangeOrientation:YES
1453 resultingOrientation:UIInterfaceOrientationPortraitUpsideDown];
1454
1455 [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscape
1456 currentOrientation:UIInterfaceOrientationPortrait
1457 didChangeOrientation:YES
1458 resultingOrientation:UIInterfaceOrientationLandscapeLeft];
1459
1460 [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscape
1461 currentOrientation:UIInterfaceOrientationPortraitUpsideDown
1462 didChangeOrientation:YES
1463 resultingOrientation:UIInterfaceOrientationLandscapeLeft];
1464
1465 [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeLeft
1466 currentOrientation:UIInterfaceOrientationPortrait
1467 didChangeOrientation:YES
1468 resultingOrientation:UIInterfaceOrientationLandscapeLeft];
1469
1470 [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeLeft
1471 currentOrientation:UIInterfaceOrientationLandscapeRight
1472 didChangeOrientation:YES
1473 resultingOrientation:UIInterfaceOrientationLandscapeLeft];
1474
1475 [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeLeft
1476 currentOrientation:UIInterfaceOrientationPortraitUpsideDown
1477 didChangeOrientation:YES
1478 resultingOrientation:UIInterfaceOrientationLandscapeLeft];
1479
1480 [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeRight
1481 currentOrientation:UIInterfaceOrientationPortrait
1482 didChangeOrientation:YES
1483 resultingOrientation:UIInterfaceOrientationLandscapeRight];
1484
1485 [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeRight
1486 currentOrientation:UIInterfaceOrientationLandscapeLeft
1487 didChangeOrientation:YES
1488 resultingOrientation:UIInterfaceOrientationLandscapeRight];
1489
1490 [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeRight
1491 currentOrientation:UIInterfaceOrientationPortraitUpsideDown
1492 didChangeOrientation:YES
1493 resultingOrientation:UIInterfaceOrientationLandscapeRight];
1494
1495 [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAllButUpsideDown
1496 currentOrientation:UIInterfaceOrientationPortraitUpsideDown
1497 didChangeOrientation:YES
1498 resultingOrientation:UIInterfaceOrientationPortrait];
1499 }
1500
1501 - (void)testPerformOrientationUpdateDoesNotForceOrientationChange {
1502 [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAll
1503 currentOrientation:UIInterfaceOrientationPortrait
1504 didChangeOrientation:NO
1505 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1506
1507 [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAll
1508 currentOrientation:UIInterfaceOrientationPortraitUpsideDown
1509 didChangeOrientation:NO
1510 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1511
1512 [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAll
1513 currentOrientation:UIInterfaceOrientationLandscapeLeft
1514 didChangeOrientation:NO
1515 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1516
1517 [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAll
1518 currentOrientation:UIInterfaceOrientationLandscapeRight
1519 didChangeOrientation:NO
1520 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1521
1522 [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAllButUpsideDown
1523 currentOrientation:UIInterfaceOrientationPortrait
1524 didChangeOrientation:NO
1525 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1526
1527 [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAllButUpsideDown
1528 currentOrientation:UIInterfaceOrientationLandscapeLeft
1529 didChangeOrientation:NO
1530 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1531
1532 [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAllButUpsideDown
1533 currentOrientation:UIInterfaceOrientationLandscapeRight
1534 didChangeOrientation:NO
1535 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1536
1537 [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortrait
1538 currentOrientation:UIInterfaceOrientationPortrait
1539 didChangeOrientation:NO
1540 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1541
1542 [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortraitUpsideDown
1543 currentOrientation:UIInterfaceOrientationPortraitUpsideDown
1544 didChangeOrientation:NO
1545 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1546
1547 [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscape
1548 currentOrientation:UIInterfaceOrientationLandscapeLeft
1549 didChangeOrientation:NO
1550 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1551
1552 [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscape
1553 currentOrientation:UIInterfaceOrientationLandscapeRight
1554 didChangeOrientation:NO
1555 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1556
1557 [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeLeft
1558 currentOrientation:UIInterfaceOrientationLandscapeLeft
1559 didChangeOrientation:NO
1560 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1561
1562 [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeRight
1563 currentOrientation:UIInterfaceOrientationLandscapeRight
1564 didChangeOrientation:NO
1565 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1566 }
1567
1568 // Perform an orientation update test that fails when the expected outcome
1569 // for an orientation update is not met
1570 - (void)orientationTestWithOrientationUpdate:(UIInterfaceOrientationMask)mask
1571 currentOrientation:(UIInterfaceOrientation)currentOrientation
1572 didChangeOrientation:(BOOL)didChange
1573 resultingOrientation:(UIInterfaceOrientation)resultingOrientation {
1574 id mockApplication = OCMClassMock([UIApplication class]);
1575 id mockWindowScene;
1576 id deviceMock;
1577 id mockVC;
1578 __block __weak id weakPreferences;
1579 @autoreleasepool {
1580 FlutterViewController* realVC = [[FlutterViewController alloc] initWithEngine:self.mockEngine
1581 nibName:nil
1582 bundle:nil];
1583
1584 if (@available(iOS 16.0, *)) {
1585 mockWindowScene = OCMClassMock([UIWindowScene class]);
1586 mockVC = OCMPartialMock(realVC);
1587 OCMStub([mockVC flutterWindowSceneIfViewLoaded]).andReturn(mockWindowScene);
1588 if (realVC.supportedInterfaceOrientations == mask) {
1589 OCMReject([mockWindowScene requestGeometryUpdateWithPreferences:[OCMArg any]
1590 errorHandler:[OCMArg any]]);
1591 } else {
1592 // iOS 16 will decide whether to rotate based on the new preference, so always set it
1593 // when it changes.
1594 OCMExpect([mockWindowScene
1595 requestGeometryUpdateWithPreferences:[OCMArg checkWithBlock:^BOOL(
1596 UIWindowSceneGeometryPreferencesIOS*
1597 preferences) {
1598 weakPreferences = preferences;
1599 return preferences.interfaceOrientations == mask;
1600 }]
1601 errorHandler:[OCMArg any]]);
1602 }
1603 OCMStub([mockApplication sharedApplication]).andReturn(mockApplication);
1604 OCMStub([mockApplication connectedScenes]).andReturn([NSSet setWithObject:mockWindowScene]);
1605 } else {
1606 deviceMock = OCMPartialMock([UIDevice currentDevice]);
1607 if (!didChange) {
1608 OCMReject([deviceMock setValue:[OCMArg any] forKey:@"orientation"]);
1609 } else {
1610 OCMExpect([deviceMock setValue:@(resultingOrientation) forKey:@"orientation"]);
1611 }
1612 if (@available(iOS 13.0, *)) {
1613 mockWindowScene = OCMClassMock([UIWindowScene class]);
1614 mockVC = OCMPartialMock(realVC);
1615 OCMStub([mockVC flutterWindowSceneIfViewLoaded]).andReturn(mockWindowScene);
1616 OCMStub(((UIWindowScene*)mockWindowScene).interfaceOrientation)
1617 .andReturn(currentOrientation);
1618 } else {
1619 OCMStub([mockApplication sharedApplication]).andReturn(mockApplication);
1620 OCMStub([mockApplication statusBarOrientation]).andReturn(currentOrientation);
1621 }
1622 }
1623
1624 [realVC performOrientationUpdate:mask];
1625 if (@available(iOS 16.0, *)) {
1626 OCMVerifyAll(mockWindowScene);
1627 } else {
1628 OCMVerifyAll(deviceMock);
1629 }
1630 }
1631 [mockWindowScene stopMocking];
1632 [deviceMock stopMocking];
1633 [mockApplication stopMocking];
1634 XCTAssertNil(weakPreferences);
1635 }
1636
1637 // Creates a mocked UITraitCollection with nil values for everything except accessibilityContrast,
1638 // which is set to the given "contrast".
1639 - (UITraitCollection*)fakeTraitCollectionWithContrast:(UIAccessibilityContrast)contrast {
1640 id mockTraitCollection = OCMClassMock([UITraitCollection class]);
1641 OCMStub([mockTraitCollection accessibilityContrast]).andReturn(contrast);
1642 return mockTraitCollection;
1643 }
1644
1645 - (void)testWillDeallocNotification {
1646 XCTestExpectation* expectation =
1647 [[XCTestExpectation alloc] initWithDescription:@"notification called"];
1648 id engine = [[MockEngine alloc] init];
1649 @autoreleasepool {
1650 // NOLINTNEXTLINE(clang-analyzer-deadcode.DeadStores)
1651 FlutterViewController* realVC = [[FlutterViewController alloc] initWithEngine:engine
1652 nibName:nil
1653 bundle:nil];
1654 [[NSNotificationCenter defaultCenter] addObserverForName:FlutterViewControllerWillDealloc
1655 object:nil
1656 queue:[NSOperationQueue mainQueue]
1657 usingBlock:^(NSNotification* _Nonnull note) {
1658 [expectation fulfill];
1659 }];
1660 XCTAssertNotNil(realVC);
1661 realVC = nil;
1662 }
1663 [self waitForExpectations:@[ expectation ] timeout:1.0];
1664 }
1665
1666 - (void)testReleasesKeyboardManagerOnDealloc {
1667 __weak FlutterKeyboardManager* weakKeyboardManager = nil;
1668 @autoreleasepool {
1669 FlutterViewController* viewController = [[FlutterViewController alloc] init];
1670
1671 [viewController addInternalPlugins];
1672 weakKeyboardManager = viewController.keyboardManager;
1673 XCTAssertNotNil(weakKeyboardManager);
1674 [viewController deregisterNotifications];
1675 viewController = nil;
1676 }
1677 // View controller has released the keyboard manager.
1678 XCTAssertNil(weakKeyboardManager);
1679 }
1680
1681 - (void)testDoesntLoadViewInInit {
1682 FlutterDartProject* project = [[FlutterDartProject alloc] init];
1683 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
1684 [engine createShell:@"" libraryURI:@"" initialRoute:nil];
1685 FlutterViewController* realVC = [[FlutterViewController alloc] initWithEngine:engine
1686 nibName:nil
1687 bundle:nil];
1688 XCTAssertFalse([realVC isViewLoaded], @"shouldn't have loaded since it hasn't been shown");
1689 engine.viewController = nil;
1690 }
1691
1692 - (void)testHideOverlay {
1693 FlutterDartProject* project = [[FlutterDartProject alloc] init];
1694 FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
1695 [engine createShell:@"" libraryURI:@"" initialRoute:nil];
1696 FlutterViewController* realVC = [[FlutterViewController alloc] initWithEngine:engine
1697 nibName:nil
1698 bundle:nil];
1699 XCTAssertFalse(realVC.prefersHomeIndicatorAutoHidden, @"");
1700 [[NSNotificationCenter defaultCenter] postNotificationName:FlutterViewControllerHideHomeIndicator
1701 object:nil];
1702 XCTAssertTrue(realVC.prefersHomeIndicatorAutoHidden, @"");
1703 engine.viewController = nil;
1704 }
1705
1706 - (void)testNotifyLowMemory {
1707 FlutterEnginePartialMock* mockEngine = [[FlutterEnginePartialMock alloc] init];
1708 FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:mockEngine
1709 nibName:nil
1710 bundle:nil];
1711 id viewControllerMock = OCMPartialMock(viewController);
1712 OCMStub([viewControllerMock surfaceUpdated:NO]);
1713 [viewController beginAppearanceTransition:NO animated:NO];
1714 [viewController endAppearanceTransition];
1715 XCTAssertTrue(mockEngine.didCallNotifyLowMemory);
1716 }
1717
1718 - (void)sendMessage:(id _Nullable)message reply:(FlutterReply _Nullable)callback {
1719 NSMutableDictionary* replyMessage = [@{
1720 @"handled" : @YES,
1721 } mutableCopy];
1722 // Response is async, so we have to post it to the run loop instead of calling
1723 // it directly.
1724 self.messageSent = message;
1725 CFRunLoopPerformBlock(CFRunLoopGetCurrent(), fml::MessageLoopDarwin::kMessageLoopCFRunLoopMode,
1726 ^() {
1727 callback(replyMessage);
1728 });
1729 }
1730
1731 - (void)testValidKeyUpEvent API_AVAILABLE(ios(13.4)) {
1732 if (@available(iOS 13.4, *)) {
1733 // noop
1734 } else {
1735 return;
1736 }
1737 FlutterEnginePartialMock* mockEngine = [[FlutterEnginePartialMock alloc] init];
1738 mockEngine.keyEventChannel = OCMClassMock([FlutterBasicMessageChannel class]);
1739 OCMStub([mockEngine.keyEventChannel sendMessage:[OCMArg any] reply:[OCMArg any]])
1740 .andCall(self, @selector(sendMessage:reply:));
1741 OCMStub([self.mockTextInputPlugin handlePress:[OCMArg any]]).andReturn(YES);
1742 mockEngine.textInputPlugin = self.mockTextInputPlugin;
1743
1744 FlutterViewController* vc = [[FlutterViewController alloc] initWithEngine:mockEngine
1745 nibName:nil
1746 bundle:nil];
1747
1748 // Allocate the keyboard manager in the view controller by adding the internal
1749 // plugins.
1750 [vc addInternalPlugins];
1751
1752 [vc handlePressEvent:keyUpEvent(UIKeyboardHIDUsageKeyboardA, UIKeyModifierShift, 123.0)
1753 nextAction:^(){
1754 }];
1755
1756 XCTAssert(self.messageSent != nil);
1757 XCTAssert([self.messageSent[@"keymap"] isEqualToString:@"ios"]);
1758 XCTAssert([self.messageSent[@"type"] isEqualToString:@"keyup"]);
1759 XCTAssert([self.messageSent[@"keyCode"] isEqualToNumber:[NSNumber numberWithInt:4]]);
1760 XCTAssert([self.messageSent[@"modifiers"] isEqualToNumber:[NSNumber numberWithInt:0]]);
1761 XCTAssert([self.messageSent[@"characters"] isEqualToString:@""]);
1762 XCTAssert([self.messageSent[@"charactersIgnoringModifiers"] isEqualToString:@""]);
1763 [vc deregisterNotifications];
1764 }
1765
1766 - (void)testValidKeyDownEvent API_AVAILABLE(ios(13.4)) {
1767 if (@available(iOS 13.4, *)) {
1768 // noop
1769 } else {
1770 return;
1771 }
1772
1773 FlutterEnginePartialMock* mockEngine = [[FlutterEnginePartialMock alloc] init];
1774 mockEngine.keyEventChannel = OCMClassMock([FlutterBasicMessageChannel class]);
1775 OCMStub([mockEngine.keyEventChannel sendMessage:[OCMArg any] reply:[OCMArg any]])
1776 .andCall(self, @selector(sendMessage:reply:));
1777 OCMStub([self.mockTextInputPlugin handlePress:[OCMArg any]]).andReturn(YES);
1778 mockEngine.textInputPlugin = self.mockTextInputPlugin;
1779
1780 __strong FlutterViewController* vc = [[FlutterViewController alloc] initWithEngine:mockEngine
1781 nibName:nil
1782 bundle:nil];
1783 // Allocate the keyboard manager in the view controller by adding the internal
1784 // plugins.
1785 [vc addInternalPlugins];
1786
1787 [vc handlePressEvent:keyDownEvent(UIKeyboardHIDUsageKeyboardA, UIKeyModifierShift, 123.0f, "A",
1788 "a")
1789 nextAction:^(){
1790 }];
1791
1792 XCTAssert(self.messageSent != nil);
1793 XCTAssert([self.messageSent[@"keymap"] isEqualToString:@"ios"]);
1794 XCTAssert([self.messageSent[@"type"] isEqualToString:@"keydown"]);
1795 XCTAssert([self.messageSent[@"keyCode"] isEqualToNumber:[NSNumber numberWithInt:4]]);
1796 XCTAssert([self.messageSent[@"modifiers"] isEqualToNumber:[NSNumber numberWithInt:0]]);
1797 XCTAssert([self.messageSent[@"characters"] isEqualToString:@"A"]);
1798 XCTAssert([self.messageSent[@"charactersIgnoringModifiers"] isEqualToString:@"a"]);
1799 [vc deregisterNotifications];
1800 vc = nil;
1801 }
1802
1803 - (void)testIgnoredKeyEvents API_AVAILABLE(ios(13.4)) {
1804 if (@available(iOS 13.4, *)) {
1805 // noop
1806 } else {
1807 return;
1808 }
1809 id keyEventChannel = OCMClassMock([FlutterBasicMessageChannel class]);
1810 OCMStub([keyEventChannel sendMessage:[OCMArg any] reply:[OCMArg any]])
1811 .andCall(self, @selector(sendMessage:reply:));
1812 OCMStub([self.mockTextInputPlugin handlePress:[OCMArg any]]).andReturn(YES);
1813 OCMStub([self.mockEngine keyEventChannel]).andReturn(keyEventChannel);
1814
1815 FlutterViewController* vc = [[FlutterViewController alloc] initWithEngine:self.mockEngine
1816 nibName:nil
1817 bundle:nil];
1818
1819 // Allocate the keyboard manager in the view controller by adding the internal
1820 // plugins.
1821 [vc addInternalPlugins];
1822
1823 [vc handlePressEvent:keyEventWithPhase(UIPressPhaseStationary, UIKeyboardHIDUsageKeyboardA,
1824 UIKeyModifierShift, 123.0)
1825 nextAction:^(){
1826 }];
1827 [vc handlePressEvent:keyEventWithPhase(UIPressPhaseCancelled, UIKeyboardHIDUsageKeyboardA,
1828 UIKeyModifierShift, 123.0)
1829 nextAction:^(){
1830 }];
1831 [vc handlePressEvent:keyEventWithPhase(UIPressPhaseChanged, UIKeyboardHIDUsageKeyboardA,
1832 UIKeyModifierShift, 123.0)
1833 nextAction:^(){
1834 }];
1835
1836 XCTAssert(self.messageSent == nil);
1837 OCMVerify(never(), [keyEventChannel sendMessage:[OCMArg any]]);
1838 [vc deregisterNotifications];
1839 }
1840
1841 - (void)testPanGestureRecognizer API_AVAILABLE(ios(13.4)) {
1842 if (@available(iOS 13.4, *)) {
1843 // noop
1844 } else {
1845 return;
1846 }
1847
1848 FlutterViewController* vc = [[FlutterViewController alloc] initWithEngine:self.mockEngine
1849 nibName:nil
1850 bundle:nil];
1851 XCTAssertNotNil(vc);
1852 UIView* view = vc.view;
1853 XCTAssertNotNil(view);
1854 NSArray* gestureRecognizers = view.gestureRecognizers;
1855 XCTAssertNotNil(gestureRecognizers);
1856
1857 BOOL found = NO;
1858 for (id gesture in gestureRecognizers) {
1859 if ([gesture isKindOfClass:[UIPanGestureRecognizer class]]) {
1860 found = YES;
1861 break;
1862 }
1863 }
1864 XCTAssertTrue(found);
1865 }
1866
1867 - (void)testMouseSupport API_AVAILABLE(ios(13.4)) {
1868 if (@available(iOS 13.4, *)) {
1869 // noop
1870 } else {
1871 return;
1872 }
1873
1874 FlutterViewController* vc = [[FlutterViewController alloc] initWithEngine:self.mockEngine
1875 nibName:nil
1876 bundle:nil];
1877 XCTAssertNotNil(vc);
1878
1879 id mockPanGestureRecognizer = OCMClassMock([UIPanGestureRecognizer class]);
1880 XCTAssertNotNil(mockPanGestureRecognizer);
1881
1882 [vc discreteScrollEvent:mockPanGestureRecognizer];
1883
1884 // The mouse position within panGestureRecognizer should be checked
1885 [[mockPanGestureRecognizer verify] locationInView:[OCMArg any]];
1886 [[[self.mockEngine verify] ignoringNonObjectArgs]
1887 dispatchPointerDataPacket:std::make_unique<flutter::PointerDataPacket>(0)];
1888 }
1889
1890 - (void)testFakeEventTimeStamp {
1891 FlutterViewController* vc = [[FlutterViewController alloc] initWithEngine:self.mockEngine
1892 nibName:nil
1893 bundle:nil];
1894 XCTAssertNotNil(vc);
1895
1896 flutter::PointerData pointer_data = [vc generatePointerDataForFake];
1897 int64_t current_micros = [[NSProcessInfo processInfo] systemUptime] * 1000 * 1000;
1898 int64_t interval_micros = current_micros - pointer_data.time_stamp;
1899 const int64_t tolerance_millis = 2;
1900 XCTAssertTrue(interval_micros / 1000 < tolerance_millis,
1901 @"PointerData.time_stamp should be equal to NSProcessInfo.systemUptime");
1902 }
1903
1904 - (void)testSplashScreenViewCanSetNil {
1905 FlutterViewController* flutterViewController =
1906 [[FlutterViewController alloc] initWithProject:nil nibName:nil bundle:nil];
1907 [flutterViewController setSplashScreenView:nil];
1908 }
1909
1910 - (void)testLifeCycleNotificationBecameActive {
1911 FlutterEngine* engine = [[FlutterEngine alloc] init];
1912 [engine runWithEntrypoint:nil];
1913 FlutterViewController* flutterViewController =
1914 [[FlutterViewController alloc] initWithEngine:engine nibName:nil bundle:nil];
1915 UIWindow* window = [[UIWindow alloc] init];
1916 [window addSubview:flutterViewController.view];
1917 flutterViewController.view.bounds = CGRectMake(0, 0, 100, 100);
1918 [flutterViewController viewDidLayoutSubviews];
1919 NSNotification* sceneNotification =
1920 [NSNotification notificationWithName:UISceneDidActivateNotification object:nil userInfo:nil];
1921 NSNotification* applicationNotification =
1922 [NSNotification notificationWithName:UIApplicationDidBecomeActiveNotification
1923 object:nil
1924 userInfo:nil];
1925 id mockVC = OCMPartialMock(flutterViewController);
1926 [[NSNotificationCenter defaultCenter] postNotification:sceneNotification];
1927 [[NSNotificationCenter defaultCenter] postNotification:applicationNotification];
1928 #if APPLICATION_EXTENSION_API_ONLY
1929 OCMVerify([mockVC sceneBecameActive:[OCMArg any]]);
1930 OCMReject([mockVC applicationBecameActive:[OCMArg any]]);
1931 #else
1932 OCMReject([mockVC sceneBecameActive:[OCMArg any]]);
1933 OCMVerify([mockVC applicationBecameActive:[OCMArg any]]);
1934 #endif
1935 XCTAssertFalse(flutterViewController.isKeyboardInOrTransitioningFromBackground);
1936 OCMVerify([mockVC surfaceUpdated:YES]);
1937 XCTestExpectation* timeoutApplicationLifeCycle =
1938 [self expectationWithDescription:@"timeoutApplicationLifeCycle"];
1939 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)),
1940 dispatch_get_main_queue(), ^{
1941 [timeoutApplicationLifeCycle fulfill];
1942 OCMVerify([mockVC goToApplicationLifecycle:@"AppLifecycleState.resumed"]);
1943 [flutterViewController deregisterNotifications];
1944 });
1945 [self waitForExpectationsWithTimeout:5.0 handler:nil];
1946 }
1947
1948 - (void)testLifeCycleNotificationWillResignActive {
1949 FlutterEngine* engine = [[FlutterEngine alloc] init];
1950 [engine runWithEntrypoint:nil];
1951 FlutterViewController* flutterViewController =
1952 [[FlutterViewController alloc] initWithEngine:engine nibName:nil bundle:nil];
1953 NSNotification* sceneNotification =
1954 [NSNotification notificationWithName:UISceneWillDeactivateNotification
1955 object:nil
1956 userInfo:nil];
1957 NSNotification* applicationNotification =
1958 [NSNotification notificationWithName:UIApplicationWillResignActiveNotification
1959 object:nil
1960 userInfo:nil];
1961 id mockVC = OCMPartialMock(flutterViewController);
1962 [[NSNotificationCenter defaultCenter] postNotification:sceneNotification];
1963 [[NSNotificationCenter defaultCenter] postNotification:applicationNotification];
1964 #if APPLICATION_EXTENSION_API_ONLY
1965 OCMVerify([mockVC sceneWillResignActive:[OCMArg any]]);
1966 OCMReject([mockVC applicationWillResignActive:[OCMArg any]]);
1967 #else
1968 OCMReject([mockVC sceneWillResignActive:[OCMArg any]]);
1969 OCMVerify([mockVC applicationWillResignActive:[OCMArg any]]);
1970 #endif
1971 OCMVerify([mockVC goToApplicationLifecycle:@"AppLifecycleState.inactive"]);
1972 [flutterViewController deregisterNotifications];
1973 }
1974
1975 - (void)testLifeCycleNotificationWillTerminate {
1976 FlutterEngine* engine = [[FlutterEngine alloc] init];
1977 [engine runWithEntrypoint:nil];
1978 FlutterViewController* flutterViewController =
1979 [[FlutterViewController alloc] initWithEngine:engine nibName:nil bundle:nil];
1980 NSNotification* sceneNotification =
1981 [NSNotification notificationWithName:UISceneDidDisconnectNotification
1982 object:nil
1983 userInfo:nil];
1984 NSNotification* applicationNotification =
1985 [NSNotification notificationWithName:UIApplicationWillTerminateNotification
1986 object:nil
1987 userInfo:nil];
1988 id mockVC = OCMPartialMock(flutterViewController);
1989 id mockEngine = OCMPartialMock(engine);
1990 OCMStub([mockVC engine]).andReturn(mockEngine);
1991 [[NSNotificationCenter defaultCenter] postNotification:sceneNotification];
1992 [[NSNotificationCenter defaultCenter] postNotification:applicationNotification];
1993 #if APPLICATION_EXTENSION_API_ONLY
1994 OCMVerify([mockVC sceneWillDisconnect:[OCMArg any]]);
1995 OCMReject([mockVC applicationWillTerminate:[OCMArg any]]);
1996 #else
1997 OCMReject([mockVC sceneWillDisconnect:[OCMArg any]]);
1998 OCMVerify([mockVC applicationWillTerminate:[OCMArg any]]);
1999 #endif
2000 OCMVerify([mockVC goToApplicationLifecycle:@"AppLifecycleState.detached"]);
2001 OCMVerify([mockEngine destroyContext]);
2002 [flutterViewController deregisterNotifications];
2003 }
2004
2005 - (void)testLifeCycleNotificationDidEnterBackground {
2006 FlutterEngine* engine = [[FlutterEngine alloc] init];
2007 [engine runWithEntrypoint:nil];
2008 FlutterViewController* flutterViewController =
2009 [[FlutterViewController alloc] initWithEngine:engine nibName:nil bundle:nil];
2010 NSNotification* sceneNotification =
2011 [NSNotification notificationWithName:UISceneDidEnterBackgroundNotification
2012 object:nil
2013 userInfo:nil];
2014 NSNotification* applicationNotification =
2015 [NSNotification notificationWithName:UIApplicationDidEnterBackgroundNotification
2016 object:nil
2017 userInfo:nil];
2018 id mockVC = OCMPartialMock(flutterViewController);
2019 [[NSNotificationCenter defaultCenter] postNotification:sceneNotification];
2020 [[NSNotificationCenter defaultCenter] postNotification:applicationNotification];
2021 #if APPLICATION_EXTENSION_API_ONLY
2022 OCMVerify([mockVC sceneDidEnterBackground:[OCMArg any]]);
2023 OCMReject([mockVC applicationDidEnterBackground:[OCMArg any]]);
2024 #else
2025 OCMReject([mockVC sceneDidEnterBackground:[OCMArg any]]);
2026 OCMVerify([mockVC applicationDidEnterBackground:[OCMArg any]]);
2027 #endif
2028 XCTAssertTrue(flutterViewController.isKeyboardInOrTransitioningFromBackground);
2029 OCMVerify([mockVC surfaceUpdated:NO]);
2030 OCMVerify([mockVC goToApplicationLifecycle:@"AppLifecycleState.paused"]);
2031 [flutterViewController deregisterNotifications];
2032 }
2033
2034 - (void)testLifeCycleNotificationWillEnterForeground {
2035 FlutterEngine* engine = [[FlutterEngine alloc] init];
2036 [engine runWithEntrypoint:nil];
2037 FlutterViewController* flutterViewController =
2038 [[FlutterViewController alloc] initWithEngine:engine nibName:nil bundle:nil];
2039 NSNotification* sceneNotification =
2040 [NSNotification notificationWithName:UISceneWillEnterForegroundNotification
2041 object:nil
2042 userInfo:nil];
2043 NSNotification* applicationNotification =
2044 [NSNotification notificationWithName:UIApplicationWillEnterForegroundNotification
2045 object:nil
2046 userInfo:nil];
2047 id mockVC = OCMPartialMock(flutterViewController);
2048 [[NSNotificationCenter defaultCenter] postNotification:sceneNotification];
2049 [[NSNotificationCenter defaultCenter] postNotification:applicationNotification];
2050 #if APPLICATION_EXTENSION_API_ONLY
2051 OCMVerify([mockVC sceneWillEnterForeground:[OCMArg any]]);
2052 OCMReject([mockVC applicationWillEnterForeground:[OCMArg any]]);
2053 #else
2054 OCMReject([mockVC sceneWillEnterForeground:[OCMArg any]]);
2055 OCMVerify([mockVC applicationWillEnterForeground:[OCMArg any]]);
2056 #endif
2057 OCMVerify([mockVC goToApplicationLifecycle:@"AppLifecycleState.inactive"]);
2058 [flutterViewController deregisterNotifications];
2059 }
2060
2061 - (void)testLifeCycleNotificationCancelledInvalidResumed {
2062 FlutterEngine* engine = [[FlutterEngine alloc] init];
2063 [engine runWithEntrypoint:nil];
2064 FlutterViewController* flutterViewController =
2065 [[FlutterViewController alloc] initWithEngine:engine nibName:nil bundle:nil];
2066 NSNotification* applicationDidBecomeActiveNotification =
2067 [NSNotification notificationWithName:UIApplicationDidBecomeActiveNotification
2068 object:nil
2069 userInfo:nil];
2070 NSNotification* applicationWillResignActiveNotification =
2071 [NSNotification notificationWithName:UIApplicationWillResignActiveNotification
2072 object:nil
2073 userInfo:nil];
2074 id mockVC = OCMPartialMock(flutterViewController);
2075 [[NSNotificationCenter defaultCenter] postNotification:applicationDidBecomeActiveNotification];
2076 [[NSNotificationCenter defaultCenter] postNotification:applicationWillResignActiveNotification];
2077 #if APPLICATION_EXTENSION_API_ONLY
2078 #else
2079 OCMVerify([mockVC goToApplicationLifecycle:@"AppLifecycleState.inactive"]);
2080 #endif
2081
2082 XCTestExpectation* timeoutApplicationLifeCycle =
2083 [self expectationWithDescription:@"timeoutApplicationLifeCycle"];
2084 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)),
2085 dispatch_get_main_queue(), ^{
2086 OCMReject([mockVC goToApplicationLifecycle:@"AppLifecycleState.resumed"]);
2087 [timeoutApplicationLifeCycle fulfill];
2088 [flutterViewController deregisterNotifications];
2089 });
2090 [self waitForExpectationsWithTimeout:5.0 handler:nil];
2091 }
2092
2093 - (void)testSetupKeyboardAnimationVsyncClientWillCreateNewVsyncClientForFlutterViewController {
2094 id bundleMock = OCMPartialMock([NSBundle mainBundle]);
2095 OCMStub([bundleMock objectForInfoDictionaryKey:@"CADisableMinimumFrameDurationOnPhone"])
2096 .andReturn(@YES);
2097 id mockDisplayLinkManager = [OCMockObject mockForClass:[DisplayLinkManager class]];
2098 double maxFrameRate = 120;
2099 [[[mockDisplayLinkManager stub] andReturnValue:@(maxFrameRate)] displayRefreshRate];
2100 FlutterEngine* engine = [[FlutterEngine alloc] init];
2101 [engine runWithEntrypoint:nil];
2102 FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine
2103 nibName:nil
2104 bundle:nil];
2105 FlutterKeyboardAnimationCallback callback = ^(fml::TimePoint targetTime) {
2106 };
2107 [viewController setUpKeyboardAnimationVsyncClient:callback];
2108 XCTAssertNotNil(viewController.keyboardAnimationVSyncClient);
2109 CADisplayLink* link = [viewController.keyboardAnimationVSyncClient getDisplayLink];
2110 XCTAssertNotNil(link);
2111 if (@available(iOS 15.0, *)) {
2112 XCTAssertEqual(link.preferredFrameRateRange.maximum, maxFrameRate);
2113 XCTAssertEqual(link.preferredFrameRateRange.preferred, maxFrameRate);
2114 XCTAssertEqual(link.preferredFrameRateRange.minimum, maxFrameRate / 2);
2115 } else {
2116 XCTAssertEqual(link.preferredFramesPerSecond, maxFrameRate);
2117 }
2118 }
2119
2120 - (void)
2121 testCreateTouchRateCorrectionVSyncClientWillCreateVsyncClientWhenRefreshRateIsLargerThan60HZ {
2122 id mockDisplayLinkManager = [OCMockObject mockForClass:[DisplayLinkManager class]];
2123 double maxFrameRate = 120;
2124 [[[mockDisplayLinkManager stub] andReturnValue:@(maxFrameRate)] displayRefreshRate];
2125 FlutterEngine* engine = [[FlutterEngine alloc] init];
2126 [engine runWithEntrypoint:nil];
2127 FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine
2128 nibName:nil
2129 bundle:nil];
2130 [viewController createTouchRateCorrectionVSyncClientIfNeeded];
2131 XCTAssertNotNil(viewController.touchRateCorrectionVSyncClient);
2132 }
2133
2134 - (void)testCreateTouchRateCorrectionVSyncClientWillNotCreateNewVSyncClientWhenClientAlreadyExists {
2135 id mockDisplayLinkManager = [OCMockObject mockForClass:[DisplayLinkManager class]];
2136 double maxFrameRate = 120;
2137 [[[mockDisplayLinkManager stub] andReturnValue:@(maxFrameRate)] displayRefreshRate];
2138
2139 FlutterEngine* engine = [[FlutterEngine alloc] init];
2140 [engine runWithEntrypoint:nil];
2141 FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine
2142 nibName:nil
2143 bundle:nil];
2144 [viewController createTouchRateCorrectionVSyncClientIfNeeded];
2145 VSyncClient* clientBefore = viewController.touchRateCorrectionVSyncClient;
2146 XCTAssertNotNil(clientBefore);
2147
2148 [viewController createTouchRateCorrectionVSyncClientIfNeeded];
2149 VSyncClient* clientAfter = viewController.touchRateCorrectionVSyncClient;
2150 XCTAssertNotNil(clientAfter);
2151
2152 XCTAssertTrue(clientBefore == clientAfter);
2153 }
2154
2155 - (void)testCreateTouchRateCorrectionVSyncClientWillNotCreateVsyncClientWhenRefreshRateIs60HZ {
2156 id mockDisplayLinkManager = [OCMockObject mockForClass:[DisplayLinkManager class]];
2157 double maxFrameRate = 60;
2158 [[[mockDisplayLinkManager stub] andReturnValue:@(maxFrameRate)] displayRefreshRate];
2159 FlutterEngine* engine = [[FlutterEngine alloc] init];
2160 [engine runWithEntrypoint:nil];
2161 FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine
2162 nibName:nil
2163 bundle:nil];
2164 [viewController createTouchRateCorrectionVSyncClientIfNeeded];
2165 XCTAssertNil(viewController.touchRateCorrectionVSyncClient);
2166 }
2167
2168 - (void)testTriggerTouchRateCorrectionVSyncClientCorrectly {
2169 id mockDisplayLinkManager = [OCMockObject mockForClass:[DisplayLinkManager class]];
2170 double maxFrameRate = 120;
2171 [[[mockDisplayLinkManager stub] andReturnValue:@(maxFrameRate)] displayRefreshRate];
2172 FlutterEngine* engine = [[FlutterEngine alloc] init];
2173 [engine runWithEntrypoint:nil];
2174 FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine
2175 nibName:nil
2176 bundle:nil];
2177 [viewController loadView];
2178 [viewController viewDidLoad];
2179
2180 VSyncClient* client = viewController.touchRateCorrectionVSyncClient;
2181 CADisplayLink* link = [client getDisplayLink];
2182
2183 UITouch* fakeTouchBegan = [[UITouch alloc] init];
2184 fakeTouchBegan.phase = UITouchPhaseBegan;
2185
2186 UITouch* fakeTouchMove = [[UITouch alloc] init];
2187 fakeTouchMove.phase = UITouchPhaseMoved;
2188
2189 UITouch* fakeTouchEnd = [[UITouch alloc] init];
2190 fakeTouchEnd.phase = UITouchPhaseEnded;
2191
2192 UITouch* fakeTouchCancelled = [[UITouch alloc] init];
2193 fakeTouchCancelled.phase = UITouchPhaseCancelled;
2194
2195 [viewController
2196 triggerTouchRateCorrectionIfNeeded:[[NSSet alloc] initWithObjects:fakeTouchBegan, nil]];
2197 XCTAssertFalse(link.isPaused);
2198
2199 [viewController
2200 triggerTouchRateCorrectionIfNeeded:[[NSSet alloc] initWithObjects:fakeTouchEnd, nil]];
2201 XCTAssertTrue(link.isPaused);
2202
2203 [viewController
2204 triggerTouchRateCorrectionIfNeeded:[[NSSet alloc] initWithObjects:fakeTouchMove, nil]];
2205 XCTAssertFalse(link.isPaused);
2206
2207 [viewController
2208 triggerTouchRateCorrectionIfNeeded:[[NSSet alloc] initWithObjects:fakeTouchCancelled, nil]];
2209 XCTAssertTrue(link.isPaused);
2210
2211 [viewController
2212 triggerTouchRateCorrectionIfNeeded:[[NSSet alloc]
2213 initWithObjects:fakeTouchBegan, fakeTouchEnd, nil]];
2214 XCTAssertFalse(link.isPaused);
2215
2216 [viewController
2217 triggerTouchRateCorrectionIfNeeded:[[NSSet alloc] initWithObjects:fakeTouchEnd,
2218 fakeTouchCancelled, nil]];
2219 XCTAssertTrue(link.isPaused);
2220
2221 [viewController
2222 triggerTouchRateCorrectionIfNeeded:[[NSSet alloc]
2223 initWithObjects:fakeTouchMove, fakeTouchEnd, nil]];
2224 XCTAssertFalse(link.isPaused);
2225 }
2226
2227 - (void)testFlutterViewControllerStartKeyboardAnimationWillCreateVsyncClientCorrectly {
2228 FlutterEngine* engine = [[FlutterEngine alloc] init];
2229 [engine runWithEntrypoint:nil];
2230 FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine
2231 nibName:nil
2232 bundle:nil];
2233 viewController.targetViewInsetBottom = 100;
2234 [viewController startKeyBoardAnimation:0.25];
2235 XCTAssertNotNil(viewController.keyboardAnimationVSyncClient);
2236 }
2237
2238 - (void)
2239 testSetupKeyboardAnimationVsyncClientWillNotCreateNewVsyncClientWhenKeyboardAnimationCallbackIsNil {
2240 FlutterEngine* engine = [[FlutterEngine alloc] init];
2241 [engine runWithEntrypoint:nil];
2242 FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine
2243 nibName:nil
2244 bundle:nil];
2245 [viewController setUpKeyboardAnimationVsyncClient:nil];
2246 XCTAssertNil(viewController.keyboardAnimationVSyncClient);
2247 }
2248
2249 - (void)testSupportsShowingSystemContextMenuForIOS16AndAbove {
2250 FlutterEngine* engine = [[FlutterEngine alloc] init];
2251 [engine runWithEntrypoint:nil];
2252 FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine
2253 nibName:nil
2254 bundle:nil];
2255 BOOL supportsShowingSystemContextMenu = [viewController supportsShowingSystemContextMenu];
2256 if (@available(iOS 16.0, *)) {
2257 XCTAssertTrue(supportsShowingSystemContextMenu);
2258 } else {
2259 XCTAssertFalse(supportsShowingSystemContextMenu);
2260 }
2261 }
2262
2263 @end
-[FlutterViewController(Tests) invalidateKeyboardAnimationVSyncClient]
void invalidateKeyboardAnimationVSyncClient()
FlutterEnginePartialMock::lifecycleChannel
FlutterBasicMessageChannel * lifecycleChannel
Definition: FlutterViewControllerTest.mm:41
FlutterEnginePartialMock
Definition: FlutterViewControllerTest.mm:40
FlutterEngine
Definition: FlutterEngine.h:61
FlutterHourFormat.h
FlutterBasicMessageChannel
Definition: FlutterChannels.h:37
FlutterViewController
Definition: FlutterViewController.h:57
FlutterMethodChannel
Definition: FlutterChannels.h:220
-[FlutterViewController(Tests) updateViewportMetricsIfNeeded]
void updateViewportMetricsIfNeeded()
-[FlutterEngine runWithEntrypoint:]
BOOL runWithEntrypoint:(nullable NSString *entrypoint)
FlutterViewController(Tests)::targetViewInsetBottom
double targetViewInsetBottom
Definition: FlutterViewControllerTest.mm:125
FlutterTextInputPlugin.h
API_AVAILABLE
UITextSmartQuotesType smartQuotesType API_AVAILABLE(ios(11.0))
FlutterViewController(Tests)::keyboardAnimationIsShowing
BOOL keyboardAnimationIsShowing
Definition: FlutterViewControllerTest.mm:127
-[VSyncClient(Testing) getDisplayLink]
CADisplayLink * getDisplayLink()
DisplayLinkManager
Definition: FlutterMetalLayer.mm:16
FlutterViewController(Tests)::touchRateCorrectionVSyncClient
VSyncClient * touchRateCorrectionVSyncClient
Definition: FlutterViewControllerTest.mm:129
FlutterViewControllerTest
Definition: FlutterViewControllerTest.mm:171
flutter::testing
Definition: FlutterFakeKeyEvents.h:51
FlutterMacros.h
FlutterEmbedderKeyResponder.h
FlutterSendKeyEvent
void(^ FlutterSendKeyEvent)(const FlutterKeyEvent &, _Nullable FlutterKeyEventCallback, void *_Nullable)
Definition: FlutterEmbedderKeyResponder.h:13
FlutterViewControllerTest::messageSent
id messageSent
Definition: FlutterViewControllerTest.mm:174
viewController
FlutterViewController * viewController
Definition: FlutterTextInputPluginTest.mm:92
FlutterEmbedderKeyResponder(Tests)::sendEvent
FlutterSendKeyEvent sendEvent
Definition: FlutterViewControllerTest.mm:120
FlutterKeyboardAnimationCallback
void(^ FlutterKeyboardAnimationCallback)(fml::TimePoint)
Definition: FlutterViewController_Internal.h:42
FlutterViewControllerTest::mockTextInputPlugin
id mockTextInputPlugin
Definition: FlutterViewControllerTest.mm:173
FlutterEngine(TestLowMemory)
Definition: FlutterViewControllerTest.mm:87
FlutterHourFormat
Definition: FlutterHourFormat.h:10
FlutterFakeKeyEvents.h
-[FlutterEngine(TestLowMemory) notifyLowMemory]
void notifyLowMemory()
flutter
Definition: accessibility_bridge.h:28
-[FlutterViewController(Tests) addInternalPlugins]
void addInternalPlugins()
FlutterBinaryMessenger.h
FlutterTextInputPlugin
Definition: FlutterTextInputPlugin.h:33
FlutterEnginePartialMock::keyEventChannel
FlutterBasicMessageChannel * keyEventChannel
Definition: FlutterViewControllerTest.mm:42
FlutterViewControllerTest::mockEngine
id mockEngine
Definition: FlutterViewControllerTest.mm:172
-[FlutterViewController(Tests) keyboardAnimationView]
UIView * keyboardAnimationView()
UIViewController+FlutterScreenAndSceneIfLoaded.h
FlutterViewController(Tests)::keyboardAnimationVSyncClient
VSyncClient * keyboardAnimationVSyncClient
Definition: FlutterViewControllerTest.mm:128
FlutterViewController(Tests)
Definition: FlutterViewControllerTest.mm:123
-[FlutterViewController(Tests) keyboardSpringAnimation]
SpringAnimation * keyboardSpringAnimation()
FlutterReply
NS_ASSUME_NONNULL_BEGIN typedef void(^ FlutterReply)(id _Nullable reply)
engine
id engine
Definition: FlutterTextInputPluginTest.mm:89
textInputPlugin
FlutterTextInputPlugin * textInputPlugin
Definition: FlutterTextInputPluginTest.mm:90
FlutterViewController_Internal.h
FlutterKeyboardManager(Tests)
Definition: FlutterViewControllerTest.mm:114
FlutterUIPressProxy
Definition: FlutterUIPressProxy.h:17
MockEngine
Definition: FlutterViewControllerTest.mm:101
FlutterEmbedderKeyResponder(Tests)
Definition: FlutterViewControllerTest.mm:119
FlutterView
Definition: FlutterView.h:34
VSyncClient(Testing)
Definition: FlutterViewControllerTest.mm:184
-[FlutterEngine destroyContext]
void destroyContext()
Definition: FlutterEngine.mm:479
vsync_waiter_ios.h
FlutterKeyboardManager(Tests)::primaryResponders
NSMutableArray< id< FlutterKeyPrimaryResponder > > * primaryResponders
Definition: FlutterViewControllerTest.mm:116
FlutterViewController(Tests)::isKeyboardInOrTransitioningFromBackground
BOOL isKeyboardInOrTransitioningFromBackground
Definition: FlutterViewControllerTest.mm:126
FlutterViewController::binaryMessenger
NSObject< FlutterBinaryMessenger > * binaryMessenger
Definition: FlutterViewController.h:244
FlutterDartProject
Definition: FlutterDartProject.mm:258
-[FlutterViewController(Tests) ensureViewportMetricsIsCorrect]
void ensureViewportMetricsIsCorrect()
FlutterKeyboardManager
Definition: FlutterKeyboardManager.h:53
-[FlutterViewController(Tests) createTouchRateCorrectionVSyncClientIfNeeded]
void createTouchRateCorrectionVSyncClientIfNeeded()
FlutterBinaryMessenger-p
Definition: FlutterBinaryMessenger.h:49
FlutterEmbedderKeyResponder
Definition: FlutterEmbedderKeyResponder.h:23
FLUTTER_ASSERT_ARC
Definition: FlutterChannelKeyResponder.mm:13
VSyncClient
Definition: vsync_waiter_ios.h:36
FlutterView.h
FlutterViewController.h
FlutterViewControllerWillDealloc
const NSNotificationName FlutterViewControllerWillDealloc
Definition: FlutterViewController.mm:43