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"
24 #import "flutter/shell/platform/embedder/embedder.h"
25 #import "flutter/third_party/spring_animation/spring_animation.h"
39 @property(nonatomic, assign) BOOL didCallNotifyLowMemory;
43 - (void)sendKeyEvent:(const FlutterKeyEvent&)event
44 callback:(nullable FlutterKeyEventCallback)callback
45 userData:(nullable
void*)userData;
56 - (void)notifyLowMemory {
57 _didCallNotifyLowMemory = YES;
60 - (void)sendKeyEvent:(const FlutterKeyEvent&)event
61 callback:(FlutterKeyEventCallback)callback
62 userData:(
void*)userData API_AVAILABLE(ios(9.0)) {
63 if (callback == nil) {
69 CFRunLoopPerformBlock(CFRunLoopGetCurrent(), fml::MessageLoopDarwin::kMessageLoopCFRunLoopMode,
71 callback(
true, userData);
77 - (BOOL)createShell:(NSString*)entrypoint
78 libraryURI:(NSString*)libraryURI
79 initialRoute:(NSString*)initialRoute;
80 - (void)dispatchPointerDataPacket:(std::unique_ptr<
flutter::PointerDataPacket>)packet;
81 - (void)updateViewportMetrics:(
flutter::ViewportMetrics)viewportMetrics;
113 @property(nonatomic, retain, readonly)
130 - (void)surfaceUpdated:(BOOL)appeared;
131 - (void)performOrientationUpdate:(UIInterfaceOrientationMask)new_preferences;
133 nextAction:(
void (^)())next API_AVAILABLE(ios(13.4));
134 - (void)discreteScrollEvent:(UIPanGestureRecognizer*)recognizer;
136 - (void)onUserSettingsChanged:(NSNotification*)notification;
137 - (void)applicationWillTerminate:(NSNotification*)notification;
138 - (void)goToApplicationLifecycle:(nonnull NSString*)state;
139 - (void)handleKeyboardNotification:(NSNotification*)notification;
140 - (CGFloat)calculateKeyboardInset:(CGRect)keyboardFrame keyboardMode:(
int)keyboardMode;
141 - (BOOL)shouldIgnoreKeyboardNotification:(NSNotification*)notification;
142 - (FlutterKeyboardMode)calculateKeyboardAttachMode:(NSNotification*)notification;
143 - (CGFloat)calculateMultitaskingAdjustment:(CGRect)screenRect keyboardFrame:(CGRect)keyboardFrame;
144 - (void)startKeyBoardAnimation:(NSTimeInterval)duration;
148 - (void)setUpKeyboardSpringAnimationIfNeeded:(CAAnimation*)keyboardAnimation;
149 - (void)setUpKeyboardAnimationVsyncClient:
154 - (
flutter::PointerData)generatePointerDataForFake;
156 initialRoute:(nullable NSString*)initialRoute;
157 - (void)applicationBecameActive:(NSNotification*)notification;
158 - (void)applicationWillResignActive:(NSNotification*)notification;
159 - (void)applicationWillTerminate:(NSNotification*)notification;
160 - (void)applicationDidEnterBackground:(NSNotification*)notification;
161 - (void)applicationWillEnterForeground:(NSNotification*)notification;
162 - (void)sceneBecameActive:(NSNotification*)notification API_AVAILABLE(ios(13.0));
163 - (void)sceneWillResignActive:(NSNotification*)notification API_AVAILABLE(ios(13.0));
164 - (void)sceneWillDisconnect:(NSNotification*)notification API_AVAILABLE(ios(13.0));
165 - (void)sceneDidEnterBackground:(NSNotification*)notification API_AVAILABLE(ios(13.0));
166 - (void)sceneWillEnterForeground:(NSNotification*)notification API_AVAILABLE(ios(13.0));
167 - (void)triggerTouchRateCorrectionIfNeeded:(NSSet*)touches;
171 @property(nonatomic, strong)
id mockEngine;
172 @property(nonatomic, strong)
id mockTextInputPlugin;
173 @property(nonatomic, strong)
id messageSent;
174 - (void)sendMessage:(
id _Nullable)message reply:(
FlutterReply _Nullable)callback;
177 @interface UITouch ()
179 @property(nonatomic, readwrite) UITouchPhase phase;
195 self.messageSent = nil;
201 [
self.mockEngine stopMocking];
202 self.mockEngine = nil;
203 self.mockTextInputPlugin = nil;
204 self.messageSent = nil;
207 - (id)setUpMockScreen {
208 UIScreen* mockScreen = OCMClassMock([UIScreen
class]);
210 CGRect screenBounds = CGRectMake(0, 0, 1170, 2532);
211 OCMStub([mockScreen bounds]).andReturn(screenBounds);
212 CGFloat screenScale = 1;
213 OCMStub([mockScreen scale]).andReturn(screenScale);
219 screen:(UIScreen*)screen
220 viewFrame:(CGRect)viewFrame
221 convertedFrame:(CGRect)convertedFrame {
222 OCMStub([viewControllerMock flutterScreenIfViewLoaded]).andReturn(screen);
223 id mockView = OCMClassMock([UIView
class]);
224 OCMStub([mockView frame]).andReturn(viewFrame);
225 OCMStub([mockView convertRect:viewFrame toCoordinateSpace:[OCMArg any]])
226 .andReturn(convertedFrame);
227 OCMStub([viewControllerMock viewIfLoaded]).andReturn(mockView);
232 - (void)testViewDidLoadWillInvokeCreateTouchRateCorrectionVSyncClient {
239 [viewControllerMock loadView];
240 [viewControllerMock viewDidLoad];
241 OCMVerify([viewControllerMock createTouchRateCorrectionVSyncClientIfNeeded]);
244 - (void)testStartKeyboardAnimationWillInvokeSetupKeyboardSpringAnimationIfNeeded {
251 viewControllerMock.targetViewInsetBottom = 100;
252 [viewControllerMock startKeyBoardAnimation:0.25];
254 CAAnimation* keyboardAnimation =
255 [[viewControllerMock keyboardAnimationView].layer animationForKey:@"position"];
257 OCMVerify([viewControllerMock setUpKeyboardSpringAnimationIfNeeded:keyboardAnimation]);
260 - (void)testSetupKeyboardSpringAnimationIfNeeded {
267 UIScreen* screen = [
self setUpMockScreen];
268 CGRect viewFrame = screen.bounds;
269 [
self setUpMockView:viewControllerMock
272 convertedFrame:viewFrame];
275 [viewControllerMock setUpKeyboardSpringAnimationIfNeeded:nil];
276 SpringAnimation* keyboardSpringAnimation = [viewControllerMock keyboardSpringAnimation];
277 XCTAssertTrue(keyboardSpringAnimation == nil);
280 CABasicAnimation* nonSpringAnimation = [CABasicAnimation animation];
281 nonSpringAnimation.duration = 1.0;
282 nonSpringAnimation.fromValue = [NSNumber numberWithFloat:0.0];
283 nonSpringAnimation.toValue = [NSNumber numberWithFloat:1.0];
284 nonSpringAnimation.keyPath =
@"position";
285 [viewControllerMock setUpKeyboardSpringAnimationIfNeeded:nonSpringAnimation];
286 keyboardSpringAnimation = [viewControllerMock keyboardSpringAnimation];
288 XCTAssertTrue(keyboardSpringAnimation == nil);
291 CASpringAnimation* springAnimation = [CASpringAnimation animation];
292 springAnimation.mass = 1.0;
293 springAnimation.stiffness = 100.0;
294 springAnimation.damping = 10.0;
295 springAnimation.keyPath =
@"position";
296 springAnimation.fromValue = [NSValue valueWithCGPoint:CGPointMake(0, 0)];
297 springAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(100, 100)];
298 [viewControllerMock setUpKeyboardSpringAnimationIfNeeded:springAnimation];
299 keyboardSpringAnimation = [viewControllerMock keyboardSpringAnimation];
300 XCTAssertTrue(keyboardSpringAnimation != nil);
303 - (void)testKeyboardAnimationIsShowingAndCompounding {
310 UIScreen* screen = [
self setUpMockScreen];
311 CGRect viewFrame = screen.bounds;
312 [
self setUpMockView:viewControllerMock
315 convertedFrame:viewFrame];
318 CGFloat screenHeight = screen.bounds.size.height;
319 CGFloat screenWidth = screen.bounds.size.height;
322 CGRect initialShowKeyboardBeginFrame = CGRectMake(0, screenHeight, screenWidth, 250);
323 CGRect initialShowKeyboardEndFrame = CGRectMake(0, screenHeight - 250, screenWidth, 500);
324 NSNotification* fakeNotification = [NSNotification
325 notificationWithName:UIKeyboardWillChangeFrameNotification
328 @"UIKeyboardFrameBeginUserInfoKey" : @(initialShowKeyboardBeginFrame),
329 @"UIKeyboardFrameEndUserInfoKey" : @(initialShowKeyboardEndFrame),
330 @"UIKeyboardAnimationDurationUserInfoKey" : @(0.25),
331 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
333 viewControllerMock.targetViewInsetBottom = 0;
334 [viewControllerMock handleKeyboardNotification:fakeNotification];
335 BOOL isShowingAnimation1 = viewControllerMock.keyboardAnimationIsShowing;
336 XCTAssertTrue(isShowingAnimation1);
339 CGRect compoundingShowKeyboardBeginFrame = CGRectMake(0, screenHeight - 250, screenWidth, 250);
340 CGRect compoundingShowKeyboardEndFrame = CGRectMake(0, screenHeight - 500, screenWidth, 500);
341 fakeNotification = [NSNotification
342 notificationWithName:UIKeyboardWillChangeFrameNotification
345 @"UIKeyboardFrameBeginUserInfoKey" : @(compoundingShowKeyboardBeginFrame),
346 @"UIKeyboardFrameEndUserInfoKey" : @(compoundingShowKeyboardEndFrame),
347 @"UIKeyboardAnimationDurationUserInfoKey" : @(0.25),
348 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
351 [viewControllerMock handleKeyboardNotification:fakeNotification];
352 BOOL isShowingAnimation2 = viewControllerMock.keyboardAnimationIsShowing;
353 XCTAssertTrue(isShowingAnimation2);
354 XCTAssertTrue(isShowingAnimation1 == isShowingAnimation2);
357 CGRect initialHideKeyboardBeginFrame = CGRectMake(0, screenHeight - 500, screenWidth, 250);
358 CGRect initialHideKeyboardEndFrame = CGRectMake(0, screenHeight - 250, screenWidth, 500);
359 fakeNotification = [NSNotification
360 notificationWithName:UIKeyboardWillChangeFrameNotification
363 @"UIKeyboardFrameBeginUserInfoKey" : @(initialHideKeyboardBeginFrame),
364 @"UIKeyboardFrameEndUserInfoKey" : @(initialHideKeyboardEndFrame),
365 @"UIKeyboardAnimationDurationUserInfoKey" : @(0.25),
366 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
369 [viewControllerMock handleKeyboardNotification:fakeNotification];
370 BOOL isShowingAnimation3 = viewControllerMock.keyboardAnimationIsShowing;
371 XCTAssertFalse(isShowingAnimation3);
372 XCTAssertTrue(isShowingAnimation2 != isShowingAnimation3);
375 CGRect compoundingHideKeyboardBeginFrame = CGRectMake(0, screenHeight - 250, screenWidth, 250);
376 CGRect compoundingHideKeyboardEndFrame = CGRectMake(0, screenHeight, screenWidth, 500);
377 fakeNotification = [NSNotification
378 notificationWithName:UIKeyboardWillChangeFrameNotification
381 @"UIKeyboardFrameBeginUserInfoKey" : @(compoundingHideKeyboardBeginFrame),
382 @"UIKeyboardFrameEndUserInfoKey" : @(compoundingHideKeyboardEndFrame),
383 @"UIKeyboardAnimationDurationUserInfoKey" : @(0.25),
384 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
387 [viewControllerMock handleKeyboardNotification:fakeNotification];
388 BOOL isShowingAnimation4 = viewControllerMock.keyboardAnimationIsShowing;
389 XCTAssertFalse(isShowingAnimation4);
390 XCTAssertTrue(isShowingAnimation3 == isShowingAnimation4);
393 - (void)testShouldIgnoreKeyboardNotification {
395 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
400 UIScreen* screen = [
self setUpMockScreen];
401 CGRect viewFrame = screen.bounds;
402 [
self setUpMockView:viewControllerMock
405 convertedFrame:viewFrame];
407 CGFloat screenWidth = screen.bounds.size.width;
408 CGFloat screenHeight = screen.bounds.size.height;
409 CGRect emptyKeyboard = CGRectZero;
410 CGRect zeroHeightKeyboard = CGRectMake(0, 0, screenWidth, 0);
411 CGRect validKeyboardEndFrame = CGRectMake(0, screenHeight - 320, screenWidth, 320);
415 NSNotification* notification =
416 [NSNotification notificationWithName:UIKeyboardWillHideNotification
419 @"UIKeyboardFrameEndUserInfoKey" : @(validKeyboardEndFrame),
420 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
421 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
424 BOOL shouldIgnore = [viewControllerMock shouldIgnoreKeyboardNotification:notification];
425 XCTAssertTrue(shouldIgnore == NO);
429 notification = [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
432 @"UIKeyboardFrameEndUserInfoKey" : @(emptyKeyboard),
433 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
434 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
436 shouldIgnore = [viewControllerMock shouldIgnoreKeyboardNotification:notification];
437 XCTAssertTrue(shouldIgnore == YES);
442 [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
445 @"UIKeyboardFrameEndUserInfoKey" : @(zeroHeightKeyboard),
446 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
447 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
449 shouldIgnore = [viewControllerMock shouldIgnoreKeyboardNotification:notification];
450 XCTAssertTrue(shouldIgnore == NO);
455 [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
458 @"UIKeyboardFrameEndUserInfoKey" : @(validKeyboardEndFrame),
459 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
460 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
462 shouldIgnore = [viewControllerMock shouldIgnoreKeyboardNotification:notification];
463 XCTAssertTrue(shouldIgnore == YES);
468 [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
471 @"UIKeyboardFrameEndUserInfoKey" : @(validKeyboardEndFrame),
472 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
473 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
475 shouldIgnore = [viewControllerMock shouldIgnoreKeyboardNotification:notification];
476 XCTAssertTrue(shouldIgnore == NO);
478 if (@available(iOS 13.0, *)) {
482 OCMStub([viewControllerMock isKeyboardInOrTransitioningFromBackground]).andReturn(YES);
486 [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
489 @"UIKeyboardFrameEndUserInfoKey" : @(validKeyboardEndFrame),
490 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
491 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
493 shouldIgnore = [viewControllerMock shouldIgnoreKeyboardNotification:notification];
494 XCTAssertTrue(shouldIgnore == YES);
497 - (void)testKeyboardAnimationWillNotCrashWhenEngineDestroyed {
503 [viewController setUpKeyboardAnimationVsyncClient:^(fml::TimePoint){
508 - (void)testKeyboardAnimationWillWaitUIThreadVsync {
521 const int delayTime = 1;
522 [engine uiTaskRunner]->PostTask([] { sleep(delayTime); });
523 XCTestExpectation* expectation = [
self expectationWithDescription:@"keyboard animation callback"];
525 __block CFTimeInterval fulfillTime;
527 fulfillTime = CACurrentMediaTime();
528 [expectation fulfill];
530 CFTimeInterval startTime = CACurrentMediaTime();
531 [viewController setUpKeyboardAnimationVsyncClient:callback];
532 [
self waitForExpectationsWithTimeout:5.0 handler:nil];
533 XCTAssertTrue(fulfillTime - startTime > delayTime);
536 - (void)testCalculateKeyboardAttachMode {
538 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
544 UIScreen* screen = [
self setUpMockScreen];
545 CGRect viewFrame = screen.bounds;
546 [
self setUpMockView:viewControllerMock
549 convertedFrame:viewFrame];
551 CGFloat screenWidth = screen.bounds.size.width;
552 CGFloat screenHeight = screen.bounds.size.height;
555 CGRect keyboardFrame = CGRectZero;
556 NSNotification* notification =
557 [NSNotification notificationWithName:UIKeyboardWillHideNotification
560 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
561 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
562 @"UIKeyboardIsLocalUserInfoKey" : @(YES)
564 FlutterKeyboardMode keyboardMode = [viewControllerMock calculateKeyboardAttachMode:notification];
565 XCTAssertTrue(keyboardMode == FlutterKeyboardModeHidden);
568 keyboardFrame = CGRectZero;
569 notification = [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
572 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
573 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
574 @"UIKeyboardIsLocalUserInfoKey" : @(YES)
576 keyboardMode = [viewControllerMock calculateKeyboardAttachMode:notification];
577 XCTAssertTrue(keyboardMode == FlutterKeyboardModeFloating);
580 keyboardFrame = CGRectMake(0, 0, screenWidth, 0);
581 notification = [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
584 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
585 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
586 @"UIKeyboardIsLocalUserInfoKey" : @(YES)
588 keyboardMode = [viewControllerMock calculateKeyboardAttachMode:notification];
589 XCTAssertTrue(keyboardMode == FlutterKeyboardModeHidden);
592 keyboardFrame = CGRectMake(0, 0, 320, 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, 0, 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 == FlutterKeyboardModeFloating);
616 keyboardFrame = CGRectMake(0, screenHeight - 320, screenWidth, 320);
617 notification = [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
620 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
621 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
622 @"UIKeyboardIsLocalUserInfoKey" : @(YES)
624 keyboardMode = [viewControllerMock calculateKeyboardAttachMode:notification];
625 XCTAssertTrue(keyboardMode == FlutterKeyboardModeDocked);
628 CGFloat longDecimalHeight = 320.666666666666666;
629 keyboardFrame = CGRectMake(0, screenHeight - longDecimalHeight, 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 == FlutterKeyboardModeDocked);
641 keyboardFrame = CGRectMake(0, screenHeight - .0000001, screenWidth, longDecimalHeight);
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 keyboardFrame = CGRectMake(0, screenHeight, screenWidth, 320);
654 notification = [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
657 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
658 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
659 @"UIKeyboardIsLocalUserInfoKey" : @(YES)
661 keyboardMode = [viewControllerMock calculateKeyboardAttachMode:notification];
662 XCTAssertTrue(keyboardMode == FlutterKeyboardModeHidden);
665 - (void)testCalculateMultitaskingAdjustment {
667 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
673 UIScreen* screen = [
self setUpMockScreen];
674 CGFloat screenWidth = screen.bounds.size.width;
675 CGFloat screenHeight = screen.bounds.size.height;
676 CGRect screenRect = screen.bounds;
677 CGRect viewOrigFrame = CGRectMake(0, 0, 320, screenHeight - 40);
678 CGRect convertedViewFrame = CGRectMake(20, 20, 320, screenHeight - 40);
679 CGRect keyboardFrame = CGRectMake(20, screenHeight - 320, screenWidth, 300);
680 id mockView = [
self setUpMockView:viewControllerMock
682 viewFrame:viewOrigFrame
683 convertedFrame:convertedViewFrame];
684 id mockTraitCollection = OCMClassMock([UITraitCollection
class]);
685 OCMStub([mockTraitCollection userInterfaceIdiom]).andReturn(UIUserInterfaceIdiomPad);
686 OCMStub([mockTraitCollection horizontalSizeClass]).andReturn(UIUserInterfaceSizeClassCompact);
687 OCMStub([mockTraitCollection verticalSizeClass]).andReturn(UIUserInterfaceSizeClassRegular);
688 OCMStub([mockView traitCollection]).andReturn(mockTraitCollection);
690 CGFloat adjustment = [viewControllerMock calculateMultitaskingAdjustment:screenRect
691 keyboardFrame:keyboardFrame];
692 XCTAssertTrue(adjustment == 20);
695 - (void)testCalculateKeyboardInset {
697 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
702 UIScreen* screen = [
self setUpMockScreen];
703 OCMStub([viewControllerMock flutterScreenIfViewLoaded]).andReturn(screen);
705 CGFloat screenWidth = screen.bounds.size.width;
706 CGFloat screenHeight = screen.bounds.size.height;
707 CGRect viewOrigFrame = CGRectMake(0, 0, 320, screenHeight - 40);
708 CGRect convertedViewFrame = CGRectMake(20, 20, 320, screenHeight - 40);
709 CGRect keyboardFrame = CGRectMake(20, screenHeight - 320, screenWidth, 300);
711 [
self setUpMockView:viewControllerMock
713 viewFrame:viewOrigFrame
714 convertedFrame:convertedViewFrame];
716 CGFloat inset = [viewControllerMock calculateKeyboardInset:keyboardFrame
717 keyboardMode:FlutterKeyboardModeDocked];
718 XCTAssertTrue(inset == 300 * screen.scale);
721 - (void)testHandleKeyboardNotification {
728 UIScreen* screen = [
self setUpMockScreen];
729 CGFloat screenWidth = screen.bounds.size.width;
730 CGFloat screenHeight = screen.bounds.size.height;
731 CGRect keyboardFrame = CGRectMake(0, screenHeight - 320, screenWidth, 320);
732 CGRect viewFrame = screen.bounds;
734 NSNotification* notification =
735 [NSNotification notificationWithName:UIKeyboardWillShowNotification
738 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
739 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
740 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
743 [
self setUpMockView:viewControllerMock
746 convertedFrame:viewFrame];
747 viewControllerMock.targetViewInsetBottom = 0;
748 XCTestExpectation* expectation = [
self expectationWithDescription:@"update viewport"];
749 OCMStub([viewControllerMock updateViewportMetricsIfNeeded]).andDo(^(NSInvocation* invocation) {
750 [expectation fulfill];
753 [viewControllerMock handleKeyboardNotification:notification];
754 XCTAssertTrue(viewControllerMock.targetViewInsetBottom == 320 * screen.scale);
755 OCMVerify([viewControllerMock startKeyBoardAnimation:0.25]);
756 [
self waitForExpectationsWithTimeout:5.0 handler:nil];
759 - (void)testEnsureBottomInsetIsZeroWhenKeyboardDismissed {
761 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
767 CGRect keyboardFrame = CGRectZero;
769 NSNotification* fakeNotification =
770 [NSNotification notificationWithName:UIKeyboardWillHideNotification
773 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
774 @"UIKeyboardAnimationDurationUserInfoKey" : @(0.25),
775 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
778 viewControllerMock.targetViewInsetBottom = 10;
779 [viewControllerMock handleKeyboardNotification:fakeNotification];
780 XCTAssertTrue(viewControllerMock.targetViewInsetBottom == 0);
783 - (void)testStopKeyBoardAnimationWhenReceivedWillHideNotificationAfterWillShowNotification {
792 UIScreen* screen = [
self setUpMockScreen];
793 CGRect viewFrame = screen.bounds;
794 [
self setUpMockView:viewControllerMock
797 convertedFrame:viewFrame];
798 viewControllerMock.targetViewInsetBottom = 0;
800 CGFloat screenHeight = screen.bounds.size.height;
801 CGFloat screenWidth = screen.bounds.size.height;
802 CGRect keyboardFrame = CGRectMake(0, screenHeight - 320, screenWidth, 320);
806 NSNotification* fakeShowNotification =
807 [NSNotification notificationWithName:UIKeyboardWillShowNotification
810 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
811 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
812 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
814 [viewControllerMock handleKeyboardNotification:fakeShowNotification];
815 XCTAssertTrue(viewControllerMock.targetViewInsetBottom == 320 * screen.scale);
818 NSNotification* fakeHideNotification =
819 [NSNotification notificationWithName:UIKeyboardWillHideNotification
822 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
823 @"UIKeyboardAnimationDurationUserInfoKey" : @(0.0),
824 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
826 [viewControllerMock handleKeyboardNotification:fakeHideNotification];
827 XCTAssertTrue(viewControllerMock.targetViewInsetBottom == 0);
830 XCTAssertNil(viewControllerMock.keyboardAnimationView);
831 XCTAssertNil(viewControllerMock.keyboardSpringAnimation);
834 - (void)testEnsureViewportMetricsWillInvokeAndDisplayLinkWillInvalidateInViewDidDisappear {
836 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
841 [viewControllerMock viewDidDisappear:YES];
842 OCMVerify([viewControllerMock ensureViewportMetricsIsCorrect]);
843 OCMVerify([viewControllerMock invalidateKeyboardAnimationVSyncClient]);
846 - (void)testViewDidDisappearDoesntPauseEngineWhenNotTheViewController {
849 mockEngine.lifecycleChannel = lifecycleChannel;
854 id viewControllerMock = OCMPartialMock(viewControllerA);
855 OCMStub([viewControllerMock surfaceUpdated:NO]);
857 [viewControllerA viewDidDisappear:NO];
858 OCMReject([lifecycleChannel sendMessage:
@"AppLifecycleState.paused"]);
859 OCMReject([viewControllerMock surfaceUpdated:[OCMArg any]]);
862 - (void)testAppWillTerminateViewDidDestroyTheEngine {
864 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
869 OCMStub([viewControllerMock goToApplicationLifecycle:
@"AppLifecycleState.detached"]);
871 [viewController applicationWillTerminate:nil];
872 OCMVerify([viewControllerMock goToApplicationLifecycle:
@"AppLifecycleState.detached"]);
876 - (void)testViewDidDisappearDoesPauseEngineWhenIsTheViewController {
879 mockEngine.lifecycleChannel = lifecycleChannel;
887 OCMStub([viewControllerMock surfaceUpdated:NO]);
888 [viewController viewDidDisappear:NO];
889 OCMVerify([lifecycleChannel sendMessage:
@"AppLifecycleState.paused"]);
890 OCMVerify([viewControllerMock surfaceUpdated:NO]);
892 XCTAssertNil(weakViewController);
896 testEngineConfigSyncMethodWillExecuteWhenViewControllerInEngineIsCurrentViewControllerInViewWillAppear {
898 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
902 [viewController viewWillAppear:YES];
907 testEngineConfigSyncMethodWillNotExecuteWhenViewControllerInEngineIsNotCurrentViewControllerInViewWillAppear {
909 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
919 [viewControllerA viewWillAppear:YES];
920 OCMVerify(never(), [viewControllerA onUserSettingsChanged:nil]);
924 testEngineConfigSyncMethodWillExecuteWhenViewControllerInEngineIsCurrentViewControllerInViewDidAppear {
926 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
930 [viewController viewDidAppear:YES];
935 testEngineConfigSyncMethodWillNotExecuteWhenViewControllerInEngineIsNotCurrentViewControllerInViewDidAppear {
937 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
947 [viewControllerA viewDidAppear:YES];
948 OCMVerify(never(), [viewControllerA onUserSettingsChanged:nil]);
952 testEngineConfigSyncMethodWillExecuteWhenViewControllerInEngineIsCurrentViewControllerInViewWillDisappear {
955 mockEngine.lifecycleChannel = lifecycleChannel;
960 [viewController viewWillDisappear:NO];
961 OCMVerify([lifecycleChannel sendMessage:
@"AppLifecycleState.inactive"]);
965 testEngineConfigSyncMethodWillNotExecuteWhenViewControllerInEngineIsNotCurrentViewControllerInViewWillDisappear {
968 mockEngine.lifecycleChannel = lifecycleChannel;
976 [viewControllerA viewDidDisappear:NO];
977 OCMReject([lifecycleChannel sendMessage:
@"AppLifecycleState.inactive"]);
980 - (void)testUpdateViewportMetricsIfNeeded_DoesntInvokeEngineWhenNotTheViewController {
982 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
991 [viewControllerA updateViewportMetricsIfNeeded];
992 flutter::ViewportMetrics viewportMetrics;
993 OCMVerify(never(), [
mockEngine updateViewportMetrics:viewportMetrics]);
996 - (void)testUpdateViewportMetricsIfNeeded_DoesInvokeEngineWhenIsTheViewController {
998 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1003 flutter::ViewportMetrics viewportMetrics;
1004 OCMExpect([
mockEngine updateViewportMetrics:viewportMetrics]).ignoringNonObjectArgs();
1005 [viewController updateViewportMetricsIfNeeded];
1009 - (void)testUpdateViewportMetricsIfNeeded_DoesNotInvokeEngineWhenShouldBeIgnoredDuringRotation {
1011 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1016 UIScreen* screen = [
self setUpMockScreen];
1017 OCMStub([viewControllerMock flutterScreenIfViewLoaded]).andReturn(screen);
1020 id mockCoordinator = OCMProtocolMock(
@protocol(UIViewControllerTransitionCoordinator));
1021 OCMStub([mockCoordinator transitionDuration]).andReturn(0.5);
1024 [viewController viewWillTransitionToSize:CGSizeZero withTransitionCoordinator:mockCoordinator];
1026 [viewController updateViewportMetricsIfNeeded];
1028 OCMVerify(never(), [
mockEngine updateViewportMetrics:flutter::ViewportMetrics()]);
1031 - (void)testViewWillTransitionToSize_DoesDelayEngineCallIfNonZeroDuration {
1033 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1038 UIScreen* screen = [
self setUpMockScreen];
1039 OCMStub([viewControllerMock flutterScreenIfViewLoaded]).andReturn(screen);
1043 NSTimeInterval transitionDuration = 0.5;
1044 id mockCoordinator = OCMProtocolMock(
@protocol(UIViewControllerTransitionCoordinator));
1045 OCMStub([mockCoordinator transitionDuration]).andReturn(transitionDuration);
1047 flutter::ViewportMetrics viewportMetrics;
1048 OCMExpect([
mockEngine updateViewportMetrics:viewportMetrics]).ignoringNonObjectArgs();
1050 [viewController viewWillTransitionToSize:CGSizeZero withTransitionCoordinator:mockCoordinator];
1052 [viewController updateViewportMetricsIfNeeded];
1053 OCMVerify(never(), [
mockEngine updateViewportMetrics:flutter::ViewportMetrics()]);
1057 XCTWaiterResult result = [XCTWaiter
1058 waitForExpectations:@[ [
self expectationWithDescription:@"Waiting for rotation duration"] ]
1059 timeout:transitionDuration];
1060 XCTAssertEqual(result, XCTWaiterResultTimedOut);
1065 - (void)testViewWillTransitionToSize_DoesNotDelayEngineCallIfZeroDuration {
1067 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1072 UIScreen* screen = [
self setUpMockScreen];
1073 OCMStub([viewControllerMock flutterScreenIfViewLoaded]).andReturn(screen);
1077 id mockCoordinator = OCMProtocolMock(
@protocol(UIViewControllerTransitionCoordinator));
1078 OCMStub([mockCoordinator transitionDuration]).andReturn(0);
1080 flutter::ViewportMetrics viewportMetrics;
1081 OCMExpect([
mockEngine updateViewportMetrics:viewportMetrics]).ignoringNonObjectArgs();
1084 [viewController viewWillTransitionToSize:CGSizeZero withTransitionCoordinator:mockCoordinator];
1085 [viewController updateViewportMetricsIfNeeded];
1090 - (void)testViewDidLoadDoesntInvokeEngineWhenNotTheViewController {
1092 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1101 UIView* view = viewControllerA.view;
1102 XCTAssertNotNil(view);
1106 - (void)testViewDidLoadDoesInvokeEngineWhenIsTheViewController {
1108 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1115 XCTAssertNotNil(view);
1116 OCMVerify(times(1), [
mockEngine attachView]);
1119 - (void)testViewDidLoadDoesntInvokeEngineAttachViewWhenEngineNeedsLaunch {
1121 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1127 [viewController sharedSetupWithProject:nil initialRoute:nil];
1130 XCTAssertNotNil(view);
1134 - (void)testSplashScreenViewRemoveNotCrash {
1139 [flutterViewController setSplashScreenView:[[UIView alloc] init]];
1140 [flutterViewController setSplashScreenView:nil];
1143 - (void)testInternalPluginsWeakPtrNotCrash {
1149 [vc addInternalPlugins];
1152 [(NSArray<id<FlutterKeyPrimaryResponder>>*)keyboardManager.primaryResponders firstObject];
1153 sendEvent = [keyPrimaryResponder sendEvent];
1157 sendEvent({}, nil, nil);
1162 - (void)testInternalPluginsInvokeInViewDidLoad {
1164 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1172 XCTAssertNotNil(view);
1173 [viewController viewDidLoad];
1177 - (void)testBinaryMessenger {
1181 XCTAssertNotNil(vc);
1183 OCMStub([
self.
mockEngine binaryMessenger]).andReturn(messenger);
1185 OCMVerify([
self.
mockEngine binaryMessenger]);
1188 - (void)testViewControllerIsReleased {
1190 __weak UIView* weakView;
1199 [viewController loadView];
1200 [viewController viewDidLoad];
1204 XCTAssertNil(weakViewController);
1205 XCTAssertNil(weakView);
1208 #pragma mark - Platform Brightness
1210 - (void)testItReportsLightPlatformBrightnessByDefault {
1213 OCMStub([
self.
mockEngine settingsChannel]).andReturn(settingsChannel);
1220 [vc traitCollectionDidChange:nil];
1223 OCMVerify([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(
id message) {
1224 return [message[@"platformBrightness"] isEqualToString:@"light"];
1228 [settingsChannel stopMocking];
1231 - (void)testItReportsPlatformBrightnessWhenViewWillAppear {
1235 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1236 OCMStub([
mockEngine settingsChannel]).andReturn(settingsChannel);
1242 [vc viewWillAppear:false];
1245 OCMVerify([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(
id message) {
1246 return [message[@"platformBrightness"] isEqualToString:@"light"];
1250 [settingsChannel stopMocking];
1253 - (void)testItReportsDarkPlatformBrightnessWhenTraitCollectionRequestsIt {
1254 if (@available(iOS 13, *)) {
1262 OCMStub([
self.
mockEngine settingsChannel]).andReturn(settingsChannel);
1263 id mockTraitCollection =
1264 [
self fakeTraitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleDark];
1273 OCMStub([partialMockVC traitCollection]).andReturn(mockTraitCollection);
1276 [partialMockVC traitCollectionDidChange:nil];
1279 OCMVerify([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(
id message) {
1280 return [message[@"platformBrightness"] isEqualToString:@"dark"];
1284 [partialMockVC stopMocking];
1285 [settingsChannel stopMocking];
1286 [mockTraitCollection stopMocking];
1291 - (UITraitCollection*)fakeTraitCollectionWithUserInterfaceStyle:(UIUserInterfaceStyle)style {
1292 id mockTraitCollection = OCMClassMock([UITraitCollection
class]);
1293 OCMStub([mockTraitCollection userInterfaceStyle]).andReturn(style);
1294 return mockTraitCollection;
1297 #pragma mark - Platform Contrast
1299 - (void)testItReportsNormalPlatformContrastByDefault {
1300 if (@available(iOS 13, *)) {
1308 OCMStub([
self.
mockEngine settingsChannel]).andReturn(settingsChannel);
1315 [vc traitCollectionDidChange:nil];
1318 OCMVerify([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(
id message) {
1319 return [message[@"platformContrast"] isEqualToString:@"normal"];
1323 [settingsChannel stopMocking];
1326 - (void)testItReportsPlatformContrastWhenViewWillAppear {
1327 if (@available(iOS 13, *)) {
1333 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1337 OCMStub([
mockEngine settingsChannel]).andReturn(settingsChannel);
1343 [vc viewWillAppear:false];
1346 OCMVerify([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(
id message) {
1347 return [message[@"platformContrast"] isEqualToString:@"normal"];
1351 [settingsChannel stopMocking];
1354 - (void)testItReportsHighContrastWhenTraitCollectionRequestsIt {
1355 if (@available(iOS 13, *)) {
1363 OCMStub([
self.
mockEngine settingsChannel]).andReturn(settingsChannel);
1365 id mockTraitCollection = [
self fakeTraitCollectionWithContrast:UIAccessibilityContrastHigh];
1374 OCMStub([partialMockVC traitCollection]).andReturn(mockTraitCollection);
1377 [partialMockVC traitCollectionDidChange:mockTraitCollection];
1380 OCMVerify([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(
id message) {
1381 return [message[@"platformContrast"] isEqualToString:@"high"];
1385 [partialMockVC stopMocking];
1386 [settingsChannel stopMocking];
1387 [mockTraitCollection stopMocking];
1390 - (void)testItReportsAlwaysUsed24HourFormat {
1393 OCMStub([
self.
mockEngine settingsChannel]).andReturn(settingsChannel);
1399 OCMStub([mockHourFormat isAlwaysUse24HourFormat]).andReturn(YES);
1400 OCMExpect([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(
id message) {
1401 return [message[@"alwaysUse24HourFormat"] isEqual:@(YES)];
1403 [vc onUserSettingsChanged:nil];
1404 [mockHourFormat stopMocking];
1408 OCMStub([mockHourFormat isAlwaysUse24HourFormat]).andReturn(NO);
1409 OCMExpect([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(
id message) {
1410 return [message[@"alwaysUse24HourFormat"] isEqual:@(NO)];
1412 [vc onUserSettingsChanged:nil];
1413 [mockHourFormat stopMocking];
1416 [settingsChannel stopMocking];
1419 - (void)testItReportsAccessibilityOnOffSwitchLabelsFlagNotSet {
1420 if (@available(iOS 13, *)) {
1430 OCMStub([partialMockViewController accessibilityIsOnOffSwitchLabelsEnabled]).andReturn(NO);
1433 int32_t flags = [partialMockViewController accessibilityFlags];
1436 XCTAssert((flags & (int32_t)flutter::AccessibilityFeatureFlag::kOnOffSwitchLabels) == 0);
1439 - (void)testItReportsAccessibilityOnOffSwitchLabelsFlagSet {
1440 if (@available(iOS 13, *)) {
1450 OCMStub([partialMockViewController accessibilityIsOnOffSwitchLabelsEnabled]).andReturn(YES);
1453 int32_t flags = [partialMockViewController accessibilityFlags];
1456 XCTAssert((flags & (int32_t)flutter::AccessibilityFeatureFlag::kOnOffSwitchLabels) != 0);
1459 - (void)testAccessibilityPerformEscapePopsRoute {
1461 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1463 OCMStub([
mockEngine navigationChannel]).andReturn(mockNavigationChannel);
1470 OCMVerify([mockNavigationChannel invokeMethod:
@"popRoute" arguments:nil]);
1472 [mockNavigationChannel stopMocking];
1475 - (void)testPerformOrientationUpdateForcesOrientationChange {
1476 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortrait
1477 currentOrientation:UIInterfaceOrientationLandscapeLeft
1478 didChangeOrientation:YES
1479 resultingOrientation:UIInterfaceOrientationPortrait];
1481 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortrait
1482 currentOrientation:UIInterfaceOrientationLandscapeRight
1483 didChangeOrientation:YES
1484 resultingOrientation:UIInterfaceOrientationPortrait];
1486 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortrait
1487 currentOrientation:UIInterfaceOrientationPortraitUpsideDown
1488 didChangeOrientation:YES
1489 resultingOrientation:UIInterfaceOrientationPortrait];
1491 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortraitUpsideDown
1492 currentOrientation:UIInterfaceOrientationLandscapeLeft
1493 didChangeOrientation:YES
1494 resultingOrientation:UIInterfaceOrientationPortraitUpsideDown];
1496 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortraitUpsideDown
1497 currentOrientation:UIInterfaceOrientationLandscapeRight
1498 didChangeOrientation:YES
1499 resultingOrientation:UIInterfaceOrientationPortraitUpsideDown];
1501 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortraitUpsideDown
1502 currentOrientation:UIInterfaceOrientationPortrait
1503 didChangeOrientation:YES
1504 resultingOrientation:UIInterfaceOrientationPortraitUpsideDown];
1506 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscape
1507 currentOrientation:UIInterfaceOrientationPortrait
1508 didChangeOrientation:YES
1509 resultingOrientation:UIInterfaceOrientationLandscapeLeft];
1511 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscape
1512 currentOrientation:UIInterfaceOrientationPortraitUpsideDown
1513 didChangeOrientation:YES
1514 resultingOrientation:UIInterfaceOrientationLandscapeLeft];
1516 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeLeft
1517 currentOrientation:UIInterfaceOrientationPortrait
1518 didChangeOrientation:YES
1519 resultingOrientation:UIInterfaceOrientationLandscapeLeft];
1521 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeLeft
1522 currentOrientation:UIInterfaceOrientationLandscapeRight
1523 didChangeOrientation:YES
1524 resultingOrientation:UIInterfaceOrientationLandscapeLeft];
1526 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeLeft
1527 currentOrientation:UIInterfaceOrientationPortraitUpsideDown
1528 didChangeOrientation:YES
1529 resultingOrientation:UIInterfaceOrientationLandscapeLeft];
1531 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeRight
1532 currentOrientation:UIInterfaceOrientationPortrait
1533 didChangeOrientation:YES
1534 resultingOrientation:UIInterfaceOrientationLandscapeRight];
1536 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeRight
1537 currentOrientation:UIInterfaceOrientationLandscapeLeft
1538 didChangeOrientation:YES
1539 resultingOrientation:UIInterfaceOrientationLandscapeRight];
1541 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeRight
1542 currentOrientation:UIInterfaceOrientationPortraitUpsideDown
1543 didChangeOrientation:YES
1544 resultingOrientation:UIInterfaceOrientationLandscapeRight];
1546 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAllButUpsideDown
1547 currentOrientation:UIInterfaceOrientationPortraitUpsideDown
1548 didChangeOrientation:YES
1549 resultingOrientation:UIInterfaceOrientationPortrait];
1552 - (void)testPerformOrientationUpdateDoesNotForceOrientationChange {
1553 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAll
1554 currentOrientation:UIInterfaceOrientationPortrait
1555 didChangeOrientation:NO
1556 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1558 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAll
1559 currentOrientation:UIInterfaceOrientationPortraitUpsideDown
1560 didChangeOrientation:NO
1561 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1563 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAll
1564 currentOrientation:UIInterfaceOrientationLandscapeLeft
1565 didChangeOrientation:NO
1566 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1568 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAll
1569 currentOrientation:UIInterfaceOrientationLandscapeRight
1570 didChangeOrientation:NO
1571 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1573 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAllButUpsideDown
1574 currentOrientation:UIInterfaceOrientationPortrait
1575 didChangeOrientation:NO
1576 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1578 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAllButUpsideDown
1579 currentOrientation:UIInterfaceOrientationLandscapeLeft
1580 didChangeOrientation:NO
1581 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1583 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAllButUpsideDown
1584 currentOrientation:UIInterfaceOrientationLandscapeRight
1585 didChangeOrientation:NO
1586 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1588 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortrait
1589 currentOrientation:UIInterfaceOrientationPortrait
1590 didChangeOrientation:NO
1591 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1593 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortraitUpsideDown
1594 currentOrientation:UIInterfaceOrientationPortraitUpsideDown
1595 didChangeOrientation:NO
1596 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1598 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscape
1599 currentOrientation:UIInterfaceOrientationLandscapeLeft
1600 didChangeOrientation:NO
1601 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1603 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscape
1604 currentOrientation:UIInterfaceOrientationLandscapeRight
1605 didChangeOrientation:NO
1606 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1608 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeLeft
1609 currentOrientation:UIInterfaceOrientationLandscapeLeft
1610 didChangeOrientation:NO
1611 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1613 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeRight
1614 currentOrientation:UIInterfaceOrientationLandscapeRight
1615 didChangeOrientation:NO
1616 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1621 - (void)orientationTestWithOrientationUpdate:(UIInterfaceOrientationMask)mask
1622 currentOrientation:(UIInterfaceOrientation)currentOrientation
1623 didChangeOrientation:(BOOL)didChange
1624 resultingOrientation:(UIInterfaceOrientation)resultingOrientation {
1625 id mockApplication = OCMClassMock([UIApplication
class]);
1629 __block __weak
id weakPreferences;
1635 if (@available(iOS 16.0, *)) {
1636 mockWindowScene = OCMClassMock([UIWindowScene
class]);
1637 mockVC = OCMPartialMock(realVC);
1638 OCMStub([mockVC flutterWindowSceneIfViewLoaded]).andReturn(mockWindowScene);
1639 if (realVC.supportedInterfaceOrientations == mask) {
1640 OCMReject([mockWindowScene requestGeometryUpdateWithPreferences:[OCMArg any]
1641 errorHandler:[OCMArg any]]);
1645 OCMExpect([mockWindowScene
1646 requestGeometryUpdateWithPreferences:[OCMArg checkWithBlock:^BOOL(
1647 UIWindowSceneGeometryPreferencesIOS*
1649 weakPreferences = preferences;
1650 return preferences.interfaceOrientations == mask;
1652 errorHandler:[OCMArg any]]);
1654 OCMStub([mockApplication sharedApplication]).andReturn(mockApplication);
1655 OCMStub([mockApplication connectedScenes]).andReturn([NSSet setWithObject:mockWindowScene]);
1657 deviceMock = OCMPartialMock([UIDevice currentDevice]);
1659 OCMReject([deviceMock setValue:[OCMArg any] forKey:
@"orientation"]);
1661 OCMExpect([deviceMock setValue:@(resultingOrientation) forKey:
@"orientation"]);
1663 if (@available(iOS 13.0, *)) {
1664 mockWindowScene = OCMClassMock([UIWindowScene
class]);
1665 mockVC = OCMPartialMock(realVC);
1666 OCMStub([mockVC flutterWindowSceneIfViewLoaded]).andReturn(mockWindowScene);
1667 OCMStub(((UIWindowScene*)mockWindowScene).interfaceOrientation)
1668 .andReturn(currentOrientation);
1670 OCMStub([mockApplication sharedApplication]).andReturn(mockApplication);
1671 OCMStub([mockApplication statusBarOrientation]).andReturn(currentOrientation);
1675 [realVC performOrientationUpdate:mask];
1676 if (@available(iOS 16.0, *)) {
1677 OCMVerifyAll(mockWindowScene);
1679 OCMVerifyAll(deviceMock);
1682 [mockWindowScene stopMocking];
1683 [deviceMock stopMocking];
1684 [mockApplication stopMocking];
1685 XCTAssertNil(weakPreferences);
1690 - (UITraitCollection*)fakeTraitCollectionWithContrast:(UIAccessibilityContrast)contrast {
1691 id mockTraitCollection = OCMClassMock([UITraitCollection
class]);
1692 OCMStub([mockTraitCollection accessibilityContrast]).andReturn(contrast);
1693 return mockTraitCollection;
1696 - (void)testWillDeallocNotification {
1697 XCTestExpectation* expectation =
1698 [[XCTestExpectation alloc] initWithDescription:@"notification called"];
1705 [NSNotificationCenter.defaultCenter addObserverForName:FlutterViewControllerWillDealloc
1707 queue:[NSOperationQueue mainQueue]
1708 usingBlock:^(NSNotification* _Nonnull note) {
1709 [expectation fulfill];
1711 XCTAssertNotNil(realVC);
1714 [
self waitForExpectations:@[ expectation ] timeout:1.0];
1717 - (void)testReleasesKeyboardManagerOnDealloc {
1722 [viewController addInternalPlugins];
1724 XCTAssertNotNil(weakKeyboardManager);
1725 [viewController deregisterNotifications];
1729 XCTAssertNil(weakKeyboardManager);
1732 - (void)testDoesntLoadViewInInit {
1735 [engine createShell:@"" libraryURI:@"" initialRoute:nil];
1739 XCTAssertFalse([realVC isViewLoaded],
@"shouldn't have loaded since it hasn't been shown");
1740 engine.viewController = nil;
1743 - (void)testHideOverlay {
1746 [engine createShell:@"" libraryURI:@"" initialRoute:nil];
1750 XCTAssertFalse(realVC.prefersHomeIndicatorAutoHidden,
@"");
1751 [NSNotificationCenter.defaultCenter postNotificationName:FlutterViewControllerHideHomeIndicator
1753 XCTAssertTrue(realVC.prefersHomeIndicatorAutoHidden,
@"");
1754 engine.viewController = nil;
1757 - (void)testNotifyLowMemory {
1763 OCMStub([viewControllerMock surfaceUpdated:NO]);
1764 [viewController beginAppearanceTransition:NO animated:NO];
1765 [viewController endAppearanceTransition];
1766 XCTAssertTrue(
mockEngine.didCallNotifyLowMemory);
1769 - (void)sendMessage:(
id _Nullable)message reply:(
FlutterReply _Nullable)callback {
1770 NSMutableDictionary* replyMessage = [@{
1775 self.messageSent = message;
1776 CFRunLoopPerformBlock(CFRunLoopGetCurrent(), fml::MessageLoopDarwin::kMessageLoopCFRunLoopMode,
1778 callback(replyMessage);
1783 if (@available(iOS 13.4, *)) {
1790 OCMStub([
mockEngine.keyEventChannel sendMessage:[OCMArg any] reply:[OCMArg any]])
1791 .andCall(
self,
@selector(sendMessage:reply:));
1793 mockEngine.textInputPlugin =
self.mockTextInputPlugin;
1801 [vc addInternalPlugins];
1803 [vc handlePressEvent:keyUpEvent(UIKeyboardHIDUsageKeyboardA, UIKeyModifierShift, 123.0)
1808 XCTAssert([
self.
messageSent[
@"keymap"] isEqualToString:
@"ios"]);
1809 XCTAssert([
self.
messageSent[
@"type"] isEqualToString:
@"keyup"]);
1810 XCTAssert([
self.
messageSent[
@"keyCode"] isEqualToNumber:[NSNumber numberWithInt:4]]);
1811 XCTAssert([
self.
messageSent[
@"modifiers"] isEqualToNumber:[NSNumber numberWithInt:0]]);
1812 XCTAssert([
self.
messageSent[
@"characters"] isEqualToString:
@""]);
1813 XCTAssert([
self.
messageSent[
@"charactersIgnoringModifiers"] isEqualToString:
@""]);
1814 [vc deregisterNotifications];
1818 if (@available(iOS 13.4, *)) {
1826 OCMStub([
mockEngine.keyEventChannel sendMessage:[OCMArg any] reply:[OCMArg any]])
1827 .andCall(
self,
@selector(sendMessage:reply:));
1829 mockEngine.textInputPlugin =
self.mockTextInputPlugin;
1836 [vc addInternalPlugins];
1838 [vc handlePressEvent:keyDownEvent(UIKeyboardHIDUsageKeyboardA, UIKeyModifierShift, 123.0f, "A",
1844 XCTAssert([
self.
messageSent[
@"keymap"] isEqualToString:
@"ios"]);
1845 XCTAssert([
self.
messageSent[
@"type"] isEqualToString:
@"keydown"]);
1846 XCTAssert([
self.
messageSent[
@"keyCode"] isEqualToNumber:[NSNumber numberWithInt:4]]);
1847 XCTAssert([
self.
messageSent[
@"modifiers"] isEqualToNumber:[NSNumber numberWithInt:0]]);
1848 XCTAssert([
self.
messageSent[
@"characters"] isEqualToString:
@"A"]);
1849 XCTAssert([
self.
messageSent[
@"charactersIgnoringModifiers"] isEqualToString:
@"a"]);
1850 [vc deregisterNotifications];
1855 if (@available(iOS 13.4, *)) {
1861 OCMStub([keyEventChannel sendMessage:[OCMArg any] reply:[OCMArg any]])
1862 .andCall(
self,
@selector(sendMessage:reply:));
1864 OCMStub([
self.
mockEngine keyEventChannel]).andReturn(keyEventChannel);
1872 [vc addInternalPlugins];
1874 [vc handlePressEvent:keyEventWithPhase(UIPressPhaseStationary, UIKeyboardHIDUsageKeyboardA,
1875 UIKeyModifierShift, 123.0)
1878 [vc handlePressEvent:keyEventWithPhase(UIPressPhaseCancelled, UIKeyboardHIDUsageKeyboardA,
1879 UIKeyModifierShift, 123.0)
1882 [vc handlePressEvent:keyEventWithPhase(UIPressPhaseChanged, UIKeyboardHIDUsageKeyboardA,
1883 UIKeyModifierShift, 123.0)
1888 OCMVerify(never(), [keyEventChannel sendMessage:[OCMArg any]]);
1889 [vc deregisterNotifications];
1893 if (@available(iOS 13.4, *)) {
1902 XCTAssertNotNil(vc);
1903 UIView* view = vc.view;
1904 XCTAssertNotNil(view);
1905 NSArray* gestureRecognizers = view.gestureRecognizers;
1906 XCTAssertNotNil(gestureRecognizers);
1909 for (
id gesture in gestureRecognizers) {
1910 if ([gesture isKindOfClass:[UIPanGestureRecognizer
class]]) {
1915 XCTAssertTrue(found);
1919 if (@available(iOS 13.4, *)) {
1928 XCTAssertNotNil(vc);
1930 id mockPanGestureRecognizer = OCMClassMock([UIPanGestureRecognizer
class]);
1931 XCTAssertNotNil(mockPanGestureRecognizer);
1933 [vc discreteScrollEvent:mockPanGestureRecognizer];
1936 [[mockPanGestureRecognizer verify] locationInView:[OCMArg any]];
1937 [[[
self.mockEngine verify] ignoringNonObjectArgs]
1938 dispatchPointerDataPacket:std::make_unique<flutter::PointerDataPacket>(0)];
1941 - (void)testFakeEventTimeStamp {
1945 XCTAssertNotNil(vc);
1947 flutter::PointerData pointer_data = [vc generatePointerDataForFake];
1948 int64_t current_micros = [[NSProcessInfo processInfo] systemUptime] * 1000 * 1000;
1949 int64_t interval_micros = current_micros - pointer_data.time_stamp;
1950 const int64_t tolerance_millis = 2;
1951 XCTAssertTrue(interval_micros / 1000 < tolerance_millis,
1952 @"PointerData.time_stamp should be equal to NSProcessInfo.systemUptime");
1955 - (void)testSplashScreenViewCanSetNil {
1958 [flutterViewController setSplashScreenView:nil];
1961 - (void)testLifeCycleNotificationApplicationBecameActive {
1966 UIWindow* window = [[UIWindow alloc] init];
1967 [window addSubview:flutterViewController.view];
1968 flutterViewController.view.bounds = CGRectMake(0, 0, 100, 100);
1969 [flutterViewController viewDidLayoutSubviews];
1970 NSNotification* sceneNotification =
1971 [NSNotification notificationWithName:UISceneDidActivateNotification object:nil userInfo:nil];
1972 NSNotification* applicationNotification =
1973 [NSNotification notificationWithName:UIApplicationDidBecomeActiveNotification
1976 id mockVC = OCMPartialMock(flutterViewController);
1977 [NSNotificationCenter.defaultCenter postNotification:sceneNotification];
1978 [NSNotificationCenter.defaultCenter postNotification:applicationNotification];
1979 OCMReject([mockVC sceneBecameActive:[OCMArg any]]);
1980 OCMVerify([mockVC applicationBecameActive:[OCMArg any]]);
1981 XCTAssertFalse(flutterViewController.isKeyboardInOrTransitioningFromBackground);
1982 OCMVerify([mockVC surfaceUpdated:YES]);
1983 XCTestExpectation* timeoutApplicationLifeCycle =
1984 [
self expectationWithDescription:@"timeoutApplicationLifeCycle"];
1985 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)),
1986 dispatch_get_main_queue(), ^{
1987 [timeoutApplicationLifeCycle fulfill];
1988 OCMVerify([mockVC goToApplicationLifecycle:
@"AppLifecycleState.resumed"]);
1989 [flutterViewController deregisterNotifications];
1991 [
self waitForExpectationsWithTimeout:5.0 handler:nil];
1994 - (void)testLifeCycleNotificationSceneBecameActive {
1995 id mockBundle = OCMPartialMock([NSBundle mainBundle]);
1996 OCMStub([mockBundle objectForInfoDictionaryKey:
@"NSExtension"]).andReturn(@{
1997 @"NSExtensionPointIdentifier" :
@"com.apple.share-services"
2003 UIWindow* window = [[UIWindow alloc] init];
2004 [window addSubview:flutterViewController.view];
2005 flutterViewController.view.bounds = CGRectMake(0, 0, 100, 100);
2006 [flutterViewController viewDidLayoutSubviews];
2007 NSNotification* sceneNotification =
2008 [NSNotification notificationWithName:UISceneDidActivateNotification object:nil userInfo:nil];
2009 NSNotification* applicationNotification =
2010 [NSNotification notificationWithName:UIApplicationDidBecomeActiveNotification
2013 id mockVC = OCMPartialMock(flutterViewController);
2014 [NSNotificationCenter.defaultCenter postNotification:sceneNotification];
2015 [NSNotificationCenter.defaultCenter postNotification:applicationNotification];
2016 OCMVerify([mockVC sceneBecameActive:[OCMArg any]]);
2017 OCMReject([mockVC applicationBecameActive:[OCMArg any]]);
2018 XCTAssertFalse(flutterViewController.isKeyboardInOrTransitioningFromBackground);
2019 OCMVerify([mockVC surfaceUpdated:YES]);
2020 XCTestExpectation* timeoutApplicationLifeCycle =
2021 [
self expectationWithDescription:@"timeoutApplicationLifeCycle"];
2022 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)),
2023 dispatch_get_main_queue(), ^{
2024 [timeoutApplicationLifeCycle fulfill];
2025 OCMVerify([mockVC goToApplicationLifecycle:
@"AppLifecycleState.resumed"]);
2026 [flutterViewController deregisterNotifications];
2028 [
self waitForExpectationsWithTimeout:5.0 handler:nil];
2029 [mockBundle stopMocking];
2032 - (void)testLifeCycleNotificationApplicationWillResignActive {
2037 NSNotification* sceneNotification =
2038 [NSNotification notificationWithName:UISceneWillDeactivateNotification
2041 NSNotification* applicationNotification =
2042 [NSNotification notificationWithName:UIApplicationWillResignActiveNotification
2045 id mockVC = OCMPartialMock(flutterViewController);
2046 [NSNotificationCenter.defaultCenter postNotification:sceneNotification];
2047 [NSNotificationCenter.defaultCenter postNotification:applicationNotification];
2048 OCMReject([mockVC sceneWillResignActive:[OCMArg any]]);
2049 OCMVerify([mockVC applicationWillResignActive:[OCMArg any]]);
2050 OCMVerify([mockVC goToApplicationLifecycle:
@"AppLifecycleState.inactive"]);
2051 [flutterViewController deregisterNotifications];
2054 - (void)testLifeCycleNotificationSceneWillResignActive {
2055 id mockBundle = OCMPartialMock([NSBundle mainBundle]);
2056 OCMStub([mockBundle objectForInfoDictionaryKey:
@"NSExtension"]).andReturn(@{
2057 @"NSExtensionPointIdentifier" :
@"com.apple.share-services"
2063 NSNotification* sceneNotification =
2064 [NSNotification notificationWithName:UISceneWillDeactivateNotification
2067 NSNotification* applicationNotification =
2068 [NSNotification notificationWithName:UIApplicationWillResignActiveNotification
2071 id mockVC = OCMPartialMock(flutterViewController);
2072 [NSNotificationCenter.defaultCenter postNotification:sceneNotification];
2073 [NSNotificationCenter.defaultCenter postNotification:applicationNotification];
2074 OCMVerify([mockVC sceneWillResignActive:[OCMArg any]]);
2075 OCMReject([mockVC applicationWillResignActive:[OCMArg any]]);
2076 OCMVerify([mockVC goToApplicationLifecycle:
@"AppLifecycleState.inactive"]);
2077 [flutterViewController deregisterNotifications];
2078 [mockBundle stopMocking];
2081 - (void)testLifeCycleNotificationApplicationWillTerminate {
2086 NSNotification* sceneNotification =
2087 [NSNotification notificationWithName:UISceneDidDisconnectNotification
2090 NSNotification* applicationNotification =
2091 [NSNotification notificationWithName:UIApplicationWillTerminateNotification
2094 id mockVC = OCMPartialMock(flutterViewController);
2097 [NSNotificationCenter.defaultCenter postNotification:sceneNotification];
2098 [NSNotificationCenter.defaultCenter postNotification:applicationNotification];
2099 OCMReject([mockVC sceneWillDisconnect:[OCMArg any]]);
2100 OCMVerify([mockVC applicationWillTerminate:[OCMArg any]]);
2101 OCMVerify([mockVC goToApplicationLifecycle:
@"AppLifecycleState.detached"]);
2103 [flutterViewController deregisterNotifications];
2106 - (void)testLifeCycleNotificationSceneWillTerminate {
2107 id mockBundle = OCMPartialMock([NSBundle mainBundle]);
2108 OCMStub([mockBundle objectForInfoDictionaryKey:
@"NSExtension"]).andReturn(@{
2109 @"NSExtensionPointIdentifier" :
@"com.apple.share-services"
2115 NSNotification* sceneNotification =
2116 [NSNotification notificationWithName:UISceneDidDisconnectNotification
2119 NSNotification* applicationNotification =
2120 [NSNotification notificationWithName:UIApplicationWillTerminateNotification
2123 id mockVC = OCMPartialMock(flutterViewController);
2126 [NSNotificationCenter.defaultCenter postNotification:sceneNotification];
2127 [NSNotificationCenter.defaultCenter postNotification:applicationNotification];
2128 OCMVerify([mockVC sceneWillDisconnect:[OCMArg any]]);
2129 OCMReject([mockVC applicationWillTerminate:[OCMArg any]]);
2130 OCMVerify([mockVC goToApplicationLifecycle:
@"AppLifecycleState.detached"]);
2132 [flutterViewController deregisterNotifications];
2133 [mockBundle stopMocking];
2136 - (void)testLifeCycleNotificationApplicationDidEnterBackground {
2141 NSNotification* sceneNotification =
2142 [NSNotification notificationWithName:UISceneDidEnterBackgroundNotification
2145 NSNotification* applicationNotification =
2146 [NSNotification notificationWithName:UIApplicationDidEnterBackgroundNotification
2149 id mockVC = OCMPartialMock(flutterViewController);
2150 [NSNotificationCenter.defaultCenter postNotification:sceneNotification];
2151 [NSNotificationCenter.defaultCenter postNotification:applicationNotification];
2152 OCMReject([mockVC sceneDidEnterBackground:[OCMArg any]]);
2153 OCMVerify([mockVC applicationDidEnterBackground:[OCMArg any]]);
2154 XCTAssertTrue(flutterViewController.isKeyboardInOrTransitioningFromBackground);
2155 OCMVerify([mockVC surfaceUpdated:NO]);
2156 OCMVerify([mockVC goToApplicationLifecycle:
@"AppLifecycleState.paused"]);
2157 [flutterViewController deregisterNotifications];
2160 - (void)testLifeCycleNotificationSceneDidEnterBackground {
2161 id mockBundle = OCMPartialMock([NSBundle mainBundle]);
2162 OCMStub([mockBundle objectForInfoDictionaryKey:
@"NSExtension"]).andReturn(@{
2163 @"NSExtensionPointIdentifier" :
@"com.apple.share-services"
2169 NSNotification* sceneNotification =
2170 [NSNotification notificationWithName:UISceneDidEnterBackgroundNotification
2173 NSNotification* applicationNotification =
2174 [NSNotification notificationWithName:UIApplicationDidEnterBackgroundNotification
2177 id mockVC = OCMPartialMock(flutterViewController);
2178 [NSNotificationCenter.defaultCenter postNotification:sceneNotification];
2179 [NSNotificationCenter.defaultCenter postNotification:applicationNotification];
2180 OCMVerify([mockVC sceneDidEnterBackground:[OCMArg any]]);
2181 OCMReject([mockVC applicationDidEnterBackground:[OCMArg any]]);
2182 XCTAssertTrue(flutterViewController.isKeyboardInOrTransitioningFromBackground);
2183 OCMVerify([mockVC surfaceUpdated:NO]);
2184 OCMVerify([mockVC goToApplicationLifecycle:
@"AppLifecycleState.paused"]);
2185 [flutterViewController deregisterNotifications];
2186 [mockBundle stopMocking];
2189 - (void)testLifeCycleNotificationApplicationWillEnterForeground {
2194 NSNotification* sceneNotification =
2195 [NSNotification notificationWithName:UISceneWillEnterForegroundNotification
2198 NSNotification* applicationNotification =
2199 [NSNotification notificationWithName:UIApplicationWillEnterForegroundNotification
2202 id mockVC = OCMPartialMock(flutterViewController);
2203 [NSNotificationCenter.defaultCenter postNotification:sceneNotification];
2204 [NSNotificationCenter.defaultCenter postNotification:applicationNotification];
2205 OCMReject([mockVC sceneWillEnterForeground:[OCMArg any]]);
2206 OCMVerify([mockVC applicationWillEnterForeground:[OCMArg any]]);
2207 OCMVerify([mockVC goToApplicationLifecycle:
@"AppLifecycleState.inactive"]);
2208 [flutterViewController deregisterNotifications];
2211 - (void)testLifeCycleNotificationSceneWillEnterForeground {
2212 id mockBundle = OCMPartialMock([NSBundle mainBundle]);
2213 OCMStub([mockBundle objectForInfoDictionaryKey:
@"NSExtension"]).andReturn(@{
2214 @"NSExtensionPointIdentifier" :
@"com.apple.share-services"
2220 NSNotification* sceneNotification =
2221 [NSNotification notificationWithName:UISceneWillEnterForegroundNotification
2224 NSNotification* applicationNotification =
2225 [NSNotification notificationWithName:UIApplicationWillEnterForegroundNotification
2228 id mockVC = OCMPartialMock(flutterViewController);
2229 [NSNotificationCenter.defaultCenter postNotification:sceneNotification];
2230 [NSNotificationCenter.defaultCenter postNotification:applicationNotification];
2231 OCMVerify([mockVC sceneWillEnterForeground:[OCMArg any]]);
2232 OCMReject([mockVC applicationWillEnterForeground:[OCMArg any]]);
2233 OCMVerify([mockVC goToApplicationLifecycle:
@"AppLifecycleState.inactive"]);
2234 [flutterViewController deregisterNotifications];
2235 [mockBundle stopMocking];
2238 - (void)testLifeCycleNotificationCancelledInvalidResumed {
2243 NSNotification* applicationDidBecomeActiveNotification =
2244 [NSNotification notificationWithName:UIApplicationDidBecomeActiveNotification
2247 NSNotification* applicationWillResignActiveNotification =
2248 [NSNotification notificationWithName:UIApplicationWillResignActiveNotification
2251 id mockVC = OCMPartialMock(flutterViewController);
2252 [NSNotificationCenter.defaultCenter postNotification:applicationDidBecomeActiveNotification];
2253 [NSNotificationCenter.defaultCenter postNotification:applicationWillResignActiveNotification];
2254 OCMVerify([mockVC goToApplicationLifecycle:
@"AppLifecycleState.inactive"]);
2256 XCTestExpectation* timeoutApplicationLifeCycle =
2257 [
self expectationWithDescription:@"timeoutApplicationLifeCycle"];
2258 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)),
2259 dispatch_get_main_queue(), ^{
2260 OCMReject([mockVC goToApplicationLifecycle:
@"AppLifecycleState.resumed"]);
2261 [timeoutApplicationLifeCycle fulfill];
2262 [flutterViewController deregisterNotifications];
2264 [
self waitForExpectationsWithTimeout:5.0 handler:nil];
2267 - (void)testSetupKeyboardAnimationVsyncClientWillCreateNewVsyncClientForFlutterViewController {
2268 id bundleMock = OCMPartialMock([NSBundle mainBundle]);
2272 double maxFrameRate = 120;
2273 [[[mockDisplayLinkManager stub] andReturnValue:@(maxFrameRate)] displayRefreshRate];
2281 [viewController setUpKeyboardAnimationVsyncClient:callback];
2283 CADisplayLink* link = [viewController.keyboardAnimationVSyncClient getDisplayLink];
2284 XCTAssertNotNil(link);
2285 if (@available(iOS 15.0, *)) {
2286 XCTAssertEqual(link.preferredFrameRateRange.maximum, maxFrameRate);
2287 XCTAssertEqual(link.preferredFrameRateRange.preferred, maxFrameRate);
2288 XCTAssertEqual(link.preferredFrameRateRange.minimum, maxFrameRate / 2);
2290 XCTAssertEqual(link.preferredFramesPerSecond, maxFrameRate);
2295 testCreateTouchRateCorrectionVSyncClientWillCreateVsyncClientWhenRefreshRateIsLargerThan60HZ {
2297 double maxFrameRate = 120;
2298 [[[mockDisplayLinkManager stub] andReturnValue:@(maxFrameRate)] displayRefreshRate];
2304 [viewController createTouchRateCorrectionVSyncClientIfNeeded];
2308 - (void)testCreateTouchRateCorrectionVSyncClientWillNotCreateNewVSyncClientWhenClientAlreadyExists {
2310 double maxFrameRate = 120;
2311 [[[mockDisplayLinkManager stub] andReturnValue:@(maxFrameRate)] displayRefreshRate];
2318 [viewController createTouchRateCorrectionVSyncClientIfNeeded];
2320 XCTAssertNotNil(clientBefore);
2322 [viewController createTouchRateCorrectionVSyncClientIfNeeded];
2324 XCTAssertNotNil(clientAfter);
2326 XCTAssertTrue(clientBefore == clientAfter);
2329 - (void)testCreateTouchRateCorrectionVSyncClientWillNotCreateVsyncClientWhenRefreshRateIs60HZ {
2331 double maxFrameRate = 60;
2332 [[[mockDisplayLinkManager stub] andReturnValue:@(maxFrameRate)] displayRefreshRate];
2338 [viewController createTouchRateCorrectionVSyncClientIfNeeded];
2342 - (void)testTriggerTouchRateCorrectionVSyncClientCorrectly {
2344 double maxFrameRate = 120;
2345 [[[mockDisplayLinkManager stub] andReturnValue:@(maxFrameRate)] displayRefreshRate];
2351 [viewController loadView];
2352 [viewController viewDidLoad];
2355 CADisplayLink* link = [client getDisplayLink];
2357 UITouch* fakeTouchBegan = [[UITouch alloc] init];
2358 fakeTouchBegan.phase = UITouchPhaseBegan;
2360 UITouch* fakeTouchMove = [[UITouch alloc] init];
2361 fakeTouchMove.phase = UITouchPhaseMoved;
2363 UITouch* fakeTouchEnd = [[UITouch alloc] init];
2364 fakeTouchEnd.phase = UITouchPhaseEnded;
2366 UITouch* fakeTouchCancelled = [[UITouch alloc] init];
2367 fakeTouchCancelled.phase = UITouchPhaseCancelled;
2370 triggerTouchRateCorrectionIfNeeded:[[NSSet alloc] initWithObjects:fakeTouchBegan, nil]];
2371 XCTAssertFalse(link.isPaused);
2374 triggerTouchRateCorrectionIfNeeded:[[NSSet alloc] initWithObjects:fakeTouchEnd, nil]];
2375 XCTAssertTrue(link.isPaused);
2378 triggerTouchRateCorrectionIfNeeded:[[NSSet alloc] initWithObjects:fakeTouchMove, nil]];
2379 XCTAssertFalse(link.isPaused);
2382 triggerTouchRateCorrectionIfNeeded:[[NSSet alloc] initWithObjects:fakeTouchCancelled, nil]];
2383 XCTAssertTrue(link.isPaused);
2386 triggerTouchRateCorrectionIfNeeded:[[NSSet alloc]
2387 initWithObjects:fakeTouchBegan, fakeTouchEnd, nil]];
2388 XCTAssertFalse(link.isPaused);
2391 triggerTouchRateCorrectionIfNeeded:[[NSSet alloc] initWithObjects:fakeTouchEnd,
2392 fakeTouchCancelled, nil]];
2393 XCTAssertTrue(link.isPaused);
2396 triggerTouchRateCorrectionIfNeeded:[[NSSet alloc]
2397 initWithObjects:fakeTouchMove, fakeTouchEnd, nil]];
2398 XCTAssertFalse(link.isPaused);
2401 - (void)testFlutterViewControllerStartKeyboardAnimationWillCreateVsyncClientCorrectly {
2408 [viewController startKeyBoardAnimation:0.25];
2413 testSetupKeyboardAnimationVsyncClientWillNotCreateNewVsyncClientWhenKeyboardAnimationCallbackIsNil {
2419 [viewController setUpKeyboardAnimationVsyncClient:nil];
2423 - (void)testSupportsShowingSystemContextMenuForIOS16AndAbove {
2429 BOOL supportsShowingSystemContextMenu = [viewController supportsShowingSystemContextMenu];
2430 if (@available(iOS 16.0, *)) {
2431 XCTAssertTrue(supportsShowingSystemContextMenu);
2433 XCTAssertFalse(supportsShowingSystemContextMenu);
2437 - (void)testStateIsActiveAndBackgroundWhenApplicationStateIsActive {
2443 id mockApplication = OCMClassMock([UIApplication
class]);
2444 OCMStub([mockApplication applicationState]).andReturn(UIApplicationStateActive);
2445 OCMStub([mockApplication sharedApplication]).andReturn(mockApplication);
2450 - (void)testStateIsActiveAndBackgroundWhenApplicationStateIsBackground {
2456 id mockApplication = OCMClassMock([UIApplication
class]);
2457 OCMStub([mockApplication applicationState]).andReturn(UIApplicationStateBackground);
2458 OCMStub([mockApplication sharedApplication]).andReturn(mockApplication);
2463 - (void)testStateIsActiveAndBackgroundWhenApplicationStateIsInactive {
2469 id mockApplication = OCMClassMock([UIApplication
class]);
2470 OCMStub([mockApplication applicationState]).andReturn(UIApplicationStateInactive);
2471 OCMStub([mockApplication sharedApplication]).andReturn(mockApplication);
2476 - (void)testStateIsActiveAndBackgroundWhenSceneStateIsActive {
2477 id mockBundle = OCMPartialMock([NSBundle mainBundle]);
2478 OCMStub([mockBundle objectForInfoDictionaryKey:
@"NSExtension"]).andReturn(@{
2479 @"NSExtensionPointIdentifier" :
@"com.apple.share-services"
2487 OCMStub([mockVC activationState]).andReturn(UISceneActivationStateForegroundActive);
2491 [mockBundle stopMocking];
2492 [mockVC stopMocking];
2495 - (void)testStateIsActiveAndBackgroundWhenSceneStateIsBackground {
2496 id mockBundle = OCMPartialMock([NSBundle mainBundle]);
2497 OCMStub([mockBundle objectForInfoDictionaryKey:
@"NSExtension"]).andReturn(@{
2498 @"NSExtensionPointIdentifier" :
@"com.apple.share-services"
2506 OCMStub([mockVC activationState]).andReturn(UISceneActivationStateBackground);
2510 [mockBundle stopMocking];
2511 [mockVC stopMocking];
2514 - (void)testStateIsActiveAndBackgroundWhenSceneStateIsInactive {
2515 id mockBundle = OCMPartialMock([NSBundle mainBundle]);
2516 OCMStub([mockBundle objectForInfoDictionaryKey:
@"NSExtension"]).andReturn(@{
2517 @"NSExtensionPointIdentifier" :
@"com.apple.share-services"
2525 OCMStub([mockVC activationState]).andReturn(UISceneActivationStateForegroundInactive);
2529 [mockBundle stopMocking];
2530 [mockVC stopMocking];
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()
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...