Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 15 additions & 8 deletions paparazzi/api/paparazzi.api
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ public final class app/cash/paparazzi/EnvironmentKt {
public static final fun detectEnvironment ()Lapp/cash/paparazzi/Environment;
}

public abstract interface class app/cash/paparazzi/FileNameProvider {
public abstract fun snapshotFileName (Lapp/cash/paparazzi/Snapshot;Ljava/lang/String;)Ljava/lang/String;
}

public final class app/cash/paparazzi/Flags {
public static final field $stable I
public static final field DEBUG_LINKED_OBJECTS Ljava/lang/String;
Expand All @@ -97,7 +101,8 @@ public final class app/cash/paparazzi/HtmlReportWriter : app/cash/paparazzi/Snap
public fun <init> (Ljava/lang/String;)V
public fun <init> (Ljava/lang/String;Ljava/io/File;)V
public fun <init> (Ljava/lang/String;Ljava/io/File;Ljava/io/File;)V
public synthetic fun <init> (Ljava/lang/String;Ljava/io/File;Ljava/io/File;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (Ljava/lang/String;Ljava/io/File;Ljava/io/File;Lapp/cash/paparazzi/FileNameProvider;)V
public synthetic fun <init> (Ljava/lang/String;Ljava/io/File;Ljava/io/File;Lapp/cash/paparazzi/FileNameProvider;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun close ()V
public fun newFrameHandler (Lapp/cash/paparazzi/Snapshot;II)Lapp/cash/paparazzi/SnapshotHandler$FrameHandler;
}
Expand All @@ -117,12 +122,13 @@ public final class app/cash/paparazzi/Paparazzi : org/junit/rules/TestRule {
public fun <init> (Lapp/cash/paparazzi/Environment;Lapp/cash/paparazzi/DeviceConfig;Ljava/lang/String;Lcom/android/ide/common/rendering/api/SessionParams$RenderingMode;)V
public fun <init> (Lapp/cash/paparazzi/Environment;Lapp/cash/paparazzi/DeviceConfig;Ljava/lang/String;Lcom/android/ide/common/rendering/api/SessionParams$RenderingMode;Z)V
public fun <init> (Lapp/cash/paparazzi/Environment;Lapp/cash/paparazzi/DeviceConfig;Ljava/lang/String;Lcom/android/ide/common/rendering/api/SessionParams$RenderingMode;ZD)V
public fun <init> (Lapp/cash/paparazzi/Environment;Lapp/cash/paparazzi/DeviceConfig;Ljava/lang/String;Lcom/android/ide/common/rendering/api/SessionParams$RenderingMode;ZDLapp/cash/paparazzi/SnapshotHandler;)V
public fun <init> (Lapp/cash/paparazzi/Environment;Lapp/cash/paparazzi/DeviceConfig;Ljava/lang/String;Lcom/android/ide/common/rendering/api/SessionParams$RenderingMode;ZDLapp/cash/paparazzi/SnapshotHandler;Ljava/util/Set;)V
public fun <init> (Lapp/cash/paparazzi/Environment;Lapp/cash/paparazzi/DeviceConfig;Ljava/lang/String;Lcom/android/ide/common/rendering/api/SessionParams$RenderingMode;ZDLapp/cash/paparazzi/SnapshotHandler;Ljava/util/Set;Z)V
public fun <init> (Lapp/cash/paparazzi/Environment;Lapp/cash/paparazzi/DeviceConfig;Ljava/lang/String;Lcom/android/ide/common/rendering/api/SessionParams$RenderingMode;ZDLapp/cash/paparazzi/SnapshotHandler;Ljava/util/Set;ZZ)V
public fun <init> (Lapp/cash/paparazzi/Environment;Lapp/cash/paparazzi/DeviceConfig;Ljava/lang/String;Lcom/android/ide/common/rendering/api/SessionParams$RenderingMode;ZDLapp/cash/paparazzi/SnapshotHandler;Ljava/util/Set;ZZZ)V
public synthetic fun <init> (Lapp/cash/paparazzi/Environment;Lapp/cash/paparazzi/DeviceConfig;Ljava/lang/String;Lcom/android/ide/common/rendering/api/SessionParams$RenderingMode;ZDLapp/cash/paparazzi/SnapshotHandler;Ljava/util/Set;ZZZILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (Lapp/cash/paparazzi/Environment;Lapp/cash/paparazzi/DeviceConfig;Ljava/lang/String;Lcom/android/ide/common/rendering/api/SessionParams$RenderingMode;ZDLapp/cash/paparazzi/FileNameProvider;)V
public fun <init> (Lapp/cash/paparazzi/Environment;Lapp/cash/paparazzi/DeviceConfig;Ljava/lang/String;Lcom/android/ide/common/rendering/api/SessionParams$RenderingMode;ZDLapp/cash/paparazzi/FileNameProvider;Lapp/cash/paparazzi/SnapshotHandler;)V
public fun <init> (Lapp/cash/paparazzi/Environment;Lapp/cash/paparazzi/DeviceConfig;Ljava/lang/String;Lcom/android/ide/common/rendering/api/SessionParams$RenderingMode;ZDLapp/cash/paparazzi/FileNameProvider;Lapp/cash/paparazzi/SnapshotHandler;Ljava/util/Set;)V
public fun <init> (Lapp/cash/paparazzi/Environment;Lapp/cash/paparazzi/DeviceConfig;Ljava/lang/String;Lcom/android/ide/common/rendering/api/SessionParams$RenderingMode;ZDLapp/cash/paparazzi/FileNameProvider;Lapp/cash/paparazzi/SnapshotHandler;Ljava/util/Set;Z)V
public fun <init> (Lapp/cash/paparazzi/Environment;Lapp/cash/paparazzi/DeviceConfig;Ljava/lang/String;Lcom/android/ide/common/rendering/api/SessionParams$RenderingMode;ZDLapp/cash/paparazzi/FileNameProvider;Lapp/cash/paparazzi/SnapshotHandler;Ljava/util/Set;ZZ)V
public fun <init> (Lapp/cash/paparazzi/Environment;Lapp/cash/paparazzi/DeviceConfig;Ljava/lang/String;Lcom/android/ide/common/rendering/api/SessionParams$RenderingMode;ZDLapp/cash/paparazzi/FileNameProvider;Lapp/cash/paparazzi/SnapshotHandler;Ljava/util/Set;ZZZ)V
public synthetic fun <init> (Lapp/cash/paparazzi/Environment;Lapp/cash/paparazzi/DeviceConfig;Ljava/lang/String;Lcom/android/ide/common/rendering/api/SessionParams$RenderingMode;ZDLapp/cash/paparazzi/FileNameProvider;Lapp/cash/paparazzi/SnapshotHandler;Ljava/util/Set;ZZZILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun apply (Lorg/junit/runners/model/Statement;Lorg/junit/runner/Description;)Lorg/junit/runners/model/Statement;
public final fun close ()V
public final fun getContext ()Landroid/content/Context;
Expand Down Expand Up @@ -178,7 +184,8 @@ public final class app/cash/paparazzi/SnapshotVerifier : app/cash/paparazzi/Snap
public static final field $stable I
public fun <init> (D)V
public fun <init> (DLjava/io/File;)V
public synthetic fun <init> (DLjava/io/File;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (DLjava/io/File;Lapp/cash/paparazzi/FileNameProvider;)V
public synthetic fun <init> (DLjava/io/File;Lapp/cash/paparazzi/FileNameProvider;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun close ()V
public fun newFrameHandler (Lapp/cash/paparazzi/Snapshot;II)Lapp/cash/paparazzi/SnapshotHandler$FrameHandler;
}
Expand Down
24 changes: 24 additions & 0 deletions paparazzi/src/main/java/app/cash/paparazzi/FileNameProvider.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package app.cash.paparazzi

import java.util.Locale

public interface FileNameProvider {
public fun snapshotFileName(snapshot: Snapshot, extension: String): String
}

internal class DefaultFileNameProvider(
private val delimiter: String = "_"
) : FileNameProvider {

override fun snapshotFileName(snapshot: Snapshot, extension: String): String {
val name = snapshot.name
val formattedLabel = if (name != null) {
"$delimiter${name.lowercase(Locale.US).replace("\\s".toRegex(), delimiter)}"
} else {
""
}

val testName = snapshot.testName
return "${testName.packageName}${delimiter}${testName.className}${delimiter}${testName.methodName}$formattedLabel.$extension"
}
}
20 changes: 15 additions & 5 deletions paparazzi/src/main/java/app/cash/paparazzi/HtmlReportWriter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ import javax.imageio.ImageIO
public class HtmlReportWriter @JvmOverloads constructor(
private val runName: String = defaultRunName(),
private val rootDirectory: File = File(System.getProperty("paparazzi.report.dir")),
snapshotRootDirectory: File = File(System.getProperty("paparazzi.snapshot.dir"))
snapshotRootDirectory: File = File(System.getProperty("paparazzi.snapshot.dir")),
private val fileNameProvider: FileNameProvider = DefaultFileNameProvider()
) : SnapshotHandler {
private val runsDirectory: File = File(rootDirectory, "runs")
private val imagesDirectory: File = File(rootDirectory, "images")
Expand Down Expand Up @@ -101,7 +102,10 @@ public class HtmlReportWriter @JvmOverloads constructor(
val shot = if (hashes.size == 1) {
val original = File(imagesDirectory, "${hashes[0]}.png")
if (isRecording) {
val goldenFile = File(goldenImagesDirectory, snapshot.toFileName("_", "png"))
val goldenFile = File(
goldenImagesDirectory,
fileNameProvider.snapshotFileName(snapshot, extension = "png")
)
original.copyTo(goldenFile, overwrite = true)
}
snapshot.copy(file = original.toJsonPath())
Expand All @@ -112,15 +116,21 @@ public class HtmlReportWriter @JvmOverloads constructor(
for ((index, frameHash) in hashes.withIndex()) {
val originalFrame = File(imagesDirectory, "$frameHash.png")
val frameSnapshot = snapshot.copy(name = "${snapshot.name} $index")
val goldenFile = File(goldenImagesDirectory, frameSnapshot.toFileName("_", "png"))
val goldenFile = File(
goldenImagesDirectory,
fileNameProvider.snapshotFileName(frameSnapshot, extension = "png")
)
if (!goldenFile.exists()) {
originalFrame.copyTo(goldenFile)
}
}
}
val original = File(videosDirectory, "$hash.mov")
if (isRecording) {
val goldenFile = File(goldenVideosDirectory, snapshot.toFileName("_", "mov"))
val goldenFile = File(
goldenVideosDirectory,
fileNameProvider.snapshotFileName(snapshot, extension = "mov")
)
if (!goldenFile.exists()) {
original.copyTo(goldenFile)
}
Expand Down Expand Up @@ -290,5 +300,5 @@ internal val filenameSafeChars = CharMatcher.inRange('a', 'z')
.or(CharMatcher.anyOf("_-.~@^()[]{}:;,"))

internal fun String.sanitizeForFilename(): String? {
return filenameSafeChars.negate().replaceFrom(toLowerCase(Locale.US), '_')
return filenameSafeChars.negate().replaceFrom(lowercase(Locale.US), '_')
}
18 changes: 13 additions & 5 deletions paparazzi/src/main/java/app/cash/paparazzi/Paparazzi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,11 @@ public class Paparazzi @JvmOverloads constructor(
private val renderingMode: RenderingMode = RenderingMode.NORMAL,
private val appCompatEnabled: Boolean = true,
private val maxPercentDifference: Double = 0.1,
private val snapshotHandler: SnapshotHandler = determineHandler(maxPercentDifference),
private val fileNameProvider: FileNameProvider = DefaultFileNameProvider(),
private val snapshotHandler: SnapshotHandler = determineHandler(
maxPercentDifference,
fileNameProvider
),
private val renderExtensions: Set<RenderExtension> = setOf(),
private val supportsRtl: Boolean = false,
private val showSystemUi: Boolean = false,
Expand Down Expand Up @@ -675,11 +679,15 @@ public class Paparazzi @JvmOverloads constructor(
}
}

private fun determineHandler(maxPercentDifference: Double): SnapshotHandler =
if (isVerifying) {
SnapshotVerifier(maxPercentDifference)
private fun determineHandler(
maxPercentDifference: Double,
fileNameProvider: FileNameProvider
): SnapshotHandler {
return if (isVerifying) {
SnapshotVerifier(maxPercentDifference, fileNameProvider = fileNameProvider)
} else {
HtmlReportWriter()
HtmlReportWriter(fileNameProvider = fileNameProvider)
}
}
}
}
13 changes: 0 additions & 13 deletions paparazzi/src/main/java/app/cash/paparazzi/Snapshot.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ package app.cash.paparazzi

import dev.drewhamilton.poko.Poko
import java.util.Date
import java.util.Locale

@Poko
public class Snapshot(
Expand All @@ -35,15 +34,3 @@ public class Snapshot(
file: String? = this.file
): Snapshot = Snapshot(name, testName, timestamp, tags, file)
}

internal fun Snapshot.toFileName(
delimiter: String = "_",
extension: String
): String {
val formattedLabel = if (name != null) {
"$delimiter${name.toLowerCase(Locale.US).replace("\\s".toRegex(), delimiter)}"
} else {
""
}
return "${testName.packageName}${delimiter}${testName.className}${delimiter}${testName.methodName}$formattedLabel.$extension"
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ import javax.imageio.ImageIO

public class SnapshotVerifier @JvmOverloads constructor(
private val maxPercentDifference: Double,
rootDirectory: File = File(System.getProperty("paparazzi.snapshot.dir"))
rootDirectory: File = File(System.getProperty("paparazzi.snapshot.dir")),
private val fileNameProvider: FileNameProvider = DefaultFileNameProvider()
) : SnapshotHandler {
private val imagesDirectory: File = File(rootDirectory, "images")
private val videosDirectory: File = File(rootDirectory, "videos")
Expand All @@ -41,7 +42,8 @@ public class SnapshotVerifier @JvmOverloads constructor(
return object : FrameHandler {
override fun handle(image: BufferedImage) {
// Note: does not handle videos or its frames at the moment
val expected = File(imagesDirectory, snapshot.toFileName(extension = "png"))
val expected =
File(imagesDirectory, fileNameProvider.snapshotFileName(snapshot, extension = "png"))
if (!expected.exists()) {
throw AssertionError("File $expected does not exist")
}
Expand Down
37 changes: 37 additions & 0 deletions paparazzi/src/test/java/app/cash/paparazzi/HtmlReportWriterTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,43 @@ class HtmlReportWriterTest {
}
}

@Test
fun useFileNameProvider() {
// set record mode
System.setProperty("paparazzi.test.record", "true")

val htmlReportWriter = HtmlReportWriter(
"record_run",
fileNameProvider = object : FileNameProvider {
override fun snapshotFileName(snapshot: Snapshot, extension: String): String {
return "${snapshot.name}.$extension"
}
},
rootDirectory = reportRoot.root,
snapshotRootDirectory = snapshotRoot.root
)
htmlReportWriter.use {
val snapshot = Snapshot(
name = "test",
testName = TestName("app.cash.paparazzi", "HomeView", "testSettings"),
timestamp = Instant.parse("2021-02-23T10:27:43Z").toDate()
)
val golden = File("${snapshotRoot.root}/images/test.png")

// precondition
assertThat(golden).doesNotExist()

// take 1
val frameHandler1 = htmlReportWriter.newFrameHandler(
snapshot = snapshot,
frameCount = 1,
fps = -1
)
frameHandler1.use { frameHandler1.handle(anyImage) }
assertThat(golden).exists()
}
}

private fun Instant.toDate() = Date(toEpochMilli())

private fun File.lastModifiedTime(): FileTime {
Expand Down