| // Copyright 2014 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #import "ios/web/public/ui/crw_web_view_scroll_view_proxy.h" |
| |
| #import <UIKit/UIKit.h> |
| |
| #import "base/test/scoped_feature_list.h" |
| #import "ios/web/common/features.h" |
| #import "ios/web/web_state/ui/crw_web_view_scroll_view_delegate_proxy.h" |
| #import "testing/gtest/include/gtest/gtest.h" |
| #import "testing/platform_test.h" |
| #import "third_party/ocmock/OCMock/OCMock.h" |
| #import "third_party/ocmock/gtest_support.h" |
| |
| // TODO(crbug.com/40661733): Rewrite tests Delegate, MultipleScrollView, |
| // DelegateClearingUp not to depend on this, and delete this. |
| @interface CRWWebViewScrollViewProxy (Testing) |
| |
| @property(nonatomic, readonly) CRWWebViewScrollViewDelegateProxy* delegateProxy; |
| |
| @end |
| |
| @interface UIScrollView (TestingCategory) |
| - (int)crw_categoryMethod; |
| @end |
| |
| @implementation UIScrollView (TestingCategory) |
| |
| - (int)crw_categoryMethod { |
| return 1; |
| } |
| |
| @end |
| |
| @interface CRWTestObserver : NSObject |
| |
| - (instancetype)initWithProxy:(CRWWebViewScrollViewProxy*)proxy |
| NS_DESIGNATED_INITIALIZER; |
| - (instancetype)init NS_UNAVAILABLE; |
| |
| @end |
| |
| @implementation CRWTestObserver { |
| CRWWebViewScrollViewProxy* _proxy; |
| } |
| |
| - (instancetype)initWithProxy:(CRWWebViewScrollViewProxy*)proxy { |
| self = [super init]; |
| if (self) { |
| _proxy = proxy; |
| [_proxy addObserver:self |
| forKeyPath:@"contentSize" |
| options:NSKeyValueObservingOptionNew |
| context:nullptr]; |
| } |
| return self; |
| } |
| |
| - (void)dealloc { |
| [_proxy removeObserver:self forKeyPath:@"contentSize"]; |
| } |
| |
| @end |
| |
| namespace { |
| |
| class CRWWebViewScrollViewProxyTest : public PlatformTest { |
| protected: |
| void SetUp() override { |
| mock_underlying_scroll_view_ = OCMClassMock([UIScrollView class]); |
| web_view_scroll_view_proxy_ = [[CRWWebViewScrollViewProxy alloc] init]; |
| } |
| ~CRWWebViewScrollViewProxyTest() override { |
| [web_view_scroll_view_proxy_ setScrollView:nil]; |
| } |
| id mock_underlying_scroll_view_; |
| CRWWebViewScrollViewProxy* web_view_scroll_view_proxy_; |
| }; |
| |
| // Tests that the UIScrollViewDelegate is set correctly. |
| TEST_F(CRWWebViewScrollViewProxyTest, Delegate) { |
| OCMExpect([static_cast<UIScrollView*>(mock_underlying_scroll_view_) |
| setDelegate:web_view_scroll_view_proxy_.delegateProxy]); |
| [web_view_scroll_view_proxy_ setScrollView:mock_underlying_scroll_view_]; |
| EXPECT_OCMOCK_VERIFY(mock_underlying_scroll_view_); |
| } |
| |
| // Tests that setting 2 scroll views consecutively, clears the delegate of the |
| // previous scroll view. |
| TEST_F(CRWWebViewScrollViewProxyTest, MultipleScrollView) { |
| UIScrollView* mock_scroll_view1 = [[UIScrollView alloc] init]; |
| UIScrollView* mock_scroll_view2 = [[UIScrollView alloc] init]; |
| [web_view_scroll_view_proxy_ setScrollView:mock_scroll_view1]; |
| [web_view_scroll_view_proxy_ setScrollView:mock_scroll_view2]; |
| EXPECT_FALSE([mock_scroll_view1 delegate]); |
| EXPECT_EQ(web_view_scroll_view_proxy_.delegateProxy, |
| [mock_scroll_view2 delegate]); |
| [web_view_scroll_view_proxy_ setScrollView:nil]; |
| } |
| |
| // Tests that when releasing a scroll view from the CRWWebViewScrollViewProxy, |
| // the UIScrollView's delegate is also cleared. |
| TEST_F(CRWWebViewScrollViewProxyTest, DelegateClearingUp) { |
| UIScrollView* mock_scroll_view1 = [[UIScrollView alloc] init]; |
| [web_view_scroll_view_proxy_ setScrollView:mock_scroll_view1]; |
| EXPECT_EQ(web_view_scroll_view_proxy_.delegateProxy, |
| [mock_scroll_view1 delegate]); |
| [web_view_scroll_view_proxy_ setScrollView:nil]; |
| EXPECT_FALSE([mock_scroll_view1 delegate]); |
| } |
| |
| // Tests that CRWWebViewScrollViewProxy returns the correct property values from |
| // the underlying UIScrollView. |
| TEST_F(CRWWebViewScrollViewProxyTest, ScrollViewPresent) { |
| [web_view_scroll_view_proxy_ setScrollView:mock_underlying_scroll_view_]; |
| OCMStub([mock_underlying_scroll_view_ isZooming]).andReturn(YES); |
| EXPECT_TRUE([web_view_scroll_view_proxy_ isZooming]); |
| |
| // Arbitrary point. |
| const CGPoint point = CGPointMake(10, 10); |
| OCMStub([mock_underlying_scroll_view_ contentOffset]).andReturn(point); |
| EXPECT_TRUE( |
| CGPointEqualToPoint(point, [web_view_scroll_view_proxy_ contentOffset])); |
| |
| // Arbitrary inset. |
| const UIEdgeInsets content_inset = UIEdgeInsetsMake(10, 10, 10, 10); |
| OCMStub([mock_underlying_scroll_view_ contentInset]).andReturn(content_inset); |
| EXPECT_TRUE(UIEdgeInsetsEqualToEdgeInsets( |
| content_inset, [web_view_scroll_view_proxy_ contentInset])); |
| |
| // Arbitrary inset. |
| const UIEdgeInsets scroll_indicator_insets = UIEdgeInsetsMake(20, 20, 20, 20); |
| OCMStub([mock_underlying_scroll_view_ scrollIndicatorInsets]) |
| .andReturn(scroll_indicator_insets); |
| EXPECT_TRUE(UIEdgeInsetsEqualToEdgeInsets( |
| scroll_indicator_insets, |
| [web_view_scroll_view_proxy_ scrollIndicatorInsets])); |
| |
| // Arbitrary size. |
| const CGSize content_size = CGSizeMake(19, 19); |
| OCMStub([mock_underlying_scroll_view_ contentSize]).andReturn(content_size); |
| EXPECT_TRUE(CGSizeEqualToSize(content_size, |
| [web_view_scroll_view_proxy_ contentSize])); |
| |
| // Arbitrary rect. |
| const CGRect frame = CGRectMake(2, 4, 5, 1); |
| OCMStub([mock_underlying_scroll_view_ frame]).andReturn(frame); |
| EXPECT_TRUE(CGRectEqualToRect(frame, [web_view_scroll_view_proxy_ frame])); |
| |
| OCMExpect([mock_underlying_scroll_view_ isDecelerating]).andReturn(YES); |
| EXPECT_TRUE([web_view_scroll_view_proxy_ isDecelerating]); |
| |
| OCMExpect([mock_underlying_scroll_view_ isDecelerating]).andReturn(NO); |
| EXPECT_FALSE([web_view_scroll_view_proxy_ isDecelerating]); |
| |
| OCMExpect([mock_underlying_scroll_view_ isDragging]).andReturn(YES); |
| EXPECT_TRUE([web_view_scroll_view_proxy_ isDragging]); |
| |
| OCMExpect([mock_underlying_scroll_view_ isDragging]).andReturn(NO); |
| EXPECT_FALSE([web_view_scroll_view_proxy_ isDragging]); |
| |
| OCMExpect([mock_underlying_scroll_view_ isTracking]).andReturn(YES); |
| EXPECT_TRUE([web_view_scroll_view_proxy_ isTracking]); |
| |
| OCMExpect([mock_underlying_scroll_view_ isTracking]).andReturn(NO); |
| EXPECT_FALSE([web_view_scroll_view_proxy_ isTracking]); |
| |
| OCMExpect([mock_underlying_scroll_view_ scrollsToTop]).andReturn(YES); |
| EXPECT_TRUE([web_view_scroll_view_proxy_ scrollsToTop]); |
| |
| OCMExpect([mock_underlying_scroll_view_ scrollsToTop]).andReturn(NO); |
| EXPECT_FALSE([web_view_scroll_view_proxy_ scrollsToTop]); |
| |
| NSArray<__kindof UIView*>* subviews = [NSArray array]; |
| OCMExpect([mock_underlying_scroll_view_ subviews]).andReturn(subviews); |
| EXPECT_EQ(subviews, [web_view_scroll_view_proxy_ subviews]); |
| |
| OCMExpect([mock_underlying_scroll_view_ contentInsetAdjustmentBehavior]) |
| .andReturn(UIScrollViewContentInsetAdjustmentAutomatic); |
| EXPECT_EQ(UIScrollViewContentInsetAdjustmentAutomatic, |
| [web_view_scroll_view_proxy_ contentInsetAdjustmentBehavior]); |
| |
| OCMExpect([mock_underlying_scroll_view_ contentInsetAdjustmentBehavior]) |
| .andReturn(UIScrollViewContentInsetAdjustmentNever); |
| EXPECT_EQ(UIScrollViewContentInsetAdjustmentNever, |
| [web_view_scroll_view_proxy_ contentInsetAdjustmentBehavior]); |
| |
| OCMExpect([mock_underlying_scroll_view_ clipsToBounds]).andReturn(NO); |
| EXPECT_FALSE([web_view_scroll_view_proxy_ clipsToBounds]); |
| |
| OCMExpect([mock_underlying_scroll_view_ clipsToBounds]).andReturn(YES); |
| EXPECT_TRUE([web_view_scroll_view_proxy_ clipsToBounds]); |
| } |
| |
| // Tests that CRWWebViewScrollViewProxy returns the default values of |
| // UIScrollView's properties when there is no underlying UIScrollView. |
| TEST_F(CRWWebViewScrollViewProxyTest, ScrollViewAbsent) { |
| [web_view_scroll_view_proxy_ setScrollView:nil]; |
| |
| EXPECT_TRUE(CGPointEqualToPoint(CGPointZero, |
| [web_view_scroll_view_proxy_ contentOffset])); |
| EXPECT_TRUE(UIEdgeInsetsEqualToEdgeInsets( |
| UIEdgeInsetsZero, [web_view_scroll_view_proxy_ contentInset])); |
| EXPECT_TRUE(UIEdgeInsetsEqualToEdgeInsets( |
| UIEdgeInsetsZero, [web_view_scroll_view_proxy_ scrollIndicatorInsets])); |
| EXPECT_TRUE( |
| CGSizeEqualToSize(CGSizeZero, [web_view_scroll_view_proxy_ contentSize])); |
| EXPECT_TRUE( |
| CGRectEqualToRect(CGRectZero, [web_view_scroll_view_proxy_ frame])); |
| EXPECT_FALSE([web_view_scroll_view_proxy_ isDecelerating]); |
| EXPECT_FALSE([web_view_scroll_view_proxy_ isDragging]); |
| EXPECT_FALSE([web_view_scroll_view_proxy_ isTracking]); |
| EXPECT_TRUE([web_view_scroll_view_proxy_ scrollsToTop]); |
| EXPECT_EQ((NSUInteger)0, [web_view_scroll_view_proxy_ subviews].count); |
| EXPECT_EQ(UIScrollViewContentInsetAdjustmentAutomatic, |
| [web_view_scroll_view_proxy_ contentInsetAdjustmentBehavior]); |
| EXPECT_TRUE([web_view_scroll_view_proxy_ clipsToBounds]); |
| |
| // Make sure setting the properties is fine too. |
| // Arbitrary point. |
| const CGPoint kPoint = CGPointMake(10, 10); |
| [web_view_scroll_view_proxy_ setContentOffset:kPoint]; |
| // Arbitrary inset. |
| const UIEdgeInsets kContentInset = UIEdgeInsetsMake(10, 10, 10, 10); |
| [web_view_scroll_view_proxy_ setContentInset:kContentInset]; |
| [web_view_scroll_view_proxy_ setScrollIndicatorInsets:kContentInset]; |
| // Arbitrary size. |
| const CGSize kContentSize = CGSizeMake(19, 19); |
| [web_view_scroll_view_proxy_ setContentSize:kContentSize]; |
| } |
| |
| // Tests that CRWWebViewScrollViewProxy returns the correct property values when |
| // they are set while there isn't an underlying scroll view, then a new scroll |
| // view is set. |
| TEST_F(CRWWebViewScrollViewProxyTest, ScrollViewAbsentThenReset) { |
| [web_view_scroll_view_proxy_ setScrollView:nil]; |
| UIScrollView* underlying_scroll_view = [[UIScrollView alloc] init]; |
| |
| OCMExpect([mock_underlying_scroll_view_ setClipsToBounds:YES]); |
| [web_view_scroll_view_proxy_ setClipsToBounds:YES]; |
| OCMExpect([mock_underlying_scroll_view_ |
| setContentInsetAdjustmentBehavior: |
| UIScrollViewContentInsetAdjustmentNever]); |
| [web_view_scroll_view_proxy_ setContentInsetAdjustmentBehavior: |
| UIScrollViewContentInsetAdjustmentNever]; |
| |
| [web_view_scroll_view_proxy_ setScrollView:underlying_scroll_view]; |
| |
| [web_view_scroll_view_proxy_ setScrollView:mock_underlying_scroll_view_]; |
| |
| EXPECT_OCMOCK_VERIFY(mock_underlying_scroll_view_); |
| } |
| |
| // Tests that CRWWebViewScrollViewProxy returns the correct property values when |
| // they are set while there is an underlying scroll view, then a new scroll view |
| // is set. |
| TEST_F(CRWWebViewScrollViewProxyTest, ScrollViewPresentThenReset) { |
| [web_view_scroll_view_proxy_ setScrollView:nil]; |
| UIScrollView* underlying_scroll_view = [[UIScrollView alloc] init]; |
| |
| [web_view_scroll_view_proxy_ setScrollView:underlying_scroll_view]; |
| OCMExpect([mock_underlying_scroll_view_ setClipsToBounds:YES]); |
| [web_view_scroll_view_proxy_ setClipsToBounds:YES]; |
| OCMExpect([mock_underlying_scroll_view_ |
| setContentInsetAdjustmentBehavior: |
| UIScrollViewContentInsetAdjustmentNever]); |
| [web_view_scroll_view_proxy_ setContentInsetAdjustmentBehavior: |
| UIScrollViewContentInsetAdjustmentNever]; |
| |
| [web_view_scroll_view_proxy_ setScrollView:mock_underlying_scroll_view_]; |
| |
| EXPECT_OCMOCK_VERIFY(mock_underlying_scroll_view_); |
| } |
| |
| // Tests releasing a scroll view when none is owned by the |
| // CRWWebViewScrollViewProxy. |
| TEST_F(CRWWebViewScrollViewProxyTest, ReleasingAScrollView) { |
| [web_view_scroll_view_proxy_ setScrollView:nil]; |
| } |
| |
| // Tests that CRWWebViewScrollViewProxy correctly delegates property setters to |
| // the underlying UIScrollView. |
| TEST_F(CRWWebViewScrollViewProxyTest, ScrollViewSetProperties) { |
| [web_view_scroll_view_proxy_ setScrollView:mock_underlying_scroll_view_]; |
| |
| OCMExpect([mock_underlying_scroll_view_ |
| setContentInsetAdjustmentBehavior: |
| UIScrollViewContentInsetAdjustmentNever]); |
| [web_view_scroll_view_proxy_ setContentInsetAdjustmentBehavior: |
| UIScrollViewContentInsetAdjustmentNever]; |
| EXPECT_OCMOCK_VERIFY(mock_underlying_scroll_view_); |
| } |
| |
| // Tests that -setContentInsetAdjustmentBehavior: works even if it is called |
| // before setting the scroll view. |
| TEST_F(CRWWebViewScrollViewProxyTest, |
| SetContentInsetAdjustmentBehaviorBeforeSettingScrollView) { |
| OCMExpect([mock_underlying_scroll_view_ |
| setContentInsetAdjustmentBehavior: |
| UIScrollViewContentInsetAdjustmentNever]); |
| |
| [web_view_scroll_view_proxy_ setScrollView:nil]; |
| [web_view_scroll_view_proxy_ setContentInsetAdjustmentBehavior: |
| UIScrollViewContentInsetAdjustmentNever]; |
| [web_view_scroll_view_proxy_ setScrollView:mock_underlying_scroll_view_]; |
| |
| EXPECT_OCMOCK_VERIFY(mock_underlying_scroll_view_); |
| } |
| |
| // Tests that -setClipsToBounds: works even if it is called before setting the |
| // scroll view. |
| TEST_F(CRWWebViewScrollViewProxyTest, SetClipsToBoundsBeforeSettingScrollView) { |
| OCMExpect([mock_underlying_scroll_view_ setClipsToBounds:YES]); |
| |
| [web_view_scroll_view_proxy_ setScrollView:nil]; |
| [web_view_scroll_view_proxy_ setClipsToBounds:YES]; |
| [web_view_scroll_view_proxy_ setScrollView:mock_underlying_scroll_view_]; |
| |
| EXPECT_OCMOCK_VERIFY(mock_underlying_scroll_view_); |
| } |
| |
| // Tests that frame changes are communicated to observers. |
| TEST_F(CRWWebViewScrollViewProxyTest, FrameDidChange) { |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitAndEnableFeature( |
| web::features::kSmoothScrollingDefault); |
| |
| UIScrollView* underlying_scroll_view = |
| [[UIScrollView alloc] initWithFrame:CGRectZero]; |
| [web_view_scroll_view_proxy_ setScrollView:underlying_scroll_view]; |
| id mock_delegate = |
| OCMProtocolMock(@protocol(CRWWebViewScrollViewProxyObserver)); |
| [web_view_scroll_view_proxy_ addObserver:mock_delegate]; |
| OCMExpect([mock_delegate |
| webViewScrollViewFrameDidChange:web_view_scroll_view_proxy_]); |
| underlying_scroll_view.frame = CGRectMake(1, 2, 3, 4); |
| EXPECT_OCMOCK_VERIFY(mock_delegate); |
| [web_view_scroll_view_proxy_ setScrollView:nil]; |
| } |
| |
| // Tests that contentInset changes are communicated to observers. |
| TEST_F(CRWWebViewScrollViewProxyTest, ContentInsetDidChange) { |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitAndEnableFeature( |
| web::features::kSmoothScrollingDefault); |
| |
| UIScrollView* underlying_scroll_view = |
| [[UIScrollView alloc] initWithFrame:CGRectZero]; |
| [web_view_scroll_view_proxy_ setScrollView:underlying_scroll_view]; |
| id mock_delegate = |
| OCMProtocolMock(@protocol(CRWWebViewScrollViewProxyObserver)); |
| [web_view_scroll_view_proxy_ addObserver:mock_delegate]; |
| OCMExpect([mock_delegate |
| webViewScrollViewDidResetContentInset:web_view_scroll_view_proxy_]); |
| underlying_scroll_view.contentInset = UIEdgeInsetsMake(0, 1, 2, 3); |
| EXPECT_OCMOCK_VERIFY(mock_delegate); |
| [web_view_scroll_view_proxy_ setScrollView:nil]; |
| } |
| |
| // Verifies that method calls to -asUIScrollView are simply forwarded to the |
| // underlying scroll view if the method is not implemented in |
| // CRWWebViewScrollViewProxy. |
| TEST_F(CRWWebViewScrollViewProxyTest, AsUIScrollViewWithUnderlyingScrollView) { |
| [web_view_scroll_view_proxy_ setScrollView:mock_underlying_scroll_view_]; |
| |
| // Verifies that a return value is properly propagated. |
| // -viewPrintFormatter is not implemented in CRWWebViewScrollViewProxy. |
| UIViewPrintFormatter* print_formatter_mock = |
| OCMClassMock([UIViewPrintFormatter class]); |
| OCMStub([mock_underlying_scroll_view_ viewPrintFormatter]) |
| .andReturn(print_formatter_mock); |
| EXPECT_EQ(print_formatter_mock, |
| [[web_view_scroll_view_proxy_ asUIScrollView] viewPrintFormatter]); |
| |
| // Verifies that a parameter is properly propagated. |
| // -drawRect: is not implemented in CRWWebViewScrollViewProxy. |
| CGRect rect = CGRectMake(0, 0, 1, 1); |
| OCMExpect([mock_underlying_scroll_view_ drawRect:rect]); |
| [[web_view_scroll_view_proxy_ asUIScrollView] drawRect:rect]; |
| EXPECT_OCMOCK_VERIFY((id)mock_underlying_scroll_view_); |
| |
| [web_view_scroll_view_proxy_ setScrollView:nil]; |
| } |
| |
| // Verifies that method calls to -asUIScrollView are no-op if the underlying |
| // scroll view is not set and the method is not implemented in |
| // CRWWebViewScrollViewProxy. |
| TEST_F(CRWWebViewScrollViewProxyTest, |
| AsUIScrollViewWithoutUnderlyingScrollView) { |
| [web_view_scroll_view_proxy_ setScrollView:nil]; |
| |
| // Any methods should return nil when the underlying scroll view is not set. |
| EXPECT_EQ(nil, [[web_view_scroll_view_proxy_ asUIScrollView] |
| restorationIdentifier]); |
| |
| // It is expected that nothing happens. Just verifies that it doesn't crash. |
| CGRect rect = CGRectMake(0, 0, 1, 1); |
| [[web_view_scroll_view_proxy_ asUIScrollView] drawRect:rect]; |
| } |
| |
| // Verify that -[CRWWebViewScrollViewProxy isKindOfClass:] works as expected. |
| TEST_F(CRWWebViewScrollViewProxyTest, IsKindOfClass) { |
| // The proxy is a kind of its own class. |
| EXPECT_TRUE([web_view_scroll_view_proxy_ |
| isKindOfClass:[CRWWebViewScrollViewProxy class]]); |
| |
| // The proxy prentends itself to be a kind of UIScrollView. |
| EXPECT_TRUE([web_view_scroll_view_proxy_ isKindOfClass:[UIScrollView class]]); |
| |
| // It should return YES for ancestor classes of UIScrollView. |
| EXPECT_TRUE([web_view_scroll_view_proxy_ isKindOfClass:[UIView class]]); |
| |
| // Returns NO if none of above applies. |
| EXPECT_FALSE([web_view_scroll_view_proxy_ isKindOfClass:[NSString class]]); |
| } |
| |
| // Verify that -[CRWWebViewScrollViewProxy respondsToSelector:] works as |
| // expected. |
| TEST_F(CRWWebViewScrollViewProxyTest, RespondsToSelector) { |
| // A method defined in CRWWebViewScrollViewProxy but not in UIScrollView. |
| EXPECT_TRUE( |
| [web_view_scroll_view_proxy_ respondsToSelector:@selector(addObserver:)]); |
| |
| // A method defined in CRWWebViewScrollViewProxy and also in UIScrollView. |
| EXPECT_TRUE([web_view_scroll_view_proxy_ |
| respondsToSelector:@selector(contentOffset)]); |
| |
| // A method defined in UIScrollView but not in CRWWebViewScrollViewProxy. |
| EXPECT_TRUE([web_view_scroll_view_proxy_ |
| respondsToSelector:@selector(indexDisplayMode)]); |
| |
| // A method defined in UIView (a superclass of UIScrollView) but not in |
| // CRWWebViewScrollViewProxy. |
| EXPECT_TRUE([web_view_scroll_view_proxy_ |
| respondsToSelector:@selector(viewPrintFormatter)]); |
| |
| // A method defined in none of above. |
| EXPECT_FALSE([web_view_scroll_view_proxy_ |
| respondsToSelector:@selector(containsString:)]); |
| } |
| |
| // Tests delegate method forwarding to [web_view_scroll_view_proxy_ |
| // asUIScrollView].delegate when: |
| // - [web_view_scroll_view_proxy_ asUIScrollView].delegate is not nil |
| // - CRWWebViewScrollViewDelegateProxy implements the method |
| // |
| // Expects that a method call to the delegate of the underlying scroll view is |
| // forwarded to [web_view_scroll_view_proxy_ asUIScrollView].delegate. |
| TEST_F(CRWWebViewScrollViewProxyTest, |
| ProxyDelegateMethodForwardingForImplementedMethod) { |
| UIScrollView* underlying_scroll_view = [[UIScrollView alloc] init]; |
| [web_view_scroll_view_proxy_ setScrollView:underlying_scroll_view]; |
| |
| id<UIScrollViewDelegate> mock_proxy_delegate = |
| OCMProtocolMock(@protocol(UIScrollViewDelegate)); |
| [web_view_scroll_view_proxy_ asUIScrollView].delegate = mock_proxy_delegate; |
| |
| UIView* mock_view = OCMClassMock([UIView class]); |
| OCMExpect([mock_proxy_delegate |
| scrollViewWillBeginZooming:[web_view_scroll_view_proxy_ asUIScrollView] |
| withView:mock_view]); |
| |
| EXPECT_TRUE([underlying_scroll_view.delegate |
| respondsToSelector:@selector(scrollViewWillBeginZooming:withView:)]); |
| [underlying_scroll_view.delegate |
| scrollViewWillBeginZooming:underlying_scroll_view |
| withView:mock_view]; |
| |
| EXPECT_OCMOCK_VERIFY(static_cast<id>(mock_proxy_delegate)); |
| [web_view_scroll_view_proxy_ setScrollView:nil]; |
| } |
| |
| // Tests delegate method forwarding to [web_view_scroll_view_proxy_ |
| // asUIScrollView].delegate when: |
| // - [web_view_scroll_view_proxy_ asUIScrollView].delegate is not nil |
| // - CRWWebViewScrollViewDelegateProxy does *not* implement the method |
| // |
| // Expects that a method call to the delegate of the underlying scroll view is |
| // forwarded to [web_view_scroll_view_proxy_ asUIScrollView].delegate. |
| TEST_F(CRWWebViewScrollViewProxyTest, |
| ProxyDelegateMethodForwardingForUnimplementedMethod) { |
| UIScrollView* underlying_scroll_view = [[UIScrollView alloc] init]; |
| [web_view_scroll_view_proxy_ setScrollView:underlying_scroll_view]; |
| |
| id<UIScrollViewDelegate> mock_proxy_delegate = |
| OCMProtocolMock(@protocol(UIScrollViewDelegate)); |
| [web_view_scroll_view_proxy_ asUIScrollView].delegate = mock_proxy_delegate; |
| |
| UIView* mock_view = OCMClassMock([UIView class]); |
| OCMExpect([mock_proxy_delegate |
| viewForZoomingInScrollView:[web_view_scroll_view_proxy_ |
| asUIScrollView]]) |
| .andReturn(mock_view); |
| |
| EXPECT_TRUE([underlying_scroll_view.delegate |
| respondsToSelector:@selector(viewForZoomingInScrollView:)]); |
| EXPECT_EQ(mock_view, [underlying_scroll_view.delegate |
| viewForZoomingInScrollView:underlying_scroll_view]); |
| |
| EXPECT_OCMOCK_VERIFY(static_cast<id>(mock_proxy_delegate)); |
| [web_view_scroll_view_proxy_ setScrollView:nil]; |
| } |
| |
| // Tests delegate method forwarding to [web_view_scroll_view_proxy_ |
| // asUIScrollView].delegate when: |
| // - [web_view_scroll_view_proxy_ asUIScrollView].delegate is nil |
| // - CRWWebViewScrollViewDelegateProxy implements the method |
| // |
| // Expects that the delegate of the underlying scroll view responds to the |
| // method but does nothing. |
| TEST_F(CRWWebViewScrollViewProxyTest, |
| ProxyDelegateMethodForwardingForImplementedMethodWhenDelegateIsNil) { |
| UIScrollView* underlying_scroll_view = [[UIScrollView alloc] init]; |
| [web_view_scroll_view_proxy_ setScrollView:underlying_scroll_view]; |
| |
| [web_view_scroll_view_proxy_ asUIScrollView].delegate = nil; |
| |
| EXPECT_TRUE([underlying_scroll_view.delegate |
| respondsToSelector:@selector(scrollViewWillBeginZooming:withView:)]); |
| UIView* mock_view = OCMClassMock([UIView class]); |
| // Expects that nothing happens by calling this. |
| [underlying_scroll_view.delegate |
| scrollViewWillBeginZooming:underlying_scroll_view |
| withView:mock_view]; |
| |
| [web_view_scroll_view_proxy_ setScrollView:nil]; |
| } |
| |
| // Tests delegate method forwarding to [web_view_scroll_view_proxy_ |
| // asUIScrollView].delegate when: |
| // - [web_view_scroll_view_proxy_ asUIScrollView].delegate is nil |
| // - CRWWebViewScrollViewDelegateProxy does *not* implement the method |
| // |
| // Expects that the delegate of the underlying scroll view does *not* respond to |
| // the method. |
| TEST_F(CRWWebViewScrollViewProxyTest, |
| ProxyDelegateMethodForwardingForUnimplementedMethodWhenDelegateIsNil) { |
| UIScrollView* underlying_scroll_view = [[UIScrollView alloc] init]; |
| [web_view_scroll_view_proxy_ setScrollView:underlying_scroll_view]; |
| |
| [web_view_scroll_view_proxy_ asUIScrollView].delegate = nil; |
| |
| EXPECT_FALSE([underlying_scroll_view.delegate |
| respondsToSelector:@selector(viewForZoomingInScrollView:)]); |
| |
| [web_view_scroll_view_proxy_ setScrollView:nil]; |
| } |
| |
| // Verifies that adding a key-value observer to a CRWWebViewScrollViewProxy |
| // works as expected. |
| TEST_F(CRWWebViewScrollViewProxyTest, AddKVObserver) { |
| UIScrollView* underlying_scroll_view = [[UIScrollView alloc] init]; |
| underlying_scroll_view.contentOffset = CGPointZero; |
| [web_view_scroll_view_proxy_ setScrollView:underlying_scroll_view]; |
| |
| // Add a key-value observer to a CRWWebViewScrollViewProxy. |
| NSObject* observer = OCMClassMock([NSObject class]); |
| int context = 0; |
| [web_view_scroll_view_proxy_ |
| addObserver:observer |
| forKeyPath:@"contentOffset" |
| options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew |
| context:&context]; |
| |
| // Setting `contentOffset` of the underlying scroll view should trigger a KVO |
| // notification. The `object` of the notification should be the |
| // CRWWebViewScrollViewProxy, not the underlying scroll view. |
| CGPoint new_offset = CGPointMake(10, 20); |
| NSDictionary<NSKeyValueChangeKey, id>* expected_change = @{ |
| NSKeyValueChangeKindKey : @(NSKeyValueChangeSetting), |
| NSKeyValueChangeOldKey : @(CGPointZero), |
| NSKeyValueChangeNewKey : @(new_offset) |
| }; |
| OCMExpect([observer observeValueForKeyPath:@"contentOffset" |
| ofObject:web_view_scroll_view_proxy_ |
| change:expected_change |
| context:&context]); |
| underlying_scroll_view.contentOffset = new_offset; |
| |
| EXPECT_OCMOCK_VERIFY(static_cast<id>(observer)); |
| [web_view_scroll_view_proxy_ removeObserver:observer |
| forKeyPath:@"contentOffset"]; |
| } |
| |
| // Verifies that a key-value observer is kept after the underlying scroll view |
| // is set. |
| TEST_F(CRWWebViewScrollViewProxyTest, |
| KVObserversAreKeptAfterSettingUnderlyingScrollView) { |
| // Add a key-value observer to a CRWWebViewScrollViewProxy. |
| NSObject* observer = OCMClassMock([NSObject class]); |
| int context = 0; |
| [web_view_scroll_view_proxy_ |
| addObserver:observer |
| forKeyPath:@"contentOffset" |
| options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew |
| context:&context]; |
| |
| // Set the underlying scroll view. |
| UIScrollView* underlying_scroll_view = [[UIScrollView alloc] init]; |
| underlying_scroll_view.contentOffset = CGPointZero; |
| [web_view_scroll_view_proxy_ setScrollView:underlying_scroll_view]; |
| |
| // KVO is inherited to the new underlying scroll view. |
| CGPoint new_offset = CGPointMake(10, 20); |
| NSDictionary<NSKeyValueChangeKey, id>* expected_change = @{ |
| NSKeyValueChangeKindKey : @(NSKeyValueChangeSetting), |
| NSKeyValueChangeOldKey : @(CGPointZero), |
| NSKeyValueChangeNewKey : @(new_offset) |
| }; |
| OCMExpect([observer observeValueForKeyPath:@"contentOffset" |
| ofObject:web_view_scroll_view_proxy_ |
| change:expected_change |
| context:&context]); |
| underlying_scroll_view.contentOffset = new_offset; |
| |
| EXPECT_OCMOCK_VERIFY(static_cast<id>(observer)); |
| [web_view_scroll_view_proxy_ removeObserver:observer |
| forKeyPath:@"contentOffset"]; |
| } |
| |
| // Verifies that removing a key-value observer from a CRWWebViewScrollViewProxy |
| // works as expected. |
| TEST_F(CRWWebViewScrollViewProxyTest, RemoveKVObserver) { |
| UIScrollView* underlying_scroll_view = [[UIScrollView alloc] init]; |
| underlying_scroll_view.contentOffset = CGPointZero; |
| [web_view_scroll_view_proxy_ setScrollView:underlying_scroll_view]; |
| |
| // Add and then remove a key-value observer. |
| NSObject* observer = OCMClassMock([NSObject class]); |
| int context = 0; |
| [web_view_scroll_view_proxy_ |
| addObserver:observer |
| forKeyPath:@"contentOffset" |
| options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew |
| context:&context]; |
| [web_view_scroll_view_proxy_ removeObserver:observer |
| forKeyPath:@"contentOffset"]; |
| |
| // The observer should not be notified of a change after the removal. |
| CGPoint new_offset = CGPointMake(10, 20); |
| NSDictionary<NSKeyValueChangeKey, id>* expected_change = @{ |
| NSKeyValueChangeKindKey : @(NSKeyValueChangeSetting), |
| NSKeyValueChangeOldKey : @(CGPointZero), |
| NSKeyValueChangeNewKey : @(new_offset) |
| }; |
| [[static_cast<id>(observer) reject] |
| observeValueForKeyPath:@"contentOffset" |
| ofObject:web_view_scroll_view_proxy_ |
| change:expected_change |
| context:&context]; |
| underlying_scroll_view.contentOffset = new_offset; |
| |
| EXPECT_OCMOCK_VERIFY(static_cast<id>(observer)); |
| } |
| |
| // When -addObserver:forKeyPath:options:context: is called multiple times with |
| // the same observer and key path, -removeObserver:forKeyPath: removes the last |
| // observation. |
| // |
| // This matches the (undocumented) behavior of the built-in KVO. |
| TEST_F(CRWWebViewScrollViewProxyTest, RemoveKVObserverRemovesLastObservation) { |
| UIScrollView* underlying_scroll_view = [[UIScrollView alloc] init]; |
| underlying_scroll_view.contentOffset = CGPointZero; |
| [web_view_scroll_view_proxy_ setScrollView:underlying_scroll_view]; |
| |
| // Add an observer twice with `context1` and then with `context2`. |
| NSObject* observer = OCMClassMock([NSObject class]); |
| int context1 = 0; |
| int context2 = 0; |
| [web_view_scroll_view_proxy_ |
| addObserver:observer |
| forKeyPath:@"contentOffset" |
| options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew |
| context:&context1]; |
| [web_view_scroll_view_proxy_ |
| addObserver:observer |
| forKeyPath:@"contentOffset" |
| options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew |
| context:&context2]; |
| |
| // Remove an observer once. This should remove the observation with |
| // `context2`. |
| [web_view_scroll_view_proxy_ removeObserver:observer |
| forKeyPath:@"contentOffset"]; |
| |
| // The observer should be notified of a change with `context1` but not with |
| // `context2`. |
| CGPoint new_offset = CGPointMake(10, 20); |
| NSDictionary<NSKeyValueChangeKey, id>* expected_change = @{ |
| NSKeyValueChangeKindKey : @(NSKeyValueChangeSetting), |
| NSKeyValueChangeOldKey : @(CGPointZero), |
| NSKeyValueChangeNewKey : @(new_offset) |
| }; |
| OCMExpect([observer observeValueForKeyPath:@"contentOffset" |
| ofObject:web_view_scroll_view_proxy_ |
| change:expected_change |
| context:&context1]); |
| [[static_cast<id>(observer) reject] |
| observeValueForKeyPath:@"contentOffset" |
| ofObject:web_view_scroll_view_proxy_ |
| change:expected_change |
| context:&context2]; |
| |
| underlying_scroll_view.contentOffset = new_offset; |
| |
| EXPECT_OCMOCK_VERIFY(static_cast<id>(observer)); |
| [web_view_scroll_view_proxy_ removeObserver:observer |
| forKeyPath:@"contentOffset"]; |
| } |
| |
| // Verifies that removing a key-value observer from a CRWWebViewScrollViewProxy |
| // works as expected when given a context. |
| TEST_F(CRWWebViewScrollViewProxyTest, RemoveKVObserverWithContext) { |
| UIScrollView* underlying_scroll_view = [[UIScrollView alloc] init]; |
| underlying_scroll_view.contentOffset = CGPointZero; |
| [web_view_scroll_view_proxy_ setScrollView:underlying_scroll_view]; |
| |
| // Add an observer twice with `context1` and then with `context2`. |
| NSObject* observer = OCMClassMock([NSObject class]); |
| int context1 = 0; |
| int context2 = 0; |
| [web_view_scroll_view_proxy_ |
| addObserver:observer |
| forKeyPath:@"contentOffset" |
| options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew |
| context:&context1]; |
| [web_view_scroll_view_proxy_ |
| addObserver:observer |
| forKeyPath:@"contentOffset" |
| options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew |
| context:&context2]; |
| |
| // Remove the observation with `context1`. |
| [web_view_scroll_view_proxy_ removeObserver:observer |
| forKeyPath:@"contentOffset" |
| context:&context1]; |
| |
| // The observer should be notified of a change with `context2` but not with |
| // `context1`. |
| CGPoint new_offset = CGPointMake(10, 20); |
| NSDictionary<NSKeyValueChangeKey, id>* expected_change = @{ |
| NSKeyValueChangeKindKey : @(NSKeyValueChangeSetting), |
| NSKeyValueChangeOldKey : @(CGPointZero), |
| NSKeyValueChangeNewKey : @(new_offset) |
| }; |
| OCMExpect([observer observeValueForKeyPath:@"contentOffset" |
| ofObject:web_view_scroll_view_proxy_ |
| change:expected_change |
| context:&context2]); |
| [[static_cast<id>(observer) reject] |
| observeValueForKeyPath:@"contentOffset" |
| ofObject:web_view_scroll_view_proxy_ |
| change:expected_change |
| context:&context1]; |
| |
| underlying_scroll_view.contentOffset = new_offset; |
| |
| EXPECT_OCMOCK_VERIFY(static_cast<id>(observer)); |
| [web_view_scroll_view_proxy_ removeObserver:observer |
| forKeyPath:@"contentOffset" |
| context:&context2]; |
| } |
| |
| // Verifies that it is safe to call -removeObserver:forKeyPath: against the |
| // proxy during -dealloc of the observer. |
| TEST_F(CRWWebViewScrollViewProxyTest, |
| RemoveKVObserverWhileDeallocatingObserver) { |
| // CRWTestObserver adds itself as a key-value observer of the proxy in its |
| // initializer, and removes itself as a observer during its -dealloc. |
| [[maybe_unused]] CRWTestObserver* observer = |
| [[CRWTestObserver alloc] initWithProxy:web_view_scroll_view_proxy_]; |
| } |
| |
| // Verifies that properties registered to `propertiesStore` are preserved if: |
| // - the setter is called when the underlying scroll view is not set |
| // - the getter is called after the underlying scroll view is still not set |
| TEST_F(CRWWebViewScrollViewProxyTest, |
| PreservePropertiesWhileUnderlyingScrollViewIsAbsent) { |
| // Recreate CRWWebViewScrollViewProxy with the updated feature flags. |
| web_view_scroll_view_proxy_ = [[CRWWebViewScrollViewProxy alloc] init]; |
| |
| [web_view_scroll_view_proxy_ setScrollView:nil]; |
| |
| // A preserved property with a primitive type. |
| [web_view_scroll_view_proxy_ asUIScrollView].directionalLockEnabled = YES; |
| EXPECT_TRUE( |
| [web_view_scroll_view_proxy_ asUIScrollView].directionalLockEnabled); |
| [web_view_scroll_view_proxy_ asUIScrollView].directionalLockEnabled = NO; |
| EXPECT_FALSE( |
| [web_view_scroll_view_proxy_ asUIScrollView].directionalLockEnabled); |
| |
| // A preserved property with an object type. |
| [web_view_scroll_view_proxy_ asUIScrollView].tintColor = UIColor.redColor; |
| EXPECT_EQ(UIColor.redColor, |
| [web_view_scroll_view_proxy_ asUIScrollView].tintColor); |
| } |
| |
| // Verifies that properties registered to `propertiesStore` are preserved if: |
| // - the setter is called when the underlying scroll view is not set |
| // - the getter is called after the underlying scroll view is set |
| TEST_F(CRWWebViewScrollViewProxyTest, |
| PreservePropertiesWhenUnderlyingScrollViewIsNewlyAssigned) { |
| // Recreate CRWWebViewScrollViewProxy with the updated feature flags. |
| web_view_scroll_view_proxy_ = [[CRWWebViewScrollViewProxy alloc] init]; |
| |
| [web_view_scroll_view_proxy_ setScrollView:nil]; |
| |
| [web_view_scroll_view_proxy_ asUIScrollView].directionalLockEnabled = YES; |
| [web_view_scroll_view_proxy_ asUIScrollView].tintColor = UIColor.redColor; |
| |
| UIScrollView* underlying_scroll_view = [[UIScrollView alloc] init]; |
| [web_view_scroll_view_proxy_ setScrollView:underlying_scroll_view]; |
| |
| // The properties are restored on the underlying scroll view. |
| EXPECT_TRUE(underlying_scroll_view.directionalLockEnabled); |
| EXPECT_EQ(UIColor.redColor, underlying_scroll_view.tintColor); |
| |
| // The same property values are available via the scroll view proxy as well. |
| EXPECT_TRUE( |
| [web_view_scroll_view_proxy_ asUIScrollView].directionalLockEnabled); |
| EXPECT_EQ(UIColor.redColor, |
| [web_view_scroll_view_proxy_ asUIScrollView].tintColor); |
| |
| [web_view_scroll_view_proxy_ setScrollView:nil]; |
| } |
| |
| // Verifies that properties registered to `propertiesStore` are preserved if: |
| // - the setter is called when the underlying scroll view is set |
| // - the getter is called after the underlying scroll view is reassigned |
| TEST_F(CRWWebViewScrollViewProxyTest, |
| PreservePropertiesWhenUnderlyingScrollViewIsReassigned) { |
| // Recreate CRWWebViewScrollViewProxy with the updated feature flags. |
| web_view_scroll_view_proxy_ = [[CRWWebViewScrollViewProxy alloc] init]; |
| |
| UIScrollView* underlying_scroll_view1 = [[UIScrollView alloc] init]; |
| [web_view_scroll_view_proxy_ setScrollView:underlying_scroll_view1]; |
| |
| [web_view_scroll_view_proxy_ asUIScrollView].directionalLockEnabled = YES; |
| [web_view_scroll_view_proxy_ asUIScrollView].tintColor = UIColor.redColor; |
| |
| UIScrollView* underlying_scroll_view2 = [[UIScrollView alloc] init]; |
| [web_view_scroll_view_proxy_ setScrollView:underlying_scroll_view2]; |
| |
| // The properties are restored on the underlying scroll view. |
| EXPECT_TRUE(underlying_scroll_view2.directionalLockEnabled); |
| EXPECT_EQ(UIColor.redColor, underlying_scroll_view2.tintColor); |
| |
| // The same property values are available via the scroll view proxy as well. |
| EXPECT_TRUE( |
| [web_view_scroll_view_proxy_ asUIScrollView].directionalLockEnabled); |
| EXPECT_EQ(UIColor.redColor, |
| [web_view_scroll_view_proxy_ asUIScrollView].tintColor); |
| |
| [web_view_scroll_view_proxy_ setScrollView:nil]; |
| } |
| |
| // Verifies that the proxy uses the real implementation of a method defined in a |
| // category of UIScrollView while the underlying scroll view is not set. |
| TEST_F(CRWWebViewScrollViewProxyTest, |
| UIScrollViewCategoryWithoutUnderlyingScrollView) { |
| // Recreate CRWWebViewScrollViewProxy with the updated feature flags. |
| web_view_scroll_view_proxy_ = [[CRWWebViewScrollViewProxy alloc] init]; |
| |
| [web_view_scroll_view_proxy_ setScrollView:nil]; |
| |
| EXPECT_EQ(1, |
| [[web_view_scroll_view_proxy_ asUIScrollView] crw_categoryMethod]); |
| } |
| |
| // Verifies that the proxy uses the real implementation of a method defined in a |
| // category of UIScrollView while the underlying scroll view is set. |
| TEST_F(CRWWebViewScrollViewProxyTest, |
| UIScrollViewCategoryWithUnderlyingScrollView) { |
| // Recreate CRWWebViewScrollViewProxy with the updated feature flags. |
| web_view_scroll_view_proxy_ = [[CRWWebViewScrollViewProxy alloc] init]; |
| |
| UIScrollView* underlying_scroll_view = [[UIScrollView alloc] init]; |
| [web_view_scroll_view_proxy_ setScrollView:underlying_scroll_view]; |
| |
| EXPECT_EQ(1, |
| [[web_view_scroll_view_proxy_ asUIScrollView] crw_categoryMethod]); |
| } |
| |
| // Verifies that the scroll view backgound color is not preserved between |
| // scroll views. Used to prevent regression of crbug.com/1078790. |
| TEST_F(CRWWebViewScrollViewProxyTest, DontPreserveBackgroundColor) { |
| // Recreate CRWWebViewScrollViewProxy with the updated feature flags. |
| web_view_scroll_view_proxy_ = [[CRWWebViewScrollViewProxy alloc] init]; |
| |
| // Set an underlying UIScrollView, and update its background color to red. |
| UIScrollView* underlying_scroll_view1 = [[UIScrollView alloc] init]; |
| [web_view_scroll_view_proxy_ setScrollView:underlying_scroll_view1]; |
| [web_view_scroll_view_proxy_ asUIScrollView].backgroundColor = |
| UIColor.redColor; |
| |
| // Create a second UIScrollView and set its background color to black. |
| UIScrollView* underlying_scroll_view2 = [[UIScrollView alloc] init]; |
| underlying_scroll_view2.backgroundColor = UIColor.blackColor; |
| [web_view_scroll_view_proxy_ setScrollView:underlying_scroll_view2]; |
| |
| // Verify that the second scroll view's background color remains black. |
| EXPECT_EQ(UIColor.blackColor, underlying_scroll_view2.backgroundColor); |
| } |
| |
| } // namespace |