Merge pull request #6765 from thundernest/add-textfields-to-design-system
Add textfields to design system
This commit is contained in:
commit
3f8a3ff0c9
15 changed files with 653 additions and 2 deletions
|
@ -15,6 +15,7 @@ import app.k9mail.ui.catalog.items.buttonItems
|
||||||
import app.k9mail.ui.catalog.items.colorItems
|
import app.k9mail.ui.catalog.items.colorItems
|
||||||
import app.k9mail.ui.catalog.items.imageItems
|
import app.k9mail.ui.catalog.items.imageItems
|
||||||
import app.k9mail.ui.catalog.items.selectionControlItems
|
import app.k9mail.ui.catalog.items.selectionControlItems
|
||||||
|
import app.k9mail.ui.catalog.items.textFieldItems
|
||||||
import app.k9mail.ui.catalog.items.themeHeaderItem
|
import app.k9mail.ui.catalog.items.themeHeaderItem
|
||||||
import app.k9mail.ui.catalog.items.themeSelectorItems
|
import app.k9mail.ui.catalog.items.themeSelectorItems
|
||||||
import app.k9mail.ui.catalog.items.typographyItems
|
import app.k9mail.ui.catalog.items.typographyItems
|
||||||
|
@ -48,6 +49,7 @@ fun CatalogContent(
|
||||||
colorItems()
|
colorItems()
|
||||||
buttonItems()
|
buttonItems()
|
||||||
selectionControlItems()
|
selectionControlItems()
|
||||||
|
textFieldItems()
|
||||||
imageItems()
|
imageItems()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,90 @@
|
||||||
|
package app.k9mail.ui.catalog.items
|
||||||
|
|
||||||
|
import androidx.compose.foundation.lazy.grid.LazyGridScope
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import app.k9mail.core.ui.compose.designsystem.atom.textfield.PasswordTextFieldOutlined
|
||||||
|
import app.k9mail.core.ui.compose.designsystem.atom.textfield.TextFieldOutlined
|
||||||
|
|
||||||
|
fun LazyGridScope.textFieldItems() {
|
||||||
|
sectionHeaderItem(text = "Text fields")
|
||||||
|
textFieldOutlinedItems()
|
||||||
|
passwordTextFieldOutlinedItems()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun LazyGridScope.textFieldOutlinedItems() {
|
||||||
|
sectionSubtitleItem(text = "Outlined")
|
||||||
|
item {
|
||||||
|
WithRememberedInput(text = "Initial text") { input ->
|
||||||
|
TextFieldOutlined(
|
||||||
|
value = input.value,
|
||||||
|
label = "Label",
|
||||||
|
onValueChange = { input.value = it },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
WithRememberedInput(text = "Input text with error") { input ->
|
||||||
|
TextFieldOutlined(
|
||||||
|
value = input.value,
|
||||||
|
label = "Label",
|
||||||
|
onValueChange = { input.value = it },
|
||||||
|
isError = true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
WithRememberedInput(text = "Input text disabled") { input ->
|
||||||
|
TextFieldOutlined(
|
||||||
|
value = input.value,
|
||||||
|
label = "Label",
|
||||||
|
onValueChange = { input.value = it },
|
||||||
|
enabled = false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun LazyGridScope.passwordTextFieldOutlinedItems() {
|
||||||
|
sectionSubtitleItem(text = "Password outlined")
|
||||||
|
item {
|
||||||
|
WithRememberedInput(text = "Input text") { input ->
|
||||||
|
PasswordTextFieldOutlined(
|
||||||
|
value = input.value,
|
||||||
|
label = "Label",
|
||||||
|
onValueChange = { input.value = it },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
WithRememberedInput(text = "Input text with error") { input ->
|
||||||
|
PasswordTextFieldOutlined(
|
||||||
|
value = input.value,
|
||||||
|
label = "Label",
|
||||||
|
onValueChange = { input.value = it },
|
||||||
|
isError = true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
WithRememberedInput(text = "Input text disabled") { input ->
|
||||||
|
PasswordTextFieldOutlined(
|
||||||
|
value = input.value,
|
||||||
|
label = "Label",
|
||||||
|
onValueChange = { input.value = it },
|
||||||
|
enabled = false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun WithRememberedInput(
|
||||||
|
text: String,
|
||||||
|
content: @Composable (text: MutableState<String>) -> Unit,
|
||||||
|
) {
|
||||||
|
val inputText = remember { mutableStateOf(text) }
|
||||||
|
content(inputText)
|
||||||
|
}
|
|
@ -6,6 +6,13 @@ android {
|
||||||
configureSharedComposeConfig(libs)
|
configureSharedComposeConfig(libs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
androidComponents {
|
||||||
|
beforeVariants(selector().withBuildType("release")) { variantBuilder ->
|
||||||
|
variantBuilder.enableUnitTest = false
|
||||||
|
variantBuilder.enableAndroidTest = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
configureSharedComposeDependencies(libs)
|
configureSharedComposeDependencies(libs)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,10 +4,13 @@ plugins {
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "app.k9mail.core.ui.compose.designsystem"
|
namespace = "app.k9mail.core.ui.compose.designsystem"
|
||||||
resourcePrefix = "core_ui_designsystem_"
|
resourcePrefix = "designsystem_"
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api(projects.core.ui.compose.theme)
|
api(projects.core.ui.compose.theme)
|
||||||
implementation(libs.androidx.compose.material)
|
implementation(libs.androidx.compose.material)
|
||||||
|
implementation(libs.androidx.compose.material.icons.extended)
|
||||||
|
|
||||||
|
testImplementation(projects.core.ui.compose.testing)
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,3 +31,15 @@ internal fun CheckboxPreview() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Preview(showBackground = true)
|
||||||
|
@Composable
|
||||||
|
internal fun CheckboxDisabledPreview() {
|
||||||
|
PreviewWithThemes {
|
||||||
|
Checkbox(
|
||||||
|
checked = true,
|
||||||
|
onCheckedChange = {},
|
||||||
|
enabled = false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,154 @@
|
||||||
|
package app.k9mail.core.ui.compose.designsystem.atom.textfield
|
||||||
|
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.material.Icon
|
||||||
|
import androidx.compose.material.IconButton
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Visibility
|
||||||
|
import androidx.compose.material.icons.filled.VisibilityOff
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
|
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||||
|
import androidx.compose.ui.text.input.VisualTransformation
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import app.k9mail.core.ui.compose.designsystem.R
|
||||||
|
import app.k9mail.core.ui.compose.theme.PreviewWithThemes
|
||||||
|
import androidx.compose.material.OutlinedTextField as MaterialOutlinedTextField
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun PasswordTextFieldOutlined(
|
||||||
|
value: String,
|
||||||
|
onValueChange: (String) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
enabled: Boolean = true,
|
||||||
|
label: String? = null,
|
||||||
|
isError: Boolean = false,
|
||||||
|
) {
|
||||||
|
var passwordVisibilityState by rememberSaveable { mutableStateOf(false) }
|
||||||
|
|
||||||
|
MaterialOutlinedTextField(
|
||||||
|
value = value,
|
||||||
|
onValueChange = onValueChange,
|
||||||
|
modifier = modifier,
|
||||||
|
enabled = enabled,
|
||||||
|
label = selectLabel(label),
|
||||||
|
trailingIcon = selectTrailingIcon(
|
||||||
|
isEnabled = enabled,
|
||||||
|
isPasswordVisible = passwordVisibilityState,
|
||||||
|
onClick = { passwordVisibilityState = !passwordVisibilityState },
|
||||||
|
),
|
||||||
|
isError = isError,
|
||||||
|
visualTransformation = selectVisualTransformation(
|
||||||
|
isEnabled = enabled,
|
||||||
|
isPasswordVisible = passwordVisibilityState,
|
||||||
|
),
|
||||||
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
|
||||||
|
singleLine = true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun selectLabel(label: String?): @Composable (() -> Unit)? {
|
||||||
|
return if (label != null) {
|
||||||
|
{
|
||||||
|
Text(text = label)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun selectTrailingIcon(
|
||||||
|
isEnabled: Boolean,
|
||||||
|
isPasswordVisible: Boolean,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
hasTrailingIcon: Boolean = true,
|
||||||
|
): @Composable (() -> Unit)? {
|
||||||
|
return if (hasTrailingIcon) {
|
||||||
|
{
|
||||||
|
val image = if (isShowPasswordAllowed(isEnabled, isPasswordVisible)) {
|
||||||
|
Icons.Filled.Visibility
|
||||||
|
} else {
|
||||||
|
Icons.Filled.VisibilityOff
|
||||||
|
}
|
||||||
|
|
||||||
|
val description = if (isShowPasswordAllowed(isEnabled, isPasswordVisible)) {
|
||||||
|
stringResource(id = R.string.designsystem_atom_password_textfield_hide_password)
|
||||||
|
} else {
|
||||||
|
stringResource(id = R.string.designsystem_atom_password_textfield_show_password)
|
||||||
|
}
|
||||||
|
|
||||||
|
IconButton(onClick = onClick) {
|
||||||
|
Icon(imageVector = image, contentDescription = description)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun selectVisualTransformation(
|
||||||
|
isEnabled: Boolean,
|
||||||
|
isPasswordVisible: Boolean,
|
||||||
|
): VisualTransformation {
|
||||||
|
return if (isShowPasswordAllowed(isEnabled, isPasswordVisible)) {
|
||||||
|
VisualTransformation.None
|
||||||
|
} else {
|
||||||
|
PasswordVisualTransformation()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isShowPasswordAllowed(isEnabled: Boolean, isPasswordVisible: Boolean) = isEnabled && isPasswordVisible
|
||||||
|
|
||||||
|
@Preview(showBackground = true)
|
||||||
|
@Composable
|
||||||
|
internal fun PasswordTextFieldOutlinedPreview() {
|
||||||
|
PreviewWithThemes {
|
||||||
|
PasswordTextFieldOutlined(
|
||||||
|
value = "Input text",
|
||||||
|
onValueChange = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(showBackground = true)
|
||||||
|
@Composable
|
||||||
|
internal fun PasswordTextFieldOutlinedWithLabelPreview() {
|
||||||
|
PreviewWithThemes {
|
||||||
|
PasswordTextFieldOutlined(
|
||||||
|
value = "Input text",
|
||||||
|
label = "Label",
|
||||||
|
onValueChange = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(showBackground = true)
|
||||||
|
@Composable
|
||||||
|
internal fun PasswordTextFieldOutlinedDisabledPreview() {
|
||||||
|
PreviewWithThemes {
|
||||||
|
PasswordTextFieldOutlined(
|
||||||
|
value = "Input text",
|
||||||
|
onValueChange = {},
|
||||||
|
enabled = false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(showBackground = true)
|
||||||
|
@Composable
|
||||||
|
internal fun PasswordTextFieldOutlinedErrorPreview() {
|
||||||
|
PreviewWithThemes {
|
||||||
|
PasswordTextFieldOutlined(
|
||||||
|
value = "Input text",
|
||||||
|
onValueChange = {},
|
||||||
|
isError = true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
package app.k9mail.core.ui.compose.designsystem.atom.textfield
|
||||||
|
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import app.k9mail.core.ui.compose.theme.PreviewWithThemes
|
||||||
|
import androidx.compose.material.OutlinedTextField as MaterialOutlinedTextField
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun TextFieldOutlined(
|
||||||
|
value: String,
|
||||||
|
onValueChange: (String) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
enabled: Boolean = true,
|
||||||
|
label: String? = null,
|
||||||
|
isError: Boolean = false,
|
||||||
|
) {
|
||||||
|
MaterialOutlinedTextField(
|
||||||
|
value = value,
|
||||||
|
onValueChange = onValueChange,
|
||||||
|
modifier = modifier,
|
||||||
|
enabled = enabled,
|
||||||
|
label = selectLabel(label),
|
||||||
|
isError = isError,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun selectLabel(label: String?): @Composable (() -> Unit)? {
|
||||||
|
return if (label != null) {
|
||||||
|
{
|
||||||
|
Text(text = label)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(showBackground = true)
|
||||||
|
@Composable
|
||||||
|
internal fun TextFieldOutlinedPreview() {
|
||||||
|
PreviewWithThemes {
|
||||||
|
TextFieldOutlined(
|
||||||
|
value = "Input text",
|
||||||
|
onValueChange = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(showBackground = true)
|
||||||
|
@Composable
|
||||||
|
internal fun TextFieldOutlinedWithLabelPreview() {
|
||||||
|
PreviewWithThemes {
|
||||||
|
TextFieldOutlined(
|
||||||
|
value = "Input text",
|
||||||
|
label = "Label",
|
||||||
|
onValueChange = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(showBackground = true)
|
||||||
|
@Composable
|
||||||
|
internal fun TextFieldOutlinedDisabledPreview() {
|
||||||
|
PreviewWithThemes {
|
||||||
|
TextFieldOutlined(
|
||||||
|
value = "Input text",
|
||||||
|
onValueChange = {},
|
||||||
|
enabled = false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(showBackground = true)
|
||||||
|
@Composable
|
||||||
|
internal fun TextFieldOutlinedErrorPreview() {
|
||||||
|
PreviewWithThemes {
|
||||||
|
TextFieldOutlined(
|
||||||
|
value = "Input text",
|
||||||
|
onValueChange = {},
|
||||||
|
isError = true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="designsystem_atom_password_textfield_hide_password">Hide password</string>
|
||||||
|
<string name="designsystem_atom_password_textfield_show_password">Show password</string>
|
||||||
|
</resources>
|
|
@ -0,0 +1,98 @@
|
||||||
|
package app.k9mail.core.ui.compose.designsystem.atom.textfield
|
||||||
|
|
||||||
|
import androidx.compose.ui.test.SemanticsNodeInteraction
|
||||||
|
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||||
|
import androidx.compose.ui.test.assertIsDisplayed
|
||||||
|
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||||
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
|
import androidx.compose.ui.test.performClick
|
||||||
|
import app.k9mail.core.ui.compose.designsystem.R
|
||||||
|
import app.k9mail.core.ui.compose.testing.ComposeTest
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
private const val PASSWORD = "Password input"
|
||||||
|
|
||||||
|
class PasswordTextFieldOutlinedKtTest : ComposeTest() {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `should not display password by default`() = runComposeTest {
|
||||||
|
setContent {
|
||||||
|
PasswordTextFieldOutlined(
|
||||||
|
value = PASSWORD,
|
||||||
|
onValueChange = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
onNodeWithText(PASSWORD).assertDoesNotExist()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `should display password when show password is clicked`() = runComposeTest {
|
||||||
|
setContent {
|
||||||
|
PasswordTextFieldOutlined(
|
||||||
|
value = PASSWORD,
|
||||||
|
onValueChange = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
onShowPasswordNode().performClick()
|
||||||
|
|
||||||
|
onNodeWithText(PASSWORD).assertIsDisplayed()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `should not display password when hide password is clicked`() = runComposeTest {
|
||||||
|
setContent {
|
||||||
|
PasswordTextFieldOutlined(
|
||||||
|
value = PASSWORD,
|
||||||
|
onValueChange = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onShowPasswordNode().performClick()
|
||||||
|
|
||||||
|
onHidePasswordNode().performClick()
|
||||||
|
|
||||||
|
onNodeWithText(PASSWORD).assertDoesNotExist()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `should display hide password icon when show password is clicked`() = runComposeTest {
|
||||||
|
setContent {
|
||||||
|
PasswordTextFieldOutlined(
|
||||||
|
value = PASSWORD,
|
||||||
|
onValueChange = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
onShowPasswordNode().performClick()
|
||||||
|
|
||||||
|
onHidePasswordNode().assertIsDisplayed()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `should display show password icon when hide password icon is clicked`() = runComposeTest {
|
||||||
|
setContent {
|
||||||
|
PasswordTextFieldOutlined(
|
||||||
|
value = PASSWORD,
|
||||||
|
onValueChange = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onShowPasswordNode().performClick()
|
||||||
|
|
||||||
|
onHidePasswordNode().performClick()
|
||||||
|
|
||||||
|
onShowPasswordNode().assertIsDisplayed()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun SemanticsNodeInteractionsProvider.onShowPasswordNode(): SemanticsNodeInteraction {
|
||||||
|
return onNodeWithContentDescription(
|
||||||
|
getString(R.string.designsystem_atom_password_textfield_show_password),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun SemanticsNodeInteractionsProvider.onHidePasswordNode(): SemanticsNodeInteraction {
|
||||||
|
return onNodeWithContentDescription(
|
||||||
|
getString(R.string.designsystem_atom_password_textfield_hide_password),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,154 @@
|
||||||
|
package app.k9mail.core.ui.compose.designsystem.atom.textfield
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.testTag
|
||||||
|
import androidx.compose.ui.test.assertIsDisplayed
|
||||||
|
import androidx.compose.ui.test.assertIsEnabled
|
||||||
|
import androidx.compose.ui.test.assertIsNotEnabled
|
||||||
|
import androidx.compose.ui.test.onNodeWithTag
|
||||||
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
|
import androidx.compose.ui.test.performClick
|
||||||
|
import androidx.compose.ui.test.performTextInput
|
||||||
|
import app.k9mail.core.ui.compose.testing.ComposeTest
|
||||||
|
import assertk.assertThat
|
||||||
|
import assertk.assertions.isEqualTo
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.robolectric.ParameterizedRobolectricTestRunner
|
||||||
|
|
||||||
|
private const val VALUE = "Input text"
|
||||||
|
private const val LABEL = "Label"
|
||||||
|
|
||||||
|
data class TextFieldTestData(
|
||||||
|
val name: String,
|
||||||
|
val content: @Composable (
|
||||||
|
value: String,
|
||||||
|
onValueChange: (String) -> Unit,
|
||||||
|
modifier: Modifier,
|
||||||
|
enabled: Boolean?,
|
||||||
|
label: String?,
|
||||||
|
) -> Unit,
|
||||||
|
)
|
||||||
|
|
||||||
|
@RunWith(ParameterizedRobolectricTestRunner::class)
|
||||||
|
class TextFieldKtTest(
|
||||||
|
data: TextFieldTestData,
|
||||||
|
) : ComposeTest() {
|
||||||
|
|
||||||
|
private val testSubjectName = data.name
|
||||||
|
private val testSubject = data.content
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `should call onValueChange when value changes`() = runComposeTest {
|
||||||
|
var value = VALUE
|
||||||
|
setContent {
|
||||||
|
testSubject(
|
||||||
|
value = value,
|
||||||
|
onValueChange = { value = it },
|
||||||
|
modifier = Modifier.testTag(testSubjectName),
|
||||||
|
enabled = null,
|
||||||
|
label = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
onNodeWithTag(testSubjectName).performClick()
|
||||||
|
onNodeWithTag(testSubjectName).performTextInput(" + added text")
|
||||||
|
|
||||||
|
assertThat(value).isEqualTo("$VALUE + added text")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `should be enabled by default`() = runComposeTest {
|
||||||
|
setContent {
|
||||||
|
testSubject(
|
||||||
|
value = VALUE,
|
||||||
|
onValueChange = {},
|
||||||
|
modifier = Modifier.testTag(testSubjectName),
|
||||||
|
enabled = null,
|
||||||
|
label = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
onNodeWithTag(testSubjectName).assertIsEnabled()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `should be disabled when enabled is false`() = runComposeTest {
|
||||||
|
setContent {
|
||||||
|
testSubject(
|
||||||
|
value = VALUE,
|
||||||
|
onValueChange = {},
|
||||||
|
modifier = Modifier.testTag(testSubjectName),
|
||||||
|
enabled = false,
|
||||||
|
label = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
onNodeWithTag(testSubjectName).assertIsNotEnabled()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `should show label when label is not null`() = runComposeTest {
|
||||||
|
setContent {
|
||||||
|
testSubject(
|
||||||
|
value = VALUE,
|
||||||
|
onValueChange = {},
|
||||||
|
modifier = Modifier.testTag(testSubjectName),
|
||||||
|
enabled = null,
|
||||||
|
label = LABEL,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
onNodeWithText(LABEL).assertIsDisplayed()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
@ParameterizedRobolectricTestRunner.Parameters(name = "{0}")
|
||||||
|
fun data(): List<TextFieldTestData> = listOf(
|
||||||
|
TextFieldTestData(
|
||||||
|
name = "TextFieldOutlined",
|
||||||
|
content = { value, onValueChange, modifier, enabled, label ->
|
||||||
|
if (enabled != null) {
|
||||||
|
TextFieldOutlined(
|
||||||
|
value = value,
|
||||||
|
onValueChange = onValueChange,
|
||||||
|
modifier = modifier,
|
||||||
|
enabled = enabled,
|
||||||
|
label = label,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
TextFieldOutlined(
|
||||||
|
value = value,
|
||||||
|
onValueChange = onValueChange,
|
||||||
|
modifier = modifier,
|
||||||
|
label = label,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
TextFieldTestData(
|
||||||
|
name = "PasswordTextFieldOutlined",
|
||||||
|
content = { value, onValueChange, modifier, enabled, label ->
|
||||||
|
if (enabled != null) {
|
||||||
|
PasswordTextFieldOutlined(
|
||||||
|
value = value,
|
||||||
|
onValueChange = onValueChange,
|
||||||
|
modifier = modifier,
|
||||||
|
enabled = enabled,
|
||||||
|
label = label,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
PasswordTextFieldOutlined(
|
||||||
|
value = value,
|
||||||
|
onValueChange = onValueChange,
|
||||||
|
modifier = modifier,
|
||||||
|
label = label,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
3
core/ui/compose/testing/README.md
Normal file
3
core/ui/compose/testing/README.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
## Core - UI - Compose - Testing
|
||||||
|
|
||||||
|
Uses [`:core:ui:compose:theme`](../theme/README.md)
|
14
core/ui/compose/testing/build.gradle.kts
Normal file
14
core/ui/compose/testing/build.gradle.kts
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
plugins {
|
||||||
|
id(ThunderbirdPlugins.Library.androidCompose)
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "app.k9mail.core.ui.compose.testing"
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(projects.core.ui.compose.theme)
|
||||||
|
implementation(libs.androidx.compose.material)
|
||||||
|
|
||||||
|
implementation(libs.bundles.shared.jvm.test.compose)
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package app.k9mail.core.ui.compose.testing
|
||||||
|
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.compose.ui.test.junit4.ComposeContentTestRule
|
||||||
|
import androidx.compose.ui.test.junit4.createComposeRule
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.robolectric.RobolectricTestRunner
|
||||||
|
import org.robolectric.RuntimeEnvironment
|
||||||
|
|
||||||
|
@RunWith(RobolectricTestRunner::class)
|
||||||
|
open class ComposeTest {
|
||||||
|
|
||||||
|
@get:Rule
|
||||||
|
val composeTestRule = createComposeRule()
|
||||||
|
|
||||||
|
fun getString(@StringRes resourceId: Int): String = RuntimeEnvironment.getApplication().getString(resourceId)
|
||||||
|
|
||||||
|
fun runComposeTest(testContent: ComposeContentTestRule.() -> Unit): Unit = with(composeTestRule) {
|
||||||
|
testContent()
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,6 +21,7 @@ androidxDrawerLayout = "1.1.1"
|
||||||
androidxTransition = "1.4.1"
|
androidxTransition = "1.4.1"
|
||||||
androidxComposeCompiler = "1.4.1"
|
androidxComposeCompiler = "1.4.1"
|
||||||
androidxComposeBom = "2023.01.00"
|
androidxComposeBom = "2023.01.00"
|
||||||
|
androidxComposeMaterial = "1.3.1"
|
||||||
fastAdapter = "5.7.0"
|
fastAdapter = "5.7.0"
|
||||||
preferencesFix = "1.1.0"
|
preferencesFix = "1.1.0"
|
||||||
timber = "5.0.1"
|
timber = "5.0.1"
|
||||||
|
@ -80,7 +81,8 @@ androidx-compose-ui-test-manifest = { module = "androidx.compose.ui:ui-test-mani
|
||||||
androidx-compose-ui-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4" }
|
androidx-compose-ui-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4" }
|
||||||
androidx-compose-activity = "androidx.activity:activity-compose:1.6.1"
|
androidx-compose-activity = "androidx.activity:activity-compose:1.6.1"
|
||||||
androidx-compose-lifecycle-viewmodel = "androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1"
|
androidx-compose-lifecycle-viewmodel = "androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1"
|
||||||
androidx-compose-material = "androidx.compose.material:material:1.3.1"
|
androidx-compose-material = { module = "androidx.compose.material:material", version.ref = "androidxComposeMaterial" }
|
||||||
|
androidx-compose-material-icons-extended = { module = "androidx.compose.material:material-icons-extended", version.ref = "androidxComposeMaterial" }
|
||||||
androidx-test-core = "androidx.test:core:1.5.0"
|
androidx-test-core = "androidx.test:core:1.5.0"
|
||||||
androidx-test-ext-junit-ktx = "androidx.test.ext:junit-ktx:1.1.5"
|
androidx-test-ext-junit-ktx = "androidx.test.ext:junit-ktx:1.1.5"
|
||||||
androidx-test-espresso-core = "androidx.test.espresso:espresso-core:3.5.1"
|
androidx-test-espresso-core = "androidx.test.espresso:espresso-core:3.5.1"
|
||||||
|
|
|
@ -49,6 +49,7 @@ include(
|
||||||
":core:ui:compose:common",
|
":core:ui:compose:common",
|
||||||
":core:ui:compose:designsystem",
|
":core:ui:compose:designsystem",
|
||||||
":core:ui:compose:theme",
|
":core:ui:compose:theme",
|
||||||
|
":core:ui:compose:testing",
|
||||||
)
|
)
|
||||||
|
|
||||||
include(
|
include(
|
||||||
|
|
Loading…
Reference in a new issue