From bcffbe6fcebfc61aea0f0829708907836e2a8f95 Mon Sep 17 00:00:00 2001 From: Andrew Haisting <142518658+ahaisting-livefront@users.noreply.github.com> Date: Wed, 8 Nov 2023 12:12:17 -0600 Subject: [PATCH] Add BitwardenStepper component (#226) --- .../platform/components/BitwardenStepper.kt | 73 +++++++ .../feature/generator/GeneratorScreen.kt | 83 ++------ .../feature/generator/GeneratorViewModel.kt | 24 +-- .../ui/tools/feature/send/NewSendScreen.kt | 52 ++--- .../ui/tools/feature/send/NewSendViewModel.kt | 7 +- .../feature/generator/GeneratorScreenTest.kt | 199 +++++++++++++++++- .../tools/feature/send/NewSendScreenTest.kt | 19 ++ .../feature/send/NewSendViewModelTest.kt | 10 - 8 files changed, 323 insertions(+), 144 deletions(-) create mode 100644 app/src/main/java/com/x8bit/bitwarden/ui/platform/components/BitwardenStepper.kt diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/BitwardenStepper.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/BitwardenStepper.kt new file mode 100644 index 000000000..86348b7ea --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/BitwardenStepper.kt @@ -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 = 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, + ) +} diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorScreen.kt index d0e6284ec..0eb104fb2 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorScreen.kt @@ -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), ) } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorViewModel.kt index 26984e9df..8f849d323 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorViewModel.kt @@ -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) } } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/NewSendScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/NewSendScreen.kt index 9b67ffc95..251a01a5c 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/NewSendScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/NewSendScreen.kt @@ -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( diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/NewSendViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/NewSendViewModel.kt index e49c3fc7c..3902b85f0 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/NewSendViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/NewSendViewModel.kt @@ -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. diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorScreenTest.kt index a2aeaaf64..2b3a22d9e 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/generator/GeneratorScreenTest.kt @@ -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`() { diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/NewSendScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/NewSendScreenTest.kt index 84f5fbb25..605bc6856 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/NewSendScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/NewSendScreenTest.kt @@ -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: diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/NewSendViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/NewSendViewModelTest.kt index 2d7963aa6..8c131066a 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/NewSendViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/NewSendViewModelTest.kt @@ -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()