[go: nahoru, domu]

blob: 552bd275635926e51fc76d8cbdecd5420b9f8a5e [file] [log] [blame]
/*
* Copyright 2020 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.
*/
@file:OptIn(ExperimentalComposeApi::class)
package androidx.compose.runtime
import android.widget.TextView
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import androidx.ui.core.ExperimentalLayoutNodeApi
import androidx.ui.core.LayoutNode
import androidx.ui.core.subcomposeInto
import androidx.ui.viewinterop.emitView
import org.junit.After
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
// Create a normal (dynamic) ambient with a string value
val someTextAmbient = ambientOf { "Default" }
// Create a normal (dynamic) ambient with an int value
val someIntAmbient = ambientOf { 1 }
// Create a non-overridable ambient provider key
private val someOtherIntProvider = ambientOf { 1 }
// Make public the consumer key.
val someOtherIntAmbient: Ambient<Int> =
someOtherIntProvider
// Create a static ambient with an int value
val someStaticInt = staticAmbientOf { 40 }
@MediumTest
@RunWith(AndroidJUnit4::class)
class AmbientTests : BaseComposeTest() {
@Composable
fun Text(value: String, id: Int = 100) {
emitView(::TextView) { it.id = id; it.text = value; }
}
@Composable
fun ReadStringAmbient(ambient: Ambient<String>, id: Int = 100) {
Text(value = ambient.current, id = id)
}
@get:Rule
override val activityRule = makeTestActivityRule()
@Test
fun testAmbientApi() {
compose {
assertEquals("Default", someTextAmbient.current)
assertEquals(1, someIntAmbient.current)
Providers(
someTextAmbient provides "Test1",
someIntAmbient provides 12,
someOtherIntProvider provides 42,
someStaticInt provides 50
) {
assertEquals(
"Test1",
someTextAmbient.current
)
assertEquals(12, someIntAmbient.current)
assertEquals(
42,
someOtherIntAmbient.current
)
assertEquals(50, someStaticInt.current)
Providers(
someTextAmbient provides "Test2",
someStaticInt provides 60
) {
assertEquals(
"Test2",
someTextAmbient.current
)
assertEquals(
12,
someIntAmbient.current
)
assertEquals(
42,
someOtherIntAmbient.current
)
assertEquals(60, someStaticInt.current)
}
assertEquals(
"Test1",
someTextAmbient.current
)
assertEquals(12, someIntAmbient.current)
assertEquals(
42,
someOtherIntAmbient.current
)
assertEquals(50, someStaticInt.current)
}
assertEquals("Default", someTextAmbient.current)
assertEquals(1, someIntAmbient.current)
}.then {
// Force the composition to run
}
}
@Test
fun recompose_Dynamic() {
val tvId = 100
val invalidates = mutableListOf<() -> Unit>()
fun doInvalidate() = invalidates.forEach { it() }.also { invalidates.clear() }
var someText = "Unmodified"
compose {
invalidates.add(invalidate)
Providers(
someTextAmbient provides someText
) {
ReadStringAmbient(
ambient = someTextAmbient,
id = tvId
)
}
}.then { activity ->
assertEquals(someText, activity.findViewById<TextView>(100).text)
someText = "Modified"
doInvalidate()
}.then { activity ->
assertEquals(someText, activity.findViewById<TextView>(100).text)
}
}
@Test
fun recompose_Static() {
val tvId = 100
val invalidates = mutableListOf<() -> Unit>()
fun doInvalidate() = invalidates.forEach { it() }.also { invalidates.clear() }
val staticStringAmbient = staticAmbientOf { "Default" }
var someText = "Unmodified"
compose {
invalidates.add(invalidate)
Providers(
staticStringAmbient provides someText
) {
ReadStringAmbient(
ambient = staticStringAmbient,
id = tvId
)
}
}.then { activity ->
assertEquals(someText, activity.findViewById<TextView>(100).text)
someText = "Modified"
doInvalidate()
}.then { activity ->
assertEquals(someText, activity.findViewById<TextView>(100).text)
}
}
@Test
fun subCompose_Dynamic() {
val tvId = 100
val invalidates = mutableListOf<() -> Unit>()
fun doInvalidate() = invalidates.forEach { it() }.also { invalidates.clear() }
var someText = "Unmodified"
compose {
invalidates.add(invalidate)
Providers(
someTextAmbient provides someText,
someIntAmbient provides 0
) {
ReadStringAmbient(ambient = someTextAmbient, id = tvId)
subCompose {
assertEquals(
someText,
someTextAmbient.current
)
assertEquals(0, someIntAmbient.current)
Providers(
someIntAmbient provides 42
) {
assertEquals(
someText,
someTextAmbient.current
)
assertEquals(
42,
someIntAmbient.current
)
}
}
}
}.then {
assertEquals(someText, it.findViewById<TextView>(tvId).text)
someText = "Modified"
doInvalidate()
}.then {
assertEquals(someText, it.findViewById<TextView>(tvId).text)
}.done()
}
@Test
fun subCompose_Static() {
val tvId = 100
val invalidates = mutableListOf<() -> Unit>()
fun doInvalidate() = invalidates.forEach { it() }.also { invalidates.clear() }
val staticSomeTextAmbient =
staticAmbientOf { "Default" }
val staticSomeIntAmbient = staticAmbientOf { -1 }
var someText = "Unmodified"
compose {
invalidates.add(invalidate)
Providers(
staticSomeTextAmbient provides someText,
staticSomeIntAmbient provides 0
) {
assertEquals(someText, staticSomeTextAmbient.current)
assertEquals(0, staticSomeIntAmbient.current)
ReadStringAmbient(
ambient = staticSomeTextAmbient,
id = tvId
)
subCompose {
assertEquals(someText, staticSomeTextAmbient.current)
assertEquals(0, staticSomeIntAmbient.current)
Providers(
staticSomeIntAmbient provides 42
) {
assertEquals(someText, staticSomeTextAmbient.current)
assertEquals(42, staticSomeIntAmbient.current)
}
}
}
}.then {
assertEquals(someText, it.findViewById<TextView>(tvId).text)
someText = "Modified"
doInvalidate()
}.then {
assertEquals(someText, it.findViewById<TextView>(tvId).text)
}.done()
}
@Test
fun deferredSubCompose_Dynamic() {
val tvId = 100
val invalidates = mutableListOf<() -> Unit>()
fun doInvalidate() = invalidates.forEach { it() }.also { invalidates.clear() }
var someText = "Unmodified"
var doSubCompose: () -> Unit = { error("Sub-compose callback not set") }
compose {
invalidates.add(invalidate)
Providers(
someTextAmbient provides someText,
someIntAmbient provides 0
) {
ReadStringAmbient(
ambient = someTextAmbient,
id = tvId
)
doSubCompose = deferredSubCompose {
assertEquals(
someText,
someTextAmbient.current
)
assertEquals(0, someIntAmbient.current)
Providers(
someIntAmbient provides 42
) {
assertEquals(
someText,
someTextAmbient.current
)
assertEquals(
42,
someIntAmbient.current
)
}
}
}
}.then {
assertEquals(someText, it.findViewById<TextView>(tvId).text)
doSubCompose()
someText = "Modified"
doInvalidate()
}.then {
assertEquals(someText, it.findViewById<TextView>(tvId).text)
doSubCompose()
}.done()
}
@Test
fun deferredSubCompose_Static() {
val tvId = 100
val invalidates = mutableListOf<() -> Unit>()
fun doInvalidate() = invalidates.forEach { it() }.also { invalidates.clear() }
var someText = "Unmodified"
var doSubCompose: () -> Unit = { error("Sub-compose callback not set") }
val staticSomeTextAmbient =
staticAmbientOf { "Default" }
val staticSomeIntAmbient = staticAmbientOf { -1 }
compose {
invalidates.add(invalidate)
Providers(
staticSomeTextAmbient provides someText,
staticSomeIntAmbient provides 0
) {
assertEquals(someText, staticSomeTextAmbient.current)
assertEquals(0, staticSomeIntAmbient.current)
ReadStringAmbient(
ambient = staticSomeTextAmbient,
id = tvId
)
doSubCompose = deferredSubCompose {
assertEquals(someText, staticSomeTextAmbient.current)
assertEquals(0, staticSomeIntAmbient.current)
Providers(
staticSomeIntAmbient provides 42
) {
assertEquals(someText, staticSomeTextAmbient.current)
assertEquals(42, staticSomeIntAmbient.current)
}
}
}
}.then {
assertEquals(someText, it.findViewById<TextView>(tvId).text)
doSubCompose()
someText = "Modified"
doInvalidate()
}.then {
assertEquals(someText, it.findViewById<TextView>(tvId).text)
doSubCompose()
}.done()
}
@Test
fun deferredSubCompose_Nested_Static() {
val tvId = 100
val invalidates = mutableListOf<() -> Unit>()
fun doInvalidate() = invalidates.forEach { it() }.also { invalidates.clear() }
var someText = "Unmodified"
var doSubCompose1: () -> Unit = { error("Sub-compose-1 callback not set") }
var doSubCompose2: () -> Unit = { error("Sub-compose-2 callback not set") }
val staticSomeTextAmbient = staticAmbientOf { "Default" }
val staticSomeIntAmbient = staticAmbientOf { -1 }
compose {
invalidates.add(invalidate)
Providers(
staticSomeTextAmbient provides someText,
staticSomeIntAmbient provides 0
) {
assertEquals(someText, staticSomeTextAmbient.current)
assertEquals(0, staticSomeIntAmbient.current)
ReadStringAmbient(ambient = staticSomeTextAmbient, id = tvId)
doSubCompose1 = deferredSubCompose {
assertEquals(someText, staticSomeTextAmbient.current)
assertEquals(0, staticSomeIntAmbient.current)
doSubCompose2 = deferredSubCompose {
assertEquals(someText, staticSomeTextAmbient.current)
assertEquals(0, staticSomeIntAmbient.current)
}
}
}
}.then {
assertEquals(someText, it.findViewById<TextView>(tvId).text)
doSubCompose1()
}.then {
doSubCompose2()
}.then {
someText = "Modified"
doInvalidate()
}.then {
assertEquals(someText, it.findViewById<TextView>(tvId).text)
doSubCompose1()
}.then {
doSubCompose2()
}.done()
}
@Test
fun insertShouldSeePreviouslyProvidedValues() {
val invalidates = mutableListOf<() -> Unit>()
fun doInvalidate() = invalidates.forEach { it() }.also { invalidates.clear() }
val someStaticString =
staticAmbientOf { "Default" }
var shouldRead = false
compose {
Providers(
someStaticString provides "Provided A"
) {
Observe {
invalidates.add(invalidate)
if (shouldRead)
ReadStringAmbient(someStaticString)
}
}
}.then {
assertEquals(null, it.findViewById<TextView?>(100))
shouldRead = true
doInvalidate()
}.then {
assertEquals("Provided A", it.findViewById<TextView>(100).text)
}
}
@Test
fun providingANewDataClassValueShouldNotRecompose() {
val invalidates = mutableListOf<() -> Unit>()
fun doInvalidate() = invalidates.forEach { it() }.also { invalidates.clear() }
val someDataAmbient = ambientOf(structuralEqualityPolicy()) { SomeData() }
var composed = false
@Composable
fun ReadSomeDataAmbient(ambient: Ambient<SomeData>, id: Int = 100) {
composed = true
Text(value = ambient.current.value, id = id)
}
compose {
Observe {
invalidates.add(invalidate)
Providers(
someDataAmbient provides SomeData("provided")
) {
ReadSomeDataAmbient(someDataAmbient)
}
}
}.then {
assertTrue(composed)
composed = false
doInvalidate()
}.then {
assertFalse(composed)
}
}
@After
fun ensureNoSubcomposePending() {
activityRule.activity.uiThread {
val hasPendingChanges = Recomposer.current().hasPendingChanges()
assertTrue(!hasPendingChanges, "Pending changes detected after test completed")
}
}
class Ref<T : Any> {
lateinit var value: T
}
@Composable fun narrowInvalidateForReference(ref: Ref<CompositionReference>) {
ref.value = compositionReference()
}
@Composable fun deferredSubCompose(block: @Composable () -> Unit): () -> Unit {
@OptIn(ExperimentalLayoutNodeApi::class)
val container = remember { LayoutNode() }
val ref = Ref<CompositionReference>()
narrowInvalidateForReference(ref = ref)
return {
@OptIn(ExperimentalComposeApi::class)
// TODO(b/150390669): Review use of @ComposableContract(tracked = false)
subcomposeInto(
container,
Recomposer.current(),
ref.value
) @ComposableContract(tracked = false) {
block()
}
}
}
}
private data class SomeData(val value: String = "default")