package com.beloo.widget.chipslayoutmanager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.support.annotation.UiThread;
import android.support.test.InstrumentationRegistry;
import android.support.test.espresso.ViewAction;
import android.support.test.espresso.ViewInteraction;
import android.support.test.espresso.contrib.RecyclerViewActions;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;
import com.beloo.chipslayoutmanager.sample.entity.ChipsEntity;
import com.beloo.chipslayoutmanager.sample.ui.LayoutManagerFactory;
import com.beloo.chipslayoutmanager.sample.ui.ChipsFacade;
import com.beloo.chipslayoutmanager.sample.ui.TestActivity;
import com.beloo.chipslayoutmanager.sample.ui.adapter.ChipsAdapter;
import com.beloo.widget.chipslayoutmanager.util.InstrumentalUtil;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.MockitoAnnotations;
import com.beloo.chipslayoutmanager.sample.R;
import java.util.List;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import static com.beloo.test.util.RecyclerViewEspressoFactory.*;
/**
*/
@RunWith(AndroidJUnit4.class)
public class RowTest {
static {
TestActivity.isInitializeOutside = true;
}
@Rule
public ActivityTestRule<TestActivity> activityTestRule = new ActivityTestRule<TestActivity>(TestActivity.class) {
@Override
protected void afterActivityLaunched() {
super.afterActivityLaunched();
}
};
private ChipsLayoutManager layoutManager;
private TestActivity activity;
private List<ChipsEntity> items;
private ViewInteraction recyclerView;
@Before
public final void setUp() throws Throwable {
MockitoAnnotations.initMocks(this);
activity = activityTestRule.getActivity();
LayoutManagerFactory layoutManagerFactory = new LayoutManagerFactory() {
@Override
public RecyclerView.LayoutManager layoutManager(Context context) {
return retrieveLayoutManager();
}
};
RecyclerView rvTest = (RecyclerView) activityTestRule.getActivity().findViewById(R.id.rvTest);
//disable all animations
rvTest.setItemAnimator(null);
//set items
ChipsFacade chipsFacade = spy(new ChipsFacade());
items = chipsFacade.getItems();
when(chipsFacade.getItems()).thenReturn(items);
TestActivity.setItemsFactory(chipsFacade);
TestActivity.setLmFactory(layoutManagerFactory);
recyclerView = onView(withId(R.id.rvTest)).check(matches(isDisplayed()));
onSetUp();
activity.runOnUiThread(() -> activity.initialize());
}
public void onSetUp() throws Exception {}
private RecyclerView.LayoutManager retrieveLayoutManager() {
layoutManager = getLayoutManager();
return layoutManager;
}
@UiThread
protected ChipsLayoutManager getLayoutManager() {
if (activityTestRule.getActivity() == null) return null;
return ChipsLayoutManager.newBuilder(activityTestRule.getActivity())
.setOrientation(ChipsLayoutManager.HORIZONTAL)
.build();
}
///////////////////////////////////////////////////////////////////////////
// layouting, main feature
///////////////////////////////////////////////////////////////////////////
@Test
public void layouting_ScrollForwardOffScreenAndBackward_ItemsStayOnASamePlace() throws Exception {
InstrumentalUtil.waitForIdle();
//arrange
RecyclerView rvTest = (RecyclerView) activityTestRule.getActivity().findViewById(R.id.rvTest);
View child = getViewForPosition(rvTest, 7);
Rect expectedViewRect = layoutManager.getCanvas().getViewRect(child);
//act
recyclerView.perform(scrollBy(0, 1000), scrollBy(0, -1000));
Rect resultViewRect = layoutManager.getCanvas().getViewRect(child);
//assert
assertEquals(expectedViewRect, resultViewRect);
}
@Test
public void layouting_ScrollForwardOnScreenAndBackward_ItemsStayOnASamePlace() throws Exception {
InstrumentalUtil.waitForIdle();
//arrange
RecyclerView rvTest = (RecyclerView) activityTestRule.getActivity().findViewById(R.id.rvTest);
View child = getViewForPosition(rvTest, 6);
Rect expectedViewRect = layoutManager.getCanvas().getViewRect(child);
//act
recyclerView.perform(scrollBy(0, 250), scrollBy(0, -250));
Rect resultViewRect = layoutManager.getCanvas().getViewRect(child);
//assert
assertEquals(expectedViewRect, resultViewRect);
}
@Test
public void layouting_ScrollForwardAndBackward_VerifyCorrectOrder () throws Exception {
//arrange
//act
recyclerView.perform(scrollBy(0, 300));
recyclerView.perform(scrollBy(0, -300));
InstrumentalUtil.waitForIdle();
//assert
recyclerView.check(matches(incrementOrder()));
}
///////////////////////////////////////////////////////////////////////////
// scroll
///////////////////////////////////////////////////////////////////////////
@Test
public void scrollBy_LMInInitialStateAndScrollForward_CorrectFirstCompletelyVisibleItem() throws Exception {
//arrange
InstrumentalUtil.waitForIdle();
//act
recyclerView.perform(scrollBy(0, 300));
int actual = layoutManager.findFirstCompletelyVisibleItemPosition();
//assert
assertEquals(4, actual);
}
@Test
public void scrollBy_ScrolledForwardScrollBackward_CorrectFirstCompletelyVisibleItem() throws Exception {
//arrange
InstrumentalUtil.waitForIdle();
recyclerView.perform(scrollBy(0, 300));
//act
recyclerView.perform(scrollBy(0, -300));
int actual = layoutManager.findFirstCompletelyVisibleItemPosition();
//assert
assertEquals(0, actual);
}
@Test
public void scrollBy_LastItemInLastRowHasSmallSize_scrolledCompletelyToBiggestItemSize() throws Exception {
//arrange
{
items.remove(39);
items.remove(37);
}
activity.runOnUiThread(() -> activity.initialize());
InstrumentalUtil.waitForIdle();
//act
recyclerView.perform(RecyclerViewActions.scrollToPosition(37),
scrollBy(0, -200),
scrollBy(0, 200));
//assert
recyclerView.check(matches(atPosition(36, rvPaddingMatcher())));
}
@Test
public void scrollToPosition_LMInInitialState_FirstVisiblePositionsEqualsScrollingTarget() throws Exception {
//arrange
//act
recyclerView.perform(RecyclerViewActions.scrollToPosition(8));
InstrumentalUtil.waitForIdle();
//assert
int actual = layoutManager.findFirstCompletelyVisibleItemPosition();
assertEquals(8, actual);
}
@Test
public synchronized void smoothScrollToPosition_LMInInitialState_FirstVisiblePositionsEqualsScrollingTarget() throws Exception {
//arrange
InstrumentalUtil.waitForIdle();
//act
ViewAction scrollAction = smoothScrollToPosition(8);
//noinspection SynchronizationOnLocalVariableOrMethodParameter
synchronized (scrollAction) {
recyclerView.perform(scrollAction);
//wait for completion of SmoothScrollAction
scrollAction.wait();
}
//assert
int actual = layoutManager.findFirstCompletelyVisibleItemPosition();
assertEquals(8, actual);
}
///////////////////////////////////////////////////////////////////////////
// find visible item
///////////////////////////////////////////////////////////////////////////
@Test
public void findFirstVisibleItem_scrolledCompletelyToItemInTheMiddle_resultCorrect() throws Exception {
//arrange
recyclerView.perform(RecyclerViewActions.scrollToPosition(7),
scrollBy(0, 50));
//act
int actual = layoutManager.findFirstVisibleItemPosition();
//assert
assertEquals(6, actual);
}
@Test
public void findFirstCompletelyVisibleItem_scrolledCompletelyToItemInTheMiddle_resultCorrect() throws Exception {
//arrange
recyclerView.perform(RecyclerViewActions.scrollToPosition(7),
scrollBy(0, 50));
//act
int actual = layoutManager.findFirstCompletelyVisibleItemPosition();
//assert
assertEquals(7, actual);
}
@Test
public void findLastVisibleItem_scrolledCompletelyToItemInTheMiddle_resultCorrect() throws Exception {
//arrange
recyclerView.perform(RecyclerViewActions.scrollToPosition(7));
InstrumentalUtil.waitForIdle();
//act
int actual = layoutManager.findLastVisibleItemPosition();
//assert
assertEquals(19, actual);
}
@Test
public void findLastCompletelyVisibleItem_scrolledCompletelyToItemInTheMiddle_resultCorrect() throws Exception {
//arrange
recyclerView.perform(RecyclerViewActions.scrollToPosition(7));
InstrumentalUtil.waitForIdle();
//act
int actual = layoutManager.findLastCompletelyVisibleItemPosition();
//assert
assertEquals(17, actual);
}
///////////////////////////////////////////////////////////////////////////
// rotate
///////////////////////////////////////////////////////////////////////////
void rotateAndWaitIdle() throws Exception {
//arrange
final int orientation = InstrumentationRegistry.getTargetContext()
.getResources()
.getConfiguration()
.orientation;
//act
activityTestRule.getActivity().setRequestedOrientation(
orientation == Configuration.ORIENTATION_PORTRAIT ?
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE :
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
InstrumentalUtil.waitForIdle();
//verify no exceptions
}
void resetToInitialAfterRotate() throws Exception {
activityTestRule.launchActivity(new Intent(activity, TestActivity.class));
InstrumentalUtil.waitForIdle();
}
@Test
public void rotate_LMHasItems_firstItemNotChanged() throws Exception {
//arrange
recyclerView.perform(RecyclerViewActions.scrollToPosition(7));
InstrumentalUtil.waitForIdle();
int expected = layoutManager.findFirstVisibleItemPosition();
//act
rotateAndWaitIdle();
int actual = layoutManager.findFirstVisibleItemPosition();
resetToInitialAfterRotate();
//assert
assertNotEquals(-1, expected);
assertNotEquals(-1, actual);
assertEquals("first visible positions before and after rotation doesn't match", expected, actual);
System.out.println("first visible position = " + actual);
}
@Test
public void rotate_ScrolledToEndOfItems_BottomPaddingStaySame() throws Exception {
InstrumentalUtil.waitForIdle();
//arrange
RecyclerView rvTest = (RecyclerView) activityTestRule.getActivity().findViewById(R.id.rvTest);
recyclerView.perform(RecyclerViewActions.scrollToPosition(layoutManager.getItemCount() - 1));
InstrumentalUtil.waitForIdle();
Thread.sleep(200);
View child = getViewForPosition(rvTest, layoutManager.findLastVisibleItemPosition());
double bottom = (rvTest.getY() + rvTest.getHeight()) - (child.getY() + child.getHeight());
Log.println(Log.ASSERT, "rotateTest", "expected child padding = " + bottom);
//act
rotateAndWaitIdle();
recyclerView.perform(RecyclerViewActions.scrollToPosition(layoutManager.getItemCount() - 1));
rotateAndWaitIdle();
rvTest = (RecyclerView) activityTestRule.getActivity().findViewById(R.id.rvTest);
child = getViewForPosition(rvTest, layoutManager.findLastVisibleItemPosition());
double result = (rvTest.getY() + rvTest.getHeight()) - (child.getY() + child.getHeight());
Log.println(Log.ASSERT, "rotateTest", "result child padding = " + result);
//reset
resetToInitialAfterRotate();
//assert
assertNotEquals(0.0d, bottom, 0.01);
assertNotEquals(0.0d, result, 0.01);
assertEquals(bottom, result, 0.01);
}
@Test
public void setAdapterTwice_ChipsLayoutManagerHaveSetToRecyclerView_NoException() throws Exception {
//arrange
ChipsFacade chipsFacade = spy(new ChipsFacade());
ChipsAdapter chipsAdapter = new ChipsAdapter(chipsFacade.getItems(), null);
//act
recyclerView.perform(setAdapter(chipsAdapter));
recyclerView.perform(setAdapter(chipsAdapter));
InstrumentalUtil.waitForIdle();
//assert
int pos = layoutManager.findFirstVisibleItemPosition();
assertNotEquals(RecyclerView.NO_POSITION, pos);
}
///////////////////////////////////////////////////////////////////////////
// padding
///////////////////////////////////////////////////////////////////////////
@Test
public void clipToPadding_IsTrue_paddingStaySame() throws Exception {
//arrange
RecyclerView rvTest = (RecyclerView) activityTestRule.getActivity().findViewById(R.id.rvTest);
ViewAction initAction = actionDelegate((uiController, view) -> {
view.setClipToPadding(true);
view.setPadding(150, 150, 150, 150);
view.requestLayout();
});
recyclerView.perform(initAction);
//act
recyclerView.perform(RecyclerViewActions.scrollToPosition(8));
//assert
View view = layoutManager.getChildAt(0);
double padding = view.getY() - rvTest.getY();
assertTrue(padding >= 150);
}
@Test
public void clipToPadding_IsFalse_paddingOfScrolledViewIsLowerThanInitial() throws Exception {
//arrange
ViewAction arrangeAction = actionDelegate((uiController, view) -> {
view.setClipToPadding(false);
view.setPadding(150, 150, 150, 150);
view.requestLayout();
});
recyclerView.perform(arrangeAction);
//act
recyclerView.perform(RecyclerViewActions.scrollToPosition(8),
scrollBy(0, 200));
//assert
View view = layoutManager.getChildAt(0);
int padding = layoutManager.getDecoratedTop(view);
assertTrue(padding < 0);
}
private ViewHolderMatcher<RecyclerView.ViewHolder> rvPaddingMatcher() {
return new ViewHolderMatcher<RecyclerView.ViewHolder>() {
@Override
public boolean matches(RecyclerView parent, View itemView, RecyclerView.ViewHolder viewHolder) {
int expectedPadding = parent.getPaddingBottom();
int bottom = layoutManager.getDecoratedBottom(itemView);
int parentBottom = parent.getBottom();
int padding = parentBottom - bottom;
assertEquals("padding of RecyclerView item doesn't equal expected padding" ,expectedPadding, padding);
return true;
}
};
}
@Test
public void gapsNormalization_OnLastRowDeleted_PaddingStaySame() throws Exception {
//arrange
recyclerView.perform(RecyclerViewActions.scrollToPosition(39));
//act
recyclerView.perform(actionDelegate((uiController, recyclerView) -> items.remove(39)),
notifyItemRemovedAction(39));
//assert
recyclerView.check(matches(atPosition(38, rvPaddingMatcher())));
}
private View getViewForPosition(RecyclerView recyclerView, int position) {
return recyclerView.findViewHolderForAdapterPosition(position).itemView;
}
}