blob: 9495e6abf0905b83e68d28ee3a3fafd19a943576 [file] [log] [blame]
package androidx.compose.foundation.text2
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.text.BasicText
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.text.selection.fetchTextLayoutResult
import androidx.compose.foundation.text2.input.TextFieldCharSequence
import androidx.compose.foundation.text2.input.TextFieldState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.semantics.SemanticsActions
import androidx.compose.ui.semantics.SemanticsProperties
import androidx.compose.ui.semantics.getOrNull
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.SemanticsMatcher
import androidx.compose.ui.test.SemanticsNodeInteraction
import androidx.compose.ui.test.assert
import androidx.compose.ui.test.assertHasClickAction
import androidx.compose.ui.test.assertTextEquals
import androidx.compose.ui.test.hasImeAction
import androidx.compose.ui.test.hasSetTextAction
import androidx.compose.ui.test.isEnabled
import androidx.compose.ui.test.isFocused
import androidx.compose.ui.test.isNotEnabled
import androidx.compose.ui.test.isNotFocused
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performSemanticsAction
import androidx.compose.ui.test.performTextInput
import androidx.compose.ui.test.performTextInputSelection
import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.input.ImeAction
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@OptIn(ExperimentalFoundationApi::class)
@LargeTest
@RunWith(AndroidJUnit4::class)
class BasicTextField2SemanticsTest {
@get:Rule
val rule = createComposeRule()
private val Tag = "TextField"
@Test
fun defaultSemantics() {
rule.setContent {
BasicTextField2(
modifier = Modifier.testTag(Tag),
state = remember { TextFieldState() },
decorationBox = {
Column {
BasicText("label")
it()
}
}
)
}
rule.onNodeWithTag(Tag)
.assertEditableTextEquals("")
.assertTextEquals("label", includeEditableText = false)
.assertHasClickAction()
.assert(hasSetTextAction())
.assert(hasImeAction(ImeAction.Default))
.assert(isNotFocused())
.assert(
SemanticsMatcher.expectValue(
SemanticsProperties.TextSelectionRange,
TextRange.Zero
)
)
.assert(SemanticsMatcher.keyIsDefined(SemanticsActions.SetText))
.assert(SemanticsMatcher.keyNotDefined(SemanticsProperties.Password))
// TODO(halilibo): enable after selection work is completed.
// .assert(SemanticsMatcher.keyIsDefined(SemanticsActions.SetSelection))
.assert(SemanticsMatcher.keyIsDefined(SemanticsActions.GetTextLayoutResult))
val textLayoutResults = mutableListOf<TextLayoutResult>()
rule.onNodeWithTag(Tag)
.performSemanticsAction(SemanticsActions.GetTextLayoutResult) { it(textLayoutResults) }
assert(textLayoutResults.size == 1) { "TextLayoutResult is null" }
}
@Test
fun semantics_enabledStatus() {
var enabled by mutableStateOf(true)
rule.setContent {
val state = remember { TextFieldState() }
BasicTextField2(
state = state,
modifier = Modifier.testTag(Tag),
enabled = enabled
)
}
rule.onNodeWithTag(Tag)
.assert(isEnabled())
enabled = false
rule.waitForIdle()
rule.onNodeWithTag(Tag)
.assert(isNotEnabled())
}
@Test
fun semantics_clickAction() {
rule.setContent {
val state = remember { TextFieldState() }
BasicTextField2(
state = state,
modifier = Modifier.testTag(Tag)
)
}
rule.onNodeWithTag(Tag)
.assert(isNotFocused())
.performSemanticsAction(SemanticsActions.OnClick)
rule.onNodeWithTag(Tag)
.assert(isFocused())
}
@Test
fun semantics_imeOption() {
rule.setContent {
val state = remember { TextFieldState() }
BasicTextField2(
state = state,
modifier = Modifier.testTag(Tag),
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search)
)
}
rule.onNodeWithTag(Tag).assert(hasImeAction(ImeAction.Search))
}
@Test
fun contentSemanticsAreSet_inTheFirstComposition() {
val state = TextFieldState("hello")
rule.setContent {
BasicTextField2(
state = state,
modifier = Modifier.testTag(Tag)
)
}
rule.onNodeWithTag(Tag).assertTextEquals("hello")
}
@Test
fun contentSemanticsAreSet_afterRecomposition() {
val state = TextFieldState("hello")
rule.setContent {
BasicTextField2(
state = state,
modifier = Modifier.testTag(Tag)
)
}
rule.onNodeWithTag(Tag).assertTextEquals("hello")
state.editProcessor.reset(TextFieldCharSequence("hello2"))
rule.onNodeWithTag(Tag).assertTextEquals("hello2")
}
@Test
fun selectionSemanticsAreSet_inTheFirstComposition() {
val state = TextFieldState("hello", initialSelectionInChars = TextRange(2))
rule.setContent {
BasicTextField2(
state = state,
modifier = Modifier.testTag(Tag)
)
}
with(rule.onNodeWithTag(Tag)) {
assertTextEquals("hello")
assertSelection(TextRange(2))
}
}
@Test
fun selectionSemanticsAreSet_afterRecomposition() {
val state = TextFieldState("hello")
rule.setContent {
BasicTextField2(
state = state,
modifier = Modifier.testTag(Tag)
)
}
with(rule.onNodeWithTag(Tag)) {
assertTextEquals("hello")
assertSelection(TextRange.Zero)
}
state.editProcessor.reset(TextFieldCharSequence("hello", selection = TextRange(2)))
with(rule.onNodeWithTag(Tag)) {
assertTextEquals("hello")
assertSelection(TextRange(2))
}
}
@OptIn(ExperimentalTestApi::class)
@Test
fun inputSelection_changesSelectionState() {
val state = TextFieldState("hello")
rule.setContent {
BasicTextField2(
state = state,
modifier = Modifier.testTag(Tag)
)
}
rule.onNodeWithTag(Tag).performTextInputSelection(TextRange(2))
rule.runOnIdle {
assertThat(state.value.selectionInChars).isEqualTo(TextRange(2))
}
}
@Test
fun textLayoutResultSemanticsAreSet_inTheFirstComposition() {
val state = TextFieldState("hello")
rule.setContent {
BasicTextField2(
state = state,
modifier = Modifier
.testTag(Tag)
)
}
rule.onNodeWithTag(Tag).assertTextEquals("hello")
assertThat(rule.onNodeWithTag(Tag).fetchTextLayoutResult().layoutInput.text.text)
.isEqualTo("hello")
}
@Test
fun textLayoutResultSemanticsAreUpdated_afterRecomposition() {
val state = TextFieldState()
rule.setContent {
BasicTextField2(
state = state,
modifier = Modifier
.testTag(Tag)
)
}
rule.onNodeWithTag(Tag).assertTextEquals("")
rule.onNodeWithTag(Tag).performTextInput("hello")
assertThat(rule.onNodeWithTag(Tag).fetchTextLayoutResult().layoutInput.text.text)
.isEqualTo("hello")
}
@Test
fun semanticsAreSet_afterStateObjectChanges() {
val state1 = TextFieldState("hello")
val state2 = TextFieldState("world", TextRange(2))
var chosenState by mutableStateOf(true)
rule.setContent {
BasicTextField2(
state = if (chosenState) state1 else state2,
modifier = Modifier.testTag(Tag)
)
}
with(rule.onNodeWithTag(Tag)) {
assertTextEquals("hello")
assertSelection(TextRange.Zero)
}
chosenState = false
with(rule.onNodeWithTag(Tag)) {
assertTextEquals("world")
assertSelection(TextRange(2))
}
}
private fun SemanticsNodeInteraction.assertSelection(expected: TextRange) {
val selection = fetchSemanticsNode().config
.getOrNull(SemanticsProperties.TextSelectionRange)
assertThat(selection).isEqualTo(expected)
}
private fun SemanticsNodeInteraction.assertEditableTextEquals(
value: String
): SemanticsNodeInteraction =
assert(
SemanticsMatcher("${SemanticsProperties.EditableText.name} = '$value'") {
it.config.getOrNull(SemanticsProperties.EditableText)?.text.equals(value)
}
)
}