/**
* Copyright (c) 2017-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.litho.widget;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.OrientationHelper;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.OnScrollListener;
import com.facebook.litho.Component;
import com.facebook.litho.ComponentContext;
import com.facebook.litho.ComponentInfo;
import com.facebook.litho.ComponentTree;
import com.facebook.litho.LayoutHandler;
import com.facebook.litho.Size;
import com.facebook.litho.SizeSpec;
import com.facebook.litho.testing.testrunner.ComponentsTestRunner;
import junit.framework.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.rule.PowerMockRule;
import org.robolectric.RuntimeEnvironment;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* Tests for {@link RecyclerBinder}
*/
@RunWith(ComponentsTestRunner.class)
@PrepareForTest(ComponentTreeHolder.class)
@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"})
public class RecyclerBinderTest {
@Rule
public PowerMockRule mPowerMockRule = new PowerMockRule();
private static final float RANGE_RATIO = 2.0f;
private static final int RANGE_SIZE = 3;
private final Map<Component, TestComponentTreeHolder> mHoldersForComponents = new HashMap<>();
private RecyclerBinder mRecyclerBinder;
private LayoutInfo mLayoutInfo;
private ComponentContext mComponentContext;
private final Answer<ComponentTreeHolder> mComponentTreeHolderAnswer =
new Answer<ComponentTreeHolder>() {
@Override
public ComponentTreeHolder answer(InvocationOnMock invocation) throws Throwable {
final ComponentInfo componentInfo = (ComponentInfo) invocation.getArguments()[0];
final TestComponentTreeHolder holder = new TestComponentTreeHolder(componentInfo);
mHoldersForComponents.put(componentInfo.getComponent(), holder);
return holder;
}
};
@Before
public void setup() {
mComponentContext = new ComponentContext(RuntimeEnvironment.application);
PowerMockito.mockStatic(ComponentTreeHolder.class);
PowerMockito.when(ComponentTreeHolder.acquire(
any(ComponentInfo.class),
any(LayoutHandler.class),
any(boolean.class)))
.thenAnswer(mComponentTreeHolderAnswer);
mLayoutInfo = mock(LayoutInfo.class);
setupBaseLayoutInfoMock();
mRecyclerBinder = new RecyclerBinder(mComponentContext, RANGE_RATIO, mLayoutInfo);
}
private void setupBaseLayoutInfoMock() {
when(mLayoutInfo.getScrollDirection()).thenReturn(OrientationHelper.VERTICAL);
when(mLayoutInfo.getLayoutManager())
.thenReturn(new LinearLayoutManager(mComponentContext));
when(mLayoutInfo.approximateRangeSize(anyInt(), anyInt(), anyInt(), anyInt()))
.thenReturn(RANGE_SIZE);
when(mLayoutInfo.getChildHeightSpec(anyInt(), any(ComponentInfo.class)))
.thenReturn(SizeSpec.makeSizeSpec(100, SizeSpec.EXACTLY));
when(mLayoutInfo.getChildWidthSpec(anyInt(), any(ComponentInfo.class)))
.thenReturn(SizeSpec.makeSizeSpec(100, SizeSpec.EXACTLY));
}
@Test
public void testComponentTreeHolderCreation() {
final List<ComponentInfo> components = new ArrayList<>();
for (int i = 0; i < 100; i++) {
components.add(ComponentInfo.create().component(mock(Component.class)).build());
mRecyclerBinder.insertItemAt(0, components.get(i));
}
for (int i = 0; i < 100; i++) {
Assert.assertNotNull(mHoldersForComponents.get(components.get(i).getComponent()));
}
}
@Test
public void testOnMeasureAfterAddingItems() {
final List<ComponentInfo> components = new ArrayList<>();
for (int i = 0; i < 100; i++) {
components.add(ComponentInfo.create().component(mock(Component.class)).build());
mRecyclerBinder.insertItemAt(i, components.get(i));
}
for (int i = 0; i < 100; i++) {
Assert.assertNotNull(mHoldersForComponents.get(components.get(i).getComponent()));
}
final Size size = new Size();
final int widthSpec = SizeSpec.makeSizeSpec(200, SizeSpec.AT_MOST);
final int heightSpec = SizeSpec.makeSizeSpec(200, SizeSpec.EXACTLY);
mRecyclerBinder.measure(size, widthSpec, heightSpec);
TestComponentTreeHolder componentTreeHolder =
mHoldersForComponents.get(components.get(0).getComponent());
assertTrue(componentTreeHolder.isTreeValid());
assertTrue(componentTreeHolder.mLayoutSyncCalled);
int rangeTotal = RANGE_SIZE + (int) (RANGE_SIZE * RANGE_RATIO);
for (int i = 1; i <= rangeTotal; i++) {
componentTreeHolder = mHoldersForComponents.get(components.get(i).getComponent());
assertTrue(componentTreeHolder.isTreeValid());
assertTrue(componentTreeHolder.mLayoutAsyncCalled);
assertFalse(componentTreeHolder.mLayoutSyncCalled);
}
for (int k = rangeTotal + 1; k < components.size(); k++) {
componentTreeHolder = mHoldersForComponents.get(components.get(k).getComponent());
assertFalse(componentTreeHolder.isTreeValid());
assertFalse(componentTreeHolder.mLayoutAsyncCalled);
assertFalse(componentTreeHolder.mLayoutSyncCalled);
}
Assert.assertEquals(100, size.width);
}
@Test
public void onBoundsDefined() {
final List<ComponentInfo> components = prepareLoadedBinder();
for (int i = 0; i < components.size(); i++) {
final TestComponentTreeHolder holder =
mHoldersForComponents.get(components.get(i).getComponent());
holder.mLayoutAsyncCalled = false;
holder.mLayoutSyncCalled = false;
}
mRecyclerBinder.setSize(200, 200);
for (int i = 0; i < components.size(); i++) {
final TestComponentTreeHolder holder =
mHoldersForComponents.get(components.get(i).getComponent());
assertFalse(holder.mLayoutAsyncCalled);
assertFalse(holder.mLayoutSyncCalled);
}
}
@Test
public void onBoundsDefinedWithDifferentSize() {
final List<ComponentInfo> components = prepareLoadedBinder();
for (int i = 0; i < components.size(); i++) {
final TestComponentTreeHolder holder =
mHoldersForComponents.get(components.get(i).getComponent());
holder.mLayoutAsyncCalled = false;
holder.mLayoutSyncCalled = false;
}
mRecyclerBinder.setSize(300, 200);
final int rangeTotal = (int) (RANGE_SIZE + (RANGE_RATIO * RANGE_SIZE));
TestComponentTreeHolder holder = mHoldersForComponents.get(components.get(0).getComponent());
assertTrue(holder.isTreeValid());
assertTrue(holder.mLayoutSyncCalled);
for (int i = 1; i <= rangeTotal; i++) {
holder = mHoldersForComponents.get(components.get(i).getComponent());
assertTrue(holder.isTreeValid());
assertTrue(holder.mLayoutAsyncCalled);
}
for (int i = rangeTotal + 1; i < components.size(); i++) {
holder = mHoldersForComponents.get(components.get(i).getComponent());
assertFalse(holder.isTreeValid());
assertFalse(holder.mLayoutAsyncCalled);
assertFalse(holder.mLayoutSyncCalled);
}
}
@Test
public void testMount() {
RecyclerView recyclerView = mock(RecyclerView.class);
mRecyclerBinder.mount(recyclerView);
verify(recyclerView).setLayoutManager(mLayoutInfo.getLayoutManager());
verify(recyclerView).setAdapter(any(RecyclerView.Adapter.class));
verify(mLayoutInfo).setComponentInfoCollection(mRecyclerBinder);
verify(recyclerView).addOnScrollListener(any(OnScrollListener.class));
}
@Test
public void testMountWithStaleView() {
RecyclerView recyclerView = mock(RecyclerView.class);
mRecyclerBinder.mount(recyclerView);
verify(recyclerView).setLayoutManager(mLayoutInfo.getLayoutManager());
verify(recyclerView).setAdapter(any(RecyclerView.Adapter.class));
verify(recyclerView).addOnScrollListener(any(OnScrollListener.class));
RecyclerView secondRecyclerView = mock(RecyclerView.class);
mRecyclerBinder.mount(secondRecyclerView);
verify(recyclerView).setLayoutManager(null);
verify(recyclerView).setAdapter(null);
verify(recyclerView).removeOnScrollListener(any(OnScrollListener.class));
verify(secondRecyclerView).setLayoutManager(mLayoutInfo.getLayoutManager());
verify(secondRecyclerView).setAdapter(any(RecyclerView.Adapter.class));
verify(secondRecyclerView).addOnScrollListener(any(OnScrollListener.class));
}
@Test
public void testUnmount() {
RecyclerView recyclerView = mock(RecyclerView.class);
mRecyclerBinder.mount(recyclerView);
verify(recyclerView).setLayoutManager(mLayoutInfo.getLayoutManager());
verify(recyclerView).setAdapter(any(RecyclerView.Adapter.class));
verify(recyclerView).addOnScrollListener(any(OnScrollListener.class));
mRecyclerBinder.unmount(recyclerView);
verify(recyclerView).setLayoutManager(null);
verify(recyclerView).setAdapter(null);
verify(mLayoutInfo).setComponentInfoCollection(null);
verify(recyclerView).removeOnScrollListener(any(OnScrollListener.class));
}
@Test
public void testAddStickyHeaderIfRecyclerViewWrapperExists() throws Exception {
RecyclerView recyclerView = mock(RecyclerView.class);
RecyclerViewWrapper wrapper = mock(RecyclerViewWrapper.class);
when(recyclerView.getParent()).thenReturn(wrapper);
when(recyclerView.getLayoutManager()).thenReturn(mock(RecyclerView.LayoutManager.class));
when(wrapper.getRecyclerView()).thenReturn(recyclerView);
mRecyclerBinder.mount(recyclerView);
verify(recyclerView).setAdapter(any(RecyclerView.Adapter.class));
verify(recyclerView, times(2)).addOnScrollListener(any(OnScrollListener.class));
}
@Test
public void onRemeasureWithDifferentSize() {
final List<ComponentInfo> components = prepareLoadedBinder();
for (int i = 0; i < components.size(); i++) {
final TestComponentTreeHolder holder =
mHoldersForComponents.get(components.get(i).getComponent());
holder.mLayoutAsyncCalled = false;
holder.mLayoutSyncCalled = false;
}
int widthSpec = SizeSpec.makeSizeSpec(100, SizeSpec.EXACTLY);
int heightSpec = SizeSpec.makeSizeSpec(200, SizeSpec.EXACTLY);
mRecyclerBinder.measure(new Size(), widthSpec, heightSpec);
final int rangeTotal = (int) (RANGE_SIZE + (RANGE_RATIO * RANGE_SIZE));
TestComponentTreeHolder holder = mHoldersForComponents.get(components.get(0).getComponent());
assertTrue(holder.isTreeValid());
assertTrue(holder.mLayoutSyncCalled);
for (int i = 1; i <= rangeTotal; i++) {
holder = mHoldersForComponents.get(components.get(i).getComponent());
assertTrue(holder.isTreeValid());
assertTrue(holder.mLayoutAsyncCalled);
}
for (int i = rangeTotal + 1; i < components.size(); i++) {
holder = mHoldersForComponents.get(components.get(i).getComponent());
assertFalse(holder.isTreeValid());
assertFalse(holder.mLayoutAsyncCalled);
assertFalse(holder.mLayoutSyncCalled);
}
}
@Test
public void testComponentWithDifferentSpanSize() {
final List<ComponentInfo> components = new ArrayList<>();
for (int i = 0; i < 100; i++) {
components.add(ComponentInfo.create()
.component(mock(Component.class))
.spanSize((i == 0 || i % 3 == 0) ? 2 : 1)
.build());
mRecyclerBinder.insertItemAt(i, components.get(i));
}
when(mLayoutInfo.getChildWidthSpec(anyInt(), any(ComponentInfo.class)))
.thenAnswer(new Answer<Integer>() {
@Override
public Integer answer(InvocationOnMock invocation) throws Throwable {
final ComponentInfo componentInfo = (ComponentInfo) invocation.getArguments()[1];
final int spanSize = componentInfo.getSpanSize();
return SizeSpec.makeSizeSpec(100 * spanSize, SizeSpec.EXACTLY);
}
});
for (int i = 0; i < 100; i++) {
Assert.assertNotNull(mHoldersForComponents.get(components.get(i).getComponent()));
}
Size size = new Size();
int widthSpec = SizeSpec.makeSizeSpec(200, SizeSpec.EXACTLY);
int heightSpec = SizeSpec.makeSizeSpec(200, SizeSpec.EXACTLY);
mRecyclerBinder.measure(size, widthSpec, heightSpec);
TestComponentTreeHolder componentTreeHolder =
mHoldersForComponents.get(components.get(0).getComponent());
assertTrue(componentTreeHolder.isTreeValid());
assertTrue(componentTreeHolder.mLayoutSyncCalled);
Assert.assertEquals(200, size.width);
final int rangeTotal = (int) (RANGE_SIZE + (RANGE_RATIO * RANGE_SIZE));
for (int i = 1; i <= rangeTotal; i++) {
componentTreeHolder = mHoldersForComponents.get(components.get(i).getComponent());
assertTrue(componentTreeHolder.isTreeValid());
assertTrue(componentTreeHolder.mLayoutAsyncCalled);
final int expectedWidth = i % 3 == 0 ? 200 : 100;
Assert.assertEquals(expectedWidth, componentTreeHolder.mChildWidth);
Assert.assertEquals(100, componentTreeHolder.mChildHeight);
}
}
@Test
public void testAddItemsAfterMeasuring() {
final Size size = new Size();
final int widthSpec = SizeSpec.makeSizeSpec(200, SizeSpec.EXACTLY);
final int heightSpec = SizeSpec.makeSizeSpec(200, SizeSpec.EXACTLY);
mRecyclerBinder.measure(size, widthSpec, heightSpec);
Assert.assertEquals(200, size.width);
final List<ComponentInfo> components = new ArrayList<>();
for (int i = 0; i < 100; i++) {
components.add(ComponentInfo.create().component(mock(Component.class)).build());
mRecyclerBinder.insertItemAt(i, components.get(i));
}
for (int i = 0; i < 100; i++) {
Assert.assertNotNull(mHoldersForComponents.get(components.get(i).getComponent()));
}
TestComponentTreeHolder componentTreeHolder;
int rangeTotal = RANGE_SIZE + (int) (RANGE_SIZE * RANGE_RATIO);
for (int i = 0; i < RANGE_SIZE; i++) {
componentTreeHolder = mHoldersForComponents.get(components.get(i).getComponent());
assertTrue(componentTreeHolder.isTreeValid());
assertTrue(componentTreeHolder.mLayoutSyncCalled);
}
for (int i = RANGE_SIZE; i <= rangeTotal; i++) {
componentTreeHolder = mHoldersForComponents.get(components.get(i).getComponent());
assertTrue(componentTreeHolder.isTreeValid());
assertTrue(componentTreeHolder.mLayoutAsyncCalled);
assertFalse(componentTreeHolder.mLayoutSyncCalled);
}
for (int i = rangeTotal + 1; i < components.size(); i++) {
componentTreeHolder = mHoldersForComponents.get(components.get(i).getComponent());
assertFalse(componentTreeHolder.isTreeValid());
assertFalse(componentTreeHolder.mLayoutAsyncCalled);
assertFalse(componentTreeHolder.mLayoutSyncCalled);
}
}
@Test
public void testRangeBiggerThanContent() {
final List<ComponentInfo> components = new ArrayList<>();
for (int i = 0; i < 2; i++) {
components.add(ComponentInfo.create().component(mock(Component.class)).build());
mRecyclerBinder.insertItemAt(i, components.get(i));
}
for (int i = 0; i < 2; i++) {
Assert.assertNotNull(mHoldersForComponents.get(components.get(i).getComponent()));
}
Size size = new Size();
int widthSpec = SizeSpec.makeSizeSpec(200, SizeSpec.AT_MOST);
int heightSpec = SizeSpec.makeSizeSpec(200, SizeSpec.EXACTLY);
mRecyclerBinder.measure(size, widthSpec, heightSpec);
TestComponentTreeHolder componentTreeHolder =
mHoldersForComponents.get(components.get(0).getComponent());
assertTrue(componentTreeHolder.isTreeValid());
assertTrue(componentTreeHolder.mLayoutSyncCalled);
for (int i = 1; i < 2; i++) {
componentTreeHolder = mHoldersForComponents.get(components.get(i).getComponent());
assertTrue(componentTreeHolder.isTreeValid());
assertTrue(componentTreeHolder.mLayoutAsyncCalled);
assertFalse(componentTreeHolder.mLayoutSyncCalled);
}
Assert.assertEquals(100, size.width);
}
@Test
public void testMoveRange() {
final List<ComponentInfo> components = prepareLoadedBinder();
final int newRangeStart = 40;
final int newRangeEnd = 42;
final int rangeTotal = (int) (RANGE_SIZE + (RANGE_RATIO * RANGE_SIZE));
mRecyclerBinder.onNewVisibleRange(newRangeStart, newRangeEnd);
TestComponentTreeHolder componentTreeHolder;
for (int i = 0; i < components.size(); i++) {
componentTreeHolder = mHoldersForComponents.get(components.get(i).getComponent());
if (i >= newRangeStart - (RANGE_RATIO * RANGE_SIZE) && i <= newRangeStart + rangeTotal) {
assertTrue(componentTreeHolder.isTreeValid());
assertTrue(componentTreeHolder.mLayoutAsyncCalled);
assertFalse(componentTreeHolder.mLayoutSyncCalled);
} else {
assertFalse(componentTreeHolder.isTreeValid());
assertFalse(componentTreeHolder.mLayoutAsyncCalled);
assertFalse(componentTreeHolder.mLayoutSyncCalled);
if (i <= rangeTotal) {
assertTrue(componentTreeHolder.mDidAcquireStateHandler);
} else {
assertFalse(componentTreeHolder.mDidAcquireStateHandler);
}
}
}
}
@Test
public void testRealRangeOverridesEstimatedRange() {
final List<ComponentInfo> components = prepareLoadedBinder();
final int newRangeStart = 40;
final int newRangeEnd = 50;
int rangeSize = newRangeEnd - newRangeStart;
final int rangeTotal = (int) (rangeSize + (RANGE_RATIO * rangeSize));
mRecyclerBinder.onNewVisibleRange(newRangeStart, newRangeEnd);
TestComponentTreeHolder componentTreeHolder;
for (int i = 0; i < components.size(); i++) {
componentTreeHolder = mHoldersForComponents.get(components.get(i).getComponent());
if (i >= newRangeStart - (RANGE_RATIO * rangeSize) && i <= newRangeStart + rangeTotal) {
assertTrue(componentTreeHolder.isTreeValid());
assertTrue(componentTreeHolder.mLayoutAsyncCalled);
assertFalse(componentTreeHolder.mLayoutSyncCalled);
} else {
assertFalse(componentTreeHolder.isTreeValid());
assertFalse(componentTreeHolder.mLayoutAsyncCalled);
assertFalse(componentTreeHolder.mLayoutSyncCalled);
}
}
}
@Test
public void testStickyComponentsStayValidOutsideRange() {
final List<ComponentInfo> components = prepareLoadedBinder();
makeIndexSticky(components, 5);
makeIndexSticky(components, 40);
makeIndexSticky(components, 80);
Size size = new Size();
int widthSpec = SizeSpec.makeSizeSpec(200, SizeSpec.EXACTLY);
int heightSpec = SizeSpec.makeSizeSpec(200, SizeSpec.EXACTLY);
mRecyclerBinder.measure(size, widthSpec, heightSpec);
assertTrue(mHoldersForComponents.get(components.get(5).getComponent()).isTreeValid());
final int newRangeStart = 40;
final int newRangeEnd = 50;
int rangeSize = newRangeEnd - newRangeStart;
final int rangeTotal = (int) (rangeSize + (RANGE_RATIO * rangeSize));
mRecyclerBinder.onNewVisibleRange(newRangeStart, newRangeEnd);
TestComponentTreeHolder componentTreeHolder;
for (int i = 0; i < components.size(); i++) {
componentTreeHolder = mHoldersForComponents.get(components.get(i).getComponent());
boolean isIndexInRange =
i >= newRangeStart - (RANGE_RATIO * rangeSize) && i <= newRangeStart + rangeTotal;
boolean isPreviouslyComputedTreeAndSticky =
i <= newRangeStart + rangeTotal && componentTreeHolder.getComponentInfo().isSticky();
if (isIndexInRange || isPreviouslyComputedTreeAndSticky) {
assertTrue(componentTreeHolder.isTreeValid());
assertTrue(componentTreeHolder.mLayoutAsyncCalled);
assertFalse(componentTreeHolder.mLayoutSyncCalled);
} else {
assertFalse(componentTreeHolder.isTreeValid());
assertFalse(componentTreeHolder.mLayoutAsyncCalled);
assertFalse(componentTreeHolder.mLayoutSyncCalled);
}
}
}
@Test
public void testMoveRangeToEnd() {
final List<ComponentInfo> components = prepareLoadedBinder();
final int newRangeStart = 99;
final int newRangeEnd = 99;
final int rangeTotal = (int) (RANGE_SIZE + (RANGE_RATIO * RANGE_SIZE));
mRecyclerBinder.onNewVisibleRange(newRangeStart, newRangeEnd);
TestComponentTreeHolder componentTreeHolder;
for (int i = 0; i < components.size(); i++) {
componentTreeHolder = mHoldersForComponents.get(components.get(i).getComponent());
if (i >= newRangeStart - (RANGE_RATIO * RANGE_SIZE) && i <= newRangeStart + rangeTotal) {
assertTrue(componentTreeHolder.isTreeValid());
assertTrue(componentTreeHolder.mLayoutAsyncCalled);
assertFalse(componentTreeHolder.mLayoutSyncCalled);
} else {
assertFalse(componentTreeHolder.isTreeValid());
assertFalse(componentTreeHolder.mLayoutAsyncCalled);
assertFalse(componentTreeHolder.mLayoutSyncCalled);
if (i <= rangeTotal) {
assertTrue(componentTreeHolder.mDidAcquireStateHandler);
} else {
assertFalse(componentTreeHolder.mDidAcquireStateHandler);
}
}
}
}
@Test
public void testMoveItemOutsideFromRange() {
final List<ComponentInfo> components = prepareLoadedBinder();
mRecyclerBinder.moveItem(0, 99);
final TestComponentTreeHolder movedHolder =
mHoldersForComponents.get(components.get(0).getComponent());
assertFalse(movedHolder.isTreeValid());
assertFalse(movedHolder.mLayoutAsyncCalled);
assertFalse(movedHolder.mLayoutSyncCalled);
assertTrue(movedHolder.mDidAcquireStateHandler);
final int rangeTotal = (int) (RANGE_SIZE + (RANGE_RATIO * RANGE_SIZE));
final TestComponentTreeHolder holderMovedIntoRange =
mHoldersForComponents.get(components.get(rangeTotal + 1).getComponent());
assertTrue(holderMovedIntoRange.isTreeValid());
assertTrue(holderMovedIntoRange.mLayoutAsyncCalled);
assertFalse(holderMovedIntoRange.mLayoutSyncCalled);
}
@Test
public void testMoveItemInsideRange() {
final List<ComponentInfo> components = prepareLoadedBinder();
mRecyclerBinder.moveItem(99, 4);
TestComponentTreeHolder movedHolder =
mHoldersForComponents.get(components.get(99).getComponent());
assertTrue(movedHolder.isTreeValid());
assertTrue(movedHolder.mLayoutAsyncCalled);
assertFalse(movedHolder.mLayoutSyncCalled);
assertFalse(movedHolder.mDidAcquireStateHandler);
final int rangeTotal = (int) (RANGE_SIZE + (RANGE_RATIO * RANGE_SIZE));
TestComponentTreeHolder holderMovedOutsideRange =
mHoldersForComponents.get(components.get(rangeTotal).getComponent());
assertFalse(holderMovedOutsideRange.isTreeValid());
assertFalse(holderMovedOutsideRange.mLayoutAsyncCalled);
assertFalse(holderMovedOutsideRange.mLayoutSyncCalled);
}
@Test
public void testMoveItemInsideVisibleRange() {
final List<ComponentInfo> components = prepareLoadedBinder();
mRecyclerBinder.moveItem(99, 2);
final TestComponentTreeHolder movedHolder =
mHoldersForComponents.get(components.get(99).getComponent());
assertTrue(movedHolder.isTreeValid());
assertFalse(movedHolder.mLayoutAsyncCalled);
assertTrue(movedHolder.mLayoutSyncCalled);
assertFalse(movedHolder.mDidAcquireStateHandler);
final int rangeTotal = (int) (RANGE_SIZE + (RANGE_RATIO * RANGE_SIZE));
final TestComponentTreeHolder holderMovedOutsideRange =
mHoldersForComponents.get(components.get(rangeTotal).getComponent());
assertFalse(holderMovedOutsideRange.isTreeValid());
assertFalse(holderMovedOutsideRange.mLayoutAsyncCalled);
assertFalse(holderMovedOutsideRange.mLayoutSyncCalled);
assertTrue(holderMovedOutsideRange.mDidAcquireStateHandler);
}
@Test
public void testMoveItemOutsideRange() {
final List<ComponentInfo> components = prepareLoadedBinder();
mRecyclerBinder.moveItem(2, 99);
final TestComponentTreeHolder movedHolder =
mHoldersForComponents.get(components.get(2).getComponent());
assertFalse(movedHolder.isTreeValid());
assertFalse(movedHolder.mLayoutAsyncCalled);
assertFalse(movedHolder.mLayoutSyncCalled);
assertTrue(movedHolder.mDidAcquireStateHandler);
final int rangeTotal = (int) (RANGE_SIZE + (RANGE_RATIO * RANGE_SIZE));
final TestComponentTreeHolder holderMovedInsideRange =
mHoldersForComponents.get(components.get(rangeTotal + 1).getComponent());
assertTrue(holderMovedInsideRange.isTreeValid());
assertTrue(holderMovedInsideRange.mLayoutAsyncCalled);
assertFalse(holderMovedInsideRange.mLayoutSyncCalled);
assertFalse(holderMovedInsideRange.mDidAcquireStateHandler);
}
@Test
public void testMoveWithinRange() {
final List<ComponentInfo> components = prepareLoadedBinder();
final TestComponentTreeHolder movedHolderOne =
mHoldersForComponents.get(components.get(0).getComponent());
final TestComponentTreeHolder movedHolderTwo =
mHoldersForComponents.get(components.get(1).getComponent());
movedHolderOne.mLayoutSyncCalled = false;
movedHolderOne.mLayoutAsyncCalled = false;
movedHolderOne.mDidAcquireStateHandler = false;
movedHolderTwo.mLayoutSyncCalled = false;
movedHolderTwo.mLayoutAsyncCalled = false;
movedHolderTwo.mDidAcquireStateHandler = false;
mRecyclerBinder.moveItem(0, 1);
assertTrue(movedHolderOne.isTreeValid());
assertFalse(movedHolderOne.mLayoutAsyncCalled);
assertFalse(movedHolderOne.mLayoutSyncCalled);
assertFalse(movedHolderOne.mDidAcquireStateHandler);
assertTrue(movedHolderTwo.isTreeValid());
assertFalse(movedHolderTwo.mLayoutAsyncCalled);
assertFalse(movedHolderTwo.mLayoutSyncCalled);
assertFalse(movedHolderTwo.mDidAcquireStateHandler);
}
@Test
public void testInsertInVisibleRange() {
final List<ComponentInfo> components = prepareLoadedBinder();
final ComponentInfo newComponentInfo =
ComponentInfo.create().component(mock(Component.class)).build();
mRecyclerBinder.insertItemAt(1, newComponentInfo);
final TestComponentTreeHolder holder =
mHoldersForComponents.get(newComponentInfo.getComponent());
assertTrue(holder.isTreeValid());
assertTrue(holder.mLayoutSyncCalled);
assertFalse(holder.mLayoutAsyncCalled);
assertFalse(holder.mDidAcquireStateHandler);
final int rangeTotal = (int) (RANGE_SIZE + (RANGE_RATIO * RANGE_SIZE));
final TestComponentTreeHolder holderMovedOutsideRange =
mHoldersForComponents.get(components.get(rangeTotal).getComponent());
assertFalse(holderMovedOutsideRange.isTreeValid());
assertFalse(holderMovedOutsideRange.mLayoutSyncCalled);
assertFalse(holderMovedOutsideRange.mLayoutAsyncCalled);
assertTrue(holderMovedOutsideRange.mDidAcquireStateHandler);
}
@Test
public void testInsertInRange() {
final List<ComponentInfo> components = prepareLoadedBinder();
final ComponentInfo newComponentInfo =
ComponentInfo.create().component(mock(Component.class)).build();
mRecyclerBinder.insertItemAt(RANGE_SIZE + 1, newComponentInfo);
final TestComponentTreeHolder holder =
mHoldersForComponents.get(newComponentInfo.getComponent());
assertTrue(holder.isTreeValid());
assertFalse(holder.mLayoutSyncCalled);
assertTrue(holder.mLayoutAsyncCalled);
assertFalse(holder.mDidAcquireStateHandler);
final int rangeTotal = (int) (RANGE_SIZE + (RANGE_RATIO * RANGE_SIZE));
final TestComponentTreeHolder holderMovedOutsideRange =
mHoldersForComponents.get(components.get(rangeTotal).getComponent());
assertFalse(holderMovedOutsideRange.isTreeValid());
assertFalse(holderMovedOutsideRange.mLayoutSyncCalled);
assertFalse(holderMovedOutsideRange.mLayoutAsyncCalled);
assertTrue(holderMovedOutsideRange.mDidAcquireStateHandler);
}
@Test
public void testInsertRange() {
final List<ComponentInfo> components = prepareLoadedBinder();
final List<ComponentInfo> newComponents = new ArrayList<>();
for (int i = 0; i < 3; i++) {
newComponents.add(
ComponentInfo.create().component(mock(Component.class)).build());
}
mRecyclerBinder.insertRangeAt(0, newComponents);
// The new elements were scheduled for layout.
for (int i = 0; i < 3; i++) {
final TestComponentTreeHolder holder =
mHoldersForComponents.get(newComponents.get(i).getComponent());
assertTrue(holder.isTreeValid());
assertFalse(holder.mLayoutSyncCalled);
assertTrue(holder.mLayoutAsyncCalled);
assertFalse(holder.mDidAcquireStateHandler);
}
final int rangeTotal = (int) (RANGE_SIZE + (RANGE_RATIO * RANGE_SIZE));
// The elements that went outside the layout range have been released.
for (int i = rangeTotal - 2; i <= rangeTotal; i++) {
final TestComponentTreeHolder holder =
mHoldersForComponents.get(components.get(i).getComponent());
assertFalse(holder.isTreeValid());
assertFalse(holder.mLayoutSyncCalled);
assertFalse(holder.mLayoutAsyncCalled);
assertTrue(holder.mDidAcquireStateHandler);
}
}
@Test
public void testInsertOusideRange() {
prepareLoadedBinder();
final ComponentInfo newComponentInfo =
ComponentInfo.create().component(mock(Component.class)).build();
final int rangeTotal = (int) (RANGE_SIZE + (RANGE_RATIO * RANGE_SIZE));
mRecyclerBinder.insertItemAt(rangeTotal + 1, newComponentInfo);
final TestComponentTreeHolder holder =
mHoldersForComponents.get(newComponentInfo.getComponent());
assertFalse(holder.isTreeValid());
assertFalse(holder.mLayoutSyncCalled);
assertFalse(holder.mLayoutAsyncCalled);
assertFalse(holder.mDidAcquireStateHandler);
}
@Test
public void testRemoveItem() {
final List<ComponentInfo> components = prepareLoadedBinder();
final int rangeTotal = (int) (RANGE_SIZE + (RANGE_RATIO * RANGE_SIZE));
mRecyclerBinder.removeItemAt(rangeTotal + 1);
final TestComponentTreeHolder holder =
mHoldersForComponents.get(components.get(rangeTotal + 1).getComponent());
assertTrue(holder.mReleased);
}
@Test
public void testRemoveFromRange() {
final List<ComponentInfo> components = prepareLoadedBinder();
final int rangeTotal = (int) (RANGE_SIZE + (RANGE_RATIO * RANGE_SIZE));
mRecyclerBinder.removeItemAt(rangeTotal);
final TestComponentTreeHolder holder =
mHoldersForComponents.get(components.get(rangeTotal).getComponent());
assertTrue(holder.mReleased);
final TestComponentTreeHolder holderMovedInRange =
mHoldersForComponents.get(components.get(rangeTotal + 1).getComponent());
assertTrue(holderMovedInRange.isTreeValid());
assertFalse(holderMovedInRange.mLayoutSyncCalled);
assertTrue(holderMovedInRange.mLayoutAsyncCalled);
assertFalse(holderMovedInRange.mDidAcquireStateHandler);
}
@Test
public void testRemoveRange() {
final List<ComponentInfo> components = prepareLoadedBinder();
final int rangeTotal = (int) (RANGE_SIZE + (RANGE_RATIO * RANGE_SIZE));
mRecyclerBinder.removeRangeAt(0, RANGE_SIZE);
// The elements that were removed have been released.
for (int i = 0; i < RANGE_SIZE; i++) {
final TestComponentTreeHolder holder =
mHoldersForComponents.get(components.get(i).getComponent());
assertTrue(holder.mReleased);
}
// The elements that are now in the range get their layout computed
for (int i = rangeTotal + 1; i <= rangeTotal + RANGE_SIZE; i++) {
final TestComponentTreeHolder holder =
mHoldersForComponents.get(components.get(i).getComponent());
assertTrue(holder.isTreeValid());
assertFalse(holder.mLayoutSyncCalled);
assertTrue(holder.mLayoutAsyncCalled);
assertFalse(holder.mDidAcquireStateHandler);
}
}
@Test
public void testUpdate() {
final List<ComponentInfo> components = prepareLoadedBinder();
final TestComponentTreeHolder holder =
mHoldersForComponents.get(components.get(0).getComponent());
assertTrue(holder.isTreeValid());
holder.mTreeValid = false;
assertFalse(holder.isTreeValid());
final ComponentInfo newComponentInfo =
ComponentInfo.create().component(mock(Component.class)).build();
mRecyclerBinder.updateItemAt(0, newComponentInfo);
assertEquals(holder.getComponentInfo(), newComponentInfo);
assertTrue(holder.isTreeValid());
}
@Test
public void testUpdateRange() {
final List<ComponentInfo> components = prepareLoadedBinder();
for (int i = 0; i < RANGE_SIZE; i++) {
final TestComponentTreeHolder holder =
mHoldersForComponents.get(components.get(i).getComponent());
assertTrue(holder.isTreeValid());
holder.mTreeValid = false;
assertFalse(holder.isTreeValid());
}
final List<ComponentInfo> newInfos = new ArrayList<>();
for (int i = 0; i < RANGE_SIZE; i++) {
newInfos.add(
ComponentInfo.create().component(mock(Component.class)).build());
}
mRecyclerBinder.updateRangeAt(0, newInfos);
for (int i = 0; i < RANGE_SIZE; i++) {
final TestComponentTreeHolder holder =
mHoldersForComponents.get(components.get(i).getComponent());
assertEquals(holder.getComponentInfo(), newInfos.get(i));
assertTrue(holder.isTreeValid());
}
}
@Test
public void testGetItemCount() {
for (int i = 0; i < 100; i++) {
assertEquals(i, mRecyclerBinder.getItemCount());
mRecyclerBinder.insertItemAt(
i,
ComponentInfo.create().component(mock(Component.class)).build());
}
}
private List<ComponentInfo> prepareLoadedBinder() {
final List<ComponentInfo> components = new ArrayList<>();
for (int i = 0; i < 100; i++) {
components.add(ComponentInfo.create().component(mock(Component.class)).build());
mRecyclerBinder.insertItemAt(i, components.get(i));
}
for (int i = 0; i < 100; i++) {
Assert.assertNotNull(mHoldersForComponents.get(components.get(i).getComponent()));
}
Size size = new Size();
int widthSpec = SizeSpec.makeSizeSpec(200, SizeSpec.EXACTLY);
int heightSpec = SizeSpec.makeSizeSpec(200, SizeSpec.EXACTLY);
mRecyclerBinder.measure(size, widthSpec, heightSpec);
return components;
}
private void makeIndexSticky(List<ComponentInfo> components, int i) {
components.set(
i,
ComponentInfo.create().component(mock(Component.class)).isSticky(true).build());
mRecyclerBinder.removeItemAt(i);
mRecyclerBinder.insertItemAt(i, components.get(i));
}
private static class TestComponentTreeHolder extends ComponentTreeHolder {
private boolean mTreeValid;
private ComponentTree mComponentTree;
private ComponentInfo mComponentInfo;
private boolean mLayoutAsyncCalled;
private boolean mLayoutSyncCalled;
private boolean mDidAcquireStateHandler;
private boolean mReleased;
private int mChildWidth;
private int mChildHeight;
private TestComponentTreeHolder(ComponentInfo componentInfo) {
mComponentInfo = componentInfo;
}
@Override
protected void release() {
mReleased = true;
}
@Override
protected synchronized void acquireStateHandlerAndReleaseTree() {
mComponentTree = null;
mTreeValid = false;
mLayoutAsyncCalled = false;
mLayoutSyncCalled = false;
mDidAcquireStateHandler = true;
}
@Override
protected synchronized void invalidateTree() {
mTreeValid = false;
mLayoutAsyncCalled = false;
mLayoutSyncCalled = false;
}
@Override
protected synchronized void computeLayoutAsync(
ComponentContext context,
int widthSpec,
int heightSpec) {
mComponentTree = mock(ComponentTree.class);
mTreeValid = true;
mLayoutAsyncCalled = true;
mChildWidth = SizeSpec.getSize(widthSpec);
mChildHeight = SizeSpec.getSize(heightSpec);
}
@Override
protected void computeLayoutSync(
ComponentContext context, int widthSpec, int heightSpec, Size size) {
mComponentTree = mock(ComponentTree.class);
mTreeValid = true;
if (size != null) {
size.width = SizeSpec.getSize(widthSpec);
size.height = SizeSpec.getSize(heightSpec);
}
mLayoutSyncCalled = true;
}
@Override
public void setComponentInfo(ComponentInfo componentInfo) {
mComponentInfo = componentInfo;
}
@Override
protected synchronized boolean isTreeValid() {
return mTreeValid;
}
@Override
protected synchronized ComponentTree getComponentTree() {
return mComponentTree;
}
@Override
public ComponentInfo getComponentInfo() {
return mComponentInfo;
}
}
}