Skip to content

Commit

Permalink
Fix binding diff with prop group (#347)
Browse files Browse the repository at this point in the history
* Fix diff binding with groups

* update tests

* unit tests

* update tests
  • Loading branch information
elihart authored Nov 21, 2017
1 parent 44360e2 commit ba6fd27
Show file tree
Hide file tree
Showing 75 changed files with 717 additions and 673 deletions.
4 changes: 2 additions & 2 deletions .idea/codeStyleSettings.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {

ext.KOTLIN_VERSION = "1.1.51"
ext.KOTLIN_VERSION = "1.1.60"
ext.ANDROID_PLUGIN_VERSION = "3.0.0"

repositories {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import android.util.AttributeSet;
import android.view.View;

import com.airbnb.epoxy.CallbackProp;
import com.airbnb.epoxy.ModelProp;
import com.airbnb.epoxy.ModelProp.Option;
import com.airbnb.epoxy.ModelView;
Expand Down Expand Up @@ -51,4 +52,35 @@ public void setTextWithDefault(CharSequence text) {
public void setNullableTextWithDefault(@Nullable CharSequence text) {
nullableTextWithDefault = text;
}

@CallbackProp
@Override
public void setOnClickListener(@Nullable OnClickListener l) {
super.setOnClickListener(l);
}

@ModelProp
public void setGroupWithNoDefault(String url) {

}

@CallbackProp
public void setGroupWithNoDefault(@Nullable OnClickListener url) {

}

@ModelProp
public void setGroupWithDefault(String url) {

}

@CallbackProp
public void setGroupWithDefault(@Nullable OnClickListener url) {

}

@ModelProp
public void setGroupWithDefault(int url) {

}
}
123 changes: 123 additions & 0 deletions epoxy-integrationtest/src/test/java/com/airbnb/epoxy/BindDiffTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package com.airbnb.epoxy

import android.view.View
import com.airbnb.epoxy.integrationtest.BuildConfig
import com.airbnb.epoxy.integrationtest.ViewWithAnnotationsForIntegrationTest
import com.airbnb.epoxy.integrationtest.ViewWithAnnotationsForIntegrationTestModel_
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config

/** Tests that a partial bind of model (from a diff) binds the correct props. This is particularly tricky with prop groups. */
@RunWith(RobolectricTestRunner::class)
@Config(constants = BuildConfig::class, sdk = intArrayOf(21))
class BindDiffTest {

private inline fun validateDiff(
model1Props: ViewWithAnnotationsForIntegrationTestModel_.() -> Unit,
model2Props: ViewWithAnnotationsForIntegrationTestModel_.() -> Unit,
viewCallVerifications: ViewWithAnnotationsForIntegrationTest.() -> Unit
) {
val model1 = ViewWithAnnotationsForIntegrationTestModel_().id(1).apply(model1Props)
val model2 = ViewWithAnnotationsForIntegrationTestModel_().id(1).apply(model2Props)

val viewMock = mock(ViewWithAnnotationsForIntegrationTest::class.java)
model2.bind(viewMock, model1)

verify(viewMock).viewCallVerifications()
}

@Test
fun singlePropChanged() {
validateDiff(
model1Props = {
requiredText("hello")
groupWithNoDefault("text")
groupWithDefault("text")
},
model2Props = {
requiredText("hello2")
groupWithNoDefault("text")
groupWithDefault("text")
},
viewCallVerifications = {
setRequiredText("hello2")
}
)
}

@Test
fun multiplePropsChanged() {
validateDiff(
model1Props = {
requiredText("hello")
groupWithNoDefault("text")
},
model2Props = {
requiredText("hello2")
groupWithNoDefault("text2")
},
viewCallVerifications = {
setGroupWithNoDefault("text2")
setRequiredText("hello2")
}
)
}

@Test
fun propGroupChangedFromOneAttributeToAnother() {
val clickListener = View.OnClickListener { v -> }
validateDiff(
model1Props = {
requiredText("hello")
groupWithNoDefault("text")
},
model2Props = {
requiredText("hello")
groupWithNoDefault(clickListener)
},
viewCallVerifications = {
setGroupWithNoDefault(clickListener)
}
)
}

@Test
fun propGroupChangedToDefault() {
validateDiff(
model1Props = {
requiredText("hello")
groupWithNoDefault("text")
groupWithDefault("custom value")
},
model2Props = {
requiredText("hello")
groupWithNoDefault("text")
},
viewCallVerifications = {
setGroupWithDefault(null as View.OnClickListener?)
}
)
}

@Test
fun propGroupChangedFromDefault() {
validateDiff(
model1Props = {
requiredText("hello")
groupWithNoDefault("text")
},
model2Props = {
requiredText("hello")
groupWithNoDefault("text")
groupWithDefault("custom value")
},
viewCallVerifications = {
setGroupWithDefault("custom value")
}
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1346,26 +1346,35 @@ internal class GeneratedModelWriter(
useObjectHashCode: Boolean,
type: TypeName,
accessorCode: String
) = with(builder) {
if (useObjectHashCode) {
when {
type === FLOAT -> beginControlFlow("if (Float.compare(that.\$L, \$L) != 0)",
accessorCode, accessorCode)
type === DOUBLE -> beginControlFlow("if (Double.compare(that.\$L, \$L) != 0)",
accessorCode, accessorCode)
type.isPrimitive -> beginControlFlow("if (\$L != that.\$L)",
accessorCode, accessorCode)
type is ArrayTypeName -> beginControlFlow("if (!\$T.equals(\$L, that.\$L))",
TypeName.get(Arrays::class.java),
accessorCode, accessorCode)
else -> beginControlFlow(
"if (\$L != null ? !\$L.equals(that.\$L) : that.\$L != null)",
accessorCode, accessorCode, accessorCode, accessorCode)
}
} else {
beginControlFlow("if ((\$L == null) != (that.\$L == null))", accessorCode,
accessorCode)
) = builder.beginControlFlow("if (\$L)",
notEqualsCodeBlock(useObjectHashCode, type, accessorCode))

fun notEqualsCodeBlock(attribute: AttributeInfo): CodeBlock {
val attributeType = attribute.typeName
val useHash = attributeType.isPrimitive || attribute.useInHash()
return notEqualsCodeBlock(useHash, attributeType, attribute.getterCode())
}

fun notEqualsCodeBlock(
useObjectHashCode: Boolean,
type: TypeName,
accessorCode: String
): CodeBlock = if (useObjectHashCode) {
when {
type === FLOAT -> CodeBlock.of("(Float.compare(that.\$L, \$L) != 0)",
accessorCode, accessorCode)
type === DOUBLE -> CodeBlock.of("(Double.compare(that.\$L, \$L) != 0)",
accessorCode, accessorCode)
type.isPrimitive -> CodeBlock.of("(\$L != that.\$L)", accessorCode, accessorCode)
type is ArrayTypeName -> CodeBlock.of("!\$T.equals(\$L, that.\$L)",
TypeName.get(Arrays::class.java),
accessorCode, accessorCode)
else -> CodeBlock.of(
"(\$L != null ? !\$L.equals(that.\$L) : that.\$L != null)",
accessorCode, accessorCode, accessorCode, accessorCode)
}
} else {
CodeBlock.of("((\$L == null) != (that.\$L == null))", accessorCode, accessorCode)
}

private fun addHashCodeLineForType(
Expand Down
Loading

0 comments on commit ba6fd27

Please sign in to comment.