Skip to content

Commit

Permalink
Implemented Showkase + Paparazzi artifact to automate screenshot test…
Browse files Browse the repository at this point in the history
…ing (#294)

* Stash changes

* Working version

* Expose an artifact that auto generates Paparazzi test class using Showkase

* Generate snapshots with default setup

* Revert change in gradle.properties

* Add validations for PaparazziShowkaseScreenshotTest

* Fix detekt issues

* Fix refactor error

* Delay check of Paparazzi classes in validation for backward compatibility

* Fix detekt issue

* Fix lint error

* Fix more lint errors

* Attempt to fix duplicate class issue on build

* Use correct version of paparazzi

* Fix error

* Add a compileOnly dependency to paparazzi

* Added tests and documentation

* Update paparazzi screenshots after name change
  • Loading branch information
vinaygaba authored Jan 24, 2023
1 parent c7d5110 commit 9832c54
Show file tree
Hide file tree
Showing 245 changed files with 1,551 additions and 52 deletions.
6 changes: 4 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ buildscript {
'detekt' : '1.7.4',
'espresso' : '3.2.0',
'gradle' : '7.2.1',
'junit' : '4.13',
'junit' : '4.13.2',
'junitImplementation' : '1.1.2',
'kotlin' : '1.7.10',
'kotlinCompilerVersion' : '1.7.0',
Expand All @@ -23,6 +23,7 @@ buildscript {
'ksp' : "$KSP_VERSION",
'ktx' : '1.1.0',
'lifecycle' : '2.2.0',
'paparazzi' : '1.1.0',
'picasso' : '2.8',
'appcompat' : '1.4.0',
'testRunner' : '1.4.0',
Expand Down Expand Up @@ -77,7 +78,8 @@ buildscript {
'androidxTestRunner' : "androidx.test:runner:${versions.testRunner}",
'strikt' : "io.strikt:strikt-core:${versions.strikt}",
'shotAndroid' : "com.karumi:shot-android:${versions.shot}",
'testParameterInjector': "com.google.testparameterinjector:test-parameter-injector:${versions.testParameterInjector}"
'testParameterInjector': "com.google.testparameterinjector:test-parameter-injector:${versions.testParameterInjector}",
'paparazzi' : "app.cash.paparazzi:paparazzi:${versions.paparazzi}"
],
'material' : [
'material' : "com.google.android.material:material:${versions.material}",
Expand Down
1 change: 1 addition & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ include ':showkase-browser-testing'
include ':showkase-browser-testing-submodule'
include ':showkase-screenshot-testing-shot'
include ':showkase-screenshot-testing-paparazzi-sample'
include ':showkase-screenshot-testing-paparazzi'
2 changes: 2 additions & 0 deletions showkase-processor-testing/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ dependencies {
testImplementation deps.test.junit
testImplementation deps.kotlinCompileTesting
testImplementation deps.kotlinCompileTestingKsp
testImplementation project(':showkase-screenshot-testing-paparazzi')
testImplementation deps.test.paparazzi
}

// Needed for Java17 otherwise these tests failed to run locally.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import java.io.File
* Temporarily set this to true to have the test runner update test resource file expected outputs
* instead of failing tests on mismatch. Use this to easily update expected outputs.
*/
const val UPDATE_TEST_OUTPUTS = false
const val UPDATE_TEST_OUTPUTS = true

abstract class BaseProcessorTest {
@Rule
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ class ShowkaseProcessorTest : BaseProcessorTest() {
@Test
fun `open class with no interface but ShowkaseScreenshoTest annotation throws compilation error`() {
assertCompilationFails(
"Only an implementation of com.airbnb.android.showkase.screenshot.testing.ShowkaseScreenshotTest can be annotated with @ShowkaseScreenshot"
"Only an implementation of com.airbnb.android.showkase.screenshot.testing.ShowkaseScreenshotTest or com.airbnb.android.showkase.screenshot.testing.paparazzi.PaparazziShowkaseScreenshotTest can be annotated with @ShowkaseScreenshot"
)
}

Expand All @@ -211,6 +211,21 @@ class ShowkaseProcessorTest : BaseProcessorTest() {
assertCompilationFails("Class annotated with ShowkaseScreenshot needs to be an abstract/open class")
}

@Test
fun `closed class with PaparazziShowkaseScreenshotTest and ShowkaseScreensho annotation throws compilation error`() {
assertCompilationFails("Class annotated with ShowkaseScreenshot needs to be an abstract/open class")
}

@Test
fun `class implementing PaparazziShowkaseScreenshotTest but not companion object throws compilation error`() {
assertCompilationFails("Classes implementing the com.airbnb.android.showkase.screenshot.testing.paparazzi.PaparazziShowkaseScreenshotTest interface should have a companion object that implements the com.airbnb.android.showkase.screenshot.testing.paparazzi.PaparazziShowkaseScreenshotTest.CompanionObject interface")
}

@Test
fun `class implementing PaparazziShowkaseScreenshotTest and companion object implementing different interface throws compilation error`() {
assertCompilationFails("Classes implementing the com.airbnb.android.showkase.screenshot.testing.paparazzi.PaparazziShowkaseScreenshotTest interface should have a companion object that implements the com.airbnb.android.showkase.screenshot.testing.paparazzi.PaparazziShowkaseScreenshotTest.CompanionObject interface")
}

@Test
fun `top level composable function with showkase annotation generates only metadata file`() {
compileInputsAndVerifyOutputs()
Expand Down Expand Up @@ -508,11 +523,21 @@ class ShowkaseProcessorTest : BaseProcessorTest() {
compileInputsAndVerifyOutputs()
}

@Test
fun `top level composable and class with @ScreenshotTest generates Paparazzi screenshot test for composable`() {
compileInputsAndVerifyOutputs()
}

@Test
fun `top level color and class with @ScreenshotTest generates screenshot test for composable`() {
compileInputsAndVerifyOutputs()
}

@Test
fun `top level color and class with @ScreenshotTest generates paparazzi screenshot test for composable`() {
compileInputsAndVerifyOutputs()
}

@Test
fun `top level textstyle and class with @ScreenshotTest generates screenshot test for composable`() {
compileInputsAndVerifyOutputs()
Expand All @@ -528,6 +553,11 @@ class ShowkaseProcessorTest : BaseProcessorTest() {
compileInputsAndVerifyOutputs()
}

@Test
fun `class with @ScreenshotTest generates paparazzi screenshot test for all UI elements`() {
compileInputsAndVerifyOutputs()
}

@Test
fun `composable function with multiple preview functions compiles`() {
compileInputsAndVerifyOutputs()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

import android.graphics.Bitmap
import com.airbnb.android.showkase.annotation.ShowkaseScreenshot
import com.airbnb.android.showkase.screenshot.testing.paparazzi.PaparazziShowkaseScreenshotTest

@ShowkaseScreenshot(rootShowkaseClass = TestShowkaseRoot::class)
public abstract class MyScreenshotTest: PaparazziShowkaseScreenshotTest {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

import com.airbnb.android.showkase.annotation.ShowkaseRoot
import com.airbnb.android.showkase.annotation.ShowkaseRootModule

@ShowkaseRoot
public class TestShowkaseRoot: ShowkaseRootModule {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@

import androidx.compose.runtime.Composable
import com.airbnb.android.showkase.annotation.ShowkaseColor
import com.airbnb.android.showkase.annotation.ShowkaseComposable
import com.airbnb.android.showkase.annotation.ShowkaseTypography
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily

@ShowkaseComposable(name = "name1", group = "group1")
@Composable
public fun TestComposable1() {

}

@ShowkaseComposable(name = "name2", group = "group2")
@Composable
public fun TestComposable2() {

}

@ShowkaseColor("name", "color")
public val red: Color = Color(0xffff0000)

@ShowkaseTypography("name", "typography")
public val title: TextStyle = TextStyle(
fontFamily = FontFamily.Cursive
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import android.graphics.Bitmap
import com.airbnb.android.showkase.annotation.ShowkaseScreenshot
import com.airbnb.android.showkase.screenshot.testing.paparazzi.PaparazziShowkaseScreenshotTest

@ShowkaseScreenshot(rootShowkaseClass = TestShowkaseRoot::class)
public abstract class MyScreenshotTest: PaparazziShowkaseScreenshotTest {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import com.airbnb.android.showkase.annotation.ShowkaseComposable
import androidx.compose.runtime.Composable
import com.airbnb.android.showkase.annotation.ShowkaseRoot
import com.airbnb.android.showkase.annotation.ShowkaseRootModule

@ShowkaseRoot
public class TestShowkaseRoot: ShowkaseRootModule {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import androidx.compose.runtime.Composable
import com.airbnb.android.showkase.annotation.ShowkaseColor
import com.airbnb.android.showkase.annotation.ShowkaseComposable
import com.airbnb.android.showkase.annotation.ShowkaseTypography
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily

@ShowkaseComposable(name = "name1", group = "group1")
@Composable
public fun TestComposable1() {

}

@ShowkaseComposable(name = "name2", group = "group2")
@Composable
public fun TestComposable2() {

}

@ShowkaseColor("name", "color")
public val red: Color = Color(0xffff0000)

@ShowkaseTypography("name", "typography")
public val title: TextStyle = TextStyle(
fontFamily = FontFamily.Cursive
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@

import android.graphics.Bitmap
import com.airbnb.android.showkase.annotation.ShowkaseScreenshot
import com.airbnb.android.showkase.screenshot.testing.ShowkaseScreenshotTest
import com.airbnb.android.showkase.screenshot.testing.ShowkaseScreenshotType
import com.airbnb.android.showkase.screenshot.testing.paparazzi.PaparazziShowkaseScreenshotTest

@ShowkaseScreenshot(rootShowkaseClass = TestShowkaseRoot::class)
public abstract class MyScreenshotTest: PaparazziShowkaseScreenshotTest {
public companion object: PaparazziShowkaseScreenshotTest.CompanionObject
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

import com.airbnb.android.showkase.annotation.ShowkaseComposable
import androidx.compose.runtime.Composable
import com.airbnb.android.showkase.annotation.ShowkaseRoot
import com.airbnb.android.showkase.annotation.ShowkaseRootModule

@ShowkaseRoot
public class TestShowkaseRoot: ShowkaseRootModule {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@

import androidx.compose.runtime.Composable
import com.airbnb.android.showkase.annotation.ShowkaseColor
import com.airbnb.android.showkase.annotation.ShowkaseComposable
import com.airbnb.android.showkase.annotation.ShowkaseTypography
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily

@ShowkaseComposable(name = "name1", group = "group1")
@Composable
public fun TestComposable1() {

}

@ShowkaseComposable(name = "name2", group = "group2")
@Composable
public fun TestComposable2() {

}

@ShowkaseColor("name", "color")
public val red: Color = Color(0xffff0000)

@ShowkaseTypography("name", "typography")
public val title: TextStyle = TextStyle(
fontFamily = FontFamily.Cursive
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// This is an auto-generated file. Please do not edit/modify this file.
import androidx.compose.ui.unit.LayoutDirection
import app.cash.paparazzi.Paparazzi
import com.airbnb.android.showkase.models.Showkase
import com.airbnb.android.showkase.screenshot.testing.paparazzi.ColorPaparazziShowkaseTestPreview
import com.airbnb.android.showkase.screenshot.testing.paparazzi.ComponentPaparazziShowkaseTestPreview
import com.airbnb.android.showkase.screenshot.testing.paparazzi.PaparazziShowkaseDeviceConfig
import com.airbnb.android.showkase.screenshot.testing.paparazzi.PaparazziShowkaseTestPreview
import com.airbnb.android.showkase.screenshot.testing.paparazzi.PaparazziShowkaseUIMode
import com.airbnb.android.showkase.screenshot.testing.paparazzi.TypographyPaparazziShowkaseTestPreview
import com.google.testing.junit.testparameterinjector.TestParameter
import com.google.testing.junit.testparameterinjector.TestParameter.TestParameterValuesProvider
import com.google.testing.junit.testparameterinjector.TestParameterInjector
import getMetadata
import kotlin.Unit
import kotlin.collections.List
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(TestParameterInjector::class)
public class MyScreenshotTest_PaparazziShowkaseTest : MyScreenshotTest() {
@get:Rule
public val paparazzi: Paparazzi = providePaparazzi()

@Test
public fun test_previews(
@TestParameter(valuesProvider = PaparazziShowkasePreviewProvider::class)
elementPreview: PaparazziShowkaseTestPreview,
@TestParameter(valuesProvider = PaparazziShowkaseDeviceConfigProvider::class)
config: PaparazziShowkaseDeviceConfig,
@TestParameter(valuesProvider = PaparazziShowkaseLayoutDirectionProvider::class)
direction: LayoutDirection,
@TestParameter(valuesProvider = PaparazziShowkaseUIModeProvider::class)
uiMode: PaparazziShowkaseUIMode,
): Unit {
paparazzi.unsafeUpdateConfig(config.deviceConfig.copy(softButtons = false))
takePaparazziSnapshot(paparazzi, elementPreview, direction, uiMode)
}

private object PaparazziShowkasePreviewProvider : TestParameter.TestParameterValuesProvider {
public override fun provideValues(): List<PaparazziShowkaseTestPreview> {
val metadata = Showkase.getMetadata()
val components = metadata.componentList.map(::ComponentPaparazziShowkaseTestPreview)
val colors = metadata.colorList.map(::ColorPaparazziShowkaseTestPreview)
val typography = metadata.typographyList.map(::TypographyPaparazziShowkaseTestPreview)
return components + colors + typography
}
}

private object PaparazziShowkaseDeviceConfigProvider : TestParameter.TestParameterValuesProvider {
public override fun provideValues(): List<PaparazziShowkaseDeviceConfig> = deviceConfigs()
}

private object PaparazziShowkaseLayoutDirectionProvider :
TestParameter.TestParameterValuesProvider {
public override fun provideValues(): List<LayoutDirection> = layoutDirections()
}

private object PaparazziShowkaseUIModeProvider : TestParameter.TestParameterValuesProvider {
public override fun provideValues(): List<PaparazziShowkaseUIMode> = uiModes()
}
}
Loading

0 comments on commit 9832c54

Please sign in to comment.