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;
147 - (void)setUpKeyboardSpringAnimationIfNeeded:(CAAnimation*)keyboardAnimation;
148 - (void)setUpKeyboardAnimationVsyncClient:
153 - (
flutter::PointerData)generatePointerDataForFake;
155 initialRoute:(nullable NSString*)initialRoute;
156 - (void)applicationBecameActive:(NSNotification*)notification;
157 - (void)applicationWillResignActive:(NSNotification*)notification;
158 - (void)applicationWillTerminate:(NSNotification*)notification;
159 - (void)applicationDidEnterBackground:(NSNotification*)notification;
160 - (void)applicationWillEnterForeground:(NSNotification*)notification;
161 - (void)sceneBecameActive:(NSNotification*)notification API_AVAILABLE(ios(13.0));
162 - (void)sceneWillResignActive:(NSNotification*)notification API_AVAILABLE(ios(13.0));
163 - (void)sceneWillDisconnect:(NSNotification*)notification API_AVAILABLE(ios(13.0));
164 - (void)sceneDidEnterBackground:(NSNotification*)notification API_AVAILABLE(ios(13.0));
165 - (void)sceneWillEnterForeground:(NSNotification*)notification API_AVAILABLE(ios(13.0));
166 - (void)triggerTouchRateCorrectionIfNeeded:(NSSet*)touches;
170 @property(nonatomic, strong)
id mockEngine;
171 @property(nonatomic, strong)
id mockTextInputPlugin;
172 @property(nonatomic, strong)
id messageSent;
173 - (void)sendMessage:(
id _Nullable)message reply:(
FlutterReply _Nullable)callback;
176 @interface UITouch ()
178 @property(nonatomic, readwrite) UITouchPhase phase;
194 self.messageSent = nil;
200 [
self.mockEngine stopMocking];
201 self.mockEngine = nil;
202 self.mockTextInputPlugin = nil;
203 self.messageSent = nil;
206 - (id)setUpMockScreen {
207 UIScreen* mockScreen = OCMClassMock([UIScreen
class]);
209 CGRect screenBounds = CGRectMake(0, 0, 1170, 2532);
210 OCMStub([mockScreen bounds]).andReturn(screenBounds);
211 CGFloat screenScale = 1;
212 OCMStub([mockScreen scale]).andReturn(screenScale);
218 screen:(UIScreen*)screen
219 viewFrame:(CGRect)viewFrame
220 convertedFrame:(CGRect)convertedFrame {
221 OCMStub([viewControllerMock flutterScreenIfViewLoaded]).andReturn(screen);
222 id mockView = OCMClassMock([UIView
class]);
223 OCMStub([mockView frame]).andReturn(viewFrame);
224 OCMStub([mockView convertRect:viewFrame toCoordinateSpace:[OCMArg any]])
225 .andReturn(convertedFrame);
226 OCMStub([viewControllerMock viewIfLoaded]).andReturn(mockView);
231 - (void)testViewDidLoadWillInvokeCreateTouchRateCorrectionVSyncClient {
238 [viewControllerMock loadView];
239 [viewControllerMock viewDidLoad];
240 OCMVerify([viewControllerMock createTouchRateCorrectionVSyncClientIfNeeded]);
243 - (void)testStartKeyboardAnimationWillInvokeSetupKeyboardSpringAnimationIfNeeded {
250 viewControllerMock.targetViewInsetBottom = 100;
251 [viewControllerMock startKeyBoardAnimation:0.25];
253 CAAnimation* keyboardAnimation =
254 [[viewControllerMock keyboardAnimationView].layer animationForKey:@"position"];
256 OCMVerify([viewControllerMock setUpKeyboardSpringAnimationIfNeeded:keyboardAnimation]);
259 - (void)testSetupKeyboardSpringAnimationIfNeeded {
266 UIScreen* screen = [
self setUpMockScreen];
267 CGRect viewFrame = screen.bounds;
268 [
self setUpMockView:viewControllerMock
271 convertedFrame:viewFrame];
274 [viewControllerMock setUpKeyboardSpringAnimationIfNeeded:nil];
275 SpringAnimation* keyboardSpringAnimation = [viewControllerMock keyboardSpringAnimation];
276 XCTAssertTrue(keyboardSpringAnimation == nil);
279 CABasicAnimation* nonSpringAnimation = [CABasicAnimation animation];
280 nonSpringAnimation.duration = 1.0;
281 nonSpringAnimation.fromValue = [NSNumber numberWithFloat:0.0];
282 nonSpringAnimation.toValue = [NSNumber numberWithFloat:1.0];
283 nonSpringAnimation.keyPath =
@"position";
284 [viewControllerMock setUpKeyboardSpringAnimationIfNeeded:nonSpringAnimation];
285 keyboardSpringAnimation = [viewControllerMock keyboardSpringAnimation];
287 XCTAssertTrue(keyboardSpringAnimation == nil);
290 CASpringAnimation* springAnimation = [CASpringAnimation animation];
291 springAnimation.mass = 1.0;
292 springAnimation.stiffness = 100.0;
293 springAnimation.damping = 10.0;
294 springAnimation.keyPath =
@"position";
295 springAnimation.fromValue = [NSValue valueWithCGPoint:CGPointMake(0, 0)];
296 springAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(100, 100)];
297 [viewControllerMock setUpKeyboardSpringAnimationIfNeeded:springAnimation];
298 keyboardSpringAnimation = [viewControllerMock keyboardSpringAnimation];
299 XCTAssertTrue(keyboardSpringAnimation != nil);
302 - (void)testKeyboardAnimationIsShowingAndCompounding {
309 UIScreen* screen = [
self setUpMockScreen];
310 CGRect viewFrame = screen.bounds;
311 [
self setUpMockView:viewControllerMock
314 convertedFrame:viewFrame];
317 CGFloat screenHeight = screen.bounds.size.height;
318 CGFloat screenWidth = screen.bounds.size.height;
321 CGRect initialShowKeyboardBeginFrame = CGRectMake(0, screenHeight, screenWidth, 250);
322 CGRect initialShowKeyboardEndFrame = CGRectMake(0, screenHeight - 250, screenWidth, 500);
323 NSNotification* fakeNotification = [NSNotification
324 notificationWithName:UIKeyboardWillChangeFrameNotification
327 @"UIKeyboardFrameBeginUserInfoKey" : @(initialShowKeyboardBeginFrame),
328 @"UIKeyboardFrameEndUserInfoKey" : @(initialShowKeyboardEndFrame),
329 @"UIKeyboardAnimationDurationUserInfoKey" : @(0.25),
330 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
332 viewControllerMock.targetViewInsetBottom = 0;
333 [viewControllerMock handleKeyboardNotification:fakeNotification];
334 BOOL isShowingAnimation1 = viewControllerMock.keyboardAnimationIsShowing;
335 XCTAssertTrue(isShowingAnimation1);
338 CGRect compoundingShowKeyboardBeginFrame = CGRectMake(0, screenHeight - 250, screenWidth, 250);
339 CGRect compoundingShowKeyboardEndFrame = CGRectMake(0, screenHeight - 500, screenWidth, 500);
340 fakeNotification = [NSNotification
341 notificationWithName:UIKeyboardWillChangeFrameNotification
344 @"UIKeyboardFrameBeginUserInfoKey" : @(compoundingShowKeyboardBeginFrame),
345 @"UIKeyboardFrameEndUserInfoKey" : @(compoundingShowKeyboardEndFrame),
346 @"UIKeyboardAnimationDurationUserInfoKey" : @(0.25),
347 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
350 [viewControllerMock handleKeyboardNotification:fakeNotification];
351 BOOL isShowingAnimation2 = viewControllerMock.keyboardAnimationIsShowing;
352 XCTAssertTrue(isShowingAnimation2);
353 XCTAssertTrue(isShowingAnimation1 == isShowingAnimation2);
356 CGRect initialHideKeyboardBeginFrame = CGRectMake(0, screenHeight - 500, screenWidth, 250);
357 CGRect initialHideKeyboardEndFrame = CGRectMake(0, screenHeight - 250, screenWidth, 500);
358 fakeNotification = [NSNotification
359 notificationWithName:UIKeyboardWillChangeFrameNotification
362 @"UIKeyboardFrameBeginUserInfoKey" : @(initialHideKeyboardBeginFrame),
363 @"UIKeyboardFrameEndUserInfoKey" : @(initialHideKeyboardEndFrame),
364 @"UIKeyboardAnimationDurationUserInfoKey" : @(0.25),
365 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
368 [viewControllerMock handleKeyboardNotification:fakeNotification];
369 BOOL isShowingAnimation3 = viewControllerMock.keyboardAnimationIsShowing;
370 XCTAssertFalse(isShowingAnimation3);
371 XCTAssertTrue(isShowingAnimation2 != isShowingAnimation3);
374 CGRect compoundingHideKeyboardBeginFrame = CGRectMake(0, screenHeight - 250, screenWidth, 250);
375 CGRect compoundingHideKeyboardEndFrame = CGRectMake(0, screenHeight, screenWidth, 500);
376 fakeNotification = [NSNotification
377 notificationWithName:UIKeyboardWillChangeFrameNotification
380 @"UIKeyboardFrameBeginUserInfoKey" : @(compoundingHideKeyboardBeginFrame),
381 @"UIKeyboardFrameEndUserInfoKey" : @(compoundingHideKeyboardEndFrame),
382 @"UIKeyboardAnimationDurationUserInfoKey" : @(0.25),
383 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
386 [viewControllerMock handleKeyboardNotification:fakeNotification];
387 BOOL isShowingAnimation4 = viewControllerMock.keyboardAnimationIsShowing;
388 XCTAssertFalse(isShowingAnimation4);
389 XCTAssertTrue(isShowingAnimation3 == isShowingAnimation4);
392 - (void)testShouldIgnoreKeyboardNotification {
394 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
399 UIScreen* screen = [
self setUpMockScreen];
400 CGRect viewFrame = screen.bounds;
401 [
self setUpMockView:viewControllerMock
404 convertedFrame:viewFrame];
406 CGFloat screenWidth = screen.bounds.size.width;
407 CGFloat screenHeight = screen.bounds.size.height;
408 CGRect emptyKeyboard = CGRectZero;
409 CGRect zeroHeightKeyboard = CGRectMake(0, 0, screenWidth, 0);
410 CGRect validKeyboardEndFrame = CGRectMake(0, screenHeight - 320, screenWidth, 320);
414 NSNotification* notification =
415 [NSNotification notificationWithName:UIKeyboardWillHideNotification
418 @"UIKeyboardFrameEndUserInfoKey" : @(validKeyboardEndFrame),
419 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
420 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
423 BOOL shouldIgnore = [viewControllerMock shouldIgnoreKeyboardNotification:notification];
424 XCTAssertTrue(shouldIgnore == NO);
428 notification = [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
431 @"UIKeyboardFrameEndUserInfoKey" : @(emptyKeyboard),
432 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
433 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
435 shouldIgnore = [viewControllerMock shouldIgnoreKeyboardNotification:notification];
436 XCTAssertTrue(shouldIgnore == YES);
441 [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
444 @"UIKeyboardFrameEndUserInfoKey" : @(zeroHeightKeyboard),
445 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
446 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
448 shouldIgnore = [viewControllerMock shouldIgnoreKeyboardNotification:notification];
449 XCTAssertTrue(shouldIgnore == NO);
454 [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
457 @"UIKeyboardFrameEndUserInfoKey" : @(validKeyboardEndFrame),
458 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
459 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
461 shouldIgnore = [viewControllerMock shouldIgnoreKeyboardNotification:notification];
462 XCTAssertTrue(shouldIgnore == YES);
467 [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
470 @"UIKeyboardFrameEndUserInfoKey" : @(validKeyboardEndFrame),
471 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
472 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
474 shouldIgnore = [viewControllerMock shouldIgnoreKeyboardNotification:notification];
475 XCTAssertTrue(shouldIgnore == NO);
477 if (@available(iOS 13.0, *)) {
481 OCMStub([viewControllerMock isKeyboardInOrTransitioningFromBackground]).andReturn(YES);
485 [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
488 @"UIKeyboardFrameEndUserInfoKey" : @(validKeyboardEndFrame),
489 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
490 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
492 shouldIgnore = [viewControllerMock shouldIgnoreKeyboardNotification:notification];
493 XCTAssertTrue(shouldIgnore == YES);
496 - (void)testKeyboardAnimationWillNotCrashWhenEngineDestroyed {
502 [viewController setUpKeyboardAnimationVsyncClient:^(fml::TimePoint){
507 - (void)testKeyboardAnimationWillWaitUIThreadVsync {
520 const int delayTime = 1;
521 [engine uiTaskRunner]->PostTask([] { sleep(delayTime); });
522 XCTestExpectation* expectation = [
self expectationWithDescription:@"keyboard animation callback"];
524 __block CFTimeInterval fulfillTime;
526 fulfillTime = CACurrentMediaTime();
527 [expectation fulfill];
529 CFTimeInterval startTime = CACurrentMediaTime();
530 [viewController setUpKeyboardAnimationVsyncClient:callback];
531 [
self waitForExpectationsWithTimeout:5.0 handler:nil];
532 XCTAssertTrue(fulfillTime - startTime > delayTime);
535 - (void)testCalculateKeyboardAttachMode {
537 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
543 UIScreen* screen = [
self setUpMockScreen];
544 CGRect viewFrame = screen.bounds;
545 [
self setUpMockView:viewControllerMock
548 convertedFrame:viewFrame];
550 CGFloat screenWidth = screen.bounds.size.width;
551 CGFloat screenHeight = screen.bounds.size.height;
554 CGRect keyboardFrame = CGRectZero;
555 NSNotification* notification =
556 [NSNotification notificationWithName:UIKeyboardWillHideNotification
559 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
560 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
561 @"UIKeyboardIsLocalUserInfoKey" : @(YES)
563 FlutterKeyboardMode keyboardMode = [viewControllerMock calculateKeyboardAttachMode:notification];
564 XCTAssertTrue(keyboardMode == FlutterKeyboardModeHidden);
567 keyboardFrame = CGRectZero;
568 notification = [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
571 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
572 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
573 @"UIKeyboardIsLocalUserInfoKey" : @(YES)
575 keyboardMode = [viewControllerMock calculateKeyboardAttachMode:notification];
576 XCTAssertTrue(keyboardMode == FlutterKeyboardModeFloating);
579 keyboardFrame = CGRectMake(0, 0, screenWidth, 0);
580 notification = [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
583 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
584 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
585 @"UIKeyboardIsLocalUserInfoKey" : @(YES)
587 keyboardMode = [viewControllerMock calculateKeyboardAttachMode:notification];
588 XCTAssertTrue(keyboardMode == FlutterKeyboardModeHidden);
591 keyboardFrame = CGRectMake(0, 0, 320, 320);
592 notification = [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
595 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
596 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
597 @"UIKeyboardIsLocalUserInfoKey" : @(YES)
599 keyboardMode = [viewControllerMock calculateKeyboardAttachMode:notification];
600 XCTAssertTrue(keyboardMode == FlutterKeyboardModeFloating);
603 keyboardFrame = CGRectMake(0, 0, screenWidth, 320);
604 notification = [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
607 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
608 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
609 @"UIKeyboardIsLocalUserInfoKey" : @(YES)
611 keyboardMode = [viewControllerMock calculateKeyboardAttachMode:notification];
612 XCTAssertTrue(keyboardMode == FlutterKeyboardModeFloating);
615 keyboardFrame = CGRectMake(0, screenHeight - 320, screenWidth, 320);
616 notification = [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
619 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
620 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
621 @"UIKeyboardIsLocalUserInfoKey" : @(YES)
623 keyboardMode = [viewControllerMock calculateKeyboardAttachMode:notification];
624 XCTAssertTrue(keyboardMode == FlutterKeyboardModeDocked);
627 CGFloat longDecimalHeight = 320.666666666666666;
628 keyboardFrame = CGRectMake(0, screenHeight - longDecimalHeight, screenWidth, longDecimalHeight);
629 notification = [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
632 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
633 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
634 @"UIKeyboardIsLocalUserInfoKey" : @(YES)
636 keyboardMode = [viewControllerMock calculateKeyboardAttachMode:notification];
637 XCTAssertTrue(keyboardMode == FlutterKeyboardModeDocked);
640 keyboardFrame = CGRectMake(0, screenHeight - .0000001, screenWidth, longDecimalHeight);
641 notification = [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
644 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
645 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
646 @"UIKeyboardIsLocalUserInfoKey" : @(YES)
648 keyboardMode = [viewControllerMock calculateKeyboardAttachMode:notification];
649 XCTAssertTrue(keyboardMode == FlutterKeyboardModeHidden);
652 keyboardFrame = CGRectMake(0, screenHeight, screenWidth, 320);
653 notification = [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
656 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
657 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
658 @"UIKeyboardIsLocalUserInfoKey" : @(YES)
660 keyboardMode = [viewControllerMock calculateKeyboardAttachMode:notification];
661 XCTAssertTrue(keyboardMode == FlutterKeyboardModeHidden);
664 - (void)testCalculateMultitaskingAdjustment {
666 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
672 UIScreen* screen = [
self setUpMockScreen];
673 CGFloat screenWidth = screen.bounds.size.width;
674 CGFloat screenHeight = screen.bounds.size.height;
675 CGRect screenRect = screen.bounds;
676 CGRect viewOrigFrame = CGRectMake(0, 0, 320, screenHeight - 40);
677 CGRect convertedViewFrame = CGRectMake(20, 20, 320, screenHeight - 40);
678 CGRect keyboardFrame = CGRectMake(20, screenHeight - 320, screenWidth, 300);
679 id mockView = [
self setUpMockView:viewControllerMock
681 viewFrame:viewOrigFrame
682 convertedFrame:convertedViewFrame];
683 id mockTraitCollection = OCMClassMock([UITraitCollection
class]);
684 OCMStub([mockTraitCollection userInterfaceIdiom]).andReturn(UIUserInterfaceIdiomPad);
685 OCMStub([mockTraitCollection horizontalSizeClass]).andReturn(UIUserInterfaceSizeClassCompact);
686 OCMStub([mockTraitCollection verticalSizeClass]).andReturn(UIUserInterfaceSizeClassRegular);
687 OCMStub([mockView traitCollection]).andReturn(mockTraitCollection);
689 CGFloat adjustment = [viewControllerMock calculateMultitaskingAdjustment:screenRect
690 keyboardFrame:keyboardFrame];
691 XCTAssertTrue(adjustment == 20);
694 - (void)testCalculateKeyboardInset {
696 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
701 UIScreen* screen = [
self setUpMockScreen];
702 OCMStub([viewControllerMock flutterScreenIfViewLoaded]).andReturn(screen);
704 CGFloat screenWidth = screen.bounds.size.width;
705 CGFloat screenHeight = screen.bounds.size.height;
706 CGRect viewOrigFrame = CGRectMake(0, 0, 320, screenHeight - 40);
707 CGRect convertedViewFrame = CGRectMake(20, 20, 320, screenHeight - 40);
708 CGRect keyboardFrame = CGRectMake(20, screenHeight - 320, screenWidth, 300);
710 [
self setUpMockView:viewControllerMock
712 viewFrame:viewOrigFrame
713 convertedFrame:convertedViewFrame];
715 CGFloat inset = [viewControllerMock calculateKeyboardInset:keyboardFrame
716 keyboardMode:FlutterKeyboardModeDocked];
717 XCTAssertTrue(inset == 300 * screen.scale);
720 - (void)testHandleKeyboardNotification {
727 UIScreen* screen = [
self setUpMockScreen];
728 CGFloat screenWidth = screen.bounds.size.width;
729 CGFloat screenHeight = screen.bounds.size.height;
730 CGRect keyboardFrame = CGRectMake(0, screenHeight - 320, screenWidth, 320);
731 CGRect viewFrame = screen.bounds;
733 NSNotification* notification =
734 [NSNotification notificationWithName:UIKeyboardWillShowNotification
737 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
738 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
739 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
742 [
self setUpMockView:viewControllerMock
745 convertedFrame:viewFrame];
746 viewControllerMock.targetViewInsetBottom = 0;
747 XCTestExpectation* expectation = [
self expectationWithDescription:@"update viewport"];
748 OCMStub([viewControllerMock updateViewportMetricsIfNeeded]).andDo(^(NSInvocation* invocation) {
749 [expectation fulfill];
752 [viewControllerMock handleKeyboardNotification:notification];
753 XCTAssertTrue(viewControllerMock.targetViewInsetBottom == 320 * screen.scale);
754 OCMVerify([viewControllerMock startKeyBoardAnimation:0.25]);
755 [
self waitForExpectationsWithTimeout:5.0 handler:nil];
758 - (void)testEnsureBottomInsetIsZeroWhenKeyboardDismissed {
760 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
766 CGRect keyboardFrame = CGRectZero;
768 NSNotification* fakeNotification =
769 [NSNotification notificationWithName:UIKeyboardWillHideNotification
772 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
773 @"UIKeyboardAnimationDurationUserInfoKey" : @(0.25),
774 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
777 viewControllerMock.targetViewInsetBottom = 10;
778 [viewControllerMock handleKeyboardNotification:fakeNotification];
779 XCTAssertTrue(viewControllerMock.targetViewInsetBottom == 0);
782 - (void)testEnsureViewportMetricsWillInvokeAndDisplayLinkWillInvalidateInViewDidDisappear {
784 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
789 [viewControllerMock viewDidDisappear:YES];
790 OCMVerify([viewControllerMock ensureViewportMetricsIsCorrect]);
791 OCMVerify([viewControllerMock invalidateKeyboardAnimationVSyncClient]);
794 - (void)testViewDidDisappearDoesntPauseEngineWhenNotTheViewController {
797 mockEngine.lifecycleChannel = lifecycleChannel;
802 id viewControllerMock = OCMPartialMock(viewControllerA);
803 OCMStub([viewControllerMock surfaceUpdated:NO]);
805 [viewControllerA viewDidDisappear:NO];
806 OCMReject([lifecycleChannel sendMessage:
@"AppLifecycleState.paused"]);
807 OCMReject([viewControllerMock surfaceUpdated:[OCMArg any]]);
810 - (void)testAppWillTerminateViewDidDestroyTheEngine {
812 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
817 OCMStub([viewControllerMock goToApplicationLifecycle:
@"AppLifecycleState.detached"]);
819 [viewController applicationWillTerminate:nil];
820 OCMVerify([viewControllerMock goToApplicationLifecycle:
@"AppLifecycleState.detached"]);
824 - (void)testViewDidDisappearDoesPauseEngineWhenIsTheViewController {
827 mockEngine.lifecycleChannel = lifecycleChannel;
835 OCMStub([viewControllerMock surfaceUpdated:NO]);
836 [viewController viewDidDisappear:NO];
837 OCMVerify([lifecycleChannel sendMessage:
@"AppLifecycleState.paused"]);
838 OCMVerify([viewControllerMock surfaceUpdated:NO]);
840 XCTAssertNil(weakViewController);
844 testEngineConfigSyncMethodWillExecuteWhenViewControllerInEngineIsCurrentViewControllerInViewWillAppear {
846 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
850 [viewController viewWillAppear:YES];
855 testEngineConfigSyncMethodWillNotExecuteWhenViewControllerInEngineIsNotCurrentViewControllerInViewWillAppear {
857 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
867 [viewControllerA viewWillAppear:YES];
868 OCMVerify(never(), [viewControllerA onUserSettingsChanged:nil]);
872 testEngineConfigSyncMethodWillExecuteWhenViewControllerInEngineIsCurrentViewControllerInViewDidAppear {
874 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
878 [viewController viewDidAppear:YES];
883 testEngineConfigSyncMethodWillNotExecuteWhenViewControllerInEngineIsNotCurrentViewControllerInViewDidAppear {
885 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
895 [viewControllerA viewDidAppear:YES];
896 OCMVerify(never(), [viewControllerA onUserSettingsChanged:nil]);
900 testEngineConfigSyncMethodWillExecuteWhenViewControllerInEngineIsCurrentViewControllerInViewWillDisappear {
903 mockEngine.lifecycleChannel = lifecycleChannel;
908 [viewController viewWillDisappear:NO];
909 OCMVerify([lifecycleChannel sendMessage:
@"AppLifecycleState.inactive"]);
913 testEngineConfigSyncMethodWillNotExecuteWhenViewControllerInEngineIsNotCurrentViewControllerInViewWillDisappear {
916 mockEngine.lifecycleChannel = lifecycleChannel;
924 [viewControllerA viewDidDisappear:NO];
925 OCMReject([lifecycleChannel sendMessage:
@"AppLifecycleState.inactive"]);
928 - (void)testUpdateViewportMetricsIfNeeded_DoesntInvokeEngineWhenNotTheViewController {
930 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
939 [viewControllerA updateViewportMetricsIfNeeded];
940 flutter::ViewportMetrics viewportMetrics;
941 OCMVerify(never(), [
mockEngine updateViewportMetrics:viewportMetrics]);
944 - (void)testUpdateViewportMetricsIfNeeded_DoesInvokeEngineWhenIsTheViewController {
946 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
951 flutter::ViewportMetrics viewportMetrics;
952 OCMExpect([
mockEngine updateViewportMetrics:viewportMetrics]).ignoringNonObjectArgs();
953 [viewController updateViewportMetricsIfNeeded];
957 - (void)testUpdateViewportMetricsIfNeeded_DoesNotInvokeEngineWhenShouldBeIgnoredDuringRotation {
959 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
964 UIScreen* screen = [
self setUpMockScreen];
965 OCMStub([viewControllerMock flutterScreenIfViewLoaded]).andReturn(screen);
968 id mockCoordinator = OCMProtocolMock(
@protocol(UIViewControllerTransitionCoordinator));
969 OCMStub([mockCoordinator transitionDuration]).andReturn(0.5);
972 [viewController viewWillTransitionToSize:CGSizeZero withTransitionCoordinator:mockCoordinator];
974 [viewController updateViewportMetricsIfNeeded];
976 OCMVerify(never(), [
mockEngine updateViewportMetrics:flutter::ViewportMetrics()]);
979 - (void)testViewWillTransitionToSize_DoesDelayEngineCallIfNonZeroDuration {
981 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
986 UIScreen* screen = [
self setUpMockScreen];
987 OCMStub([viewControllerMock flutterScreenIfViewLoaded]).andReturn(screen);
991 NSTimeInterval transitionDuration = 0.5;
992 id mockCoordinator = OCMProtocolMock(
@protocol(UIViewControllerTransitionCoordinator));
993 OCMStub([mockCoordinator transitionDuration]).andReturn(transitionDuration);
995 flutter::ViewportMetrics viewportMetrics;
996 OCMExpect([
mockEngine updateViewportMetrics:viewportMetrics]).ignoringNonObjectArgs();
998 [viewController viewWillTransitionToSize:CGSizeZero withTransitionCoordinator:mockCoordinator];
1000 [viewController updateViewportMetricsIfNeeded];
1001 OCMVerify(never(), [
mockEngine updateViewportMetrics:flutter::ViewportMetrics()]);
1005 XCTWaiterResult result = [XCTWaiter
1006 waitForExpectations:@[ [
self expectationWithDescription:@"Waiting for rotation duration"] ]
1007 timeout:transitionDuration];
1008 XCTAssertEqual(result, XCTWaiterResultTimedOut);
1013 - (void)testViewWillTransitionToSize_DoesNotDelayEngineCallIfZeroDuration {
1015 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1020 UIScreen* screen = [
self setUpMockScreen];
1021 OCMStub([viewControllerMock flutterScreenIfViewLoaded]).andReturn(screen);
1025 id mockCoordinator = OCMProtocolMock(
@protocol(UIViewControllerTransitionCoordinator));
1026 OCMStub([mockCoordinator transitionDuration]).andReturn(0);
1028 flutter::ViewportMetrics viewportMetrics;
1029 OCMExpect([
mockEngine updateViewportMetrics:viewportMetrics]).ignoringNonObjectArgs();
1032 [viewController viewWillTransitionToSize:CGSizeZero withTransitionCoordinator:mockCoordinator];
1033 [viewController updateViewportMetricsIfNeeded];
1038 - (void)testViewDidLoadDoesntInvokeEngineWhenNotTheViewController {
1040 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1049 UIView* view = viewControllerA.view;
1050 XCTAssertNotNil(view);
1054 - (void)testViewDidLoadDoesInvokeEngineWhenIsTheViewController {
1056 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1063 XCTAssertNotNil(view);
1064 OCMVerify(times(1), [
mockEngine attachView]);
1067 - (void)testViewDidLoadDoesntInvokeEngineAttachViewWhenEngineNeedsLaunch {
1069 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1075 [viewController sharedSetupWithProject:nil initialRoute:nil];
1078 XCTAssertNotNil(view);
1082 - (void)testSplashScreenViewRemoveNotCrash {
1087 [flutterViewController setSplashScreenView:[[UIView alloc] init]];
1088 [flutterViewController setSplashScreenView:nil];
1091 - (void)testInternalPluginsWeakPtrNotCrash {
1097 [vc addInternalPlugins];
1100 [(NSArray<id<FlutterKeyPrimaryResponder>>*)keyboardManager.primaryResponders firstObject];
1101 sendEvent = [keyPrimaryResponder sendEvent];
1105 sendEvent({}, nil, nil);
1110 - (void)testInternalPluginsInvokeInViewDidLoad {
1112 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1120 XCTAssertNotNil(view);
1121 [viewController viewDidLoad];
1125 - (void)testBinaryMessenger {
1129 XCTAssertNotNil(vc);
1131 OCMStub([
self.
mockEngine binaryMessenger]).andReturn(messenger);
1133 OCMVerify([
self.
mockEngine binaryMessenger]);
1136 - (void)testViewControllerIsReleased {
1138 __weak UIView* weakView;
1147 [viewController loadView];
1148 [viewController viewDidLoad];
1152 XCTAssertNil(weakViewController);
1153 XCTAssertNil(weakView);
1156 #pragma mark - Platform Brightness
1158 - (void)testItReportsLightPlatformBrightnessByDefault {
1161 OCMStub([
self.
mockEngine settingsChannel]).andReturn(settingsChannel);
1168 [vc traitCollectionDidChange:nil];
1171 OCMVerify([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(
id message) {
1172 return [message[@"platformBrightness"] isEqualToString:@"light"];
1176 [settingsChannel stopMocking];
1179 - (void)testItReportsPlatformBrightnessWhenViewWillAppear {
1183 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1184 OCMStub([
mockEngine settingsChannel]).andReturn(settingsChannel);
1190 [vc viewWillAppear:false];
1193 OCMVerify([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(
id message) {
1194 return [message[@"platformBrightness"] isEqualToString:@"light"];
1198 [settingsChannel stopMocking];
1201 - (void)testItReportsDarkPlatformBrightnessWhenTraitCollectionRequestsIt {
1202 if (@available(iOS 13, *)) {
1210 OCMStub([
self.
mockEngine settingsChannel]).andReturn(settingsChannel);
1211 id mockTraitCollection =
1212 [
self fakeTraitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleDark];
1221 OCMStub([partialMockVC traitCollection]).andReturn(mockTraitCollection);
1224 [partialMockVC traitCollectionDidChange:nil];
1227 OCMVerify([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(
id message) {
1228 return [message[@"platformBrightness"] isEqualToString:@"dark"];
1232 [partialMockVC stopMocking];
1233 [settingsChannel stopMocking];
1234 [mockTraitCollection stopMocking];
1239 - (UITraitCollection*)fakeTraitCollectionWithUserInterfaceStyle:(UIUserInterfaceStyle)style {
1240 id mockTraitCollection = OCMClassMock([UITraitCollection
class]);
1241 OCMStub([mockTraitCollection userInterfaceStyle]).andReturn(style);
1242 return mockTraitCollection;
1245 #pragma mark - Platform Contrast
1247 - (void)testItReportsNormalPlatformContrastByDefault {
1248 if (@available(iOS 13, *)) {
1256 OCMStub([
self.
mockEngine settingsChannel]).andReturn(settingsChannel);
1263 [vc traitCollectionDidChange:nil];
1266 OCMVerify([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(
id message) {
1267 return [message[@"platformContrast"] isEqualToString:@"normal"];
1271 [settingsChannel stopMocking];
1274 - (void)testItReportsPlatformContrastWhenViewWillAppear {
1275 if (@available(iOS 13, *)) {
1281 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1285 OCMStub([
mockEngine settingsChannel]).andReturn(settingsChannel);
1291 [vc viewWillAppear:false];
1294 OCMVerify([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(
id message) {
1295 return [message[@"platformContrast"] isEqualToString:@"normal"];
1299 [settingsChannel stopMocking];
1302 - (void)testItReportsHighContrastWhenTraitCollectionRequestsIt {
1303 if (@available(iOS 13, *)) {
1311 OCMStub([
self.
mockEngine settingsChannel]).andReturn(settingsChannel);
1313 id mockTraitCollection = [
self fakeTraitCollectionWithContrast:UIAccessibilityContrastHigh];
1322 OCMStub([partialMockVC traitCollection]).andReturn(mockTraitCollection);
1325 [partialMockVC traitCollectionDidChange:mockTraitCollection];
1328 OCMVerify([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(
id message) {
1329 return [message[@"platformContrast"] isEqualToString:@"high"];
1333 [partialMockVC stopMocking];
1334 [settingsChannel stopMocking];
1335 [mockTraitCollection stopMocking];
1338 - (void)testItReportsAlwaysUsed24HourFormat {
1341 OCMStub([
self.
mockEngine settingsChannel]).andReturn(settingsChannel);
1347 OCMStub([mockHourFormat isAlwaysUse24HourFormat]).andReturn(YES);
1348 OCMExpect([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(
id message) {
1349 return [message[@"alwaysUse24HourFormat"] isEqual:@(YES)];
1351 [vc onUserSettingsChanged:nil];
1352 [mockHourFormat stopMocking];
1356 OCMStub([mockHourFormat isAlwaysUse24HourFormat]).andReturn(NO);
1357 OCMExpect([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(
id message) {
1358 return [message[@"alwaysUse24HourFormat"] isEqual:@(NO)];
1360 [vc onUserSettingsChanged:nil];
1361 [mockHourFormat stopMocking];
1364 [settingsChannel stopMocking];
1367 - (void)testItReportsAccessibilityOnOffSwitchLabelsFlagNotSet {
1368 if (@available(iOS 13, *)) {
1378 OCMStub([partialMockViewController accessibilityIsOnOffSwitchLabelsEnabled]).andReturn(NO);
1381 int32_t flags = [partialMockViewController accessibilityFlags];
1384 XCTAssert((flags & (int32_t)flutter::AccessibilityFeatureFlag::kOnOffSwitchLabels) == 0);
1387 - (void)testItReportsAccessibilityOnOffSwitchLabelsFlagSet {
1388 if (@available(iOS 13, *)) {
1398 OCMStub([partialMockViewController accessibilityIsOnOffSwitchLabelsEnabled]).andReturn(YES);
1401 int32_t flags = [partialMockViewController accessibilityFlags];
1404 XCTAssert((flags & (int32_t)flutter::AccessibilityFeatureFlag::kOnOffSwitchLabels) != 0);
1407 - (void)testAccessibilityPerformEscapePopsRoute {
1409 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1411 OCMStub([
mockEngine navigationChannel]).andReturn(mockNavigationChannel);
1418 OCMVerify([mockNavigationChannel invokeMethod:
@"popRoute" arguments:nil]);
1420 [mockNavigationChannel stopMocking];
1423 - (void)testPerformOrientationUpdateForcesOrientationChange {
1424 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortrait
1425 currentOrientation:UIInterfaceOrientationLandscapeLeft
1426 didChangeOrientation:YES
1427 resultingOrientation:UIInterfaceOrientationPortrait];
1429 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortrait
1430 currentOrientation:UIInterfaceOrientationLandscapeRight
1431 didChangeOrientation:YES
1432 resultingOrientation:UIInterfaceOrientationPortrait];
1434 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortrait
1435 currentOrientation:UIInterfaceOrientationPortraitUpsideDown
1436 didChangeOrientation:YES
1437 resultingOrientation:UIInterfaceOrientationPortrait];
1439 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortraitUpsideDown
1440 currentOrientation:UIInterfaceOrientationLandscapeLeft
1441 didChangeOrientation:YES
1442 resultingOrientation:UIInterfaceOrientationPortraitUpsideDown];
1444 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortraitUpsideDown
1445 currentOrientation:UIInterfaceOrientationLandscapeRight
1446 didChangeOrientation:YES
1447 resultingOrientation:UIInterfaceOrientationPortraitUpsideDown];
1449 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortraitUpsideDown
1450 currentOrientation:UIInterfaceOrientationPortrait
1451 didChangeOrientation:YES
1452 resultingOrientation:UIInterfaceOrientationPortraitUpsideDown];
1454 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscape
1455 currentOrientation:UIInterfaceOrientationPortrait
1456 didChangeOrientation:YES
1457 resultingOrientation:UIInterfaceOrientationLandscapeLeft];
1459 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscape
1460 currentOrientation:UIInterfaceOrientationPortraitUpsideDown
1461 didChangeOrientation:YES
1462 resultingOrientation:UIInterfaceOrientationLandscapeLeft];
1464 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeLeft
1465 currentOrientation:UIInterfaceOrientationPortrait
1466 didChangeOrientation:YES
1467 resultingOrientation:UIInterfaceOrientationLandscapeLeft];
1469 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeLeft
1470 currentOrientation:UIInterfaceOrientationLandscapeRight
1471 didChangeOrientation:YES
1472 resultingOrientation:UIInterfaceOrientationLandscapeLeft];
1474 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeLeft
1475 currentOrientation:UIInterfaceOrientationPortraitUpsideDown
1476 didChangeOrientation:YES
1477 resultingOrientation:UIInterfaceOrientationLandscapeLeft];
1479 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeRight
1480 currentOrientation:UIInterfaceOrientationPortrait
1481 didChangeOrientation:YES
1482 resultingOrientation:UIInterfaceOrientationLandscapeRight];
1484 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeRight
1485 currentOrientation:UIInterfaceOrientationLandscapeLeft
1486 didChangeOrientation:YES
1487 resultingOrientation:UIInterfaceOrientationLandscapeRight];
1489 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeRight
1490 currentOrientation:UIInterfaceOrientationPortraitUpsideDown
1491 didChangeOrientation:YES
1492 resultingOrientation:UIInterfaceOrientationLandscapeRight];
1494 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAllButUpsideDown
1495 currentOrientation:UIInterfaceOrientationPortraitUpsideDown
1496 didChangeOrientation:YES
1497 resultingOrientation:UIInterfaceOrientationPortrait];
1500 - (void)testPerformOrientationUpdateDoesNotForceOrientationChange {
1501 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAll
1502 currentOrientation:UIInterfaceOrientationPortrait
1503 didChangeOrientation:NO
1504 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1506 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAll
1507 currentOrientation:UIInterfaceOrientationPortraitUpsideDown
1508 didChangeOrientation:NO
1509 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1511 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAll
1512 currentOrientation:UIInterfaceOrientationLandscapeLeft
1513 didChangeOrientation:NO
1514 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1516 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAll
1517 currentOrientation:UIInterfaceOrientationLandscapeRight
1518 didChangeOrientation:NO
1519 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1521 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAllButUpsideDown
1522 currentOrientation:UIInterfaceOrientationPortrait
1523 didChangeOrientation:NO
1524 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1526 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAllButUpsideDown
1527 currentOrientation:UIInterfaceOrientationLandscapeLeft
1528 didChangeOrientation:NO
1529 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1531 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAllButUpsideDown
1532 currentOrientation:UIInterfaceOrientationLandscapeRight
1533 didChangeOrientation:NO
1534 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1536 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortrait
1537 currentOrientation:UIInterfaceOrientationPortrait
1538 didChangeOrientation:NO
1539 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1541 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortraitUpsideDown
1542 currentOrientation:UIInterfaceOrientationPortraitUpsideDown
1543 didChangeOrientation:NO
1544 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1546 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscape
1547 currentOrientation:UIInterfaceOrientationLandscapeLeft
1548 didChangeOrientation:NO
1549 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1551 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscape
1552 currentOrientation:UIInterfaceOrientationLandscapeRight
1553 didChangeOrientation:NO
1554 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1556 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeLeft
1557 currentOrientation:UIInterfaceOrientationLandscapeLeft
1558 didChangeOrientation:NO
1559 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1561 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeRight
1562 currentOrientation:UIInterfaceOrientationLandscapeRight
1563 didChangeOrientation:NO
1564 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1569 - (void)orientationTestWithOrientationUpdate:(UIInterfaceOrientationMask)mask
1570 currentOrientation:(UIInterfaceOrientation)currentOrientation
1571 didChangeOrientation:(BOOL)didChange
1572 resultingOrientation:(UIInterfaceOrientation)resultingOrientation {
1573 id mockApplication = OCMClassMock([UIApplication
class]);
1577 __block __weak
id weakPreferences;
1583 if (@available(iOS 16.0, *)) {
1584 mockWindowScene = OCMClassMock([UIWindowScene
class]);
1585 mockVC = OCMPartialMock(realVC);
1586 OCMStub([mockVC flutterWindowSceneIfViewLoaded]).andReturn(mockWindowScene);
1587 if (realVC.supportedInterfaceOrientations == mask) {
1588 OCMReject([mockWindowScene requestGeometryUpdateWithPreferences:[OCMArg any]
1589 errorHandler:[OCMArg any]]);
1593 OCMExpect([mockWindowScene
1594 requestGeometryUpdateWithPreferences:[OCMArg checkWithBlock:^BOOL(
1595 UIWindowSceneGeometryPreferencesIOS*
1597 weakPreferences = preferences;
1598 return preferences.interfaceOrientations == mask;
1600 errorHandler:[OCMArg any]]);
1602 OCMStub([mockApplication sharedApplication]).andReturn(mockApplication);
1603 OCMStub([mockApplication connectedScenes]).andReturn([NSSet setWithObject:mockWindowScene]);
1605 deviceMock = OCMPartialMock([UIDevice currentDevice]);
1607 OCMReject([deviceMock setValue:[OCMArg any] forKey:
@"orientation"]);
1609 OCMExpect([deviceMock setValue:@(resultingOrientation) forKey:
@"orientation"]);
1611 if (@available(iOS 13.0, *)) {
1612 mockWindowScene = OCMClassMock([UIWindowScene
class]);
1613 mockVC = OCMPartialMock(realVC);
1614 OCMStub([mockVC flutterWindowSceneIfViewLoaded]).andReturn(mockWindowScene);
1615 OCMStub(((UIWindowScene*)mockWindowScene).interfaceOrientation)
1616 .andReturn(currentOrientation);
1618 OCMStub([mockApplication sharedApplication]).andReturn(mockApplication);
1619 OCMStub([mockApplication statusBarOrientation]).andReturn(currentOrientation);
1623 [realVC performOrientationUpdate:mask];
1624 if (@available(iOS 16.0, *)) {
1625 OCMVerifyAll(mockWindowScene);
1627 OCMVerifyAll(deviceMock);
1630 [mockWindowScene stopMocking];
1631 [deviceMock stopMocking];
1632 [mockApplication stopMocking];
1633 XCTAssertNil(weakPreferences);
1638 - (UITraitCollection*)fakeTraitCollectionWithContrast:(UIAccessibilityContrast)contrast {
1639 id mockTraitCollection = OCMClassMock([UITraitCollection
class]);
1640 OCMStub([mockTraitCollection accessibilityContrast]).andReturn(contrast);
1641 return mockTraitCollection;
1644 - (void)testWillDeallocNotification {
1645 XCTestExpectation* expectation =
1646 [[XCTestExpectation alloc] initWithDescription:@"notification called"];
1653 [[NSNotificationCenter defaultCenter] addObserverForName:FlutterViewControllerWillDealloc
1655 queue:[NSOperationQueue mainQueue]
1656 usingBlock:^(NSNotification* _Nonnull note) {
1657 [expectation fulfill];
1659 XCTAssertNotNil(realVC);
1662 [
self waitForExpectations:@[ expectation ] timeout:1.0];
1665 - (void)testReleasesKeyboardManagerOnDealloc {
1670 [viewController addInternalPlugins];
1672 XCTAssertNotNil(weakKeyboardManager);
1673 [viewController deregisterNotifications];
1677 XCTAssertNil(weakKeyboardManager);
1680 - (void)testDoesntLoadViewInInit {
1683 [engine createShell:@"" libraryURI:@"" initialRoute:nil];
1687 XCTAssertFalse([realVC isViewLoaded],
@"shouldn't have loaded since it hasn't been shown");
1688 engine.viewController = nil;
1691 - (void)testHideOverlay {
1694 [engine createShell:@"" libraryURI:@"" initialRoute:nil];
1698 XCTAssertFalse(realVC.prefersHomeIndicatorAutoHidden,
@"");
1699 [[NSNotificationCenter defaultCenter] postNotificationName:FlutterViewControllerHideHomeIndicator
1701 XCTAssertTrue(realVC.prefersHomeIndicatorAutoHidden,
@"");
1702 engine.viewController = nil;
1705 - (void)testNotifyLowMemory {
1711 OCMStub([viewControllerMock surfaceUpdated:NO]);
1712 [viewController beginAppearanceTransition:NO animated:NO];
1713 [viewController endAppearanceTransition];
1714 XCTAssertTrue(
mockEngine.didCallNotifyLowMemory);
1717 - (void)sendMessage:(
id _Nullable)message reply:(
FlutterReply _Nullable)callback {
1718 NSMutableDictionary* replyMessage = [@{
1723 self.messageSent = message;
1724 CFRunLoopPerformBlock(CFRunLoopGetCurrent(), fml::MessageLoopDarwin::kMessageLoopCFRunLoopMode,
1726 callback(replyMessage);
1731 if (@available(iOS 13.4, *)) {
1738 OCMStub([
mockEngine.keyEventChannel sendMessage:[OCMArg any] reply:[OCMArg any]])
1739 .andCall(
self,
@selector(sendMessage:reply:));
1741 mockEngine.textInputPlugin =
self.mockTextInputPlugin;
1749 [vc addInternalPlugins];
1751 [vc handlePressEvent:keyUpEvent(UIKeyboardHIDUsageKeyboardA, UIKeyModifierShift, 123.0)
1756 XCTAssert([
self.
messageSent[
@"keymap"] isEqualToString:
@"ios"]);
1757 XCTAssert([
self.
messageSent[
@"type"] isEqualToString:
@"keyup"]);
1758 XCTAssert([
self.
messageSent[
@"keyCode"] isEqualToNumber:[NSNumber numberWithInt:4]]);
1759 XCTAssert([
self.
messageSent[
@"modifiers"] isEqualToNumber:[NSNumber numberWithInt:0]]);
1760 XCTAssert([
self.
messageSent[
@"characters"] isEqualToString:
@""]);
1761 XCTAssert([
self.
messageSent[
@"charactersIgnoringModifiers"] isEqualToString:
@""]);
1762 [vc deregisterNotifications];
1766 if (@available(iOS 13.4, *)) {
1774 OCMStub([
mockEngine.keyEventChannel sendMessage:[OCMArg any] reply:[OCMArg any]])
1775 .andCall(
self,
@selector(sendMessage:reply:));
1777 mockEngine.textInputPlugin =
self.mockTextInputPlugin;
1784 [vc addInternalPlugins];
1786 [vc handlePressEvent:keyDownEvent(UIKeyboardHIDUsageKeyboardA, UIKeyModifierShift, 123.0f, "A",
1792 XCTAssert([
self.
messageSent[
@"keymap"] isEqualToString:
@"ios"]);
1793 XCTAssert([
self.
messageSent[
@"type"] isEqualToString:
@"keydown"]);
1794 XCTAssert([
self.
messageSent[
@"keyCode"] isEqualToNumber:[NSNumber numberWithInt:4]]);
1795 XCTAssert([
self.
messageSent[
@"modifiers"] isEqualToNumber:[NSNumber numberWithInt:0]]);
1796 XCTAssert([
self.
messageSent[
@"characters"] isEqualToString:
@"A"]);
1797 XCTAssert([
self.
messageSent[
@"charactersIgnoringModifiers"] isEqualToString:
@"a"]);
1798 [vc deregisterNotifications];
1803 if (@available(iOS 13.4, *)) {
1809 OCMStub([keyEventChannel sendMessage:[OCMArg any] reply:[OCMArg any]])
1810 .andCall(
self,
@selector(sendMessage:reply:));
1812 OCMStub([
self.
mockEngine keyEventChannel]).andReturn(keyEventChannel);
1820 [vc addInternalPlugins];
1822 [vc handlePressEvent:keyEventWithPhase(UIPressPhaseStationary, UIKeyboardHIDUsageKeyboardA,
1823 UIKeyModifierShift, 123.0)
1826 [vc handlePressEvent:keyEventWithPhase(UIPressPhaseCancelled, UIKeyboardHIDUsageKeyboardA,
1827 UIKeyModifierShift, 123.0)
1830 [vc handlePressEvent:keyEventWithPhase(UIPressPhaseChanged, UIKeyboardHIDUsageKeyboardA,
1831 UIKeyModifierShift, 123.0)
1836 OCMVerify(never(), [keyEventChannel sendMessage:[OCMArg any]]);
1837 [vc deregisterNotifications];
1841 if (@available(iOS 13.4, *)) {
1850 XCTAssertNotNil(vc);
1851 UIView* view = vc.view;
1852 XCTAssertNotNil(view);
1853 NSArray* gestureRecognizers = view.gestureRecognizers;
1854 XCTAssertNotNil(gestureRecognizers);
1857 for (
id gesture in gestureRecognizers) {
1858 if ([gesture isKindOfClass:[UIPanGestureRecognizer
class]]) {
1863 XCTAssertTrue(found);
1867 if (@available(iOS 13.4, *)) {
1876 XCTAssertNotNil(vc);
1878 id mockPanGestureRecognizer = OCMClassMock([UIPanGestureRecognizer
class]);
1879 XCTAssertNotNil(mockPanGestureRecognizer);
1881 [vc discreteScrollEvent:mockPanGestureRecognizer];
1884 [[mockPanGestureRecognizer verify] locationInView:[OCMArg any]];
1885 [[[
self.mockEngine verify] ignoringNonObjectArgs]
1886 dispatchPointerDataPacket:std::make_unique<flutter::PointerDataPacket>(0)];
1889 - (void)testFakeEventTimeStamp {
1893 XCTAssertNotNil(vc);
1895 flutter::PointerData pointer_data = [vc generatePointerDataForFake];
1896 int64_t current_micros = [[NSProcessInfo processInfo] systemUptime] * 1000 * 1000;
1897 int64_t interval_micros = current_micros - pointer_data.time_stamp;
1898 const int64_t tolerance_millis = 2;
1899 XCTAssertTrue(interval_micros / 1000 < tolerance_millis,
1900 @"PointerData.time_stamp should be equal to NSProcessInfo.systemUptime");
1903 - (void)testSplashScreenViewCanSetNil {
1906 [flutterViewController setSplashScreenView:nil];
1909 - (void)testLifeCycleNotificationBecameActive {
1914 UIWindow* window = [[UIWindow alloc] init];
1915 [window addSubview:flutterViewController.view];
1916 flutterViewController.view.bounds = CGRectMake(0, 0, 100, 100);
1917 [flutterViewController viewDidLayoutSubviews];
1918 NSNotification* sceneNotification =
1919 [NSNotification notificationWithName:UISceneDidActivateNotification object:nil userInfo:nil];
1920 NSNotification* applicationNotification =
1921 [NSNotification notificationWithName:UIApplicationDidBecomeActiveNotification
1924 id mockVC = OCMPartialMock(flutterViewController);
1925 [[NSNotificationCenter defaultCenter] postNotification:sceneNotification];
1926 [[NSNotificationCenter defaultCenter] postNotification:applicationNotification];
1927 #if APPLICATION_EXTENSION_API_ONLY
1928 OCMVerify([mockVC sceneBecameActive:[OCMArg any]]);
1929 OCMReject([mockVC applicationBecameActive:[OCMArg any]]);
1931 OCMReject([mockVC sceneBecameActive:[OCMArg any]]);
1932 OCMVerify([mockVC applicationBecameActive:[OCMArg any]]);
1934 XCTAssertFalse(flutterViewController.isKeyboardInOrTransitioningFromBackground);
1935 OCMVerify([mockVC surfaceUpdated:YES]);
1936 XCTestExpectation* timeoutApplicationLifeCycle =
1937 [
self expectationWithDescription:@"timeoutApplicationLifeCycle"];
1938 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)),
1939 dispatch_get_main_queue(), ^{
1940 [timeoutApplicationLifeCycle fulfill];
1941 OCMVerify([mockVC goToApplicationLifecycle:
@"AppLifecycleState.resumed"]);
1942 [flutterViewController deregisterNotifications];
1944 [
self waitForExpectationsWithTimeout:5.0 handler:nil];
1947 - (void)testLifeCycleNotificationWillResignActive {
1952 NSNotification* sceneNotification =
1953 [NSNotification notificationWithName:UISceneWillDeactivateNotification
1956 NSNotification* applicationNotification =
1957 [NSNotification notificationWithName:UIApplicationWillResignActiveNotification
1960 id mockVC = OCMPartialMock(flutterViewController);
1961 [[NSNotificationCenter defaultCenter] postNotification:sceneNotification];
1962 [[NSNotificationCenter defaultCenter] postNotification:applicationNotification];
1963 #if APPLICATION_EXTENSION_API_ONLY
1964 OCMVerify([mockVC sceneWillResignActive:[OCMArg any]]);
1965 OCMReject([mockVC applicationWillResignActive:[OCMArg any]]);
1967 OCMReject([mockVC sceneWillResignActive:[OCMArg any]]);
1968 OCMVerify([mockVC applicationWillResignActive:[OCMArg any]]);
1970 OCMVerify([mockVC goToApplicationLifecycle:
@"AppLifecycleState.inactive"]);
1971 [flutterViewController deregisterNotifications];
1974 - (void)testLifeCycleNotificationWillTerminate {
1979 NSNotification* sceneNotification =
1980 [NSNotification notificationWithName:UISceneDidDisconnectNotification
1983 NSNotification* applicationNotification =
1984 [NSNotification notificationWithName:UIApplicationWillTerminateNotification
1987 id mockVC = OCMPartialMock(flutterViewController);
1990 [[NSNotificationCenter defaultCenter] postNotification:sceneNotification];
1991 [[NSNotificationCenter defaultCenter] postNotification:applicationNotification];
1992 #if APPLICATION_EXTENSION_API_ONLY
1993 OCMVerify([mockVC sceneWillDisconnect:[OCMArg any]]);
1994 OCMReject([mockVC applicationWillTerminate:[OCMArg any]]);
1996 OCMReject([mockVC sceneWillDisconnect:[OCMArg any]]);
1997 OCMVerify([mockVC applicationWillTerminate:[OCMArg any]]);
1999 OCMVerify([mockVC goToApplicationLifecycle:
@"AppLifecycleState.detached"]);
2001 [flutterViewController deregisterNotifications];
2004 - (void)testLifeCycleNotificationDidEnterBackground {
2009 NSNotification* sceneNotification =
2010 [NSNotification notificationWithName:UISceneDidEnterBackgroundNotification
2013 NSNotification* applicationNotification =
2014 [NSNotification notificationWithName:UIApplicationDidEnterBackgroundNotification
2017 id mockVC = OCMPartialMock(flutterViewController);
2018 [[NSNotificationCenter defaultCenter] postNotification:sceneNotification];
2019 [[NSNotificationCenter defaultCenter] postNotification:applicationNotification];
2020 #if APPLICATION_EXTENSION_API_ONLY
2021 OCMVerify([mockVC sceneDidEnterBackground:[OCMArg any]]);
2022 OCMReject([mockVC applicationDidEnterBackground:[OCMArg any]]);
2024 OCMReject([mockVC sceneDidEnterBackground:[OCMArg any]]);
2025 OCMVerify([mockVC applicationDidEnterBackground:[OCMArg any]]);
2027 XCTAssertTrue(flutterViewController.isKeyboardInOrTransitioningFromBackground);
2028 OCMVerify([mockVC surfaceUpdated:NO]);
2029 OCMVerify([mockVC goToApplicationLifecycle:
@"AppLifecycleState.paused"]);
2030 [flutterViewController deregisterNotifications];
2033 - (void)testLifeCycleNotificationWillEnterForeground {
2038 NSNotification* sceneNotification =
2039 [NSNotification notificationWithName:UISceneWillEnterForegroundNotification
2042 NSNotification* applicationNotification =
2043 [NSNotification notificationWithName:UIApplicationWillEnterForegroundNotification
2046 id mockVC = OCMPartialMock(flutterViewController);
2047 [[NSNotificationCenter defaultCenter] postNotification:sceneNotification];
2048 [[NSNotificationCenter defaultCenter] postNotification:applicationNotification];
2049 #if APPLICATION_EXTENSION_API_ONLY
2050 OCMVerify([mockVC sceneWillEnterForeground:[OCMArg any]]);
2051 OCMReject([mockVC applicationWillEnterForeground:[OCMArg any]]);
2053 OCMReject([mockVC sceneWillEnterForeground:[OCMArg any]]);
2054 OCMVerify([mockVC applicationWillEnterForeground:[OCMArg any]]);
2056 OCMVerify([mockVC goToApplicationLifecycle:
@"AppLifecycleState.inactive"]);
2057 [flutterViewController deregisterNotifications];
2060 - (void)testLifeCycleNotificationCancelledInvalidResumed {
2065 NSNotification* applicationDidBecomeActiveNotification =
2066 [NSNotification notificationWithName:UIApplicationDidBecomeActiveNotification
2069 NSNotification* applicationWillResignActiveNotification =
2070 [NSNotification notificationWithName:UIApplicationWillResignActiveNotification
2073 id mockVC = OCMPartialMock(flutterViewController);
2074 [[NSNotificationCenter defaultCenter] postNotification:applicationDidBecomeActiveNotification];
2075 [[NSNotificationCenter defaultCenter] postNotification:applicationWillResignActiveNotification];
2076 #if APPLICATION_EXTENSION_API_ONLY
2078 OCMVerify([mockVC goToApplicationLifecycle:
@"AppLifecycleState.inactive"]);
2081 XCTestExpectation* timeoutApplicationLifeCycle =
2082 [
self expectationWithDescription:@"timeoutApplicationLifeCycle"];
2083 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)),
2084 dispatch_get_main_queue(), ^{
2085 OCMReject([mockVC goToApplicationLifecycle:
@"AppLifecycleState.resumed"]);
2086 [timeoutApplicationLifeCycle fulfill];
2087 [flutterViewController deregisterNotifications];
2089 [
self waitForExpectationsWithTimeout:5.0 handler:nil];
2092 - (void)testSetupKeyboardAnimationVsyncClientWillCreateNewVsyncClientForFlutterViewController {
2093 id bundleMock = OCMPartialMock([NSBundle mainBundle]);
2097 double maxFrameRate = 120;
2098 [[[mockDisplayLinkManager stub] andReturnValue:@(maxFrameRate)] displayRefreshRate];
2106 [viewController setUpKeyboardAnimationVsyncClient:callback];
2108 CADisplayLink* link = [viewController.keyboardAnimationVSyncClient getDisplayLink];
2109 XCTAssertNotNil(link);
2110 if (@available(iOS 15.0, *)) {
2111 XCTAssertEqual(link.preferredFrameRateRange.maximum, maxFrameRate);
2112 XCTAssertEqual(link.preferredFrameRateRange.preferred, maxFrameRate);
2113 XCTAssertEqual(link.preferredFrameRateRange.minimum, maxFrameRate / 2);
2115 XCTAssertEqual(link.preferredFramesPerSecond, maxFrameRate);
2120 testCreateTouchRateCorrectionVSyncClientWillCreateVsyncClientWhenRefreshRateIsLargerThan60HZ {
2122 double maxFrameRate = 120;
2123 [[[mockDisplayLinkManager stub] andReturnValue:@(maxFrameRate)] displayRefreshRate];
2129 [viewController createTouchRateCorrectionVSyncClientIfNeeded];
2133 - (void)testCreateTouchRateCorrectionVSyncClientWillNotCreateNewVSyncClientWhenClientAlreadyExists {
2135 double maxFrameRate = 120;
2136 [[[mockDisplayLinkManager stub] andReturnValue:@(maxFrameRate)] displayRefreshRate];
2143 [viewController createTouchRateCorrectionVSyncClientIfNeeded];
2145 XCTAssertNotNil(clientBefore);
2147 [viewController createTouchRateCorrectionVSyncClientIfNeeded];
2149 XCTAssertNotNil(clientAfter);
2151 XCTAssertTrue(clientBefore == clientAfter);
2154 - (void)testCreateTouchRateCorrectionVSyncClientWillNotCreateVsyncClientWhenRefreshRateIs60HZ {
2156 double maxFrameRate = 60;
2157 [[[mockDisplayLinkManager stub] andReturnValue:@(maxFrameRate)] displayRefreshRate];
2163 [viewController createTouchRateCorrectionVSyncClientIfNeeded];
2167 - (void)testTriggerTouchRateCorrectionVSyncClientCorrectly {
2169 double maxFrameRate = 120;
2170 [[[mockDisplayLinkManager stub] andReturnValue:@(maxFrameRate)] displayRefreshRate];
2176 [viewController loadView];
2177 [viewController viewDidLoad];
2180 CADisplayLink* link = [client getDisplayLink];
2182 UITouch* fakeTouchBegan = [[UITouch alloc] init];
2183 fakeTouchBegan.phase = UITouchPhaseBegan;
2185 UITouch* fakeTouchMove = [[UITouch alloc] init];
2186 fakeTouchMove.phase = UITouchPhaseMoved;
2188 UITouch* fakeTouchEnd = [[UITouch alloc] init];
2189 fakeTouchEnd.phase = UITouchPhaseEnded;
2191 UITouch* fakeTouchCancelled = [[UITouch alloc] init];
2192 fakeTouchCancelled.phase = UITouchPhaseCancelled;
2195 triggerTouchRateCorrectionIfNeeded:[[NSSet alloc] initWithObjects:fakeTouchBegan, nil]];
2196 XCTAssertFalse(link.isPaused);
2199 triggerTouchRateCorrectionIfNeeded:[[NSSet alloc] initWithObjects:fakeTouchEnd, nil]];
2200 XCTAssertTrue(link.isPaused);
2203 triggerTouchRateCorrectionIfNeeded:[[NSSet alloc] initWithObjects:fakeTouchMove, nil]];
2204 XCTAssertFalse(link.isPaused);
2207 triggerTouchRateCorrectionIfNeeded:[[NSSet alloc] initWithObjects:fakeTouchCancelled, nil]];
2208 XCTAssertTrue(link.isPaused);
2211 triggerTouchRateCorrectionIfNeeded:[[NSSet alloc]
2212 initWithObjects:fakeTouchBegan, fakeTouchEnd, nil]];
2213 XCTAssertFalse(link.isPaused);
2216 triggerTouchRateCorrectionIfNeeded:[[NSSet alloc] initWithObjects:fakeTouchEnd,
2217 fakeTouchCancelled, nil]];
2218 XCTAssertTrue(link.isPaused);
2221 triggerTouchRateCorrectionIfNeeded:[[NSSet alloc]
2222 initWithObjects:fakeTouchMove, fakeTouchEnd, nil]];
2223 XCTAssertFalse(link.isPaused);
2226 - (void)testFlutterViewControllerStartKeyboardAnimationWillCreateVsyncClientCorrectly {
2233 [viewController startKeyBoardAnimation:0.25];
2238 testSetupKeyboardAnimationVsyncClientWillNotCreateNewVsyncClientWhenKeyboardAnimationCallbackIsNil {
2244 [viewController setUpKeyboardAnimationVsyncClient:nil];
2248 - (void)testSupportsShowingSystemContextMenuForIOS16AndAbove {
2254 BOOL supportsShowingSystemContextMenu = [viewController supportsShowingSystemContextMenu];
2255 if (@available(iOS 16.0, *)) {
2256 XCTAssertTrue(supportsShowingSystemContextMenu);
2258 XCTAssertFalse(supportsShowingSystemContextMenu);