[go: nahoru, domu]

blob: 8e2468b668c9b369628108d0502dfe3b568acfb5 [file] [log] [blame]
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.compose.plugins.kotlin
import android.widget.TextView
import androidx.compose.FrameManager
import androidx.compose.Composer
import androidx.compose.currentComposerNonNull
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.Robolectric
import org.robolectric.RuntimeEnvironment
import org.robolectric.annotation.Config
val PRESIDENT_NAME_1 = "George Washington"
val PRESIDENT_AGE_1 = 57
val PRESIDENT_NAME_16 = "Abraham Lincoln"
val PRESIDENT_AGE_16 = 52
@RunWith(ComposeRobolectricTestRunner::class)
@Config(
manifest = Config.NONE,
minSdk = 23,
maxSdk = 23
)
class KtxModelCodeGenTests : AbstractCodegenTest() {
@Before
fun before() {
val scheduler = RuntimeEnvironment.getMasterScheduler()
scheduler.pause()
}
override fun setUp() {
isSetup = true
super.setUp()
}
private var isSetup = false
private inline fun <T> ensureSetup(crossinline block: () -> T): T {
if (!isSetup) setUp()
return FrameManager.isolated { block() }
}
@Test
@Ignore("TODO(b/138720405): Investigate synchronisation issues in tests")
fun testCGModelView_PersonModel(): Unit = ensureSetup {
val tvNameId = 384
val tvAgeId = 385
var name = PRESIDENT_NAME_1
var age = PRESIDENT_AGE_1
compose(
"""
@Model
class Person4(var name: String, var age: Int)
@Composable
fun PersonView4(person: Person4) {
Observe {
TextView(text=person.name, id=$tvNameId)
TextView(text=person.age.toString(), id=$tvAgeId)
}
}
val president = Person4("$PRESIDENT_NAME_1", $PRESIDENT_AGE_1)
""", { mapOf("name" to name, "age" to age) }, """
president.name = name
president.age = age
""", """
PersonView4(person=president)
""").then { activity ->
val tvName = activity.findViewById(tvNameId) as TextView
val tvAge = activity.findViewById(tvAgeId) as TextView
assertEquals(PRESIDENT_NAME_1, tvName.text)
assertEquals(PRESIDENT_AGE_1.toString(), tvAge.text)
name = PRESIDENT_NAME_16
age = PRESIDENT_AGE_16
}.then { activity ->
val tvName = activity.findViewById(tvNameId) as TextView
val tvAge = activity.findViewById(tvAgeId) as TextView
assertEquals(PRESIDENT_NAME_16, tvName.text)
assertEquals(PRESIDENT_AGE_16.toString(), tvAge.text)
}
}
@Test // b/120843442
fun testCGModelView_ObjectModel(): Unit = ensureSetup {
val tvNameId = 384
val tvAgeId = 385
var name = PRESIDENT_NAME_1
var age = PRESIDENT_AGE_1
compose(
"""
@Model
object president {
var name: String = "$PRESIDENT_NAME_1"
var age: Int = $PRESIDENT_AGE_1
}
@Composable
fun PresidentView() {
Observe {
TextView(text=president.name, id=$tvNameId)
TextView(text=president.age.toString(), id=$tvAgeId)
}
}
""", { mapOf("name" to name, "age" to age) }, """
president.name = name
president.age = age
""", """
PresidentView()
""").then { activity ->
val tvName = activity.findViewById(tvNameId) as TextView
val tvAge = activity.findViewById(tvAgeId) as TextView
assertEquals(PRESIDENT_NAME_1, tvName.text)
assertEquals(PRESIDENT_AGE_1.toString(), tvAge.text)
name = PRESIDENT_NAME_16
age = PRESIDENT_AGE_16
}.then { activity ->
val tvName = activity.findViewById(tvNameId) as TextView
val tvAge = activity.findViewById(tvAgeId) as TextView
assertEquals(PRESIDENT_NAME_16, tvName.text)
assertEquals(PRESIDENT_AGE_16.toString(), tvAge.text)
}
}
@Test // b/120836313
fun testCGModelView_DataModel(): Unit = ensureSetup {
val tvNameId = 384
val tvAgeId = 385
var name = PRESIDENT_NAME_1
var age = PRESIDENT_AGE_1
compose(
"""
@Model
data class PersonB(var name: String, var age: Int)
@Composable
fun PersonViewB(person: PersonB) {
Observe {
TextView(text=person.name, id=$tvNameId)
TextView(text=person.age.toString(), id=$tvAgeId)
}
}
val president = PersonB("$PRESIDENT_NAME_1", $PRESIDENT_AGE_1)
""", { mapOf("name" to name, "age" to age) }, """
president.name = name
president.age = age
""", """
PersonViewB(person=president)
""").then { activity ->
val tvName = activity.findViewById(tvNameId) as TextView
val tvAge = activity.findViewById(tvAgeId) as TextView
assertEquals(PRESIDENT_NAME_1, tvName.text)
assertEquals(PRESIDENT_AGE_1.toString(), tvAge.text)
name = PRESIDENT_NAME_16
age = PRESIDENT_AGE_16
}.then { activity ->
val tvName = activity.findViewById(tvNameId) as TextView
val tvAge = activity.findViewById(tvAgeId) as TextView
assertEquals(PRESIDENT_NAME_16, tvName.text)
assertEquals(PRESIDENT_AGE_16.toString(), tvAge.text)
}
}
@Test // b/120843442
fun testCGModelView_ZeroFrame(): Unit = ensureSetup {
val tvNameId = 384
val tvAgeId = 385
var name = PRESIDENT_NAME_1
var age = PRESIDENT_AGE_1
compose(
"""
@Model
class PersonC(var name: String, var age: Int)
@Composable
fun PersonViewC(person: PersonC) {
Observe {
TextView(text=person.name, id=$tvNameId)
TextView(text=person.age.toString(), id=$tvAgeId)
}
}
val president = FrameManager.unframed { PersonC("$PRESIDENT_NAME_1", $PRESIDENT_AGE_1) }
""", { mapOf("name" to name, "age" to age) }, """
president.name = name
president.age = age
""", """
PersonViewC(person=president)
""").then { activity ->
val tvName = activity.findViewById(tvNameId) as TextView
val tvAge = activity.findViewById(tvAgeId) as TextView
assertEquals(name, tvName.text)
assertEquals(age.toString(), tvAge.text)
name = PRESIDENT_NAME_16
age = PRESIDENT_AGE_16
}.then { activity ->
val tvName = activity.findViewById(tvNameId) as TextView
val tvAge = activity.findViewById(tvAgeId) as TextView
assertEquals(PRESIDENT_NAME_16, tvName.text)
assertEquals(PRESIDENT_AGE_16.toString(), tvAge.text)
}
}
@Test // b/120843442
fun testCGModelView_ZeroFrame_Modification(): Unit = ensureSetup {
val tvNameId = 384
val tvAgeId = 385
var name = PRESIDENT_NAME_1
var age = PRESIDENT_AGE_1
compose(
"""
@Model
class PersonD(var name: String, var age: Int)
@Composable
fun PersonViewD(person: PersonD) {
Observe {
TextView(text=person.name, id=$tvNameId)
TextView(text=person.age.toString(), id=$tvAgeId)
}
}
val president = FrameManager.framed { PersonD("$PRESIDENT_NAME_1", $PRESIDENT_AGE_1).apply { age = $PRESIDENT_AGE_1 } }
""", { mapOf("name" to name, "age" to age) }, """
president.name = name
president.age = age
""", """
PersonViewD(person=president)
""").then { activity ->
val tvName = activity.findViewById(tvNameId) as TextView
val tvAge = activity.findViewById(tvAgeId) as TextView
assertEquals(PRESIDENT_NAME_1, tvName.text)
assertEquals(PRESIDENT_AGE_1.toString(), tvAge.text)
name = PRESIDENT_NAME_16
age = PRESIDENT_AGE_16
}.then { activity ->
val tvName = activity.findViewById(tvNameId) as TextView
val tvAge = activity.findViewById(tvAgeId) as TextView
assertEquals(PRESIDENT_NAME_16, tvName.text)
assertEquals(PRESIDENT_AGE_16.toString(), tvAge.text)
}
}
fun compose(
prefix: String,
valuesFactory: () -> Map<String, Any>,
advance: String,
composition: String,
dumpClasses: Boolean = false
): RobolectricComposeTester {
val className = "Test_${uniqueNumber++}"
val fileName = "$className.kt"
val candidateValues = valuesFactory()
@Suppress("NO_REFLECTION_IN_CLASS_PATH")
val parameterList = candidateValues.map {
"${it.key}: ${it.value::class.qualifiedName}"
}.joinToString()
val parameterTypes = candidateValues.map {
it.value::class.javaPrimitiveType ?: it.value::class.javaObjectType
}.toTypedArray()
val compiledClasses = classLoader("""
import android.content.Context
import android.widget.*
import androidx.compose.*
$prefix
class $className {
@Composable
fun compose() {
$composition
}
fun advance($parameterList) {
$advance
}
}
""", fileName, dumpClasses)
val allClassFiles = compiledClasses.allGeneratedFiles.filter {
it.relativePath.endsWith(".class")
}
val instanceClass = run {
var instanceClass: Class<*>? = null
var loadedOne = false
for (outFile in allClassFiles) {
val bytes = outFile.asByteArray()
val loadedClass = loadClass(
this.javaClass.classLoader!!,
null,
bytes
)
if (loadedClass.name == className) instanceClass = loadedClass
loadedOne = true
}
if (!loadedOne) error("No classes loaded")
instanceClass ?: error("Could not find class $className in loaded classes")
}
val instanceOfClass = instanceClass.newInstance()
val advanceMethod = instanceClass.getMethod("advance", *parameterTypes)
val composeMethod = if (ComposeFlags.COMPOSER_PARAM)
instanceClass.getMethod("compose", Composer::class.java)
else
instanceClass.getMethod("compose")
return composeMulti({
if (ComposeFlags.COMPOSER_PARAM) {
composeMethod.invoke(instanceOfClass, currentComposerNonNull)
} else {
composeMethod.invoke(instanceOfClass)
}
}) {
val values = valuesFactory()
val arguments = values.map { it.value }.toTypedArray()
advanceMethod.invoke(instanceOfClass, *arguments)
}
}
}