[go: nahoru, domu]

blob: a227236bbdd7773c707de880c9929c7d09b51b01 [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.
*/
package androidx.compose.material.textfield
import android.content.Context
import android.os.Build
import android.view.View
import android.view.inputmethod.InputMethodManager
import androidx.compose.foundation.interaction.FocusInteraction
import androidx.compose.foundation.interaction.Interaction
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.requiredWidth
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.Icon
import androidx.compose.material.LocalContentAlpha
import androidx.compose.material.LocalContentColor
import androidx.compose.material.LocalTextStyle
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.TextField
import androidx.compose.material.TextFieldDefaults
import androidx.compose.material.TextFieldPadding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.runOnIdleWithDensity
import androidx.compose.material.setMaterialContent
import androidx.compose.material.setMaterialContentForSizeAssertions
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.testutils.assertShape
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusModifier
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.layout.FirstBaseline
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.positionInRoot
import androidx.compose.ui.node.Ref
import androidx.compose.ui.platform.LocalTextInputService
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.assertHeightIsEqualTo
import androidx.compose.ui.test.assertWidthIsEqualTo
import androidx.compose.ui.test.captureToImage
import androidx.compose.ui.test.click
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performGesture
import androidx.compose.ui.text.SoftwareKeyboardController
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.ImeOptions
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.PlatformTextInputService
import androidx.compose.ui.text.input.TextInputService
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import androidx.test.filters.MediumTest
import androidx.test.filters.SdkSuppress
import com.google.common.truth.Truth.assertThat
import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.atLeastOnce
import com.nhaarman.mockitokotlin2.eq
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.verify
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import kotlin.math.roundToInt
@MediumTest
@RunWith(AndroidJUnit4::class)
@OptIn(ExperimentalTestApi::class)
class TextFieldTest {
private val ExpectedDefaultTextFieldHeight = 56.dp
private val ExpectedDefaultTextFieldWidth = 280.dp
private val ExpectedPadding = 16.dp
private val IconPadding = 12.dp
private val ExpectedBaselineOffset = 20.dp
private val TopPaddingFilledTextfield = 4.dp
private val IconColorAlpha = 0.54f
private val TextfieldTag = "textField"
@get:Rule
val rule = createComposeRule()
@Test
fun testTextField_minimumHeight() {
rule.setMaterialContentForSizeAssertions {
TextField(
value = "input",
onValueChange = {},
label = {},
modifier = Modifier.height(20.dp)
)
}
.assertHeightIsEqualTo(20.dp)
}
@Test
fun testTextField_setSmallWidth() {
rule.setMaterialContentForSizeAssertions {
TextField(
value = "input",
onValueChange = {},
label = {},
modifier = Modifier.requiredWidth(40.dp)
)
}
.assertWidthIsEqualTo(40.dp)
}
@Test
fun testTextField_defaultWidth() {
rule.setMaterialContentForSizeAssertions {
TextField(
value = "input",
onValueChange = {},
label = {}
)
}
.assertWidthIsEqualTo(ExpectedDefaultTextFieldWidth)
}
@Test
fun testTextFields_singleFocus() {
val textField1Tag = "TextField1"
val textField2Tag = "TextField2"
val interactionSource1 = MutableInteractionSource()
val interactionSource2 = MutableInteractionSource()
var scope: CoroutineScope? = null
rule.setMaterialContent {
scope = rememberCoroutineScope()
Column {
TextField(
modifier = Modifier.testTag(textField1Tag),
value = "input1",
onValueChange = {},
label = {},
interactionSource = interactionSource1
)
TextField(
modifier = Modifier.testTag(textField2Tag),
value = "input2",
onValueChange = {},
label = {},
interactionSource = interactionSource2
)
}
}
val interactions1 = mutableListOf<Interaction>()
val interactions2 = mutableListOf<Interaction>()
scope!!.launch {
interactionSource1.interactions.collect { interactions1.add(it) }
}
scope!!.launch {
interactionSource2.interactions.collect { interactions2.add(it) }
}
rule.runOnIdle {
assertThat(interactions1).isEmpty()
assertThat(interactions2).isEmpty()
}
rule.onNodeWithTag(textField1Tag).performClick()
rule.runOnIdle {
// Not asserting total size as we have other interactions here too
assertThat(interactions1.filterIsInstance<FocusInteraction.Focus>()).hasSize(1)
assertThat(interactions2).isEmpty()
}
rule.onNodeWithTag(textField2Tag).performClick()
rule.runOnIdle {
// Not asserting total size as we have other interactions here too
assertThat(interactions1.filterIsInstance<FocusInteraction.Focus>()).hasSize(1)
assertThat(interactions1.filterIsInstance<FocusInteraction.Unfocus>()).hasSize(1)
assertThat(interactions2.filterIsInstance<FocusInteraction.Focus>()).hasSize(1)
assertThat(interactions2.filterIsInstance<FocusInteraction.Unfocus>()).isEmpty()
}
}
@Test
fun testTextField_getFocus_whenClickedOnSurfaceArea() {
val interactionSource = MutableInteractionSource()
var scope: CoroutineScope? = null
rule.setMaterialContent {
scope = rememberCoroutineScope()
TextField(
modifier = Modifier.testTag(TextfieldTag),
value = "input",
onValueChange = {},
label = {},
interactionSource = interactionSource
)
}
val interactions = mutableListOf<Interaction>()
scope!!.launch {
interactionSource.interactions.collect { interactions.add(it) }
}
rule.runOnIdle {
assertThat(interactions).isEmpty()
}
// Click on (2, 2) which is Surface area and outside input area
rule.onNodeWithTag(TextfieldTag).performGesture {
click(Offset(2f, 2f))
}
rule.runOnIdle {
// Not asserting total size as we have other interactions here too
assertThat(interactions.filterIsInstance<FocusInteraction.Focus>()).hasSize(1)
}
}
@ExperimentalComposeUiApi
@Test
fun testTextField_showHideKeyboardBasedOnFocus() {
val (focusRequester, parentFocusRequester) = FocusRequester.createRefs()
lateinit var hostView: View
rule.setMaterialContent {
hostView = LocalView.current
Box {
TextField(
modifier = Modifier
.focusRequester(parentFocusRequester)
.focusModifier()
.focusRequester(focusRequester)
.testTag(TextfieldTag),
value = "input",
onValueChange = {},
label = {}
)
}
}
// Shows keyboard when the text field is focused.
rule.runOnIdle { focusRequester.requestFocus() }
rule.runOnIdle { assertThat(hostView.isSoftwareKeyboardShown).isTrue() }
// Hides keyboard when the text field is not focused.
rule.runOnIdle { parentFocusRequester.requestFocus() }
rule.runOnIdle { assertThat(hostView.isSoftwareKeyboardShown).isFalse() }
}
@ExperimentalComposeUiApi
@Test
fun testTextField_clickingOnTextAfterDismissingKeyboard_showsKeyboard() {
val (focusRequester, parentFocusRequester) = FocusRequester.createRefs()
lateinit var softwareKeyboardController: SoftwareKeyboardController
lateinit var hostView: View
rule.setMaterialContent {
hostView = LocalView.current
Box {
TextField(
modifier = Modifier
.focusRequester(parentFocusRequester)
.focusModifier()
.focusRequester(focusRequester)
.testTag(TextfieldTag),
value = "input",
onValueChange = {},
onTextInputStarted = { softwareKeyboardController = it },
label = {}
)
}
}
// Shows keyboard when the text field is focused.
rule.runOnIdle { focusRequester.requestFocus() }
rule.runOnIdle { assertThat(hostView.isSoftwareKeyboardShown).isTrue() }
// Hide keyboard.
rule.runOnIdle { softwareKeyboardController.hideSoftwareKeyboard() }
// Clicking on the text field shows the keyboard.
rule.onNodeWithTag(TextfieldTag).performClick()
rule.runOnIdle { assertThat(hostView.isSoftwareKeyboardShown).isTrue() }
}
@Test
fun testTextField_labelPosition_initial_singleLine() {
val labelSize = Ref<IntSize>()
val labelPosition = Ref<Offset>()
rule.setMaterialContent {
Box {
TextField(
value = "",
onValueChange = {},
singleLine = true,
label = {
Text(
text = "label",
fontSize = 10.sp,
modifier = Modifier
.onGloballyPositioned {
labelPosition.value = it.positionInRoot()
labelSize.value = it.size
}
)
},
modifier = Modifier.height(56.dp)
)
}
}
rule.runOnIdleWithDensity {
// size
assertThat(labelSize.value).isNotNull()
assertThat(labelSize.value?.height).isGreaterThan(0)
assertThat(labelSize.value?.width).isGreaterThan(0)
// centered position
assertThat(labelPosition.value?.x).isEqualTo(
ExpectedPadding.roundToPx().toFloat()
)
assertThat(labelPosition.value?.y).isEqualTo(
((ExpectedDefaultTextFieldHeight.roundToPx() - labelSize.value!!.height) / 2f)
.roundToInt().toFloat()
)
}
}
@Test
fun testTextField_labelPosition_initial_withDefaultHeight() {
val labelSize = Ref<IntSize>()
val labelPosition = Ref<Offset>()
rule.setMaterialContent {
Box {
TextField(
value = "",
onValueChange = {},
label = {
Text(
text = "label",
fontSize = 10.sp,
modifier = Modifier
.onGloballyPositioned {
labelPosition.value = it.positionInRoot()
labelSize.value = it.size
}
)
},
modifier = Modifier.height(56.dp)
)
}
}
rule.runOnIdleWithDensity {
// size
assertThat(labelSize.value).isNotNull()
assertThat(labelSize.value?.height).isGreaterThan(0)
assertThat(labelSize.value?.width).isGreaterThan(0)
// centered position
assertThat(labelPosition.value?.x).isEqualTo(
ExpectedPadding.roundToPx().toFloat()
)
assertThat(labelPosition.value?.y).isEqualTo(
TextFieldPadding.roundToPx()
)
}
}
@Test
fun testTextField_labelPosition_initial_withCustomHeight() {
val height = 80.dp
val labelSize = Ref<IntSize>()
val labelPosition = Ref<Offset>()
rule.setMaterialContent {
Box {
TextField(
value = "",
onValueChange = {},
modifier = Modifier.height(height),
label = {
Text(
text = "label",
modifier = Modifier.onGloballyPositioned {
labelPosition.value = it.positionInRoot()
labelSize.value = it.size
}
)
}
)
}
}
rule.runOnIdleWithDensity {
// size
assertThat(labelSize.value).isNotNull()
assertThat(labelSize.value?.height).isGreaterThan(0)
assertThat(labelSize.value?.width).isGreaterThan(0)
// centered position
assertThat(labelPosition.value?.x).isEqualTo(
ExpectedPadding.roundToPx().toFloat()
)
assertThat(labelPosition.value?.y).isEqualTo(
TextFieldPadding.roundToPx()
)
}
}
@Test
fun testTextField_labelPosition_whenFocused() {
val labelSize = Ref<IntSize>()
val labelPosition = Ref<Offset>()
val baseline = Ref<Float>()
rule.setMaterialContent {
Box {
TextField(
modifier = Modifier.testTag(TextfieldTag),
value = "",
onValueChange = {},
label = {
Text(
text = "label",
modifier = Modifier.onGloballyPositioned {
labelPosition.value = it.positionInRoot()
labelSize.value = it.size
baseline.value = it[FirstBaseline].toFloat() +
labelPosition.value!!.y
}
)
}
)
}
}
// click to focus
rule.onNodeWithTag(TextfieldTag).performClick()
rule.runOnIdleWithDensity {
// size
assertThat(labelSize.value).isNotNull()
assertThat(labelSize.value?.height).isGreaterThan(0)
assertThat(labelSize.value?.width).isGreaterThan(0)
// label's top position
assertThat(labelPosition.value?.x).isEqualTo(
ExpectedPadding.roundToPx().toFloat()
)
assertThat(baseline.value).isEqualTo(
ExpectedBaselineOffset.roundToPx().toFloat()
)
}
}
@Test
fun testTextField_labelPosition_whenInput() {
val labelSize = Ref<IntSize>()
val labelPosition = Ref<Offset>()
val baseline = Ref<Float>()
rule.setMaterialContent {
Box {
TextField(
value = "input",
onValueChange = {},
label = {
Text(
text = "label",
modifier = Modifier.onGloballyPositioned {
labelPosition.value = it.positionInRoot()
labelSize.value = it.size
baseline.value =
it[FirstBaseline].toFloat() + labelPosition.value!!.y
}
)
}
)
}
}
rule.runOnIdleWithDensity {
// size
assertThat(labelSize.value).isNotNull()
assertThat(labelSize.value?.height).isGreaterThan(0)
assertThat(labelSize.value?.width).isGreaterThan(0)
// label's top position
assertThat(labelPosition.value?.x).isEqualTo(
ExpectedPadding.roundToPx().toFloat()
)
assertThat(baseline.value).isEqualTo(
ExpectedBaselineOffset.roundToPx().toFloat()
)
}
}
@Test
fun testTextField_placeholderPosition_withLabel() {
val placeholderSize = Ref<IntSize>()
val placeholderPosition = Ref<Offset>()
rule.setMaterialContent {
Box {
TextField(
modifier = Modifier
.height(60.dp)
.testTag(TextfieldTag),
value = "",
onValueChange = {},
label = { Text("label") },
placeholder = {
Text(
text = "placeholder",
modifier = Modifier.onGloballyPositioned {
placeholderPosition.value = it.positionInRoot()
placeholderSize.value = it.size
}
)
}
)
}
}
// click to focus
rule.onNodeWithTag(TextfieldTag).performClick()
rule.runOnIdleWithDensity {
// size
assertThat(placeholderSize.value).isNotNull()
assertThat(placeholderSize.value?.height).isGreaterThan(0)
assertThat(placeholderSize.value?.width).isGreaterThan(0)
// placeholder's position
assertThat(placeholderPosition.value?.x).isEqualTo(
ExpectedPadding.roundToPx().toFloat()
)
assertThat(placeholderPosition.value?.y)
.isEqualTo(
(ExpectedBaselineOffset.roundToPx() + TopPaddingFilledTextfield.roundToPx())
.toFloat()
)
}
}
@Test
fun testTextField_placeholderPosition_whenNoLabel() {
val placeholderSize = Ref<IntSize>()
val placeholderPosition = Ref<Offset>()
val height = 60.dp
rule.setMaterialContent {
Box {
TextField(
modifier = Modifier.height(height).testTag(TextfieldTag),
value = "",
onValueChange = {},
label = {},
placeholder = {
Text(
text = "placeholder",
modifier = Modifier.requiredHeight(20.dp)
.onGloballyPositioned {
placeholderPosition.value = it.positionInRoot()
placeholderSize.value = it.size
}
)
}
)
}
}
// click to focus
rule.onNodeWithTag(TextfieldTag).performClick()
rule.runOnIdleWithDensity {
// size
assertThat(placeholderSize.value).isNotNull()
assertThat(placeholderSize.value?.height).isEqualTo(20.dp.roundToPx())
assertThat(placeholderSize.value?.width).isGreaterThan(0)
// centered position
assertThat(placeholderPosition.value?.x).isEqualTo(
ExpectedPadding.roundToPx().toFloat()
)
assertThat(placeholderPosition.value?.y).isEqualTo(
TextFieldPadding.roundToPx()
)
}
}
@Test
fun testTextField_noPlaceholder_whenInputNotEmpty() {
val placeholderSize = Ref<IntSize>()
val placeholderPosition = Ref<Offset>()
rule.setMaterialContent {
Column {
TextField(
modifier = Modifier.testTag(TextfieldTag),
value = "input",
onValueChange = {},
label = {},
placeholder = {
Text(
text = "placeholder",
modifier = Modifier.onGloballyPositioned {
placeholderPosition.value = it.positionInRoot()
placeholderSize.value = it.size
}
)
}
)
}
}
// click to focus
rule.onNodeWithTag(TextfieldTag).performClick()
rule.runOnIdleWithDensity {
assertThat(placeholderSize.value).isNull()
assertThat(placeholderPosition.value).isNull()
}
}
@Test
fun testTextField_placeholderColorAndTextStyle() {
rule.setMaterialContent {
TextField(
modifier = Modifier.testTag(TextfieldTag),
value = "",
onValueChange = {},
label = {},
placeholder = {
Text("placeholder")
assertThat(
LocalContentColor.current.copy(
alpha = LocalContentAlpha.current
)
)
.isEqualTo(
MaterialTheme.colors.onSurface.copy(
alpha = 0.6f
)
)
assertThat(LocalTextStyle.current)
.isEqualTo(MaterialTheme.typography.subtitle1)
}
)
}
// click to focus
rule.onNodeWithTag(TextfieldTag).performClick()
}
@Test
fun testTextField_trailingAndLeading_sizeAndPosition() {
val textFieldHeight = 60.dp
val textFieldWidth = 300.dp
val size = 30.dp
val leadingPosition = Ref<Offset>()
val leadingSize = Ref<IntSize>()
val trailingPosition = Ref<Offset>()
val trailingSize = Ref<IntSize>()
rule.setMaterialContent {
TextField(
value = "text",
onValueChange = {},
modifier = Modifier.size(textFieldWidth, textFieldHeight),
label = {},
leadingIcon = {
Box(
Modifier.size(size).onGloballyPositioned {
leadingPosition.value = it.positionInRoot()
leadingSize.value = it.size
}
)
},
trailingIcon = {
Box(
Modifier.size(size).onGloballyPositioned {
trailingPosition.value = it.positionInRoot()
trailingSize.value = it.size
}
)
}
)
}
rule.runOnIdleWithDensity {
// leading
assertThat(leadingSize.value).isEqualTo(IntSize(size.roundToPx(), size.roundToPx()))
assertThat(leadingPosition.value?.x).isEqualTo(IconPadding.roundToPx().toFloat())
assertThat(leadingPosition.value?.y).isEqualTo(
((textFieldHeight.roundToPx() - leadingSize.value!!.height) / 2f).roundToInt()
.toFloat()
)
// trailing
assertThat(trailingSize.value).isEqualTo(IntSize(size.roundToPx(), size.roundToPx()))
assertThat(trailingPosition.value?.x).isEqualTo(
(textFieldWidth.roundToPx() - IconPadding.roundToPx() - trailingSize.value!!.width)
.toFloat()
)
assertThat(trailingPosition.value?.y)
.isEqualTo(
((textFieldHeight.roundToPx() - trailingSize.value!!.height) / 2f)
.roundToInt().toFloat()
)
}
}
@Test
fun testTextField_labelPositionX_initial_withTrailingAndLeading() {
val height = 60.dp
val iconSize = 30.dp
val labelPosition = Ref<Offset>()
rule.setMaterialContent {
Box {
TextField(
value = "",
onValueChange = {},
modifier = Modifier.height(height),
label = {
Text(
text = "label",
modifier = Modifier.onGloballyPositioned {
labelPosition.value = it.positionInRoot()
}
)
},
trailingIcon = { Box(Modifier.size(iconSize)) },
leadingIcon = { Box(Modifier.size(iconSize)) }
)
}
}
rule.runOnIdleWithDensity {
assertThat(labelPosition.value?.x).isEqualTo(
(ExpectedPadding.roundToPx() + IconPadding.roundToPx() + iconSize.roundToPx())
.toFloat()
)
}
}
@Test
fun testTextField_labelPositionX_initial_withEmptyTrailingAndLeading() {
val height = 60.dp
val labelPosition = Ref<Offset>()
rule.setMaterialContent {
Box {
TextField(
value = "",
onValueChange = {},
modifier = Modifier.height(height),
label = {
Text(
text = "label",
modifier = Modifier.onGloballyPositioned {
labelPosition.value = it.positionInRoot()
}
)
},
trailingIcon = {},
leadingIcon = {}
)
}
}
rule.runOnIdleWithDensity {
assertThat(labelPosition.value?.x).isEqualTo(
ExpectedPadding.roundToPx().toFloat()
)
}
}
@Test
fun testTextField_colorInLeadingTrailing_whenValidInput() {
rule.setMaterialContent {
TextField(
value = "",
onValueChange = {},
label = {},
isError = false,
leadingIcon = {
assertThat(LocalContentColor.current)
.isEqualTo(
MaterialTheme.colors.onSurface.copy(
IconColorAlpha
)
)
},
trailingIcon = {
assertThat(LocalContentColor.current)
.isEqualTo(
MaterialTheme.colors.onSurface.copy(
IconColorAlpha
)
)
}
)
}
}
@Test
fun testTextField_colorInLeadingTrailing_whenInvalidInput() {
rule.setMaterialContent {
TextField(
value = "",
onValueChange = {},
label = {},
isError = true,
leadingIcon = {
assertThat(LocalContentColor.current)
.isEqualTo(
MaterialTheme.colors.onSurface.copy(
IconColorAlpha
)
)
},
trailingIcon = {
assertThat(LocalContentColor.current).isEqualTo(MaterialTheme.colors.error)
}
)
}
}
@Test
fun testTextField_imeActionAndKeyboardTypePropagatedDownstream() {
val platformTextInputService = mock<PlatformTextInputService>()
val textInputService = TextInputService(platformTextInputService)
rule.setContent {
CompositionLocalProvider(
LocalTextInputService provides textInputService
) {
val text = remember { mutableStateOf("") }
TextField(
modifier = Modifier.testTag(TextfieldTag),
value = text.value,
onValueChange = { text.value = it },
label = {},
keyboardOptions = KeyboardOptions(
imeAction = ImeAction.Go,
keyboardType = KeyboardType.Email
)
)
}
}
rule.onNodeWithTag(TextfieldTag).performClick()
rule.runOnIdle {
verify(platformTextInputService, atLeastOnce()).startInput(
value = any(),
imeOptions = eq(
ImeOptions(
keyboardType = KeyboardType.Email,
imeAction = ImeAction.Go
)
),
onEditCommand = any(),
onImeActionPerformed = any()
)
}
}
@Test
@LargeTest
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
fun testTextField_visualTransformationPropagated() {
rule.setMaterialContent {
TextField(
modifier = Modifier.testTag(TextfieldTag),
value = "qwerty",
onValueChange = {},
visualTransformation = PasswordVisualTransformation('\u0020'),
shape = RectangleShape,
colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.White)
)
}
rule.onNodeWithTag(TextfieldTag)
.captureToImage()
.assertShape(
density = rule.density,
backgroundColor = Color.White,
shapeColor = Color.White,
shape = RectangleShape,
// avoid elevation artifacts
shapeOverlapPixelCount = with(rule.density) { 3.dp.toPx() }
)
}
@Test
@LargeTest
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
fun testTextField_alphaNotApplied_toCustomBackgroundColorAndTransparentColors() {
rule.setMaterialContent {
Box(Modifier.background(color = Color.White)) {
TextField(
modifier = Modifier.testTag(TextfieldTag),
value = "test",
onValueChange = {},
label = { Text("label") },
shape = RectangleShape,
leadingIcon = {
Icon(Icons.Default.Favorite, null, tint = Color.Transparent)
},
trailingIcon = {
Icon(Icons.Default.Favorite, null, tint = Color.Transparent)
},
colors = TextFieldDefaults.textFieldColors(
backgroundColor = Color.Blue,
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent,
textColor = Color.Transparent,
cursorColor = Color.Transparent,
focusedLabelColor = Color.Transparent,
unfocusedLabelColor = Color.Transparent
)
)
}
}
rule.onNodeWithTag(TextfieldTag)
.captureToImage()
.assertShape(
density = rule.density,
backgroundColor = Color.White,
shapeColor = Color.Blue,
shape = RectangleShape,
// avoid elevation artifacts
shapeOverlapPixelCount = with(rule.density) { 1.dp.toPx() }
)
rule.onNodeWithTag(TextfieldTag).performClick()
rule.onNodeWithTag(TextfieldTag)
.captureToImage()
.assertShape(
density = rule.density,
backgroundColor = Color.White,
shapeColor = Color.Blue,
shape = RectangleShape,
// avoid elevation artifacts
shapeOverlapPixelCount = with(rule.density) { 1.dp.toPx() }
)
}
@Test
fun testTextField_onTextInputStartedCallback() {
var controller: SoftwareKeyboardController? = null
rule.setMaterialContent {
TextField(
modifier = Modifier.testTag(TextfieldTag),
value = "",
onValueChange = {},
label = {},
onTextInputStarted = {
controller = it
}
)
}
assertThat(controller).isNull()
rule.onNodeWithTag(TextfieldTag)
.performClick()
rule.runOnIdle {
assertThat(controller).isNotNull()
}
}
private val View.isSoftwareKeyboardShown: Boolean
get() {
val inputMethodManager =
context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
// TODO(b/163742556): This is just a proxy for software keyboard visibility. Find a better
// way to check if the software keyboard is shown.
return inputMethodManager.isAcceptingText()
}
}