reverted view clipping changes

Summary: Reveting the recent view clipping changes, since it doesn't work well with modals and the fix is not super simple.

Reviewed By: mmmulani

Differential Revision: D4204490

fbshipit-source-id: 510f2b04c604b3f3a223dc4accb424b030876fbe
This commit is contained in:
Martin Kralik
2016-11-18 14:33:23 -08:00
committed by Facebook Github Bot
parent cac64f98a9
commit a78ee4323b
11 changed files with 234 additions and 687 deletions

View File

@@ -105,7 +105,6 @@
2DD323EA1DA2DE3F000FE1B8 /* libReact-tvOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 2DD323D91DA2DD8B000FE1B8 /* libReact-tvOS.a */; };
3578590A1B28D2CF00341EDB /* libRCTLinking.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 357859011B28D2C500341EDB /* libRCTLinking.a */; };
39AA31A41DC1DFDC000F7EBB /* RCTUnicodeDecodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 39AA31A31DC1DFDC000F7EBB /* RCTUnicodeDecodeTests.m */; };
397D6A731DB12C1100E99986 /* RCTSubviewClippingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 397D6A711DB12C1100E99986 /* RCTSubviewClippingTests.m */; };
3D13F8481D6F6AF900E69E0E /* ImageInBundle.png in Resources */ = {isa = PBXBuildFile; fileRef = 3D13F8441D6F6AF200E69E0E /* ImageInBundle.png */; };
3D13F84A1D6F6AFD00E69E0E /* OtherImages.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 3D13F8451D6F6AF200E69E0E /* OtherImages.xcassets */; };
3D299BAF1D33EBFA00FA1057 /* RCTLoggingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3D299BAE1D33EBFA00FA1057 /* RCTLoggingTests.m */; };
@@ -391,7 +390,6 @@
2DD323A51DA2DD8B000FE1B8 /* UIExplorer-tvOSUnitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "UIExplorer-tvOSUnitTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
357858F81B28D2C400341EDB /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = ../../Libraries/LinkingIOS/RCTLinking.xcodeproj; sourceTree = "<group>"; };
39AA31A31DC1DFDC000F7EBB /* RCTUnicodeDecodeTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTUnicodeDecodeTests.m; sourceTree = "<group>"; };
397D6A711DB12C1100E99986 /* RCTSubviewClippingTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTSubviewClippingTests.m; sourceTree = "<group>"; };
3D13F83E1D6F6AE000E69E0E /* UIExplorerBundle.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UIExplorerBundle.bundle; sourceTree = BUILT_PRODUCTS_DIR; };
3D13F8401D6F6AE000E69E0E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = ../Info.plist; sourceTree = "<group>"; };
3D13F8441D6F6AF200E69E0E /* ImageInBundle.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = ImageInBundle.png; sourceTree = "<group>"; };
@@ -618,7 +616,6 @@
143BC57C1B21E18100462512 /* UIExplorerUnitTests */ = {
isa = PBXGroup;
children = (
397D6A711DB12C1100E99986 /* RCTSubviewClippingTests.m */,
13B6C1A21C34225900D3FAF5 /* RCTURLUtilsTests.m */,
68FF44371CF6111500720EFD /* RCTBundleURLProviderTests.m */,
1497CFA41B21F5E400C1F8F2 /* RCTAllocationTests.m */,
@@ -1344,7 +1341,6 @@
1497CFAE1B21F5E400C1F8F2 /* RCTJSCExecutorTests.m in Sources */,
13129DD41C85F87C007D611C /* RCTModuleInitNotificationRaceTests.m in Sources */,
1497CFAD1B21F5E400C1F8F2 /* RCTBridgeTests.m in Sources */,
397D6A731DB12C1100E99986 /* RCTSubviewClippingTests.m in Sources */,
134CB92A1C85A38800265FA6 /* RCTModuleInitTests.m in Sources */,
1497CFB11B21F5E400C1F8F2 /* RCTEventDispatcherTests.m in Sources */,
1497CFB31B21F5E400C1F8F2 /* RCTUIManagerTests.m in Sources */,

View File

@@ -1,407 +0,0 @@
/**
* 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])]]);
}
/**
In this test case the react view hiearchy is
clippingView -> directReactChildView -> deeperChildView
while the uiview hierarchy is
clippingView -> nonReactChildView -> directReactChildView -> deeperChildView
*/
- (void)testClippingWhenReactHierarchyDoesntMatchUIHierarchy
{
RCTView *clippingView = [RCTView new];
[clippingView reactSetFrame:CGRectMake(0, 0, 50, 50)];
RCTView *directReactChildView = [RCTView new];
[directReactChildView reactSetFrame:CGRectMake(-50, 0, 100, 50)];
[clippingView insertReactSubview:directReactChildView atIndex:0];
[clippingView didUpdateReactSubviews];
RCTView *deeperChildView = [RCTView new];
[deeperChildView reactSetFrame:CGRectMake(0, 0, 50, 50)];
[directReactChildView insertReactSubview:deeperChildView atIndex:0];
[directReactChildView didUpdateReactSubviews];
UIView *nonReactChildView = [UIView new];
[nonReactChildView setFrame:CGRectMake(50, 0, 50, 50)];
[clippingView addSubview:nonReactChildView];
[nonReactChildView addSubview:directReactChildView];
[clippingView rct_setRemovesClippedSubviews:YES];
[directReactChildView reactSetFrame:CGRectMake(-50, 0, 99, 50)];
XCTAssertEqual(clippingView.subviews.count, 1u);
XCTAssertEqual(nonReactChildView.subviews.count, 1u);
XCTAssertEqual(directReactChildView.subviews.count, 1u);
}
@end