Skip to content

Commit

Permalink
Add a goldens based test utility for compose
Browse files Browse the repository at this point in the history
  • Loading branch information
sjudd committed Sep 3, 2023
1 parent e94916e commit 6ff7f32
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 2 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
package com.bumptech.glide.integration.compose

import android.content.Context
import android.graphics.drawable.AnimationDrawable
import android.graphics.drawable.Drawable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.material.Text
import androidx.compose.material.TextButton
Expand All @@ -20,6 +20,7 @@ import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.test.assert
import androidx.compose.ui.test.captureToImage
import androidx.compose.ui.test.hasTestTag
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText
Expand All @@ -40,18 +41,22 @@ import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target
import com.bumptech.glide.test.compareToGolden
import com.google.common.truth.Truth.assertThat
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.atomic.AtomicReference
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TestName

@OptIn(ExperimentalGlideComposeApi::class, InternalGlideApi::class)
class GlideImageTest {
private val context: Context = ApplicationProvider.getApplicationContext()

@get:Rule
val glideComposeRule = GlideComposeRule()
@get:Rule
val testName = TestName()

@Test
fun glideImage_noModifierSize_resourceDrawable_displaysDrawable() {
Expand Down Expand Up @@ -391,4 +396,27 @@ class GlideImageTest {
}
glideComposeRule.waitForIdle()
}

@Test
fun glideImage_startsAnimatedDrawable() {
val drawable = AnimationDrawable()
val firstFrame = context.getDrawable(android.R.drawable.star_big_on)!!
val secondFrame = context.getDrawable(android.R.drawable.star_big_off)!!
drawable.addFrame(firstFrame, 0)
drawable.addFrame(secondFrame, 10000)
val description = "test"
glideComposeRule.setContent {
GlideImage(
model = drawable,
contentDescription = description,
modifier = Modifier.wrapContentSize(),
) {
it.override(20).dontTransform()
}
}
glideComposeRule
.onNodeWithContentDescription(description)
.captureToImage()
.compareToGolden(testName.methodName)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package com.bumptech.glide.integration.compose.test
import android.content.Context
import android.content.res.Resources
import android.graphics.Bitmap
import android.graphics.drawable.Animatable
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.util.TypedValue
Expand Down Expand Up @@ -42,6 +43,11 @@ fun expectDisplayedDrawableSize(expectedSize: Size): SemanticsMatcher =
fun expectDisplayedDrawable(expectedValue: Drawable?): SemanticsMatcher =
expectDisplayedDrawable(expectedValue.bitmapOrThrow(), ::compareBitmaps) { it.bitmapOrThrow() }

fun expectAnimatingDrawable(): SemanticsMatcher =
expectDisplayedDrawable(true) {
(it as Animatable).isRunning
}

fun expectNoDrawable(): SemanticsMatcher = expectDisplayedDrawable(null)

private fun compareBitmaps(first: Bitmap?, second: Bitmap?): Boolean {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package com.bumptech.glide.test

import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.os.Environment
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.toPixelMap
import androidx.test.core.app.ApplicationProvider
import java.io.BufferedOutputStream
import java.io.File
import java.io.FileNotFoundException
import java.io.FileOutputStream
import java.io.IOException
import java.io.OutputStream
import java.lang.IllegalStateException

const val GENERATED_FILES_DIR = "compose_goldens"
const val EXTENSION = "png"

fun ImageBitmap.compareToGolden(testName: String) {
val bitmap = toBitmap()
val existingGolden = readExistingGolden(testName)
val reasonForWrite = if (existingGolden == null) {
"Missing golden"
} else if (!existingGolden.sameAs(bitmap)) {
"Different golden"
} else {
null
}
if (reasonForWrite != null) {
val filePath = writeBitmap(bitmap, testName)
throw IllegalStateException("$reasonForWrite for $testName, wrote a new one. cd to androidTest/assets and run: adb pull $filePath")
}
}

private fun ImageBitmap.toBitmap(): Bitmap {
val pixels = toPixelMap()
val bitmap = Bitmap.createBitmap(pixels.width, pixels.height, Bitmap.Config.ARGB_8888)
bitmap.setPixels(
pixels.buffer,
pixels.bufferOffset,
pixels.stride,
0,
0,
pixels.width,
pixels.height
)
return bitmap
}

private fun readExistingGolden(testName: String): Bitmap? {
return try {
ApplicationProvider.getApplicationContext<Context>()
.assets
.open(testFileName(testName)).use {
val options = BitmapFactory.Options()
options.inScaled = false
BitmapFactory.decodeStream(it, null, options)
}
} catch (e: FileNotFoundException) {
null
}
}

private fun testFileName(testName: String) = "$testName.$EXTENSION"

private fun getTestFilesDir(): File {
val dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)
return File(dir, GENERATED_FILES_DIR)
}

private fun writeBitmap(bitmap: Bitmap, testName: String): String {
val testFilesDir = getTestFilesDir()
require(!(!testFilesDir.exists() && !testFilesDir.mkdirs())) { "Failed to make directory: $testFilesDir" }
val file = File(testFilesDir, testFileName(testName))
check(!(file.exists() && !file.delete())) { "Failed to remove existing file: $file" }
var os: OutputStream? = null
try {
os = BufferedOutputStream(FileOutputStream(file))
bitmap.compress(Bitmap.CompressFormat.PNG, 100, os)
os.close()
} catch (e: IOException) {
throw RuntimeException(e)
} finally {
if (os != null) {
try {
os.close()
} catch (e: IOException) {
// Ignored.
}
}
}
return file.absolutePath
}

0 comments on commit 6ff7f32

Please sign in to comment.