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.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.view.View;
import com.beloo.chipslayoutmanager.sample.R;
import com.beloo.chipslayoutmanager.sample.entity.ChipsEntity;
import com.beloo.chipslayoutmanager.sample.ui.ChipsFacade;
import com.beloo.chipslayoutmanager.sample.ui.LayoutManagerFactory;
import com.beloo.chipslayoutmanager.sample.ui.TestActivity;
import com.beloo.chipslayoutmanager.sample.ui.adapter.ChipsAdapter;
import com.beloo.widget.chipslayoutmanager.util.InstrumentalUtil;
import com.beloo.test.util.RecyclerViewEspressoFactory;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.MockitoAnnotations;
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 ColumnTest {
static {
TestActivity.isInitializeOutside = true;
}
@Rule
public ActivityTestRule<TestActivity> activityTestRule = new ActivityTestRule<>(TestActivity.class);
private TestActivity activity;
private List<ChipsEntity> items;
private ChipsLayoutManager layoutManager;
private ViewInteraction recyclerView;
@Before
public void setUp() throws Throwable {
MockitoAnnotations.initMocks(this);
activity = activityTestRule.getActivity();
recyclerView = onView(withId(R.id.rvTest)).check(matches(isDisplayed()));
layoutManager = getLayoutManager();
LayoutManagerFactory layoutManagerFactory = new LayoutManagerFactory() {
@Override
public RecyclerView.LayoutManager layoutManager(Context context) {
//we need clean layout manager for each request
return retrieveLayoutManager();
}
};
//set items
ChipsFacade chipsFacade = spy(new ChipsFacade());
items = chipsFacade.getItems();
when(chipsFacade.getItems()).thenReturn(items);
TestActivity.setItemsFactory(chipsFacade);
TestActivity.setLmFactory(layoutManagerFactory);
activity.runOnUiThread(() -> activity.initialize());
}
private RecyclerView.LayoutManager retrieveLayoutManager() {
this.layoutManager = getLayoutManager();
return layoutManager;
}
private ChipsLayoutManager getLayoutManager() {
if (activityTestRule.getActivity() == null) return null;
return ChipsLayoutManager.newBuilder(activityTestRule.getActivity())
.setOrientation(ChipsLayoutManager.VERTICAL)
.build();
}
@Test
public void layouting_ScrollForwardAndBackward_VerifyCorrectOrder () throws Exception {
//arrange
InstrumentalUtil.waitForIdle();
//act
recyclerView.perform(scrollBy(1000, 0));
recyclerView.perform(scrollBy(-1000, 0));
//assert
recyclerView.check(matches(incrementOrder()));
}
///////////////////////////////////////////////////////////////////////////
// scrolling
///////////////////////////////////////////////////////////////////////////
@Test
public void scrollBy_LMInInitialStateAndScrollForward_CorrectFirstCompletelyVisibleItem() throws Exception {
//arrange
InstrumentalUtil.waitForIdle();
//act
recyclerView.perform(scrollBy(300, 0));
int actual = layoutManager.findFirstCompletelyVisibleItemPosition();
//assert
assertEquals(9, actual);
}
@Test
public void scrollBy_ScrolledForwardScrollBackward_CorrectFirstCompletelyVisibleItem() throws Exception {
//arrange
InstrumentalUtil.waitForIdle();
recyclerView.perform(scrollBy(1000, 0));
//act
recyclerView.perform(scrollBy(-1000, 0));
int actual = layoutManager.findFirstCompletelyVisibleItemPosition();
//assert
assertEquals(0, actual);
}
@Test
public void scrollBy_LastItemInLastRowHasSmallSize_scrolledCompletelyToBiggestItemSize() throws Exception {
//arrange
//act
recyclerView.perform(RecyclerViewActions.scrollToPosition(36),
scrollBy(0, -200),
scrollBy(0, 200));
//assert
recyclerView.check(matches(atPosition(39, rvPaddingMatcher())));
}
private ViewHolderMatcher<RecyclerView.ViewHolder> rvPaddingMatcher() {
return new RecyclerViewEspressoFactory.ViewHolderMatcher<RecyclerView.ViewHolder>() {
@Override
public boolean matches(RecyclerView parent, View itemView, RecyclerView.ViewHolder viewHolder) {
int expectedPadding = parent.getPaddingRight();
int right = layoutManager.getDecoratedRight(itemView);
int parentRight = parent.getRight();
int padding = parentRight - right;
assertEquals("padding of RecyclerView item doesn't equal expected padding" ,expectedPadding, padding);
return true;
}
};
}
@Test
public void scrollToPosition_ScrollItemIsNotVisible_FirstVisiblePositionsEqualsScrollingTarget() throws Exception {
//arrange
recyclerView.perform(RecyclerViewActions.scrollToPosition(0));
InstrumentalUtil.waitForIdle();
//act
recyclerView.perform(RecyclerViewActions.scrollToPosition(18));
InstrumentalUtil.waitForIdle();
//assert
int actual = layoutManager.findFirstCompletelyVisibleItemPosition();
assertEquals(18, actual);
}
@Test
public synchronized void smoothScrollToPosition_ScrollItemIsNotVisible_FirstVisiblePositionsEqualsScrollingTarget() throws Exception {
//arrange
InstrumentalUtil.waitForIdle();
//act
ViewAction scrollAction = smoothScrollToPosition(18);
//noinspection SynchronizationOnLocalVariableOrMethodParameter
synchronized (scrollAction) {
recyclerView.perform(scrollAction);
//wait for completion of SmoothScrollAction
scrollAction.wait();
}
//assert
int actual = layoutManager.findFirstCompletelyVisibleItemPosition();
assertEquals(18, actual);
}
@Test
public synchronized void smoothScrollToPosition_ScrollItemIsVisible_ScrollItemDockedToStartBorder() throws Exception {
//arrange
InstrumentalUtil.waitForIdle();
//act
ViewAction scrollAction = smoothScrollToPosition(3);
//noinspection SynchronizationOnLocalVariableOrMethodParameter
synchronized (scrollAction) {
recyclerView.perform(scrollAction);
//wait for completion of SmoothScrollAction
scrollAction.wait();
}
//assert
int actual = layoutManager.findFirstCompletelyVisibleItemPosition();
assertEquals(3, actual);
}
///////////////////////////////////////////////////////////////////////////
// find visible item
///////////////////////////////////////////////////////////////////////////
@Test
public void findFirstVisibleItem_scrolledCompletelyToItemInTheMiddle_resultCorrect() throws Exception {
//arrange
recyclerView.perform(RecyclerViewActions.scrollToPosition(19));
InstrumentalUtil.waitForIdle();
//act
int actual = layoutManager.findFirstVisibleItemPosition();
//assert
assertEquals(18, actual);
}
@Test
public void findLastVisibleItem_scrolledCompletelyToItemInTheMiddle_resultCorrect() throws Exception {
//arrange
recyclerView.perform(RecyclerViewActions.scrollToPosition(18));
InstrumentalUtil.waitForIdle();
//act
int actual = layoutManager.findLastVisibleItemPosition();
//assert
assertEquals(35, actual);
}
@Test
public void findLastCompletelyVisibleItem_scrolledCompletelyToItemInTheMiddle_resultCorrect() throws Exception {
//arrange
recyclerView.perform(RecyclerViewActions.scrollToPosition(18));
InstrumentalUtil.waitForIdle();
//act
int actual = layoutManager.findLastCompletelyVisibleItemPosition();
//assert
assertEquals(26, actual);
}
private 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
}
/**
* verify that orientation change is performed successfully
*/
@Test
public void rotate_LMBuiltFirstTime_NoExceptions() throws Exception {
//arrange
//act
rotateAndWaitIdle();
//assert
}
@Test
public void rotate_LMHasItems_firstItemNotChanged() throws Exception {
//arrange
recyclerView.perform(RecyclerViewActions.scrollToPosition(18));
InstrumentalUtil.waitForIdle();
int expected = layoutManager.findFirstVisibleItemPosition();
//act
rotateAndWaitIdle();
int actual = layoutManager.findFirstVisibleItemPosition();
//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);
resetToInitialAfterRotate();
}
void resetToInitialAfterRotate() throws Exception {
activityTestRule.launchActivity(new Intent(activity, TestActivity.class));
InstrumentalUtil.waitForIdle();
}
@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);
}
@Test
public void clipToPadding_IsTrue_paddingStaySame() throws Exception {
//arrange
RecyclerView rvTest = (RecyclerView) activityTestRule.getActivity().findViewById(R.id.rvTest);
ViewAction viewAction = actionDelegate((uiController, view) -> {
view.setClipToPadding(true);
view.setPadding(150, 150, 150, 150);
view.requestLayout();
});
//act
recyclerView.perform(viewAction);
recyclerView.perform(RecyclerViewActions.scrollToPosition(18));
//assert
View view = layoutManager.getChildAt(0);
double padding = view.getX() - rvTest.getX();
assertTrue(padding >= 150);
}
@Test
public void clipToPadding_IsFalse_paddingOfScrolledViewIsLowerThanInitial() throws Exception {
//arrange
ViewAction viewAction = actionDelegate((uiController, view) -> {
view.setClipToPadding(false);
view.setPadding(150, 150, 150, 150);
view.requestLayout();
});
//act
recyclerView.perform(viewAction,
RecyclerViewActions.scrollToPosition(18),
scrollBy(200, 0));
//assert
View view = layoutManager.getChildAt(0);
int padding = layoutManager.getDecoratedLeft(view);
assertTrue(padding < 0);
}
private View getViewForPosition(RecyclerView recyclerView, int position) {
return recyclerView.findViewHolderForAdapterPosition(position).itemView;
}
@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(2000, 0), scrollBy(-2000, 0));
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(500, 0), scrollBy(-500, 0));
Rect resultViewRect = layoutManager.getCanvas().getViewRect(child);
//assert
assertEquals(expectedViewRect, resultViewRect);
}
@Test
public void gapsNormalization_OnLastRowDeleted_PaddingStaySame() throws Exception {
//arrange
{
items.remove(39);
items.remove(38);
items.remove(37);
activity.runOnUiThread(() -> activity.initialize());
InstrumentalUtil.waitForIdle();
}
recyclerView.perform(RecyclerViewActions.scrollToPosition(36));
//act
recyclerView.perform(actionDelegate((uiController, recyclerView) -> items.remove(36)),
notifyItemRemovedAction(36));
//assert
recyclerView.check(matches(atPosition(29, rvPaddingMatcher())));
}
}