mirror of
https://github.com/bitwarden/android.git
synced 2024-11-21 17:05:44 +03:00
Add BitwardenStepper component (#226)
This commit is contained in:
parent
0185456dca
commit
bcffbe6fce
8 changed files with 323 additions and 144 deletions
|
@ -0,0 +1,73 @@
|
|||
package com.x8bit.bitwarden.ui.platform.components
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.IconResource
|
||||
|
||||
/**
|
||||
* Displays a stepper that allows the user to increment and decrement an int value.
|
||||
*
|
||||
* @param label Label for the stepper.
|
||||
* @param value Value to display. Null will display nothing. Will be clamped to [range] before
|
||||
* display.
|
||||
* @param onValueChange callback invoked when the user increments or decrements the count. Note
|
||||
* that this will not be called if the attempts to move value outside of [range].
|
||||
* @param modifier Modifier.
|
||||
* @param range Range of valid values.
|
||||
* @param isIncrementEnabled whether or not the increment button should be enabled.
|
||||
* @param isDecrementEnabled whether or not the decrement button should be enabled.
|
||||
*/
|
||||
@Composable
|
||||
fun BitwardenStepper(
|
||||
label: String,
|
||||
value: Int?,
|
||||
onValueChange: (Int) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
range: ClosedRange<Int> = 1..Int.MAX_VALUE,
|
||||
isIncrementEnabled: Boolean = true,
|
||||
isDecrementEnabled: Boolean = true,
|
||||
) {
|
||||
val clampedValue = value?.coerceIn(range)
|
||||
if (clampedValue != value && clampedValue != null) {
|
||||
onValueChange(clampedValue)
|
||||
}
|
||||
BitwardenReadOnlyTextFieldWithActions(
|
||||
label = label,
|
||||
// we use a space instead of empty string to make sure label is shown small and above
|
||||
// the input
|
||||
value = clampedValue
|
||||
?.toString()
|
||||
?: " ",
|
||||
actions = {
|
||||
BitwardenIconButtonWithResource(
|
||||
iconRes = IconResource(
|
||||
iconPainter = painterResource(id = R.drawable.ic_minus),
|
||||
contentDescription = "\u2212",
|
||||
),
|
||||
onClick = {
|
||||
val decrementedValue = ((value ?: 0) - 1).coerceIn(range)
|
||||
if (decrementedValue != value) {
|
||||
onValueChange(decrementedValue)
|
||||
}
|
||||
},
|
||||
isEnabled = isDecrementEnabled,
|
||||
)
|
||||
BitwardenIconButtonWithResource(
|
||||
iconRes = IconResource(
|
||||
iconPainter = painterResource(id = R.drawable.ic_plus),
|
||||
contentDescription = "+",
|
||||
),
|
||||
onClick = {
|
||||
val incrementedValue = ((value ?: 0) + 1).coerceIn(range)
|
||||
if (incrementedValue != value) {
|
||||
onValueChange(incrementedValue)
|
||||
}
|
||||
},
|
||||
isEnabled = isIncrementEnabled,
|
||||
)
|
||||
},
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
|
@ -50,10 +50,15 @@ import com.x8bit.bitwarden.ui.platform.components.BitwardenMultiSelectButton
|
|||
import com.x8bit.bitwarden.ui.platform.components.BitwardenOverflowActionItem
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenReadOnlyTextFieldWithActions
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenScaffold
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenStepper
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenTextField
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenWideSwitch
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.IconResource
|
||||
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
||||
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Passcode.PasscodeType.Passphrase.Companion.PASSPHRASE_MAX_NUMBER_OF_WORDS
|
||||
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Passcode.PasscodeType.Passphrase.Companion.PASSPHRASE_MIN_NUMBER_OF_WORDS
|
||||
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Passcode.PasscodeType.Password.Companion.PASSWORD_COUNTER_MAX
|
||||
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Passcode.PasscodeType.Password.Companion.PASSWORD_COUNTER_MIN
|
||||
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Passcode.PasscodeType.Password.Companion.PASSWORD_LENGTH_SLIDER_MAX
|
||||
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Passcode.PasscodeType.Password.Companion.PASSWORD_LENGTH_SLIDER_MIN
|
||||
|
||||
|
@ -472,29 +477,11 @@ private fun PasswordMinNumbersCounterItem(
|
|||
minNumbers: Int,
|
||||
onPasswordMinNumbersCounterChange: (Int) -> Unit,
|
||||
) {
|
||||
BitwardenReadOnlyTextFieldWithActions(
|
||||
BitwardenStepper(
|
||||
label = stringResource(id = R.string.min_numbers),
|
||||
value = minNumbers.toString(),
|
||||
actions = {
|
||||
BitwardenIconButtonWithResource(
|
||||
iconRes = IconResource(
|
||||
iconPainter = painterResource(id = R.drawable.ic_minus),
|
||||
contentDescription = "\u2212",
|
||||
),
|
||||
onClick = {
|
||||
onPasswordMinNumbersCounterChange(minNumbers - 1)
|
||||
},
|
||||
)
|
||||
BitwardenIconButtonWithResource(
|
||||
iconRes = IconResource(
|
||||
iconPainter = painterResource(id = R.drawable.ic_plus),
|
||||
contentDescription = "+",
|
||||
),
|
||||
onClick = {
|
||||
onPasswordMinNumbersCounterChange(minNumbers + 1)
|
||||
},
|
||||
)
|
||||
},
|
||||
value = minNumbers,
|
||||
range = PASSWORD_COUNTER_MIN..PASSWORD_COUNTER_MAX,
|
||||
onValueChange = onPasswordMinNumbersCounterChange,
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
)
|
||||
}
|
||||
|
@ -504,29 +491,11 @@ private fun PasswordMinSpecialCharactersCounterItem(
|
|||
minSpecial: Int,
|
||||
onPasswordMinSpecialCharactersChange: (Int) -> Unit,
|
||||
) {
|
||||
BitwardenReadOnlyTextFieldWithActions(
|
||||
BitwardenStepper(
|
||||
label = stringResource(id = R.string.min_special),
|
||||
value = minSpecial.toString(),
|
||||
actions = {
|
||||
BitwardenIconButtonWithResource(
|
||||
iconRes = IconResource(
|
||||
iconPainter = painterResource(id = R.drawable.ic_minus),
|
||||
contentDescription = "\u2212",
|
||||
),
|
||||
onClick = {
|
||||
onPasswordMinSpecialCharactersChange(minSpecial - 1)
|
||||
},
|
||||
)
|
||||
BitwardenIconButtonWithResource(
|
||||
iconRes = IconResource(
|
||||
iconPainter = painterResource(id = R.drawable.ic_plus),
|
||||
contentDescription = "+",
|
||||
),
|
||||
onClick = {
|
||||
onPasswordMinSpecialCharactersChange(minSpecial + 1)
|
||||
},
|
||||
)
|
||||
},
|
||||
value = minSpecial,
|
||||
range = PASSWORD_COUNTER_MIN..PASSWORD_COUNTER_MAX,
|
||||
onValueChange = onPasswordMinSpecialCharactersChange,
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
)
|
||||
}
|
||||
|
@ -586,29 +555,11 @@ private fun PassphraseNumWordsCounterItem(
|
|||
numWords: Int,
|
||||
onPassphraseNumWordsCounterChange: (Int) -> Unit,
|
||||
) {
|
||||
BitwardenReadOnlyTextFieldWithActions(
|
||||
BitwardenStepper(
|
||||
label = stringResource(id = R.string.number_of_words),
|
||||
value = numWords.toString(),
|
||||
actions = {
|
||||
BitwardenIconButtonWithResource(
|
||||
iconRes = IconResource(
|
||||
iconPainter = painterResource(id = R.drawable.ic_minus),
|
||||
contentDescription = "\u2212",
|
||||
),
|
||||
onClick = {
|
||||
onPassphraseNumWordsCounterChange(numWords - 1)
|
||||
},
|
||||
)
|
||||
BitwardenIconButtonWithResource(
|
||||
iconRes = IconResource(
|
||||
iconPainter = painterResource(id = R.drawable.ic_plus),
|
||||
contentDescription = "+",
|
||||
),
|
||||
onClick = {
|
||||
onPassphraseNumWordsCounterChange(numWords + 1)
|
||||
},
|
||||
)
|
||||
},
|
||||
value = numWords,
|
||||
range = PASSPHRASE_MIN_NUMBER_OF_WORDS..PASSPHRASE_MAX_NUMBER_OF_WORDS,
|
||||
onValueChange = onPassphraseNumWordsCounterChange,
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -9,11 +9,7 @@ import com.x8bit.bitwarden.R
|
|||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
||||
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Passcode
|
||||
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Passcode.PasscodeType.Passphrase
|
||||
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Passcode.PasscodeType.Passphrase.Companion.PASSPHRASE_MAX_NUMBER_OF_WORDS
|
||||
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Passcode.PasscodeType.Passphrase.Companion.PASSPHRASE_MIN_NUMBER_OF_WORDS
|
||||
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Passcode.PasscodeType.Password
|
||||
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Passcode.PasscodeType.Password.Companion.PASSWORD_COUNTER_MAX
|
||||
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Passcode.PasscodeType.Password.Companion.PASSWORD_COUNTER_MIN
|
||||
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Passcode.PasscodeTypeOption
|
||||
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Username
|
||||
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Username.UsernameType.ForwardedEmailAlias.ServiceType.AnonAddy
|
||||
|
@ -24,8 +20,6 @@ import kotlinx.coroutines.flow.onEach
|
|||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import java.lang.Integer.max
|
||||
import java.lang.Integer.min
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val KEY_STATE = "state"
|
||||
|
@ -266,24 +260,16 @@ class GeneratorViewModel @Inject constructor(
|
|||
private fun handleMinNumbersChange(
|
||||
action: GeneratorAction.MainType.Passcode.PasscodeType.Password.MinNumbersCounterChange,
|
||||
) {
|
||||
val adjustedMinNumbers = action
|
||||
.minNumbers
|
||||
.coerceIn(PASSWORD_COUNTER_MIN, PASSWORD_COUNTER_MAX)
|
||||
|
||||
updatePasswordType { currentPasswordType ->
|
||||
currentPasswordType.copy(minNumbers = adjustedMinNumbers)
|
||||
currentPasswordType.copy(minNumbers = action.minNumbers)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleMinSpecialChange(
|
||||
action: GeneratorAction.MainType.Passcode.PasscodeType.Password.MinSpecialCharactersChange,
|
||||
) {
|
||||
val adjustedMinSpecial = action
|
||||
.minSpecial
|
||||
.coerceIn(PASSWORD_COUNTER_MIN, PASSWORD_COUNTER_MAX)
|
||||
|
||||
updatePasswordType { currentPasswordType ->
|
||||
currentPasswordType.copy(minSpecial = adjustedMinSpecial)
|
||||
currentPasswordType.copy(minSpecial = action.minSpecial)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -352,11 +338,7 @@ class GeneratorViewModel @Inject constructor(
|
|||
action: GeneratorAction.MainType.Passcode.PasscodeType.Passphrase.NumWordsCounterChange,
|
||||
) {
|
||||
updatePassphraseType { passphraseType ->
|
||||
val newNumWords = max(
|
||||
PASSPHRASE_MIN_NUMBER_OF_WORDS,
|
||||
min(PASSPHRASE_MAX_NUMBER_OF_WORDS, action.numWords),
|
||||
)
|
||||
passphraseType.copy(numWords = newNumWords)
|
||||
passphraseType.copy(numWords = action.numWords)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -43,19 +43,16 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenFilledTonalButton
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenIconButtonWithResource
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenListHeaderText
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenPasswordField
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenReadOnlyTextFieldWithActions
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenScaffold
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenSegmentedButton
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenStepper
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenTextButton
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenTextField
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenTopAppBar
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenWideSwitch
|
||||
import com.x8bit.bitwarden.ui.platform.components.SegmentedButtonState
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.IconResource
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.NewSendAction.MaxAccessCountChange
|
||||
|
||||
/**
|
||||
* Displays new send UX.
|
||||
|
@ -211,11 +208,8 @@ fun NewSendScreen(
|
|||
Spacer(modifier = Modifier.height(16.dp))
|
||||
NewSendOptions(
|
||||
state = state,
|
||||
onIncrementMaxAccessCountClick = remember(viewModel) {
|
||||
{ viewModel.trySendAction(MaxAccessCountChange(it)) }
|
||||
},
|
||||
onDecrementMaxAccessCountClick = remember(viewModel) {
|
||||
{ viewModel.trySendAction(MaxAccessCountChange(it)) }
|
||||
onMaxAccessCountChange = remember(viewModel) {
|
||||
{ viewModel.trySendAction(NewSendAction.MaxAccessCountChange(it)) }
|
||||
},
|
||||
onPasswordChange = remember(viewModel) {
|
||||
{ viewModel.trySendAction(NewSendAction.PasswordChange(it)) }
|
||||
|
@ -238,8 +232,7 @@ fun NewSendScreen(
|
|||
* Displays a collapsable set of new send options.
|
||||
*
|
||||
* @param state state.
|
||||
* @param onIncrementMaxAccessCountClick called when increment max access count is clicked.
|
||||
* @param onDecrementMaxAccessCountClick called when decrement max access count is clicked.
|
||||
* @param onMaxAccessCountChange called when max access count changes.
|
||||
* @param onPasswordChange called when the password changes.
|
||||
* @param onNoteChange called when the notes changes.
|
||||
* @param onHideEmailChecked called when hide email is checked.
|
||||
|
@ -249,8 +242,7 @@ fun NewSendScreen(
|
|||
@Composable
|
||||
private fun NewSendOptions(
|
||||
state: NewSendState,
|
||||
onIncrementMaxAccessCountClick: (Int) -> Unit,
|
||||
onDecrementMaxAccessCountClick: (Int) -> Unit,
|
||||
onMaxAccessCountChange: (Int) -> Unit,
|
||||
onPasswordChange: (String) -> Unit,
|
||||
onNoteChange: (String) -> Unit,
|
||||
onHideEmailChecked: (Boolean) -> Unit,
|
||||
|
@ -300,33 +292,13 @@ private fun NewSendOptions(
|
|||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
BitwardenReadOnlyTextFieldWithActions(
|
||||
label = stringResource(R.string.maximum_access_count),
|
||||
// we use a space instead of empty string to make sure label is shown small and
|
||||
// above the input
|
||||
value = state.maxAccessCount?.toString() ?: " ",
|
||||
actions = {
|
||||
BitwardenIconButtonWithResource(
|
||||
iconRes = IconResource(
|
||||
iconPainter = painterResource(id = R.drawable.ic_minus),
|
||||
contentDescription = "\u2212",
|
||||
),
|
||||
onClick = {
|
||||
onIncrementMaxAccessCountClick.invoke((state.maxAccessCount ?: 0) - 1)
|
||||
},
|
||||
isEnabled = state.maxAccessCount != null,
|
||||
)
|
||||
BitwardenIconButtonWithResource(
|
||||
iconRes = IconResource(
|
||||
iconPainter = painterResource(id = R.drawable.ic_plus),
|
||||
contentDescription = "+",
|
||||
),
|
||||
onClick = {
|
||||
onDecrementMaxAccessCountClick.invoke((state.maxAccessCount ?: 0) + 1)
|
||||
},
|
||||
)
|
||||
},
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
BitwardenStepper(
|
||||
label = stringResource(id = R.string.maximum_access_count),
|
||||
value = state.maxAccessCount,
|
||||
onValueChange = onMaxAccessCountChange,
|
||||
isDecrementEnabled = state.maxAccessCount != null,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Text(
|
||||
|
|
|
@ -10,7 +10,6 @@ import kotlinx.coroutines.flow.onEach
|
|||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import javax.inject.Inject
|
||||
import kotlin.math.max
|
||||
|
||||
private const val KEY_STATE = "state"
|
||||
|
||||
|
@ -127,7 +126,7 @@ class NewSendViewModel @Inject constructor(
|
|||
|
||||
private fun handleMaxAccessCountChange(action: NewSendAction.MaxAccessCountChange) {
|
||||
mutableStateFlow.update {
|
||||
it.copy(maxAccessCount = max(1, action.newValue))
|
||||
it.copy(maxAccessCount = action.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -239,9 +238,9 @@ sealed class NewSendAction {
|
|||
data class HideByDefaultToggle(val isChecked: Boolean) : NewSendAction()
|
||||
|
||||
/**
|
||||
* User incremented or decremented the max access count.
|
||||
* User incremented the max access count.
|
||||
*/
|
||||
data class MaxAccessCountChange(val newValue: Int) : NewSendAction()
|
||||
data class MaxAccessCountChange(val value: Int) : NewSendAction()
|
||||
|
||||
/**
|
||||
* User toggled the "hide my email" toggle.
|
||||
|
|
|
@ -32,6 +32,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import org.junit.Test
|
||||
|
||||
@Suppress("LargeClass")
|
||||
class GeneratorScreenTest : BaseComposeTest() {
|
||||
private val mutableStateFlow = MutableStateFlow(
|
||||
GeneratorState(
|
||||
|
@ -409,6 +410,70 @@ class GeneratorScreenTest : BaseComposeTest() {
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `in Passcode_Password state, decrementing the minimum numbers counter below 0 should do nothing`() {
|
||||
val initialMinNumbers = 0
|
||||
updateState(
|
||||
GeneratorState(
|
||||
generatedText = "Placeholder",
|
||||
selectedType = GeneratorState
|
||||
.MainType
|
||||
.Passcode(
|
||||
GeneratorState
|
||||
.MainType
|
||||
.Passcode
|
||||
.PasscodeType
|
||||
.Password(minNumbers = initialMinNumbers),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
composeTestRule.setContent {
|
||||
GeneratorScreen(viewModel = viewModel)
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithContentDescription("Minimum numbers, $initialMinNumbers")
|
||||
.onChildren()
|
||||
.filterToOne(hasContentDescription("\u2212"))
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
|
||||
verify(exactly = 0) { viewModel.trySendAction(any()) }
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `in Passcode_Password state, incrementing the minimum numbers counter above 5 should do nothing`() {
|
||||
val initialMinNumbers = 5
|
||||
updateState(
|
||||
GeneratorState(
|
||||
generatedText = "Placeholder",
|
||||
selectedType = GeneratorState
|
||||
.MainType
|
||||
.Passcode(
|
||||
GeneratorState
|
||||
.MainType
|
||||
.Passcode
|
||||
.PasscodeType
|
||||
.Password(minNumbers = initialMinNumbers),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
composeTestRule.setContent {
|
||||
GeneratorScreen(viewModel = viewModel)
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithContentDescription("Minimum numbers, $initialMinNumbers")
|
||||
.onChildren()
|
||||
.filterToOne(hasContentDescription("+"))
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
|
||||
verify(exactly = 0) { viewModel.trySendAction(any()) }
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `in Passcode_Password state, decrementing the minimum special characters counter should send MinSpecialCharactersChange action`() {
|
||||
|
@ -485,6 +550,70 @@ class GeneratorScreenTest : BaseComposeTest() {
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `in Passcode_Password state, decrementing the minimum special characters below 0 should do nothing`() {
|
||||
val initialSpecialChars = 0
|
||||
updateState(
|
||||
GeneratorState(
|
||||
generatedText = "Placeholder",
|
||||
selectedType = GeneratorState
|
||||
.MainType
|
||||
.Passcode(
|
||||
GeneratorState
|
||||
.MainType
|
||||
.Passcode
|
||||
.PasscodeType
|
||||
.Password(minSpecial = initialSpecialChars),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
composeTestRule.setContent {
|
||||
GeneratorScreen(viewModel = viewModel)
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithContentDescription("Minimum special, $initialSpecialChars")
|
||||
.onChildren()
|
||||
.filterToOne(hasContentDescription("\u2212"))
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
|
||||
verify(exactly = 0) { viewModel.trySendAction(any()) }
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `in Passcode_Password state, decrementing the minimum special characters above 5 should do nothing`() {
|
||||
val initialSpecialChars = 5
|
||||
updateState(
|
||||
GeneratorState(
|
||||
generatedText = "Placeholder",
|
||||
selectedType = GeneratorState
|
||||
.MainType
|
||||
.Passcode(
|
||||
GeneratorState
|
||||
.MainType
|
||||
.Passcode
|
||||
.PasscodeType
|
||||
.Password(minSpecial = initialSpecialChars),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
composeTestRule.setContent {
|
||||
GeneratorScreen(viewModel = viewModel)
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithContentDescription("Minimum special, $initialSpecialChars")
|
||||
.onChildren()
|
||||
.filterToOne(hasContentDescription("+"))
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
|
||||
verify(exactly = 0) { viewModel.trySendAction(any()) }
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `in Passcode_Password state, toggling the use avoid ambiguous characters toggle should send ToggleSpecialCharactersChange action`() {
|
||||
|
@ -517,7 +646,7 @@ class GeneratorScreenTest : BaseComposeTest() {
|
|||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `in Passcode_Passphrase state, decrementing number of words should send NumWordsCounterChange action with decremented value`() {
|
||||
val initialNumWords = 3
|
||||
val initialNumWords = 4
|
||||
updateState(
|
||||
GeneratorState(
|
||||
generatedText = "Placeholder",
|
||||
|
@ -528,7 +657,7 @@ class GeneratorScreenTest : BaseComposeTest() {
|
|||
.MainType
|
||||
.Passcode
|
||||
.PasscodeType
|
||||
.Passphrase(),
|
||||
.Passphrase(numWords = initialNumWords),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
@ -539,7 +668,7 @@ class GeneratorScreenTest : BaseComposeTest() {
|
|||
|
||||
// Unicode for "minus" used for content description
|
||||
composeTestRule
|
||||
.onNodeWithContentDescription("Number of words, 3")
|
||||
.onNodeWithContentDescription("Number of words, $initialNumWords")
|
||||
.onChildren()
|
||||
.filterToOne(hasContentDescription("\u2212"))
|
||||
.performScrollTo()
|
||||
|
@ -554,6 +683,70 @@ class GeneratorScreenTest : BaseComposeTest() {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `in Passcode_Passphrase state, decrementing number of words under 3 should do nothing`() {
|
||||
val initialNumWords = 3
|
||||
updateState(
|
||||
GeneratorState(
|
||||
generatedText = "Placeholder",
|
||||
selectedType = GeneratorState
|
||||
.MainType
|
||||
.Passcode(
|
||||
GeneratorState
|
||||
.MainType
|
||||
.Passcode
|
||||
.PasscodeType
|
||||
.Passphrase(numWords = initialNumWords),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
composeTestRule.setContent {
|
||||
GeneratorScreen(viewModel = viewModel)
|
||||
}
|
||||
|
||||
// Unicode for "minus" used for content description
|
||||
composeTestRule
|
||||
.onNodeWithContentDescription("Number of words, $initialNumWords")
|
||||
.onChildren()
|
||||
.filterToOne(hasContentDescription("\u2212"))
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
verify(exactly = 0) { viewModel.trySendAction(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `in Passcode_Passphrase state, incrementing number of words over 20 should do nothing`() {
|
||||
val initialNumWords = 20
|
||||
updateState(
|
||||
GeneratorState(
|
||||
generatedText = "Placeholder",
|
||||
selectedType = GeneratorState
|
||||
.MainType
|
||||
.Passcode(
|
||||
GeneratorState
|
||||
.MainType
|
||||
.Passcode
|
||||
.PasscodeType
|
||||
.Passphrase(numWords = initialNumWords),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
composeTestRule.setContent {
|
||||
GeneratorScreen(viewModel = viewModel)
|
||||
}
|
||||
|
||||
// Unicode for "minus" used for content description
|
||||
composeTestRule
|
||||
.onNodeWithContentDescription("Number of words, $initialNumWords")
|
||||
.onChildren()
|
||||
.filterToOne(hasContentDescription("+"))
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
verify(exactly = 0) { viewModel.trySendAction(any()) }
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `in Passcode_Passphrase state, incrementing number of words should send NumWordsCounterChange action with incremented value`() {
|
||||
|
|
|
@ -248,6 +248,25 @@ class NewSendScreenTest : BaseComposeTest() {
|
|||
verify { viewModel.trySendAction(NewSendAction.MaxAccessCountChange(2)) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `max access count decrement when set to 1 should do nothing`() =
|
||||
runTest {
|
||||
mutableStateFlow.update {
|
||||
it.copy(maxAccessCount = 1)
|
||||
}
|
||||
// Expand options section:
|
||||
composeTestRule
|
||||
.onNodeWithText("Options")
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithContentDescription("\u2212")
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
verify(exactly = 0) { viewModel.trySendAction(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on max access count increment should send MaxAccessCountChange`() = runTest {
|
||||
// Expand options section:
|
||||
|
|
|
@ -87,16 +87,6 @@ class NewSendViewModelTest : BaseViewModelTest() {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `MaxAccessCountChang below 1 should keep maxAccessCount at 1`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(DEFAULT_STATE, awaitItem())
|
||||
viewModel.trySendAction(NewSendAction.MaxAccessCountChange(0))
|
||||
assertEquals(DEFAULT_STATE.copy(maxAccessCount = 1), awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `TextChange should update text input`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
|
|
Loading…
Reference in a new issue