Skip to content

Commit

Permalink
Gracefully handle compose errors and record compose preview informati…
Browse files Browse the repository at this point in the history
…on for displaying
  • Loading branch information
rbro112 committed May 24, 2024
1 parent 2dfd431 commit 5df6ba3
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ fun TextRowWithIcon(
@FontScalePreviews
@Composable
fun TextRowWithIconPreviewFromMain() {
throw IllegalStateException("This should not be called")
TextRowWithIcon(
titleText = stringResource(com.emergetools.snapshots.sample.R.string.sample_title),
subtitleText = stringResource(com.emergetools.snapshots.sample.R.string.sample_subtitle)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,16 @@ class EmergeSnapshots : TestRule {
composePreviewSnapshotConfig = composePreviewSnapshotConfig,
)
}

internal fun saveError(
type: SnapshotType,
composePreviewSnapshotConfig: ComposePreviewSnapshotConfig? = null,
) {
SnapshotSaver.saveError(
displayName = null,
fqn = fqn,
type = type,
composePreviewSnapshotConfig = composePreviewSnapshotConfig,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,40 @@ enum class SnapshotType {
ACTIVITY,
}

sealed class SnapshotMetadata {
abstract val name: String
abstract val displayName: String?
abstract val fqn: String
abstract val type: SnapshotType
abstract val composePreviewSnapshotConfig: ComposePreviewSnapshotConfig?
}

@Serializable
internal data class SnapshotImageMetadata(
// Used as the primary key
val name: String,
override val name: String,
@Deprecated("Use name instead")
val keyName: String,
// User defined name, or set to defaults by our backend
val displayName: String?,
override val displayName: String?,
// Filename of the outputted image
val filename: String,
// FQN of the test class
val fqn: String,
val type: SnapshotType,
override val fqn: String,
override val type: SnapshotType,
// Compose-specific metadata, only set if type == COMPOSABLE
override val composePreviewSnapshotConfig: ComposePreviewSnapshotConfig? = null,
): SnapshotMetadata()

@Serializable
internal data class SnapshotErrorMetadata(
// Used as the primary key
override val name: String,
// User defined name, or set to defaults by our backend
override val displayName: String?,
// FQN of the test class
override val fqn: String,
override val type: SnapshotType,
// Compose-specific metadata, only set if type == COMPOSABLE
val composePreviewSnapshotConfig: ComposePreviewSnapshotConfig? = null,
)
override val composePreviewSnapshotConfig: ComposePreviewSnapshotConfig? = null,
): SnapshotMetadata()
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,35 @@ internal object SnapshotSaver {
}
}

fun saveError(
displayName: String?,
fqn: String,
type: SnapshotType,
composePreviewSnapshotConfig: ComposePreviewSnapshotConfig? = null,
) {
val snapshotsDir = File(filesDir, SNAPSHOTS_DIR_NAME)
if (!snapshotsDir.exists() && !snapshotsDir.mkdirs()) {
error("Unable to create snapshots storage directory.")
}

// We need a stable key to use for the filename and comparison
// For composables, see [ComposePreviewSnapshotConfig.keyName]
// For non-composables, we use the normalized displayName
val keyName = keyName(
type = type,
displayName = displayName,
composePreviewSnapshotConfig = composePreviewSnapshotConfig,
)
saveErrorMetadata(
snapshotsDir = snapshotsDir,
displayName = displayName,
keyName = keyName,
type = type,
fqn = fqn,
composePreviewSnapshotConfig = composePreviewSnapshotConfig,
)
}

private fun saveImage(
snapshotsDir: File,
keyName: String,
Expand Down Expand Up @@ -107,6 +136,29 @@ internal object SnapshotSaver {
}
}

private fun saveErrorMetadata(
snapshotsDir: File,
keyName: String,
displayName: String?,
fqn: String,
type: SnapshotType,
composePreviewSnapshotConfig: ComposePreviewSnapshotConfig? = null,
) {
val metadata = SnapshotErrorMetadata(
name = keyName,
displayName = displayName,
fqn = fqn,
type = type,
composePreviewSnapshotConfig = composePreviewSnapshotConfig,
)

val jsonString = Json.encodeToString(metadata)

saveFile(snapshotsDir, "$keyName$JSON_EXTENSION") {
write(jsonString.toByteArray(Charset.defaultCharset()))
}
}

@VisibleForTesting
fun keyName(
type: SnapshotType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import androidx.compose.ui.test.junit4.ComposeContentTestRule
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.test.platform.app.InstrumentationRegistry
import com.emergetools.snapshots.EmergeSnapshots
import com.emergetools.snapshots.SnapshotSaver
import com.emergetools.snapshots.SnapshotType
import com.emergetools.snapshots.SnapshotType.COMPOSABLE
import com.emergetools.snapshots.shared.ComposePreviewSnapshotConfig
import com.emergetools.snapshots.shared.ComposeSnapshots
import kotlinx.serialization.json.Json
Expand Down Expand Up @@ -52,24 +55,31 @@ class EmergeComposeSnapshotReflectiveParameterizedInvoker(private val previewCon

@Test
fun reflectiveComposableInvoker() {
composeRule.setContent {
val klass = Class.forName(previewConfig.fullyQualifiedClassName)
val composableMethod = klass.methods.find {
it.name == previewConfig.originalFqn.substringAfterLast(".")
}
try {
composeRule.setContent {
val klass = Class.forName(previewConfig.fullyQualifiedClassName)
val composableMethod = klass.methods.find {
it.name == previewConfig.originalFqn.substringAfterLast(".")
}

Log.d(TAG, "Invoking composable method: $composableMethod")
Log.d(TAG, "Invoking composable method: $composableMethod")

composableMethod?.let {
it.isAccessible = true
SnapshotVariantProvider(previewConfig) {
it.invoke(null, currentComposer, 0)
composableMethod?.let {
it.isAccessible = true
SnapshotVariantProvider(previewConfig) {
it.invoke(null, currentComposer, 0)
}
} ?: run {
// TODO: Ryan look to write error to file for better debugging
error("Unable to find composable method: ${previewConfig.originalFqn}")
}
} ?: run {
// TODO: Ryan look to write error to file for better debugging
error("Unable to find composable method: ${previewConfig.originalFqn}")
}
snapshotRule.take(composeRule, previewConfig)
} catch (e: Exception) {
Log.e(TAG, "Error invoking composable method", e)
snapshotRule.saveError(COMPOSABLE, previewConfig)
// Re-throw to fail the test
throw e
}
snapshotRule.take(composeRule, previewConfig)
}
}

0 comments on commit 5df6ba3

Please sign in to comment.