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"
25 #import "flutter/shell/platform/embedder/embedder.h"
26 #import "flutter/testing/ios/IosUnitTests/App/AppDelegate.h"
27 #import "flutter/third_party/spring_animation/spring_animation.h"
41 @property(nonatomic, assign) BOOL didCallNotifyLowMemory;
45 - (void)sendKeyEvent:(const FlutterKeyEvent&)event
46 callback:(nullable FlutterKeyEventCallback)callback
47 userData:(nullable
void*)userData;
58 - (void)notifyLowMemory {
59 _didCallNotifyLowMemory = YES;
62 - (void)sendKeyEvent:(const FlutterKeyEvent&)event
63 callback:(FlutterKeyEventCallback)callback
64 userData:(
void*)userData API_AVAILABLE(ios(9.0)) {
65 if (callback == nil) {
71 CFRunLoopPerformBlock(CFRunLoopGetCurrent(), fml::MessageLoopDarwin::kMessageLoopCFRunLoopMode,
73 callback(
true, userData);
79 - (BOOL)createShell:(NSString*)entrypoint
80 libraryURI:(NSString*)libraryURI
81 initialRoute:(NSString*)initialRoute;
82 - (void)dispatchPointerDataPacket:(std::unique_ptr<
flutter::PointerDataPacket>)packet;
83 - (void)updateViewportMetrics:(
flutter::ViewportMetrics)viewportMetrics;
115 @property(nonatomic, retain, readonly)
136 - (void)surfaceUpdated:(BOOL)appeared;
137 - (void)performOrientationUpdate:(UIInterfaceOrientationMask)new_preferences;
138 - (void)handlePressEvent:(FlutterUIPressProxy*)press
139 nextAction:(
void (^)())next API_AVAILABLE(ios(13.4));
140 - (void)discreteScrollEvent:(UIPanGestureRecognizer*)recognizer;
142 - (void)onUserSettingsChanged:(NSNotification*)notification;
143 - (void)applicationWillTerminate:(NSNotification*)notification;
144 - (void)goToApplicationLifecycle:(nonnull NSString*)state;
145 - (void)handleKeyboardNotification:(NSNotification*)notification;
146 - (CGFloat)calculateKeyboardInset:(CGRect)keyboardFrame keyboardMode:(
int)keyboardMode;
147 - (BOOL)shouldIgnoreKeyboardNotification:(NSNotification*)notification;
148 - (FlutterKeyboardMode)calculateKeyboardAttachMode:(NSNotification*)notification;
149 - (CGFloat)calculateMultitaskingAdjustment:(CGRect)screenRect keyboardFrame:(CGRect)keyboardFrame;
150 - (void)startKeyBoardAnimation:(NSTimeInterval)duration;
154 - (void)setUpKeyboardSpringAnimationIfNeeded:(CAAnimation*)keyboardAnimation;
155 - (void)setUpKeyboardAnimationVsyncClient:
160 - (
flutter::PointerData)generatePointerDataForFake;
162 initialRoute:(nullable NSString*)initialRoute;
163 - (void)applicationBecameActive:(NSNotification*)notification;
164 - (void)applicationWillResignActive:(NSNotification*)notification;
165 - (void)applicationWillTerminate:(NSNotification*)notification;
166 - (void)applicationDidEnterBackground:(NSNotification*)notification;
167 - (void)applicationWillEnterForeground:(NSNotification*)notification;
168 - (void)sceneBecameActive:(NSNotification*)notification API_AVAILABLE(ios(13.0));
169 - (void)sceneWillResignActive:(NSNotification*)notification API_AVAILABLE(ios(13.0));
170 - (void)sceneWillDisconnect:(NSNotification*)notification API_AVAILABLE(ios(13.0));
171 - (void)sceneDidEnterBackground:(NSNotification*)notification API_AVAILABLE(ios(13.0));
172 - (void)sceneWillEnterForeground:(NSNotification*)notification API_AVAILABLE(ios(13.0));
173 - (void)triggerTouchRateCorrectionIfNeeded:(NSSet*)touches;
177 @property(nonatomic, strong)
id mockEngine;
178 @property(nonatomic, strong)
id mockTextInputPlugin;
179 @property(nonatomic, strong)
id messageSent;
180 - (void)sendMessage:(
id _Nullable)message reply:(
FlutterReply _Nullable)callback;
183 @interface UITouch ()
185 @property(nonatomic, readwrite) UITouchPhase phase;
201 self.messageSent = nil;
207 [
self.mockEngine stopMocking];
208 self.mockEngine = nil;
209 self.mockTextInputPlugin = nil;
210 self.messageSent = nil;
213 - (id)setUpMockScreen {
214 UIScreen* mockScreen = OCMClassMock([UIScreen
class]);
216 CGRect screenBounds = CGRectMake(0, 0, 1170, 2532);
217 OCMStub([mockScreen bounds]).andReturn(screenBounds);
218 CGFloat screenScale = 1;
219 OCMStub([mockScreen scale]).andReturn(screenScale);
225 screen:(UIScreen*)screen
226 viewFrame:(CGRect)viewFrame
227 convertedFrame:(CGRect)convertedFrame {
228 OCMStub([viewControllerMock flutterScreenIfViewLoaded]).andReturn(screen);
229 id mockView = OCMClassMock([UIView
class]);
230 OCMStub([mockView frame]).andReturn(viewFrame);
231 OCMStub([mockView convertRect:viewFrame toCoordinateSpace:[OCMArg any]])
232 .andReturn(convertedFrame);
233 OCMStub([viewControllerMock viewIfLoaded]).andReturn(mockView);
238 - (void)testViewDidLoadWillInvokeCreateTouchRateCorrectionVSyncClient {
245 [viewControllerMock loadView];
246 [viewControllerMock viewDidLoad];
247 OCMVerify([viewControllerMock createTouchRateCorrectionVSyncClientIfNeeded]);
250 - (void)testStartKeyboardAnimationWillInvokeSetupKeyboardSpringAnimationIfNeeded {
257 viewControllerMock.targetViewInsetBottom = 100;
258 [viewControllerMock startKeyBoardAnimation:0.25];
260 CAAnimation* keyboardAnimation =
261 [[viewControllerMock keyboardAnimationView].layer animationForKey:@"position"];
263 OCMVerify([viewControllerMock setUpKeyboardSpringAnimationIfNeeded:keyboardAnimation]);
266 - (void)testSetupKeyboardSpringAnimationIfNeeded {
273 UIScreen* screen = [
self setUpMockScreen];
274 CGRect viewFrame = screen.bounds;
275 [
self setUpMockView:viewControllerMock
278 convertedFrame:viewFrame];
281 [viewControllerMock setUpKeyboardSpringAnimationIfNeeded:nil];
282 SpringAnimation* keyboardSpringAnimation = [viewControllerMock keyboardSpringAnimation];
283 XCTAssertTrue(keyboardSpringAnimation == nil);
286 CABasicAnimation* nonSpringAnimation = [CABasicAnimation animation];
287 nonSpringAnimation.duration = 1.0;
288 nonSpringAnimation.fromValue = [NSNumber numberWithFloat:0.0];
289 nonSpringAnimation.toValue = [NSNumber numberWithFloat:1.0];
290 nonSpringAnimation.keyPath =
@"position";
291 [viewControllerMock setUpKeyboardSpringAnimationIfNeeded:nonSpringAnimation];
292 keyboardSpringAnimation = [viewControllerMock keyboardSpringAnimation];
294 XCTAssertTrue(keyboardSpringAnimation == nil);
297 CASpringAnimation* springAnimation = [CASpringAnimation animation];
298 springAnimation.mass = 1.0;
299 springAnimation.stiffness = 100.0;
300 springAnimation.damping = 10.0;
301 springAnimation.keyPath =
@"position";
302 springAnimation.fromValue = [NSValue valueWithCGPoint:CGPointMake(0, 0)];
303 springAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(100, 100)];
304 [viewControllerMock setUpKeyboardSpringAnimationIfNeeded:springAnimation];
305 keyboardSpringAnimation = [viewControllerMock keyboardSpringAnimation];
306 XCTAssertTrue(keyboardSpringAnimation != nil);
309 - (void)testKeyboardAnimationIsShowingAndCompounding {
316 UIScreen* screen = [
self setUpMockScreen];
317 CGRect viewFrame = screen.bounds;
318 [
self setUpMockView:viewControllerMock
321 convertedFrame:viewFrame];
324 CGFloat screenHeight = screen.bounds.size.height;
325 CGFloat screenWidth = screen.bounds.size.height;
328 CGRect initialShowKeyboardBeginFrame = CGRectMake(0, screenHeight, screenWidth, 250);
329 CGRect initialShowKeyboardEndFrame = CGRectMake(0, screenHeight - 250, screenWidth, 500);
330 NSNotification* fakeNotification = [NSNotification
331 notificationWithName:UIKeyboardWillChangeFrameNotification
334 @"UIKeyboardFrameBeginUserInfoKey" : @(initialShowKeyboardBeginFrame),
335 @"UIKeyboardFrameEndUserInfoKey" : @(initialShowKeyboardEndFrame),
336 @"UIKeyboardAnimationDurationUserInfoKey" : @(0.25),
337 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
339 viewControllerMock.targetViewInsetBottom = 0;
340 [viewControllerMock handleKeyboardNotification:fakeNotification];
341 BOOL isShowingAnimation1 = viewControllerMock.keyboardAnimationIsShowing;
342 XCTAssertTrue(isShowingAnimation1);
345 CGRect compoundingShowKeyboardBeginFrame = CGRectMake(0, screenHeight - 250, screenWidth, 250);
346 CGRect compoundingShowKeyboardEndFrame = CGRectMake(0, screenHeight - 500, screenWidth, 500);
347 fakeNotification = [NSNotification
348 notificationWithName:UIKeyboardWillChangeFrameNotification
351 @"UIKeyboardFrameBeginUserInfoKey" : @(compoundingShowKeyboardBeginFrame),
352 @"UIKeyboardFrameEndUserInfoKey" : @(compoundingShowKeyboardEndFrame),
353 @"UIKeyboardAnimationDurationUserInfoKey" : @(0.25),
354 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
357 [viewControllerMock handleKeyboardNotification:fakeNotification];
358 BOOL isShowingAnimation2 = viewControllerMock.keyboardAnimationIsShowing;
359 XCTAssertTrue(isShowingAnimation2);
360 XCTAssertTrue(isShowingAnimation1 == isShowingAnimation2);
363 CGRect initialHideKeyboardBeginFrame = CGRectMake(0, screenHeight - 500, screenWidth, 250);
364 CGRect initialHideKeyboardEndFrame = CGRectMake(0, screenHeight - 250, screenWidth, 500);
365 fakeNotification = [NSNotification
366 notificationWithName:UIKeyboardWillChangeFrameNotification
369 @"UIKeyboardFrameBeginUserInfoKey" : @(initialHideKeyboardBeginFrame),
370 @"UIKeyboardFrameEndUserInfoKey" : @(initialHideKeyboardEndFrame),
371 @"UIKeyboardAnimationDurationUserInfoKey" : @(0.25),
372 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
375 [viewControllerMock handleKeyboardNotification:fakeNotification];
376 BOOL isShowingAnimation3 = viewControllerMock.keyboardAnimationIsShowing;
377 XCTAssertFalse(isShowingAnimation3);
378 XCTAssertTrue(isShowingAnimation2 != isShowingAnimation3);
381 CGRect compoundingHideKeyboardBeginFrame = CGRectMake(0, screenHeight - 250, screenWidth, 250);
382 CGRect compoundingHideKeyboardEndFrame = CGRectMake(0, screenHeight, screenWidth, 500);
383 fakeNotification = [NSNotification
384 notificationWithName:UIKeyboardWillChangeFrameNotification
387 @"UIKeyboardFrameBeginUserInfoKey" : @(compoundingHideKeyboardBeginFrame),
388 @"UIKeyboardFrameEndUserInfoKey" : @(compoundingHideKeyboardEndFrame),
389 @"UIKeyboardAnimationDurationUserInfoKey" : @(0.25),
390 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
393 [viewControllerMock handleKeyboardNotification:fakeNotification];
394 BOOL isShowingAnimation4 = viewControllerMock.keyboardAnimationIsShowing;
395 XCTAssertFalse(isShowingAnimation4);
396 XCTAssertTrue(isShowingAnimation3 == isShowingAnimation4);
399 - (void)testShouldIgnoreKeyboardNotification {
401 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
406 UIScreen* screen = [
self setUpMockScreen];
407 CGRect viewFrame = screen.bounds;
408 [
self setUpMockView:viewControllerMock
411 convertedFrame:viewFrame];
413 CGFloat screenWidth = screen.bounds.size.width;
414 CGFloat screenHeight = screen.bounds.size.height;
415 CGRect emptyKeyboard = CGRectZero;
416 CGRect zeroHeightKeyboard = CGRectMake(0, 0, screenWidth, 0);
417 CGRect validKeyboardEndFrame = CGRectMake(0, screenHeight - 320, screenWidth, 320);
421 NSNotification* notification =
422 [NSNotification notificationWithName:UIKeyboardWillHideNotification
425 @"UIKeyboardFrameEndUserInfoKey" : @(validKeyboardEndFrame),
426 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
427 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
430 BOOL shouldIgnore = [viewControllerMock shouldIgnoreKeyboardNotification:notification];
431 XCTAssertTrue(shouldIgnore == NO);
435 notification = [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
438 @"UIKeyboardFrameEndUserInfoKey" : @(emptyKeyboard),
439 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
440 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
442 shouldIgnore = [viewControllerMock shouldIgnoreKeyboardNotification:notification];
443 XCTAssertTrue(shouldIgnore == YES);
448 [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
451 @"UIKeyboardFrameEndUserInfoKey" : @(zeroHeightKeyboard),
452 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
453 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
455 shouldIgnore = [viewControllerMock shouldIgnoreKeyboardNotification:notification];
456 XCTAssertTrue(shouldIgnore == NO);
461 [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
464 @"UIKeyboardFrameEndUserInfoKey" : @(validKeyboardEndFrame),
465 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
466 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
468 shouldIgnore = [viewControllerMock shouldIgnoreKeyboardNotification:notification];
469 XCTAssertTrue(shouldIgnore == YES);
474 [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
477 @"UIKeyboardFrameEndUserInfoKey" : @(validKeyboardEndFrame),
478 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
479 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
481 shouldIgnore = [viewControllerMock shouldIgnoreKeyboardNotification:notification];
482 XCTAssertTrue(shouldIgnore == NO);
485 - (void)testKeyboardAnimationWillNotCrashWhenEngineDestroyed {
491 [viewController setUpKeyboardAnimationVsyncClient:^(fml::TimePoint){
496 - (void)testKeyboardAnimationWillWaitUIThreadVsync {
509 const int delayTime = 1;
510 [engine uiTaskRunner]->PostTask([] { sleep(delayTime); });
511 XCTestExpectation* expectation = [
self expectationWithDescription:@"keyboard animation callback"];
513 __block CFTimeInterval fulfillTime;
515 fulfillTime = CACurrentMediaTime();
516 [expectation fulfill];
518 CFTimeInterval startTime = CACurrentMediaTime();
519 [viewController setUpKeyboardAnimationVsyncClient:callback];
520 [
self waitForExpectationsWithTimeout:5.0 handler:nil];
521 XCTAssertTrue(fulfillTime - startTime > delayTime);
524 - (void)testCalculateKeyboardAttachMode {
526 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
532 UIScreen* screen = [
self setUpMockScreen];
533 CGRect viewFrame = screen.bounds;
534 [
self setUpMockView:viewControllerMock
537 convertedFrame:viewFrame];
539 CGFloat screenWidth = screen.bounds.size.width;
540 CGFloat screenHeight = screen.bounds.size.height;
543 CGRect keyboardFrame = CGRectZero;
544 NSNotification* notification =
545 [NSNotification notificationWithName:UIKeyboardWillHideNotification
548 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
549 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
550 @"UIKeyboardIsLocalUserInfoKey" : @(YES)
552 FlutterKeyboardMode keyboardMode = [viewControllerMock calculateKeyboardAttachMode:notification];
553 XCTAssertTrue(keyboardMode == FlutterKeyboardModeHidden);
556 keyboardFrame = CGRectZero;
557 notification = [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
560 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
561 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
562 @"UIKeyboardIsLocalUserInfoKey" : @(YES)
564 keyboardMode = [viewControllerMock calculateKeyboardAttachMode:notification];
565 XCTAssertTrue(keyboardMode == FlutterKeyboardModeFloating);
568 keyboardFrame = CGRectMake(0, 0, screenWidth, 0);
569 notification = [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
572 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
573 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
574 @"UIKeyboardIsLocalUserInfoKey" : @(YES)
576 keyboardMode = [viewControllerMock calculateKeyboardAttachMode:notification];
577 XCTAssertTrue(keyboardMode == FlutterKeyboardModeHidden);
580 keyboardFrame = CGRectMake(0, 0, 320, 320);
581 notification = [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
584 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
585 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
586 @"UIKeyboardIsLocalUserInfoKey" : @(YES)
588 keyboardMode = [viewControllerMock calculateKeyboardAttachMode:notification];
589 XCTAssertTrue(keyboardMode == FlutterKeyboardModeFloating);
592 keyboardFrame = CGRectMake(0, 0, screenWidth, 320);
593 notification = [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
596 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
597 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
598 @"UIKeyboardIsLocalUserInfoKey" : @(YES)
600 keyboardMode = [viewControllerMock calculateKeyboardAttachMode:notification];
601 XCTAssertTrue(keyboardMode == FlutterKeyboardModeFloating);
604 keyboardFrame = CGRectMake(0, screenHeight - 320, screenWidth, 320);
605 notification = [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
608 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
609 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
610 @"UIKeyboardIsLocalUserInfoKey" : @(YES)
612 keyboardMode = [viewControllerMock calculateKeyboardAttachMode:notification];
613 XCTAssertTrue(keyboardMode == FlutterKeyboardModeDocked);
616 CGFloat longDecimalHeight = 320.666666666666666;
617 keyboardFrame = CGRectMake(0, screenHeight - longDecimalHeight, screenWidth, longDecimalHeight);
618 notification = [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
621 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
622 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
623 @"UIKeyboardIsLocalUserInfoKey" : @(YES)
625 keyboardMode = [viewControllerMock calculateKeyboardAttachMode:notification];
626 XCTAssertTrue(keyboardMode == FlutterKeyboardModeDocked);
629 keyboardFrame = CGRectMake(0, screenHeight - .0000001, screenWidth, longDecimalHeight);
630 notification = [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
633 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
634 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
635 @"UIKeyboardIsLocalUserInfoKey" : @(YES)
637 keyboardMode = [viewControllerMock calculateKeyboardAttachMode:notification];
638 XCTAssertTrue(keyboardMode == FlutterKeyboardModeHidden);
641 keyboardFrame = CGRectMake(0, screenHeight, screenWidth, 320);
642 notification = [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
645 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
646 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
647 @"UIKeyboardIsLocalUserInfoKey" : @(YES)
649 keyboardMode = [viewControllerMock calculateKeyboardAttachMode:notification];
650 XCTAssertTrue(keyboardMode == FlutterKeyboardModeHidden);
653 - (void)testCalculateMultitaskingAdjustment {
655 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
661 UIScreen* screen = [
self setUpMockScreen];
662 CGFloat screenWidth = screen.bounds.size.width;
663 CGFloat screenHeight = screen.bounds.size.height;
664 CGRect screenRect = screen.bounds;
665 CGRect viewOrigFrame = CGRectMake(0, 0, 320, screenHeight - 40);
666 CGRect convertedViewFrame = CGRectMake(20, 20, 320, screenHeight - 40);
667 CGRect keyboardFrame = CGRectMake(20, screenHeight - 320, screenWidth, 300);
668 id mockView = [
self setUpMockView:viewControllerMock
670 viewFrame:viewOrigFrame
671 convertedFrame:convertedViewFrame];
672 id mockTraitCollection = OCMClassMock([UITraitCollection
class]);
673 OCMStub([mockTraitCollection userInterfaceIdiom]).andReturn(UIUserInterfaceIdiomPad);
674 OCMStub([mockTraitCollection horizontalSizeClass]).andReturn(UIUserInterfaceSizeClassCompact);
675 OCMStub([mockTraitCollection verticalSizeClass]).andReturn(UIUserInterfaceSizeClassRegular);
676 OCMStub([mockView traitCollection]).andReturn(mockTraitCollection);
678 CGFloat adjustment = [viewControllerMock calculateMultitaskingAdjustment:screenRect
679 keyboardFrame:keyboardFrame];
680 XCTAssertTrue(adjustment == 20);
683 - (void)testCalculateKeyboardInset {
685 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
690 UIScreen* screen = [
self setUpMockScreen];
691 OCMStub([viewControllerMock flutterScreenIfViewLoaded]).andReturn(screen);
693 CGFloat screenWidth = screen.bounds.size.width;
694 CGFloat screenHeight = screen.bounds.size.height;
695 CGRect viewOrigFrame = CGRectMake(0, 0, 320, screenHeight - 40);
696 CGRect convertedViewFrame = CGRectMake(20, 20, 320, screenHeight - 40);
697 CGRect keyboardFrame = CGRectMake(20, screenHeight - 320, screenWidth, 300);
699 [
self setUpMockView:viewControllerMock
701 viewFrame:viewOrigFrame
702 convertedFrame:convertedViewFrame];
704 CGFloat inset = [viewControllerMock calculateKeyboardInset:keyboardFrame
705 keyboardMode:FlutterKeyboardModeDocked];
706 XCTAssertTrue(inset == 300 * screen.scale);
709 - (void)testHandleKeyboardNotification {
716 UIScreen* screen = [
self setUpMockScreen];
717 CGFloat screenWidth = screen.bounds.size.width;
718 CGFloat screenHeight = screen.bounds.size.height;
719 CGRect keyboardFrame = CGRectMake(0, screenHeight - 320, screenWidth, 320);
720 CGRect viewFrame = screen.bounds;
722 NSNotification* notification =
723 [NSNotification notificationWithName:UIKeyboardWillShowNotification
726 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
727 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
728 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
731 [
self setUpMockView:viewControllerMock
734 convertedFrame:viewFrame];
735 viewControllerMock.targetViewInsetBottom = 0;
736 XCTestExpectation* expectation = [
self expectationWithDescription:@"update viewport"];
737 OCMStub([viewControllerMock updateViewportMetricsIfNeeded]).andDo(^(NSInvocation* invocation) {
738 [expectation fulfill];
741 [viewControllerMock handleKeyboardNotification:notification];
742 XCTAssertTrue(viewControllerMock.targetViewInsetBottom == 320 * screen.scale);
743 OCMVerify([viewControllerMock startKeyBoardAnimation:0.25]);
744 [
self waitForExpectationsWithTimeout:5.0 handler:nil];
747 - (void)testEnsureBottomInsetIsZeroWhenKeyboardDismissed {
749 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
755 CGRect keyboardFrame = CGRectZero;
757 NSNotification* fakeNotification =
758 [NSNotification notificationWithName:UIKeyboardWillHideNotification
761 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
762 @"UIKeyboardAnimationDurationUserInfoKey" : @(0.25),
763 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
766 viewControllerMock.targetViewInsetBottom = 10;
767 [viewControllerMock handleKeyboardNotification:fakeNotification];
768 XCTAssertTrue(viewControllerMock.targetViewInsetBottom == 0);
771 - (void)testStopKeyBoardAnimationWhenReceivedWillHideNotificationAfterWillShowNotification {
780 UIScreen* screen = [
self setUpMockScreen];
781 CGRect viewFrame = screen.bounds;
782 [
self setUpMockView:viewControllerMock
785 convertedFrame:viewFrame];
786 viewControllerMock.targetViewInsetBottom = 0;
788 CGFloat screenHeight = screen.bounds.size.height;
789 CGFloat screenWidth = screen.bounds.size.height;
790 CGRect keyboardFrame = CGRectMake(0, screenHeight - 320, screenWidth, 320);
794 NSNotification* fakeShowNotification =
795 [NSNotification notificationWithName:UIKeyboardWillShowNotification
798 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
799 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
800 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
802 [viewControllerMock handleKeyboardNotification:fakeShowNotification];
803 XCTAssertTrue(viewControllerMock.targetViewInsetBottom == 320 * screen.scale);
806 NSNotification* fakeHideNotification =
807 [NSNotification notificationWithName:UIKeyboardWillHideNotification
810 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
811 @"UIKeyboardAnimationDurationUserInfoKey" : @(0.0),
812 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
814 [viewControllerMock handleKeyboardNotification:fakeHideNotification];
815 XCTAssertTrue(viewControllerMock.targetViewInsetBottom == 0);
818 XCTAssertNil(viewControllerMock.keyboardAnimationView);
819 XCTAssertNil(viewControllerMock.keyboardSpringAnimation);
822 - (void)testEnsureViewportMetricsWillInvokeAndDisplayLinkWillInvalidateInViewDidDisappear {
824 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
829 [viewControllerMock viewDidDisappear:YES];
830 OCMVerify([viewControllerMock ensureViewportMetricsIsCorrect]);
831 OCMVerify([viewControllerMock invalidateKeyboardAnimationVSyncClient]);
834 - (void)testViewDidDisappearDoesntPauseEngineWhenNotTheViewController {
837 mockEngine.lifecycleChannel = lifecycleChannel;
842 id viewControllerMock = OCMPartialMock(viewControllerA);
843 OCMStub([viewControllerMock surfaceUpdated:NO]);
845 [viewControllerA viewDidDisappear:NO];
846 OCMReject([lifecycleChannel sendMessage:
@"AppLifecycleState.paused"]);
847 OCMReject([viewControllerMock surfaceUpdated:[OCMArg any]]);
850 - (void)testAppWillTerminateViewDidDestroyTheEngine {
852 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
857 OCMStub([viewControllerMock goToApplicationLifecycle:
@"AppLifecycleState.detached"]);
859 [viewController applicationWillTerminate:nil];
860 OCMVerify([viewControllerMock goToApplicationLifecycle:
@"AppLifecycleState.detached"]);
864 - (void)testViewDidDisappearDoesPauseEngineWhenIsTheViewController {
867 mockEngine.lifecycleChannel = lifecycleChannel;
875 OCMStub([viewControllerMock surfaceUpdated:NO]);
876 [viewController viewDidDisappear:NO];
877 OCMVerify([lifecycleChannel sendMessage:
@"AppLifecycleState.paused"]);
878 OCMVerify([viewControllerMock surfaceUpdated:NO]);
880 XCTAssertNil(weakViewController);
884 testEngineConfigSyncMethodWillExecuteWhenViewControllerInEngineIsCurrentViewControllerInViewWillAppear {
886 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
890 [viewController viewWillAppear:YES];
895 testEngineConfigSyncMethodWillNotExecuteWhenViewControllerInEngineIsNotCurrentViewControllerInViewWillAppear {
897 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
907 [viewControllerA viewWillAppear:YES];
908 OCMVerify(never(), [viewControllerA onUserSettingsChanged:nil]);
912 testEngineConfigSyncMethodWillExecuteWhenViewControllerInEngineIsCurrentViewControllerInViewDidAppear {
914 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
918 [viewController viewDidAppear:YES];
923 testEngineConfigSyncMethodWillNotExecuteWhenViewControllerInEngineIsNotCurrentViewControllerInViewDidAppear {
925 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
935 [viewControllerA viewDidAppear:YES];
936 OCMVerify(never(), [viewControllerA onUserSettingsChanged:nil]);
940 testEngineConfigSyncMethodWillExecuteWhenViewControllerInEngineIsCurrentViewControllerInViewWillDisappear {
943 mockEngine.lifecycleChannel = lifecycleChannel;
948 [viewController viewWillDisappear:NO];
949 OCMVerify([lifecycleChannel sendMessage:
@"AppLifecycleState.inactive"]);
953 testEngineConfigSyncMethodWillNotExecuteWhenViewControllerInEngineIsNotCurrentViewControllerInViewWillDisappear {
956 mockEngine.lifecycleChannel = lifecycleChannel;
964 [viewControllerA viewDidDisappear:NO];
965 OCMReject([lifecycleChannel sendMessage:
@"AppLifecycleState.inactive"]);
968 - (void)testUpdateViewportMetricsIfNeeded_DoesntInvokeEngineWhenNotTheViewController {
970 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
979 [viewControllerA updateViewportMetricsIfNeeded];
980 flutter::ViewportMetrics viewportMetrics;
981 OCMVerify(never(), [
mockEngine updateViewportMetrics:viewportMetrics]);
984 - (void)testUpdateViewportMetricsIfNeeded_DoesInvokeEngineWhenIsTheViewController {
986 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
991 flutter::ViewportMetrics viewportMetrics;
992 OCMExpect([
mockEngine updateViewportMetrics:viewportMetrics]).ignoringNonObjectArgs();
993 [viewController updateViewportMetricsIfNeeded];
997 - (void)testUpdateViewportMetricsIfNeeded_DoesNotInvokeEngineWhenShouldBeIgnoredDuringRotation {
999 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1004 UIScreen* screen = [
self setUpMockScreen];
1005 OCMStub([viewControllerMock flutterScreenIfViewLoaded]).andReturn(screen);
1008 id mockCoordinator = OCMProtocolMock(
@protocol(UIViewControllerTransitionCoordinator));
1009 OCMStub([mockCoordinator transitionDuration]).andReturn(0.5);
1012 [viewController viewWillTransitionToSize:CGSizeZero withTransitionCoordinator:mockCoordinator];
1014 [viewController updateViewportMetricsIfNeeded];
1016 OCMVerify(never(), [
mockEngine updateViewportMetrics:flutter::ViewportMetrics()]);
1019 - (void)testViewWillTransitionToSize_DoesDelayEngineCallIfNonZeroDuration {
1021 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1026 UIScreen* screen = [
self setUpMockScreen];
1027 OCMStub([viewControllerMock flutterScreenIfViewLoaded]).andReturn(screen);
1031 NSTimeInterval transitionDuration = 0.5;
1032 id mockCoordinator = OCMProtocolMock(
@protocol(UIViewControllerTransitionCoordinator));
1033 OCMStub([mockCoordinator transitionDuration]).andReturn(transitionDuration);
1035 flutter::ViewportMetrics viewportMetrics;
1036 OCMExpect([
mockEngine updateViewportMetrics:viewportMetrics]).ignoringNonObjectArgs();
1038 [viewController viewWillTransitionToSize:CGSizeZero withTransitionCoordinator:mockCoordinator];
1040 [viewController updateViewportMetricsIfNeeded];
1041 OCMVerify(never(), [
mockEngine updateViewportMetrics:flutter::ViewportMetrics()]);
1045 XCTWaiterResult result = [XCTWaiter
1046 waitForExpectations:@[ [
self expectationWithDescription:@"Waiting for rotation duration"] ]
1047 timeout:transitionDuration];
1048 XCTAssertEqual(result, XCTWaiterResultTimedOut);
1053 - (void)testViewWillTransitionToSize_DoesNotDelayEngineCallIfZeroDuration {
1055 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1060 UIScreen* screen = [
self setUpMockScreen];
1061 OCMStub([viewControllerMock flutterScreenIfViewLoaded]).andReturn(screen);
1065 id mockCoordinator = OCMProtocolMock(
@protocol(UIViewControllerTransitionCoordinator));
1066 OCMStub([mockCoordinator transitionDuration]).andReturn(0);
1068 flutter::ViewportMetrics viewportMetrics;
1069 OCMExpect([
mockEngine updateViewportMetrics:viewportMetrics]).ignoringNonObjectArgs();
1072 [viewController viewWillTransitionToSize:CGSizeZero withTransitionCoordinator:mockCoordinator];
1073 [viewController updateViewportMetricsIfNeeded];
1078 - (void)testViewDidLoadDoesntInvokeEngineWhenNotTheViewController {
1080 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1089 UIView* view = viewControllerA.view;
1090 XCTAssertNotNil(view);
1094 - (void)testViewDidLoadDoesInvokeEngineWhenIsTheViewController {
1096 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1103 XCTAssertNotNil(view);
1104 OCMVerify(times(1), [
mockEngine attachView]);
1107 - (void)testViewDidLoadDoesntInvokeEngineAttachViewWhenEngineNeedsLaunch {
1109 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1115 [viewController sharedSetupWithProject:nil initialRoute:nil];
1118 XCTAssertNotNil(view);
1122 - (void)testSplashScreenViewRemoveNotCrash {
1127 [flutterViewController setSplashScreenView:[[UIView alloc] init]];
1128 [flutterViewController setSplashScreenView:nil];
1131 - (void)testInternalPluginsWeakPtrNotCrash {
1137 [vc addInternalPlugins];
1140 [(NSArray<id<FlutterKeyPrimaryResponder>>*)keyboardManager.primaryResponders firstObject];
1141 sendEvent = [keyPrimaryResponder sendEvent];
1145 sendEvent({}, nil, nil);
1150 - (void)testInternalPluginsInvokeInViewDidLoad {
1152 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1160 XCTAssertNotNil(view);
1161 [viewController viewDidLoad];
1165 - (void)testBinaryMessenger {
1169 XCTAssertNotNil(vc);
1171 OCMStub([
self.
mockEngine binaryMessenger]).andReturn(messenger);
1173 OCMVerify([
self.
mockEngine binaryMessenger]);
1176 - (void)testViewControllerIsReleased {
1178 __weak UIView* weakView;
1187 [viewController loadView];
1188 [viewController viewDidLoad];
1192 XCTAssertNil(weakViewController);
1193 XCTAssertNil(weakView);
1196 #pragma mark - Platform Brightness
1198 - (void)testItReportsLightPlatformBrightnessByDefault {
1201 OCMStub([
self.
mockEngine settingsChannel]).andReturn(settingsChannel);
1208 [vc traitCollectionDidChange:nil];
1211 OCMVerify([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(
id message) {
1212 return [message[@"platformBrightness"] isEqualToString:@"light"];
1216 [settingsChannel stopMocking];
1219 - (void)testItReportsPlatformBrightnessWhenViewWillAppear {
1223 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1224 OCMStub([
mockEngine settingsChannel]).andReturn(settingsChannel);
1230 [vc viewWillAppear:false];
1233 OCMVerify([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(
id message) {
1234 return [message[@"platformBrightness"] isEqualToString:@"light"];
1238 [settingsChannel stopMocking];
1241 - (void)testItReportsDarkPlatformBrightnessWhenTraitCollectionRequestsIt {
1244 OCMStub([
self.
mockEngine settingsChannel]).andReturn(settingsChannel);
1245 id mockTraitCollection =
1246 [
self fakeTraitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleDark];
1255 OCMStub([partialMockVC traitCollection]).andReturn(mockTraitCollection);
1258 [partialMockVC traitCollectionDidChange:nil];
1261 OCMVerify([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(
id message) {
1262 return [message[@"platformBrightness"] isEqualToString:@"dark"];
1266 [partialMockVC stopMocking];
1267 [settingsChannel stopMocking];
1268 [mockTraitCollection stopMocking];
1273 - (UITraitCollection*)fakeTraitCollectionWithUserInterfaceStyle:(UIUserInterfaceStyle)style {
1274 id mockTraitCollection = OCMClassMock([UITraitCollection
class]);
1275 OCMStub([mockTraitCollection userInterfaceStyle]).andReturn(style);
1276 return mockTraitCollection;
1279 #pragma mark - Platform Contrast
1281 - (void)testItReportsNormalPlatformContrastByDefault {
1284 OCMStub([
self.
mockEngine settingsChannel]).andReturn(settingsChannel);
1291 [vc traitCollectionDidChange:nil];
1294 OCMVerify([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(
id message) {
1295 return [message[@"platformContrast"] isEqualToString:@"normal"];
1299 [settingsChannel stopMocking];
1302 - (void)testItReportsPlatformContrastWhenViewWillAppear {
1304 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1308 OCMStub([
mockEngine settingsChannel]).andReturn(settingsChannel);
1314 [vc viewWillAppear:false];
1317 OCMVerify([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(
id message) {
1318 return [message[@"platformContrast"] isEqualToString:@"normal"];
1322 [settingsChannel stopMocking];
1325 - (void)testItReportsHighContrastWhenTraitCollectionRequestsIt {
1328 OCMStub([
self.
mockEngine settingsChannel]).andReturn(settingsChannel);
1330 id mockTraitCollection = [
self fakeTraitCollectionWithContrast:UIAccessibilityContrastHigh];
1339 OCMStub([partialMockVC traitCollection]).andReturn(mockTraitCollection);
1342 [partialMockVC traitCollectionDidChange:mockTraitCollection];
1345 OCMVerify([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(
id message) {
1346 return [message[@"platformContrast"] isEqualToString:@"high"];
1350 [partialMockVC stopMocking];
1351 [settingsChannel stopMocking];
1352 [mockTraitCollection stopMocking];
1355 - (void)testItReportsAlwaysUsed24HourFormat {
1358 OCMStub([
self.
mockEngine settingsChannel]).andReturn(settingsChannel);
1364 OCMStub([mockHourFormat isAlwaysUse24HourFormat]).andReturn(YES);
1365 OCMExpect([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(
id message) {
1366 return [message[@"alwaysUse24HourFormat"] isEqual:@(YES)];
1368 [vc onUserSettingsChanged:nil];
1369 [mockHourFormat stopMocking];
1373 OCMStub([mockHourFormat isAlwaysUse24HourFormat]).andReturn(NO);
1374 OCMExpect([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(
id message) {
1375 return [message[@"alwaysUse24HourFormat"] isEqual:@(NO)];
1377 [vc onUserSettingsChanged:nil];
1378 [mockHourFormat stopMocking];
1381 [settingsChannel stopMocking];
1384 - (void)testItReportsAccessibilityOnOffSwitchLabelsFlagNotSet {
1389 OCMStub([partialMockViewController accessibilityIsOnOffSwitchLabelsEnabled]).andReturn(NO);
1392 int32_t flags = [partialMockViewController accessibilityFlags];
1395 XCTAssert((flags & (int32_t)flutter::AccessibilityFeatureFlag::kOnOffSwitchLabels) == 0);
1398 - (void)testItReportsAccessibilityOnOffSwitchLabelsFlagSet {
1403 OCMStub([partialMockViewController accessibilityIsOnOffSwitchLabelsEnabled]).andReturn(YES);
1406 int32_t flags = [partialMockViewController accessibilityFlags];
1409 XCTAssert((flags & (int32_t)flutter::AccessibilityFeatureFlag::kOnOffSwitchLabels) != 0);
1412 - (void)testAccessibilityPerformEscapePopsRoute {
1414 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1416 OCMStub([
mockEngine navigationChannel]).andReturn(mockNavigationChannel);
1423 OCMVerify([mockNavigationChannel invokeMethod:
@"popRoute" arguments:nil]);
1425 [mockNavigationChannel stopMocking];
1428 - (void)testPerformOrientationUpdateForcesOrientationChange {
1429 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortrait
1430 currentOrientation:UIInterfaceOrientationLandscapeLeft
1431 didChangeOrientation:YES
1432 resultingOrientation:UIInterfaceOrientationPortrait];
1434 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortrait
1435 currentOrientation:UIInterfaceOrientationLandscapeRight
1436 didChangeOrientation:YES
1437 resultingOrientation:UIInterfaceOrientationPortrait];
1439 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortrait
1440 currentOrientation:UIInterfaceOrientationPortraitUpsideDown
1441 didChangeOrientation:YES
1442 resultingOrientation:UIInterfaceOrientationPortrait];
1444 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortraitUpsideDown
1445 currentOrientation:UIInterfaceOrientationLandscapeLeft
1446 didChangeOrientation:YES
1447 resultingOrientation:UIInterfaceOrientationPortraitUpsideDown];
1449 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortraitUpsideDown
1450 currentOrientation:UIInterfaceOrientationLandscapeRight
1451 didChangeOrientation:YES
1452 resultingOrientation:UIInterfaceOrientationPortraitUpsideDown];
1454 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortraitUpsideDown
1455 currentOrientation:UIInterfaceOrientationPortrait
1456 didChangeOrientation:YES
1457 resultingOrientation:UIInterfaceOrientationPortraitUpsideDown];
1459 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscape
1460 currentOrientation:UIInterfaceOrientationPortrait
1461 didChangeOrientation:YES
1462 resultingOrientation:UIInterfaceOrientationLandscapeLeft];
1464 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscape
1465 currentOrientation:UIInterfaceOrientationPortraitUpsideDown
1466 didChangeOrientation:YES
1467 resultingOrientation:UIInterfaceOrientationLandscapeLeft];
1469 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeLeft
1470 currentOrientation:UIInterfaceOrientationPortrait
1471 didChangeOrientation:YES
1472 resultingOrientation:UIInterfaceOrientationLandscapeLeft];
1474 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeLeft
1475 currentOrientation:UIInterfaceOrientationLandscapeRight
1476 didChangeOrientation:YES
1477 resultingOrientation:UIInterfaceOrientationLandscapeLeft];
1479 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeLeft
1480 currentOrientation:UIInterfaceOrientationPortraitUpsideDown
1481 didChangeOrientation:YES
1482 resultingOrientation:UIInterfaceOrientationLandscapeLeft];
1484 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeRight
1485 currentOrientation:UIInterfaceOrientationPortrait
1486 didChangeOrientation:YES
1487 resultingOrientation:UIInterfaceOrientationLandscapeRight];
1489 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeRight
1490 currentOrientation:UIInterfaceOrientationLandscapeLeft
1491 didChangeOrientation:YES
1492 resultingOrientation:UIInterfaceOrientationLandscapeRight];
1494 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeRight
1495 currentOrientation:UIInterfaceOrientationPortraitUpsideDown
1496 didChangeOrientation:YES
1497 resultingOrientation:UIInterfaceOrientationLandscapeRight];
1499 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAllButUpsideDown
1500 currentOrientation:UIInterfaceOrientationPortraitUpsideDown
1501 didChangeOrientation:YES
1502 resultingOrientation:UIInterfaceOrientationPortrait];
1505 - (void)testPerformOrientationUpdateDoesNotForceOrientationChange {
1506 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAll
1507 currentOrientation:UIInterfaceOrientationPortrait
1508 didChangeOrientation:NO
1509 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1511 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAll
1512 currentOrientation:UIInterfaceOrientationPortraitUpsideDown
1513 didChangeOrientation:NO
1514 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1516 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAll
1517 currentOrientation:UIInterfaceOrientationLandscapeLeft
1518 didChangeOrientation:NO
1519 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1521 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAll
1522 currentOrientation:UIInterfaceOrientationLandscapeRight
1523 didChangeOrientation:NO
1524 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1526 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAllButUpsideDown
1527 currentOrientation:UIInterfaceOrientationPortrait
1528 didChangeOrientation:NO
1529 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1531 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAllButUpsideDown
1532 currentOrientation:UIInterfaceOrientationLandscapeLeft
1533 didChangeOrientation:NO
1534 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1536 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAllButUpsideDown
1537 currentOrientation:UIInterfaceOrientationLandscapeRight
1538 didChangeOrientation:NO
1539 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1541 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortrait
1542 currentOrientation:UIInterfaceOrientationPortrait
1543 didChangeOrientation:NO
1544 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1546 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortraitUpsideDown
1547 currentOrientation:UIInterfaceOrientationPortraitUpsideDown
1548 didChangeOrientation:NO
1549 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1551 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscape
1552 currentOrientation:UIInterfaceOrientationLandscapeLeft
1553 didChangeOrientation:NO
1554 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1556 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscape
1557 currentOrientation:UIInterfaceOrientationLandscapeRight
1558 didChangeOrientation:NO
1559 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1561 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeLeft
1562 currentOrientation:UIInterfaceOrientationLandscapeLeft
1563 didChangeOrientation:NO
1564 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1566 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeRight
1567 currentOrientation:UIInterfaceOrientationLandscapeRight
1568 didChangeOrientation:NO
1569 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1574 - (void)orientationTestWithOrientationUpdate:(UIInterfaceOrientationMask)mask
1575 currentOrientation:(UIInterfaceOrientation)currentOrientation
1576 didChangeOrientation:(BOOL)didChange
1577 resultingOrientation:(UIInterfaceOrientation)resultingOrientation {
1578 id mockApplication = OCMClassMock([UIApplication
class]);
1582 __block __weak
id weakPreferences;
1588 if (@available(iOS 16.0, *)) {
1589 mockWindowScene = OCMClassMock([UIWindowScene
class]);
1590 mockVC = OCMPartialMock(realVC);
1591 OCMStub([mockVC flutterWindowSceneIfViewLoaded]).andReturn(mockWindowScene);
1592 if (realVC.supportedInterfaceOrientations == mask) {
1593 OCMReject([mockWindowScene requestGeometryUpdateWithPreferences:[OCMArg any]
1594 errorHandler:[OCMArg any]]);
1598 OCMExpect([mockWindowScene
1599 requestGeometryUpdateWithPreferences:[OCMArg checkWithBlock:^BOOL(
1600 UIWindowSceneGeometryPreferencesIOS*
1602 weakPreferences = preferences;
1603 return preferences.interfaceOrientations == mask;
1605 errorHandler:[OCMArg any]]);
1607 OCMStub([mockApplication sharedApplication]).andReturn(mockApplication);
1608 OCMStub([mockApplication connectedScenes]).andReturn([NSSet setWithObject:mockWindowScene]);
1610 deviceMock = OCMPartialMock([UIDevice currentDevice]);
1612 OCMReject([deviceMock setValue:[OCMArg any] forKey:
@"orientation"]);
1614 OCMExpect([deviceMock setValue:@(resultingOrientation) forKey:
@"orientation"]);
1616 mockWindowScene = OCMClassMock([UIWindowScene
class]);
1617 mockVC = OCMPartialMock(realVC);
1618 OCMStub([mockVC flutterWindowSceneIfViewLoaded]).andReturn(mockWindowScene);
1619 OCMStub(((UIWindowScene*)mockWindowScene).interfaceOrientation).andReturn(currentOrientation);
1622 [realVC performOrientationUpdate:mask];
1623 if (@available(iOS 16.0, *)) {
1624 OCMVerifyAll(mockWindowScene);
1626 OCMVerifyAll(deviceMock);
1629 [mockWindowScene stopMocking];
1630 [deviceMock stopMocking];
1631 [mockApplication stopMocking];
1632 XCTAssertNil(weakPreferences);
1637 - (UITraitCollection*)fakeTraitCollectionWithContrast:(UIAccessibilityContrast)contrast {
1638 id mockTraitCollection = OCMClassMock([UITraitCollection
class]);
1639 OCMStub([mockTraitCollection accessibilityContrast]).andReturn(contrast);
1640 return mockTraitCollection;
1643 - (void)testWillDeallocNotification {
1644 XCTestExpectation* expectation =
1645 [[XCTestExpectation alloc] initWithDescription:@"notification called"];
1652 [NSNotificationCenter.defaultCenter addObserverForName:FlutterViewControllerWillDealloc
1654 queue:[NSOperationQueue mainQueue]
1655 usingBlock:^(NSNotification* _Nonnull note) {
1656 [expectation fulfill];
1658 XCTAssertNotNil(realVC);
1661 [
self waitForExpectations:@[ expectation ] timeout:1.0];
1664 - (void)testReleasesKeyboardManagerOnDealloc {
1669 [viewController addInternalPlugins];
1671 XCTAssertNotNil(weakKeyboardManager);
1672 [viewController deregisterNotifications];
1676 XCTAssertNil(weakKeyboardManager);
1679 - (void)testDoesntLoadViewInInit {
1682 [engine createShell:@"" libraryURI:@"" initialRoute:nil];
1686 XCTAssertFalse([realVC isViewLoaded],
@"shouldn't have loaded since it hasn't been shown");
1687 engine.viewController = nil;
1690 - (void)testHideOverlay {
1693 [engine createShell:@"" libraryURI:@"" initialRoute:nil];
1697 XCTAssertFalse(realVC.prefersHomeIndicatorAutoHidden,
@"");
1698 [NSNotificationCenter.defaultCenter postNotificationName:FlutterViewControllerHideHomeIndicator
1700 XCTAssertTrue(realVC.prefersHomeIndicatorAutoHidden,
@"");
1701 engine.viewController = nil;
1704 - (void)testNotifyLowMemory {
1710 OCMStub([viewControllerMock surfaceUpdated:NO]);
1711 [viewController beginAppearanceTransition:NO animated:NO];
1712 [viewController endAppearanceTransition];
1713 XCTAssertTrue(
mockEngine.didCallNotifyLowMemory);
1716 - (void)sendMessage:(
id _Nullable)message reply:(
FlutterReply _Nullable)callback {
1717 NSMutableDictionary* replyMessage = [@{
1722 self.messageSent = message;
1723 CFRunLoopPerformBlock(CFRunLoopGetCurrent(), fml::MessageLoopDarwin::kMessageLoopCFRunLoopMode,
1725 callback(replyMessage);
1730 if (@available(iOS 13.4, *)) {
1737 OCMStub([
mockEngine.keyEventChannel sendMessage:[OCMArg any] reply:[OCMArg any]])
1738 .andCall(
self,
@selector(sendMessage:reply:));
1740 mockEngine.textInputPlugin =
self.mockTextInputPlugin;
1748 [vc addInternalPlugins];
1750 [vc handlePressEvent:keyUpEvent(UIKeyboardHIDUsageKeyboardA, UIKeyModifierShift, 123.0)
1755 XCTAssert([
self.
messageSent[
@"keymap"] isEqualToString:
@"ios"]);
1756 XCTAssert([
self.
messageSent[
@"type"] isEqualToString:
@"keyup"]);
1757 XCTAssert([
self.
messageSent[
@"keyCode"] isEqualToNumber:[NSNumber numberWithInt:4]]);
1758 XCTAssert([
self.
messageSent[
@"modifiers"] isEqualToNumber:[NSNumber numberWithInt:0]]);
1759 XCTAssert([
self.
messageSent[
@"characters"] isEqualToString:
@""]);
1760 XCTAssert([
self.
messageSent[
@"charactersIgnoringModifiers"] isEqualToString:
@""]);
1761 [vc deregisterNotifications];
1765 if (@available(iOS 13.4, *)) {
1773 OCMStub([
mockEngine.keyEventChannel sendMessage:[OCMArg any] reply:[OCMArg any]])
1774 .andCall(
self,
@selector(sendMessage:reply:));
1776 mockEngine.textInputPlugin =
self.mockTextInputPlugin;
1783 [vc addInternalPlugins];
1785 [vc handlePressEvent:keyDownEvent(UIKeyboardHIDUsageKeyboardA, UIKeyModifierShift, 123.0f, "A",
1791 XCTAssert([
self.
messageSent[
@"keymap"] isEqualToString:
@"ios"]);
1792 XCTAssert([
self.
messageSent[
@"type"] isEqualToString:
@"keydown"]);
1793 XCTAssert([
self.
messageSent[
@"keyCode"] isEqualToNumber:[NSNumber numberWithInt:4]]);
1794 XCTAssert([
self.
messageSent[
@"modifiers"] isEqualToNumber:[NSNumber numberWithInt:0]]);
1795 XCTAssert([
self.
messageSent[
@"characters"] isEqualToString:
@"A"]);
1796 XCTAssert([
self.
messageSent[
@"charactersIgnoringModifiers"] isEqualToString:
@"a"]);
1797 [vc deregisterNotifications];
1802 if (@available(iOS 13.4, *)) {
1808 OCMStub([keyEventChannel sendMessage:[OCMArg any] reply:[OCMArg any]])
1809 .andCall(
self,
@selector(sendMessage:reply:));
1811 OCMStub([
self.
mockEngine keyEventChannel]).andReturn(keyEventChannel);
1819 [vc addInternalPlugins];
1821 [vc handlePressEvent:keyEventWithPhase(UIPressPhaseStationary, UIKeyboardHIDUsageKeyboardA,
1822 UIKeyModifierShift, 123.0)
1825 [vc handlePressEvent:keyEventWithPhase(UIPressPhaseCancelled, UIKeyboardHIDUsageKeyboardA,
1826 UIKeyModifierShift, 123.0)
1829 [vc handlePressEvent:keyEventWithPhase(UIPressPhaseChanged, UIKeyboardHIDUsageKeyboardA,
1830 UIKeyModifierShift, 123.0)
1835 OCMVerify(never(), [keyEventChannel sendMessage:[OCMArg any]]);
1836 [vc deregisterNotifications];
1840 if (@available(iOS 13.4, *)) {
1849 XCTAssertNotNil(vc);
1850 UIView* view = vc.view;
1851 XCTAssertNotNil(view);
1852 NSArray* gestureRecognizers = view.gestureRecognizers;
1853 XCTAssertNotNil(gestureRecognizers);
1856 for (
id gesture in gestureRecognizers) {
1857 if ([gesture isKindOfClass:[UIPanGestureRecognizer
class]]) {
1862 XCTAssertTrue(found);
1866 if (@available(iOS 13.4, *)) {
1875 XCTAssertNotNil(vc);
1877 id mockPanGestureRecognizer = OCMClassMock([UIPanGestureRecognizer
class]);
1878 XCTAssertNotNil(mockPanGestureRecognizer);
1880 [vc discreteScrollEvent:mockPanGestureRecognizer];
1883 [[mockPanGestureRecognizer verify] locationInView:[OCMArg any]];
1884 [[[
self.mockEngine verify] ignoringNonObjectArgs]
1885 dispatchPointerDataPacket:std::make_unique<flutter::PointerDataPacket>(0)];
1888 - (void)testFakeEventTimeStamp {
1892 XCTAssertNotNil(vc);
1894 flutter::PointerData pointer_data = [vc generatePointerDataForFake];
1895 int64_t current_micros = [[NSProcessInfo processInfo] systemUptime] * 1000 * 1000;
1896 int64_t interval_micros = current_micros - pointer_data.time_stamp;
1897 const int64_t tolerance_millis = 2;
1898 XCTAssertTrue(interval_micros / 1000 < tolerance_millis,
1899 @"PointerData.time_stamp should be equal to NSProcessInfo.systemUptime");
1902 - (void)testSplashScreenViewCanSetNil {
1905 [flutterViewController setSplashScreenView:nil];
1908 - (void)testLifeCycleNotificationApplicationBecameActive {
1913 UIWindow* window = [[UIWindow alloc] init];
1914 [window addSubview:flutterViewController.view];
1915 flutterViewController.view.bounds = CGRectMake(0, 0, 100, 100);
1916 [flutterViewController viewDidLayoutSubviews];
1917 NSNotification* sceneNotification =
1918 [NSNotification notificationWithName:UISceneDidActivateNotification object:nil userInfo:nil];
1919 NSNotification* applicationNotification =
1920 [NSNotification notificationWithName:UIApplicationDidBecomeActiveNotification
1923 id mockVC = OCMPartialMock(flutterViewController);
1924 [NSNotificationCenter.defaultCenter postNotification:sceneNotification];
1925 [NSNotificationCenter.defaultCenter postNotification:applicationNotification];
1926 OCMReject([mockVC sceneBecameActive:[OCMArg any]]);
1927 OCMVerify([mockVC applicationBecameActive:[OCMArg any]]);
1928 XCTAssertFalse(flutterViewController.isKeyboardInOrTransitioningFromBackground);
1929 OCMVerify([mockVC surfaceUpdated:YES]);
1930 XCTestExpectation* timeoutApplicationLifeCycle =
1931 [
self expectationWithDescription:@"timeoutApplicationLifeCycle"];
1932 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)),
1933 dispatch_get_main_queue(), ^{
1934 [timeoutApplicationLifeCycle fulfill];
1935 OCMVerify([mockVC goToApplicationLifecycle:
@"AppLifecycleState.resumed"]);
1936 [flutterViewController deregisterNotifications];
1938 [
self waitForExpectationsWithTimeout:5.0 handler:nil];
1941 - (void)testLifeCycleNotificationSceneBecameActive {
1942 id mockBundle = OCMPartialMock([NSBundle mainBundle]);
1943 OCMStub([mockBundle objectForInfoDictionaryKey:
@"NSExtension"]).andReturn(@{
1944 @"NSExtensionPointIdentifier" :
@"com.apple.share-services"
1950 UIWindow* window = [[UIWindow alloc] init];
1951 [window addSubview:flutterViewController.view];
1952 flutterViewController.view.bounds = CGRectMake(0, 0, 100, 100);
1953 [flutterViewController viewDidLayoutSubviews];
1954 NSNotification* sceneNotification =
1955 [NSNotification notificationWithName:UISceneDidActivateNotification object:nil userInfo:nil];
1956 NSNotification* applicationNotification =
1957 [NSNotification notificationWithName:UIApplicationDidBecomeActiveNotification
1960 id mockVC = OCMPartialMock(flutterViewController);
1961 [NSNotificationCenter.defaultCenter postNotification:sceneNotification];
1962 [NSNotificationCenter.defaultCenter postNotification:applicationNotification];
1963 OCMVerify([mockVC sceneBecameActive:[OCMArg any]]);
1964 OCMReject([mockVC applicationBecameActive:[OCMArg any]]);
1965 XCTAssertFalse(flutterViewController.isKeyboardInOrTransitioningFromBackground);
1966 OCMVerify([mockVC surfaceUpdated:YES]);
1967 XCTestExpectation* timeoutApplicationLifeCycle =
1968 [
self expectationWithDescription:@"timeoutApplicationLifeCycle"];
1969 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)),
1970 dispatch_get_main_queue(), ^{
1971 [timeoutApplicationLifeCycle fulfill];
1972 OCMVerify([mockVC goToApplicationLifecycle:
@"AppLifecycleState.resumed"]);
1973 [flutterViewController deregisterNotifications];
1975 [
self waitForExpectationsWithTimeout:5.0 handler:nil];
1976 [mockBundle stopMocking];
1979 - (void)testLifeCycleNotificationApplicationWillResignActive {
1984 NSNotification* sceneNotification =
1985 [NSNotification notificationWithName:UISceneWillDeactivateNotification
1988 NSNotification* applicationNotification =
1989 [NSNotification notificationWithName:UIApplicationWillResignActiveNotification
1992 id mockVC = OCMPartialMock(flutterViewController);
1993 [NSNotificationCenter.defaultCenter postNotification:sceneNotification];
1994 [NSNotificationCenter.defaultCenter postNotification:applicationNotification];
1995 OCMReject([mockVC sceneWillResignActive:[OCMArg any]]);
1996 OCMVerify([mockVC applicationWillResignActive:[OCMArg any]]);
1997 OCMVerify([mockVC goToApplicationLifecycle:
@"AppLifecycleState.inactive"]);
1998 [flutterViewController deregisterNotifications];
2001 - (void)testLifeCycleNotificationSceneWillResignActive {
2002 id mockBundle = OCMPartialMock([NSBundle mainBundle]);
2003 OCMStub([mockBundle objectForInfoDictionaryKey:
@"NSExtension"]).andReturn(@{
2004 @"NSExtensionPointIdentifier" :
@"com.apple.share-services"
2010 NSNotification* sceneNotification =
2011 [NSNotification notificationWithName:UISceneWillDeactivateNotification
2014 NSNotification* applicationNotification =
2015 [NSNotification notificationWithName:UIApplicationWillResignActiveNotification
2018 id mockVC = OCMPartialMock(flutterViewController);
2019 [NSNotificationCenter.defaultCenter postNotification:sceneNotification];
2020 [NSNotificationCenter.defaultCenter postNotification:applicationNotification];
2021 OCMVerify([mockVC sceneWillResignActive:[OCMArg any]]);
2022 OCMReject([mockVC applicationWillResignActive:[OCMArg any]]);
2023 OCMVerify([mockVC goToApplicationLifecycle:
@"AppLifecycleState.inactive"]);
2024 [flutterViewController deregisterNotifications];
2025 [mockBundle stopMocking];
2028 - (void)testLifeCycleNotificationApplicationWillTerminate {
2033 NSNotification* sceneNotification =
2034 [NSNotification notificationWithName:UISceneDidDisconnectNotification
2037 NSNotification* applicationNotification =
2038 [NSNotification notificationWithName:UIApplicationWillTerminateNotification
2041 id mockVC = OCMPartialMock(flutterViewController);
2044 [NSNotificationCenter.defaultCenter postNotification:sceneNotification];
2045 [NSNotificationCenter.defaultCenter postNotification:applicationNotification];
2046 OCMReject([mockVC sceneWillDisconnect:[OCMArg any]]);
2047 OCMVerify([mockVC applicationWillTerminate:[OCMArg any]]);
2048 OCMVerify([mockVC goToApplicationLifecycle:
@"AppLifecycleState.detached"]);
2050 [flutterViewController deregisterNotifications];
2053 - (void)testLifeCycleNotificationSceneWillTerminate {
2054 id mockBundle = OCMPartialMock([NSBundle mainBundle]);
2055 OCMStub([mockBundle objectForInfoDictionaryKey:
@"NSExtension"]).andReturn(@{
2056 @"NSExtensionPointIdentifier" :
@"com.apple.share-services"
2062 NSNotification* sceneNotification =
2063 [NSNotification notificationWithName:UISceneDidDisconnectNotification
2066 NSNotification* applicationNotification =
2067 [NSNotification notificationWithName:UIApplicationWillTerminateNotification
2070 id mockVC = OCMPartialMock(flutterViewController);
2073 [NSNotificationCenter.defaultCenter postNotification:sceneNotification];
2074 [NSNotificationCenter.defaultCenter postNotification:applicationNotification];
2075 OCMVerify([mockVC sceneWillDisconnect:[OCMArg any]]);
2076 OCMReject([mockVC applicationWillTerminate:[OCMArg any]]);
2077 OCMVerify([mockVC goToApplicationLifecycle:
@"AppLifecycleState.detached"]);
2079 [flutterViewController deregisterNotifications];
2080 [mockBundle stopMocking];
2083 - (void)testLifeCycleNotificationApplicationDidEnterBackground {
2088 NSNotification* sceneNotification =
2089 [NSNotification notificationWithName:UISceneDidEnterBackgroundNotification
2092 NSNotification* applicationNotification =
2093 [NSNotification notificationWithName:UIApplicationDidEnterBackgroundNotification
2096 id mockVC = OCMPartialMock(flutterViewController);
2097 [NSNotificationCenter.defaultCenter postNotification:sceneNotification];
2098 [NSNotificationCenter.defaultCenter postNotification:applicationNotification];
2099 OCMReject([mockVC sceneDidEnterBackground:[OCMArg any]]);
2100 OCMVerify([mockVC applicationDidEnterBackground:[OCMArg any]]);
2101 XCTAssertTrue(flutterViewController.isKeyboardInOrTransitioningFromBackground);
2102 OCMVerify([mockVC surfaceUpdated:NO]);
2103 OCMVerify([mockVC goToApplicationLifecycle:
@"AppLifecycleState.paused"]);
2104 [flutterViewController deregisterNotifications];
2107 - (void)testLifeCycleNotificationSceneDidEnterBackground {
2108 id mockBundle = OCMPartialMock([NSBundle mainBundle]);
2109 OCMStub([mockBundle objectForInfoDictionaryKey:
@"NSExtension"]).andReturn(@{
2110 @"NSExtensionPointIdentifier" :
@"com.apple.share-services"
2116 NSNotification* sceneNotification =
2117 [NSNotification notificationWithName:UISceneDidEnterBackgroundNotification
2120 NSNotification* applicationNotification =
2121 [NSNotification notificationWithName:UIApplicationDidEnterBackgroundNotification
2124 id mockVC = OCMPartialMock(flutterViewController);
2125 [NSNotificationCenter.defaultCenter postNotification:sceneNotification];
2126 [NSNotificationCenter.defaultCenter postNotification:applicationNotification];
2127 OCMVerify([mockVC sceneDidEnterBackground:[OCMArg any]]);
2128 OCMReject([mockVC applicationDidEnterBackground:[OCMArg any]]);
2129 XCTAssertTrue(flutterViewController.isKeyboardInOrTransitioningFromBackground);
2130 OCMVerify([mockVC surfaceUpdated:NO]);
2131 OCMVerify([mockVC goToApplicationLifecycle:
@"AppLifecycleState.paused"]);
2132 [flutterViewController deregisterNotifications];
2133 [mockBundle stopMocking];
2136 - (void)testLifeCycleNotificationApplicationWillEnterForeground {
2141 NSNotification* sceneNotification =
2142 [NSNotification notificationWithName:UISceneWillEnterForegroundNotification
2145 NSNotification* applicationNotification =
2146 [NSNotification notificationWithName:UIApplicationWillEnterForegroundNotification
2149 id mockVC = OCMPartialMock(flutterViewController);
2150 [NSNotificationCenter.defaultCenter postNotification:sceneNotification];
2151 [NSNotificationCenter.defaultCenter postNotification:applicationNotification];
2152 OCMReject([mockVC sceneWillEnterForeground:[OCMArg any]]);
2153 OCMVerify([mockVC applicationWillEnterForeground:[OCMArg any]]);
2154 OCMVerify([mockVC goToApplicationLifecycle:
@"AppLifecycleState.inactive"]);
2155 [flutterViewController deregisterNotifications];
2158 - (void)testLifeCycleNotificationSceneWillEnterForeground {
2159 id mockBundle = OCMPartialMock([NSBundle mainBundle]);
2160 OCMStub([mockBundle objectForInfoDictionaryKey:
@"NSExtension"]).andReturn(@{
2161 @"NSExtensionPointIdentifier" :
@"com.apple.share-services"
2167 NSNotification* sceneNotification =
2168 [NSNotification notificationWithName:UISceneWillEnterForegroundNotification
2171 NSNotification* applicationNotification =
2172 [NSNotification notificationWithName:UIApplicationWillEnterForegroundNotification
2175 id mockVC = OCMPartialMock(flutterViewController);
2176 [NSNotificationCenter.defaultCenter postNotification:sceneNotification];
2177 [NSNotificationCenter.defaultCenter postNotification:applicationNotification];
2178 OCMVerify([mockVC sceneWillEnterForeground:[OCMArg any]]);
2179 OCMReject([mockVC applicationWillEnterForeground:[OCMArg any]]);
2180 OCMVerify([mockVC goToApplicationLifecycle:
@"AppLifecycleState.inactive"]);
2181 [flutterViewController deregisterNotifications];
2182 [mockBundle stopMocking];
2185 - (void)testLifeCycleNotificationCancelledInvalidResumed {
2190 NSNotification* applicationDidBecomeActiveNotification =
2191 [NSNotification notificationWithName:UIApplicationDidBecomeActiveNotification
2194 NSNotification* applicationWillResignActiveNotification =
2195 [NSNotification notificationWithName:UIApplicationWillResignActiveNotification
2198 id mockVC = OCMPartialMock(flutterViewController);
2199 [NSNotificationCenter.defaultCenter postNotification:applicationDidBecomeActiveNotification];
2200 [NSNotificationCenter.defaultCenter postNotification:applicationWillResignActiveNotification];
2201 OCMVerify([mockVC goToApplicationLifecycle:
@"AppLifecycleState.inactive"]);
2203 XCTestExpectation* timeoutApplicationLifeCycle =
2204 [
self expectationWithDescription:@"timeoutApplicationLifeCycle"];
2205 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)),
2206 dispatch_get_main_queue(), ^{
2207 OCMReject([mockVC goToApplicationLifecycle:
@"AppLifecycleState.resumed"]);
2208 [timeoutApplicationLifeCycle fulfill];
2209 [flutterViewController deregisterNotifications];
2211 [
self waitForExpectationsWithTimeout:5.0 handler:nil];
2214 - (void)testSetupKeyboardAnimationVsyncClientWillCreateNewVsyncClientForFlutterViewController {
2215 id bundleMock = OCMPartialMock([NSBundle mainBundle]);
2219 double maxFrameRate = 120;
2220 [[[mockDisplayLinkManager stub] andReturnValue:@(maxFrameRate)] displayRefreshRate];
2228 [viewController setUpKeyboardAnimationVsyncClient:callback];
2230 CADisplayLink* link = [viewController.keyboardAnimationVSyncClient getDisplayLink];
2231 XCTAssertNotNil(link);
2232 if (@available(iOS 15.0, *)) {
2233 XCTAssertEqual(link.preferredFrameRateRange.maximum, maxFrameRate);
2234 XCTAssertEqual(link.preferredFrameRateRange.preferred, maxFrameRate);
2235 XCTAssertEqual(link.preferredFrameRateRange.minimum, maxFrameRate / 2);
2237 XCTAssertEqual(link.preferredFramesPerSecond, maxFrameRate);
2242 testCreateTouchRateCorrectionVSyncClientWillCreateVsyncClientWhenRefreshRateIsLargerThan60HZ {
2244 double maxFrameRate = 120;
2245 [[[mockDisplayLinkManager stub] andReturnValue:@(maxFrameRate)] displayRefreshRate];
2251 [viewController createTouchRateCorrectionVSyncClientIfNeeded];
2255 - (void)testCreateTouchRateCorrectionVSyncClientWillNotCreateNewVSyncClientWhenClientAlreadyExists {
2257 double maxFrameRate = 120;
2258 [[[mockDisplayLinkManager stub] andReturnValue:@(maxFrameRate)] displayRefreshRate];
2265 [viewController createTouchRateCorrectionVSyncClientIfNeeded];
2267 XCTAssertNotNil(clientBefore);
2269 [viewController createTouchRateCorrectionVSyncClientIfNeeded];
2271 XCTAssertNotNil(clientAfter);
2273 XCTAssertTrue(clientBefore == clientAfter);
2276 - (void)testCreateTouchRateCorrectionVSyncClientWillNotCreateVsyncClientWhenRefreshRateIs60HZ {
2278 double maxFrameRate = 60;
2279 [[[mockDisplayLinkManager stub] andReturnValue:@(maxFrameRate)] displayRefreshRate];
2285 [viewController createTouchRateCorrectionVSyncClientIfNeeded];
2289 - (void)testTriggerTouchRateCorrectionVSyncClientCorrectly {
2291 double maxFrameRate = 120;
2292 [[[mockDisplayLinkManager stub] andReturnValue:@(maxFrameRate)] displayRefreshRate];
2298 [viewController loadView];
2299 [viewController viewDidLoad];
2302 CADisplayLink* link = [client getDisplayLink];
2304 UITouch* fakeTouchBegan = [[UITouch alloc] init];
2305 fakeTouchBegan.phase = UITouchPhaseBegan;
2307 UITouch* fakeTouchMove = [[UITouch alloc] init];
2308 fakeTouchMove.phase = UITouchPhaseMoved;
2310 UITouch* fakeTouchEnd = [[UITouch alloc] init];
2311 fakeTouchEnd.phase = UITouchPhaseEnded;
2313 UITouch* fakeTouchCancelled = [[UITouch alloc] init];
2314 fakeTouchCancelled.phase = UITouchPhaseCancelled;
2317 triggerTouchRateCorrectionIfNeeded:[[NSSet alloc] initWithObjects:fakeTouchBegan, nil]];
2318 XCTAssertFalse(link.isPaused);
2321 triggerTouchRateCorrectionIfNeeded:[[NSSet alloc] initWithObjects:fakeTouchEnd, nil]];
2322 XCTAssertTrue(link.isPaused);
2325 triggerTouchRateCorrectionIfNeeded:[[NSSet alloc] initWithObjects:fakeTouchMove, nil]];
2326 XCTAssertFalse(link.isPaused);
2329 triggerTouchRateCorrectionIfNeeded:[[NSSet alloc] initWithObjects:fakeTouchCancelled, nil]];
2330 XCTAssertTrue(link.isPaused);
2333 triggerTouchRateCorrectionIfNeeded:[[NSSet alloc]
2334 initWithObjects:fakeTouchBegan, fakeTouchEnd, nil]];
2335 XCTAssertFalse(link.isPaused);
2338 triggerTouchRateCorrectionIfNeeded:[[NSSet alloc] initWithObjects:fakeTouchEnd,
2339 fakeTouchCancelled, nil]];
2340 XCTAssertTrue(link.isPaused);
2343 triggerTouchRateCorrectionIfNeeded:[[NSSet alloc]
2344 initWithObjects:fakeTouchMove, fakeTouchEnd, nil]];
2345 XCTAssertFalse(link.isPaused);
2348 - (void)testFlutterViewControllerStartKeyboardAnimationWillCreateVsyncClientCorrectly {
2355 [viewController startKeyBoardAnimation:0.25];
2360 testSetupKeyboardAnimationVsyncClientWillNotCreateNewVsyncClientWhenKeyboardAnimationCallbackIsNil {
2366 [viewController setUpKeyboardAnimationVsyncClient:nil];
2370 - (void)testSupportsShowingSystemContextMenuForIOS16AndAbove {
2376 BOOL supportsShowingSystemContextMenu = [viewController supportsShowingSystemContextMenu];
2377 if (@available(iOS 16.0, *)) {
2378 XCTAssertTrue(supportsShowingSystemContextMenu);
2380 XCTAssertFalse(supportsShowingSystemContextMenu);
2384 - (void)testStateIsActiveAndBackgroundWhenApplicationStateIsActive {
2390 id mockApplication = OCMClassMock([UIApplication
class]);
2391 OCMStub([mockApplication applicationState]).andReturn(UIApplicationStateActive);
2392 OCMStub([mockApplication sharedApplication]).andReturn(mockApplication);
2397 - (void)testStateIsActiveAndBackgroundWhenApplicationStateIsBackground {
2403 id mockApplication = OCMClassMock([UIApplication
class]);
2404 OCMStub([mockApplication applicationState]).andReturn(UIApplicationStateBackground);
2405 OCMStub([mockApplication sharedApplication]).andReturn(mockApplication);
2410 - (void)testStateIsActiveAndBackgroundWhenApplicationStateIsInactive {
2416 id mockApplication = OCMClassMock([UIApplication
class]);
2417 OCMStub([mockApplication applicationState]).andReturn(UIApplicationStateInactive);
2418 OCMStub([mockApplication sharedApplication]).andReturn(mockApplication);
2423 - (void)testStateIsActiveAndBackgroundWhenSceneStateIsActive {
2424 id mockBundle = OCMPartialMock([NSBundle mainBundle]);
2425 OCMStub([mockBundle objectForInfoDictionaryKey:
@"NSExtension"]).andReturn(@{
2426 @"NSExtensionPointIdentifier" :
@"com.apple.share-services"
2434 OCMStub([mockVC activationState]).andReturn(UISceneActivationStateForegroundActive);
2438 [mockBundle stopMocking];
2439 [mockVC stopMocking];
2442 - (void)testStateIsActiveAndBackgroundWhenSceneStateIsBackground {
2443 id mockBundle = OCMPartialMock([NSBundle mainBundle]);
2444 OCMStub([mockBundle objectForInfoDictionaryKey:
@"NSExtension"]).andReturn(@{
2445 @"NSExtensionPointIdentifier" :
@"com.apple.share-services"
2453 OCMStub([mockVC activationState]).andReturn(UISceneActivationStateBackground);
2457 [mockBundle stopMocking];
2458 [mockVC stopMocking];
2461 - (void)testStateIsActiveAndBackgroundWhenSceneStateIsInactive {
2462 id mockBundle = OCMPartialMock([NSBundle mainBundle]);
2463 OCMStub([mockBundle objectForInfoDictionaryKey:
@"NSExtension"]).andReturn(@{
2464 @"NSExtensionPointIdentifier" :
@"com.apple.share-services"
2472 OCMStub([mockVC activationState]).andReturn(UISceneActivationStateForegroundInactive);
2476 [mockBundle stopMocking];
2477 [mockVC stopMocking];
2480 - (void)testAppDelegatePluginRegistrant {
2482 id appDelegate = [[UIApplication sharedApplication] delegate];
2483 XCTAssertTrue([appDelegate respondsToSelector:
@selector(setPluginRegistrant:)]);
2484 [appDelegate setPluginRegistrant:mockRegistrant];
2486 [appDelegate setPluginRegistrant:nil];
2490 - (void)testGrabLaunchEngine {
2491 id appDelegate = [[UIApplication sharedApplication] delegate];
2492 XCTAssertTrue([appDelegate respondsToSelector:
@selector(setMockLaunchEngine:)]);
2493 [appDelegate setMockLaunchEngine:self.mockEngine];
2494 UIStoryboard* storyboard = [UIStoryboard storyboardWithName:@"Flutter" bundle:nil];
2495 XCTAssertTrue(storyboard);
2501 [appDelegate setMockLaunchEngine:nil];
2504 - (void)testDoesntGrabLaunchEngine {
2505 id appDelegate = [[UIApplication sharedApplication] delegate];
2506 XCTAssertTrue([appDelegate respondsToSelector:
@selector(setMockLaunchEngine:)]);
2507 [appDelegate setMockLaunchEngine:self.mockEngine];
2509 XCTAssertNotNil(flutterViewController.
engine);
2510 XCTAssertNotEqual(flutterViewController.
engine,
self.mockEngine);
2511 [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()
double targetViewInsetBottom
UIView * keyboardAnimationView()
void hideKeyboardImmediately()
void invalidateKeyboardAnimationVSyncClient()
VSyncClient * keyboardAnimationVSyncClient
void createTouchRateCorrectionVSyncClientIfNeeded()
void ensureViewportMetricsIsCorrect()
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...