5 #import <OCMock/OCMock.h>
6 #import <XCTest/XCTest.h>
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"
27 #import "flutter/shell/platform/embedder/embedder.h"
28 #import "flutter/testing/ios/IosUnitTests/App/AppDelegate.h"
29 #import "flutter/third_party/spring_animation/spring_animation.h"
43 @property(nonatomic, assign) BOOL didCallNotifyLowMemory;
47 - (void)sendKeyEvent:(const FlutterKeyEvent&)event
48 callback:(nullable FlutterKeyEventCallback)callback
49 userData:(nullable
void*)userData;
60 - (void)notifyLowMemory {
61 _didCallNotifyLowMemory = YES;
64 - (void)sendKeyEvent:(const FlutterKeyEvent&)event
65 callback:(FlutterKeyEventCallback)callback
66 userData:(
void*)userData API_AVAILABLE(ios(9.0)) {
67 if (callback == nil) {
73 CFRunLoopPerformBlock(CFRunLoopGetCurrent(), fml::MessageLoopDarwin::kMessageLoopCFRunLoopMode,
75 callback(
true, userData);
81 - (BOOL)createShell:(NSString*)entrypoint
82 libraryURI:(NSString*)libraryURI
83 initialRoute:(NSString*)initialRoute;
84 - (void)dispatchPointerDataPacket:(std::unique_ptr<
flutter::PointerDataPacket>)packet;
85 - (void)updateViewportMetrics:(
flutter::ViewportMetrics)viewportMetrics;
117 @property(nonatomic, retain, readonly)
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;
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;
159 - (void)setUpKeyboardSpringAnimationIfNeeded:(CAAnimation*)keyboardAnimation;
160 - (void)setUpKeyboardAnimationVsyncClient:
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;
189 @interface UITouch ()
191 @property(nonatomic, readwrite) UITouchPhase phase;
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 {
843 mockEngine.lifecycleChannel = lifecycleChannel;
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"]);
865 [viewController applicationWillTerminate:nil];
866 OCMVerify([viewControllerMock goToApplicationLifecycle:
@"AppLifecycleState.detached"]);
870 - (void)testViewDidDisappearDoesPauseEngineWhenIsTheViewController {
873 mockEngine.lifecycleChannel = lifecycleChannel;
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 {
949 mockEngine.lifecycleChannel = lifecycleChannel;
954 [viewController viewWillDisappear:NO];
955 OCMVerify([lifecycleChannel sendMessage:
@"AppLifecycleState.inactive"]);
959 testEngineConfigSyncMethodWillNotExecuteWhenViewControllerInEngineIsNotCurrentViewControllerInViewWillDisappear {
962 mockEngine.lifecycleChannel = lifecycleChannel;
970 [viewControllerA viewDidDisappear:NO];
971 OCMReject([lifecycleChannel sendMessage:
@"AppLifecycleState.inactive"]);
974 - (void)testUpdateViewportMetricsIfNeeded_DoesntInvokeEngineWhenNotTheViewController {
976 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
985 [viewControllerA updateViewportMetricsIfNeeded];
986 flutter::ViewportMetrics viewportMetrics;
987 OCMVerify(never(), [
mockEngine updateViewportMetrics:viewportMetrics]);
990 - (void)testUpdateViewportMetricsIfNeeded_DoesInvokeEngineWhenIsTheViewController {
992 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
997 flutter::ViewportMetrics viewportMetrics;
998 OCMExpect([
mockEngine updateViewportMetrics:viewportMetrics]).ignoringNonObjectArgs();
999 [viewController updateViewportMetricsIfNeeded];
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];
1060 OCMVerify(never(), [
mockEngine updateViewportMetrics:flutter::ViewportMetrics()]);
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);
1079 flutter::ViewportMetrics viewportMetrics;
1080 OCMExpect([
mockEngine updateViewportMetrics:viewportMetrics]).ignoringNonObjectArgs();
1082 [viewController viewWillTransitionToSize:CGSizeZero withTransitionCoordinator:mockCoordinator];
1084 [viewController updateViewportMetricsIfNeeded];
1085 OCMVerify(never(), [
mockEngine updateViewportMetrics:flutter::ViewportMetrics()]);
1089 XCTWaiterResult result = [XCTWaiter
1090 waitForExpectations:@[ [
self expectationWithDescription:@"Waiting for rotation duration"] ]
1091 timeout:transitionDuration];
1092 XCTAssertEqual(result, XCTWaiterResultTimedOut);
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);
1112 flutter::ViewportMetrics viewportMetrics;
1113 OCMExpect([
mockEngine updateViewportMetrics:viewportMetrics]).ignoringNonObjectArgs();
1116 [viewController viewWillTransitionToSize:CGSizeZero withTransitionCoordinator:mockCoordinator];
1117 [viewController updateViewportMetricsIfNeeded];
1122 - (void)testViewDidLoadDoesntInvokeEngineWhenNotTheViewController {
1124 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1133 UIView* view = viewControllerA.view;
1134 XCTAssertNotNil(view);
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);
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);
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 {
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");
1765 engine.viewController = nil;
1768 - (void)testHideOverlay {
1771 [engine createShell:@"" libraryURI:@"" initialRoute:nil];
1775 XCTAssertFalse(realVC.prefersHomeIndicatorAutoHidden,
@"");
1776 [NSNotificationCenter.defaultCenter postNotificationName:FlutterViewControllerHideHomeIndicator
1778 XCTAssertTrue(realVC.prefersHomeIndicatorAutoHidden,
@"");
1779 engine.viewController = nil;
1782 - (void)testNotifyLowMemory {
1788 OCMStub([viewControllerMock surfaceUpdated:NO]);
1789 [viewController beginAppearanceTransition:NO animated:NO];
1790 [viewController endAppearanceTransition];
1791 XCTAssertTrue(
mockEngine.didCallNotifyLowMemory);
1794 - (void)sendMessage:(
id _Nullable)message reply:(
FlutterReply _Nullable)callback {
1795 NSMutableDictionary* replyMessage = [@{
1800 self.messageSent = message;
1801 CFRunLoopPerformBlock(CFRunLoopGetCurrent(), fml::MessageLoopDarwin::kMessageLoopCFRunLoopMode,
1803 callback(replyMessage);
1808 if (@available(iOS 13.4, *)) {
1815 OCMStub([
mockEngine.keyEventChannel sendMessage:[OCMArg any] reply:[OCMArg any]])
1816 .andCall(
self,
@selector(sendMessage:reply:));
1818 mockEngine.textInputPlugin =
self.mockTextInputPlugin;
1826 [vc addInternalPlugins];
1828 [vc handlePressEvent:keyUpEvent(UIKeyboardHIDUsageKeyboardA, UIKeyModifierShift, 123.0)
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:));
1854 mockEngine.textInputPlugin =
self.mockTextInputPlugin;
1861 [vc addInternalPlugins];
1863 [vc handlePressEvent:keyDownEvent(UIKeyboardHIDUsageKeyboardA, UIKeyModifierShift, 123.0f, "A",
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:));
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)
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);
1972 flutter::PointerData pointer_data = [vc generatePointerDataForFake];
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"
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"
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);
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"]);
2128 [flutterViewController deregisterNotifications];
2131 - (void)testLifeCycleNotificationSceneWillTerminate {
2132 id mockBundle = OCMPartialMock([NSBundle mainBundle]);
2133 OCMStub([mockBundle objectForInfoDictionaryKey:
@"NSExtension"]).andReturn(@{
2134 @"NSExtensionPointIdentifier" :
@"com.apple.share-services"
2140 NSNotification* sceneNotification =
2141 [NSNotification notificationWithName:UISceneDidDisconnectNotification
2144 NSNotification* applicationNotification =
2145 [NSNotification notificationWithName:UIApplicationWillTerminateNotification
2148 id mockVC = OCMPartialMock(flutterViewController);
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"]);
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"
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"
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"
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"
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"
2550 OCMStub([mockVC activationState]).andReturn(UISceneActivationStateForegroundInactive);
2554 [mockBundle stopMocking];
2555 [mockVC stopMocking];
2558 - (void)testPerformImplicitEngineCallbacks {
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)
void(^ FlutterSendKeyEvent)(const FlutterKeyEvent &, _Nullable FlutterKeyEventCallback, void *_Nullable)
UITextSmartQuotesType smartQuotesType API_AVAILABLE(ios(11.0))
FlutterViewController * viewController
FlutterTextInputPlugin * textInputPlugin
void(^ FlutterKeyboardAnimationCallback)(fml::TimePoint)
NSNotificationName const FlutterViewControllerWillDealloc
FlutterSendKeyEvent sendEvent
NSMutableArray< id< FlutterKeyPrimaryResponder > > * primaryResponders
VSyncClient * touchRateCorrectionVSyncClient
BOOL keyboardAnimationIsShowing
void addInternalPlugins()
void updateAutoResizeConstraints()
double targetViewInsetBottom
UIView * keyboardAnimationView()
void hideKeyboardImmediately()
void invalidateKeyboardAnimationVSyncClient()
VSyncClient * keyboardAnimationVSyncClient
void createTouchRateCorrectionVSyncClientIfNeeded()
void ensureViewportMetricsIsCorrect()
void checkAndUpdateAutoResizeConstraints()
void updateViewportMetricsIfNeeded()
BOOL isKeyboardInOrTransitioningFromBackground
SpringAnimation * keyboardSpringAnimation()
FlutterEngine * mockLaunchEngine
CADisplayLink * getDisplayLink()
BOOL runWithEntrypoint:(nullable NSString *entrypoint)
FlutterBasicMessageChannel * lifecycleChannel
FlutterBasicMessageChannel * keyEventChannel
NSObject< FlutterBinaryMessenger > * binaryMessenger
NSString *const kCADisableMinimumFrameDurationOnPhoneKey
Info.plist key enabling the full range of ProMotion refresh rates for CADisplayLink callbacks and CAA...