I'm trying to use Kaspresso for tests and I'm checking whether a view has a certain drawable with the method:
withId<KImageView>(R.id.criticalErrorImage) {
hasDrawable(R.drawable.error_graphic)
}
With PNG works really well, while comparing the image stored in the imageView and restored and the VectorDrawable fails.
The file where the check is made is this one.
In particular this part of code:
var expectedDrawable: Drawable? = drawable ?: getResourceDrawable(resId)?.mutate()
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP && expectedDrawable != null) {
expectedDrawable = DrawableCompat.wrap(expectedDrawable).mutate()
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
tintColorId?.let { tintColorId ->
val tintColor = getResourceColor(tintColorId)
expectedDrawable?.apply {
setTintList(ColorStateList.valueOf(tintColor))
setTintMode(PorterDuff.Mode.SRC_IN)
}
}
}
if (expectedDrawable == null) {
return false
}
val convertDrawable = (imageView as ImageView).drawable.mutate()
val bitmap = toBitmap?.invoke(convertDrawable) ?: convertDrawable.toBitmap()
val otherBitmap = toBitmap?.invoke(expectedDrawable) ?: expectedDrawable.toBitmap()
The funny part is that it works if I'm setting the image through databinding, while it doesn't work if I set it in all other ways. Cannot understand why.
I've created a POC here: https://github.com/filnik/proof_of_concept
In particular, here is the test: https://github.com/filnik/proof_of_concept/blob/master/app/src/androidTest/java/com/neato/test/POCInstrumentationTest.kt
While here I dynamically set the image: https://github.com/filnik/proof_of_concept/blob/master/app/src/main/java/com/neato/test/FirstFragment.kt
The issue was because actually the image is scaled. So the scaled image is different from the original one.
To avoid this issue I've used this "altered" KImageView:
package your_package
import android.content.res.ColorStateList
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.PorterDuff
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.graphics.drawable.StateListDrawable
import android.os.Build
import android.view.View
import android.widget.ImageView
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.core.graphics.drawable.DrawableCompat
import androidx.test.espresso.DataInteraction
import androidx.test.espresso.assertion.ViewAssertions
import com.agoda.kakao.common.assertions.BaseAssertions
import com.agoda.kakao.common.builders.ViewBuilder
import com.agoda.kakao.common.utilities.getResourceColor
import com.agoda.kakao.common.utilities.getResourceDrawable
import com.agoda.kakao.common.views.KBaseView
import com.agoda.kakao.image.KImageView
import org.hamcrest.Description
import org.hamcrest.Matcher
import org.hamcrest.TypeSafeMatcher
class KImageView2 : KBaseView<KImageView>, ImageViewAssertions2 {
constructor(function: ViewBuilder.() -> Unit) : super(function)
constructor(parent: Matcher<View>, function: ViewBuilder.() -> Unit) : super(parent, function)
constructor(parent: DataInteraction, function: ViewBuilder.() -> Unit) : super(parent, function)
}
interface ImageViewAssertions2 : BaseAssertions {
/**
* Checks if the view displays given drawable
*
* @param resId Drawable resource to be matched
* @param toBitmap Lambda with custom Drawable -> Bitmap converter (default is null)
*/
fun hasDrawable(@DrawableRes resId: Int, toBitmap: ((drawable: Drawable) -> Bitmap)? = null) {
view.check(ViewAssertions.matches(DrawableMatcher2(resId = resId, toBitmap = toBitmap)))
}
}
class DrawableMatcher2(
@DrawableRes private val resId: Int = -1,
@ColorRes private val tintColorId: Int? = null,
private val toBitmap: ((drawable: Drawable) -> Bitmap)? = null
) : TypeSafeMatcher<View>(View::class.java) {
override fun describeTo(desc: Description) {
desc.appendText("with drawable id $resId or provided instance")
}
override fun matchesSafely(view: View?): Boolean {
if (view !is ImageView) {
return false
}
if (resId < 0) {
return view.drawable == null
}
val bitmap = extractFromImageView(view)
view.setImageResource(resId)
val otherBitmap = extractFromImageView(view)
return bitmap?.sameAs(otherBitmap) ?: false
}
private fun extractFromImageView(view: View?): Bitmap? {
return view?.let { imageView ->
var expectedDrawable: Drawable? = getResourceDrawable(resId)?.mutate()
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP && expectedDrawable != null) {
expectedDrawable = DrawableCompat.wrap(expectedDrawable).mutate()
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
tintColorId?.let { tintColorId ->
val tintColor = getResourceColor(tintColorId)
expectedDrawable?.apply {
setTintList(ColorStateList.valueOf(tintColor))
setTintMode(PorterDuff.Mode.SRC_IN)
}
}
}
if (expectedDrawable == null) {
return null
}
val convertDrawable = (imageView as ImageView).drawable.mutate()
toBitmap?.invoke(convertDrawable) ?: convertDrawable.toBitmap()
}
}
}
internal fun Drawable.toBitmap(): Bitmap {
if (this is BitmapDrawable && this.bitmap != null) {
return this.bitmap
}
if (this is StateListDrawable && this.getCurrent() is BitmapDrawable) {
val bitmapDrawable = this.getCurrent() as BitmapDrawable
if (bitmapDrawable.bitmap != null) {
return bitmapDrawable.bitmap
}
}
val bitmap = if (this.intrinsicWidth <= 0 || this.intrinsicHeight <= 0) {
Bitmap.createBitmap(
1,
1,
Bitmap.Config.ARGB_8888
) // Single color bitmap will be created of 1x1 pixel
} else {
Bitmap.createBitmap(this.intrinsicWidth, this.intrinsicHeight, Bitmap.Config.ARGB_8888)
}
val canvas = Canvas(bitmap)
this.setBounds(0, 0, canvas.width, canvas.height)
this.draw(canvas)
return bitmap
}
Not the cleanest solution possible, but it works. If anyone can provide a better solution would be great :-)