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