[go: nahoru, domu]

blob: 9fab12a0754d55dc2e71176f692eac1a38a916a2 [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 org.junit.After
import org.junit.Ignore
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) CompositionLocal with a string value
val someTextCompositionLocal = compositionLocalOf { "Default" }
// Create a normal (dynamic) CompositionLocal with an int value
val someIntCompositionLocal = compositionLocalOf { 1 }
// Create a non-overridable CompositionLocal provider key
private val someOtherIntProvider = compositionLocalOf { 1 }
// Make public the consumer key.
val someOtherIntCompositionLocal: CompositionLocal<Int> = someOtherIntProvider
// Create a static CompositionLocal with an int value
val someStaticInt = staticCompositionLocalOf { 40 }
@MediumTest
@RunWith(AndroidJUnit4::class)
class CompositionLocalTests : BaseComposeTest() {
@Composable
@Suppress("Deprecation", "UNUSED_PARAMETER")
fun Text(value: String, id: Int = 100) {}
@Composable
fun ReadStringCompositionLocal(compositionLocal: CompositionLocal<String>, id: Int = 100) {
Text(value = compositionLocal.current, id = id)
}
@get:Rule
override val activityRule = makeTestActivityRule()
@Test
fun testCompositionLocalApi() {
compose {
assertEquals("Default", someTextCompositionLocal.current)
assertEquals(1, someIntCompositionLocal.current)
CompositionLocalProvider(
someTextCompositionLocal provides "Test1",
someIntCompositionLocal provides 12,
someOtherIntProvider provides 42,
someStaticInt provides 50
) {
assertEquals(
"Test1",
someTextCompositionLocal.current
)
assertEquals(12, someIntCompositionLocal.current)
assertEquals(
42,
someOtherIntCompositionLocal.current
)
assertEquals(50, someStaticInt.current)
CompositionLocalProvider(
someTextCompositionLocal provides "Test2",
someStaticInt provides 60
) {
assertEquals(
"Test2",
someTextCompositionLocal.current
)
assertEquals(
12,
someIntCompositionLocal.current
)
assertEquals(
42,
someOtherIntCompositionLocal.current
)
assertEquals(60, someStaticInt.current)
}
assertEquals(
"Test1",
someTextCompositionLocal.current
)
assertEquals(12, someIntCompositionLocal.current)
assertEquals(
42,
someOtherIntCompositionLocal.current
)
assertEquals(50, someStaticInt.current)
}
assertEquals("Default", someTextCompositionLocal.current)
assertEquals(1, someIntCompositionLocal.current)
}.then {
// Force the composition to run
}
}
@Test
@Ignore("b/179279455")
fun recompose_Dynamic() {
val tvId = 100
val invalidates = mutableListOf<RecomposeScope>()
fun doInvalidate() = invalidates.forEach { it.invalidate() }.also { invalidates.clear() }
var someText = "Unmodified"
compose {
invalidates.add(currentRecomposeScope)
CompositionLocalProvider(
someTextCompositionLocal provides someText
) {
ReadStringCompositionLocal(
compositionLocal = someTextCompositionLocal,
id = tvId
)
}
}.then { activity ->
assertEquals(someText, activity.findViewById<TextView>(100).text)
someText = "Modified"
doInvalidate()
}.then { activity ->
assertEquals(someText, activity.findViewById<TextView>(100).text)
}
}
@Test
@Ignore("b/179279455")
fun recompose_Static() {
val tvId = 100
val invalidates = mutableListOf<RecomposeScope>()
fun doInvalidate() = invalidates.forEach { it.invalidate() }.also { invalidates.clear() }
val staticStringCompositionLocal = staticCompositionLocalOf { "Default" }
var someText = "Unmodified"
compose {
invalidates.add(currentRecomposeScope)
CompositionLocalProvider(
staticStringCompositionLocal provides someText
) {
ReadStringCompositionLocal(
compositionLocal = staticStringCompositionLocal,
id = tvId
)
}
}.then { activity ->
assertEquals(someText, activity.findViewById<TextView>(100).text)
someText = "Modified"
doInvalidate()
}.then { activity ->
assertEquals(someText, activity.findViewById<TextView>(100).text)
}
}
@Test
@Ignore("b/179279455")
fun subCompose_Dynamic() {
val tvId = 100
val invalidates = mutableListOf<RecomposeScope>()
fun doInvalidate() = invalidates.forEach { it.invalidate() }.also { invalidates.clear() }
var someText = "Unmodified"
compose {
invalidates.add(currentRecomposeScope)
CompositionLocalProvider(
someTextCompositionLocal provides someText,
someIntCompositionLocal provides 0
) {
ReadStringCompositionLocal(compositionLocal = someTextCompositionLocal, id = tvId)
subCompose {
assertEquals(
someText,
someTextCompositionLocal.current
)
assertEquals(0, someIntCompositionLocal.current)
CompositionLocalProvider(
someIntCompositionLocal provides 42
) {
assertEquals(
someText,
someTextCompositionLocal.current
)
assertEquals(
42,
someIntCompositionLocal.current
)
}
}
}
}.then {
assertEquals(someText, it.findViewById<TextView>(tvId).text)
someText = "Modified"
doInvalidate()
}.then {
assertEquals(someText, it.findViewById<TextView>(tvId).text)
}.done()
}
@Test
@Ignore("b/179279455")
fun subCompose_Static() {
val tvId = 100
val invalidates = mutableListOf<RecomposeScope>()
fun doInvalidate() = invalidates.forEach { it.invalidate() }.also { invalidates.clear() }
val staticSomeTextCompositionLocal = staticCompositionLocalOf { "Default" }
val staticSomeIntCompositionLocal = staticCompositionLocalOf { -1 }
var someText = "Unmodified"
compose {
invalidates.add(currentRecomposeScope)
CompositionLocalProvider(
staticSomeTextCompositionLocal provides someText,
staticSomeIntCompositionLocal provides 0
) {
assertEquals(someText, staticSomeTextCompositionLocal.current)
assertEquals(0, staticSomeIntCompositionLocal.current)
ReadStringCompositionLocal(
compositionLocal = staticSomeTextCompositionLocal,
id = tvId
)
subCompose {
assertEquals(someText, staticSomeTextCompositionLocal.current)
assertEquals(0, staticSomeIntCompositionLocal.current)
CompositionLocalProvider(
staticSomeIntCompositionLocal provides 42
) {
assertEquals(someText, staticSomeTextCompositionLocal.current)
assertEquals(42, staticSomeIntCompositionLocal.current)
}
}
}
}.then {
assertEquals(someText, it.findViewById<TextView>(tvId).text)
someText = "Modified"
doInvalidate()
}.then {
assertEquals(someText, it.findViewById<TextView>(tvId).text)
}.done()
}
@Test
@Ignore("b/179279455")
fun deferredSubCompose_Dynamic() {
val tvId = 100
val invalidates = mutableListOf<RecomposeScope>()
fun doInvalidate() = invalidates.forEach { it.invalidate() }.also { invalidates.clear() }
var someText = "Unmodified"
var doSubCompose: () -> Unit = { error("Sub-compose callback not set") }
compose {
invalidates.add(currentRecomposeScope)
CompositionLocalProvider(
someTextCompositionLocal provides someText,
someIntCompositionLocal provides 0
) {
ReadStringCompositionLocal(
compositionLocal = someTextCompositionLocal,
id = tvId
)
doSubCompose = deferredSubCompose {
assertEquals(
someText,
someTextCompositionLocal.current
)
assertEquals(0, someIntCompositionLocal.current)
CompositionLocalProvider(
someIntCompositionLocal provides 42
) {
assertEquals(
someText,
someTextCompositionLocal.current
)
assertEquals(
42,
someIntCompositionLocal.current
)
}
}
}
}.then {
assertEquals(someText, it.findViewById<TextView>(tvId).text)
doSubCompose()
someText = "Modified"
doInvalidate()
}.then {
assertEquals(someText, it.findViewById<TextView>(tvId).text)
doSubCompose()
}.done()
}
@Test
@Ignore("b/179279455")
fun deferredSubCompose_Static() {
val tvId = 100
val invalidates = mutableListOf<RecomposeScope>()
fun doInvalidate() = invalidates.forEach { it.invalidate() }.also { invalidates.clear() }
var someText = "Unmodified"
var doSubCompose: () -> Unit = { error("Sub-compose callback not set") }
val staticSomeTextCompositionLocal = staticCompositionLocalOf { "Default" }
val staticSomeIntCompositionLocal = staticCompositionLocalOf { -1 }
compose {
invalidates.add(currentRecomposeScope)
CompositionLocalProvider(
staticSomeTextCompositionLocal provides someText,
staticSomeIntCompositionLocal provides 0
) {
assertEquals(someText, staticSomeTextCompositionLocal.current)
assertEquals(0, staticSomeIntCompositionLocal.current)
ReadStringCompositionLocal(
compositionLocal = staticSomeTextCompositionLocal,
id = tvId
)
doSubCompose = deferredSubCompose {
assertEquals(someText, staticSomeTextCompositionLocal.current)
assertEquals(0, staticSomeIntCompositionLocal.current)
CompositionLocalProvider(
staticSomeIntCompositionLocal provides 42
) {
assertEquals(someText, staticSomeTextCompositionLocal.current)
assertEquals(42, staticSomeIntCompositionLocal.current)
}
}
}
}.then {
assertEquals(someText, it.findViewById<TextView>(tvId).text)
doSubCompose()
someText = "Modified"
doInvalidate()
}.then {
assertEquals(someText, it.findViewById<TextView>(tvId).text)
doSubCompose()
}.done()
}
@Test
@Ignore("b/179279455")
fun deferredSubCompose_Nested_Static() {
val tvId = 100
val invalidates = mutableListOf<RecomposeScope>()
fun doInvalidate() = invalidates.forEach { it.invalidate() }.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 staticSomeTextCompositionLocal = staticCompositionLocalOf { "Default" }
val staticSomeIntCompositionLocal = staticCompositionLocalOf { -1 }
compose {
invalidates.add(currentRecomposeScope)
CompositionLocalProvider(
staticSomeTextCompositionLocal provides someText,
staticSomeIntCompositionLocal provides 0
) {
assertEquals(someText, staticSomeTextCompositionLocal.current)
assertEquals(0, staticSomeIntCompositionLocal.current)
ReadStringCompositionLocal(
compositionLocal = staticSomeTextCompositionLocal,
id = tvId
)
doSubCompose1 = deferredSubCompose {
assertEquals(someText, staticSomeTextCompositionLocal.current)
assertEquals(0, staticSomeIntCompositionLocal.current)
doSubCompose2 = deferredSubCompose {
assertEquals(someText, staticSomeTextCompositionLocal.current)
assertEquals(0, staticSomeIntCompositionLocal.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
@Ignore("b/179279455")
fun insertShouldSeePreviouslyProvidedValues() {
val invalidates = mutableListOf<RecomposeScope>()
fun doInvalidate() = invalidates.forEach { it.invalidate() }.also { invalidates.clear() }
val someStaticString = staticCompositionLocalOf { "Default" }
var shouldRead = false
compose {
CompositionLocalProvider(
someStaticString provides "Provided A"
) {
Observe {
invalidates.add(currentRecomposeScope)
if (shouldRead)
ReadStringCompositionLocal(someStaticString)
}
}
}.then {
assertEquals(null, it.findViewById<TextView?>(100))
shouldRead = true
doInvalidate()
}.then {
assertEquals("Provided A", it.findViewById<TextView>(100).text)
}
}
@Test
@Ignore("b/179279455")
fun providingANewDataClassValueShouldNotRecompose() {
val invalidates = mutableListOf<RecomposeScope>()
fun doInvalidate() = invalidates.forEach { it.invalidate() }.also { invalidates.clear() }
val someDataCompositionLocal = compositionLocalOf(structuralEqualityPolicy()) { SomeData() }
var composed = false
@Composable
fun ReadSomeDataCompositionLocal(
compositionLocal: CompositionLocal<SomeData>,
id: Int = 100
) {
composed = true
Text(value = compositionLocal.current.value, id = id)
}
compose {
Observe {
invalidates.add(currentRecomposeScope)
CompositionLocalProvider(
someDataCompositionLocal provides SomeData("provided")
) {
ReadSomeDataCompositionLocal(someDataCompositionLocal)
}
}
}.then {
assertTrue(composed)
composed = false
doInvalidate()
}.then {
assertFalse(composed)
}
}
@After
fun ensureNoSubcomposePending() {
activityRule.activity.uiThread {
val hasInvalidations = Recomposer.runningRecomposers.value.any { it.hasPendingWork }
assertFalse(hasInvalidations, "Pending changes detected after test completed")
}
}
class Ref<T : Any> {
lateinit var value: T
}
@Composable fun narrowInvalidateForReference(ref: Ref<CompositionContext>) {
ref.value = rememberCompositionContext()
}
@Suppress("UNUSED_PARAMETER")
@Composable fun deferredSubCompose(block: @Composable () -> Unit): () -> Unit {
// val container = remember { View(activity) }
val ref = Ref<CompositionContext>()
narrowInvalidateForReference(ref = ref)
return {
// Composition(
// UiApplier(container),
// ref.value
// ).apply {
// setContent {
// block()
// }
// }
}
}
}
private data class SomeData(val value: String = "default")