Skip to content

Commit a7e6eea

Browse files
Ovidiu Viorel IepureFacebook Github Bot 7
authored andcommitted
Open sourced CatalystSubviewsClippingTestCase test for RN Android
Reviewed By: bestander Differential Revision: D3411402 fbshipit-source-id: c4f51f0366c30815a3a409ee13b61900d882fcc9
1 parent c99fb9c commit a7e6eea

File tree

4 files changed

+615
-0
lines changed

4 files changed

+615
-0
lines changed

ReactAndroid/src/androidTest/java/com/facebook/react/tests/BUCK

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ SANDCASTLE_FLAKY = [
99

1010
deps = [
1111
react_native_dep('third-party/android/support/v4:lib-support-v4'),
12+
react_native_dep('third-party/java/jsr-305:jsr-305'),
1213
react_native_dep('third-party/java/junit:junit'),
1314
react_native_integration_tests_target('java/com/facebook/react/testing:testing'),
1415
react_native_target('java/com/facebook/react/bridge:bridge'),
Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
1+
/**
2+
* Copyright (c) 2013-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*/
9+
10+
package com.facebook.react.tests;
11+
12+
import java.util.ArrayList;
13+
import java.util.List;
14+
15+
import android.content.Context;
16+
import javax.annotation.Nullable;
17+
import android.widget.ScrollView;
18+
19+
import com.facebook.react.bridge.JavaScriptModule;
20+
import com.facebook.react.testing.ReactAppInstrumentationTestCase;
21+
import com.facebook.react.testing.ReactInstanceSpecForTest;
22+
import com.facebook.react.uimanager.PixelUtil;
23+
import com.facebook.react.uimanager.annotations.ReactProp;
24+
import com.facebook.react.uimanager.ThemedReactContext;
25+
import com.facebook.react.views.view.ReactViewGroup;
26+
import com.facebook.react.views.view.ReactViewManager;
27+
28+
import org.junit.Assert;
29+
import org.junit.Ignore;
30+
31+
/**
32+
* Integration test for {@code removeClippedSubviews} property that verify correct scrollview
33+
* behavior
34+
*/
35+
public class CatalystSubviewsClippingTestCase extends ReactAppInstrumentationTestCase {
36+
37+
private interface SubviewsClippingTestModule extends JavaScriptModule {
38+
void renderClippingSample1();
39+
void renderClippingSample2();
40+
void renderScrollViewTest();
41+
void renderUpdatingSample1(boolean update1, boolean update2);
42+
void renderUpdatingSample2(boolean update);
43+
}
44+
45+
private final List<String> mEvents = new ArrayList<>();
46+
47+
@Override
48+
protected String getReactApplicationKeyUnderTest() {
49+
return "SubviewsClippingTestApp";
50+
}
51+
52+
@Override
53+
protected ReactInstanceSpecForTest createReactInstanceSpecForTest() {
54+
ReactInstanceSpecForTest instanceSpec = new ReactInstanceSpecForTest();
55+
instanceSpec.addJSModule(SubviewsClippingTestModule.class);
56+
instanceSpec.addViewManager(new ClippableViewManager(mEvents));
57+
return instanceSpec;
58+
}
59+
60+
/**
61+
* In this test view are layout in a following way:
62+
* +-----------------------------+
63+
* | |
64+
* | +---------------------+ |
65+
* | | inner1 | |
66+
* | +---------------------+ |
67+
* | +-------------------------+ |
68+
* | | outer (clip=true) | |
69+
* | | +---------------------+ | |
70+
* | | | inner2 | | |
71+
* | | +---------------------+ | |
72+
* | | | |
73+
* | +-------------------------+ |
74+
* | +---------------------+ |
75+
* | | inner3 | |
76+
* | +---------------------+ |
77+
* | |
78+
* +-----------------------------+
79+
*
80+
* We expect only outer and inner2 to be attached
81+
*/
82+
public void XtestOneLevelClippingInView() throws Throwable {
83+
mEvents.clear();
84+
getReactContext().getJSModule(SubviewsClippingTestModule.class).renderClippingSample1();
85+
waitForBridgeAndUIIdle();
86+
Assert.assertArrayEquals(new String[]{"Attach_outer", "Attach_inner2"}, mEvents.toArray());
87+
}
88+
89+
/**
90+
* In this test view are layout in a following way:
91+
* +-----------------------------+
92+
* | outer (clip=true) |
93+
* | |
94+
* | |
95+
* | |
96+
* | +-----------------------------+
97+
* | | complexInner (clip=true) |
98+
* | | +----------+ | +---------+ |
99+
* | | | inner1 | | | inner2 | |
100+
* | | | | | | | |
101+
* | | +----------+ | +---------+ |
102+
* +--------------+--------------+ |
103+
* | +----------+ +---------+ |
104+
* | | inner3 | | inner4 | |
105+
* | | | | | |
106+
* | +----------+ +---------+ |
107+
* | |
108+
* +-----------------------------+
109+
*
110+
* We expect outer, complexInner & inner1 to be attached
111+
*/
112+
public void XtestTwoLevelClippingInView() throws Throwable {
113+
mEvents.clear();
114+
getReactContext().getJSModule(SubviewsClippingTestModule.class).renderClippingSample2();
115+
waitForBridgeAndUIIdle();
116+
Assert.assertArrayEquals(
117+
new String[]{"Attach_outer", "Attach_complexInner", "Attach_inner1"},
118+
mEvents.toArray());
119+
}
120+
121+
/**
122+
* This test verifies that we update clipped subviews appropriately when some of them gets
123+
* re-layouted.
124+
*
125+
* In this test scenario we render clipping view ("outer") with two subviews, one is outside and
126+
* clipped and one is inside (absolutely positioned). By updating view props we first change the
127+
* height of the first element so that it should intersect with clipping "outer" view. Then we
128+
* update top position of the second view so that is should go off screen.
129+
*/
130+
public void testClippingAfterLayoutInner() {
131+
SubviewsClippingTestModule subviewsClippingTestModule =
132+
getReactContext().getJSModule(SubviewsClippingTestModule.class);
133+
134+
mEvents.clear();
135+
subviewsClippingTestModule.renderUpdatingSample1(false, false);
136+
waitForBridgeAndUIIdle();
137+
Assert.assertArrayEquals(new String[]{"Attach_outer", "Attach_inner2"}, mEvents.toArray());
138+
139+
mEvents.clear();
140+
subviewsClippingTestModule.renderUpdatingSample1(true, false);
141+
waitForBridgeAndUIIdle();
142+
Assert.assertArrayEquals(new String[]{"Attach_inner1"}, mEvents.toArray());
143+
144+
mEvents.clear();
145+
subviewsClippingTestModule.renderUpdatingSample1(true, true);
146+
waitForBridgeAndUIIdle();
147+
Assert.assertArrayEquals(new String[]{"Detach_inner2"}, mEvents.toArray());
148+
}
149+
150+
/**
151+
* This test verifies that we update clipping views appropriately when parent view layout changes
152+
* in a way that affects clipping.
153+
*
154+
* In this test we render clipping view ("outer") set to be 100x100dp with inner view that is
155+
* absolutely positioned out of the clipping area of the parent view. Then we resize parent view
156+
* so that inner view should be visible.
157+
*/
158+
public void testClippingAfterLayoutParent() {
159+
SubviewsClippingTestModule subviewsClippingTestModule =
160+
getReactContext().getJSModule(SubviewsClippingTestModule.class);
161+
162+
mEvents.clear();
163+
subviewsClippingTestModule.renderUpdatingSample2(false);
164+
waitForBridgeAndUIIdle();
165+
Assert.assertArrayEquals(new String[]{"Attach_outer"}, mEvents.toArray());
166+
167+
mEvents.clear();
168+
subviewsClippingTestModule.renderUpdatingSample2(true);
169+
waitForBridgeAndUIIdle();
170+
Assert.assertArrayEquals(new String[]{"Attach_inner"}, mEvents.toArray());
171+
}
172+
173+
public void testOneLevelClippingInScrollView() throws Throwable {
174+
getReactContext().getJSModule(SubviewsClippingTestModule.class).renderScrollViewTest();
175+
waitForBridgeAndUIIdle();
176+
177+
// Only 3 first views should be attached at the beginning
178+
Assert.assertArrayEquals(new String[]{"Attach_0", "Attach_1", "Attach_2"}, mEvents.toArray());
179+
mEvents.clear();
180+
181+
// We scroll down such that first view get out of the bounds, we expect the first view to be
182+
// detached and 4th view to get attached
183+
scrollToDpInUIThread(120);
184+
Assert.assertArrayEquals(new String[]{"Detach_0", "Attach_3"}, mEvents.toArray());
185+
}
186+
187+
public void testTwoLevelClippingInScrollView() throws Throwable {
188+
getReactContext().getJSModule(SubviewsClippingTestModule.class).renderScrollViewTest();
189+
waitForBridgeAndUIIdle();
190+
191+
final int complexViewOffset = 4 * 120 - 300;
192+
193+
// Step 1
194+
// We scroll down such that first "complex" view is clipped & just below the bottom of the
195+
// scroll view
196+
scrollToDpInUIThread(complexViewOffset);
197+
198+
mEvents.clear();
199+
200+
// Step 2
201+
// Scroll a little bit so that "complex" view is visible, but it's inner views are not
202+
scrollToDpInUIThread(complexViewOffset + 5);
203+
204+
Assert.assertArrayEquals(new String[]{"Attach_C0"}, mEvents.toArray());
205+
mEvents.clear();
206+
207+
// Step 3
208+
// Scroll even more so that first subview of "complex" view is visible, view 1 will get off
209+
// screen
210+
scrollToDpInUIThread(complexViewOffset + 100);
211+
Assert.assertArrayEquals(new String[]{"Detach_1", "Attach_C0.1"}, mEvents.toArray());
212+
mEvents.clear();
213+
214+
// Step 4
215+
// Scroll even more to reveal second subview of "complex" view
216+
scrollToDpInUIThread(complexViewOffset + 150);
217+
Assert.assertArrayEquals(new String[]{"Attach_C0.2"}, mEvents.toArray());
218+
mEvents.clear();
219+
220+
// Step 5
221+
// Scroll back to previous position (Step 3), second view should get detached
222+
scrollToDpInUIThread(complexViewOffset + 100);
223+
Assert.assertArrayEquals(new String[]{"Detach_C0.2"}, mEvents.toArray());
224+
mEvents.clear();
225+
226+
// Step 6
227+
// Scroll back to Step 2, complex view should be visible but all subviews should be detached
228+
scrollToDpInUIThread(complexViewOffset + 5);
229+
Assert.assertArrayEquals(new String[]{"Attach_1", "Detach_C0.1"}, mEvents.toArray());
230+
mEvents.clear();
231+
232+
// Step 7
233+
// Scroll back to Step 1, complex view should be gone
234+
scrollToDpInUIThread(complexViewOffset);
235+
Assert.assertArrayEquals(new String[]{"Detach_C0"}, mEvents.toArray());
236+
}
237+
238+
private void scrollToDpInUIThread(final int yPositionInDP) throws Throwable {
239+
final ScrollView mainScrollView = getViewByTestId("scroll_view");
240+
runTestOnUiThread(
241+
new Runnable() {
242+
@Override
243+
public void run() {
244+
mainScrollView.scrollTo(0, (int) PixelUtil.toPixelFromDIP(yPositionInDP));
245+
}
246+
});
247+
waitForBridgeAndUIIdle();
248+
}
249+
250+
private static class ClippableView extends ReactViewGroup {
251+
252+
private String mClippableViewID;
253+
private final List<String> mEvents;
254+
255+
public ClippableView(Context context, List<String> events) {
256+
super(context);
257+
mEvents = events;
258+
}
259+
260+
@Override
261+
protected void onAttachedToWindow() {
262+
super.onAttachedToWindow();
263+
mEvents.add("Attach_" + mClippableViewID);
264+
}
265+
266+
@Override
267+
protected void onDetachedFromWindow() {
268+
super.onDetachedFromWindow();
269+
mEvents.add("Detach_" + mClippableViewID);
270+
}
271+
272+
public void setClippableViewID(String clippableViewID) {
273+
mClippableViewID = clippableViewID;
274+
}
275+
}
276+
277+
private static class ClippableViewManager extends ReactViewManager {
278+
279+
private final List<String> mEvents;
280+
281+
public ClippableViewManager(List<String> events) {
282+
mEvents = events;
283+
}
284+
285+
@Override
286+
public String getName() {
287+
return "ClippableView";
288+
}
289+
290+
@Override
291+
public ReactViewGroup createViewInstance(ThemedReactContext context) {
292+
return new ClippableView(context, mEvents);
293+
}
294+
295+
@ReactProp(name = "clippableViewID")
296+
public void setClippableViewId(ReactViewGroup view, @Nullable String clippableViewId) {
297+
((ClippableView) view).setClippableViewID(clippableViewId);
298+
}
299+
}
300+
}

0 commit comments

Comments
 (0)