Fixed
Status Update
Comments
je...@google.com <je...@google.com>
ap...@google.com <ap...@google.com> #2
We might just need to implement equals and hashcode for the modifier
pa...@google.com <pa...@google.com> #3
Hi, I can't reproduce this behavior. It is possible that the composable in your code is recomposing because of a different reason. Is the tag you pass for example read from a class variable? That would cause your composable to be recomposed because the compiler cannot infer that all input to the testTag is @Stable.
The test below shows that adding testTag
does not prevent Compose to skip the composable:
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.width
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.produceState
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.unit.dp
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.delay
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class TestTagTest {
@get:Rule
val rule = createComposeRule()
@Stable
private class RecompositionCounter {
var withoutTag = 0
var withTag = 0
}
@Test
fun test() {
val counter = RecompositionCounter()
rule.setContent {
val state = produceState(initialValue = 0) {
repeat(10) {
delay(500)
value++
}
}
Column {
Text(text = "value: ${state.value}")
MyComponent(modifier = Modifier.width(200.dp), "Data1", counter)
MyComponent(modifier = Modifier.width(200.dp).testTag("Data2"), "Data2", counter)
}
}
repeat(10) {
rule.waitForIdle()
rule.mainClock.advanceTimeBy(500)
}
rule.onNodeWithText("value: 10").assertExists()
assertThat(counter.withoutTag).isEqualTo(1)
assertThat(counter.withTag).isEqualTo(1)
}
@Composable
private fun MyComponent(modifier: Modifier = Modifier, text: String, rc: RecompositionCounter) {
if (text == "Data1") {
rc.withoutTag++
} else if (text == "Data2") {
rc.withTag++
}
Text(modifier = modifier, text = text)
}
}
ap...@google.com <ap...@google.com> #4
Hi,
I have change the code a bit so I can use it outside a test on a ComponentActivity.
Here the code:
package de.telekom.testapp
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.width
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.delay
@Stable
private data class RecompositionCounter(
var withoutTag: Int = 0,
var withTag: Int = 0
)
@Composable
fun TestModifier() {
val counter = remember { RecompositionCounter() }
val state = produceState(initialValue = 0) {
repeat(10) {
delay(500)
value++
}
}
Column {
Text(text = "value: ${state.value} $counter")
MyComponent(modifier = Modifier.width(200.dp), "Data1", counter)
MyComponent(
modifier = Modifier
.width(200.dp)
.testTag("Data2"), "Data2", counter
)
}
}
@Composable
private fun MyComponent(modifier: Modifier = Modifier, text: String, rc: RecompositionCounter) {
if (text == "Data1") {
rc.withoutTag++
} else if (text == "Data2") {
rc.withTag++
}
Text(modifier = modifier, text = text)
}
And the counter "withTag" still increments. Attached you find a screenshot after 10 repeats.
Description
In the more general case, we should probably be aware of anything that is scheduled to be added to the window but not yet attached, for a reasonable definition of "scheduled".
Potentially this information can be surfaced through ViewRootForTest?