Jonas Hoefflin
Jonas Hoefflin

Reputation: 11

Check CompundDrawable of TextView with Espresso

I am new to Espresso tests and Android. I am trying to test if the correct Icon is shown next to the text. The icon is set with:

public void setLabelTextIcon(@DrawableRes int iconResId) {
    txtLabel.setCompoundDrawablesRelativeWithIntrinsicBounds(iconResId, 0,0,0);
}

I found this online https://gist.github.com/frankiesardo/7490059 but it is not working for me. Due to my lack of background-knowledge I'm not able to change the code that it works. At the moment I try

    onView(withId(R.id.blue_triple_stripe_txtLabel)).check(matches(withActionIconDrawable(R.drawable.ic_date_grey)));

and the withActionIconDrawable() is

public static Matcher<View> withActionIconDrawable(@DrawableRes final int resourceId) {
    return new BoundedMatcher<View, ActionMenuItemView>(ActionMenuItemView.class) {
        @Override
        public void describeTo(final Description description) {
            description.appendText("has image drawable resource " + resourceId);
        }

        @Override
        public boolean matchesSafely(final ActionMenuItemView actionMenuItemView) {
            return sameBitmap(actionMenuItemView.getContext(), actionMenuItemView.getItemData().getIcon(), resourceId);
        }
    };
}

The Error I get is

androidx.test.espresso.base.DefaultFailureHandler$AssertionFailedWithCauseError: 'has image drawable resource 2131230871' doesn't match the selected view.
Expected: has image drawable resource 2131230871
Got: "AppCompatTextView{id=2131296335, res-name=blue_triple_stripe_txtLabel, visibility=VISIBLE, width=342, height=72, has-focus=false, has-focusable=false, has-window-focus=true, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=false, is-selected=false, layout-params=androidx.constraintlayout.widget.ConstraintLayout$LayoutParams@4d24b35, tag=null, root-is-layout-requested=false, has-input-connection=false, x=206.0, y=18.0, text=2019-04-12, input-type=0, ime-target=false, has-links=false}"

Thanks!

Upvotes: 1

Views: 1286

Answers (2)

I was able to achieve this by adapting @Sam's answer into a matcher that I had for ImageView drawables (in Kotlin):

class DrawableMatcher(
    @DrawableRes private val id: Int,
    @ColorRes private val tint: Int? = null,
    private val tintMode: PorterDuff.Mode = PorterDuff.Mode.SRC_IN
) : TypeSafeMatcher<View>() {
    override fun describeTo(description: Description) {
        description.appendText("ImageView with drawable same as drawable with id $id")
        tint?.let { description.appendText(", tint color id: $tint, mode: $tintMode") }
    }
    
    override fun matchesSafely(view: View): Boolean {
        val context = view.context
        val tintColor = tint?.toColor(context)
        val expectedBitmap = context.getDrawable(id)!!.tinted(tintColor, tintMode).toBitmap()
    
        if (view is TextView) {
            for (d in view.compoundDrawables) {
                if (d != null) {
                    if (d.toBitmap().sameAs(expectedBitmap)) {
                        return true
                    }
                }
            }
    
            return false
        }
    
        return view is ImageView && view.drawable.toBitmap().sameAs(expectedBitmap)
    }
    
    private fun Drawable.tinted(
        @ColorInt tintColor: Int? = null,
        tintMode: PorterDuff.Mode = PorterDuff.Mode.SRC_IN
    ) : Drawable {
        return this.apply {
            setTintList(tintColor?.toColorStateList())
            setTintMode(tintMode)
        }
    }
    
    private fun Int.toColor(context: Context) = ContextCompat.getColor(context, this)
    
    private fun Int.toColorStateList() = ColorStateList.valueOf(this)
    
    companion object {
        fun withDrawable(
            @DrawableRes id: Int,
            @ColorRes tint: Int? = null,
            tintMode: PorterDuff.Mode = PorterDuff.Mode.SRC_IN
        ): Matcher<View> {
            return DrawableMatcher(id, tint, tintMode)
        }
    }
}

and to use it:

onView(withId(R.id.blue_triple_stripe_txtLabel)).check(
    matches(
        withDrawable(
            R.drawable.ic_date_grey
        )
    )
)

Upvotes: 0

Sam
Sam

Reputation: 466

This works for me in Kotlin

fun withDrawableTextView(@DrawableRes id: Int) = object : TypeSafeMatcher<View>() {
   override fun describeTo(description: Description) {
       description.appendText("ImageView with drawable same as drawable with id $id")
    }

   override fun matchesSafely(tv: View?): Boolean {
       if (tv is TextView) {
           if (tv.requestFocusFromTouch()) 
               for (d in tv.compoundDrawables) 
                   if (d != null) {
                      val context = tv.context
                      if (sameBitmap(tv.getContext(), d, id)) {
                          return true
                      }
                   }
       }
      return false
    } 
}

Declare sameBitmap same as here:

private fun sameBitmap(context: Context, drawable: Drawable, resourceId: Int): Boolean {
   var drawable: Drawable? = drawable
   var otherDrawable: Drawable = context.getResources().getDrawable(resourceId)
   if (drawable == null || otherDrawable == null) {
      return false
   }
   if (drawable is StateListDrawable && otherDrawable is StateListDrawable) {
      drawable = drawable.current
      otherDrawable = otherDrawable.current
   }
   if (drawable is BitmapDrawable) {
      val bitmap: Bitmap = (drawable as BitmapDrawable).getBitmap()
      val otherBitmap: Bitmap = (otherDrawable as BitmapDrawable).getBitmap()
      return bitmap.sameAs(otherBitmap)
  }
  return false
}

Use it with the following code:

  onView(withId(R.id.edit_txt_password)).check(matches(withDrawableTextView(R.drawable.view_password_xxxdp)))

Upvotes: 0

Related Questions