Skip to content

Commit

Permalink
Generate model factory methods
Browse files Browse the repository at this point in the history
  • Loading branch information
ngsilverman committed Jun 15, 2018
1 parent c338f41 commit f101814
Show file tree
Hide file tree
Showing 30 changed files with 2,971 additions and 463 deletions.
1 change: 1 addition & 0 deletions epoxy-modelfactory/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
56 changes: 56 additions & 0 deletions epoxy-modelfactory/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import org.gradle.internal.jvm.Jvm

apply plugin: 'com.android.library'
apply from: 'build.workaround-missing-resource.gradle'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'

android {
compileSdkVersion rootProject.COMPILE_SDK_VERSION

defaultConfig {
minSdkVersion rootProject.MIN_SDK_VERSION
targetSdkVersion rootProject.TARGET_SDK_VERSION
}
}

configurations.all { strategy ->
strategy.resolutionStrategy.force rootProject.deps.junit, rootProject.deps.robolectric,
rootProject.deps.mockito
}

dependencies {
compile project(':epoxy-adapter')
compile rootProject.deps.paris

testCompile project(':epoxy-adapter')
// Need to include the processor directly since we create an instance of it in code for testing
testCompile project(':epoxy-processor')
testCompile project(':epoxy-processortest')
testCompile rootProject.deps.googleTestingCompile
testCompile rootProject.deps.junit
testCompile rootProject.deps.kotlin
// Need to include the processor directly since we create an instance of it in code for testing
testCompile rootProject.deps.parisProcessor
testCompile rootProject.deps.robolectric
testCompile files(getRuntimeJar())
testCompile files(Jvm.current().getToolsJar())

kaptTest project(':epoxy-processor')
}

static def getRuntimeJar() {
try {
final File javaBase = new File(System.getProperty("java.home")).getCanonicalFile()
File runtimeJar = new File(javaBase, "lib/rt.jar")
if (runtimeJar.exists()) {
return runtimeJar
}
runtimeJar = new File(javaBase, "jre/lib/rt.jar")
return runtimeJar.exists() ? runtimeJar : null
} catch (IOException e) {
throw new RuntimeException(e)
}
}

apply from: rootProject.file('gradle/gradle-maven-push.gradle')
23 changes: 23 additions & 0 deletions epoxy-modelfactory/build.workaround-missing-resource.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Source - https://github.com/nenick/AndroidStudioAndRobolectric/blob/master/app/build.workaround-missing-resource.gradle
// Workaround for missing test resources when running unit tests within android studio.
// This copies the test resources next to the test classes for each variant.
// Tracked at https://github.com/nenick/AndroidStudioAndRobolectric/issues/7
// Original solution comes from https://code.google.com/p/android/issues/detail?id=136013#c10
// See also https://code.google.com/p/android/issues/detail?id=64887
gradle.projectsEvaluated {
// Base path which is recognized by android studio.
def testClassesPath = "${buildDir}/intermediates/classes/test/"
// Copy must be done for each variant.
def variants = android.libraryVariants.collect()

variants.each { variant ->
def variationName = variant.name.capitalize()
def variationPath = variant.buildType.name

// Specific copy task for each variant
def copyTestResourcesTask = project.tasks.create("copyTest${variationName}Resources", Copy)
copyTestResourcesTask.from("${projectDir}/src/test/resources")
copyTestResourcesTask.into("${testClassesPath}/${variationPath}")
copyTestResourcesTask.execute()
}
}
3 changes: 3 additions & 0 deletions epoxy-modelfactory/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
POM_NAME=Epoxy Model Factory
POM_ARTIFACT_ID=epoxy-modelfactory
POM_PACKAGING=jar
2 changes: 2 additions & 0 deletions epoxy-modelfactory/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<manifest package="com.airbnb.epoxymodelfactory"
xmlns:android="http://schemas.android.com/apk/res/android" />
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.airbnb.epoxy;

import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.View.OnClickListener;

import com.airbnb.paris.styles.Style;

import java.util.List;

public interface ModelProperties {

boolean has(@NonNull String propertyName);

boolean getBoolean(@NonNull String propertyName);

double getDouble(@NonNull String propertyName);

@NonNull
Drawable getDrawable(@NonNull String propertyName);

int getInt(@NonNull String propertyName);

@NonNull
OnClickListener getOnClickListener(@NonNull String propertyName);

@NonNull
String getString(@NonNull String propertyName);

@NonNull
List<String> getStringList(@NonNull String propertyName);

/**
* @return Null to apply the default style.
*/
@Nullable
Style getStyle();
}
3 changes: 3 additions & 0 deletions epoxy-modelfactory/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<resources>
<string name="app_name">Epoxy Model Factory</string>
</resources>
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package com.airbnb.epoxy

import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.view.View
import com.airbnb.paris.styles.Style
import org.junit.Assert.assertEquals
import org.junit.Test

