Issue
I'm trying to check whether the items within a RecyclerView contain the expected image drawables. I've tried to check for descendants but get an error to do with getting the id for the image view itself not the drawable I'm looking for.
onView(new RecyclerViewMatcher(R.id.rv_sub_services).atPositionOnView(0, R.id.iv_service_image))
.check(matches(atPosition(0, hasDescendant(withId(R.drawable.eap_financial)))));
Expected: has item at position 0: has descendant: with id: 2131165345
Got: "AppCompatImageView{id=2131296664, res-name=iv_service_image, desc=Resource
type, visibility=VISIBLE, width=36...}
I've also tried to use a custom matcher but that's getting me even more frustrated as when I try to match the text against my textview it works fine but not when I try to match the imageview against my R.drawable id.
That's the guy I'm talking about:
public class RecyclerViewMatcher {
private final int recyclerViewId;
public RecyclerViewMatcher(int recyclerViewId) {
this.recyclerViewId = recyclerViewId;
}
public Matcher<View> atPosition(final int position) {
return atPositionOnView(position, -1);
}
public Matcher<View> atPositionOnView(final int position, final int targetViewId) {
return new TypeSafeMatcher<View>() {
View childView;
public void describeTo(Description description) {
}
public boolean matchesSafely(View view) {
if (childView == null) {
RecyclerView recyclerView = view.getRootView().findViewById(recyclerViewId);
if (recyclerView != null && recyclerView.getId() == recyclerViewId) {
childView = recyclerView.findViewHolderForAdapterPosition(position).itemView;
} else {
return false;
}
}
if (targetViewId == -1) {
return view == childView;
} else {
View targetView = childView.findViewById(targetViewId);
return view == targetView;
}
}
};
}
}
This piece of code works for textview
onView(new RecyclerViewMatcher(R.id.rv_sub_services).atPositionOnView(0, R.id.tv_title)).check(matches(withText("Financial Assistance")));
But not for imageview:
onView(new RecyclerViewMatcher(R.id.rv_sub_services).atPositionOnView(0, R.id.iv_service_image)).check(matches(withId(R.drawable.eap_financial));
Any help is much appreciated. Thanks very much.
Update
Adding the matcher I use to compare drawable when not a recyclerView item in case that could be adapter for this scenario as well?
public class EspressoTestsMatchers {
public Matcher<View> withDrawable(final int resourceId) {
return new DrawableMatcher(resourceId);
}
public Matcher<View> noDrawable() {
return new DrawableMatcher(-1);
}
}
public class DrawableMatcher extends TypeSafeMatcher<View> {
private final int expectedId;
public DrawableMatcher(int resourceId) {
super(View.class);
this.expectedId = resourceId;
}
@Override
protected boolean matchesSafely(View target) {
if (!(target instanceof ImageView)) {
return false;
}
ImageView imageView = (ImageView) target;
if (expectedId < 0) {
return imageView.getDrawable() == null;
}
Resources resources = target.getContext().getResources();
Drawable expectedDrawable = resources.getDrawable(expectedId);
if (expectedDrawable == null) {
return false;
}
Bitmap bitmap = getBitmap(imageView.getDrawable());
Bitmap otherBitmap = getBitmap(expectedDrawable);
return bitmap.sameAs(otherBitmap);
}
private Bitmap getBitmap(Drawable drawable) {
Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
}
@Override
public void describeTo(Description description) {
}
}
Solution
I think it's unsafe if your matcher has to call setBounds
on a View
because the test result may change unexpectedly. You can try creating a custom matcher for ImageView
based on HasBackgroundMatcher
in Espresso:
public static Matcher<View> hasImage(int drawableId) {
return new BoundedMatcher<View, ImageView>(ImageView.class) {
@Override public void describeTo(Description description) {
description.appendText("has image with drawable ID: " + drawableId);
}
@Override protected boolean matchesSafely(ImageView view) {
return assertDrawable(view.getDrawable(), drawableId, view);
}
};
}
private static boolean compareBitmaps(Bitmap img1, Bitmap img2) {
if (img1.getWidth() == img2.getWidth() && img1.getHeight() == img2.getHeight()) {
int[] img1Pixels = new int[img1.getWidth() * img1.getHeight()];
int[] img2Pixels = new int[img2.getWidth() * img2.getHeight()];
img1.getPixels(img1Pixels, 0, img1.getWidth(), 0, 0, img1.getWidth(), img1.getHeight());
img2.getPixels(img2Pixels, 0, img2.getWidth(), 0, 0, img2.getWidth(), img2.getHeight());
return Arrays.equals(img1Pixels, img2Pixels);
}
return false;
}
private static boolean assertDrawable(Drawable actual, int expectedId, View v) {
if (!(actual instanceof BitmapDrawable)) {
return false;
}
Bitmap expectedBitmap = null;
try {
expectedBitmap = BitmapFactory.decodeResource(v.getContext().getResources(), expectedId);
if (Build.VERSION.SDK_INT >= 12) {
return ((BitmapDrawable) actual).getBitmap().sameAs(expectedBitmap);
} else {
return compareBitmaps(((BitmapDrawable) actual).getBitmap(), expectedBitmap);
}
} catch (OutOfMemoryError error) {
return false;
} finally {
if (expectedBitmap != null) {
expectedBitmap.recycle();
}
}
}
Then you can do:
onView(new RecyclerViewMatcher(R.id.rv_sub_services).atPositionOnView(0, R.id.iv_service_image)).check(matches(hasImage(R.drawable.eap_financial));
I'm not sure if it works as you like but good luck!
Answered By - Aaron
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.