PM-11176: Update generator to use segmented control (#4075)

This commit is contained in:
David Perez 2024-10-14 15:43:17 -05:00 committed by GitHub
parent 2b87cdac9e
commit efbf84238d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 958 additions and 1505 deletions

View file

@ -46,6 +46,7 @@ fun BitwardenSegmentedButton(
) {
options.forEachIndexed { index, option ->
SegmentedButton(
enabled = option.isEnabled,
selected = option.isChecked,
onClick = option.onClick,
colors = bitwardenSegmentedButtonColors(),
@ -74,5 +75,6 @@ data class SegmentedButtonState(
val text: String,
val onClick: () -> Unit,
val isChecked: Boolean,
val isEnabled: Boolean = true,
val testTag: String? = null,
)

View file

@ -20,6 +20,6 @@ fun bitwardenSegmentedButtonColors(): SegmentedButtonColors = SegmentedButtonCol
disabledActiveContentColor = BitwardenTheme.colorScheme.filledButton.foregroundDisabled,
disabledActiveBorderColor = Color.Transparent,
disabledInactiveContainerColor = BitwardenTheme.colorScheme.background.primary,
disabledInactiveContentColor = BitwardenTheme.colorScheme.filledButton.foregroundDisabled,
disabledInactiveContentColor = BitwardenTheme.colorScheme.stroke.divider,
disabledInactiveBorderColor = Color.Transparent,
)

View file

@ -36,6 +36,7 @@ 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.base.util.LivecycleEventEffect
import com.x8bit.bitwarden.ui.platform.base.util.scrolledContainerBottomDivider
import com.x8bit.bitwarden.ui.platform.components.appbar.BitwardenMediumTopAppBar
import com.x8bit.bitwarden.ui.platform.components.appbar.BitwardenTopAppBar
import com.x8bit.bitwarden.ui.platform.components.appbar.action.BitwardenOverflowActionItem
@ -49,7 +50,10 @@ import com.x8bit.bitwarden.ui.platform.components.field.BitwardenTextField
import com.x8bit.bitwarden.ui.platform.components.field.BitwardenTextFieldWithActions
import com.x8bit.bitwarden.ui.platform.components.header.BitwardenListHeaderText
import com.x8bit.bitwarden.ui.platform.components.model.TooltipData
import com.x8bit.bitwarden.ui.platform.components.model.TopAppBarDividerStyle
import com.x8bit.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
import com.x8bit.bitwarden.ui.platform.components.segment.BitwardenSegmentedButton
import com.x8bit.bitwarden.ui.platform.components.segment.SegmentedButtonState
import com.x8bit.bitwarden.ui.platform.components.slider.BitwardenSlider
import com.x8bit.bitwarden.ui.platform.components.snackbar.BitwardenSnackbarHost
import com.x8bit.bitwarden.ui.platform.components.stepper.BitwardenStepper
@ -59,8 +63,8 @@ import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter
import com.x8bit.bitwarden.ui.platform.composition.LocalIntentManager
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
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.Passphrase.Companion.PASSPHRASE_MAX_NUMBER_OF_WORDS
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Passphrase.Companion.PASSPHRASE_MIN_NUMBER_OF_WORDS
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Username.UsernameType.ForwardedEmailAlias.ServiceType
import com.x8bit.bitwarden.ui.tools.feature.generator.GeneratorState.MainType.Username.UsernameType.ForwardedEmailAlias.ServiceTypeOption
import com.x8bit.bitwarden.ui.tools.feature.generator.model.GeneratorMode
@ -129,17 +133,6 @@ fun GeneratorScreen(
{ viewModel.trySendAction(GeneratorAction.MainTypeOptionSelect(it)) }
}
val onPasscodeOptionClicked: (GeneratorState.MainType.Passcode.PasscodeTypeOption) -> Unit =
remember(viewModel) {
{
viewModel.trySendAction(
GeneratorAction.MainType.Passcode.PasscodeTypeOptionSelect(
it,
),
)
}
}
val onUsernameOptionClicked: (GeneratorState.MainType.Username.UsernameTypeOption) -> Unit =
remember(viewModel) {
{
@ -183,9 +176,7 @@ fun GeneratorScreen(
BitwardenScaffold(
topBar = {
when (state.generatorMode) {
is GeneratorMode.Modal.Username,
GeneratorMode.Modal.Password,
-> {
is GeneratorMode.Modal -> {
ModalAppBar(
scrollBehavior = scrollBehavior,
onCloseClick = remember(viewModel) {
@ -212,22 +203,31 @@ fun GeneratorScreen(
},
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
) { innerPadding ->
ScrollContent(
state = state,
onRegenerateClick = onRegenerateClick,
onCopyClick = onCopyClick,
onMainStateOptionClicked = onMainStateOptionClicked,
onPasscodeSubStateOptionClicked = onPasscodeOptionClicked,
onUsernameSubStateOptionClicked = onUsernameOptionClicked,
passwordHandlers = passwordHandlers,
passphraseHandlers = passphraseHandlers,
usernameTypeHandlers = usernameTypeHandlers,
forwardedEmailAliasHandlers = forwardedEmailAliasHandlers,
plusAddressedEmailHandlers = plusAddressedEmailHandlers,
catchAllEmailHandlers = catchAllEmailHandlers,
randomWordHandlers = randomWordHandlers,
modifier = Modifier.padding(innerPadding),
)
Column(modifier = Modifier.padding(innerPadding)) {
if (state.generatorMode == GeneratorMode.Default) {
MainStateOptionsItem(
selectedType = state.selectedType,
passcodePolicyOverride = state.passcodePolicyOverride,
possibleMainStates = state.typeOptions.toImmutableList(),
onMainStateOptionClicked = onMainStateOptionClicked,
modifier = Modifier
.scrolledContainerBottomDivider(topAppBarScrollBehavior = scrollBehavior),
)
}
ScrollContent(
state = state,
onRegenerateClick = onRegenerateClick,
onCopyClick = onCopyClick,
onUsernameSubStateOptionClicked = onUsernameOptionClicked,
passwordHandlers = passwordHandlers,
passphraseHandlers = passphraseHandlers,
usernameTypeHandlers = usernameTypeHandlers,
forwardedEmailAliasHandlers = forwardedEmailAliasHandlers,
plusAddressedEmailHandlers = plusAddressedEmailHandlers,
catchAllEmailHandlers = catchAllEmailHandlers,
randomWordHandlers = randomWordHandlers,
)
}
}
}
@ -242,6 +242,7 @@ private fun DefaultAppBar(
BitwardenMediumTopAppBar(
title = stringResource(id = R.string.generator),
scrollBehavior = scrollBehavior,
dividerStyle = TopAppBarDividerStyle.NONE,
actions = {
BitwardenOverflowActionItem(
menuItemDataList = persistentListOf(
@ -288,8 +289,6 @@ private fun ScrollContent(
state: GeneratorState,
onRegenerateClick: () -> Unit,
onCopyClick: () -> Unit,
onMainStateOptionClicked: (GeneratorState.MainTypeOption) -> Unit,
onPasscodeSubStateOptionClicked: (GeneratorState.MainType.Passcode.PasscodeTypeOption) -> Unit,
onUsernameSubStateOptionClicked: (GeneratorState.MainType.Username.UsernameTypeOption) -> Unit,
passwordHandlers: PasswordHandlers,
passphraseHandlers: PassphraseHandlers,
@ -305,8 +304,8 @@ private fun ScrollContent(
.fillMaxHeight()
.verticalScroll(rememberScrollState()),
) {
if (state.isUnderPolicy) {
Spacer(modifier = Modifier.height(8.dp))
BitwardenInfoCalloutCard(
text = stringResource(id = R.string.password_generator_policy_in_effect),
modifier = Modifier
@ -324,15 +323,6 @@ private fun ScrollContent(
onRegenerateClick = onRegenerateClick,
)
if (state.generatorMode == GeneratorMode.Default) {
Spacer(modifier = Modifier.height(8.dp))
MainStateOptionsItem(
selectedType = state.selectedType,
possibleMainStates = state.typeOptions.toImmutableList(),
onMainStateOptionClicked = onMainStateOptionClicked,
)
}
Spacer(modifier = Modifier.height(16.dp))
BitwardenListHeaderText(
@ -345,13 +335,17 @@ private fun ScrollContent(
Spacer(modifier = Modifier.height(8.dp))
when (val selectedType = state.selectedType) {
is GeneratorState.MainType.Passcode -> {
PasscodeTypeItems(
passcodeState = selectedType,
onSubStateOptionClicked = onPasscodeSubStateOptionClicked,
passwordHandlers = passwordHandlers,
is GeneratorState.MainType.Passphrase -> {
PassphraseTypeContent(
passphraseTypeState = selectedType,
passphraseHandlers = passphraseHandlers,
overridePasswordPolicyRestriction = state.overridePassword,
)
}
is GeneratorState.MainType.Password -> {
PasswordTypeContent(
passwordTypeState = selectedType,
passwordHandlers = passwordHandlers,
)
}
@ -407,92 +401,55 @@ private fun GeneratedStringItem(
@Composable
private fun MainStateOptionsItem(
selectedType: GeneratorState.MainType,
passcodePolicyOverride: GeneratorState.PasscodePolicyOverride?,
possibleMainStates: ImmutableList<GeneratorState.MainTypeOption>,
onMainStateOptionClicked: (GeneratorState.MainTypeOption) -> Unit,
modifier: Modifier = Modifier,
) {
val optionsWithStrings = possibleMainStates.associateWith { stringResource(id = it.labelRes) }
BitwardenSegmentedButton(
options = possibleMainStates
.map { mainOptionType ->
SegmentedButtonState(
text = stringResource(id = mainOptionType.labelRes),
onClick = { onMainStateOptionClicked(mainOptionType) },
isChecked = selectedType.mainTypeOption == mainOptionType,
isEnabled = when (mainOptionType) {
GeneratorState.MainTypeOption.PASSWORD -> {
when (passcodePolicyOverride) {
GeneratorState.PasscodePolicyOverride.PASSWORD -> true
GeneratorState.PasscodePolicyOverride.PASSPHRASE -> false
null -> true
}
}
BitwardenMultiSelectButton(
label = stringResource(id = R.string.what_would_you_like_to_generate),
options = optionsWithStrings.values.toImmutableList(),
selectedOption = stringResource(id = selectedType.displayStringResId),
onOptionSelected = { selectedOption ->
val selectedOptionId =
optionsWithStrings.entries.first { it.value == selectedOption }.key
onMainStateOptionClicked(selectedOptionId)
},
modifier = Modifier
.padding(horizontal = 16.dp)
GeneratorState.MainTypeOption.PASSPHRASE -> {
when (passcodePolicyOverride) {
GeneratorState.PasscodePolicyOverride.PASSWORD -> false
GeneratorState.PasscodePolicyOverride.PASSPHRASE -> true
null -> true
}
}
GeneratorState.MainTypeOption.USERNAME -> true
},
testTag = mainOptionType.testTag,
)
}
.toImmutableList(),
modifier = modifier
.fillMaxWidth()
.testTag("GeneratorTypePicker"),
.testTag(tag = "GeneratorTypePicker"),
)
}
//endregion ScrollContent and Static Items
//region PasscodeType Composables
@Composable
private fun ColumnScope.PasscodeTypeItems(
passcodeState: GeneratorState.MainType.Passcode,
onSubStateOptionClicked: (GeneratorState.MainType.Passcode.PasscodeTypeOption) -> Unit,
passwordHandlers: PasswordHandlers,
passphraseHandlers: PassphraseHandlers,
overridePasswordPolicyRestriction: Boolean,
) {
PasscodeOptionsItem(passcodeState, onSubStateOptionClicked, overridePasswordPolicyRestriction)
when (val selectedType = passcodeState.selectedType) {
is GeneratorState.MainType.Passcode.PasscodeType.Password -> {
PasswordTypeContent(
passwordTypeState = selectedType,
passwordHandlers = passwordHandlers,
)
}
is GeneratorState.MainType.Passcode.PasscodeType.Passphrase -> {
PassphraseTypeContent(
passphraseTypeState = selectedType,
passphraseHandlers = passphraseHandlers,
)
}
}
}
@Composable
private fun PasscodeOptionsItem(
currentSubState: GeneratorState.MainType.Passcode,
onSubStateOptionClicked: (GeneratorState.MainType.Passcode.PasscodeTypeOption) -> Unit,
overridePasswordPolicyRestriction: Boolean,
) {
val possibleSubStates = GeneratorState.MainType.Passcode.PasscodeTypeOption.entries
val optionsWithStrings = possibleSubStates.associateWith { stringResource(id = it.labelRes) }
BitwardenMultiSelectButton(
label = stringResource(id = R.string.password_type),
options = optionsWithStrings.values.toImmutableList(),
selectedOption = stringResource(id = currentSubState.selectedType.displayStringResId),
onOptionSelected = { selectedOption ->
val selectedOptionId =
optionsWithStrings.entries.first { it.value == selectedOption }.key
onSubStateOptionClicked(selectedOptionId)
},
isEnabled = !overridePasswordPolicyRestriction,
modifier = Modifier
.padding(horizontal = 16.dp)
.fillMaxWidth()
.testTag("PasswordTypePicker"),
)
}
//endregion PasscodeType Composables
//region PasswordType Composables
@Suppress("LongMethod")
@Composable
private fun ColumnScope.PasswordTypeContent(
passwordTypeState: GeneratorState.MainType.Passcode.PasscodeType.Password,
passwordTypeState: GeneratorState.MainType.Password,
passwordHandlers: PasswordHandlers,
) {
Spacer(modifier = Modifier.height(8.dp))
@ -707,7 +664,7 @@ private fun PasswordAvoidAmbiguousCharsToggleItem(
@Composable
private fun ColumnScope.PassphraseTypeContent(
passphraseTypeState: GeneratorState.MainType.Passcode.PasscodeType.Passphrase,
passphraseTypeState: GeneratorState.MainType.Passphrase,
passphraseHandlers: PassphraseHandlers,
) {
Spacer(modifier = Modifier.height(8.dp))
@ -1232,67 +1189,59 @@ private data class PasswordHandlers(
return PasswordHandlers(
onPasswordSliderLengthChange = { newLength, isUserInteracting ->
viewModel.trySendAction(
GeneratorAction.MainType.Passcode.PasscodeType.Password
.SliderLengthChange(
length = newLength,
isUserInteracting = isUserInteracting,
),
GeneratorAction.MainType.Password.SliderLengthChange(
length = newLength,
isUserInteracting = isUserInteracting,
),
)
},
onPasswordToggleCapitalLettersChange = { shouldUseCapitals ->
viewModel.trySendAction(
GeneratorAction.MainType.Passcode.PasscodeType.Password
.ToggleCapitalLettersChange(
useCapitals = shouldUseCapitals,
),
GeneratorAction.MainType.Password.ToggleCapitalLettersChange(
useCapitals = shouldUseCapitals,
),
)
},
onPasswordToggleLowercaseLettersChange = { shouldUseLowercase ->
viewModel.trySendAction(
GeneratorAction.MainType.Passcode.PasscodeType.Password
.ToggleLowercaseLettersChange(
useLowercase = shouldUseLowercase,
),
GeneratorAction.MainType.Password.ToggleLowercaseLettersChange(
useLowercase = shouldUseLowercase,
),
)
},
onPasswordToggleNumbersChange = { shouldUseNumbers ->
viewModel.trySendAction(
GeneratorAction.MainType.Passcode.PasscodeType.Password
.ToggleNumbersChange(
useNumbers = shouldUseNumbers,
),
GeneratorAction.MainType.Password.ToggleNumbersChange(
useNumbers = shouldUseNumbers,
),
)
},
onPasswordToggleSpecialCharactersChange = { shouldUseSpecialChars ->
viewModel.trySendAction(
GeneratorAction.MainType.Passcode.PasscodeType.Password
.ToggleSpecialCharactersChange(
useSpecialChars = shouldUseSpecialChars,
),
GeneratorAction.MainType.Password.ToggleSpecialCharactersChange(
useSpecialChars = shouldUseSpecialChars,
),
)
},
onPasswordMinNumbersCounterChange = { newMinNumbers ->
viewModel.trySendAction(
GeneratorAction.MainType.Passcode.PasscodeType.Password
.MinNumbersCounterChange(
minNumbers = newMinNumbers,
),
GeneratorAction.MainType.Password.MinNumbersCounterChange(
minNumbers = newMinNumbers,
),
)
},
onPasswordMinSpecialCharactersChange = { newMinSpecial ->
viewModel.trySendAction(
GeneratorAction.MainType.Passcode.PasscodeType.Password
.MinSpecialCharactersChange(
minSpecial = newMinSpecial,
),
GeneratorAction.MainType.Password.MinSpecialCharactersChange(
minSpecial = newMinSpecial,
),
)
},
onPasswordToggleAvoidAmbiguousCharsChange = { shouldAvoidAmbiguousChars ->
viewModel.trySendAction(
GeneratorAction.MainType.Passcode.PasscodeType.Password
.ToggleAvoidAmbigousCharactersChange(
avoidAmbiguousChars = shouldAvoidAmbiguousChars,
),
GeneratorAction.MainType.Password.ToggleAvoidAmbigousCharactersChange(
avoidAmbiguousChars = shouldAvoidAmbiguousChars,
),
)
},
)
@ -1317,34 +1266,30 @@ private data class PassphraseHandlers(
return PassphraseHandlers(
onPassphraseNumWordsCounterChange = { changeInCounter ->
viewModel.trySendAction(
GeneratorAction.MainType.Passcode.PasscodeType.Passphrase
.NumWordsCounterChange(
numWords = changeInCounter,
),
GeneratorAction.MainType.Passphrase.NumWordsCounterChange(
numWords = changeInCounter,
),
)
},
onPassphraseWordSeparatorChange = { newSeparator ->
viewModel.trySendAction(
GeneratorAction.MainType.Passcode.PasscodeType.Passphrase
.WordSeparatorTextChange(
wordSeparator = newSeparator,
),
GeneratorAction.MainType.Passphrase.WordSeparatorTextChange(
wordSeparator = newSeparator,
),
)
},
onPassphraseCapitalizeToggleChange = { shouldCapitalize ->
viewModel.trySendAction(
GeneratorAction.MainType.Passcode.PasscodeType.Passphrase
.ToggleCapitalizeChange(
capitalize = shouldCapitalize,
),
GeneratorAction.MainType.Passphrase.ToggleCapitalizeChange(
capitalize = shouldCapitalize,
),
)
},
onPassphraseIncludeNumberToggleChange = { shouldIncludeNumber ->
viewModel.trySendAction(
GeneratorAction.MainType.Passcode.PasscodeType.Passphrase
.ToggleIncludeNumberChange(
includeNumber = shouldIncludeNumber,
),
GeneratorAction.MainType.Passphrase.ToggleIncludeNumberChange(
includeNumber = shouldIncludeNumber,
),
)
},
)

View file

@ -205,15 +205,7 @@ class GeneratorScreenTest : BaseComposeTest() {
fun `clicking a MainStateOption should send MainTypeOptionSelect action`() {
// Opens the menu
composeTestRule
.onNodeWithContentDescription(label = "Password. What would you like to generate?")
.performClick()
// Choose the option from the menu
composeTestRule
.onAllNodesWithText(text = "Password")
.onLast()
.performScrollTo()
.assert(hasAnyAncestor(isDialog()))
.onNodeWithText(text = "Password")
.performClick()
verify {
@ -228,45 +220,13 @@ class GeneratorScreenTest : BaseComposeTest() {
.assertDoesNotExist()
}
@Test
fun `clicking a PasscodeOption should send PasscodeTypeOption action`() {
// Opens the menu
composeTestRule
.onNodeWithContentDescription(label = "Password. Password type")
.performClick()
// Choose the option from the menu
composeTestRule
.onAllNodesWithText(text = "Passphrase")
.onLast()
.assert(hasAnyAncestor(isDialog()))
.performClick()
verify {
viewModel.trySendAction(
GeneratorAction.MainType.Passcode.PasscodeTypeOptionSelect(
GeneratorState.MainType.Passcode.PasscodeTypeOption.PASSPHRASE,
),
)
}
// Make sure dialog is hidden:
composeTestRule
.onNode(isDialog())
.assertDoesNotExist()
}
@Suppress("MaxLineLength")
@Test
fun `clicking a UsernameOption should send UsernameTypeOption action`() {
updateState(
DEFAULT_STATE.copy(
selectedType = GeneratorState.MainType.Username(
GeneratorState
.MainType
.Username
.UsernameType
.PlusAddressedEmail(),
GeneratorState.MainType.Username.UsernameType.PlusAddressedEmail(),
),
),
)
@ -302,15 +262,7 @@ class GeneratorScreenTest : BaseComposeTest() {
//region Passcode Password Tests
@Test
fun `in Passcode_Password state, the ViewModel state should update the UI correctly`() {
composeTestRule
.onNodeWithContentDescription(label = "Password. What would you like to generate?")
.assertIsDisplayed()
composeTestRule
.onNodeWithContentDescription(label = "Password. Password type")
.assertIsDisplayed()
fun `in Password state, the ViewModel state should update the UI correctly`() {
composeTestRule
.onNode(
expectValue(
@ -363,7 +315,7 @@ class GeneratorScreenTest : BaseComposeTest() {
@Suppress("MaxLineLength")
@Test
fun `in Passcode_Password state, adjusting the slider should send SliderLengthChange action with length not equal to default`() {
fun `in Password state, adjusting the slider should send SliderLengthChange action with length not equal to default`() {
composeTestRule
.onNodeWithText("Length")
.onSiblings()
@ -383,7 +335,7 @@ class GeneratorScreenTest : BaseComposeTest() {
verify {
viewModel.trySendAction(
GeneratorAction.MainType.Passcode.PasscodeType.Password.SliderLengthChange(
GeneratorAction.MainType.Password.SliderLengthChange(
length = 128,
isUserInteracting = true,
),
@ -396,7 +348,7 @@ class GeneratorScreenTest : BaseComposeTest() {
// actually get updated from its original value, and thus will be its original value of 14.
verify {
viewModel.trySendAction(
GeneratorAction.MainType.Passcode.PasscodeType.Password.SliderLengthChange(
GeneratorAction.MainType.Password.SliderLengthChange(
length = 14,
isUserInteracting = false,
),
@ -406,14 +358,14 @@ class GeneratorScreenTest : BaseComposeTest() {
@Suppress("MaxLineLength")
@Test
fun `in Passcode_Password state, toggling the capital letters toggle should send ToggleCapitalLettersChange action`() {
fun `in Password state, toggling the capital letters toggle should send ToggleCapitalLettersChange action`() {
composeTestRule.onNodeWithText("A—Z")
.performScrollTo()
.performClick()
verify {
viewModel.trySendAction(
GeneratorAction.MainType.Passcode.PasscodeType.Password.ToggleCapitalLettersChange(
GeneratorAction.MainType.Password.ToggleCapitalLettersChange(
useCapitals = false,
),
)
@ -422,36 +374,15 @@ class GeneratorScreenTest : BaseComposeTest() {
@Suppress("MaxLineLength")
@Test
fun `in Passcode_Password state, toggling the use lowercase toggle should send ToggleLowercaseLettersChange action`() {
fun `in Password state, toggling the use lowercase toggle should send ToggleLowercaseLettersChange action`() {
composeTestRule.onNodeWithText("a—z")
.performScrollTo()
.performClick()
verify {
viewModel.trySendAction(
GeneratorAction
.MainType
.Passcode
.PasscodeType
.Password
.ToggleLowercaseLettersChange(
useLowercase = false,
),
)
}
}
@Suppress("MaxLineLength")
@Test
fun `in Passcode_Password state, toggling the use numbers toggle should send ToggleNumbersChange action`() {
composeTestRule.onNodeWithText("0-9")
.performScrollTo()
.performClick()
verify {
viewModel.trySendAction(
GeneratorAction.MainType.Passcode.PasscodeType.Password.ToggleNumbersChange(
useNumbers = false,
GeneratorAction.MainType.Password.ToggleLowercaseLettersChange(
useLowercase = false,
),
)
}
@ -459,28 +390,37 @@ class GeneratorScreenTest : BaseComposeTest() {
@Suppress("MaxLineLength")
@Test
fun `in Passcode_Password state, toggling the use special characters toggle should send ToggleSpecialCharactersChange action`() {
composeTestRule.onNodeWithText("!@#$%^&*")
fun `in Password state, toggling the use numbers toggle should send ToggleNumbersChange action`() {
composeTestRule.onNodeWithText("0-9")
.performScrollTo()
.performClick()
verify {
viewModel.trySendAction(
GeneratorAction
.MainType
.Passcode
.PasscodeType
.Password
.ToggleSpecialCharactersChange(
useSpecialChars = true,
),
GeneratorAction.MainType.Password.ToggleNumbersChange(useNumbers = false),
)
}
}
@Suppress("MaxLineLength")
@Test
fun `in Passcode_Password state, decrementing the minimum numbers counter should send MinNumbersCounterChange action`() {
fun `in Password state, toggling the use special characters toggle should send ToggleSpecialCharactersChange action`() {
composeTestRule.onNodeWithText("!@#$%^&*")
.performScrollTo()
.performClick()
verify {
viewModel.trySendAction(
GeneratorAction.MainType.Password.ToggleSpecialCharactersChange(
useSpecialChars = true,
),
)
}
}
@Suppress("MaxLineLength")
@Test
fun `in Password state, decrementing the minimum numbers counter should send MinNumbersCounterChange action`() {
val initialMinNumbers = 1
composeTestRule.onNodeWithText("Minimum numbers")
@ -492,7 +432,7 @@ class GeneratorScreenTest : BaseComposeTest() {
verify {
viewModel.trySendAction(
GeneratorAction.MainType.Passcode.PasscodeType.Password.MinNumbersCounterChange(
GeneratorAction.MainType.Password.MinNumbersCounterChange(
minNumbers = initialMinNumbers - 1,
),
)
@ -501,7 +441,7 @@ class GeneratorScreenTest : BaseComposeTest() {
@Suppress("MaxLineLength")
@Test
fun `in Passcode_Password state, incrementing the minimum numbers counter should send MinNumbersCounterChange action`() {
fun `in Password state, incrementing the minimum numbers counter should send MinNumbersCounterChange action`() {
val initialMinNumbers = 1
composeTestRule.onNodeWithText("Minimum numbers")
@ -513,28 +453,19 @@ class GeneratorScreenTest : BaseComposeTest() {
verify {
viewModel.trySendAction(
GeneratorAction.MainType.Passcode.PasscodeType.Password.MinNumbersCounterChange(
GeneratorAction.MainType.Password.MinNumbersCounterChange(
minNumbers = initialMinNumbers + 1,
),
)
}
}
@Suppress("MaxLineLength")
@Test
fun `in Passcode_Password state, decrementing the minimum numbers counter below 0 should do nothing`() {
fun `in Password state, decrementing the minimum numbers counter below 0 should do nothing`() {
val initialMinNumbers = 0
updateState(
DEFAULT_STATE.copy(
selectedType = GeneratorState.MainType.Passcode(
GeneratorState
.MainType
.Passcode
.PasscodeType
.Password(
minNumbers = initialMinNumbers,
),
),
selectedType = GeneratorState.MainType.Password(minNumbers = initialMinNumbers),
),
)
@ -549,21 +480,12 @@ class GeneratorScreenTest : BaseComposeTest() {
verify(exactly = 1) { viewModel.trySendAction(any()) }
}
@Suppress("MaxLineLength")
@Test
fun `in Passcode_Password state, incrementing the minimum numbers counter above 9 should do nothing`() {
fun `in Password state, incrementing the minimum numbers counter above 9 should do nothing`() {
val initialMinNumbers = 9
updateState(
DEFAULT_STATE.copy(
selectedType = GeneratorState.MainType.Passcode(
GeneratorState
.MainType
.Passcode
.PasscodeType
.Password(
minNumbers = initialMinNumbers,
),
),
selectedType = GeneratorState.MainType.Password(minNumbers = initialMinNumbers),
),
)
@ -580,7 +502,7 @@ class GeneratorScreenTest : BaseComposeTest() {
@Suppress("MaxLineLength")
@Test
fun `in Passcode_Password state, decrementing the minimum special characters counter should send MinSpecialCharactersChange action`() {
fun `in Password state, decrementing the minimum special characters counter should send MinSpecialCharactersChange action`() {
val initialSpecialChars = 1
composeTestRule.onNodeWithText("Minimum special")
@ -592,7 +514,7 @@ class GeneratorScreenTest : BaseComposeTest() {
verify {
viewModel.trySendAction(
GeneratorAction.MainType.Passcode.PasscodeType.Password.MinSpecialCharactersChange(
GeneratorAction.MainType.Password.MinSpecialCharactersChange(
minSpecial = initialSpecialChars - 1,
),
)
@ -601,7 +523,7 @@ class GeneratorScreenTest : BaseComposeTest() {
@Suppress("MaxLineLength")
@Test
fun `in Passcode_Password state, incrementing the minimum special characters counter should send MinSpecialCharactersChange action`() {
fun `in Password state, incrementing the minimum special characters counter should send MinSpecialCharactersChange action`() {
val initialSpecialChars = 1
composeTestRule.onNodeWithText("Minimum special")
@ -613,7 +535,7 @@ class GeneratorScreenTest : BaseComposeTest() {
verify {
viewModel.trySendAction(
GeneratorAction.MainType.Passcode.PasscodeType.Password.MinSpecialCharactersChange(
GeneratorAction.MainType.Password.MinSpecialCharactersChange(
minSpecial = initialSpecialChars + 1,
),
)
@ -622,19 +544,11 @@ class GeneratorScreenTest : BaseComposeTest() {
@Suppress("MaxLineLength")
@Test
fun `in Passcode_Password state, decrementing the minimum special characters below 0 should do nothing`() {
fun `in Password state, decrementing the minimum special characters below 0 should do nothing`() {
val initialSpecialChars = 0
updateState(
DEFAULT_STATE.copy(
selectedType = GeneratorState.MainType.Passcode(
GeneratorState
.MainType
.Passcode
.PasscodeType
.Password(
minSpecial = initialSpecialChars,
),
),
selectedType = GeneratorState.MainType.Password(minSpecial = initialSpecialChars),
),
)
@ -650,19 +564,11 @@ class GeneratorScreenTest : BaseComposeTest() {
@Suppress("MaxLineLength")
@Test
fun `in Passcode_Password state, decrementing the minimum special characters above 9 should do nothing`() {
fun `in Password state, decrementing the minimum special characters above 9 should do nothing`() {
val initialSpecialChars = 9
updateState(
DEFAULT_STATE.copy(
selectedType = GeneratorState.MainType.Passcode(
GeneratorState
.MainType
.Passcode
.PasscodeType
.Password(
minSpecial = initialSpecialChars,
),
),
selectedType = GeneratorState.MainType.Password(minSpecial = initialSpecialChars),
),
)
@ -678,41 +584,30 @@ class GeneratorScreenTest : BaseComposeTest() {
@Suppress("MaxLineLength")
@Test
fun `in Passcode_Password state, toggling the use avoid ambiguous characters toggle should send ToggleSpecialCharactersChange action`() {
fun `in Password state, toggling the use avoid ambiguous characters toggle should send ToggleSpecialCharactersChange action`() {
composeTestRule.onNodeWithText("Avoid ambiguous characters")
.performScrollTo()
.performClick()
verify {
viewModel.trySendAction(
GeneratorAction
.MainType
.Passcode
.PasscodeType
.Password
.ToggleAvoidAmbigousCharactersChange(
avoidAmbiguousChars = true,
),
GeneratorAction.MainType.Password.ToggleAvoidAmbigousCharactersChange(
avoidAmbiguousChars = true,
),
)
}
}
@Test
fun `in Passcode_Password state, disabled elements should not send events`() {
fun `in Password state, disabled elements should not send events`() {
updateState(
DEFAULT_STATE.copy(
selectedType = GeneratorState.MainType.Passcode(
GeneratorState
.MainType
.Passcode
.PasscodeType
.Password(
capitalsEnabled = false,
lowercaseEnabled = false,
numbersEnabled = false,
specialCharsEnabled = false,
ambiguousCharsEnabled = false,
),
selectedType = GeneratorState.MainType.Password(
capitalsEnabled = false,
lowercaseEnabled = false,
numbersEnabled = false,
specialCharsEnabled = false,
ambiguousCharsEnabled = false,
),
),
)
@ -735,72 +630,37 @@ class GeneratorScreenTest : BaseComposeTest() {
verify(exactly = 0) {
viewModel.trySendAction(
GeneratorAction
.MainType
.Passcode
.PasscodeType
.Password
.ToggleCapitalLettersChange(
useCapitals = false,
),
GeneratorAction.MainType.Password.ToggleCapitalLettersChange(useCapitals = false),
)
viewModel.trySendAction(
GeneratorAction
.MainType
.Passcode
.PasscodeType
.Password
.ToggleLowercaseLettersChange(
useLowercase = false,
),
GeneratorAction.MainType.Password.ToggleLowercaseLettersChange(
useLowercase = false,
),
)
viewModel.trySendAction(
GeneratorAction
.MainType
.Passcode
.PasscodeType
.Password
.ToggleNumbersChange(
useNumbers = false,
),
GeneratorAction.MainType.Password.ToggleNumbersChange(useNumbers = false),
)
viewModel.trySendAction(
GeneratorAction
.MainType
.Passcode
.PasscodeType
.Password
.ToggleSpecialCharactersChange(
useSpecialChars = true,
),
GeneratorAction.MainType.Password.ToggleSpecialCharactersChange(
useSpecialChars = true,
),
)
viewModel.trySendAction(
GeneratorAction
.MainType
.Passcode
.PasscodeType
.Password
.ToggleAvoidAmbigousCharactersChange(
avoidAmbiguousChars = true,
),
GeneratorAction.MainType.Password.ToggleAvoidAmbigousCharactersChange(
avoidAmbiguousChars = true,
),
)
}
}
@Test
fun `in Passcode_Password state, minimum numbers cannot go below minimum threshold`() {
fun `in Password state, minimum numbers cannot go below minimum threshold`() {
val initialMinNumbers = 5
updateState(
DEFAULT_STATE.copy(
selectedType = GeneratorState.MainType.Passcode(
GeneratorState
.MainType
.Passcode
.PasscodeType
.Password(
minNumbersAllowed = initialMinNumbers,
),
selectedType = GeneratorState.MainType.Password(
minNumbersAllowed = initialMinNumbers,
),
),
)
@ -814,7 +674,7 @@ class GeneratorScreenTest : BaseComposeTest() {
verify(exactly = 0) {
viewModel.trySendAction(
GeneratorAction.MainType.Passcode.PasscodeType.Password.MinNumbersCounterChange(
GeneratorAction.MainType.Password.MinNumbersCounterChange(
minNumbers = 4,
),
)
@ -822,21 +682,15 @@ class GeneratorScreenTest : BaseComposeTest() {
}
@Test
fun `in Passcode_Password state, maximum numbers should match minimum if lower`() {
fun `in Password state, maximum numbers should match minimum if lower`() {
val initialMinNumbers = 7
val initialMaxNumbers = 5
updateState(
DEFAULT_STATE.copy(
selectedType = GeneratorState.MainType.Passcode(
GeneratorState
.MainType
.Passcode
.PasscodeType
.Password(
minNumbersAllowed = initialMinNumbers,
maxNumbersAllowed = initialMaxNumbers,
),
selectedType = GeneratorState.MainType.Password(
minNumbersAllowed = initialMinNumbers,
maxNumbersAllowed = initialMaxNumbers,
),
),
)
@ -850,21 +704,14 @@ class GeneratorScreenTest : BaseComposeTest() {
.assertIsDisplayed()
}
@Suppress("MaxLineLength")
@Test
fun `in Passcode_Password state, minimum special characters cannot go below minimum threshold`() {
fun `in Password state, minimum special characters cannot go below minimum threshold`() {
val initialMinSpecials = 5
updateState(
DEFAULT_STATE.copy(
selectedType = GeneratorState.MainType.Passcode(
GeneratorState
.MainType
.Passcode
.PasscodeType
.Password(
minSpecialAllowed = initialMinSpecials,
),
selectedType = GeneratorState.MainType.Password(
minSpecialAllowed = initialMinSpecials,
),
),
)
@ -878,7 +725,7 @@ class GeneratorScreenTest : BaseComposeTest() {
verify(exactly = 0) {
viewModel.trySendAction(
GeneratorAction.MainType.Passcode.PasscodeType.Password.MinSpecialCharactersChange(
GeneratorAction.MainType.Password.MinSpecialCharactersChange(
minSpecial = 4,
),
)
@ -886,21 +733,15 @@ class GeneratorScreenTest : BaseComposeTest() {
}
@Test
fun `in Passcode_Password state, maximum special should match minimum if lower `() {
fun `in Password state, maximum special should match minimum if lower `() {
val initialMinSpecials = 7
val initialMaxSpecials = 5
updateState(
DEFAULT_STATE.copy(
selectedType = GeneratorState.MainType.Passcode(
GeneratorState
.MainType
.Passcode
.PasscodeType
.Password(
minSpecialAllowed = initialMinSpecials,
maxSpecialAllowed = initialMaxSpecials,
),
selectedType = GeneratorState.MainType.Password(
minSpecialAllowed = initialMinSpecials,
maxSpecialAllowed = initialMaxSpecials,
),
),
)
@ -914,18 +755,12 @@ class GeneratorScreenTest : BaseComposeTest() {
}
@Test
fun `in Passcode_Passphrase state, disabled elements should not send events`() {
fun `in Passphrase state, disabled elements should not send events`() {
updateState(
DEFAULT_STATE.copy(
selectedType = GeneratorState.MainType.Passcode(
GeneratorState
.MainType
.Passcode
.PasscodeType
.Passphrase(
capitalizeEnabled = false,
includeNumberEnabled = false,
),
selectedType = GeneratorState.MainType.Passphrase(
capitalizeEnabled = false,
includeNumberEnabled = false,
),
),
)
@ -939,44 +774,25 @@ class GeneratorScreenTest : BaseComposeTest() {
verify(exactly = 0) {
viewModel.trySendAction(
GeneratorAction
.MainType
.Passcode
.PasscodeType
.Passphrase
.ToggleCapitalizeChange(
capitalize = true,
),
GeneratorAction.MainType.Passphrase.ToggleCapitalizeChange(
capitalize = true,
),
)
viewModel.trySendAction(
GeneratorAction
.MainType
.Passcode
.PasscodeType
.Passphrase
.ToggleIncludeNumberChange(
includeNumber = true,
),
GeneratorAction.MainType.Passphrase.ToggleIncludeNumberChange(
includeNumber = true,
),
)
}
}
@Suppress("MaxLineLength")
@Test
fun `in Passcode_passphrase state, minimum number of words cannot go below minimum threshold`() {
fun `in Passphrase state, minimum number of words cannot go below minimum threshold`() {
val initialMinWords = 5
updateState(
DEFAULT_STATE.copy(
selectedType = GeneratorState.MainType.Passcode(
GeneratorState
.MainType
.Passcode
.PasscodeType
.Passphrase(
minNumWords = initialMinWords,
),
),
selectedType = GeneratorState.MainType.Passphrase(minNumWords = initialMinWords),
),
)
@ -989,9 +805,7 @@ class GeneratorScreenTest : BaseComposeTest() {
verify(exactly = 0) {
viewModel.trySendAction(
GeneratorAction.MainType.Passcode.PasscodeType.Passphrase.NumWordsCounterChange(
numWords = 4,
),
GeneratorAction.MainType.Passphrase.NumWordsCounterChange(numWords = 4),
)
}
}
@ -1002,19 +816,11 @@ class GeneratorScreenTest : BaseComposeTest() {
@Suppress("MaxLineLength")
@Test
fun `in Passcode_Passphrase state, decrementing number of words should send NumWordsCounterChange action with decremented value`() {
fun `in Passphrase state, decrementing number of words should send NumWordsCounterChange action with decremented value`() {
val initialNumWords = 4
updateState(
DEFAULT_STATE.copy(
selectedType = GeneratorState.MainType.Passcode(
GeneratorState
.MainType
.Passcode
.PasscodeType
.Passphrase(
numWords = initialNumWords,
),
),
selectedType = GeneratorState.MainType.Passphrase(numWords = initialNumWords),
),
)
@ -1029,7 +835,7 @@ class GeneratorScreenTest : BaseComposeTest() {
verify {
viewModel.trySendAction(
GeneratorAction.MainType.Passcode.PasscodeType.Passphrase.NumWordsCounterChange(
GeneratorAction.MainType.Passphrase.NumWordsCounterChange(
numWords = initialNumWords - 1,
),
)
@ -1037,19 +843,11 @@ class GeneratorScreenTest : BaseComposeTest() {
}
@Test
fun `in Passcode_Passphrase state, decrementing number of words under 3 should do nothing`() {
fun `in Passphrase state, decrementing number of words under 3 should do nothing`() {
val initialNumWords = 3
updateState(
DEFAULT_STATE.copy(
selectedType = GeneratorState.MainType.Passcode(
GeneratorState
.MainType
.Passcode
.PasscodeType
.Passphrase(
numWords = initialNumWords,
),
),
selectedType = GeneratorState.MainType.Passphrase(numWords = initialNumWords),
),
)
@ -1066,19 +864,11 @@ class GeneratorScreenTest : BaseComposeTest() {
}
@Test
fun `in Passcode_Passphrase state, incrementing number of words over 20 should do nothing`() {
fun `in Passphrase state, incrementing number of words over 20 should do nothing`() {
val initialNumWords = 20
updateState(
DEFAULT_STATE.copy(
selectedType = GeneratorState.MainType.Passcode(
GeneratorState
.MainType
.Passcode
.PasscodeType
.Passphrase(
numWords = initialNumWords,
),
),
selectedType = GeneratorState.MainType.Passphrase(numWords = initialNumWords),
),
)
@ -1096,17 +886,11 @@ class GeneratorScreenTest : BaseComposeTest() {
@Suppress("MaxLineLength")
@Test
fun `in Passcode_Passphrase state, incrementing number of words should send NumWordsCounterChange action with incremented value`() {
fun `in Passphrase state, incrementing number of words should send NumWordsCounterChange action with incremented value`() {
val initialNumWords = 3
updateState(
DEFAULT_STATE.copy(
selectedType = GeneratorState.MainType.Passcode(
GeneratorState
.MainType
.Passcode
.PasscodeType
.Passphrase(),
),
selectedType = GeneratorState.MainType.Passphrase(),
),
)
@ -1120,25 +904,18 @@ class GeneratorScreenTest : BaseComposeTest() {
verify {
viewModel.trySendAction(
GeneratorAction.MainType.Passcode.PasscodeType.Passphrase.NumWordsCounterChange(
GeneratorAction.MainType.Passphrase.NumWordsCounterChange(
numWords = initialNumWords + 1,
),
)
}
}
@Suppress("MaxLineLength")
@Test
fun `in Passcode_Passphrase state, toggling capitalize should send ToggleCapitalizeChange action`() {
fun `in Passphrase state, toggling capitalize should send ToggleCapitalizeChange action`() {
updateState(
DEFAULT_STATE.copy(
selectedType = GeneratorState.MainType.Passcode(
GeneratorState
.MainType
.Passcode
.PasscodeType
.Passphrase(),
),
selectedType = GeneratorState.MainType.Passphrase(),
),
)
@ -1149,7 +926,7 @@ class GeneratorScreenTest : BaseComposeTest() {
verify {
viewModel.trySendAction(
GeneratorAction.MainType.Passcode.PasscodeType.Passphrase.ToggleCapitalizeChange(
GeneratorAction.MainType.Passphrase.ToggleCapitalizeChange(
capitalize = true,
),
)
@ -1158,16 +935,10 @@ class GeneratorScreenTest : BaseComposeTest() {
@Suppress("MaxLineLength")
@Test
fun `in Passcode_Passphrase state, toggling the include number toggle should send ToggleIncludeNumberChange action`() {
fun `in Passphrase state, toggling the include number toggle should send ToggleIncludeNumberChange action`() {
updateState(
DEFAULT_STATE.copy(
selectedType = GeneratorState.MainType.Passcode(
GeneratorState
.MainType
.Passcode
.PasscodeType
.Passphrase(),
),
selectedType = GeneratorState.MainType.Passphrase(),
),
)
@ -1177,7 +948,7 @@ class GeneratorScreenTest : BaseComposeTest() {
verify {
viewModel.trySendAction(
GeneratorAction.MainType.Passcode.PasscodeType.Passphrase.ToggleIncludeNumberChange(
GeneratorAction.MainType.Passphrase.ToggleIncludeNumberChange(
includeNumber = true,
),
)
@ -1186,16 +957,10 @@ class GeneratorScreenTest : BaseComposeTest() {
@Suppress("MaxLineLength")
@Test
fun `in Passcode_Passphrase state, updating text in word separator should send WordSeparatorTextChange action`() {
fun `in Passphrase state, updating text in word separator should send WordSeparatorTextChange action`() {
updateState(
DEFAULT_STATE.copy(
selectedType = GeneratorState.MainType.Passcode(
GeneratorState
.MainType
.Passcode
.PasscodeType
.Passphrase(),
),
selectedType = GeneratorState.MainType.Passphrase(),
),
)
@ -1206,7 +971,7 @@ class GeneratorScreenTest : BaseComposeTest() {
verify {
viewModel.trySendAction(
GeneratorAction.MainType.Passcode.PasscodeType.Passphrase.WordSeparatorTextChange(
GeneratorAction.MainType.Passphrase.WordSeparatorTextChange(
wordSeparator = 'a',
),
)
@ -1765,8 +1530,6 @@ class GeneratorScreenTest : BaseComposeTest() {
private val DEFAULT_STATE = GeneratorState(
generatedText = "",
selectedType = GeneratorState.MainType.Passcode(
GeneratorState.MainType.Passcode.PasscodeType.Password(),
),
selectedType = GeneratorState.MainType.Password(),
currentEmailAddress = "currentEmail",
)