react-native/Examples/UIExplorer/UIExplorerUnitTests/RCTSubviewClippingTests.m

373 lines
13 KiB
Objective-C

/**
* The examples provided by Facebook are for non-commercial testing and
* evaluation purposes only.
*
* Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#import <objc/message.h>
#import <OCMock/OCMock.h>
#import <XCTest/XCTest.h>
#import "UIView+React.h"
#import "UIView+Private.h"
#import "RCTView.h"
#import "RCTScrollView.h"
#import "RCTRootView.h"
#import "RCTViewManager.h"
#import "RCTComponentData.h"
@interface RCTSubviewClippingTests : XCTestCase
@end
@implementation RCTSubviewClippingTests
- (void)testViewOverlappingBoundsOfClippingViewIsNotClipped
{
RCTView *clippingView = [RCTView new];
[clippingView rct_setRemovesClippedSubviews:YES];
[clippingView reactSetFrame:CGRectMake(0, 0, 50, 50)];
RCTView *childView = [RCTView new];
[childView reactSetFrame:CGRectMake(25, 25, 50, 50)];
[clippingView insertReactSubview:childView atIndex:0];
[clippingView didUpdateReactSubviews];
XCTAssertEqual(clippingView.subviews.count, 1u);
}
- (void)testViewOutsideBoundsOfClippingViewIsClipped
{
RCTView *clippingView = [RCTView new];
[clippingView rct_setRemovesClippedSubviews:YES];
[clippingView reactSetFrame:CGRectMake(0, 0, 50, 50)];
RCTView *childView = [RCTView new];
[childView reactSetFrame:CGRectMake(50, 50, 50, 50)];
[clippingView insertReactSubview:childView atIndex:0];
[clippingView didUpdateReactSubviews];
XCTAssertEqual(clippingView.subviews.count, 0u);
}
- (void)testTurningOnClippingShouldRemoveView
{
RCTView *clippingView = [RCTView new];
[clippingView reactSetFrame:CGRectMake(0, 0, 50, 50)];
RCTView *childView = [RCTView new];
[childView reactSetFrame:CGRectMake(50, 50, 50, 50)];
[clippingView insertReactSubview:childView atIndex:0];
[clippingView didUpdateReactSubviews];
XCTAssertEqual(clippingView.subviews.count, 1u);
[clippingView rct_setRemovesClippedSubviews:YES];
XCTAssertEqual(clippingView.subviews.count, 0u);
}
- (void)testTurningOffClippingShouldAddViewBack
{
RCTView *clippingView = [RCTView new];
[clippingView rct_setRemovesClippedSubviews:YES];
[clippingView reactSetFrame:CGRectMake(0, 0, 50, 50)];
RCTView *childView = [RCTView new];
[childView reactSetFrame:CGRectMake(50, 50, 50, 50)];
[clippingView insertReactSubview:childView atIndex:0];
[clippingView didUpdateReactSubviews];
XCTAssertEqual(clippingView.subviews.count, 0u);
[clippingView rct_setRemovesClippedSubviews:NO];
XCTAssertEqual(clippingView.subviews.count, 1u);
}
- (void)testTransformedClippedViewBackToClippingViewAddsItBack
{
RCTView *clippingView = [RCTView new];
[clippingView rct_setRemovesClippedSubviews:YES];
[clippingView reactSetFrame:CGRectMake(0, 0, 50, 50)];
RCTView *childView = [RCTView new];
[childView reactSetFrame:CGRectMake(50, 50, 50, 50)];
[clippingView insertReactSubview:childView atIndex:0];
[clippingView didUpdateReactSubviews];
XCTAssertEqual(clippingView.subviews.count, 0u);
// Setting the transform property on a view has to be done the same way RN from js would do it.
// That unfortuantely involves some arbitrary-looking setup based on how RCTComponentData's internals works.
id mockBridge = [OCMockObject mockForClass:[RCTBridge class]];
[[[mockBridge stub] andReturn:[RCTViewManager new]] moduleForClass:OCMOCK_ANY];
RCTComponentData *componentData = [[RCTComponentData alloc] initWithManagerClass:[RCTViewManager class] bridge:mockBridge];
// this transform moves the childView to match bounds of its clippingView
[componentData setProps:@{@"transform": @[@1,@0,@0,@0,@0,@1,@0,@0,@0,@0,@1,@0,@-50,@-50,@0,@1]} forView:childView];
XCTAssertEqual(clippingView.subviews.count, 1u);
}
- (void)testMovingClippedViewBackToClippingViewAddsItBack
{
RCTView *clippingView = [RCTView new];
[clippingView rct_setRemovesClippedSubviews:YES];
[clippingView reactSetFrame:CGRectMake(0, 0, 50, 50)];
RCTView *childView = [RCTView new];
[childView reactSetFrame:CGRectMake(50, 50, 50, 50)];
[clippingView insertReactSubview:childView atIndex:0];
[clippingView didUpdateReactSubviews];
XCTAssertEqual(clippingView.subviews.count, 0u);
[childView reactSetFrame:CGRectMake(0, 0, 50, 50)];
XCTAssertEqual(clippingView.subviews.count, 1u);
}
- (void)testResizingClippingViewToContainClippedViewAddsTheClippedViewBack
{
RCTView *clippingView = [RCTView new];
[clippingView rct_setRemovesClippedSubviews:YES];
[clippingView reactSetFrame:CGRectMake(0, 0, 50, 50)];
RCTView *childView = [RCTView new];
[childView reactSetFrame:CGRectMake(50, 50, 50, 50)];
[clippingView insertReactSubview:childView atIndex:0];
[clippingView didUpdateReactSubviews];
XCTAssertEqual(clippingView.subviews.count, 0u);
[clippingView reactSetFrame:(CGRect){{0,0},{100,100}}];
XCTAssertEqual(clippingView.subviews.count, 1u);
}
#pragma mark - zIndex tests
/**
This test case models a following setup:
+--------+
| |
| |
***** | |
*C * | |
* +-*--+ |
* | * | |
***** | |
| | z3|
| +---+----+
+----+ |
| | |
| | |
| | z2|
| +---+----+
| |
| |
| |
| z1|
+--------+
*/
- (void)testZIndexOrderingIsPreservedAfterRetogglingClippingCase1
{
RCTView *clippingView = [RCTView new];
[clippingView reactSetFrame:CGRectMake(0, 0, 50, 50)];
RCTView *childView1 = [RCTView new];
[childView1 reactSetFrame:CGRectMake(-25, 75, 100, 100)];
[childView1 setReactZIndex:1];
[clippingView insertReactSubview:childView1 atIndex:0];
RCTView *childView2 = [RCTView new];
[childView2 reactSetFrame:CGRectMake(25, 25, 100, 100)];
[childView2 setReactZIndex:2];
[clippingView insertReactSubview:childView2 atIndex:0];
RCTView *childView3 = [RCTView new];
[childView3 reactSetFrame:CGRectMake(75, -25, 100, 100)];
[childView3 setReactZIndex:3];
[clippingView insertReactSubview:childView3 atIndex:0];
[clippingView didUpdateReactSubviews];
[clippingView clearSortedSubviews];
XCTAssert(([clippingView.subviews isEqualToArray:@[childView1, childView2, childView3]]));
[clippingView rct_setRemovesClippedSubviews:YES];
XCTAssert(([clippingView.subviews isEqualToArray:@[childView2]]));
[clippingView rct_setRemovesClippedSubviews:NO];
XCTAssert(([clippingView.subviews isEqualToArray:@[childView1, childView2, childView3]]));
}
/**
This test case models a following setup:
**********
*C *
* +-*-----------+
* | * |
* | * |
* | * |
* | * |
* | * |
* +----+ * |
********** |
| | |
| | |
| | +---+
| | | |
| | | |
| |z3 | |
| +---+---------+ |
| | |
| | |
| | |
| | |
|z1 | |
+--------+ |
| |
| |
|z2 |
+-------------+
*/
- (void)testZIndexOrderingIsPreservedAfterRetogglingClippingCase2
{
RCTView *clippingView = [RCTView new];
[clippingView reactSetFrame:CGRectMake(0, 0, 100, 100)];
RCTView *childView1 = [RCTView new];
[childView1 reactSetFrame:CGRectMake(25, 75, 150, 150)];
[childView1 setReactZIndex:1];
[clippingView insertReactSubview:childView1 atIndex:0];
RCTView *childView2 = [RCTView new];
[childView2 reactSetFrame:CGRectMake(125, 125, 150, 150)];
[childView2 setReactZIndex:2];
[clippingView insertReactSubview:childView2 atIndex:0];
RCTView *childView3 = [RCTView new];
[childView3 reactSetFrame:CGRectMake(75, 25, 150, 150)];
[childView3 setReactZIndex:3];
[clippingView insertReactSubview:childView3 atIndex:0];
[clippingView didUpdateReactSubviews];
[clippingView clearSortedSubviews];
XCTAssert(([clippingView.subviews isEqualToArray:@[childView1, childView2, childView3]]));
[clippingView rct_setRemovesClippedSubviews:YES];
XCTAssert(([clippingView.subviews isEqualToArray:@[childView1, childView3]]));
[clippingView rct_setRemovesClippedSubviews:NO];
XCTAssert(([clippingView.subviews isEqualToArray:@[childView1, childView2, childView3]]));
}
#pragma mark - recursive clipping tests
- (void)testNotDirectSubviewIsClipped
{
RCTView *clippingView = [RCTView new];
[clippingView rct_setRemovesClippedSubviews:YES];
[clippingView reactSetFrame:CGRectMake(0, 0, 50, 50)];
RCTView *directChildView = [RCTView new];
[directChildView reactSetFrame:CGRectMake(0, 0, 50, 50)];
[clippingView insertReactSubview:directChildView atIndex:0];
[clippingView didUpdateReactSubviews];
RCTView *deeperChildView = [RCTView new];
[deeperChildView reactSetFrame:CGRectMake(0, 100, 50, 50)];
[directChildView insertReactSubview:deeperChildView atIndex:0];
[directChildView didUpdateReactSubviews];
XCTAssertEqual(directChildView.subviews.count, 0u);
}
/** There are three views, top two ones clip and the bottom one is outside of the top one's bounds and in side of the middle one. */
- (void)testUpperClippingViewClips
{
RCTView *upperClippingView = [RCTView new];
[upperClippingView rct_setRemovesClippedSubviews:YES];
[upperClippingView reactSetFrame:CGRectMake(0, 0, 50, 50)];
RCTView *lowerClippingView = [RCTView new];
[lowerClippingView reactSetFrame:CGRectMake(0, 0, 50, 100)];
[lowerClippingView rct_setRemovesClippedSubviews:YES];
[upperClippingView insertReactSubview:lowerClippingView atIndex:0];
[upperClippingView didUpdateReactSubviews];
RCTView *viewToBeClipped = [RCTView new];
[viewToBeClipped reactSetFrame:CGRectMake(0, 50, 50, 50)];
[lowerClippingView insertReactSubview:viewToBeClipped atIndex:0];
[lowerClippingView didUpdateReactSubviews];
XCTAssertEqual(upperClippingView.subviews.count, 1u);
XCTAssertEqual(lowerClippingView.subviews.count, 0u);
}
#pragma mark - ScrollView tests
- (void)testScrollViewClips
{
RCTScrollView *scrollView = [[RCTScrollView alloc] initWithEventDispatcher:[OCMockObject mockForClass:[RCTEventDispatcher class]]];
[scrollView reactSetFrame:CGRectMake(0, 0, 320, 480)];
[scrollView rct_setRemovesClippedSubviews:YES];
RCTView *contentView = [RCTView new];
[contentView rct_setRemovesClippedSubviews:YES];
// Content view is big enough to fit all rows. It's an implementation detail of ScrollView.js.
[contentView reactSetFrame:CGRectMake(0, 0, 320, 550)];
[scrollView insertReactSubview:contentView atIndex:0];
[scrollView didUpdateReactSubviews];
RCTView *rowView1 = [RCTView new];
[rowView1 reactSetFrame:CGRectMake(0, 0, 320, 50)];
RCTView *rowView2 = [RCTView new];
[rowView2 reactSetFrame:CGRectMake(0, 200, 320, 50)];
RCTView *rowView3 = [RCTView new];
[rowView3 reactSetFrame:CGRectMake(0, 500, 320, 50)];
[contentView insertReactSubview:rowView1 atIndex:0];
[contentView insertReactSubview:rowView2 atIndex:0];
[contentView insertReactSubview:rowView3 atIndex:0];
[contentView didUpdateReactSubviews];
// This makes sure the direct subview of scrollView gets frame too (implementation detial).
[scrollView layoutSubviews];
XCTAssert([[NSSet setWithArray:contentView.subviews] isEqualToSet:[NSSet setWithArray:(@[rowView1, rowView2])]]);
}
- (void)testScrollViewClipsDuringScrolling
{
// Scrollview will try to emit events during scrolling, so we need to use a "nice" mock.
RCTScrollView *scrollView = [[RCTScrollView alloc] initWithEventDispatcher:[OCMockObject niceMockForClass:[RCTEventDispatcher class]]];
[scrollView reactSetFrame:CGRectMake(0, 0, 320, 480)];
[scrollView rct_setRemovesClippedSubviews:YES];
scrollView.reactTag = @2;
RCTView *contentView = [RCTView new];
[contentView rct_setRemovesClippedSubviews:YES];
// Content view is big enough to fit all rows. It's an implementation detail of ScrollView.js.
[contentView reactSetFrame:CGRectMake(0, 0, 320, 550)];
[scrollView insertReactSubview:contentView atIndex:0];
[scrollView didUpdateReactSubviews];
RCTView *rowView1 = [RCTView new];
[rowView1 reactSetFrame:CGRectMake(0, 0, 320, 50)];
RCTView *rowView2 = [RCTView new];
[rowView2 reactSetFrame:CGRectMake(0, 200, 320, 50)];
RCTView *rowView3 = [RCTView new];
[rowView3 reactSetFrame:CGRectMake(0, 500, 320, 50)];
[contentView insertReactSubview:rowView1 atIndex:0];
[contentView insertReactSubview:rowView2 atIndex:0];
[contentView insertReactSubview:rowView3 atIndex:0];
[contentView didUpdateReactSubviews];
// This makes sure the direct subview of scrollView gets frame too (implementation detial).
[scrollView layoutSubviews];
[scrollView scrollToOffset:CGPointMake(0, 100)];
XCTAssert([[NSSet setWithArray:contentView.subviews] isEqualToSet:[NSSet setWithArray:(@[rowView2, rowView3])]]);
}
@end