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
2 changes: 1 addition & 1 deletion android/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
build
/app/release/*
/captures
.externalNativeBuild
Expand Down
3 changes: 3 additions & 0 deletions android/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ plugins {
alias(libs.plugins.sentry)
alias(libs.plugins.roborazzi)
alias(libs.plugins.androidx.room)
alias(libs.plugins.paparazzi)
id("com.emergetools.paparazzi.preview-scanner")
}

android {
Expand Down Expand Up @@ -125,6 +127,7 @@ sentry {
}

dependencies {
implementation(project(":ui-components"))

implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
Expand Down
6 changes: 3 additions & 3 deletions android/app/src/test/kotlin/BookmarksScreenComposeTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.GraphicsMode
import java.time.Instant

@GraphicsMode(GraphicsMode.Mode.NATIVE)
@RunWith(RobolectricTestRunner::class)
//@GraphicsMode(GraphicsMode.Mode.NATIVE)
//@RunWith(RobolectricTestRunner::class)
class BookmarksScreenComposeTest {

@get:Rule
val composeRule = createComposeRule()

@Test
// @Test
fun roborazziTest() {
composeRule.setContent {
HackerNewsTheme {
Expand Down
6 changes: 3 additions & 3 deletions android/app/src/test/kotlin/StoryRowComposeTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.GraphicsMode

@GraphicsMode(GraphicsMode.Mode.NATIVE)
@RunWith(RobolectricTestRunner::class)
//@GraphicsMode(GraphicsMode.Mode.NATIVE)
//@RunWith(RobolectricTestRunner::class)
class StoryRowComposeTest {

@get:Rule
val composeRule = createComposeRule()

@Test
// @Test
fun roborazziTest() {
composeRule.setContent {
HackerNewsTheme {
Expand Down
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.
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.
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.
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.
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.
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.
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.
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.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 5 additions & 1 deletion android/gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ ksp = "2.3.4"
coreKtx = "1.17.0"
junit = "4.13.2"
junitVersion = "1.1.5" # This is to match Compose's version
reflections = "0.10.2"
robolectric = "4.16"
roborazzi = "1.52.0"
espressoCore = "3.5.0" # This is to match Compose's version
Expand All @@ -18,7 +19,7 @@ navigation = "2.9.6"
browser = "1.9.0"
emergePlugin = "4.4.0"
emergeSnapshots = "1.5.0"
sentry = "6.0.0-beta.3"
sentry = "6.0.0-rc.1"
shapes = "1.1.0"
datastore = "1.2.0"
room = "2.8.4"
Expand Down Expand Up @@ -55,6 +56,8 @@ androidx-material3 = { group = "androidx.compose.material3", name = "material3"

extendedspans = { group = "me.saket.extendedspans", name = "extendedspans", version.ref = "extendedspans" }

android-gradle-build = { module = "com.android.tools.build:gradle", version.ref = "agp" }
kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" }
retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }
retrofit-kotlinx-serialization = { group = "com.squareup.retrofit2", name = "converter-kotlinx-serialization", version.ref = "retrofit" }
Expand Down Expand Up @@ -84,6 +87,7 @@ kotlin-ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
emerge = { id = "com.emergetools.android", version.ref = "emergePlugin" }
sentry = { id = "io.sentry.android.gradle", version.ref = "sentry" }
roborazzi = { id = "io.github.takahirom.roborazzi", version.ref = "roborazzi" }
paparazzi = { id = "app.cash.paparazzi", version = "2.0.0-alpha02" }
androidx-room = { id = "androidx.room", version.ref = "room" }
android-test = { id = "com.android.test", version.ref = "agp" }

42 changes: 42 additions & 0 deletions android/paparazzi-preview-scanner-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
`kotlin-dsl`
`java-gradle-plugin`
}

dependencies {
compileOnly(libs.android.gradle.build)
implementation(libs.kotlin.gradle.plugin)

testImplementation(gradleTestKit())
testImplementation(libs.junit)
}

gradlePlugin {
plugins {
create("paparazziPreviewScanner") {
id = "com.emergetools.paparazzi.preview-scanner"
implementationClass = "com.emergetools.paparazzi.PaparazziPreviewScannerPlugin"
displayName = "Paparazzi Preview Scanner"
description = "Auto-generates Paparazzi tests for Compose previews"
}
}
}

java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

tasks.withType<ValidatePlugins>().configureEach {
failOnWarning = true
enableStricterValidation = true
}

tasks.withType<KotlinCompile>().configureEach {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_17)
}
}
12 changes: 12 additions & 0 deletions android/paparazzi-preview-scanner-plugin/settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
rootProject.name = "paparazzi-preview-scanner-plugin"

dependencyResolutionManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
versionCatalogs.create("libs") { from(files("../gradle/libs.versions.toml")) }

repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.emergetools.paparazzi

import org.gradle.api.Project
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.SetProperty
import javax.inject.Inject

abstract class PaparazziPreviewScannerExtension @Inject constructor(project: Project) {

private val objects = project.objects

/**
* Enable/disable the plugin.
* Default: true
*/
val enabled: Property<Boolean> =
objects.property(Boolean::class.java).convention(true)

