mirror of
https://github.com/bitwarden/android.git
synced 2026-10-21 18:42:48 -05:00
[PM-42349] fix: use fixed-length password mask (#7293)
This commit is contained in:
1 parent
d59fcfe088
commit
280b5edaa6
5 files changed
+203
-8
No files matched your search
+8
-4
@@ -972,6 +972,10 @@ class VaultItemScreenTest : BitwardenComposeTest() {
|
||||
)
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll(textField.name)
|
||||
.assertTextEquals(textField.name, "••••••••")
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll(textField.name)
|
||||
.onChildren()
|
||||
@@ -2942,7 +2946,7 @@ class VaultItemScreenTest : BitwardenComposeTest() {
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll("Number")
|
||||
.assertTextEquals("Number", "••••••")
|
||||
.assertTextEquals("Number", "••••••••")
|
||||
.assertIsEnabled()
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll("Number")
|
||||
@@ -2982,7 +2986,7 @@ class VaultItemScreenTest : BitwardenComposeTest() {
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll("Number")
|
||||
.assertTextEquals("Number", "••••••")
|
||||
.assertTextEquals("Number", "••••••••")
|
||||
.assertIsEnabled()
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll("Number")
|
||||
@@ -3098,7 +3102,7 @@ class VaultItemScreenTest : BitwardenComposeTest() {
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll("Security code")
|
||||
.assertTextEquals("Security code", "•••")
|
||||
.assertTextEquals("Security code", "••••••••")
|
||||
.assertIsEnabled()
|
||||
composeTestRule
|
||||
.onNodeWithContentDescription("Copy security code")
|
||||
@@ -3135,7 +3139,7 @@ class VaultItemScreenTest : BitwardenComposeTest() {
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll("Security code")
|
||||
.assertTextEquals("Security code", "•••")
|
||||
.assertTextEquals("Security code", "••••••••")
|
||||
.assertIsEnabled()
|
||||
composeTestRule
|
||||
.onNodeWithContentDescription("Copy security code")
|
||||
|
||||
+2
-2
@@ -8,7 +8,6 @@ import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalTextToolbar
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.bitwarden.ui.platform.base.util.cardStyle
|
||||
@@ -16,6 +15,7 @@ import com.bitwarden.ui.platform.base.util.nullableTestTag
|
||||
import com.bitwarden.ui.platform.components.field.color.bitwardenTextFieldColors
|
||||
import com.bitwarden.ui.platform.components.field.toolbar.BitwardenEmptyTextToolbar
|
||||
import com.bitwarden.ui.platform.components.model.CardStyle
|
||||
import com.bitwarden.ui.platform.components.util.passwordVisualTransformation
|
||||
import com.bitwarden.ui.platform.theme.BitwardenTheme
|
||||
|
||||
/**
|
||||
@@ -44,7 +44,7 @@ fun BitwardenHiddenPasswordField(
|
||||
label = label?.let { { Text(text = it) } },
|
||||
value = value,
|
||||
onValueChange = { },
|
||||
visualTransformation = PasswordVisualTransformation(),
|
||||
visualTransformation = passwordVisualTransformation(staticCharacterCount = 8),
|
||||
singleLine = true,
|
||||
enabled = false,
|
||||
readOnly = true,
|
||||
|
||||
+5
-2
@@ -38,7 +38,6 @@ import androidx.compose.ui.platform.TextToolbar
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
@@ -58,6 +57,7 @@ import com.bitwarden.ui.platform.components.model.CardStyle
|
||||
import com.bitwarden.ui.platform.components.row.BitwardenRowOfActions
|
||||
import com.bitwarden.ui.platform.components.support.BitwardenSupportingContent
|
||||
import com.bitwarden.ui.platform.components.util.nonLetterColorVisualTransformation
|
||||
import com.bitwarden.ui.platform.components.util.passwordVisualTransformation
|
||||
import com.bitwarden.ui.platform.resource.BitwardenDrawable
|
||||
import com.bitwarden.ui.platform.resource.BitwardenString
|
||||
import com.bitwarden.ui.platform.theme.BitwardenTheme
|
||||
@@ -190,7 +190,10 @@ fun BitwardenPasswordField(
|
||||
}
|
||||
},
|
||||
visualTransformation = when {
|
||||
!showPassword -> PasswordVisualTransformation()
|
||||
!showPassword -> passwordVisualTransformation(
|
||||
staticCharacterCount = 8.takeIf { readOnly },
|
||||
)
|
||||
|
||||
readOnly -> nonLetterColorVisualTransformation()
|
||||
else -> VisualTransformation.None
|
||||
},
|
||||
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package com.bitwarden.ui.platform.components.util
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.input.OffsetMapping
|
||||
import androidx.compose.ui.text.input.TransformedText
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
|
||||
/**
|
||||
* Returns a [VisualTransformation] that masks text with [mask]. When [staticCharacterCount] is
|
||||
* non-null, the transformed text always contains that number of mask characters.
|
||||
*/
|
||||
@Composable
|
||||
fun passwordVisualTransformation(
|
||||
mask: Char = '\u2022',
|
||||
staticCharacterCount: Int? = null,
|
||||
): VisualTransformation =
|
||||
remember(mask, staticCharacterCount) {
|
||||
BitwardenPasswordVisualTransformation(
|
||||
mask = mask,
|
||||
staticCharacterCount = staticCharacterCount,
|
||||
)
|
||||
}
|
||||
|
||||
private class BitwardenPasswordVisualTransformation(
|
||||
private val mask: Char,
|
||||
private val staticCharacterCount: Int?,
|
||||
) : VisualTransformation {
|
||||
override fun filter(
|
||||
text: AnnotatedString,
|
||||
): TransformedText = TransformedText(
|
||||
AnnotatedString(
|
||||
mask.toString().repeat(n = staticCharacterCount ?: text.text.length),
|
||||
),
|
||||
object : OffsetMapping {
|
||||
override fun originalToTransformed(
|
||||
offset: Int,
|
||||
): Int = staticCharacterCount?.let { offset.coerceAtMost(it) } ?: offset
|
||||
|
||||
override fun transformedToOriginal(
|
||||
offset: Int,
|
||||
): Int = offset.coerceAtMost(text.length)
|
||||
},
|
||||
)
|
||||
}
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
package com.bitwarden.ui.platform.components.field
|
||||
|
||||
import androidx.compose.ui.test.assertTextEquals
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import com.bitwarden.ui.platform.base.BaseComposeTest
|
||||
import com.bitwarden.ui.platform.components.model.CardStyle
|
||||
import com.bitwarden.ui.platform.theme.BitwardenTheme
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
class BitwardenPasswordFieldTest : BaseComposeTest() {
|
||||
|
||||
@Test
|
||||
fun `read only hidden password uses fixed length mask`() {
|
||||
val password = "correct horse battery staple"
|
||||
|
||||
setTestContent {
|
||||
BitwardenTheme {
|
||||
BitwardenPasswordField(
|
||||
label = "Password",
|
||||
value = password,
|
||||
showPassword = false,
|
||||
showPasswordChange = { },
|
||||
onValueChange = { },
|
||||
readOnly = true,
|
||||
cardStyle = CardStyle.Full,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Password")
|
||||
.assertTextEquals("Password", "••••••••")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `read only visible password shows actual value`() {
|
||||
val password = "correct horse battery staple"
|
||||
|
||||
setTestContent {
|
||||
BitwardenTheme {
|
||||
BitwardenPasswordField(
|
||||
label = "Password",
|
||||
value = password,
|
||||
showPassword = true,
|
||||
showPasswordChange = { },
|
||||
onValueChange = { },
|
||||
readOnly = true,
|
||||
cardStyle = CardStyle.Full,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Password")
|
||||
.assertTextEquals("Password", password)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `read only hidden password always uses fixed length mask`() {
|
||||
setTestContent {
|
||||
BitwardenTheme {
|
||||
BitwardenPasswordField(
|
||||
label = "Password",
|
||||
value = "12345",
|
||||
showPassword = false,
|
||||
showPasswordChange = { },
|
||||
onValueChange = { },
|
||||
readOnly = true,
|
||||
cardStyle = CardStyle.Full,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Password")
|
||||
.assertTextEquals("Password", "••••••••")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `editable hidden password preserves value length`() {
|
||||
setTestContent {
|
||||
BitwardenTheme {
|
||||
BitwardenPasswordField(
|
||||
label = "Password",
|
||||
value = "12345",
|
||||
showPassword = false,
|
||||
showPasswordChange = { },
|
||||
onValueChange = { },
|
||||
readOnly = false,
|
||||
cardStyle = CardStyle.Full,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Password")
|
||||
.assertTextEquals("Password", "•••••")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `tapping a masked read only field does not emit the mask`() {
|
||||
val emitted = mutableListOf<String>()
|
||||
|
||||
setTestContent {
|
||||
BitwardenTheme {
|
||||
BitwardenPasswordField(
|
||||
label = "Password",
|
||||
value = "correct horse battery staple",
|
||||
showPassword = false,
|
||||
showPasswordChange = { },
|
||||
onValueChange = { emitted += it },
|
||||
readOnly = true,
|
||||
cardStyle = CardStyle.Full,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithText("Password").performClick()
|
||||
composeTestRule.waitForIdle()
|
||||
|
||||
assertEquals(emptyList<String>(), emitted)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `non interactable hidden password uses fixed length mask`() {
|
||||
setTestContent {
|
||||
BitwardenTheme {
|
||||
BitwardenHiddenPasswordField(
|
||||
label = "Password",
|
||||
value = "correct horse battery staple",
|
||||
cardStyle = CardStyle.Full,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Password")
|
||||
.assertTextEquals("Password", "••••••••")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user