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"
20 #import "flutter/shell/platform/embedder/embedder.h"
21 #import "flutter/third_party/spring_animation/spring_animation.h"
29 - (void)sendKeyEvent:(const FlutterKeyEvent&)event
30 callback:(nullable FlutterKeyEventCallback)callback
31 userData:(nullable
void*)userData;
32 - (fml::RefPtr<fml::TaskRunner>)uiTaskRunner;
42 @property(nonatomic, assign) BOOL didCallNotifyLowMemory;
44 - (void)sendKeyEvent:(const FlutterKeyEvent&)event
45 callback:(nullable FlutterKeyEventCallback)callback
46 userData:(nullable
void*)userData;
55 - (void)notifyLowMemory {
56 _didCallNotifyLowMemory = YES;
59 - (void)sendKeyEvent:(const FlutterKeyEvent&)event
60 callback:(FlutterKeyEventCallback)callback
61 userData:(
void*)userData API_AVAILABLE(ios(9.0)) {
62 if (callback == nil) {
68 CFRunLoopPerformBlock(CFRunLoopGetCurrent(), fml::MessageLoopDarwin::kMessageLoopCFRunLoopMode,
70 callback(
true, userData);
76 - (BOOL)createShell:(NSString*)entrypoint
77 libraryURI:(NSString*)libraryURI
78 initialRoute:(NSString*)initialRoute;
79 - (void)dispatchPointerDataPacket:(std::unique_ptr<
flutter::PointerDataPacket>)packet;
80 - (void)updateViewportMetrics:(
flutter::ViewportMetrics)viewportMetrics;
112 @property(nonatomic, retain, readonly)
127 - (void)surfaceUpdated:(BOOL)appeared;
128 - (void)performOrientationUpdate:(UIInterfaceOrientationMask)new_preferences;
130 nextAction:(
void (^)())next API_AVAILABLE(ios(13.4));
131 - (void)discreteScrollEvent:(UIPanGestureRecognizer*)recognizer;
133 - (void)onUserSettingsChanged:(NSNotification*)notification;
134 - (void)applicationWillTerminate:(NSNotification*)notification;
135 - (void)goToApplicationLifecycle:(nonnull NSString*)state;
136 - (void)handleKeyboardNotification:(NSNotification*)notification;
137 - (CGFloat)calculateKeyboardInset:(CGRect)keyboardFrame keyboardMode:(
int)keyboardMode;
138 - (BOOL)shouldIgnoreKeyboardNotification:(NSNotification*)notification;
139 - (FlutterKeyboardMode)calculateKeyboardAttachMode:(NSNotification*)notification;
140 - (CGFloat)calculateMultitaskingAdjustment:(CGRect)screenRect keyboardFrame:(CGRect)keyboardFrame;
141 - (void)startKeyBoardAnimation:(NSTimeInterval)duration;
144 - (void)setUpKeyboardSpringAnimationIfNeeded:(CAAnimation*)keyboardAnimation;
145 - (void)setUpKeyboardAnimationVsyncClient:
150 - (
flutter::PointerData)generatePointerDataForFake;
152 initialRoute:(nullable NSString*)initialRoute;
153 - (void)applicationBecameActive:(NSNotification*)notification;
154 - (void)applicationWillResignActive:(NSNotification*)notification;
155 - (void)applicationWillTerminate:(NSNotification*)notification;
156 - (void)applicationDidEnterBackground:(NSNotification*)notification;
157 - (void)applicationWillEnterForeground:(NSNotification*)notification;
158 - (void)sceneBecameActive:(NSNotification*)notification API_AVAILABLE(ios(13.0));
159 - (void)sceneWillResignActive:(NSNotification*)notification API_AVAILABLE(ios(13.0));
160 - (void)sceneWillDisconnect:(NSNotification*)notification API_AVAILABLE(ios(13.0));
161 - (void)sceneDidEnterBackground:(NSNotification*)notification API_AVAILABLE(ios(13.0));
162 - (void)sceneWillEnterForeground:(NSNotification*)notification API_AVAILABLE(ios(13.0));
166 @property(nonatomic, strong)
id mockEngine;
167 @property(nonatomic, strong)
id mockTextInputPlugin;
168 @property(nonatomic, strong)
id messageSent;
169 - (void)sendMessage:(
id _Nullable)message reply:(
FlutterReply _Nullable)callback;
178 self.messageSent = nil;
184 [
self.mockEngine stopMocking];
185 self.mockEngine = nil;
186 self.mockTextInputPlugin = nil;
187 self.messageSent = nil;
190 - (
id)setUpMockScreen {
191 UIScreen* mockScreen = OCMClassMock([UIScreen
class]);
193 CGRect screenBounds = CGRectMake(0, 0, 1170, 2532);
194 OCMStub([mockScreen bounds]).andReturn(screenBounds);
195 CGFloat screenScale = 1;
196 OCMStub([mockScreen scale]).andReturn(screenScale);
202 screen:(UIScreen*)screen
203 viewFrame:(CGRect)viewFrame
204 convertedFrame:(CGRect)convertedFrame {
205 OCMStub([viewControllerMock flutterScreenIfViewLoaded]).andReturn(screen);
206 id mockView = OCMClassMock([UIView
class]);
207 OCMStub([mockView frame]).andReturn(viewFrame);
208 OCMStub([mockView convertRect:viewFrame toCoordinateSpace:[OCMArg any]])
209 .andReturn(convertedFrame);
210 OCMStub([viewControllerMock viewIfLoaded]).andReturn(mockView);
215 - (void)testViewDidLoadWillInvokeCreateTouchRateCorrectionVSyncClient {
222 [viewControllerMock loadView];
223 [viewControllerMock viewDidLoad];
224 OCMVerify([viewControllerMock createTouchRateCorrectionVSyncClientIfNeeded]);
227 - (void)testStartKeyboardAnimationWillInvokeSetupKeyboardSpringAnimationIfNeeded {
234 viewControllerMock.targetViewInsetBottom = 100;
235 [viewControllerMock startKeyBoardAnimation:0.25];
237 CAAnimation* keyboardAnimation =
238 [[viewControllerMock keyboardAnimationView].layer animationForKey:@"position"];
240 OCMVerify([viewControllerMock setUpKeyboardSpringAnimationIfNeeded:keyboardAnimation]);
243 - (void)testSetupKeyboardSpringAnimationIfNeeded {
250 UIScreen* screen = [
self setUpMockScreen];
251 CGRect viewFrame = screen.bounds;
252 [
self setUpMockView:viewControllerMock
255 convertedFrame:viewFrame];
258 [viewControllerMock setUpKeyboardSpringAnimationIfNeeded:nil];
259 SpringAnimation* keyboardSpringAnimation = [viewControllerMock keyboardSpringAnimation];
260 XCTAssertTrue(keyboardSpringAnimation == nil);
263 CABasicAnimation* nonSpringAnimation = [CABasicAnimation animation];
264 nonSpringAnimation.duration = 1.0;
265 nonSpringAnimation.fromValue = [NSNumber numberWithFloat:0.0];
266 nonSpringAnimation.toValue = [NSNumber numberWithFloat:1.0];
267 nonSpringAnimation.keyPath =
@"position";
268 [viewControllerMock setUpKeyboardSpringAnimationIfNeeded:nonSpringAnimation];
269 keyboardSpringAnimation = [viewControllerMock keyboardSpringAnimation];
271 XCTAssertTrue(keyboardSpringAnimation == nil);
274 CASpringAnimation* springAnimation = [CASpringAnimation animation];
275 springAnimation.mass = 1.0;
276 springAnimation.stiffness = 100.0;
277 springAnimation.damping = 10.0;
278 springAnimation.keyPath =
@"position";
279 springAnimation.fromValue = [NSValue valueWithCGPoint:CGPointMake(0, 0)];
280 springAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(100, 100)];
281 [viewControllerMock setUpKeyboardSpringAnimationIfNeeded:springAnimation];
282 keyboardSpringAnimation = [viewControllerMock keyboardSpringAnimation];
283 XCTAssertTrue(keyboardSpringAnimation != nil);
286 - (void)testKeyboardAnimationIsShowingAndCompounding {
293 UIScreen* screen = [
self setUpMockScreen];
294 CGRect viewFrame = screen.bounds;
295 [
self setUpMockView:viewControllerMock
298 convertedFrame:viewFrame];
301 CGFloat screenHeight = screen.bounds.size.height;
302 CGFloat screenWidth = screen.bounds.size.height;
305 CGRect initialShowKeyboardBeginFrame = CGRectMake(0, screenHeight, screenWidth, 250);
306 CGRect initialShowKeyboardEndFrame = CGRectMake(0, screenHeight - 250, screenWidth, 500);
307 NSNotification* fakeNotification = [NSNotification
308 notificationWithName:UIKeyboardWillChangeFrameNotification
311 @"UIKeyboardFrameBeginUserInfoKey" : @(initialShowKeyboardBeginFrame),
312 @"UIKeyboardFrameEndUserInfoKey" : @(initialShowKeyboardEndFrame),
313 @"UIKeyboardAnimationDurationUserInfoKey" : @(0.25),
314 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
316 viewControllerMock.targetViewInsetBottom = 0;
317 [viewControllerMock handleKeyboardNotification:fakeNotification];
318 BOOL isShowingAnimation1 = viewControllerMock.keyboardAnimationIsShowing;
319 XCTAssertTrue(isShowingAnimation1);
322 CGRect compoundingShowKeyboardBeginFrame = CGRectMake(0, screenHeight - 250, screenWidth, 250);
323 CGRect compoundingShowKeyboardEndFrame = CGRectMake(0, screenHeight - 500, screenWidth, 500);
324 fakeNotification = [NSNotification
325 notificationWithName:UIKeyboardWillChangeFrameNotification
328 @"UIKeyboardFrameBeginUserInfoKey" : @(compoundingShowKeyboardBeginFrame),
329 @"UIKeyboardFrameEndUserInfoKey" : @(compoundingShowKeyboardEndFrame),
330 @"UIKeyboardAnimationDurationUserInfoKey" : @(0.25),
331 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
334 [viewControllerMock handleKeyboardNotification:fakeNotification];
335 BOOL isShowingAnimation2 = viewControllerMock.keyboardAnimationIsShowing;
336 XCTAssertTrue(isShowingAnimation2);
337 XCTAssertTrue(isShowingAnimation1 == isShowingAnimation2);
340 CGRect initialHideKeyboardBeginFrame = CGRectMake(0, screenHeight - 500, screenWidth, 250);
341 CGRect initialHideKeyboardEndFrame = CGRectMake(0, screenHeight - 250, screenWidth, 500);
342 fakeNotification = [NSNotification
343 notificationWithName:UIKeyboardWillChangeFrameNotification
346 @"UIKeyboardFrameBeginUserInfoKey" : @(initialHideKeyboardBeginFrame),
347 @"UIKeyboardFrameEndUserInfoKey" : @(initialHideKeyboardEndFrame),
348 @"UIKeyboardAnimationDurationUserInfoKey" : @(0.25),
349 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
352 [viewControllerMock handleKeyboardNotification:fakeNotification];
353 BOOL isShowingAnimation3 = viewControllerMock.keyboardAnimationIsShowing;
354 XCTAssertFalse(isShowingAnimation3);
355 XCTAssertTrue(isShowingAnimation2 != isShowingAnimation3);
358 CGRect compoundingHideKeyboardBeginFrame = CGRectMake(0, screenHeight - 250, screenWidth, 250);
359 CGRect compoundingHideKeyboardEndFrame = CGRectMake(0, screenHeight, screenWidth, 500);
360 fakeNotification = [NSNotification
361 notificationWithName:UIKeyboardWillChangeFrameNotification
364 @"UIKeyboardFrameBeginUserInfoKey" : @(compoundingHideKeyboardBeginFrame),
365 @"UIKeyboardFrameEndUserInfoKey" : @(compoundingHideKeyboardEndFrame),
366 @"UIKeyboardAnimationDurationUserInfoKey" : @(0.25),
367 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
370 [viewControllerMock handleKeyboardNotification:fakeNotification];
371 BOOL isShowingAnimation4 = viewControllerMock.keyboardAnimationIsShowing;
372 XCTAssertFalse(isShowingAnimation4);
373 XCTAssertTrue(isShowingAnimation3 == isShowingAnimation4);
376 - (void)testShouldIgnoreKeyboardNotification {
378 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
383 UIScreen* screen = [
self setUpMockScreen];
384 CGRect viewFrame = screen.bounds;
385 [
self setUpMockView:viewControllerMock
388 convertedFrame:viewFrame];
390 CGFloat screenWidth = screen.bounds.size.width;
391 CGFloat screenHeight = screen.bounds.size.height;
392 CGRect emptyKeyboard = CGRectZero;
393 CGRect zeroHeightKeyboard = CGRectMake(0, 0, screenWidth, 0);
394 CGRect validKeyboardEndFrame = CGRectMake(0, screenHeight - 320, screenWidth, 320);
398 NSNotification* notification =
399 [NSNotification notificationWithName:UIKeyboardWillHideNotification
402 @"UIKeyboardFrameEndUserInfoKey" : @(validKeyboardEndFrame),
403 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
404 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
407 BOOL shouldIgnore = [viewControllerMock shouldIgnoreKeyboardNotification:notification];
408 XCTAssertTrue(shouldIgnore == NO);
412 notification = [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
415 @"UIKeyboardFrameEndUserInfoKey" : @(emptyKeyboard),
416 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
417 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
419 shouldIgnore = [viewControllerMock shouldIgnoreKeyboardNotification:notification];
420 XCTAssertTrue(shouldIgnore == YES);
425 [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
428 @"UIKeyboardFrameEndUserInfoKey" : @(zeroHeightKeyboard),
429 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
430 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
432 shouldIgnore = [viewControllerMock shouldIgnoreKeyboardNotification:notification];
433 XCTAssertTrue(shouldIgnore == NO);
438 [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
441 @"UIKeyboardFrameEndUserInfoKey" : @(validKeyboardEndFrame),
442 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
443 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
445 shouldIgnore = [viewControllerMock shouldIgnoreKeyboardNotification:notification];
446 XCTAssertTrue(shouldIgnore == YES);
451 [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
454 @"UIKeyboardFrameEndUserInfoKey" : @(validKeyboardEndFrame),
455 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
456 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
458 shouldIgnore = [viewControllerMock shouldIgnoreKeyboardNotification:notification];
459 XCTAssertTrue(shouldIgnore == NO);
461 if (@available(iOS 13.0, *)) {
465 OCMStub([viewControllerMock isKeyboardInOrTransitioningFromBackground]).andReturn(YES);
469 [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
472 @"UIKeyboardFrameEndUserInfoKey" : @(validKeyboardEndFrame),
473 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
474 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
476 shouldIgnore = [viewControllerMock shouldIgnoreKeyboardNotification:notification];
477 XCTAssertTrue(shouldIgnore == YES);
480 - (void)testKeyboardAnimationWillNotCrashWhenEngineDestroyed {
486 [viewController setUpKeyboardAnimationVsyncClient:^(fml::TimePoint){
491 - (void)testKeyboardAnimationWillWaitUIThreadVsync {
504 const int delayTime = 1;
505 [engine uiTaskRunner]->PostTask([] { sleep(delayTime); });
506 XCTestExpectation* expectation = [
self expectationWithDescription:@"keyboard animation callback"];
508 __block CFTimeInterval fulfillTime;
510 fulfillTime = CACurrentMediaTime();
511 [expectation fulfill];
513 CFTimeInterval startTime = CACurrentMediaTime();
514 [viewController setUpKeyboardAnimationVsyncClient:callback];
515 [
self waitForExpectationsWithTimeout:5.0 handler:nil];
516 XCTAssertTrue(fulfillTime - startTime > delayTime);
519 - (void)testCalculateKeyboardAttachMode {
521 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
527 UIScreen* screen = [
self setUpMockScreen];
528 CGRect viewFrame = screen.bounds;
529 [
self setUpMockView:viewControllerMock
532 convertedFrame:viewFrame];
534 CGFloat screenWidth = screen.bounds.size.width;
535 CGFloat screenHeight = screen.bounds.size.height;
538 CGRect keyboardFrame = CGRectZero;
539 NSNotification* notification =
540 [NSNotification notificationWithName:UIKeyboardWillHideNotification
543 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
544 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
545 @"UIKeyboardIsLocalUserInfoKey" : @(YES)
547 FlutterKeyboardMode keyboardMode = [viewControllerMock calculateKeyboardAttachMode:notification];
548 XCTAssertTrue(keyboardMode == FlutterKeyboardModeHidden);
551 keyboardFrame = CGRectZero;
552 notification = [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
555 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
556 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
557 @"UIKeyboardIsLocalUserInfoKey" : @(YES)
559 keyboardMode = [viewControllerMock calculateKeyboardAttachMode:notification];
560 XCTAssertTrue(keyboardMode == FlutterKeyboardModeFloating);
563 keyboardFrame = CGRectMake(0, 0, screenWidth, 0);
564 notification = [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
567 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
568 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
569 @"UIKeyboardIsLocalUserInfoKey" : @(YES)
571 keyboardMode = [viewControllerMock calculateKeyboardAttachMode:notification];
572 XCTAssertTrue(keyboardMode == FlutterKeyboardModeHidden);
575 keyboardFrame = CGRectMake(0, 0, 320, 320);
576 notification = [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
579 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
580 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
581 @"UIKeyboardIsLocalUserInfoKey" : @(YES)
583 keyboardMode = [viewControllerMock calculateKeyboardAttachMode:notification];
584 XCTAssertTrue(keyboardMode == FlutterKeyboardModeFloating);
587 keyboardFrame = CGRectMake(0, 0, screenWidth, 320);
588 notification = [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
591 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
592 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
593 @"UIKeyboardIsLocalUserInfoKey" : @(YES)
595 keyboardMode = [viewControllerMock calculateKeyboardAttachMode:notification];
596 XCTAssertTrue(keyboardMode == FlutterKeyboardModeFloating);
599 keyboardFrame = CGRectMake(0, screenHeight - 320, screenWidth, 320);
600 notification = [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
603 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
604 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
605 @"UIKeyboardIsLocalUserInfoKey" : @(YES)
607 keyboardMode = [viewControllerMock calculateKeyboardAttachMode:notification];
608 XCTAssertTrue(keyboardMode == FlutterKeyboardModeDocked);
611 CGFloat longDecimalHeight = 320.666666666666666;
612 keyboardFrame = CGRectMake(0, screenHeight - longDecimalHeight, screenWidth, longDecimalHeight);
613 notification = [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
616 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
617 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
618 @"UIKeyboardIsLocalUserInfoKey" : @(YES)
620 keyboardMode = [viewControllerMock calculateKeyboardAttachMode:notification];
621 XCTAssertTrue(keyboardMode == FlutterKeyboardModeDocked);
624 keyboardFrame = CGRectMake(0, screenHeight - .0000001, screenWidth, longDecimalHeight);
625 notification = [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
628 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
629 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
630 @"UIKeyboardIsLocalUserInfoKey" : @(YES)
632 keyboardMode = [viewControllerMock calculateKeyboardAttachMode:notification];
633 XCTAssertTrue(keyboardMode == FlutterKeyboardModeHidden);
636 keyboardFrame = CGRectMake(0, screenHeight, screenWidth, 320);
637 notification = [NSNotification notificationWithName:UIKeyboardWillChangeFrameNotification
640 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
641 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
642 @"UIKeyboardIsLocalUserInfoKey" : @(YES)
644 keyboardMode = [viewControllerMock calculateKeyboardAttachMode:notification];
645 XCTAssertTrue(keyboardMode == FlutterKeyboardModeHidden);
648 - (void)testCalculateMultitaskingAdjustment {
650 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
656 UIScreen* screen = [
self setUpMockScreen];
657 CGFloat screenWidth = screen.bounds.size.width;
658 CGFloat screenHeight = screen.bounds.size.height;
659 CGRect screenRect = screen.bounds;
660 CGRect viewOrigFrame = CGRectMake(0, 0, 320, screenHeight - 40);
661 CGRect convertedViewFrame = CGRectMake(20, 20, 320, screenHeight - 40);
662 CGRect keyboardFrame = CGRectMake(20, screenHeight - 320, screenWidth, 300);
663 id mockView = [
self setUpMockView:viewControllerMock
665 viewFrame:viewOrigFrame
666 convertedFrame:convertedViewFrame];
667 id mockTraitCollection = OCMClassMock([UITraitCollection
class]);
668 OCMStub([mockTraitCollection userInterfaceIdiom]).andReturn(UIUserInterfaceIdiomPad);
669 OCMStub([mockTraitCollection horizontalSizeClass]).andReturn(UIUserInterfaceSizeClassCompact);
670 OCMStub([mockTraitCollection verticalSizeClass]).andReturn(UIUserInterfaceSizeClassRegular);
671 OCMStub([mockView traitCollection]).andReturn(mockTraitCollection);
673 CGFloat adjustment = [viewControllerMock calculateMultitaskingAdjustment:screenRect
674 keyboardFrame:keyboardFrame];
675 XCTAssertTrue(adjustment == 20);
678 - (void)testCalculateKeyboardInset {
680 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
685 UIScreen* screen = [
self setUpMockScreen];
686 OCMStub([viewControllerMock flutterScreenIfViewLoaded]).andReturn(screen);
688 CGFloat screenWidth = screen.bounds.size.width;
689 CGFloat screenHeight = screen.bounds.size.height;
690 CGRect viewOrigFrame = CGRectMake(0, 0, 320, screenHeight - 40);
691 CGRect convertedViewFrame = CGRectMake(20, 20, 320, screenHeight - 40);
692 CGRect keyboardFrame = CGRectMake(20, screenHeight - 320, screenWidth, 300);
694 [
self setUpMockView:viewControllerMock
696 viewFrame:viewOrigFrame
697 convertedFrame:convertedViewFrame];
699 CGFloat inset = [viewControllerMock calculateKeyboardInset:keyboardFrame
700 keyboardMode:FlutterKeyboardModeDocked];
701 XCTAssertTrue(inset == 300 * screen.scale);
704 - (void)testHandleKeyboardNotification {
711 UIScreen* screen = [
self setUpMockScreen];
712 CGFloat screenWidth = screen.bounds.size.width;
713 CGFloat screenHeight = screen.bounds.size.height;
714 CGRect keyboardFrame = CGRectMake(0, screenHeight - 320, screenWidth, 320);
715 CGRect viewFrame = screen.bounds;
717 NSNotification* notification =
718 [NSNotification notificationWithName:UIKeyboardWillShowNotification
721 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
722 @"UIKeyboardAnimationDurationUserInfoKey" : @0.25,
723 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
726 [
self setUpMockView:viewControllerMock
729 convertedFrame:viewFrame];
730 viewControllerMock.targetViewInsetBottom = 0;
731 XCTestExpectation* expectation = [
self expectationWithDescription:@"update viewport"];
732 OCMStub([viewControllerMock updateViewportMetricsIfNeeded]).andDo(^(NSInvocation* invocation) {
733 [expectation fulfill];
736 [viewControllerMock handleKeyboardNotification:notification];
737 XCTAssertTrue(viewControllerMock.targetViewInsetBottom == 320 * screen.scale);
738 OCMVerify([viewControllerMock startKeyBoardAnimation:0.25]);
739 [
self waitForExpectationsWithTimeout:5.0 handler:nil];
742 - (void)testEnsureBottomInsetIsZeroWhenKeyboardDismissed {
744 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
750 CGRect keyboardFrame = CGRectZero;
752 NSNotification* fakeNotification =
753 [NSNotification notificationWithName:UIKeyboardWillHideNotification
756 @"UIKeyboardFrameEndUserInfoKey" : @(keyboardFrame),
757 @"UIKeyboardAnimationDurationUserInfoKey" : @(0.25),
758 @"UIKeyboardIsLocalUserInfoKey" : @(isLocal)
761 viewControllerMock.targetViewInsetBottom = 10;
762 [viewControllerMock handleKeyboardNotification:fakeNotification];
763 XCTAssertTrue(viewControllerMock.targetViewInsetBottom == 0);
766 - (void)testEnsureViewportMetricsWillInvokeAndDisplayLinkWillInvalidateInViewDidDisappear {
768 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
773 [viewControllerMock viewDidDisappear:YES];
774 OCMVerify([viewControllerMock ensureViewportMetricsIsCorrect]);
775 OCMVerify([viewControllerMock invalidateKeyboardAnimationVSyncClient]);
778 - (void)testViewDidDisappearDoesntPauseEngineWhenNotTheViewController {
781 mockEngine.lifecycleChannel = lifecycleChannel;
786 id viewControllerMock = OCMPartialMock(viewControllerA);
787 OCMStub([viewControllerMock surfaceUpdated:NO]);
789 [viewControllerA viewDidDisappear:NO];
790 OCMReject([lifecycleChannel sendMessage:
@"AppLifecycleState.paused"]);
791 OCMReject([viewControllerMock surfaceUpdated:[OCMArg any]]);
794 - (void)testAppWillTerminateViewDidDestroyTheEngine {
796 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
801 OCMStub([viewControllerMock goToApplicationLifecycle:
@"AppLifecycleState.detached"]);
803 [viewController applicationWillTerminate:nil];
804 OCMVerify([viewControllerMock goToApplicationLifecycle:
@"AppLifecycleState.detached"]);
808 - (void)testViewDidDisappearDoesPauseEngineWhenIsTheViewController {
811 mockEngine.lifecycleChannel = lifecycleChannel;
819 OCMStub([viewControllerMock surfaceUpdated:NO]);
820 [viewController viewDidDisappear:NO];
821 OCMVerify([lifecycleChannel sendMessage:
@"AppLifecycleState.paused"]);
822 OCMVerify([viewControllerMock surfaceUpdated:NO]);
824 XCTAssertNil(weakViewController);
828 testEngineConfigSyncMethodWillExecuteWhenViewControllerInEngineIsCurrentViewControllerInViewWillAppear {
830 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
834 [viewController viewWillAppear:YES];
839 testEngineConfigSyncMethodWillNotExecuteWhenViewControllerInEngineIsNotCurrentViewControllerInViewWillAppear {
841 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
851 [viewControllerA viewWillAppear:YES];
852 OCMVerify(never(), [viewControllerA onUserSettingsChanged:nil]);
856 testEngineConfigSyncMethodWillExecuteWhenViewControllerInEngineIsCurrentViewControllerInViewDidAppear {
858 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
862 [viewController viewDidAppear:YES];
867 testEngineConfigSyncMethodWillNotExecuteWhenViewControllerInEngineIsNotCurrentViewControllerInViewDidAppear {
869 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
879 [viewControllerA viewDidAppear:YES];
880 OCMVerify(never(), [viewControllerA onUserSettingsChanged:nil]);
884 testEngineConfigSyncMethodWillExecuteWhenViewControllerInEngineIsCurrentViewControllerInViewWillDisappear {
887 mockEngine.lifecycleChannel = lifecycleChannel;
892 [viewController viewWillDisappear:NO];
893 OCMVerify([lifecycleChannel sendMessage:
@"AppLifecycleState.inactive"]);
897 testEngineConfigSyncMethodWillNotExecuteWhenViewControllerInEngineIsNotCurrentViewControllerInViewWillDisappear {
900 mockEngine.lifecycleChannel = lifecycleChannel;
908 [viewControllerA viewDidDisappear:NO];
909 OCMReject([lifecycleChannel sendMessage:
@"AppLifecycleState.inactive"]);
912 - (void)testUpdateViewportMetricsIfNeeded_DoesntInvokeEngineWhenNotTheViewController {
914 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
923 [viewControllerA updateViewportMetricsIfNeeded];
924 flutter::ViewportMetrics viewportMetrics;
925 OCMVerify(never(), [
mockEngine updateViewportMetrics:viewportMetrics]);
928 - (void)testUpdateViewportMetricsIfNeeded_DoesInvokeEngineWhenIsTheViewController {
930 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
935 flutter::ViewportMetrics viewportMetrics;
936 OCMExpect([
mockEngine updateViewportMetrics:viewportMetrics]).ignoringNonObjectArgs();
937 [viewController updateViewportMetricsIfNeeded];
941 - (void)testUpdateViewportMetricsIfNeeded_DoesNotInvokeEngineWhenShouldBeIgnoredDuringRotation {
943 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
948 UIScreen* screen = [
self setUpMockScreen];
949 OCMStub([viewControllerMock flutterScreenIfViewLoaded]).andReturn(screen);
952 id mockCoordinator = OCMProtocolMock(
@protocol(UIViewControllerTransitionCoordinator));
953 OCMStub([mockCoordinator transitionDuration]).andReturn(0.5);
956 [viewController viewWillTransitionToSize:CGSizeZero withTransitionCoordinator:mockCoordinator];
958 [viewController updateViewportMetricsIfNeeded];
960 OCMVerify(never(), [
mockEngine updateViewportMetrics:flutter::ViewportMetrics()]);
963 - (void)testViewWillTransitionToSize_DoesDelayEngineCallIfNonZeroDuration {
965 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
970 UIScreen* screen = [
self setUpMockScreen];
971 OCMStub([viewControllerMock flutterScreenIfViewLoaded]).andReturn(screen);
975 NSTimeInterval transitionDuration = 0.5;
976 id mockCoordinator = OCMProtocolMock(
@protocol(UIViewControllerTransitionCoordinator));
977 OCMStub([mockCoordinator transitionDuration]).andReturn(transitionDuration);
979 flutter::ViewportMetrics viewportMetrics;
980 OCMExpect([
mockEngine updateViewportMetrics:viewportMetrics]).ignoringNonObjectArgs();
982 [viewController viewWillTransitionToSize:CGSizeZero withTransitionCoordinator:mockCoordinator];
984 [viewController updateViewportMetricsIfNeeded];
985 OCMVerify(never(), [
mockEngine updateViewportMetrics:flutter::ViewportMetrics()]);
989 XCTWaiterResult result = [XCTWaiter
990 waitForExpectations:@[ [
self expectationWithDescription:@"Waiting for rotation duration"] ]
991 timeout:transitionDuration];
992 XCTAssertEqual(result, XCTWaiterResultTimedOut);
997 - (void)testViewWillTransitionToSize_DoesNotDelayEngineCallIfZeroDuration {
999 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1004 UIScreen* screen = [
self setUpMockScreen];
1005 OCMStub([viewControllerMock flutterScreenIfViewLoaded]).andReturn(screen);
1009 id mockCoordinator = OCMProtocolMock(
@protocol(UIViewControllerTransitionCoordinator));
1010 OCMStub([mockCoordinator transitionDuration]).andReturn(0);
1012 flutter::ViewportMetrics viewportMetrics;
1013 OCMExpect([
mockEngine updateViewportMetrics:viewportMetrics]).ignoringNonObjectArgs();
1016 [viewController viewWillTransitionToSize:CGSizeZero withTransitionCoordinator:mockCoordinator];
1017 [viewController updateViewportMetricsIfNeeded];
1022 - (void)testViewDidLoadDoesntInvokeEngineWhenNotTheViewController {
1024 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1033 UIView* view = viewControllerA.view;
1034 XCTAssertNotNil(view);
1038 - (void)testViewDidLoadDoesInvokeEngineWhenIsTheViewController {
1040 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1047 XCTAssertNotNil(view);
1048 OCMVerify(times(1), [
mockEngine attachView]);
1051 - (void)testViewDidLoadDoesntInvokeEngineAttachViewWhenEngineNeedsLaunch {
1053 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1059 [viewController sharedSetupWithProject:nil initialRoute:nil];
1062 XCTAssertNotNil(view);
1066 - (void)testSplashScreenViewRemoveNotCrash {
1071 [flutterViewController setSplashScreenView:[[UIView alloc] init]];
1072 [flutterViewController setSplashScreenView:nil];
1075 - (void)testInternalPluginsWeakPtrNotCrash {
1081 [vc addInternalPlugins];
1084 [(NSArray<id<FlutterKeyPrimaryResponder>>*)keyboardManager.primaryResponders firstObject];
1085 sendEvent = [keyPrimaryResponder sendEvent];
1089 sendEvent({}, nil, nil);
1094 - (void)testInternalPluginsInvokeInViewDidLoad {
1096 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1104 XCTAssertNotNil(view);
1105 [viewController viewDidLoad];
1109 - (void)testBinaryMessenger {
1113 XCTAssertNotNil(vc);
1115 OCMStub([
self.
mockEngine binaryMessenger]).andReturn(messenger);
1117 OCMVerify([
self.
mockEngine binaryMessenger]);
1120 - (void)testViewControllerIsReleased {
1125 [viewController viewDidLoad];
1127 XCTAssertNil(weakViewController);
1130 #pragma mark - Platform Brightness
1132 - (void)testItReportsLightPlatformBrightnessByDefault {
1135 OCMStub([
self.
mockEngine settingsChannel]).andReturn(settingsChannel);
1142 [vc traitCollectionDidChange:nil];
1145 OCMVerify([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(
id message) {
1146 return [message[@"platformBrightness"] isEqualToString:@"light"];
1150 [settingsChannel stopMocking];
1153 - (void)testItReportsPlatformBrightnessWhenViewWillAppear {
1157 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1158 OCMStub([
mockEngine settingsChannel]).andReturn(settingsChannel);
1164 [vc viewWillAppear:false];
1167 OCMVerify([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(
id message) {
1168 return [message[@"platformBrightness"] isEqualToString:@"light"];
1172 [settingsChannel stopMocking];
1175 - (void)testItReportsDarkPlatformBrightnessWhenTraitCollectionRequestsIt {
1176 if (@available(iOS 13, *)) {
1184 OCMStub([
self.
mockEngine settingsChannel]).andReturn(settingsChannel);
1185 id mockTraitCollection =
1186 [
self fakeTraitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleDark];
1195 OCMStub([partialMockVC traitCollection]).andReturn(mockTraitCollection);
1198 [partialMockVC traitCollectionDidChange:nil];
1201 OCMVerify([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(
id message) {
1202 return [message[@"platformBrightness"] isEqualToString:@"dark"];
1206 [partialMockVC stopMocking];
1207 [settingsChannel stopMocking];
1208 [mockTraitCollection stopMocking];
1213 - (UITraitCollection*)fakeTraitCollectionWithUserInterfaceStyle:(UIUserInterfaceStyle)style {
1214 id mockTraitCollection = OCMClassMock([UITraitCollection
class]);
1215 OCMStub([mockTraitCollection userInterfaceStyle]).andReturn(style);
1216 return mockTraitCollection;
1219 #pragma mark - Platform Contrast
1221 - (void)testItReportsNormalPlatformContrastByDefault {
1222 if (@available(iOS 13, *)) {
1230 OCMStub([
self.
mockEngine settingsChannel]).andReturn(settingsChannel);
1237 [vc traitCollectionDidChange:nil];
1240 OCMVerify([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(
id message) {
1241 return [message[@"platformContrast"] isEqualToString:@"normal"];
1245 [settingsChannel stopMocking];
1248 - (void)testItReportsPlatformContrastWhenViewWillAppear {
1249 if (@available(iOS 13, *)) {
1255 [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil];
1259 OCMStub([
mockEngine settingsChannel]).andReturn(settingsChannel);
1265 [vc viewWillAppear:false];
1268 OCMVerify([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(
id message) {
1269 return [message[@"platformContrast"] isEqualToString:@"normal"];
1273 [settingsChannel stopMocking];
1276 - (void)testItReportsHighContrastWhenTraitCollectionRequestsIt {
1277 if (@available(iOS 13, *)) {
1285 OCMStub([
self.
mockEngine settingsChannel]).andReturn(settingsChannel);
1287 id mockTraitCollection = [
self fakeTraitCollectionWithContrast:UIAccessibilityContrastHigh];
1296 OCMStub([partialMockVC traitCollection]).andReturn(mockTraitCollection);
1299 [partialMockVC traitCollectionDidChange:mockTraitCollection];
1302 OCMVerify([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(
id message) {
1303 return [message[@"platformContrast"] isEqualToString:@"high"];
1307 [partialMockVC stopMocking];
1308 [settingsChannel stopMocking];
1309 [mockTraitCollection stopMocking];
1312 - (void)testItReportsAccessibilityOnOffSwitchLabelsFlagNotSet {
1313 if (@available(iOS 13, *)) {
1323 OCMStub([partialMockViewController accessibilityIsOnOffSwitchLabelsEnabled]).andReturn(NO);
1326 int32_t flags = [partialMockViewController accessibilityFlags];
1329 XCTAssert((flags & (int32_t)flutter::AccessibilityFeatureFlag::kOnOffSwitchLabels) == 0);
1332 - (void)testItReportsAccessibilityOnOffSwitchLabelsFlagSet {
1333 if (@available(iOS 13, *)) {
1343 OCMStub([partialMockViewController accessibilityIsOnOffSwitchLabelsEnabled]).andReturn(YES);
1346 int32_t flags = [partialMockViewController accessibilityFlags];
1349 XCTAssert((flags & (int32_t)flutter::AccessibilityFeatureFlag::kOnOffSwitchLabels) != 0);
1352 - (void)testPerformOrientationUpdateForcesOrientationChange {
1353 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortrait
1354 currentOrientation:UIInterfaceOrientationLandscapeLeft
1355 didChangeOrientation:YES
1356 resultingOrientation:UIInterfaceOrientationPortrait];
1358 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortrait
1359 currentOrientation:UIInterfaceOrientationLandscapeRight
1360 didChangeOrientation:YES
1361 resultingOrientation:UIInterfaceOrientationPortrait];
1363 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortrait
1364 currentOrientation:UIInterfaceOrientationPortraitUpsideDown
1365 didChangeOrientation:YES
1366 resultingOrientation:UIInterfaceOrientationPortrait];
1368 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortraitUpsideDown
1369 currentOrientation:UIInterfaceOrientationLandscapeLeft
1370 didChangeOrientation:YES
1371 resultingOrientation:UIInterfaceOrientationPortraitUpsideDown];
1373 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortraitUpsideDown
1374 currentOrientation:UIInterfaceOrientationLandscapeRight
1375 didChangeOrientation:YES
1376 resultingOrientation:UIInterfaceOrientationPortraitUpsideDown];
1378 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortraitUpsideDown
1379 currentOrientation:UIInterfaceOrientationPortrait
1380 didChangeOrientation:YES
1381 resultingOrientation:UIInterfaceOrientationPortraitUpsideDown];
1383 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscape
1384 currentOrientation:UIInterfaceOrientationPortrait
1385 didChangeOrientation:YES
1386 resultingOrientation:UIInterfaceOrientationLandscapeLeft];
1388 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscape
1389 currentOrientation:UIInterfaceOrientationPortraitUpsideDown
1390 didChangeOrientation:YES
1391 resultingOrientation:UIInterfaceOrientationLandscapeLeft];
1393 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeLeft
1394 currentOrientation:UIInterfaceOrientationPortrait
1395 didChangeOrientation:YES
1396 resultingOrientation:UIInterfaceOrientationLandscapeLeft];
1398 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeLeft
1399 currentOrientation:UIInterfaceOrientationLandscapeRight
1400 didChangeOrientation:YES
1401 resultingOrientation:UIInterfaceOrientationLandscapeLeft];
1403 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeLeft
1404 currentOrientation:UIInterfaceOrientationPortraitUpsideDown
1405 didChangeOrientation:YES
1406 resultingOrientation:UIInterfaceOrientationLandscapeLeft];
1408 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeRight
1409 currentOrientation:UIInterfaceOrientationPortrait
1410 didChangeOrientation:YES
1411 resultingOrientation:UIInterfaceOrientationLandscapeRight];
1413 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeRight
1414 currentOrientation:UIInterfaceOrientationLandscapeLeft
1415 didChangeOrientation:YES
1416 resultingOrientation:UIInterfaceOrientationLandscapeRight];
1418 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeRight
1419 currentOrientation:UIInterfaceOrientationPortraitUpsideDown
1420 didChangeOrientation:YES
1421 resultingOrientation:UIInterfaceOrientationLandscapeRight];
1423 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAllButUpsideDown
1424 currentOrientation:UIInterfaceOrientationPortraitUpsideDown
1425 didChangeOrientation:YES
1426 resultingOrientation:UIInterfaceOrientationPortrait];
1429 - (void)testPerformOrientationUpdateDoesNotForceOrientationChange {
1430 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAll
1431 currentOrientation:UIInterfaceOrientationPortrait
1432 didChangeOrientation:NO
1433 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1435 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAll
1436 currentOrientation:UIInterfaceOrientationPortraitUpsideDown
1437 didChangeOrientation:NO
1438 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1440 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAll
1441 currentOrientation:UIInterfaceOrientationLandscapeLeft
1442 didChangeOrientation:NO
1443 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1445 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAll
1446 currentOrientation:UIInterfaceOrientationLandscapeRight
1447 didChangeOrientation:NO
1448 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1450 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAllButUpsideDown
1451 currentOrientation:UIInterfaceOrientationPortrait
1452 didChangeOrientation:NO
1453 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1455 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAllButUpsideDown
1456 currentOrientation:UIInterfaceOrientationLandscapeLeft
1457 didChangeOrientation:NO
1458 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1460 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAllButUpsideDown
1461 currentOrientation:UIInterfaceOrientationLandscapeRight
1462 didChangeOrientation:NO
1463 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1465 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortrait
1466 currentOrientation:UIInterfaceOrientationPortrait
1467 didChangeOrientation:NO
1468 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1470 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortraitUpsideDown
1471 currentOrientation:UIInterfaceOrientationPortraitUpsideDown
1472 didChangeOrientation:NO
1473 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1475 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscape
1476 currentOrientation:UIInterfaceOrientationLandscapeLeft
1477 didChangeOrientation:NO
1478 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1480 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscape
1481 currentOrientation:UIInterfaceOrientationLandscapeRight
1482 didChangeOrientation:NO
1483 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1485 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeLeft
1486 currentOrientation:UIInterfaceOrientationLandscapeLeft
1487 didChangeOrientation:NO
1488 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1490 [
self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeRight
1491 currentOrientation:UIInterfaceOrientationLandscapeRight
1492 didChangeOrientation:NO
1493 resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
1498 - (void)orientationTestWithOrientationUpdate:(UIInterfaceOrientationMask)mask
1499 currentOrientation:(UIInterfaceOrientation)currentOrientation
1500 didChangeOrientation:(BOOL)didChange
1501 resultingOrientation:(UIInterfaceOrientation)resultingOrientation {
1502 id mockApplication = OCMClassMock([UIApplication
class]);
1506 __block __weak
id weakPreferences;
1512 if (@available(iOS 16.0, *)) {
1513 mockWindowScene = OCMClassMock([UIWindowScene
class]);
1514 mockVC = OCMPartialMock(realVC);
1515 OCMStub([mockVC flutterWindowSceneIfViewLoaded]).andReturn(mockWindowScene);
1516 if (realVC.supportedInterfaceOrientations == mask) {
1517 OCMReject([mockWindowScene requestGeometryUpdateWithPreferences:[OCMArg any]
1518 errorHandler:[OCMArg any]]);
1522 OCMExpect([mockWindowScene
1523 requestGeometryUpdateWithPreferences:[OCMArg checkWithBlock:^BOOL(
1524 UIWindowSceneGeometryPreferencesIOS*
1526 weakPreferences = preferences;
1527 return preferences.interfaceOrientations == mask;
1529 errorHandler:[OCMArg any]]);
1531 OCMStub([mockApplication sharedApplication]).andReturn(mockApplication);
1532 OCMStub([mockApplication connectedScenes]).andReturn([NSSet setWithObject:mockWindowScene]);
1534 deviceMock = OCMPartialMock([UIDevice currentDevice]);
1536 OCMReject([deviceMock setValue:[OCMArg any] forKey:
@"orientation"]);
1538 OCMExpect([deviceMock setValue:@(resultingOrientation) forKey:
@"orientation"]);
1540 if (@available(iOS 13.0, *)) {
1541 mockWindowScene = OCMClassMock([UIWindowScene
class]);
1542 mockVC = OCMPartialMock(realVC);
1543 OCMStub([mockVC flutterWindowSceneIfViewLoaded]).andReturn(mockWindowScene);
1544 OCMStub(((UIWindowScene*)mockWindowScene).interfaceOrientation)
1545 .andReturn(currentOrientation);
1547 OCMStub([mockApplication sharedApplication]).andReturn(mockApplication);
1548 OCMStub([mockApplication statusBarOrientation]).andReturn(currentOrientation);
1552 [realVC performOrientationUpdate:mask];
1553 if (@available(iOS 16.0, *)) {
1554 OCMVerifyAll(mockWindowScene);
1556 OCMVerifyAll(deviceMock);
1559 [mockWindowScene stopMocking];
1560 [deviceMock stopMocking];
1561 [mockApplication stopMocking];
1562 XCTAssertNil(weakPreferences);
1567 - (UITraitCollection*)fakeTraitCollectionWithContrast:(UIAccessibilityContrast)contrast {
1568 id mockTraitCollection = OCMClassMock([UITraitCollection
class]);
1569 OCMStub([mockTraitCollection accessibilityContrast]).andReturn(contrast);
1570 return mockTraitCollection;
1573 - (void)testWillDeallocNotification {
1574 XCTestExpectation* expectation =
1575 [[XCTestExpectation alloc] initWithDescription:@"notification called"];
1582 [[NSNotificationCenter defaultCenter] addObserverForName:FlutterViewControllerWillDealloc
1584 queue:[NSOperationQueue mainQueue]
1585 usingBlock:^(NSNotification* _Nonnull note) {
1586 [expectation fulfill];
1588 XCTAssertNotNil(realVC);
1591 [
self waitForExpectations:@[ expectation ] timeout:1.0];
1594 - (void)testReleasesKeyboardManagerOnDealloc {
1599 [viewController addInternalPlugins];
1601 XCTAssertNotNil(weakKeyboardManager);
1602 [viewController deregisterNotifications];
1606 XCTAssertNil(weakKeyboardManager);
1609 - (void)testDoesntLoadViewInInit {
1612 [engine createShell:@"" libraryURI:@"" initialRoute:nil];
1616 XCTAssertFalse([realVC isViewLoaded],
@"shouldn't have loaded since it hasn't been shown");
1617 engine.viewController = nil;
1620 - (void)testHideOverlay {
1623 [engine createShell:@"" libraryURI:@"" initialRoute:nil];
1627 XCTAssertFalse(realVC.prefersHomeIndicatorAutoHidden,
@"");
1628 [[NSNotificationCenter defaultCenter] postNotificationName:FlutterViewControllerHideHomeIndicator
1630 XCTAssertTrue(realVC.prefersHomeIndicatorAutoHidden,
@"");
1631 engine.viewController = nil;
1634 - (void)testNotifyLowMemory {
1640 OCMStub([viewControllerMock surfaceUpdated:NO]);
1641 [viewController beginAppearanceTransition:NO animated:NO];
1642 [viewController endAppearanceTransition];
1643 XCTAssertTrue(
mockEngine.didCallNotifyLowMemory);
1646 - (void)sendMessage:(
id _Nullable)message reply:(
FlutterReply _Nullable)callback {
1647 NSMutableDictionary* replyMessage = [@{
1652 self.messageSent = message;
1653 CFRunLoopPerformBlock(CFRunLoopGetCurrent(), fml::MessageLoopDarwin::kMessageLoopCFRunLoopMode,
1655 callback(replyMessage);
1660 if (@available(iOS 13.4, *)) {
1667 OCMStub([
mockEngine.keyEventChannel sendMessage:[OCMArg any] reply:[OCMArg any]])
1668 .andCall(
self,
@selector(sendMessage:reply:));
1670 mockEngine.textInputPlugin =
self.mockTextInputPlugin;
1678 [vc addInternalPlugins];
1680 [vc handlePressEvent:keyUpEvent(UIKeyboardHIDUsageKeyboardA, UIKeyModifierShift, 123.0)
1685 XCTAssert([
self.
messageSent[
@"keymap"] isEqualToString:
@"ios"]);
1686 XCTAssert([
self.
messageSent[
@"type"] isEqualToString:
@"keyup"]);
1687 XCTAssert([
self.
messageSent[
@"keyCode"] isEqualToNumber:[NSNumber numberWithInt:4]]);
1688 XCTAssert([
self.
messageSent[
@"modifiers"] isEqualToNumber:[NSNumber numberWithInt:0]]);
1689 XCTAssert([
self.
messageSent[
@"characters"] isEqualToString:
@""]);
1690 XCTAssert([
self.
messageSent[
@"charactersIgnoringModifiers"] isEqualToString:
@""]);
1691 [vc deregisterNotifications];
1695 if (@available(iOS 13.4, *)) {
1703 OCMStub([
mockEngine.keyEventChannel sendMessage:[OCMArg any] reply:[OCMArg any]])
1704 .andCall(
self,
@selector(sendMessage:reply:));
1706 mockEngine.textInputPlugin =
self.mockTextInputPlugin;
1713 [vc addInternalPlugins];
1715 [vc handlePressEvent:keyDownEvent(UIKeyboardHIDUsageKeyboardA, UIKeyModifierShift, 123.0f, "A",
1721 XCTAssert([
self.
messageSent[
@"keymap"] isEqualToString:
@"ios"]);
1722 XCTAssert([
self.
messageSent[
@"type"] isEqualToString:
@"keydown"]);
1723 XCTAssert([
self.
messageSent[
@"keyCode"] isEqualToNumber:[NSNumber numberWithInt:4]]);
1724 XCTAssert([
self.
messageSent[
@"modifiers"] isEqualToNumber:[NSNumber numberWithInt:0]]);
1725 XCTAssert([
self.
messageSent[
@"characters"] isEqualToString:
@"A"]);
1726 XCTAssert([
self.
messageSent[
@"charactersIgnoringModifiers"] isEqualToString:
@"a"]);
1727 [vc deregisterNotifications];
1732 if (@available(iOS 13.4, *)) {
1738 OCMStub([keyEventChannel sendMessage:[OCMArg any] reply:[OCMArg any]])
1739 .andCall(
self,
@selector(sendMessage:reply:));
1741 OCMStub([
self.
mockEngine keyEventChannel]).andReturn(keyEventChannel);
1749 [vc addInternalPlugins];
1751 [vc handlePressEvent:keyEventWithPhase(UIPressPhaseStationary, UIKeyboardHIDUsageKeyboardA,
1752 UIKeyModifierShift, 123.0)
1755 [vc handlePressEvent:keyEventWithPhase(UIPressPhaseCancelled, UIKeyboardHIDUsageKeyboardA,
1756 UIKeyModifierShift, 123.0)
1759 [vc handlePressEvent:keyEventWithPhase(UIPressPhaseChanged, UIKeyboardHIDUsageKeyboardA,
1760 UIKeyModifierShift, 123.0)
1765 OCMVerify(never(), [keyEventChannel sendMessage:[OCMArg any]]);
1766 [vc deregisterNotifications];
1770 if (@available(iOS 13.4, *)) {
1779 XCTAssertNotNil(vc);
1780 UIView* view = vc.view;
1781 XCTAssertNotNil(view);
1782 NSArray* gestureRecognizers = view.gestureRecognizers;
1783 XCTAssertNotNil(gestureRecognizers);
1786 for (
id gesture in gestureRecognizers) {
1787 if ([gesture isKindOfClass:[UIPanGestureRecognizer
class]]) {
1792 XCTAssertTrue(found);
1796 if (@available(iOS 13.4, *)) {
1805 XCTAssertNotNil(vc);
1807 id mockPanGestureRecognizer = OCMClassMock([UIPanGestureRecognizer
class]);
1808 XCTAssertNotNil(mockPanGestureRecognizer);
1810 [vc discreteScrollEvent:mockPanGestureRecognizer];
1812 [[[
self.mockEngine verify] ignoringNonObjectArgs]
1813 dispatchPointerDataPacket:std::make_unique<flutter::PointerDataPacket>(0)];
1816 - (void)testFakeEventTimeStamp {
1820 XCTAssertNotNil(vc);
1822 flutter::PointerData pointer_data = [vc generatePointerDataForFake];
1823 int64_t current_micros = [[NSProcessInfo processInfo] systemUptime] * 1000 * 1000;
1824 int64_t interval_micros = current_micros - pointer_data.time_stamp;
1825 const int64_t tolerance_millis = 2;
1826 XCTAssertTrue(interval_micros / 1000 < tolerance_millis,
1827 @"PointerData.time_stamp should be equal to NSProcessInfo.systemUptime");
1830 - (void)testSplashScreenViewCanSetNil {
1833 [flutterViewController setSplashScreenView:nil];
1836 - (void)testLifeCycleNotificationBecameActive {
1841 UIWindow* window = [[UIWindow alloc] init];
1842 [window addSubview:flutterViewController.view];
1843 flutterViewController.view.bounds = CGRectMake(0, 0, 100, 100);
1844 [flutterViewController viewDidLayoutSubviews];
1845 NSNotification* sceneNotification =
1846 [NSNotification notificationWithName:UISceneDidActivateNotification object:nil userInfo:nil];
1847 NSNotification* applicationNotification =
1848 [NSNotification notificationWithName:UIApplicationDidBecomeActiveNotification
1851 id mockVC = OCMPartialMock(flutterViewController);
1852 [[NSNotificationCenter defaultCenter] postNotification:sceneNotification];
1853 [[NSNotificationCenter defaultCenter] postNotification:applicationNotification];
1854 #if APPLICATION_EXTENSION_API_ONLY
1855 OCMVerify([mockVC sceneBecameActive:[OCMArg any]]);
1856 OCMReject([mockVC applicationBecameActive:[OCMArg any]]);
1858 OCMReject([mockVC sceneBecameActive:[OCMArg any]]);
1859 OCMVerify([mockVC applicationBecameActive:[OCMArg any]]);
1861 XCTAssertFalse(flutterViewController.isKeyboardInOrTransitioningFromBackground);
1862 OCMVerify([mockVC surfaceUpdated:YES]);
1863 XCTestExpectation* timeoutApplicationLifeCycle =
1864 [
self expectationWithDescription:@"timeoutApplicationLifeCycle"];
1865 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)),
1866 dispatch_get_main_queue(), ^{
1867 [timeoutApplicationLifeCycle fulfill];
1868 OCMVerify([mockVC goToApplicationLifecycle:
@"AppLifecycleState.resumed"]);
1869 [flutterViewController deregisterNotifications];
1871 [
self waitForExpectationsWithTimeout:5.0 handler:nil];
1874 - (void)testLifeCycleNotificationWillResignActive {
1879 NSNotification* sceneNotification =
1880 [NSNotification notificationWithName:UISceneWillDeactivateNotification
1883 NSNotification* applicationNotification =
1884 [NSNotification notificationWithName:UIApplicationWillResignActiveNotification
1887 id mockVC = OCMPartialMock(flutterViewController);
1888 [[NSNotificationCenter defaultCenter] postNotification:sceneNotification];
1889 [[NSNotificationCenter defaultCenter] postNotification:applicationNotification];
1890 #if APPLICATION_EXTENSION_API_ONLY
1891 OCMVerify([mockVC sceneWillResignActive:[OCMArg any]]);
1892 OCMReject([mockVC applicationWillResignActive:[OCMArg any]]);
1894 OCMReject([mockVC sceneWillResignActive:[OCMArg any]]);
1895 OCMVerify([mockVC applicationWillResignActive:[OCMArg any]]);
1897 OCMVerify([mockVC goToApplicationLifecycle:
@"AppLifecycleState.inactive"]);
1898 [flutterViewController deregisterNotifications];
1901 - (void)testLifeCycleNotificationWillTerminate {
1906 NSNotification* sceneNotification =
1907 [NSNotification notificationWithName:UISceneDidDisconnectNotification
1910 NSNotification* applicationNotification =
1911 [NSNotification notificationWithName:UIApplicationWillTerminateNotification
1914 id mockVC = OCMPartialMock(flutterViewController);
1917 [[NSNotificationCenter defaultCenter] postNotification:sceneNotification];
1918 [[NSNotificationCenter defaultCenter] postNotification:applicationNotification];
1919 #if APPLICATION_EXTENSION_API_ONLY
1920 OCMVerify([mockVC sceneWillDisconnect:[OCMArg any]]);
1921 OCMReject([mockVC applicationWillTerminate:[OCMArg any]]);
1923 OCMReject([mockVC sceneWillDisconnect:[OCMArg any]]);
1924 OCMVerify([mockVC applicationWillTerminate:[OCMArg any]]);
1926 OCMVerify([mockVC goToApplicationLifecycle:
@"AppLifecycleState.detached"]);
1928 [flutterViewController deregisterNotifications];
1931 - (void)testLifeCycleNotificationDidEnterBackground {
1936 NSNotification* sceneNotification =
1937 [NSNotification notificationWithName:UISceneDidEnterBackgroundNotification
1940 NSNotification* applicationNotification =
1941 [NSNotification notificationWithName:UIApplicationDidEnterBackgroundNotification
1944 id mockVC = OCMPartialMock(flutterViewController);
1945 [[NSNotificationCenter defaultCenter] postNotification:sceneNotification];
1946 [[NSNotificationCenter defaultCenter] postNotification:applicationNotification];
1947 #if APPLICATION_EXTENSION_API_ONLY
1948 OCMVerify([mockVC sceneDidEnterBackground:[OCMArg any]]);
1949 OCMReject([mockVC applicationDidEnterBackground:[OCMArg any]]);
1951 OCMReject([mockVC sceneDidEnterBackground:[OCMArg any]]);
1952 OCMVerify([mockVC applicationDidEnterBackground:[OCMArg any]]);
1954 XCTAssertTrue(flutterViewController.isKeyboardInOrTransitioningFromBackground);
1955 OCMVerify([mockVC surfaceUpdated:NO]);
1956 OCMVerify([mockVC goToApplicationLifecycle:
@"AppLifecycleState.paused"]);
1957 [flutterViewController deregisterNotifications];
1960 - (void)testLifeCycleNotificationWillEnterForeground {
1965 NSNotification* sceneNotification =
1966 [NSNotification notificationWithName:UISceneWillEnterForegroundNotification
1969 NSNotification* applicationNotification =
1970 [NSNotification notificationWithName:UIApplicationWillEnterForegroundNotification
1973 id mockVC = OCMPartialMock(flutterViewController);
1974 [[NSNotificationCenter defaultCenter] postNotification:sceneNotification];
1975 [[NSNotificationCenter defaultCenter] postNotification:applicationNotification];
1976 #if APPLICATION_EXTENSION_API_ONLY
1977 OCMVerify([mockVC sceneWillEnterForeground:[OCMArg any]]);
1978 OCMReject([mockVC applicationWillEnterForeground:[OCMArg any]]);
1980 OCMReject([mockVC sceneWillEnterForeground:[OCMArg any]]);
1981 OCMVerify([mockVC applicationWillEnterForeground:[OCMArg any]]);
1983 OCMVerify([mockVC goToApplicationLifecycle:
@"AppLifecycleState.inactive"]);
1984 [flutterViewController deregisterNotifications];
1987 - (void)testLifeCycleNotificationCancelledInvalidResumed {
1992 NSNotification* applicationDidBecomeActiveNotification =
1993 [NSNotification notificationWithName:UIApplicationDidBecomeActiveNotification
1996 NSNotification* applicationWillResignActiveNotification =
1997 [NSNotification notificationWithName:UIApplicationWillResignActiveNotification
2000 id mockVC = OCMPartialMock(flutterViewController);
2001 [[NSNotificationCenter defaultCenter] postNotification:applicationDidBecomeActiveNotification];
2002 [[NSNotificationCenter defaultCenter] postNotification:applicationWillResignActiveNotification];
2003 #if APPLICATION_EXTENSION_API_ONLY
2005 OCMVerify([mockVC goToApplicationLifecycle:
@"AppLifecycleState.inactive"]);
2008 XCTestExpectation* timeoutApplicationLifeCycle =
2009 [
self expectationWithDescription:@"timeoutApplicationLifeCycle"];
2010 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)),
2011 dispatch_get_main_queue(), ^{
2012 OCMReject([mockVC goToApplicationLifecycle:
@"AppLifecycleState.resumed"]);
2013 [timeoutApplicationLifeCycle fulfill];
2014 [flutterViewController deregisterNotifications];
2016 [
self waitForExpectationsWithTimeout:5.0 handler:nil];