/**
* Package names to scan for @Preview annotations.
* Default: auto-detect from android.namespace or applicationId
*/
val scanPackages: ListProperty<String> =
objects.listProperty(String::class.java).convention(emptyList())

/**
* Paparazzi version to use.
* Default: "2.0.0-alpha02"
*/
val paparazziVersion: Property<String> =
objects.property(String::class.java).convention("2.0.0-alpha02")

/**
* ComposePreviewScanner version.
* Default: "0.8.1"
*/
val previewScannerVersion: Property<String> =
objects.property(String::class.java).convention("0.8.1")

/**
* Include private previews in the scan.
* Default: false
*/
val includePrivatePreviews: Property<Boolean> =
objects.property(Boolean::class.java).convention(false)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package com.emergetools.paparazzi

import com.android.build.api.variant.AndroidComponentsExtension
import com.android.build.api.variant.HasUnitTest
import com.emergetools.paparazzi.tasks.GeneratePreviewScannerTestTask
import org.gradle.api.Action
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.tasks.TaskProvider
import javax.inject.Inject

abstract class PaparazziPreviewScannerPlugin @Inject constructor() : Plugin<Project> {

override fun apply(project: Project) {
val extension = project.extensions.create(
"paparazziPreviewScanner",
PaparazziPreviewScannerExtension::class.java,
project
)

project.pluginManager.withPlugin("com.android.application") {
configureForAndroid(project, extension)
}

project.pluginManager.withPlugin("com.android.library") {
configureForAndroid(project, extension)
}
}

private fun configureForAndroid(project: Project, extension: PaparazziPreviewScannerExtension) {
// Check if plugin is enabled
if (!extension.enabled.get()) {
project.logger.debug("Paparazzi Preview Scanner plugin is disabled")
return
}

if (!project.pluginManager.hasPlugin("app.cash.paparazzi")) {
project.logger.warn("Paparazzi plugin (app.cash.paparazzi) is not applied")
}

val androidComponents = project.extensions.getByType(AndroidComponentsExtension::class.java)

androidComponents.onVariants { variant ->
if (variant is HasUnitTest) {
println("variant has unit tests")
val unitTest = variant.unitTest ?: return@onVariants

val sourceDirs = mutableSetOf<String>()

variant.sources.java?.all?.get()?.forEach { directory ->
sourceDirs.add(directory.asFile.absolutePath)
}

variant.sources.kotlin?.all?.get()?.forEach { directory ->
sourceDirs.add(directory.asFile.absolutePath)
}

val generateTask = registerGenerateTask(project, variant.name, variant.namespace, sourceDirs.toList(), extension)
println("generateTask registered for variant ${variant.name} and ${variant.namespace}")


unitTest.sources.java!!.addGeneratedSourceDirectory(
generateTask,
GeneratePreviewScannerTestTask::outputDirectory
)

project.logger.lifecycle(
"Paparazzi Preview Scanner: Registered test generation for variant '${variant.name}'"
)
}
}

injectDependencies(project, extension)
}

private fun registerGenerateTask(
project: Project,
variantName: String,
namespace: org.gradle.api.provider.Provider<String>,
sourceDirs: List<String>,
extension: PaparazziPreviewScannerExtension
): TaskProvider<GeneratePreviewScannerTestTask> {
val taskName = "generate${variantName.replaceFirstChar { it.titlecase() }}PaparazziPreviewScannerTest"

val taskProvider = project.tasks.register(taskName, GeneratePreviewScannerTestTask::class.java)

taskProvider.configure(object : Action<GeneratePreviewScannerTestTask> {
override fun execute(task: GeneratePreviewScannerTestTask) {
task.scanPackages.set(extension.scanPackages)
task.namespace.set(namespace)
task.includePrivatePreviews.set(extension.includePrivatePreviews)
task.sourceDirs.set(sourceDirs)

task.outputDirectory.set(
project.layout.buildDirectory.dir(
"generated/source/paparazzi/${variantName}"
)
)
task.logger.lifecycle("Output directory is ${task.outputDirectory.get()}")
}
})

return taskProvider
}

private fun injectDependencies(project: Project, extension: PaparazziPreviewScannerExtension) {
project.dependencies.apply {
val previewScannerVersion = extension.previewScannerVersion.get()

add(
"testImplementation",
"io.github.sergio-sastre.ComposablePreviewScanner:android:$previewScannerVersion"
)

project.logger.lifecycle(
"Paparazzi Preview Scanner: Auto-injected dependencies " +
"(preview-scanner: $previewScannerVersion)"
)
}
}
}
Loading
Loading