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