5#import <OCMock/OCMock.h>
6#import <XCTest/XCTest.h>
49 userData:(nullable
void*)userData;
60- (void)notifyLowMemory {
61 _didCallNotifyLowMemory = YES;
66 userData:(
void*)userData API_AVAILABLE(ios(9.0)) {
81- (
BOOL)createShell:(NSString*)entrypoint
82 libraryURI:(NSString*)libraryURI
83 initialRoute:(NSString*)initialRoute;
89@interface FlutterEngine (TestLowMemory)
90- (void)notifyLowMemory;
116@interface FlutterKeyboardManager (Tests)
117@property(nonatomic, retain, readonly)
118 NSMutableArray<id<FlutterKeyPrimaryResponder>>* primaryResponders;
121@interface FlutterEmbedderKeyResponder (Tests)
125@interface NSObject (Tests)
129@interface FlutterViewController (Tests)
131@property(nonatomic, assign)
double targetViewInsetBottom;
132@property(nonatomic, assign)
BOOL isKeyboardInOrTransitioningFromBackground;
133@property(nonatomic, assign)
BOOL keyboardAnimationIsShowing;
134@property(nonatomic, strong)
VSyncClient* keyboardAnimationVSyncClient;
135@property(nonatomic, strong)
VSyncClient* touchRateCorrectionVSyncClient;
136@property(nonatomic, assign)
BOOL awokenFromNib;
138- (void)createTouchRateCorrectionVSyncClientIfNeeded;
139- (void)surfaceUpdated:(
BOOL)appeared;
140- (void)performOrientationUpdate:(UIInterfaceOrientationMask)new_preferences;
141- (void)handlePressEvent:(FlutterUIPressProxy*)press
142 nextAction:(
void (^)())next API_AVAILABLE(ios(13.4));
143- (void)discreteScrollEvent:(UIPanGestureRecognizer*)recognizer;
144- (void)updateViewportMetricsIfNeeded;
145- (void)updateAutoResizeConstraints;
146- (void)checkAndUpdateAutoResizeConstraints;
147- (void)onUserSettingsChanged:(NSNotification*)notification;
148- (void)applicationWillTerminate:(NSNotification*)notification;
149- (void)goToApplicationLifecycle:(nonnull NSString*)state;
150- (void)handleKeyboardNotification:(NSNotification*)notification;
151- (CGFloat)calculateKeyboardInset:(CGRect)keyboardFrame keyboardMode:(
int)keyboardMode;
152- (
BOOL)shouldIgnoreKeyboardNotification:(NSNotification*)notification;
153- (FlutterKeyboardMode)calculateKeyboardAttachMode:(NSNotification*)notification;
154- (CGFloat)calculateMultitaskingAdjustment:(CGRect)screenRect keyboardFrame:(CGRect)keyboardFrame;
155- (void)startKeyBoardAnimation:(NSTimeInterval)duration;
156- (void)hideKeyboardImmediately;
157- (UIView*)keyboardAnimationView;
159- (void)setUpKeyboardSpringAnimationIfNeeded:(CAAnimation*)keyboardAnimation;
160- (void)setUpKeyboardAnimationVsyncClient:
162- (void)ensureViewportMetricsIsCorrect;
163- (void)invalidateKeyboardAnimationVSyncClient;
164- (void)addInternalPlugins;
165- (
flutter::PointerData)generatePointerDataForFake;
167 initialRoute:(nullable NSString*)initialRoute;
168- (void)applicationBecameActive:(NSNotification*)notification;
169- (void)applicationWillResignActive:(NSNotification*)notification;
170- (void)applicationWillTerminate:(NSNotification*)notification;
171- (void)applicationDidEnterBackground:(NSNotification*)notification;
172- (void)applicationWillEnterForeground:(NSNotification*)notification;
173- (void)sceneBecameActive:(NSNotification*)notification API_AVAILABLE(ios(13.0));
174- (void)sceneWillResignActive:(NSNotification*)notification API_AVAILABLE(ios(13.0));
175- (void)sceneWillDisconnect:(NSNotification*)notification API_AVAILABLE(ios(13.0));
176- (void)sceneDidEnterBackground:(NSNotification*)notification API_AVAILABLE(ios(13.0));
177- (void)sceneWillEnterForeground:(NSNotification*)notification API_AVAILABLE(ios(13.0));
178- (void)triggerTouchRateCorrectionIfNeeded:(NSSet*)touches;
179- (void)onAccessibilityStatusChanged:(NSNotification*)notification;
183@property(nonatomic, strong)
id mockEngine;
184@property(nonatomic, strong)
id mockTextInputPlugin;
185@property(nonatomic, strong)
id messageSent;
186- (void)sendMessage:(
id _Nullable)message reply:(
FlutterReply _Nullable)callback;
191@property(nonatomic, readwrite) UITouchPhase phase;
195@interface VSyncClient (Testing)
197- (CADisplayLink*)getDisplayLink;
207 self.messageSent = nil;
213 [
self.mockEngine stopMocking];
214 self.mockEngine = nil;
215 self.mockTextInputPlugin = nil;
216 self.messageSent = nil;
219- (
id)setUpMockScreen {
220 UIScreen* mockScreen = OCMClassMock([UIScreen
class]);
222 CGRect screenBounds = CGRectMake(0, 0, 1170, 2532);
223 OCMStub([mockScreen bounds]).andReturn(screenBounds);
224 CGFloat screenScale = 1;
225 OCMStub([mockScreen scale]).andReturn(screenScale);
231 screen:(UIScreen*)screen
232 viewFrame:(CGRect)viewFrame
233 convertedFrame:(CGRect)convertedFrame {
234 OCMStub([viewControllerMock flutterScreenIfViewLoaded]).andReturn(screen);
235 id mockView = OCMClassMock([UIView
class]);
236 OCMStub([mockView frame]).andReturn(viewFrame);
237 OCMStub([mockView convertRect:viewFrame toCoordinateSpace:[OCMArg any]])
238 .andReturn(convertedFrame);
239 OCMStub([viewControllerMock viewIfLoaded]).andReturn(mockView);
244- (void)testViewDidLoadWillInvokeCreateTouchRateCorrectionVSyncClient {
251 [viewControllerMock loadView];
252 [viewControllerMock viewDidLoad];
253 OCMVerify([viewControllerMock createTouchRateCorrectionVSyncClientIfNeeded]);
256- (void)testStartKeyboardAnimationWillInvokeSetupKeyboardSpringAnimationIfNeeded {
263 viewControllerMock.targetViewInsetBottom = 100;
264 [viewControllerMock startKeyBoardAnimation:0.25];
266 CAAnimation* keyboardAnimation =
267 [[viewControllerMock keyboardAnimationView].layer animationForKey:@"position"];
269 OCMVerify([viewControllerMock setUpKeyboardSpringAnimationIfNeeded:keyboardAnimation]);
272- (void)testSetupKeyboardSpringAnimationIfNeeded {
279 UIScreen* screen = [
self setUpMockScreen];
280 CGRect viewFrame = screen.bounds;
281 [
self setUpMockView:viewControllerMock
284 convertedFrame:viewFrame];
287 [viewControllerMock setUpKeyboardSpringAnimationIfNeeded:nil];
288 SpringAnimation* keyboardSpringAnimation = [viewControllerMock keyboardSpringAnimation];
289 XCTAssertTrue(keyboardSpringAnimation == nil);
292 CABasicAnimation* nonSpringAnimation = [CABasicAnimation animation];
293 nonSpringAnimation.duration = 1.0;
294 nonSpringAnimation.fromValue = [NSNumber numberWithFloat:0.0];
295 nonSpringAnimation.toValue = [NSNumber numberWithFloat:1.0];
296 nonSpringAnimation.keyPath =
@"position";
297 [viewControllerMock setUpKeyboardSpringAnimationIfNeeded:nonSpringAnimation];
298 keyboardSpringAnimation = [viewControllerMock keyboardSpringAnimation];
300 XCTAssertTrue(keyboardSpringAnimation == nil);
303 CASpringAnimation* springAnimation = [CASpringAnimation animation];
304 springAnimation.mass = 1.0;
305 springAnimation.stiffness = 100.0;
306 springAnimation.damping = 10.0;
307 springAnimation.keyPath =
@"position";
308 springAnimation.fromValue = [NSValue valueWithCGPoint:CGPointMake(0, 0)];
309 springAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(100, 100)];
310 [viewControllerMock setUpKeyboardSpringAnimationIfNeeded:springAnimation];
311 keyboardSpringAnimation = [viewControllerMock keyboardSpringAnimation];
312 XCTAssertTrue(keyboardSpringAnimation != nil);
315- (void)testKeyboardAnimationIsShowingAndCompounding {
322 UIScreen* screen = [
self setUpMockScreen];
323 CGRect viewFrame = screen.bounds;
324 [
self setUpMockView:viewControllerMock
327 convertedFrame:viewFrame];
330 CGFloat screenHeight = screen.bounds.size.height;
331 CGFloat screenWidth = screen.bounds.size.height;
334 CGRect initialShowKeyboardBeginFrame = CGRectMake(0, screenHeight, screenWidth, 250);
335 CGRect initialShowKeyboardEndFrame = CGRectMake(0, screenHeight - 250, screenWidth, 500);
336 NSNotification* fakeNotification = [NSNotification
337 notificationWithName:UIKeyboardWillChangeFrameNotification
340 @"UIKeyboardFrameBeginUserInfoKey" : @(initialShowKeyboardBeginFrame),
341 @"UIKeyboardFrameEndUserInfoKey" : @(initialShowKeyboardEndFrame),
342 @"UIKeyboardAnimationDurationUserInfoKey" : @(0.25),
343 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
345 viewControllerMock.targetViewInsetBottom = 0;
346 [viewControllerMock handleKeyboardNotification:fakeNotification];
347 BOOL isShowingAnimation1 = viewControllerMock.keyboardAnimationIsShowing;
348 XCTAssertTrue(isShowingAnimation1);
351 CGRect compoundingShowKeyboardBeginFrame = CGRectMake(0, screenHeight - 250, screenWidth, 250);
352 CGRect compoundingShowKeyboardEndFrame = CGRectMake(0, screenHeight - 500, screenWidth, 500);
353 fakeNotification = [NSNotification
354 notificationWithName:UIKeyboardWillChangeFrameNotification
357 @"UIKeyboardFrameBeginUserInfoKey" : @(compoundingShowKeyboardBeginFrame),
358 @"UIKeyboardFrameEndUserInfoKey" : @(compoundingShowKeyboardEndFrame),
359 @"UIKeyboardAnimationDurationUserInfoKey" : @(0.25),
360 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
363 [viewControllerMock handleKeyboardNotification:fakeNotification];
364 BOOL isShowingAnimation2 = viewControllerMock.keyboardAnimationIsShowing;
365 XCTAssertTrue(isShowingAnimation2);
366 XCTAssertTrue(isShowingAnimation1 == isShowingAnimation2);
369 CGRect initialHideKeyboardBeginFrame = CGRectMake(0, screenHeight - 500, screenWidth, 250);
370 CGRect initialHideKeyboardEndFrame = CGRectMake(0, screenHeight - 250, screenWidth, 500);
371 fakeNotification = [NSNotification
372 notificationWithName:UIKeyboardWillChangeFrameNotification
375 @"UIKeyboardFrameBeginUserInfoKey" : @(initialHideKeyboardBeginFrame),
376 @"UIKeyboardFrameEndUserInfoKey" : @(initialHideKeyboardEndFrame),
377 @"UIKeyboardAnimationDurationUserInfoKey" : @(0.25),
378 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
381 [viewControllerMock handleKeyboardNotification:fakeNotification];
382 BOOL isShowingAnimation3 = viewControllerMock.keyboardAnimationIsShowing;
383 XCTAssertFalse(isShowingAnimation3);
384 XCTAssertTrue(isShowingAnimation2 != isShowingAnimation3);
387 CGRect compoundingHideKeyboardBeginFrame = CGRectMake(0, screenHeight - 250, screenWidth, 250);
388 CGRect compoundingHideKeyboardEndFrame = CGRectMake(0, screenHeight, screenWidth, 500);
389 fakeNotification = [NSNotification
390 notificationWithName:UIKeyboardWillChangeFrameNotification
393 @"UIKeyboardFrameBeginUserInfoKey" : @(compoundingHideKeyboardBeginFrame),
394 @"UIKeyboardFrameEndUserInfoKey" : @(compoundingHideKeyboardEndFrame),
395 @"UIKeyboardAnimationDurationUserInfoKey" : @(0.25),
396 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
399 [viewControllerMock handleKeyboardNotification:fakeNotification];
400 BOOL isShowingAnimation4 = viewControllerMock.keyboardAnimationIsShowing;
401 XCTAssertFalse(isShowingAnimation4);
402 XCTAssertTrue(isShowingAnimation3 == isShowingAnimation4);
405- (void)testShouldIgnoreKeyboardNotification {
407 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
412 UIScreen* screen = [
self setUpMockScreen];
413 CGRect viewFrame = screen.bounds;
414 [
self setUpMockView:viewControllerMock
417 convertedFrame:viewFrame];
419 CGFloat screenWidth = screen.bounds.size.width;
420 CGFloat screenHeight = screen.bounds.size.height;
421 CGRect emptyKeyboard = CGRectZero;
422 CGRect zeroHeightKeyboard = CGRectMake(0, 0, screenWidth, 0);
423 CGRect validKeyboardEndFrame = CGRectMake(0, screenHeight - 320, screenWidth, 320);
427 NSNotification* notification =
428 [NSNotification notificationWithName:UIKeyboardWillHideNotification
431 @"UIKeyboardFrameEndUserInfoKey" : @(validKeyboardEndFrame),
432 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
433 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
436 BOOL shouldIgnore = [viewControllerMock shouldIgnoreKeyboardNotification:notification];
437 XCTAssertTrue(shouldIgnore == NO);
441 notification = [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
444 @"UIKeyboardFrameEndUserInfoKey" : @(emptyKeyboard),
445 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
446 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
448 shouldIgnore = [viewControllerMock shouldIgnoreKeyboardNotification:notification];
449 XCTAssertTrue(shouldIgnore == YES);
454 [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
457 @"UIKeyboardFrameEndUserInfoKey" : @(zeroHeightKeyboard),
458 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
459 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
461 shouldIgnore = [viewControllerMock shouldIgnoreKeyboardNotification:notification];
462 XCTAssertTrue(shouldIgnore == NO);
467 [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
470 @"UIKeyboardFrameEndUserInfoKey" : @(validKeyboardEndFrame),
471 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
472 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
474 shouldIgnore = [viewControllerMock shouldIgnoreKeyboardNotification:notification];
475 XCTAssertTrue(shouldIgnore == YES);
480 [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
483 @"UIKeyboardFrameEndUserInfoKey" : @(validKeyboardEndFrame),
484 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
485 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
487 shouldIgnore = [viewControllerMock shouldIgnoreKeyboardNotification:notification];
488 XCTAssertTrue(shouldIgnore == NO);
491- (void)testKeyboardAnimationWillNotCrashWhenEngineDestroyed {
497 [viewController setUpKeyboardAnimationVsyncClient:^(fml::TimePoint){
502- (void)testKeyboardAnimationWillWaitUIThreadVsync {
515 const int delayTime = 1;
516 [engine uiTaskRunner]->PostTask([] { sleep(delayTime); });
517 XCTestExpectation* expectation = [
self expectationWithDescription:@"keyboard animation callback"];
519 __block CFTimeInterval fulfillTime;
521 fulfillTime = CACurrentMediaTime();
522 [expectation fulfill];
524 CFTimeInterval startTime = CACurrentMediaTime();
525 [viewController setUpKeyboardAnimationVsyncClient:callback];
526 [
self waitForExpectationsWithTimeout:5.0 handler:nil];
527 XCTAssertTrue(fulfillTime - startTime > delayTime);
530- (void)testCalculateKeyboardAttachMode {
532 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
538 UIScreen* screen = [
self setUpMockScreen];
539 CGRect viewFrame = screen.bounds;
540 [
self setUpMockView:viewControllerMock
543 convertedFrame:viewFrame];
545 CGFloat screenWidth = screen.bounds.size.width;
546 CGFloat screenHeight = screen.bounds.size.height;
549 CGRect keyboardFrame = CGRectZero;
550 NSNotification* notification =
551 [NSNotification notificationWithName:UIKeyboardWillHideNotification
554 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
555 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
556 @"UIKeyboardIsLocalUserInfoKey" : @(YES)
558 FlutterKeyboardMode keyboardMode = [viewControllerMock calculateKeyboardAttachMode:notification];
559 XCTAssertTrue(keyboardMode == FlutterKeyboardModeHidden);
562 keyboardFrame = CGRectZero;
563 notification = [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
566 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
567 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
568 @"UIKeyboardIsLocalUserInfoKey" : @(YES)
570 keyboardMode = [viewControllerMock calculateKeyboardAttachMode:notification];
571 XCTAssertTrue(keyboardMode == FlutterKeyboardModeFloating);
574 keyboardFrame = CGRectMake(0, 0, screenWidth, 0);
575 notification = [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
578 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
579 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
580 @"UIKeyboardIsLocalUserInfoKey" : @(YES)
582 keyboardMode = [viewControllerMock calculateKeyboardAttachMode:notification];
583 XCTAssertTrue(keyboardMode == FlutterKeyboardModeHidden);
586 keyboardFrame = CGRectMake(0, 0, 320, 320);
587 notification = [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
590 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
591 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
592 @"UIKeyboardIsLocalUserInfoKey" : @(YES)
594 keyboardMode = [viewControllerMock calculateKeyboardAttachMode:notification];
595 XCTAssertTrue(keyboardMode == FlutterKeyboardModeFloating);
598 keyboardFrame = CGRectMake(0, 0, screenWidth, 320);
599 notification = [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
602 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
603 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
604 @"UIKeyboardIsLocalUserInfoKey" : @(YES)
606 keyboardMode = [viewControllerMock calculateKeyboardAttachMode:notification];
607 XCTAssertTrue(keyboardMode == FlutterKeyboardModeFloating);
610 keyboardFrame = CGRectMake(0, screenHeight - 320, screenWidth, 320);
611 notification = [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
614 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
615 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
616 @"UIKeyboardIsLocalUserInfoKey" : @(YES)
618 keyboardMode = [viewControllerMock calculateKeyboardAttachMode:notification];
619 XCTAssertTrue(keyboardMode == FlutterKeyboardModeDocked);
622 CGFloat longDecimalHeight = 320.666666666666666;
623 keyboardFrame = CGRectMake(0, screenHeight - longDecimalHeight, screenWidth, longDecimalHeight);
624 notification = [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
627 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
628 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
629 @"UIKeyboardIsLocalUserInfoKey" : @(YES)
631 keyboardMode = [viewControllerMock calculateKeyboardAttachMode:notification];
632 XCTAssertTrue(keyboardMode == FlutterKeyboardModeDocked);
635 keyboardFrame = CGRectMake(0, screenHeight - .0000001, screenWidth, longDecimalHeight);
636 notification = [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
639 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
640 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
641 @"UIKeyboardIsLocalUserInfoKey" : @(YES)
643 keyboardMode = [viewControllerMock calculateKeyboardAttachMode:notification];
644 XCTAssertTrue(keyboardMode == FlutterKeyboardModeHidden);
647 keyboardFrame = CGRectMake(0, screenHeight, screenWidth, 320);
648 notification = [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
651 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
652 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
653 @"UIKeyboardIsLocalUserInfoKey" : @(YES)
655 keyboardMode = [viewControllerMock calculateKeyboardAttachMode:notification];
656 XCTAssertTrue(keyboardMode == FlutterKeyboardModeHidden);
659- (void)testCalculateMultitaskingAdjustment {
661 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
667 UIScreen* screen = [
self setUpMockScreen];
668 CGFloat screenWidth = screen.bounds.size.width;
669 CGFloat screenHeight = screen.bounds.size.height;
670 CGRect screenRect = screen.bounds;
671 CGRect viewOrigFrame = CGRectMake(0, 0, 320, screenHeight - 40);
672 CGRect convertedViewFrame = CGRectMake(20, 20, 320, screenHeight - 40);
673 CGRect keyboardFrame = CGRectMake(20, screenHeight - 320, screenWidth, 300);
674 id mockView = [
self setUpMockView:viewControllerMock
676 viewFrame:viewOrigFrame
677 convertedFrame:convertedViewFrame];
678 id mockTraitCollection = OCMClassMock([UITraitCollection
class]);
679 OCMStub([mockTraitCollection userInterfaceIdiom]).andReturn(UIUserInterfaceIdiomPad);
680 OCMStub([mockTraitCollection horizontalSizeClass]).andReturn(UIUserInterfaceSizeClassCompact);
681 OCMStub([mockTraitCollection verticalSizeClass]).andReturn(UIUserInterfaceSizeClassRegular);
682 OCMStub([mockView traitCollection]).andReturn(mockTraitCollection);
684 CGFloat adjustment = [viewControllerMock calculateMultitaskingAdjustment:screenRect
685 keyboardFrame:keyboardFrame];
686 XCTAssertTrue(adjustment == 20);
689- (void)testCalculateKeyboardInset {
691 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
696 UIScreen* screen = [
self setUpMockScreen];
697 OCMStub([viewControllerMock flutterScreenIfViewLoaded]).andReturn(screen);
699 CGFloat screenWidth = screen.bounds.size.width;
700 CGFloat screenHeight = screen.bounds.size.height;
701 CGRect viewOrigFrame = CGRectMake(0, 0, 320, screenHeight - 40);
702 CGRect convertedViewFrame = CGRectMake(20, 20, 320, screenHeight - 40);
703 CGRect keyboardFrame = CGRectMake(20, screenHeight - 320, screenWidth, 300);
705 [
self setUpMockView:viewControllerMock
707 viewFrame:viewOrigFrame
708 convertedFrame:convertedViewFrame];
710 CGFloat inset = [viewControllerMock calculateKeyboardInset:keyboardFrame
711 keyboardMode:FlutterKeyboardModeDocked];
712 XCTAssertTrue(inset == 300 * screen.scale);
715- (void)testHandleKeyboardNotification {
722 UIScreen* screen = [
self setUpMockScreen];
723 CGFloat screenWidth = screen.bounds.size.width;
724 CGFloat screenHeight = screen.bounds.size.height;
725 CGRect keyboardFrame = CGRectMake(0, screenHeight - 320, screenWidth, 320);
726 CGRect viewFrame = screen.bounds;
728 NSNotification* notification =
729 [NSNotification notificationWithName:UIKeyboardWillShowNotification
732 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
733 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
734 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
737 [
self setUpMockView:viewControllerMock
740 convertedFrame:viewFrame];
741 viewControllerMock.targetViewInsetBottom = 0;
742 XCTestExpectation* expectation = [
self expectationWithDescription:@"update viewport"];
743 OCMStub([viewControllerMock updateViewportMetricsIfNeeded]).andDo(^(NSInvocation* invocation) {
744 [expectation fulfill];
747 [viewControllerMock handleKeyboardNotification:notification];
748 XCTAssertTrue(viewControllerMock.targetViewInsetBottom == 320 * screen.scale);
749 OCMVerify([viewControllerMock startKeyBoardAnimation:0.25]);
750 [
self waitForExpectationsWithTimeout:5.0 handler:nil];
753- (void)testEnsureBottomInsetIsZeroWhenKeyboardDismissed {
755 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
761 CGRect keyboardFrame = CGRectZero;
763 NSNotification* fakeNotification =
764 [NSNotification notificationWithName:UIKeyboardWillHideNotification
767 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
768 @"UIKeyboardAnimationDurationUserInfoKey" : @(0.25),
769 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
772 viewControllerMock.targetViewInsetBottom = 10;
773 [viewControllerMock handleKeyboardNotification:fakeNotification];
774 XCTAssertTrue(viewControllerMock.targetViewInsetBottom == 0);
777- (void)testStopKeyBoardAnimationWhenReceivedWillHideNotificationAfterWillShowNotification {
786 UIScreen* screen = [
self setUpMockScreen];
787 CGRect viewFrame = screen.bounds;
788 [
self setUpMockView:viewControllerMock
791 convertedFrame:viewFrame];
792 viewControllerMock.targetViewInsetBottom = 0;
794 CGFloat screenHeight = screen.bounds.size.height;
795 CGFloat screenWidth = screen.bounds.size.height;
796 CGRect keyboardFrame = CGRectMake(0, screenHeight - 320, screenWidth, 320);
800 NSNotification* fakeShowNotification =
801 [NSNotification notificationWithName:UIKeyboardWillShowNotification
804 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
805 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
806 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
808 [viewControllerMock handleKeyboardNotification:fakeShowNotification];
809 XCTAssertTrue(viewControllerMock.targetViewInsetBottom == 320 * screen.scale);
812 NSNotification* fakeHideNotification =
813 [NSNotification notificationWithName:UIKeyboardWillHideNotification
816 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
817 @"UIKeyboardAnimationDurationUserInfoKey" : @(0.0),
818 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
820 [viewControllerMock handleKeyboardNotification:fakeHideNotification];
821 XCTAssertTrue(viewControllerMock.targetViewInsetBottom == 0);
824 XCTAssertNil(viewControllerMock.keyboardAnimationView);
825 XCTAssertNil(viewControllerMock.keyboardSpringAnimation);
828- (void)testEnsureViewportMetricsWillInvokeAndDisplayLinkWillInvalidateInViewDidDisappear {
830 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
835 [viewControllerMock viewDidDisappear:YES];
836 OCMVerify([viewControllerMock ensureViewportMetricsIsCorrect]);
837 OCMVerify([viewControllerMock invalidateKeyboardAnimationVSyncClient]);
840- (void)testViewDidDisappearDoesntPauseEngineWhenNotTheViewController {
848 id viewControllerMock = OCMPartialMock(viewControllerA);
849 OCMStub([viewControllerMock surfaceUpdated:NO]);
851 [viewControllerA viewDidDisappear:NO];
852 OCMReject([lifecycleChannel sendMessage:
@"AppLifecycleState.paused"]);
853 OCMReject([viewControllerMock surfaceUpdated:[OCMArg any]]);
856- (void)testAppWillTerminateViewDidDestroyTheEngine {
858 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
863 OCMStub([viewControllerMock goToApplicationLifecycle:
@"AppLifecycleState.detached"]);
864 OCMStub([mockEngine destroyContext]);
865 [viewController applicationWillTerminate:nil];
866 OCMVerify([viewControllerMock goToApplicationLifecycle:
@"AppLifecycleState.detached"]);
867 OCMVerify([mockEngine destroyContext]);
870- (void)testViewDidDisappearDoesPauseEngineWhenIsTheViewController {
881 OCMStub([viewControllerMock surfaceUpdated:NO]);
882 [viewController viewDidDisappear:NO];
883 OCMVerify([lifecycleChannel sendMessage:
@"AppLifecycleState.paused"]);
884 OCMVerify([viewControllerMock surfaceUpdated:NO]);
886 XCTAssertNil(weakViewController);
890 testEngineConfigSyncMethodWillExecuteWhenViewControllerInEngineIsCurrentViewControllerInViewWillAppear {
892 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
896 [viewController viewWillAppear:YES];
901 testEngineConfigSyncMethodWillNotExecuteWhenViewControllerInEngineIsNotCurrentViewControllerInViewWillAppear {
903 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
913 [viewControllerA viewWillAppear:YES];
914 OCMVerify(never(), [viewControllerA onUserSettingsChanged:nil]);
918 testEngineConfigSyncMethodWillExecuteWhenViewControllerInEngineIsCurrentViewControllerInViewDidAppear {
920 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
924 [viewController viewDidAppear:YES];
929 testEngineConfigSyncMethodWillNotExecuteWhenViewControllerInEngineIsNotCurrentViewControllerInViewDidAppear {
931 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
941 [viewControllerA viewDidAppear:YES];
942 OCMVerify(never(), [viewControllerA onUserSettingsChanged:nil]);
946 testEngineConfigSyncMethodWillExecuteWhenViewControllerInEngineIsCurrentViewControllerInViewWillDisappear {
954 [viewController viewWillDisappear:NO];
955 OCMVerify([lifecycleChannel sendMessage:
@"AppLifecycleState.inactive"]);
959 testEngineConfigSyncMethodWillNotExecuteWhenViewControllerInEngineIsNotCurrentViewControllerInViewWillDisappear {
970 [viewControllerA viewDidDisappear:NO];
971 OCMReject([lifecycleChannel sendMessage:
@"AppLifecycleState.inactive"]);
974- (void)testUpdateViewportMetricsIfNeeded_DoesntInvokeEngineWhenNotTheViewController {
976 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
985 [viewControllerA updateViewportMetricsIfNeeded];
987 OCMVerify(never(), [mockEngine updateViewportMetrics:viewportMetrics]);
990- (void)testUpdateViewportMetricsIfNeeded_DoesInvokeEngineWhenIsTheViewController {
992 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
998 OCMExpect([mockEngine updateViewportMetrics:viewportMetrics]).ignoringNonObjectArgs();
999 [viewController updateViewportMetricsIfNeeded];
1000 OCMVerifyAll(mockEngine);
1003- (void)testUpdatedViewportMetricsDoesResizeFlutterViewWhenAutoResizable {
1005 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1010 id mockVC = OCMPartialMock(realVC);
1013 OCMExpect([mockVC updateAutoResizeConstraints]);
1015 [mockVC setAutoResizable:YES];
1017 [mockVC viewDidLayoutSubviews];
1019 OCMVerifyAll(mockVC);
1022- (void)testUpdatedViewportMetricsDoesNotResizeFlutterViewWhenNotAutoResizable {
1024 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1029 id mockVC = OCMPartialMock(realVC);
1032 OCMReject([mockVC updateAutoResizeConstraints]);
1034 [mockVC setAutoResizable:NO];
1036 [mockVC viewDidLayoutSubviews];
1038 OCMVerifyAll(mockVC);
1041- (void)testUpdateViewportMetricsIfNeeded_DoesNotInvokeEngineWhenShouldBeIgnoredDuringRotation {
1043 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1048 UIScreen* screen = [
self setUpMockScreen];
1049 OCMStub([viewControllerMock flutterScreenIfViewLoaded]).andReturn(screen);
1052 id mockCoordinator = OCMProtocolMock(
@protocol(UIViewControllerTransitionCoordinator));
1053 OCMStub([mockCoordinator transitionDuration]).andReturn(0.5);
1056 [viewController viewWillTransitionToSize:CGSizeZero withTransitionCoordinator:mockCoordinator];
1058 [viewController updateViewportMetricsIfNeeded];
1063- (void)testViewWillTransitionToSize_DoesDelayEngineCallIfNonZeroDuration {
1065 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1070 UIScreen* screen = [
self setUpMockScreen];
1071 OCMStub([viewControllerMock flutterScreenIfViewLoaded]).andReturn(screen);
1075 NSTimeInterval transitionDuration = 0.5;
1076 id mockCoordinator = OCMProtocolMock(
@protocol(UIViewControllerTransitionCoordinator));
1077 OCMStub([mockCoordinator transitionDuration]).andReturn(transitionDuration);
1080 OCMExpect([mockEngine updateViewportMetrics:viewportMetrics]).ignoringNonObjectArgs();
1082 [viewController viewWillTransitionToSize:CGSizeZero withTransitionCoordinator:mockCoordinator];
1084 [viewController updateViewportMetricsIfNeeded];
1089 XCTWaiterResult result = [XCTWaiter
1090 waitForExpectations:@[ [
self expectationWithDescription:@"Waiting for rotation duration"] ]
1091 timeout:transitionDuration];
1092 XCTAssertEqual(result, XCTWaiterResultTimedOut);
1094 OCMVerifyAll(mockEngine);
1097- (void)testViewWillTransitionToSize_DoesNotDelayEngineCallIfZeroDuration {
1099 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1104 UIScreen* screen = [
self setUpMockScreen];
1105 OCMStub([viewControllerMock flutterScreenIfViewLoaded]).andReturn(screen);
1109 id mockCoordinator = OCMProtocolMock(
@protocol(UIViewControllerTransitionCoordinator));
1110 OCMStub([mockCoordinator transitionDuration]).andReturn(0);
1113 OCMExpect([mockEngine updateViewportMetrics:viewportMetrics]).ignoringNonObjectArgs();
1116 [viewController viewWillTransitionToSize:CGSizeZero withTransitionCoordinator:mockCoordinator];
1117 [viewController updateViewportMetricsIfNeeded];
1119 OCMVerifyAll(mockEngine);
1122- (void)testViewDidLoadDoesntInvokeEngineWhenNotTheViewController {
1124 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1133 UIView*
view = viewControllerA.view;
1134 XCTAssertNotNil(
view);
1135 OCMVerify(never(), [mockEngine attachView]);
1138- (void)testViewDidLoadDoesInvokeEngineWhenIsTheViewController {
1140 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1147 XCTAssertNotNil(
view);
1148 OCMVerify(times(1), [mockEngine attachView]);
1151- (void)testViewDidLoadDoesntInvokeEngineAttachViewWhenEngineNeedsLaunch {
1153 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1159 [viewController sharedSetupWithProject:nil initialRoute:nil];
1162 XCTAssertNotNil(
view);
1163 OCMVerify(never(), [mockEngine attachView]);
1166- (void)testSplashScreenViewRemoveNotCrash {
1171 [flutterViewController setSplashScreenView:[[UIView alloc] init]];
1172 [flutterViewController setSplashScreenView:nil];
1175- (void)testInternalPluginsWeakPtrNotCrash {
1181 [vc addInternalPlugins];
1184 [(NSArray<id<FlutterKeyPrimaryResponder>>*)keyboardManager.primaryResponders firstObject];
1185 sendEvent = [keyPrimaryResponder sendEvent];
1189 sendEvent({}, nil, nil);
1194- (void)testInternalPluginsInvokeInViewDidLoad {
1196 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1204 XCTAssertNotNil(
view);
1205 [viewController viewDidLoad];
1209- (void)testBinaryMessenger {
1213 XCTAssertNotNil(vc);
1214 id messenger = OCMProtocolMock(
@protocol(FlutterBinaryMessenger));
1215 OCMStub([
self.mockEngine binaryMessenger]).andReturn(messenger);
1217 OCMVerify([
self.mockEngine binaryMessenger]);
1220- (void)testViewControllerIsReleased {
1222 __weak UIView* weakView;
1231 [viewController loadView];
1232 [viewController viewDidLoad];
1236 XCTAssertNil(weakViewController);
1237 XCTAssertNil(weakView);
1240#pragma mark - Platform Brightness
1242- (void)testItReportsLightPlatformBrightnessByDefault {
1245 OCMStub([
self.mockEngine settingsChannel]).andReturn(settingsChannel);
1252 [vc traitCollectionDidChange:nil];
1255 OCMVerify([settingsChannel sendMessage:[OCMArg checkWithBlock:^
BOOL(
id message) {
1256 return [message[@"platformBrightness"] isEqualToString:@"light"];
1260 [settingsChannel stopMocking];
1263- (void)testItReportsPlatformBrightnessWhenViewWillAppear {
1267 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1268 OCMStub([mockEngine settingsChannel]).andReturn(settingsChannel);
1274 [vc viewWillAppear:false];
1277 OCMVerify([settingsChannel sendMessage:[OCMArg checkWithBlock:^
BOOL(
id message) {
1278 return [message[@"platformBrightness"] isEqualToString:@"light"];
1282 [settingsChannel stopMocking];
1285- (void)testItReportsDarkPlatformBrightnessWhenTraitCollectionRequestsIt {
1288 OCMStub([
self.mockEngine settingsChannel]).andReturn(settingsChannel);
1289 id mockTraitCollection =
1290 [
self fakeTraitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleDark];
1299 OCMStub([partialMockVC traitCollection]).andReturn(mockTraitCollection);
1302 [partialMockVC traitCollectionDidChange:nil];
1305 OCMVerify([settingsChannel sendMessage:[OCMArg checkWithBlock:^
BOOL(
id message) {
1306 return [message[@"platformBrightness"] isEqualToString:@"dark"];
1310 [partialMockVC stopMocking];
1311 [settingsChannel stopMocking];
1312 [mockTraitCollection stopMocking];
1317- (UITraitCollection*)fakeTraitCollectionWithUserInterfaceStyle:(UIUserInterfaceStyle)style {
1318 id mockTraitCollection = OCMClassMock([UITraitCollection
class]);
1319 OCMStub([mockTraitCollection userInterfaceStyle]).andReturn(style);
1320 return mockTraitCollection;
1323- (void)testTraitCollectionDidChangeCallsResetIntrinsicContentSizeWhenAutoResizable {
1325 id mockEngine = OCMPartialMock([[
FlutterEngine alloc] init]);
1326 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1331 id partialMockVC = OCMPartialMock(realVC);
1333 id mockFlutterView = OCMClassMock([
FlutterView class]);
1334 OCMStub([partialMockVC flutterView]).andReturn(mockFlutterView);
1337 OCMStub([partialMockVC isAutoResizable]).andReturn(YES);
1340 OCMExpect([mockFlutterView resetIntrinsicContentSize]);
1343 [partialMockVC traitCollectionDidChange:nil];
1346 OCMVerifyAll(mockFlutterView);
1349 [partialMockVC stopMocking];
1350 [mockFlutterView stopMocking];
1353#pragma mark - Platform Contrast
1355- (void)testItReportsNormalPlatformContrastByDefault {
1358 OCMStub([
self.mockEngine settingsChannel]).andReturn(settingsChannel);
1365 [vc traitCollectionDidChange:nil];
1368 OCMVerify([settingsChannel sendMessage:[OCMArg checkWithBlock:^
BOOL(
id message) {
1369 return [message[@"platformContrast"] isEqualToString:@"normal"];
1373 [settingsChannel stopMocking];
1376- (void)testItReportsPlatformContrastWhenViewWillAppear {
1378 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1382 OCMStub([mockEngine settingsChannel]).andReturn(settingsChannel);
1388 [vc viewWillAppear:false];
1391 OCMVerify([settingsChannel sendMessage:[OCMArg checkWithBlock:^
BOOL(
id message) {
1392 return [message[@"platformContrast"] isEqualToString:@"normal"];
1396 [settingsChannel stopMocking];
1399- (void)testItReportsHighContrastWhenTraitCollectionRequestsIt {
1402 OCMStub([
self.mockEngine settingsChannel]).andReturn(settingsChannel);
1404 id mockTraitCollection = [
self fakeTraitCollectionWithContrast:UIAccessibilityContrastHigh];
1413 OCMStub([partialMockVC traitCollection]).andReturn(mockTraitCollection);
1416 [partialMockVC traitCollectionDidChange:mockTraitCollection];
1419 OCMVerify([settingsChannel sendMessage:[OCMArg checkWithBlock:^
BOOL(
id message) {
1420 return [message[@"platformContrast"] isEqualToString:@"high"];
1424 [partialMockVC stopMocking];
1425 [settingsChannel stopMocking];
1426 [mockTraitCollection stopMocking];
1429- (void)testItReportsAlwaysUsed24HourFormat {
1432 OCMStub([
self.mockEngine settingsChannel]).andReturn(settingsChannel);
1438 OCMStub([mockHourFormat isAlwaysUse24HourFormat]).andReturn(YES);
1439 OCMExpect([settingsChannel sendMessage:[OCMArg checkWithBlock:^
BOOL(
id message) {
1440 return [message[@"alwaysUse24HourFormat"] isEqual:@(YES)];
1442 [vc onUserSettingsChanged:nil];
1443 [mockHourFormat stopMocking];
1447 OCMStub([mockHourFormat isAlwaysUse24HourFormat]).andReturn(NO);
1448 OCMExpect([settingsChannel sendMessage:[OCMArg checkWithBlock:^
BOOL(
id message) {
1449 return [message[@"alwaysUse24HourFormat"] isEqual:@(NO)];
1451 [vc onUserSettingsChanged:nil];
1452 [mockHourFormat stopMocking];
1455 [settingsChannel stopMocking];
1458- (void)testOnAccessibilityStatusChangedCallsEnableSemanticsWithFlags {
1461 id mockAccessibilityFeatures = OCMClassMock([FlutterAccessibilityFeatures
class]);
1462 OCMStub([mockAccessibilityFeatures flags]).andReturn(333);
1464 OCMStub([mockViewController accessibilityFeatures]).andReturn(mockAccessibilityFeatures);
1466 [mockViewController onAccessibilityStatusChanged:nil];
1467 OCMVerify([
self.mockEngine enableSemantics:[OCMArg any] withFlags:333]);
1470- (void)testHandleAccessibilityNotifications {
1474 __block NSUInteger callsCount = 0;
1475 OCMStub([mockViewController onAccessibilityStatusChanged:[OCMArg isNotNil]])
1476 .andDo(^(NSInvocation* invocation) {
1480 FlutterAccessibilityFeatures* accessibilityFeatures = [[FlutterAccessibilityFeatures alloc] init];
1481 NSArray<NSString*>* accessibilityNotification = [accessibilityFeatures observedNotificationNames];
1483 for (NSUInteger
i = 0;
i < [accessibilityNotification count];
i++) {
1484 NSString* notificationName = [accessibilityNotification objectAtIndex:i];
1485 [[NSNotificationCenter defaultCenter] postNotificationName:notificationName object:nil];
1486 XCTAssertEqual(callsCount,
i + 1);
1490- (void)testAccessibilityPerformEscapePopsRoute {
1492 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1494 OCMStub([mockEngine navigationChannel]).andReturn(mockNavigationChannel);
1501 OCMVerify([mockNavigationChannel invokeMethod:
@"popRoute" arguments:nil]);
1503 [mockNavigationChannel stopMocking];
1506- (void)testPerformOrientationUpdateForcesOrientationChange {
1507 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortrait
1508 currentOrientation:UIInterfaceOrientationLandscapeLeft
1509 didChangeOrientation:YES
1510 resultingOrientation:UIInterfaceOrientationPortrait];
1512 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortrait
1513 currentOrientation:UIInterfaceOrientationLandscapeRight
1514 didChangeOrientation:YES
1515 resultingOrientation:UIInterfaceOrientationPortrait];
1517 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortrait
1518 currentOrientation:UIInterfaceOrientationPortraitUpsideDown
1519 didChangeOrientation:YES
1520 resultingOrientation:UIInterfaceOrientationPortrait];
1522 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortraitUpsideDown
1523 currentOrientation:UIInterfaceOrientationLandscapeLeft
1524 didChangeOrientation:YES
1525 resultingOrientation:UIInterfaceOrientationPortraitUpsideDown];
1527 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortraitUpsideDown
1528 currentOrientation:UIInterfaceOrientationLandscapeRight
1529 didChangeOrientation:YES
1530 resultingOrientation:UIInterfaceOrientationPortraitUpsideDown];
1532 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortraitUpsideDown
1533 currentOrientation:UIInterfaceOrientationPortrait
1534 didChangeOrientation:YES
1535 resultingOrientation:UIInterfaceOrientationPortraitUpsideDown];
1537 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscape
1538 currentOrientation:UIInterfaceOrientationPortrait
1539 didChangeOrientation:YES
1540 resultingOrientation:UIInterfaceOrientationLandscapeLeft];
1542 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscape
1543 currentOrientation:UIInterfaceOrientationPortraitUpsideDown
1544 didChangeOrientation:YES
1545 resultingOrientation:UIInterfaceOrientationLandscapeLeft];
1547 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeLeft
1548 currentOrientation:UIInterfaceOrientationPortrait
1549 didChangeOrientation:YES
1550 resultingOrientation:UIInterfaceOrientationLandscapeLeft];
1552 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeLeft
1553 currentOrientation:UIInterfaceOrientationLandscapeRight
1554 didChangeOrientation:YES
1555 resultingOrientation:UIInterfaceOrientationLandscapeLeft];
1557 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeLeft
1558 currentOrientation:UIInterfaceOrientationPortraitUpsideDown
1559 didChangeOrientation:YES
1560 resultingOrientation:UIInterfaceOrientationLandscapeLeft];
1562 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeRight
1563 currentOrientation:UIInterfaceOrientationPortrait
1564 didChangeOrientation:YES
1565 resultingOrientation:UIInterfaceOrientationLandscapeRight];
1567 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeRight
1568 currentOrientation:UIInterfaceOrientationLandscapeLeft
1569 didChangeOrientation:YES
1570 resultingOrientation:UIInterfaceOrientationLandscapeRight];
1572 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeRight
1573 currentOrientation:UIInterfaceOrientationPortraitUpsideDown
1574 didChangeOrientation:YES
1575 resultingOrientation:UIInterfaceOrientationLandscapeRight];
1577 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAllButUpsideDown
1578 currentOrientation:UIInterfaceOrientationPortraitUpsideDown
1579 didChangeOrientation:YES
1580 resultingOrientation:UIInterfaceOrientationPortrait];
1583- (void)testPerformOrientationUpdateDoesNotForceOrientationChange {
1584 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAll
1585 currentOrientation:UIInterfaceOrientationPortrait
1586 didChangeOrientation:NO
1587 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1589 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAll
1590 currentOrientation:UIInterfaceOrientationPortraitUpsideDown
1591 didChangeOrientation:NO
1592 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1594 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAll
1595 currentOrientation:UIInterfaceOrientationLandscapeLeft
1596 didChangeOrientation:NO
1597 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1599 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAll
1600 currentOrientation:UIInterfaceOrientationLandscapeRight
1601 didChangeOrientation:NO
1602 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1604 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAllButUpsideDown
1605 currentOrientation:UIInterfaceOrientationPortrait
1606 didChangeOrientation:NO
1607 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1609 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAllButUpsideDown
1610 currentOrientation:UIInterfaceOrientationLandscapeLeft
1611 didChangeOrientation:NO
1612 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1614 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAllButUpsideDown
1615 currentOrientation:UIInterfaceOrientationLandscapeRight
1616 didChangeOrientation:NO
1617 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1619 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortrait
1620 currentOrientation:UIInterfaceOrientationPortrait
1621 didChangeOrientation:NO
1622 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1624 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortraitUpsideDown
1625 currentOrientation:UIInterfaceOrientationPortraitUpsideDown
1626 didChangeOrientation:NO
1627 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1629 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscape
1630 currentOrientation:UIInterfaceOrientationLandscapeLeft
1631 didChangeOrientation:NO
1632 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1634 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscape
1635 currentOrientation:UIInterfaceOrientationLandscapeRight
1636 didChangeOrientation:NO
1637 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1639 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeLeft
1640 currentOrientation:UIInterfaceOrientationLandscapeLeft
1641 didChangeOrientation:NO
1642 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1644 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeRight
1645 currentOrientation:UIInterfaceOrientationLandscapeRight
1646 didChangeOrientation:NO
1647 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1652- (void)orientationTestWithOrientationUpdate:(UIInterfaceOrientationMask)mask
1653 currentOrientation:(UIInterfaceOrientation)currentOrientation
1654 didChangeOrientation:(
BOOL)didChange
1655 resultingOrientation:(UIInterfaceOrientation)resultingOrientation {
1656 id mockApplication = OCMClassMock([UIApplication
class]);
1660 __block __weak
id weakPreferences;
1666 if (@available(iOS 16.0, *)) {
1667 mockWindowScene = OCMClassMock([UIWindowScene
class]);
1668 mockVC = OCMPartialMock(realVC);
1669 OCMStub([mockVC flutterWindowSceneIfViewLoaded]).andReturn(mockWindowScene);
1670 if (realVC.supportedInterfaceOrientations == mask) {
1671 OCMReject([mockWindowScene requestGeometryUpdateWithPreferences:[OCMArg any]
1672 errorHandler:[OCMArg any]]);
1676 OCMExpect([mockWindowScene
1677 requestGeometryUpdateWithPreferences:[OCMArg checkWithBlock:^
BOOL(
1678 UIWindowSceneGeometryPreferencesIOS*
1680 weakPreferences = preferences;
1681 return preferences.interfaceOrientations == mask;
1683 errorHandler:[OCMArg any]]);
1685 OCMStub([mockApplication sharedApplication]).andReturn(mockApplication);
1686 OCMStub([mockApplication connectedScenes]).andReturn([NSSet setWithObject:mockWindowScene]);
1688 deviceMock = OCMPartialMock([UIDevice currentDevice]);
1690 OCMReject([deviceMock setValue:[OCMArg any] forKey:
@"orientation"]);
1692 OCMExpect([deviceMock setValue:@(resultingOrientation) forKey:
@"orientation"]);
1694 mockWindowScene = OCMClassMock([UIWindowScene
class]);
1695 mockVC = OCMPartialMock(realVC);
1696 OCMStub([mockVC flutterWindowSceneIfViewLoaded]).andReturn(mockWindowScene);
1697 OCMStub(((UIWindowScene*)mockWindowScene).interfaceOrientation).andReturn(currentOrientation);
1700 [realVC performOrientationUpdate:mask];
1701 if (@available(iOS 16.0, *)) {
1702 OCMVerifyAll(mockWindowScene);
1704 OCMVerifyAll(deviceMock);
1707 [mockWindowScene stopMocking];
1708 [deviceMock stopMocking];
1709 [mockApplication stopMocking];
1710 XCTAssertNil(weakPreferences);
1715- (UITraitCollection*)fakeTraitCollectionWithContrast:(UIAccessibilityContrast)contrast {
1716 id mockTraitCollection = OCMClassMock([UITraitCollection
class]);
1717 OCMStub([mockTraitCollection accessibilityContrast]).andReturn(contrast);
1718 return mockTraitCollection;
1721- (void)testWillDeallocNotification {
1722 XCTestExpectation* expectation =
1723 [[XCTestExpectation alloc] initWithDescription:@"notification called"];
1730 [NSNotificationCenter.defaultCenter addObserverForName:FlutterViewControllerWillDealloc
1732 queue:[NSOperationQueue mainQueue]
1733 usingBlock:^(NSNotification* _Nonnull note) {
1734 [expectation fulfill];
1736 XCTAssertNotNil(realVC);
1739 [
self waitForExpectations:@[ expectation ] timeout:1.0];
1742- (void)testReleasesKeyboardManagerOnDealloc {
1747 [viewController addInternalPlugins];
1749 XCTAssertNotNil(weakKeyboardManager);
1750 [viewController deregisterNotifications];
1754 XCTAssertNil(weakKeyboardManager);
1757- (void)testDoesntLoadViewInInit {
1760 [engine createShell:@"" libraryURI:@"" initialRoute:nil];
1764 XCTAssertFalse([realVC isViewLoaded],
@"shouldn't have loaded since it hasn't been shown");
1768- (void)testHideOverlay {
1771 [engine createShell:@"" libraryURI:@"" initialRoute:nil];
1775 XCTAssertFalse(realVC.prefersHomeIndicatorAutoHidden,
@"");
1776 [NSNotificationCenter.defaultCenter postNotificationName:FlutterViewControllerHideHomeIndicator
1778 XCTAssertTrue(realVC.prefersHomeIndicatorAutoHidden,
@"");
1782- (void)testNotifyLowMemory {
1788 OCMStub([viewControllerMock surfaceUpdated:NO]);
1789 [viewController beginAppearanceTransition:NO animated:NO];
1790 [viewController endAppearanceTransition];
1794- (void)sendMessage:(
id _Nullable)message reply:(
FlutterReply _Nullable)callback {
1795 NSMutableDictionary* replyMessage = [@{
1808 if (@available(iOS 13.4, *)) {
1815 OCMStub([mockEngine.
keyEventChannel sendMessage:[OCMArg any] reply:[OCMArg any]])
1816 .andCall(
self, @selector(sendMessage:reply:));
1817 OCMStub([
self.mockTextInputPlugin handlePress:[OCMArg any]]).andReturn(YES);
1826 [vc addInternalPlugins];
1828 [vc handlePressEvent:keyUpEvent(UIKeyboardHIDUsageKeyboardA, UIKeyModifierShift, 123.0)
1832 XCTAssert(
self.messageSent != nil);
1833 XCTAssert([
self.messageSent[
@"keymap"] isEqualToString:
@"ios"]);
1834 XCTAssert([
self.messageSent[
@"type"] isEqualToString:
@"keyup"]);
1835 XCTAssert([
self.messageSent[
@"keyCode"] isEqualToNumber:[NSNumber numberWithInt:4]]);
1836 XCTAssert([
self.messageSent[
@"modifiers"] isEqualToNumber:[NSNumber numberWithInt:0]]);
1837 XCTAssert([
self.messageSent[
@"characters"] isEqualToString:
@""]);
1838 XCTAssert([
self.messageSent[
@"charactersIgnoringModifiers"] isEqualToString:
@""]);
1839 [vc deregisterNotifications];
1843 if (@available(iOS 13.4, *)) {
1851 OCMStub([mockEngine.
keyEventChannel sendMessage:[OCMArg any] reply:[OCMArg any]])
1852 .andCall(
self, @selector(sendMessage:reply:));
1853 OCMStub([
self.mockTextInputPlugin handlePress:[OCMArg any]]).andReturn(YES);
1861 [vc addInternalPlugins];
1863 [vc handlePressEvent:keyDownEvent(UIKeyboardHIDUsageKeyboardA, UIKeyModifierShift, 123.0f, "A",
1868 XCTAssert(
self.messageSent != nil);
1869 XCTAssert([
self.messageSent[
@"keymap"] isEqualToString:
@"ios"]);
1870 XCTAssert([
self.messageSent[
@"type"] isEqualToString:
@"keydown"]);
1871 XCTAssert([
self.messageSent[
@"keyCode"] isEqualToNumber:[NSNumber numberWithInt:4]]);
1872 XCTAssert([
self.messageSent[
@"modifiers"] isEqualToNumber:[NSNumber numberWithInt:0]]);
1873 XCTAssert([
self.messageSent[
@"characters"] isEqualToString:
@"A"]);
1874 XCTAssert([
self.messageSent[
@"charactersIgnoringModifiers"] isEqualToString:
@"a"]);
1875 [vc deregisterNotifications];
1880 if (@available(iOS 13.4, *)) {
1886 OCMStub([keyEventChannel sendMessage:[OCMArg any] reply:[OCMArg any]])
1887 .andCall(
self, @selector(sendMessage:reply:));
1888 OCMStub([
self.mockTextInputPlugin handlePress:[OCMArg any]]).andReturn(YES);
1889 OCMStub([
self.mockEngine keyEventChannel]).andReturn(keyEventChannel);
1897 [vc addInternalPlugins];
1899 [vc handlePressEvent:keyEventWithPhase(UIPressPhaseStationary, UIKeyboardHIDUsageKeyboardA,
1900 UIKeyModifierShift, 123.0)
1903 [vc handlePressEvent:keyEventWithPhase(UIPressPhaseCancelled, UIKeyboardHIDUsageKeyboardA,
1904 UIKeyModifierShift, 123.0)
1907 [vc handlePressEvent:keyEventWithPhase(UIPressPhaseChanged, UIKeyboardHIDUsageKeyboardA,
1908 UIKeyModifierShift, 123.0)
1912 XCTAssert(
self.messageSent == nil);
1913 OCMVerify(never(), [keyEventChannel sendMessage:[OCMArg any]]);
1914 [vc deregisterNotifications];
1918 if (@available(iOS 13.4, *)) {
1927 XCTAssertNotNil(vc);
1928 UIView*
view = vc.view;
1929 XCTAssertNotNil(
view);
1930 NSArray* gestureRecognizers =
view.gestureRecognizers;
1931 XCTAssertNotNil(gestureRecognizers);
1934 for (
id gesture in gestureRecognizers) {
1935 if ([gesture isKindOfClass:[UIPanGestureRecognizer class]]) {
1940 XCTAssertTrue(found);
1944 if (@available(iOS 13.4, *)) {
1953 XCTAssertNotNil(vc);
1955 id mockPanGestureRecognizer = OCMClassMock([UIPanGestureRecognizer
class]);
1956 XCTAssertNotNil(mockPanGestureRecognizer);
1958 [vc discreteScrollEvent:mockPanGestureRecognizer];
1961 [[mockPanGestureRecognizer verify] locationInView:[OCMArg any]];
1962 [[[
self.mockEngine verify] ignoringNonObjectArgs]
1963 dispatchPointerDataPacket:std::make_unique<flutter::PointerDataPacket>(0)];
1966- (void)testFakeEventTimeStamp {
1970 XCTAssertNotNil(vc);
1973 int64_t current_micros = [[NSProcessInfo processInfo] systemUptime] * 1000 * 1000;
1974 int64_t interval_micros = current_micros - pointer_data.time_stamp;
1975 const int64_t tolerance_millis = 2;
1976 XCTAssertTrue(interval_micros / 1000 < tolerance_millis,
1977 @"PointerData.time_stamp should be equal to NSProcessInfo.systemUptime");
1980- (void)testSplashScreenViewCanSetNil {
1983 [flutterViewController setSplashScreenView:nil];
1986- (void)testLifeCycleNotificationApplicationBecameActive {
1991 UIWindow*
window = [[UIWindow alloc] init];
1992 [window addSubview:flutterViewController.view];
1993 flutterViewController.view.bounds = CGRectMake(0, 0, 100, 100);
1994 [flutterViewController viewDidLayoutSubviews];
1995 NSNotification* sceneNotification =
1996 [NSNotification notificationWithName:UISceneDidActivateNotification object:nil userInfo:nil];
1997 NSNotification* applicationNotification =
1998 [NSNotification notificationWithName:UIApplicationDidBecomeActiveNotification
2001 id mockVC = OCMPartialMock(flutterViewController);
2002 [NSNotificationCenter.defaultCenter postNotification:sceneNotification];
2003 [NSNotificationCenter.defaultCenter postNotification:applicationNotification];
2004 OCMReject([mockVC sceneBecameActive:[OCMArg any]]);
2005 OCMVerify([mockVC applicationBecameActive:[OCMArg any]]);
2006 XCTAssertFalse(flutterViewController.isKeyboardInOrTransitioningFromBackground);
2007 OCMVerify([mockVC surfaceUpdated:YES]);
2008 XCTestExpectation* timeoutApplicationLifeCycle =
2009 [
self expectationWithDescription:@"timeoutApplicationLifeCycle"];
2010 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 *
NSEC_PER_SEC)),
2011 dispatch_get_main_queue(), ^{
2012 [timeoutApplicationLifeCycle fulfill];
2013 OCMVerify([mockVC goToApplicationLifecycle:
@"AppLifecycleState.resumed"]);
2014 [flutterViewController deregisterNotifications];
2016 [
self waitForExpectationsWithTimeout:5.0 handler:nil];
2019- (void)testLifeCycleNotificationSceneBecameActive {
2020 id mockBundle = OCMPartialMock([NSBundle mainBundle]);
2021 OCMStub([mockBundle objectForInfoDictionaryKey:
@"NSExtension"]).andReturn(@{
2022 @"NSExtensionPointIdentifier" :
@"com.apple.share-services"
2025 [engine runWithEntrypoint:nil];
2028 UIWindow*
window = [[UIWindow alloc] init];
2029 [window addSubview:flutterViewController.view];
2030 flutterViewController.view.bounds = CGRectMake(0, 0, 100, 100);
2031 [flutterViewController viewDidLayoutSubviews];
2032 NSNotification* sceneNotification =
2033 [NSNotification notificationWithName:UISceneDidActivateNotification object:nil userInfo:nil];
2034 NSNotification* applicationNotification =
2035 [NSNotification notificationWithName:UIApplicationDidBecomeActiveNotification
2038 id mockVC = OCMPartialMock(flutterViewController);
2039 [NSNotificationCenter.defaultCenter postNotification:sceneNotification];
2040 [NSNotificationCenter.defaultCenter postNotification:applicationNotification];
2041 OCMVerify([mockVC sceneBecameActive:[OCMArg any]]);
2042 OCMReject([mockVC applicationBecameActive:[OCMArg any]]);
2043 XCTAssertFalse(flutterViewController.isKeyboardInOrTransitioningFromBackground);
2044 OCMVerify([mockVC surfaceUpdated:YES]);
2045 XCTestExpectation* timeoutApplicationLifeCycle =
2046 [
self expectationWithDescription:@"timeoutApplicationLifeCycle"];
2047 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 *
NSEC_PER_SEC)),
2048 dispatch_get_main_queue(), ^{
2049 [timeoutApplicationLifeCycle fulfill];
2050 OCMVerify([mockVC goToApplicationLifecycle:
@"AppLifecycleState.resumed"]);
2051 [flutterViewController deregisterNotifications];
2053 [
self waitForExpectationsWithTimeout:5.0 handler:nil];
2054 [mockBundle stopMocking];
2057- (void)testLifeCycleNotificationApplicationWillResignActive {
2062 NSNotification* sceneNotification =
2063 [NSNotification notificationWithName:UISceneWillDeactivateNotification
2066 NSNotification* applicationNotification =
2067 [NSNotification notificationWithName:UIApplicationWillResignActiveNotification
2070 id mockVC = OCMPartialMock(flutterViewController);
2071 [NSNotificationCenter.defaultCenter postNotification:sceneNotification];
2072 [NSNotificationCenter.defaultCenter postNotification:applicationNotification];
2073 OCMReject([mockVC sceneWillResignActive:[OCMArg any]]);
2074 OCMVerify([mockVC applicationWillResignActive:[OCMArg any]]);
2075 OCMVerify([mockVC goToApplicationLifecycle:
@"AppLifecycleState.inactive"]);
2076 [flutterViewController deregisterNotifications];
2079- (void)testLifeCycleNotificationSceneWillResignActive {
2080 id mockBundle = OCMPartialMock([NSBundle mainBundle]);
2081 OCMStub([mockBundle objectForInfoDictionaryKey:
@"NSExtension"]).andReturn(@{
2082 @"NSExtensionPointIdentifier" :
@"com.apple.share-services"
2085 [engine runWithEntrypoint:nil];
2088 NSNotification* sceneNotification =
2089 [NSNotification notificationWithName:UISceneWillDeactivateNotification
2092 NSNotification* applicationNotification =
2093 [NSNotification notificationWithName:UIApplicationWillResignActiveNotification
2096 id mockVC = OCMPartialMock(flutterViewController);
2097 [NSNotificationCenter.defaultCenter postNotification:sceneNotification];
2098 [NSNotificationCenter.defaultCenter postNotification:applicationNotification];
2099 OCMVerify([mockVC sceneWillResignActive:[OCMArg any]]);
2100 OCMReject([mockVC applicationWillResignActive:[OCMArg any]]);
2101 OCMVerify([mockVC goToApplicationLifecycle:
@"AppLifecycleState.inactive"]);
2102 [flutterViewController deregisterNotifications];
2103 [mockBundle stopMocking];
2106- (void)testLifeCycleNotificationApplicationWillTerminate {
2111 NSNotification* sceneNotification =
2112 [NSNotification notificationWithName:UISceneDidDisconnectNotification
2115 NSNotification* applicationNotification =
2116 [NSNotification notificationWithName:UIApplicationWillTerminateNotification
2119 id mockVC = OCMPartialMock(flutterViewController);
2120 id mockEngine = OCMPartialMock(
engine);
2121 OCMStub([mockVC
engine]).andReturn(mockEngine);
2122 [NSNotificationCenter.defaultCenter postNotification:sceneNotification];
2123 [NSNotificationCenter.defaultCenter postNotification:applicationNotification];
2124 OCMReject([mockVC sceneWillDisconnect:[OCMArg any]]);
2125 OCMVerify([mockVC applicationWillTerminate:[OCMArg any]]);
2126 OCMVerify([mockVC goToApplicationLifecycle:
@"AppLifecycleState.detached"]);
2127 OCMVerify([mockEngine destroyContext]);
2128 [flutterViewController deregisterNotifications];
2131- (void)testLifeCycleNotificationSceneWillTerminate {
2132 id mockBundle = OCMPartialMock([NSBundle mainBundle]);
2133 OCMStub([mockBundle objectForInfoDictionaryKey:
@"NSExtension"]).andReturn(@{
2134 @"NSExtensionPointIdentifier" :
@"com.apple.share-services"
2137 [engine runWithEntrypoint:nil];
2140 NSNotification* sceneNotification =
2141 [NSNotification notificationWithName:UISceneDidDisconnectNotification
2144 NSNotification* applicationNotification =
2145 [NSNotification notificationWithName:UIApplicationWillTerminateNotification
2148 id mockVC = OCMPartialMock(flutterViewController);
2149 id mockEngine = OCMPartialMock(
engine);
2150 OCMStub([mockVC
engine]).andReturn(mockEngine);
2151 [NSNotificationCenter.defaultCenter postNotification:sceneNotification];
2152 [NSNotificationCenter.defaultCenter postNotification:applicationNotification];
2153 OCMVerify([mockVC sceneWillDisconnect:[OCMArg any]]);
2154 OCMReject([mockVC applicationWillTerminate:[OCMArg any]]);
2155 OCMVerify([mockVC goToApplicationLifecycle:
@"AppLifecycleState.detached"]);
2156 OCMVerify([mockEngine destroyContext]);
2157 [flutterViewController deregisterNotifications];
2158 [mockBundle stopMocking];
2161- (void)testLifeCycleNotificationApplicationDidEnterBackground {
2166 NSNotification* sceneNotification =
2167 [NSNotification notificationWithName:UISceneDidEnterBackgroundNotification
2170 NSNotification* applicationNotification =
2171 [NSNotification notificationWithName:UIApplicationDidEnterBackgroundNotification
2174 id mockVC = OCMPartialMock(flutterViewController);
2175 [NSNotificationCenter.defaultCenter postNotification:sceneNotification];
2176 [NSNotificationCenter.defaultCenter postNotification:applicationNotification];
2177 OCMReject([mockVC sceneDidEnterBackground:[OCMArg any]]);
2178 OCMVerify([mockVC applicationDidEnterBackground:[OCMArg any]]);
2179 XCTAssertTrue(flutterViewController.isKeyboardInOrTransitioningFromBackground);
2180 OCMVerify([mockVC surfaceUpdated:NO]);
2181 OCMVerify([mockVC goToApplicationLifecycle:
@"AppLifecycleState.paused"]);
2182 [flutterViewController deregisterNotifications];
2185- (void)testLifeCycleNotificationSceneDidEnterBackground {
2186 id mockBundle = OCMPartialMock([NSBundle mainBundle]);
2187 OCMStub([mockBundle objectForInfoDictionaryKey:
@"NSExtension"]).andReturn(@{
2188 @"NSExtensionPointIdentifier" :
@"com.apple.share-services"
2191 [engine runWithEntrypoint:nil];
2194 NSNotification* sceneNotification =
2195 [NSNotification notificationWithName:UISceneDidEnterBackgroundNotification
2198 NSNotification* applicationNotification =
2199 [NSNotification notificationWithName:UIApplicationDidEnterBackgroundNotification
2202 id mockVC = OCMPartialMock(flutterViewController);
2203 [NSNotificationCenter.defaultCenter postNotification:sceneNotification];
2204 [NSNotificationCenter.defaultCenter postNotification:applicationNotification];
2205 OCMVerify([mockVC sceneDidEnterBackground:[OCMArg any]]);
2206 OCMReject([mockVC applicationDidEnterBackground:[OCMArg any]]);
2207 XCTAssertTrue(flutterViewController.isKeyboardInOrTransitioningFromBackground);
2208 OCMVerify([mockVC surfaceUpdated:NO]);
2209 OCMVerify([mockVC goToApplicationLifecycle:
@"AppLifecycleState.paused"]);
2210 [flutterViewController deregisterNotifications];
2211 [mockBundle stopMocking];
2214- (void)testLifeCycleNotificationApplicationWillEnterForeground {
2219 NSNotification* sceneNotification =
2220 [NSNotification notificationWithName:UISceneWillEnterForegroundNotification
2223 NSNotification* applicationNotification =
2224 [NSNotification notificationWithName:UIApplicationWillEnterForegroundNotification
2227 id mockVC = OCMPartialMock(flutterViewController);
2228 [NSNotificationCenter.defaultCenter postNotification:sceneNotification];
2229 [NSNotificationCenter.defaultCenter postNotification:applicationNotification];
2230 OCMReject([mockVC sceneWillEnterForeground:[OCMArg any]]);
2231 OCMVerify([mockVC applicationWillEnterForeground:[OCMArg any]]);
2232 OCMVerify([mockVC goToApplicationLifecycle:
@"AppLifecycleState.inactive"]);
2233 [flutterViewController deregisterNotifications];
2236- (void)testLifeCycleNotificationSceneWillEnterForeground {
2237 id mockBundle = OCMPartialMock([NSBundle mainBundle]);
2238 OCMStub([mockBundle objectForInfoDictionaryKey:
@"NSExtension"]).andReturn(@{
2239 @"NSExtensionPointIdentifier" :
@"com.apple.share-services"
2242 [engine runWithEntrypoint:nil];
2245 NSNotification* sceneNotification =
2246 [NSNotification notificationWithName:UISceneWillEnterForegroundNotification
2249 NSNotification* applicationNotification =
2250 [NSNotification notificationWithName:UIApplicationWillEnterForegroundNotification
2253 id mockVC = OCMPartialMock(flutterViewController);
2254 [NSNotificationCenter.defaultCenter postNotification:sceneNotification];
2255 [NSNotificationCenter.defaultCenter postNotification:applicationNotification];
2256 OCMVerify([mockVC sceneWillEnterForeground:[OCMArg any]]);
2257 OCMReject([mockVC applicationWillEnterForeground:[OCMArg any]]);
2258 OCMVerify([mockVC goToApplicationLifecycle:
@"AppLifecycleState.inactive"]);
2259 [flutterViewController deregisterNotifications];
2260 [mockBundle stopMocking];
2263- (void)testLifeCycleNotificationCancelledInvalidResumed {
2268 NSNotification* applicationDidBecomeActiveNotification =
2269 [NSNotification notificationWithName:UIApplicationDidBecomeActiveNotification
2272 NSNotification* applicationWillResignActiveNotification =
2273 [NSNotification notificationWithName:UIApplicationWillResignActiveNotification
2276 id mockVC = OCMPartialMock(flutterViewController);
2277 [NSNotificationCenter.defaultCenter postNotification:applicationDidBecomeActiveNotification];
2278 [NSNotificationCenter.defaultCenter postNotification:applicationWillResignActiveNotification];
2279 OCMVerify([mockVC goToApplicationLifecycle:
@"AppLifecycleState.inactive"]);
2281 XCTestExpectation* timeoutApplicationLifeCycle =
2282 [
self expectationWithDescription:@"timeoutApplicationLifeCycle"];
2283 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 *
NSEC_PER_SEC)),
2284 dispatch_get_main_queue(), ^{
2285 OCMReject([mockVC goToApplicationLifecycle:
@"AppLifecycleState.resumed"]);
2286 [timeoutApplicationLifeCycle fulfill];
2287 [flutterViewController deregisterNotifications];
2289 [
self waitForExpectationsWithTimeout:5.0 handler:nil];
2292- (void)testSetupKeyboardAnimationVsyncClientWillCreateNewVsyncClientForFlutterViewController {
2293 id bundleMock = OCMPartialMock([NSBundle mainBundle]);
2297 double maxFrameRate = 120;
2298 [[[mockDisplayLinkManager stub] andReturnValue:@(maxFrameRate)] displayRefreshRate];
2306 [viewController setUpKeyboardAnimationVsyncClient:callback];
2308 CADisplayLink* link = [viewController.keyboardAnimationVSyncClient getDisplayLink];
2309 XCTAssertNotNil(link);
2310 if (@available(iOS 15.0, *)) {
2311 XCTAssertEqual(link.preferredFrameRateRange.maximum, maxFrameRate);
2312 XCTAssertEqual(link.preferredFrameRateRange.preferred, maxFrameRate);
2313 XCTAssertEqual(link.preferredFrameRateRange.minimum, maxFrameRate / 2);
2315 XCTAssertEqual(link.preferredFramesPerSecond, maxFrameRate);
2320 testCreateTouchRateCorrectionVSyncClientWillCreateVsyncClientWhenRefreshRateIsLargerThan60HZ {
2322 double maxFrameRate = 120;
2323 [[[mockDisplayLinkManager stub] andReturnValue:@(maxFrameRate)] displayRefreshRate];
2329 [viewController createTouchRateCorrectionVSyncClientIfNeeded];
2333- (void)testCreateTouchRateCorrectionVSyncClientWillNotCreateNewVSyncClientWhenClientAlreadyExists {
2335 double maxFrameRate = 120;
2336 [[[mockDisplayLinkManager stub] andReturnValue:@(maxFrameRate)] displayRefreshRate];
2343 [viewController createTouchRateCorrectionVSyncClientIfNeeded];
2345 XCTAssertNotNil(clientBefore);
2347 [viewController createTouchRateCorrectionVSyncClientIfNeeded];
2349 XCTAssertNotNil(clientAfter);
2351 XCTAssertTrue(clientBefore == clientAfter);
2354- (void)testCreateTouchRateCorrectionVSyncClientWillNotCreateVsyncClientWhenRefreshRateIs60HZ {
2356 double maxFrameRate = 60;
2357 [[[mockDisplayLinkManager stub] andReturnValue:@(maxFrameRate)] displayRefreshRate];
2363 [viewController createTouchRateCorrectionVSyncClientIfNeeded];
2367- (void)testTriggerTouchRateCorrectionVSyncClientCorrectly {
2369 double maxFrameRate = 120;
2370 [[[mockDisplayLinkManager stub] andReturnValue:@(maxFrameRate)] displayRefreshRate];
2376 [viewController loadView];
2377 [viewController viewDidLoad];
2380 CADisplayLink* link = [client getDisplayLink];
2382 UITouch* fakeTouchBegan = [[UITouch alloc] init];
2383 fakeTouchBegan.phase = UITouchPhaseBegan;
2385 UITouch* fakeTouchMove = [[UITouch alloc] init];
2386 fakeTouchMove.phase = UITouchPhaseMoved;
2388 UITouch* fakeTouchEnd = [[UITouch alloc] init];
2389 fakeTouchEnd.phase = UITouchPhaseEnded;
2391 UITouch* fakeTouchCancelled = [[UITouch alloc] init];
2392 fakeTouchCancelled.phase = UITouchPhaseCancelled;
2395 triggerTouchRateCorrectionIfNeeded:[[NSSet alloc] initWithObjects:fakeTouchBegan, nil]];
2396 XCTAssertFalse(link.isPaused);
2399 triggerTouchRateCorrectionIfNeeded:[[NSSet alloc] initWithObjects:fakeTouchEnd, nil]];
2400 XCTAssertTrue(link.isPaused);
2403 triggerTouchRateCorrectionIfNeeded:[[NSSet alloc] initWithObjects:fakeTouchMove, nil]];
2404 XCTAssertFalse(link.isPaused);
2407 triggerTouchRateCorrectionIfNeeded:[[NSSet alloc] initWithObjects:fakeTouchCancelled, nil]];
2408 XCTAssertTrue(link.isPaused);
2411 triggerTouchRateCorrectionIfNeeded:[[NSSet alloc]
2412 initWithObjects:fakeTouchBegan, fakeTouchEnd, nil]];
2413 XCTAssertFalse(link.isPaused);
2416 triggerTouchRateCorrectionIfNeeded:[[NSSet alloc] initWithObjects:fakeTouchEnd,
2417 fakeTouchCancelled, nil]];
2418 XCTAssertTrue(link.isPaused);
2421 triggerTouchRateCorrectionIfNeeded:[[NSSet alloc]
2422 initWithObjects:fakeTouchMove, fakeTouchEnd, nil]];
2423 XCTAssertFalse(link.isPaused);
2426- (void)testFlutterViewControllerStartKeyboardAnimationWillCreateVsyncClientCorrectly {
2433 [viewController startKeyBoardAnimation:0.25];
2438 testSetupKeyboardAnimationVsyncClientWillNotCreateNewVsyncClientWhenKeyboardAnimationCallbackIsNil {
2444 [viewController setUpKeyboardAnimationVsyncClient:nil];
2448- (void)testSupportsShowingSystemContextMenuForIOS16AndAbove {
2454 BOOL supportsShowingSystemContextMenu = [viewController supportsShowingSystemContextMenu];
2455 if (@available(iOS 16.0, *)) {
2456 XCTAssertTrue(supportsShowingSystemContextMenu);
2458 XCTAssertFalse(supportsShowingSystemContextMenu);
2462- (void)testStateIsActiveAndBackgroundWhenApplicationStateIsActive {
2468 id mockApplication = OCMClassMock([UIApplication
class]);
2469 OCMStub([mockApplication applicationState]).andReturn(UIApplicationStateActive);
2470 OCMStub([mockApplication sharedApplication]).andReturn(mockApplication);
2475- (void)testStateIsActiveAndBackgroundWhenApplicationStateIsBackground {
2481 id mockApplication = OCMClassMock([UIApplication
class]);
2482 OCMStub([mockApplication applicationState]).andReturn(UIApplicationStateBackground);
2483 OCMStub([mockApplication sharedApplication]).andReturn(mockApplication);
2488- (void)testStateIsActiveAndBackgroundWhenApplicationStateIsInactive {
2494 id mockApplication = OCMClassMock([UIApplication
class]);
2495 OCMStub([mockApplication applicationState]).andReturn(UIApplicationStateInactive);
2496 OCMStub([mockApplication sharedApplication]).andReturn(mockApplication);
2501- (void)testStateIsActiveAndBackgroundWhenSceneStateIsActive {
2502 id mockBundle = OCMPartialMock([NSBundle mainBundle]);
2503 OCMStub([mockBundle objectForInfoDictionaryKey:
@"NSExtension"]).andReturn(@{
2504 @"NSExtensionPointIdentifier" :
@"com.apple.share-services"
2507 [engine runWithEntrypoint:nil];
2512 OCMStub([mockVC activationState]).andReturn(UISceneActivationStateForegroundActive);
2516 [mockBundle stopMocking];
2517 [mockVC stopMocking];
2520- (void)testStateIsActiveAndBackgroundWhenSceneStateIsBackground {
2521 id mockBundle = OCMPartialMock([NSBundle mainBundle]);
2522 OCMStub([mockBundle objectForInfoDictionaryKey:
@"NSExtension"]).andReturn(@{
2523 @"NSExtensionPointIdentifier" :
@"com.apple.share-services"
2526 [engine runWithEntrypoint:nil];
2531 OCMStub([mockVC activationState]).andReturn(UISceneActivationStateBackground);
2535 [mockBundle stopMocking];
2536 [mockVC stopMocking];
2539- (void)testStateIsActiveAndBackgroundWhenSceneStateIsInactive {
2540 id mockBundle = OCMPartialMock([NSBundle mainBundle]);
2541 OCMStub([mockBundle objectForInfoDictionaryKey:
@"NSExtension"]).andReturn(@{
2542 @"NSExtensionPointIdentifier" :
@"com.apple.share-services"
2545 [engine runWithEntrypoint:nil];
2550 OCMStub([mockVC activationState]).andReturn(UISceneActivationStateForegroundInactive);
2554 [mockBundle stopMocking];
2555 [mockVC stopMocking];
2558- (void)testPerformImplicitEngineCallbacks {
2559 id mockRegistrant = OCMProtocolMock(
@protocol(FlutterPluginRegistrant));
2560 id appDelegate = [[UIApplication sharedApplication] delegate];
2561 [appDelegate setMockLaunchEngine:self.mockEngine];
2562 UIStoryboard* storyboard = [UIStoryboard storyboardWithName:@"Flutter" bundle:nil];
2563 XCTAssertTrue([appDelegate respondsToSelector:@selector(setPluginRegistrant:)]);
2564 [appDelegate setPluginRegistrant:mockRegistrant];
2567 [appDelegate setPluginRegistrant:nil];
2569 OCMVerify([
self.mockEngine performImplicitEngineCallback]);
2570 [appDelegate setMockLaunchEngine:nil];
2573- (void)testPerformImplicitEngineCallbacksUsesAppLaunchEventFallbacks {
2579 OCMStub([mockEngine performImplicitEngineCallback]).andReturn(YES);
2580 OCMStub([viewControllerMock awokenFromNib]).andReturn(YES);
2582 id mockApplication = OCMClassMock([UIApplication
class]);
2583 OCMStub([mockApplication sharedApplication]).andReturn(mockApplication);
2585 OCMStub([mockApplication delegate]).andReturn(mockApplicationDelegate);
2586 OCMStub([mockApplicationDelegate takeLaunchEngine]).andReturn(mockEngine);
2588 id mockScene = OCMClassMock([UIScene
class]);
2589 id mockSceneDelegate = OCMProtocolMock(
@protocol(UISceneDelegate));
2590 OCMStub([mockScene delegate]).andReturn(mockSceneDelegate);
2591 OCMStub([mockApplication connectedScenes]).andReturn([NSSet setWithObject:mockScene]);
2595 OCMStub([mockApplicationDelegate lifeCycleDelegate]).andReturn(mockLifecycleDelegate);
2597 [viewControllerMock sharedSetupWithProject:nil initialRoute:nil];
2598 OCMVerify([mockLifecycleDelegate sceneFallbackWillFinishLaunchingApplication:mockApplication]);
2599 OCMVerify([mockLifecycleDelegate sceneFallbackDidFinishLaunchingApplication:mockApplication]);
2602- (void)testPerformImplicitEngineCallbacksNoAppLaunchEventFallbacksWhenNoStoryboard {
2608 OCMStub([mockEngine performImplicitEngineCallback]).andReturn(YES);
2609 OCMStub([viewControllerMock awokenFromNib]).andReturn(NO);
2611 id mockApplication = OCMClassMock([UIApplication
class]);
2612 OCMStub([mockApplication sharedApplication]).andReturn(mockApplication);
2614 OCMStub([mockApplication delegate]).andReturn(mockApplicationDelegate);
2615 OCMStub([mockApplicationDelegate takeLaunchEngine]).andReturn(mockEngine);
2617 id mockScene = OCMClassMock([UIScene
class]);
2618 id mockSceneDelegate = OCMProtocolMock(
@protocol(UISceneDelegate));
2619 OCMStub([mockScene delegate]).andReturn(mockSceneDelegate);
2620 OCMStub([mockApplication connectedScenes]).andReturn([NSSet setWithObject:mockScene]);
2624 OCMStub([mockApplicationDelegate lifeCycleDelegate]).andReturn(mockLifecycleDelegate);
2626 [viewControllerMock sharedSetupWithProject:nil initialRoute:nil];
2627 OCMReject([mockLifecycleDelegate sceneFallbackWillFinishLaunchingApplication:mockApplication]);
2628 OCMReject([mockLifecycleDelegate sceneFallbackDidFinishLaunchingApplication:mockApplication]);
2631- (void)testPerformImplicitEngineCallbacksNoAppLaunchEventFallbacksWhenNoScenes {
2637 OCMStub([mockEngine performImplicitEngineCallback]).andReturn(YES);
2638 OCMStub([viewControllerMock awokenFromNib]).andReturn(YES);
2640 id mockApplication = OCMClassMock([UIApplication
class]);
2641 OCMStub([mockApplication sharedApplication]).andReturn(mockApplication);
2643 OCMStub([mockApplication delegate]).andReturn(mockApplicationDelegate);
2644 OCMStub([mockApplicationDelegate takeLaunchEngine]).andReturn(mockEngine);
2648 OCMStub([mockApplicationDelegate lifeCycleDelegate]).andReturn(mockLifecycleDelegate);
2650 [viewControllerMock sharedSetupWithProject:nil initialRoute:nil];
2651 OCMReject([mockLifecycleDelegate sceneFallbackWillFinishLaunchingApplication:mockApplication]);
2652 OCMReject([mockLifecycleDelegate sceneFallbackDidFinishLaunchingApplication:mockApplication]);
2655- (void)testGrabLaunchEngine {
2656 id appDelegate = [[UIApplication sharedApplication] delegate];
2657 XCTAssertTrue([appDelegate respondsToSelector:@selector(setMockLaunchEngine:)]);
2658 [appDelegate setMockLaunchEngine:self.mockEngine];
2659 UIStoryboard* storyboard = [UIStoryboard storyboardWithName:@"Flutter" bundle:nil];
2660 XCTAssertTrue(storyboard);
2666 [appDelegate setMockLaunchEngine:nil];
2669- (void)testDoesntGrabLaunchEngine {
2670 id appDelegate = [[UIApplication sharedApplication] delegate];
2671 XCTAssertTrue([appDelegate respondsToSelector:@selector(setMockLaunchEngine:)]);
2672 [appDelegate setMockLaunchEngine:self.mockEngine];
2674 XCTAssertNotNil(flutterViewController.
engine);
2675 XCTAssertNotEqual(flutterViewController.
engine,
self.mockEngine);
2676 [appDelegate setMockLaunchEngine:nil];
NS_ASSUME_NONNULL_BEGIN typedef void(^ FlutterReply)(id _Nullable reply)
static CFStringRef kMessageLoopCFRunLoopMode
void(* FlutterKeyEventCallback)(bool, void *)
FlutterDesktopBinaryReply callback
BOOL runWithEntrypoint:(nullable NSString *entrypoint)
FlutterViewController * viewController
FlutterTextInputPlugin * textInputPlugin
FlutterBasicMessageChannel * lifecycleChannel
BOOL didCallNotifyLowMemory
FlutterViewController * viewController
FlutterBasicMessageChannel * keyEventChannel
NSObject< FlutterBinaryMessenger > * binaryMessenger
void(^ FlutterSendKeyEvent)(const FlutterKeyEvent &, _Nullable FlutterKeyEventCallback, void *_Nullable)
UITextSmartQuotesType smartQuotesType API_AVAILABLE(ios(11.0))
FlutterViewController * viewController
FlutterTextInputPlugin * textInputPlugin
NSNotificationName const FlutterViewControllerWillDealloc
void(^ FlutterKeyboardAnimationCallback)(fml::TimePoint)
NSString *const kCADisableMinimumFrameDurationOnPhoneKey
Info.plist key enabling the full range of ProMotion refresh rates for CADisplayLink callbacks and CAA...