Skip to content

Commit

Permalink
Add tests for FakeImageLoaderEngine and rememberAsyncImagePainter. (#…
Browse files Browse the repository at this point in the history
…1909)

* Add tests for FakeImageLoaderEngine and rememberAsyncImagePainter.

* Use same screen size as Roborazzi.

* Formatting.

* Formatting.

* Fix tests.
  • Loading branch information
colinrtwhite authored Oct 30, 2023
1 parent 879fb55 commit 2e93183
Show file tree
Hide file tree
Showing 13 changed files with 105 additions and 22 deletions.
18 changes: 10 additions & 8 deletions coil-compose-base/src/main/java/coil/compose/AsyncImagePainter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ fun rememberAsyncImagePainter(
@Stable
class AsyncImagePainter internal constructor(
request: ImageRequest,
imageLoader: ImageLoader
imageLoader: ImageLoader,
) : Painter(), RememberObserver {

private var rememberScope: CoroutineScope? = null
Expand Down Expand Up @@ -263,9 +263,11 @@ class AsyncImagePainter internal constructor(
/** Update the [request] to work with [AsyncImagePainter]. */
private fun updateRequest(request: ImageRequest): ImageRequest {
return request.newBuilder()
.target(onStart = { placeholder ->
updateState(State.Loading(placeholder?.toPainter()))
})
.target(
onStart = { placeholder ->
updateState(State.Loading(placeholder?.toPainter()))
},
)
.apply {
if (request.defined.sizeResolver == null) {
// If no other size resolver is set, suspend until the canvas size is positive.
Expand Down Expand Up @@ -318,7 +320,7 @@ class AsyncImagePainter internal constructor(
contentScale = contentScale,
durationMillis = transition.durationMillis,
fadeStart = result !is SuccessResult || !result.isPlaceholderCached,
preferExactIntrinsicSize = transition.preferExactIntrinsicSize
preferExactIntrinsicSize = transition.preferExactIntrinsicSize,
)
} else {
return null
Expand Down Expand Up @@ -379,7 +381,7 @@ private fun validateRequest(request: ImageRequest) {
when (request.data) {
is ImageRequest.Builder -> unsupportedData(
name = "ImageRequest.Builder",
description = "Did you forget to call ImageRequest.Builder.build()?"
description = "Did you forget to call ImageRequest.Builder.build()?",
)
is ImageBitmap -> unsupportedData("ImageBitmap")
is ImageVector -> unsupportedData("ImageVector")
Expand All @@ -390,7 +392,7 @@ private fun validateRequest(request: ImageRequest) {

private fun unsupportedData(
name: String,
description: String = "If you wish to display this $name, use androidx.compose.foundation.Image."
description: String = "If you wish to display this $name, use androidx.compose.foundation.Image.",
): Nothing = throw IllegalArgumentException("Unsupported type: $name. $description")

private val Size.isPositive get() = width >= 0.5 && height >= 0.5
Expand All @@ -399,7 +401,7 @@ private fun Size.toSizeOrNull() = when {
isUnspecified -> CoilSize.ORIGINAL
isPositive -> CoilSize(
width = if (width.isFinite()) Dimension(width.roundToInt()) else Dimension.Undefined,
height = if (height.isFinite()) Dimension(height.roundToInt()) else Dimension.Undefined
height = if (height.isFinite()) Dimension(height.roundToInt()) else Dimension.Undefined,
)
else -> null
}
Expand Down
49 changes: 44 additions & 5 deletions coil-test-paparazzi/src/test/java/coil/test/PaparazziTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@ package coil.test
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.widget.ImageView
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import app.cash.paparazzi.DeviceConfig.Companion.PIXEL_6
import app.cash.paparazzi.DeviceConfig
import app.cash.paparazzi.Paparazzi
import coil.ImageLoader
import coil.compose.AsyncImage
import coil.compose.rememberAsyncImagePainter
import coil.decode.ImageSource
import coil.request.ImageRequest
import coil.size.Size
import kotlin.test.assertTrue
import okio.Buffer
import org.junit.Rule
Expand All @@ -19,13 +24,16 @@ class PaparazziTest {

@get:Rule
val paparazzi = Paparazzi(
deviceConfig = PIXEL_6,
deviceConfig = DeviceConfig(
screenWidth = 320,
screenHeight = 470,
),
theme = "android:Theme.Material.Light.NoActionBar.Fullscreen",
showSystemUi = false,
)

@Test
fun loadView() {
fun imageView() {
val url = "https://www.example.com/image.jpg"
val drawable = object : ColorDrawable(Color.RED) {
override fun getIntrinsicWidth() = 100
Expand All @@ -51,9 +59,8 @@ class PaparazziTest {
}

@Test
fun loadCompose() {
fun asyncImage() {
val url = "https://www.example.com/image.jpg"
// Wrap the color drawable so it isn't automatically converted into a ColorPainter.
val drawable = object : ColorDrawable(Color.RED) {
override fun getIntrinsicWidth() = 100
override fun getIntrinsicHeight() = 100
Expand All @@ -71,6 +78,38 @@ class PaparazziTest {
contentDescription = null,
imageLoader = imageLoader,
contentScale = ContentScale.None,
modifier = Modifier.fillMaxSize(),
)
}
}

@Test
fun rememberAsyncImagePainter() {
val url = "https://www.example.com/image.jpg"
val drawable = object : ColorDrawable(Color.RED) {
override fun getIntrinsicWidth() = 100
override fun getIntrinsicHeight() = 100
}
val engine = FakeImageLoaderEngine.Builder()
.intercept(url, drawable)
.build()
val imageLoader = ImageLoader.Builder(paparazzi.context)
.components { add(engine) }
.build()

paparazzi.snapshot {
Image(
painter = rememberAsyncImagePainter(
// TODO: Figure out how to avoid having to specify an immediate size.
model = ImageRequest.Builder(paparazzi.context)
.data(url)
.size(Size.ORIGINAL)
.build(),
imageLoader = imageLoader,
),
contentDescription = null,
contentScale = ContentScale.None,
modifier = Modifier.fillMaxSize(),
)
}
}
Expand Down
Binary file not shown.
Binary file not shown.
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
Expand Up @@ -2,6 +2,7 @@ package coil.test

import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
Expand All @@ -10,6 +11,9 @@ import androidx.compose.ui.test.onRoot
import androidx.test.ext.junit.runners.AndroidJUnit4
import coil.ImageLoader
import coil.compose.AsyncImage
import coil.compose.rememberAsyncImagePainter
import coil.request.ImageRequest
import coil.size.Size
import coil.util.ComposeTestActivity
import com.github.takahirom.roborazzi.RoborazziRule
import org.junit.Rule
Expand All @@ -29,14 +33,14 @@ class RoborazziComposeTest {
composeRule = composeTestRule,
captureRoot = composeTestRule.onRoot(),
options = RoborazziRule.Options(
captureType = RoborazziRule.CaptureType.LastImage(),
outputDirectoryPath = "src/test/snapshots/images",
)
)

@Test
fun loadCompose() {
fun asyncImage() {
val url = "https://www.example.com/image.jpg"
// Wrap the color drawable so it isn't automatically converted into a ColorPainter.
val drawable = object : ColorDrawable(Color.RED) {
override fun getIntrinsicWidth() = 100
override fun getIntrinsicHeight() = 100
Expand All @@ -58,4 +62,35 @@ class RoborazziComposeTest {
)
}
}

@Test
fun rememberAsyncImagePainter() {
val url = "https://www.example.com/image.jpg"
val drawable = object : ColorDrawable(Color.RED) {
override fun getIntrinsicWidth() = 100
override fun getIntrinsicHeight() = 100
}
val engine = FakeImageLoaderEngine.Builder()
.intercept(url, drawable)
.build()
val imageLoader = ImageLoader.Builder(composeTestRule.activity)
.components { add(engine) }
.build()

composeTestRule.setContent {
Image(
// TODO: Figure out how to avoid having to specify an immediate size.
painter = rememberAsyncImagePainter(
model = ImageRequest.Builder(composeTestRule.activity)
.data(url)
.size(Size.ORIGINAL)
.build(),
imageLoader = imageLoader,
),
contentDescription = null,
contentScale = ContentScale.None,
modifier = Modifier.fillMaxSize(),
)
}
}
}
17 changes: 11 additions & 6 deletions coil-test-roborazzi/src/test/java/coil/test/RoborazziViewTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import coil.ImageLoader
import coil.request.ImageRequest
import coil.util.ViewTestActivity
import coil.util.activity
import com.github.takahirom.roborazzi.captureRoboImage
import com.github.takahirom.roborazzi.RoborazziRule
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
Expand All @@ -24,8 +24,17 @@ class RoborazziViewTest {
@get:Rule
val activityRule = activityScenarioRule<ViewTestActivity>()

@get:Rule
val roborazziRule = RoborazziRule(
captureRoot = onView(isRoot()),
options = RoborazziRule.Options(
captureType = RoborazziRule.CaptureType.LastImage(),
outputDirectoryPath = "src/test/snapshots/images",
)
)

@Test
fun loadView() {
fun imageView() {
val url = "https://www.example.com/image.jpg"
val drawable = object : ColorDrawable(Color.RED) {
override fun getIntrinsicWidth() = 100
Expand All @@ -47,9 +56,5 @@ class RoborazziViewTest {

// Don't suspend to test that the image view is updated synchronously.
imageLoader.enqueue(request)

// https://github.com/takahirom/roborazzi/issues/9
onView(isRoot())
.captureRoboImage("src/test/snapshots/images/coil.test.RoborazziViewTest.loadView.png")
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 3 additions & 1 deletion coil-test/src/main/java/coil/test/FakeImageLoaderEngine.kt
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,9 @@ class FakeImageLoaderEngine private constructor(
error("No interceptors handled this request and no fallback is set: ${chain.request.data}")
}
requestTransformer = RequestTransformer { request ->
request.newBuilder().transitionFactory(Transition.Factory.NONE).build()
request.newBuilder()
.transitionFactory(Transition.Factory.NONE)
.build()
}
}

Expand Down

0 comments on commit 2e93183

Please sign in to comment.