mirror of
https://github.com/bitwarden/android.git
synced 2025-03-15 18:58:59 +03:00
BIT-659: Adding UI for plus addressed email generator (#293)
This commit is contained in:
parent
8e099a546d
commit
90f565e02b
4 changed files with 507 additions and 20 deletions
|
@ -15,6 +15,7 @@ import androidx.compose.foundation.rememberScrollState
|
|||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Slider
|
||||
import androidx.compose.material3.SnackbarDuration
|
||||
|
@ -121,10 +122,23 @@ fun GeneratorScreen(
|
|||
}
|
||||
}
|
||||
|
||||
val onUsernameOptionClicked: (GeneratorState.MainType.Username.UsernameTypeOption) -> Unit =
|
||||
remember(viewModel) {
|
||||
{
|
||||
viewModel.trySendAction(
|
||||
GeneratorAction.MainType.Username.UsernameTypeOptionSelect(
|
||||
it,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val passwordHandlers = PasswordHandlers.create(viewModel = viewModel)
|
||||
|
||||
val passphraseHandlers = PassphraseHandlers.create(viewModel = viewModel)
|
||||
|
||||
val plusAddressedEmailHandlers = PlusAddressedEmailHandlers.create(viewModel = viewModel)
|
||||
|
||||
val scrollBehavior =
|
||||
TopAppBarDefaults.exitUntilCollapsedScrollBehavior(rememberTopAppBarState())
|
||||
|
||||
|
@ -148,9 +162,11 @@ fun GeneratorScreen(
|
|||
onRegenerateClick = onRegenerateClick,
|
||||
onCopyClick = onCopyClick,
|
||||
onMainStateOptionClicked = onMainStateOptionClicked,
|
||||
onSubStateOptionClicked = onPasscodeOptionClicked,
|
||||
onPasscodeSubStateOptionClicked = onPasscodeOptionClicked,
|
||||
onUsernameSubStateOptionClicked = onUsernameOptionClicked,
|
||||
passwordHandlers = passwordHandlers,
|
||||
passphraseHandlers = passphraseHandlers,
|
||||
plusAddressedEmailHandlers = plusAddressedEmailHandlers,
|
||||
modifier = Modifier.padding(innerPadding),
|
||||
)
|
||||
}
|
||||
|
@ -165,9 +181,11 @@ private fun ScrollContent(
|
|||
onRegenerateClick: () -> Unit,
|
||||
onCopyClick: () -> Unit,
|
||||
onMainStateOptionClicked: (GeneratorState.MainTypeOption) -> Unit,
|
||||
onSubStateOptionClicked: (GeneratorState.MainType.Passcode.PasscodeTypeOption) -> Unit,
|
||||
onPasscodeSubStateOptionClicked: (GeneratorState.MainType.Passcode.PasscodeTypeOption) -> Unit,
|
||||
onUsernameSubStateOptionClicked: (GeneratorState.MainType.Username.UsernameTypeOption) -> Unit,
|
||||
passwordHandlers: PasswordHandlers,
|
||||
passphraseHandlers: PassphraseHandlers,
|
||||
plusAddressedEmailHandlers: PlusAddressedEmailHandlers,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
|
@ -205,14 +223,18 @@ private fun ScrollContent(
|
|||
is GeneratorState.MainType.Passcode -> {
|
||||
PasscodeTypeItems(
|
||||
passcodeState = selectedType,
|
||||
onSubStateOptionClicked = onSubStateOptionClicked,
|
||||
onSubStateOptionClicked = onPasscodeSubStateOptionClicked,
|
||||
passwordHandlers = passwordHandlers,
|
||||
passphraseHandlers = passphraseHandlers,
|
||||
)
|
||||
}
|
||||
|
||||
is GeneratorState.MainType.Username -> {
|
||||
// TODO(BIT-335): Username state to handle Plus Addressed Email
|
||||
UsernameTypeItems(
|
||||
usernameState = selectedType,
|
||||
onSubStateOptionClicked = onUsernameSubStateOptionClicked,
|
||||
plusAddressedEmailHandlers = plusAddressedEmailHandlers,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -661,6 +683,109 @@ private fun PassphraseIncludeNumberToggleItem(
|
|||
|
||||
//endregion PassphraseType Composables
|
||||
|
||||
//region UsernameType Composables
|
||||
|
||||
@Composable
|
||||
private fun UsernameTypeItems(
|
||||
usernameState: GeneratorState.MainType.Username,
|
||||
onSubStateOptionClicked: (GeneratorState.MainType.Username.UsernameTypeOption) -> Unit,
|
||||
plusAddressedEmailHandlers: PlusAddressedEmailHandlers,
|
||||
) {
|
||||
UsernameOptionsItem(usernameState, onSubStateOptionClicked)
|
||||
|
||||
when (val selectedType = usernameState.selectedType) {
|
||||
is GeneratorState.MainType.Username.UsernameType.PlusAddressedEmail -> {
|
||||
PlusAddressedEmailTypeContent(
|
||||
usernameTypeState = selectedType,
|
||||
plusAddressedEmailHandlers = plusAddressedEmailHandlers,
|
||||
)
|
||||
}
|
||||
|
||||
is GeneratorState.MainType.Username.UsernameType.ForwardedEmailAlias -> {
|
||||
// TODO: Implement ForwardedEmailAlias BIT-657
|
||||
}
|
||||
|
||||
is GeneratorState.MainType.Username.UsernameType.CatchAllEmail -> {
|
||||
// TODO: Implement CatchAllEmail BIT-656
|
||||
}
|
||||
|
||||
is GeneratorState.MainType.Username.UsernameType.RandomWord -> {
|
||||
// TODO: Implement RandomWord BIT-658
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun UsernameOptionsItem(
|
||||
currentSubState: GeneratorState.MainType.Username,
|
||||
onSubStateOptionClicked: (GeneratorState.MainType.Username.UsernameTypeOption) -> Unit,
|
||||
) {
|
||||
val possibleSubStates = GeneratorState.MainType.Username.UsernameTypeOption.values().toList()
|
||||
val optionsWithStrings =
|
||||
possibleSubStates.associateBy({ it }, { stringResource(id = it.labelRes) })
|
||||
|
||||
BitwardenMultiSelectButton(
|
||||
label = stringResource(id = R.string.username_type),
|
||||
options = optionsWithStrings.values.toList(),
|
||||
selectedOption = stringResource(id = currentSubState.selectedType.displayStringResId),
|
||||
onOptionSelected = { selectedOption ->
|
||||
val selectedOptionId =
|
||||
optionsWithStrings.entries.first { it.value == selectedOption }.key
|
||||
onSubStateOptionClicked(selectedOptionId)
|
||||
},
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
|
||||
//endregion UsernameType Composables
|
||||
|
||||
//region PlusAddressedEmailType Composables
|
||||
|
||||
@Composable
|
||||
private fun PlusAddressedEmailTypeContent(
|
||||
usernameTypeState: GeneratorState.MainType.Username.UsernameType.PlusAddressedEmail,
|
||||
plusAddressedEmailHandlers: PlusAddressedEmailHandlers,
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
|
||||
Text(
|
||||
text = stringResource(id = R.string.plus_addressed_email_description),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 32.dp),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
PlusAddressedEmailTextInputItem(
|
||||
email = usernameTypeState.email,
|
||||
onPlusAddressedEmailTextChange = plusAddressedEmailHandlers.onEmailChange,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PlusAddressedEmailTextInputItem(
|
||||
email: String,
|
||||
onPlusAddressedEmailTextChange: (email: String) -> Unit,
|
||||
) {
|
||||
BitwardenTextField(
|
||||
label = stringResource(id = R.string.email_required_parenthesis),
|
||||
value = email,
|
||||
onValueChange = {
|
||||
onPlusAddressedEmailTextChange(it)
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
}
|
||||
|
||||
//endregion PlusAddressedEmailType Composables
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun GeneratorPreview() {
|
||||
|
@ -808,3 +933,32 @@ private class PassphraseHandlers(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A class dedicated to handling user interactions related to plus addressed email
|
||||
* configuration.
|
||||
* Each lambda corresponds to a specific user action, allowing for easy delegation of
|
||||
* logic when user input is detected.
|
||||
*/
|
||||
private class PlusAddressedEmailHandlers(
|
||||
val onEmailChange: (String) -> Unit,
|
||||
) {
|
||||
companion object {
|
||||
fun create(viewModel: GeneratorViewModel): PlusAddressedEmailHandlers {
|
||||
return PlusAddressedEmailHandlers(
|
||||
onEmailChange = { newEmail ->
|
||||
viewModel.trySendAction(
|
||||
GeneratorAction
|
||||
.MainType
|
||||
.Username
|
||||
.UsernameType
|
||||
.PlusAddressedEmail
|
||||
.EmailTextChange(
|
||||
email = newEmail,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -95,6 +95,15 @@ class GeneratorViewModel @Inject constructor(
|
|||
is GeneratorAction.Internal.UpdateGeneratedPassphraseResult -> {
|
||||
handleUpdateGeneratedPassphraseResult(action)
|
||||
}
|
||||
|
||||
is GeneratorAction.MainType.Username.UsernameTypeOptionSelect -> {
|
||||
handleUsernameTypeOptionSelect(action)
|
||||
}
|
||||
|
||||
is GeneratorAction.MainType.Username.UsernameType.PlusAddressedEmail.EmailTextChange ->
|
||||
{
|
||||
handlePlusAddressedEmailTextInputChange(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -492,6 +501,45 @@ class GeneratorViewModel @Inject constructor(
|
|||
|
||||
//endregion Passphrase Specific Handlers
|
||||
|
||||
//region Username Type Handlers
|
||||
|
||||
private fun handleUsernameTypeOptionSelect(
|
||||
action: GeneratorAction.MainType.Username.UsernameTypeOptionSelect,
|
||||
) {
|
||||
when (action.usernameTypeOption) {
|
||||
Username.UsernameTypeOption.PLUS_ADDRESSED_EMAIL -> loadUsernameOptions(
|
||||
selectedType = Username(selectedType = PlusAddressedEmail()),
|
||||
)
|
||||
|
||||
Username.UsernameTypeOption.CATCH_ALL_EMAIL -> loadUsernameOptions(
|
||||
selectedType = Username(selectedType = Username.UsernameType.CatchAllEmail()),
|
||||
)
|
||||
|
||||
Username.UsernameTypeOption.FORWARDED_EMAIL_ALIAS -> loadUsernameOptions(
|
||||
selectedType = Username(selectedType = Username.UsernameType.ForwardedEmailAlias()),
|
||||
)
|
||||
|
||||
Username.UsernameTypeOption.RANDOM_WORD -> loadUsernameOptions(
|
||||
selectedType = Username(selectedType = Username.UsernameType.RandomWord()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
//endregion Username Type Handlers
|
||||
|
||||
//region Plus Addressed Email Specific Handlers
|
||||
|
||||
private fun handlePlusAddressedEmailTextInputChange(
|
||||
action: GeneratorAction.MainType.Username.UsernameType.PlusAddressedEmail.EmailTextChange,
|
||||
) {
|
||||
updatePlusAddressedEmailType { plusAddressedEmailType ->
|
||||
val newEmail = action.email
|
||||
plusAddressedEmailType.copy(email = newEmail)
|
||||
}
|
||||
}
|
||||
|
||||
//endregion Plus Addressed Email Specific Handlers
|
||||
|
||||
//region Utility Functions
|
||||
|
||||
private inline fun updateGeneratorMainType(
|
||||
|
@ -555,6 +603,26 @@ class GeneratorViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private inline fun updateGeneratorMainTypeUsername(
|
||||
crossinline block: (Username) -> Username,
|
||||
) {
|
||||
updateGeneratorMainType {
|
||||
if (it !is Username) null else block(it)
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun updatePlusAddressedEmailType(
|
||||
crossinline block: (PlusAddressedEmail) -> PlusAddressedEmail,
|
||||
) {
|
||||
updateGeneratorMainTypeUsername { currentSelectedType ->
|
||||
val currentUsernameType = currentSelectedType.selectedType
|
||||
if (currentUsernameType !is PlusAddressedEmail) {
|
||||
return@updateGeneratorMainTypeUsername currentSelectedType
|
||||
}
|
||||
currentSelectedType.copy(selectedType = block(currentUsernameType))
|
||||
}
|
||||
}
|
||||
|
||||
//endregion Utility Functions
|
||||
|
||||
companion object {
|
||||
|
@ -1107,7 +1175,36 @@ sealed class GeneratorAction {
|
|||
* This sealed class serves as a placeholder for future extensions
|
||||
* related to the username actions in the generator.
|
||||
*/
|
||||
sealed class Username : MainType()
|
||||
sealed class Username : MainType() {
|
||||
|
||||
/**
|
||||
* Represents the action of selecting a username type option.
|
||||
*
|
||||
* @property usernameTypeOption The selected username type option.
|
||||
*/
|
||||
data class UsernameTypeOptionSelect(
|
||||
val usernameTypeOption: GeneratorState.MainType.Username.UsernameTypeOption,
|
||||
) : Username()
|
||||
|
||||
/**
|
||||
* Represents actions related to the different types of usernames.
|
||||
*/
|
||||
sealed class UsernameType : Username() {
|
||||
|
||||
/**
|
||||
* Represents actions specifically related to Plus Addressed Email.
|
||||
*/
|
||||
sealed class PlusAddressedEmail : UsernameType() {
|
||||
|
||||
/**
|
||||
* Fired when the email text input is changed.
|
||||
*
|
||||
* @property email The new email text.
|
||||
*/
|
||||
data class EmailTextChange(val email: String) : PlusAddressedEmail()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -54,7 +54,7 @@ class GeneratorScreenTest : BaseComposeTest() {
|
|||
extraBufferCapacity = Int.MAX_VALUE,
|
||||
)
|
||||
|
||||
private val viewModel = mockk< GeneratorViewModel >(relaxed = true) {
|
||||
private val viewModel = mockk<GeneratorViewModel>(relaxed = true) {
|
||||
every { eventFlow } returns mutableEventFlow
|
||||
every { stateFlow } returns mutableStateFlow
|
||||
}
|
||||
|
@ -165,6 +165,49 @@ class GeneratorScreenTest : BaseComposeTest() {
|
|||
.assertDoesNotExist()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking a UsernameOption should send UsernameTypeOption action`() {
|
||||
updateState(
|
||||
GeneratorState(
|
||||
generatedText = "Placeholder",
|
||||
selectedType = GeneratorState.MainType.Username(
|
||||
GeneratorState.MainType.Username.UsernameType.PlusAddressedEmail(
|
||||
email = "email",
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
composeTestRule.setContent {
|
||||
GeneratorScreen(viewModel = viewModel)
|
||||
}
|
||||
|
||||
// Opens the menu
|
||||
composeTestRule
|
||||
.onNodeWithContentDescription(label = "Username type, Plus addressed email")
|
||||
.performClick()
|
||||
|
||||
// Choose the option from the menu
|
||||
composeTestRule
|
||||
.onAllNodesWithText(text = "Random word")
|
||||
.onLast()
|
||||
.assert(hasAnyAncestor(isDialog()))
|
||||
.performClick()
|
||||
|
||||
verify {
|
||||
viewModel.trySendAction(
|
||||
GeneratorAction.MainType.Username.UsernameTypeOptionSelect(
|
||||
GeneratorState.MainType.Username.UsernameTypeOption.RANDOM_WORD,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// Make sure dialog is hidden:
|
||||
composeTestRule
|
||||
.onNode(isDialog())
|
||||
.assertDoesNotExist()
|
||||
}
|
||||
|
||||
//region Passcode Password Tests
|
||||
|
||||
@Test
|
||||
|
@ -913,6 +956,45 @@ class GeneratorScreenTest : BaseComposeTest() {
|
|||
|
||||
//endregion Passcode Passphrase Tests
|
||||
|
||||
//region Username Plus Addressed Email Tests
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `in Username_PlusAddressedEmail state, updating text in email field should send EmailTextChange action`() {
|
||||
updateState(
|
||||
GeneratorState(
|
||||
generatedText = "Placeholder",
|
||||
selectedType = GeneratorState.MainType.Username(
|
||||
GeneratorState.MainType.Username.UsernameType.PlusAddressedEmail(
|
||||
email = "",
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
composeTestRule.setContent {
|
||||
GeneratorScreen(viewModel = viewModel)
|
||||
}
|
||||
|
||||
val newEmail = "test@example.com"
|
||||
|
||||
// Find the text field for PlusAddressedEmail and input text
|
||||
composeTestRule
|
||||
.onNodeWithText("Email (required)")
|
||||
.performScrollTo()
|
||||
.performTextInput(newEmail)
|
||||
|
||||
verify {
|
||||
viewModel.trySendAction(
|
||||
GeneratorAction.MainType.Username.UsernameType.PlusAddressedEmail.EmailTextChange(
|
||||
email = newEmail,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
//endregion Username Plus Addressed Email Tests
|
||||
|
||||
private fun updateState(state: GeneratorState) {
|
||||
mutableStateFlow.value = state
|
||||
}
|
||||
|
|
|
@ -17,13 +17,14 @@ import org.junit.jupiter.api.Test
|
|||
|
||||
class GeneratorViewModelTest : BaseViewModelTest() {
|
||||
|
||||
private val initialState = createPasswordState()
|
||||
private val initialSavedStateHandle = createSavedStateHandleWithState(initialState)
|
||||
private val initialPasscodeState = createPasswordState()
|
||||
private val initialPasscodeSavedStateHandle =
|
||||
createSavedStateHandleWithState(initialPasscodeState)
|
||||
|
||||
private val initialPassphraseState = createPassphraseState()
|
||||
private val passphraseSavedStateHandle = createSavedStateHandleWithState(initialPassphraseState)
|
||||
|
||||
private val initialUsernameState = createUsernameState()
|
||||
private val initialUsernameState = createPlusAddressedEmailState()
|
||||
private val usernameSavedStateHandle = createSavedStateHandleWithState(initialUsernameState)
|
||||
|
||||
private val fakeGeneratorRepository = FakeGeneratorRepository().apply {
|
||||
|
@ -36,7 +37,7 @@ class GeneratorViewModelTest : BaseViewModelTest() {
|
|||
fun `initial state should be correct`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(initialState, awaitItem())
|
||||
assertEquals(initialPasscodeState, awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -198,7 +199,7 @@ class GeneratorViewModelTest : BaseViewModelTest() {
|
|||
viewModel.actionChannel.trySend(action)
|
||||
|
||||
val expectedState =
|
||||
initialState.copy(
|
||||
initialPasscodeState.copy(
|
||||
selectedType = GeneratorState.MainType.Passcode(),
|
||||
generatedText = "updatedText",
|
||||
)
|
||||
|
@ -218,7 +219,7 @@ class GeneratorViewModelTest : BaseViewModelTest() {
|
|||
viewModel.actionChannel.trySend(action)
|
||||
|
||||
val expectedState =
|
||||
initialState.copy(selectedType = GeneratorState.MainType.Username())
|
||||
initialPasscodeState.copy(selectedType = GeneratorState.MainType.Username())
|
||||
|
||||
assertEquals(expectedState, viewModel.stateFlow.value)
|
||||
}
|
||||
|
@ -236,7 +237,7 @@ class GeneratorViewModelTest : BaseViewModelTest() {
|
|||
|
||||
viewModel.actionChannel.trySend(action)
|
||||
|
||||
val expectedState = initialState.copy(
|
||||
val expectedState = initialPasscodeState.copy(
|
||||
selectedType = GeneratorState.MainType.Passcode(
|
||||
selectedType = GeneratorState.MainType.Passcode.PasscodeType.Password(),
|
||||
),
|
||||
|
@ -261,7 +262,7 @@ class GeneratorViewModelTest : BaseViewModelTest() {
|
|||
|
||||
viewModel.actionChannel.trySend(action)
|
||||
|
||||
val expectedState = initialState.copy(
|
||||
val expectedState = initialPasscodeState.copy(
|
||||
generatedText = updatedText,
|
||||
selectedType = GeneratorState.MainType.Passcode(
|
||||
selectedType = GeneratorState.MainType.Passcode.PasscodeType.Passphrase(),
|
||||
|
@ -271,6 +272,109 @@ class GeneratorViewModelTest : BaseViewModelTest() {
|
|||
assertEquals(expectedState, viewModel.stateFlow.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `UsernameTypeOptionSelect PLUS_ADDRESSED_EMAIL should switch to PlusAddressedEmail type`() =
|
||||
runTest {
|
||||
val viewModel = createViewModel(initialUsernameState)
|
||||
|
||||
viewModel.actionChannel.trySend(
|
||||
GeneratorAction.MainType.Username.UsernameTypeOptionSelect(
|
||||
usernameTypeOption = GeneratorState
|
||||
.MainType
|
||||
.Username
|
||||
.UsernameTypeOption
|
||||
.PLUS_ADDRESSED_EMAIL,
|
||||
),
|
||||
)
|
||||
|
||||
val expectedState = initialUsernameState.copy(
|
||||
selectedType = GeneratorState.MainType.Username(
|
||||
selectedType = GeneratorState
|
||||
.MainType
|
||||
.Username
|
||||
.UsernameType
|
||||
.PlusAddressedEmail(),
|
||||
),
|
||||
)
|
||||
|
||||
assertEquals(expectedState, viewModel.stateFlow.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `UsernameTypeOptionSelect CATCH_ALL_EMAIL should switch to CatchAllEmail type`() = runTest {
|
||||
val viewModel = createViewModel(initialUsernameState)
|
||||
|
||||
viewModel.actionChannel.trySend(
|
||||
GeneratorAction.MainType.Username.UsernameTypeOptionSelect(
|
||||
usernameTypeOption = GeneratorState
|
||||
.MainType
|
||||
.Username
|
||||
.UsernameTypeOption
|
||||
.CATCH_ALL_EMAIL,
|
||||
),
|
||||
)
|
||||
|
||||
val expectedState = initialUsernameState.copy(
|
||||
selectedType = GeneratorState.MainType.Username(
|
||||
selectedType = GeneratorState.MainType.Username.UsernameType.CatchAllEmail(),
|
||||
),
|
||||
)
|
||||
|
||||
assertEquals(expectedState, viewModel.stateFlow.value)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `UsernameTypeOptionSelect FORWARDED_EMAIL_ALIAS should switch to ForwardedEmailAlias type`() =
|
||||
runTest {
|
||||
val viewModel = createViewModel(initialUsernameState)
|
||||
|
||||
viewModel.actionChannel.trySend(
|
||||
GeneratorAction.MainType.Username.UsernameTypeOptionSelect(
|
||||
usernameTypeOption = GeneratorState
|
||||
.MainType
|
||||
.Username
|
||||
.UsernameTypeOption
|
||||
.FORWARDED_EMAIL_ALIAS,
|
||||
),
|
||||
)
|
||||
|
||||
val expectedState = initialUsernameState.copy(
|
||||
selectedType = GeneratorState.MainType.Username(
|
||||
selectedType = GeneratorState
|
||||
.MainType
|
||||
.Username
|
||||
.UsernameType
|
||||
.ForwardedEmailAlias(),
|
||||
),
|
||||
)
|
||||
|
||||
assertEquals(expectedState, viewModel.stateFlow.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `UsernameTypeOptionSelect RANDOM_WORD should switch to RandomWord type`() = runTest {
|
||||
val viewModel = createViewModel(initialUsernameState)
|
||||
|
||||
viewModel.actionChannel.trySend(
|
||||
GeneratorAction.MainType.Username.UsernameTypeOptionSelect(
|
||||
usernameTypeOption = GeneratorState
|
||||
.MainType
|
||||
.Username
|
||||
.UsernameTypeOption
|
||||
.RANDOM_WORD,
|
||||
),
|
||||
)
|
||||
|
||||
val expectedState = initialUsernameState.copy(
|
||||
selectedType = GeneratorState.MainType.Username(
|
||||
selectedType = GeneratorState.MainType.Username.UsernameType.RandomWord(),
|
||||
),
|
||||
)
|
||||
|
||||
assertEquals(expectedState, viewModel.stateFlow.value)
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class PasswordActions {
|
||||
private val defaultPasswordState = createPasswordState()
|
||||
|
@ -281,7 +385,7 @@ class GeneratorViewModelTest : BaseViewModelTest() {
|
|||
fakeGeneratorRepository.setMockGeneratePasswordResult(
|
||||
GeneratedPasswordResult.Success("defaultPassword"),
|
||||
)
|
||||
viewModel = GeneratorViewModel(initialSavedStateHandle, fakeGeneratorRepository)
|
||||
viewModel = GeneratorViewModel(initialPasscodeSavedStateHandle, fakeGeneratorRepository)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
|
@ -680,6 +784,48 @@ class GeneratorViewModelTest : BaseViewModelTest() {
|
|||
assertEquals(expectedState, viewModel.stateFlow.value)
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class PlusAddressedEmailActions {
|
||||
private val defaultPlusAddressedEmailState = createPlusAddressedEmailState()
|
||||
private lateinit var viewModel: GeneratorViewModel
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
viewModel = GeneratorViewModel(usernameSavedStateHandle, fakeGeneratorRepository)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `EmailTextChange should update email correctly`() =
|
||||
runTest {
|
||||
val newEmail = "test@example.com"
|
||||
viewModel.actionChannel.trySend(
|
||||
GeneratorAction
|
||||
.MainType
|
||||
.Username
|
||||
.UsernameType
|
||||
.PlusAddressedEmail
|
||||
.EmailTextChange(
|
||||
email = newEmail,
|
||||
),
|
||||
)
|
||||
|
||||
val expectedState = defaultPlusAddressedEmailState.copy(
|
||||
selectedType = GeneratorState.MainType.Username(
|
||||
selectedType = GeneratorState
|
||||
.MainType
|
||||
.Username
|
||||
.UsernameType
|
||||
.PlusAddressedEmail(
|
||||
email = newEmail,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
assertEquals(expectedState, viewModel.stateFlow.value)
|
||||
}
|
||||
}
|
||||
//region Helper Functions
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
|
@ -729,10 +875,18 @@ class GeneratorViewModelTest : BaseViewModelTest() {
|
|||
),
|
||||
)
|
||||
|
||||
private fun createUsernameState(): GeneratorState = GeneratorState(
|
||||
generatedText = "defaultUsername",
|
||||
selectedType = GeneratorState.MainType.Username(),
|
||||
)
|
||||
private fun createPlusAddressedEmailState(
|
||||
generatedText: String = "defaultPlusAddressedEmail",
|
||||
email: String = "defaultEmail",
|
||||
): GeneratorState =
|
||||
GeneratorState(
|
||||
generatedText = generatedText,
|
||||
selectedType = GeneratorState.MainType.Username(
|
||||
GeneratorState.MainType.Username.UsernameType.PlusAddressedEmail(
|
||||
email = email,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
private fun createSavedStateHandleWithState(state: GeneratorState) =
|
||||
SavedStateHandle().apply {
|
||||
|
@ -740,7 +894,7 @@ class GeneratorViewModelTest : BaseViewModelTest() {
|
|||
}
|
||||
|
||||
private fun createViewModel(
|
||||
state: GeneratorState? = initialState,
|
||||
state: GeneratorState? = initialPasscodeState,
|
||||
): GeneratorViewModel = GeneratorViewModel(
|
||||
savedStateHandle = SavedStateHandle().apply { set("state", state) },
|
||||
generatorRepository = fakeGeneratorRepository,
|
||||
|
|
Loading…
Add table
Reference in a new issue