package com.beloo.test.util;
import android.support.annotation.NonNull;
import android.support.test.espresso.UiController;
import android.support.test.espresso.ViewAction;
import android.support.test.espresso.matcher.BoundedMatcher;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import com.beloo.widget.chipslayoutmanager.ChildViewsIterable;
import com.beloo.widget.chipslayoutmanager.support.BiConsumer;
import com.beloo.widget.chipslayoutmanager.util.ActionDelegate;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
import java.util.Locale;
import static android.support.test.espresso.core.deps.guava.base.Preconditions.checkNotNull;
import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static org.hamcrest.Matchers.allOf;
public abstract class RecyclerViewEspressoFactory {
///////////////////////////////////////////////////////////////////////////
// Actions factory
///////////////////////////////////////////////////////////////////////////
public static ViewAction scrollBy(int x, int y) {
return new ScrollByRecyclerViewAction(x, y);
}
public static ViewAction smoothScrollToPosition(int position) {
return new SmoothScrollToPositionRecyclerViewAction(position);
}
public static ViewAction setAdapter(RecyclerView.Adapter<? extends RecyclerView.ViewHolder> adapter) {
return new SetAdapterAction(adapter);
}
public static ViewAction notifyItemRemovedAction(int removePosition) {
return new NotifyItemRemovedAction(removePosition);
}
public static ViewAction notifyItemRangeRemovedAction(int removePosition, int itemCount) {
return new NotifyItemRemovedAction(removePosition, itemCount);
}
public static ViewAction actionDelegate(BiConsumer<UiController, RecyclerView> performAction) {
return new ActionDelegate<>(performAction);
}
///////////////////////////////////////////////////////////////////////////
// Matcher factory
///////////////////////////////////////////////////////////////////////////
public static Matcher<View> incrementOrder() {
return orderMatcher();
}
public static Matcher<View> atPosition(final int position, @NonNull final Matcher<View> itemMatcher) {
checkNotNull(itemMatcher);
return new BoundedMatcher<View, RecyclerView>(RecyclerView.class) {
@Override
public void describeTo(Description description) {
description.appendText("has item at position " + position + ":\n");
itemMatcher.describeTo(description);
}
@Override
protected boolean matchesSafely(final RecyclerView view) {
RecyclerView.ViewHolder viewHolder = view.findViewHolderForAdapterPosition(position);
return viewHolder != null && itemMatcher.matches(viewHolder.itemView);
}
};
}
public static <T extends RecyclerView.ViewHolder> Matcher<View> atPosition(final int position, @NonNull final ViewHolderMatcher<T> itemMatcher) {
checkNotNull(itemMatcher);
return new BoundedMatcher<View, RecyclerView>(RecyclerView.class) {
@Override
public void describeTo(Description description) {
description.appendText("has item at position " + position + ":\n");
itemMatcher.describeTo(description);
}
@Override
protected boolean matchesSafely(final RecyclerView view) {
RecyclerView.ViewHolder viewHolder = view.findViewHolderForAdapterPosition(position);
return viewHolder != null && itemMatcher.matches(viewHolder);
}
};
}
///////////////////////////////////////////////////////////////////////////
// Actions
///////////////////////////////////////////////////////////////////////////
private static final class NotifyItemRemovedAction extends RecyclerViewAction {
private final int removePosition;
private final int itemCount;
private NotifyItemRemovedAction(int removePosition, int itemCount) {
this.removePosition = removePosition;
this.itemCount = itemCount;
}
private NotifyItemRemovedAction(int removePosition) {
this.removePosition = removePosition;
this.itemCount = 1;
}
@Override
public void performAction(UiController uiController, RecyclerView recyclerView) {
recyclerView.getAdapter().notifyItemRangeRemoved(removePosition, itemCount);
}
}
private static final class SetAdapterAction extends RecyclerViewAction {
private final RecyclerView.Adapter<? extends RecyclerView.ViewHolder> adapter;
private SetAdapterAction(RecyclerView.Adapter<? extends RecyclerView.ViewHolder> adapter) {
this.adapter = adapter;
}
@Override
public String getDescription() {
return"set adapter to recycler view";
}
@Override
public void performAction(UiController uiController, RecyclerView recyclerView) {
recyclerView.setAdapter(adapter);
}
}
private static final class SmoothScrollToPositionRecyclerViewAction extends RecyclerViewAction {
private final int position;
private SmoothScrollToPositionRecyclerViewAction(int position) {
this.position = position;
}
@Override
public String getDescription() {
return String.format(Locale.getDefault(), "smooth scroll RecyclerView to position %d", position);
}
@Override
public void performAction(UiController uiController, RecyclerView recyclerView) {
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
SmoothScrollToPositionRecyclerViewAction.this.onScrollStateChanged(recyclerView, newState);
}
});
recyclerView.smoothScrollToPosition(position);
}
private synchronized void onScrollStateChanged(RecyclerView recyclerView, int newState) {
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
notify();
}
}
}
private static final class ScrollByRecyclerViewAction extends RecyclerViewAction {
private final int x;
private final int y;
private ScrollByRecyclerViewAction(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public String getDescription() {
return String.format(Locale.getDefault(), "scroll RecyclerView with offsets: x = %d, y = %d ", x, y);
}
@Override
public void performAction(UiController uiController, RecyclerView recyclerView) {
recyclerView.scrollBy(x, y);
}
}
private abstract static class RecyclerViewAction implements ViewAction {
@Override
public Matcher<View> getConstraints() {
return allOf(isAssignableFrom(RecyclerView.class), isDisplayed());
}
@Override
public String getDescription() {
return "RecyclerView action " + this.getClass().getSimpleName();
}
@Override
public final void perform(UiController uiController, View view) {
performAction(uiController, (RecyclerView) view);
}
public void performAction(UiController uiController, RecyclerView recyclerView) {
}
}
///////////////////////////////////////////////////////////////////////////
// Matcher
///////////////////////////////////////////////////////////////////////////
private static Matcher<View> orderMatcher() {
return new TypeSafeMatcher<View>() {
@Override
public void describeTo(Description description) {
description.appendText("with correct position order");
}
@Override
public boolean matchesSafely(View v) {
RecyclerView view = (RecyclerView) v;
if (view.getLayoutManager() == null) return false;
ChildViewsIterable childViews = new ChildViewsIterable(view.getLayoutManager());
int pos = view.getChildAdapterPosition(childViews.iterator().next());
for (View child : childViews) {
if (pos != view.getChildAdapterPosition(child)) {
return false;
}
pos ++;
}
return true;
}
};
}
public abstract static class ViewHolderMatcher<VH extends RecyclerView.ViewHolder> extends BaseMatcher<VH> {
@Override
public boolean matches(Object item) {
VH viewHolder = (VH) item;
RecyclerView recyclerView = (RecyclerView) viewHolder.itemView.getParent();
return matches(recyclerView, viewHolder.itemView, viewHolder);
}
@Override
public void describeTo(Description description) {
}
public abstract boolean matches(RecyclerView parent, View itemView, RecyclerView.ViewHolder viewHolder);
}
}