/**
* Asserts that using from(ModelProperties) to create a model applies the property values correctly
*/
class FromModelPropertiesTest {

@Test
fun getBoolean() {
val model = TestModelPropertiesViewModel_.from(TestModelProperties(booleanValue = true))
assertEquals(true, model.booleanValue())
}

@Test
fun getDouble() {
val model = TestModelPropertiesViewModel_.from(TestModelProperties(doubleValue = 42.0))
assertEquals(42.0, model.doubleValue(), 0.0)
}

@Test
fun getDrawable() {
val drawable = ColorDrawable(Color.GREEN)
val model = TestModelPropertiesViewModel_.from(TestModelProperties(drawable = drawable))
assertEquals(drawable, model.drawable())
}

@Test
fun getInt() {
val model = TestModelPropertiesViewModel_.from(TestModelProperties(intValue = 51))
assertEquals(51, model.intValue())
}

@Test
fun getOnClickListener() {
val clickListener = View.OnClickListener { }
val model = TestModelPropertiesViewModel_.from(TestModelProperties(onClickListener = clickListener))
assertEquals(clickListener, model.onClickListener())
}

@Test
fun getString() {
val model = TestModelPropertiesViewModel_.from(TestModelProperties(stringValue = "ModelFactory"))
assertEquals("ModelFactory", model.stringValue())
}

@Test
fun getStringList() {
val stringList = listOf("Model", "Factory")
val model = TestModelPropertiesViewModel_.from(TestModelProperties(stringList = stringList))
assertEquals(stringList, model.stringList())
}

class TestModelProperties(
private val id: String? = "",
private val booleanValue: Boolean? = null,
private val doubleValue: Double? = null,
private val drawable: Drawable? = null,
private val intValue: Int? = null,
private val onClickListener: View.OnClickListener? = null,
private val stringValue: String? = null,
private val stringList: List<String>? = null,
private val styleValue: Style? = null
) : ModelProperties {

override fun has(propertyName: String): Boolean {
return mapOf(
"id" to id,
"booleanValue" to booleanValue,
"doubleValue" to doubleValue,
"drawable" to drawable,
"intValue" to intValue,
"onClickListener" to onClickListener,
"stringList" to stringList,
"stringValue" to stringValue
)[propertyName] != null
}

override fun getBoolean(propertyName: String) = booleanValue!!

override fun getDouble(propertyName: String) = doubleValue!!

override fun getDrawable(propertyName: String) = drawable!!

override fun getInt(propertyName: String) = intValue!!

override fun getOnClickListener(propertyName: String) = onClickListener!!

override fun getString(propertyName: String): String {
return if (propertyName == "id") id!! else stringValue!!
}

override fun getStringList(propertyName: String) = stringList!!

override fun getStyle() = styleValue
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package com.airbnb.epoxy

import com.airbnb.epoxy.ProcessorTestUtils.assertGeneration
import com.google.testing.compile.JavaFileObjects
import org.junit.Test
import javax.tools.JavaFileObject

class ModelFactoryViewProcessorTest {

@Test
fun baseModel() {
assertGeneration(
"BaseModelView.java",
"BaseModelViewModel_.java"
)
}

@Test
fun callbackPropModel() {
assertGeneration(
"CallbackPropModelView.java",
"CallbackPropModelViewModel_.java"
)
}

@Test
fun textPropModel() {
assertGeneration(
"TextPropModelView.java",
"TextPropModelViewModel_.java"
)
}

@Test
fun allTypesModel() {
assertGeneration(
"AllTypesModelView.java",
"AllTypesModelViewModel_.java"
)
}

@Test
fun styleableModel() {
// If the view is styleable then the generated "from" method supports setting a style

val configClass: JavaFileObject = JavaFileObjects
.forSourceLines("com.airbnb.epoxy.package-info",
"@ParisConfig(rClass = R.class)\n"
+ "package com.airbnb.epoxy;\n"
+ "\n"
+ "import com.airbnb.paris.annotations.ParisConfig;\n"
+ "import com.airbnb.epoxymodelfactory.R;\n")

assertGeneration(
"StyleableModelView.java",
"StyleableModelViewModel_.java",
useParis = true,
helperObjects = listOf(configClass)
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.airbnb.epoxy

import com.airbnb.paris.processor.ParisProcessor
import com.google.common.truth.Truth.assert_
import com.google.testing.compile.JavaFileObjects
import com.google.testing.compile.JavaSourcesSubjectFactory.javaSources
import javax.annotation.processing.Processor
import javax.tools.JavaFileObject

object ProcessorTestUtils {
@JvmOverloads
@JvmStatic
fun assertGeneration(
inputFile: String,
generatedFile: String,
useParis: Boolean = false,
helperObjects: List<JavaFileObject> = emptyList()
) {
val model = JavaFileObjects
.forResource(inputFile)

val generatedModel = JavaFileObjects.forResource(generatedFile)

val processors = mutableListOf<Processor>().apply {
add(EpoxyProcessor())
if (useParis) add(ParisProcessor())
}

assert_().about(javaSources())
.that(helperObjects + listOf(model))
.processedWith(processors)
.compilesWithoutError()
.and()
.generatesSources(generatedModel)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.airbnb.epoxy;

import android.content.Context;
import android.graphics.drawable.Drawable;
import android.support.annotation.Nullable;
import android.widget.FrameLayout;

import com.airbnb.epoxy.ModelProp.Option;
import com.airbnb.epoxy.ModelView.Size;

import java.util.List;

@ModelView(autoLayout = Size.MATCH_WIDTH_WRAP_HEIGHT)
public class TestModelPropertiesView extends FrameLayout {

public TestModelPropertiesView(Context context) {
super(context);
}

@ModelProp
public void setBooleanValue(boolean value) {

}

@ModelProp
public void setDoubleValue(double value) {

}

@ModelProp({ Option.DoNotHash })
public void setDrawable(Drawable value) {

}

@ModelProp
public void setIntValue(int value) {

}

@CallbackProp
public void setOnClickListener(@Nullable OnClickListener value) {

}

@ModelProp
public void setStringValue(CharSequence value) {

}

@ModelProp
public void setStringList(List<String> value) {

}
}
Loading

0 comments on commit f101814

Please sign in to comment.