mirror of
https://github.com/bitwarden/android.git
synced 2025-02-16 11:59:57 +03:00
PM-10692 pass a generated password back to the complete registration … (#3806)
This commit is contained in:
parent
666c165b6f
commit
76a3265bbb
10 changed files with 229 additions and 92 deletions
|
@ -10,6 +10,7 @@ import com.x8bit.bitwarden.ui.auth.feature.checkemail.checkEmailDestination
|
||||||
import com.x8bit.bitwarden.ui.auth.feature.checkemail.navigateToCheckEmail
|
import com.x8bit.bitwarden.ui.auth.feature.checkemail.navigateToCheckEmail
|
||||||
import com.x8bit.bitwarden.ui.auth.feature.completeregistration.completeRegistrationDestination
|
import com.x8bit.bitwarden.ui.auth.feature.completeregistration.completeRegistrationDestination
|
||||||
import com.x8bit.bitwarden.ui.auth.feature.completeregistration.navigateToCompleteRegistration
|
import com.x8bit.bitwarden.ui.auth.feature.completeregistration.navigateToCompleteRegistration
|
||||||
|
import com.x8bit.bitwarden.ui.auth.feature.completeregistration.popUpToCompleteRegistration
|
||||||
import com.x8bit.bitwarden.ui.auth.feature.createaccount.createAccountDestination
|
import com.x8bit.bitwarden.ui.auth.feature.createaccount.createAccountDestination
|
||||||
import com.x8bit.bitwarden.ui.auth.feature.createaccount.navigateToCreateAccount
|
import com.x8bit.bitwarden.ui.auth.feature.createaccount.navigateToCreateAccount
|
||||||
import com.x8bit.bitwarden.ui.auth.feature.enterprisesignon.enterpriseSignOnDestination
|
import com.x8bit.bitwarden.ui.auth.feature.enterprisesignon.enterpriseSignOnDestination
|
||||||
|
@ -184,6 +185,9 @@ fun NavGraphBuilder.authGraph(
|
||||||
masterPasswordGeneratorDestination(
|
masterPasswordGeneratorDestination(
|
||||||
onNavigateBack = { navController.popBackStack() },
|
onNavigateBack = { navController.popBackStack() },
|
||||||
onNavigateToPreventLockout = { navController.navigateToPreventAccountLockout() },
|
onNavigateToPreventLockout = { navController.navigateToPreventAccountLockout() },
|
||||||
|
onNavigateBackWithPassword = {
|
||||||
|
navController.popUpToCompleteRegistration()
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,3 +72,10 @@ fun NavGraphBuilder.completeRegistrationDestination(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pop up to the complete registration screen.
|
||||||
|
*/
|
||||||
|
fun NavController.popUpToCompleteRegistration() {
|
||||||
|
popBackStack(route = COMPLETE_REGISTRATION_ROUTE, inclusive = false)
|
||||||
|
}
|
||||||
|
|
|
@ -216,7 +216,8 @@ private fun CompleteRegistrationContent(
|
||||||
callToActionText = stringResource(id = R.string.learn_more),
|
callToActionText = stringResource(id = R.string.learn_more),
|
||||||
onCardClicked = handler.onMakeStrongPassword,
|
onCardClicked = handler.onMakeStrongPassword,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth()
|
||||||
|
.standardHorizontalMargin(),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
LegacyHeaderContent(
|
LegacyHeaderContent(
|
||||||
|
|
|
@ -12,6 +12,8 @@ import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
|
||||||
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
|
import com.x8bit.bitwarden.data.platform.manager.SpecialCircumstanceManager
|
||||||
import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
|
import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
|
||||||
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
||||||
|
import com.x8bit.bitwarden.data.tools.generator.repository.GeneratorRepository
|
||||||
|
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratorResult
|
||||||
import com.x8bit.bitwarden.ui.auth.feature.completeregistration.CompleteRegistrationAction.BackClick
|
import com.x8bit.bitwarden.ui.auth.feature.completeregistration.CompleteRegistrationAction.BackClick
|
||||||
import com.x8bit.bitwarden.ui.auth.feature.completeregistration.CompleteRegistrationAction.CheckDataBreachesToggle
|
import com.x8bit.bitwarden.ui.auth.feature.completeregistration.CompleteRegistrationAction.CheckDataBreachesToggle
|
||||||
import com.x8bit.bitwarden.ui.auth.feature.completeregistration.CompleteRegistrationAction.ConfirmPasswordInputChange
|
import com.x8bit.bitwarden.ui.auth.feature.completeregistration.CompleteRegistrationAction.ConfirmPasswordInputChange
|
||||||
|
@ -28,6 +30,7 @@ import com.x8bit.bitwarden.ui.platform.base.util.isValidEmail
|
||||||
import com.x8bit.bitwarden.ui.platform.components.dialog.BasicDialogState
|
import com.x8bit.bitwarden.ui.platform.components.dialog.BasicDialogState
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.flow.filterIsInstance
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
@ -48,12 +51,12 @@ private const val MIN_PASSWORD_LENGTH = 12
|
||||||
class CompleteRegistrationViewModel @Inject constructor(
|
class CompleteRegistrationViewModel @Inject constructor(
|
||||||
savedStateHandle: SavedStateHandle,
|
savedStateHandle: SavedStateHandle,
|
||||||
featureFlagManager: FeatureFlagManager,
|
featureFlagManager: FeatureFlagManager,
|
||||||
|
generatorRepository: GeneratorRepository,
|
||||||
private val authRepository: AuthRepository,
|
private val authRepository: AuthRepository,
|
||||||
private val environmentRepository: EnvironmentRepository,
|
private val environmentRepository: EnvironmentRepository,
|
||||||
private val specialCircumstanceManager: SpecialCircumstanceManager,
|
private val specialCircumstanceManager: SpecialCircumstanceManager,
|
||||||
) : BaseViewModel<CompleteRegistrationState, CompleteRegistrationEvent, CompleteRegistrationAction>(
|
) : BaseViewModel<CompleteRegistrationState, CompleteRegistrationEvent, CompleteRegistrationAction>(
|
||||||
initialState = savedStateHandle[KEY_STATE]
|
initialState = savedStateHandle[KEY_STATE] ?: run {
|
||||||
?: run {
|
|
||||||
val args = CompleteRegistrationArgs(savedStateHandle)
|
val args = CompleteRegistrationArgs(savedStateHandle)
|
||||||
CompleteRegistrationState(
|
CompleteRegistrationState(
|
||||||
userEmail = args.emailAddress,
|
userEmail = args.emailAddress,
|
||||||
|
@ -91,6 +94,15 @@ class CompleteRegistrationViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
.onEach(::sendAction)
|
.onEach(::sendAction)
|
||||||
.launchIn(viewModelScope)
|
.launchIn(viewModelScope)
|
||||||
|
|
||||||
|
generatorRepository
|
||||||
|
.generatorResultFlow
|
||||||
|
.filterIsInstance<GeneratorResult.Password>()
|
||||||
|
.map {
|
||||||
|
Internal.GeneratedPasswordResult(generatedPassword = it.password)
|
||||||
|
}
|
||||||
|
.onEach(::sendAction)
|
||||||
|
.launchIn(viewModelScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
|
@ -124,9 +136,25 @@ class CompleteRegistrationViewModel @Inject constructor(
|
||||||
|
|
||||||
CompleteRegistrationAction.CallToActionClick -> handleCallToActionClick()
|
CompleteRegistrationAction.CallToActionClick -> handleCallToActionClick()
|
||||||
is Internal.UpdateOnboardingFeatureState -> handleUpdateOnboardingFeatureState(action)
|
is Internal.UpdateOnboardingFeatureState -> handleUpdateOnboardingFeatureState(action)
|
||||||
|
is Internal.GeneratedPasswordResult -> handleGeneratedPasswordResult(
|
||||||
|
action,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleGeneratedPasswordResult(
|
||||||
|
action: Internal.GeneratedPasswordResult,
|
||||||
|
) {
|
||||||
|
val password = action.generatedPassword
|
||||||
|
mutableStateFlow.update {
|
||||||
|
it.copy(
|
||||||
|
passwordInput = password,
|
||||||
|
confirmPasswordInput = password,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
checkPasswordStrength(input = password)
|
||||||
|
}
|
||||||
|
|
||||||
private fun verifyEmailAddress() {
|
private fun verifyEmailAddress() {
|
||||||
if (!state.fromEmail) {
|
if (!state.fromEmail) {
|
||||||
return
|
return
|
||||||
|
@ -262,21 +290,7 @@ class CompleteRegistrationViewModel @Inject constructor(
|
||||||
private fun handlePasswordInputChanged(action: PasswordInputChange) {
|
private fun handlePasswordInputChanged(action: PasswordInputChange) {
|
||||||
// Update input:
|
// Update input:
|
||||||
mutableStateFlow.update { it.copy(passwordInput = action.input) }
|
mutableStateFlow.update { it.copy(passwordInput = action.input) }
|
||||||
// Update password strength:
|
checkPasswordStrength(action.input)
|
||||||
passwordStrengthJob.cancel()
|
|
||||||
if (action.input.isEmpty()) {
|
|
||||||
mutableStateFlow.update {
|
|
||||||
it.copy(passwordStrengthState = PasswordStrengthState.NONE)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
passwordStrengthJob = viewModelScope.launch {
|
|
||||||
val result = authRepository.getPasswordStrength(
|
|
||||||
email = state.userEmail,
|
|
||||||
password = action.input,
|
|
||||||
)
|
|
||||||
trySendAction(ReceivePasswordStrengthResult(result))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleConfirmPasswordInputChanged(action: ConfirmPasswordInputChange) {
|
private fun handleConfirmPasswordInputChanged(action: ConfirmPasswordInputChange) {
|
||||||
|
@ -367,6 +381,24 @@ class CompleteRegistrationViewModel @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun checkPasswordStrength(input: String) {
|
||||||
|
// Update password strength:
|
||||||
|
passwordStrengthJob.cancel()
|
||||||
|
if (input.isEmpty()) {
|
||||||
|
mutableStateFlow.update {
|
||||||
|
it.copy(passwordStrengthState = PasswordStrengthState.NONE)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
passwordStrengthJob = viewModelScope.launch {
|
||||||
|
val result = authRepository.getPasswordStrength(
|
||||||
|
email = state.userEmail,
|
||||||
|
password = input,
|
||||||
|
)
|
||||||
|
trySendAction(ReceivePasswordStrengthResult(result))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -564,5 +596,10 @@ sealed class CompleteRegistrationAction {
|
||||||
* Indicate on boarding feature state has been updated.
|
* Indicate on boarding feature state has been updated.
|
||||||
*/
|
*/
|
||||||
data class UpdateOnboardingFeatureState(val newValue: Boolean) : Internal()
|
data class UpdateOnboardingFeatureState(val newValue: Boolean) : Internal()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates a generated password has been received.
|
||||||
|
*/
|
||||||
|
data class GeneratedPasswordResult(val generatedPassword: String) : Internal()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ fun NavController.navigateToMasterPasswordGenerator(navOptions: NavOptions? = nu
|
||||||
fun NavGraphBuilder.masterPasswordGeneratorDestination(
|
fun NavGraphBuilder.masterPasswordGeneratorDestination(
|
||||||
onNavigateBack: () -> Unit,
|
onNavigateBack: () -> Unit,
|
||||||
onNavigateToPreventLockout: () -> Unit,
|
onNavigateToPreventLockout: () -> Unit,
|
||||||
|
onNavigateBackWithPassword: () -> Unit,
|
||||||
) {
|
) {
|
||||||
composableWithSlideTransitions(
|
composableWithSlideTransitions(
|
||||||
route = MASTER_PASSWORD_GENERATOR,
|
route = MASTER_PASSWORD_GENERATOR,
|
||||||
|
@ -27,6 +28,7 @@ fun NavGraphBuilder.masterPasswordGeneratorDestination(
|
||||||
MasterPasswordGeneratorScreen(
|
MasterPasswordGeneratorScreen(
|
||||||
onNavigateBack = onNavigateBack,
|
onNavigateBack = onNavigateBack,
|
||||||
onNavigateToPreventLockout = onNavigateToPreventLockout,
|
onNavigateToPreventLockout = onNavigateToPreventLockout,
|
||||||
|
onNavigateBackWithPassword = onNavigateBackWithPassword,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,6 +53,7 @@ import com.x8bit.bitwarden.ui.platform.theme.LocalNonMaterialTypography
|
||||||
fun MasterPasswordGeneratorScreen(
|
fun MasterPasswordGeneratorScreen(
|
||||||
onNavigateBack: () -> Unit,
|
onNavigateBack: () -> Unit,
|
||||||
onNavigateToPreventLockout: () -> Unit,
|
onNavigateToPreventLockout: () -> Unit,
|
||||||
|
onNavigateBackWithPassword: () -> Unit,
|
||||||
viewModel: MasterPasswordGeneratorViewModel = hiltViewModel(),
|
viewModel: MasterPasswordGeneratorViewModel = hiltViewModel(),
|
||||||
) {
|
) {
|
||||||
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
|
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
|
||||||
|
@ -71,6 +72,10 @@ fun MasterPasswordGeneratorScreen(
|
||||||
duration = SnackbarDuration.Short,
|
duration = SnackbarDuration.Short,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is MasterPasswordGeneratorEvent.NavigateBackToRegistration -> {
|
||||||
|
onNavigateBackWithPassword()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ import com.x8bit.bitwarden.data.platform.manager.PolicyManager
|
||||||
import com.x8bit.bitwarden.data.platform.manager.util.getActivePolicies
|
import com.x8bit.bitwarden.data.platform.manager.util.getActivePolicies
|
||||||
import com.x8bit.bitwarden.data.tools.generator.repository.GeneratorRepository
|
import com.x8bit.bitwarden.data.tools.generator.repository.GeneratorRepository
|
||||||
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedPassphraseResult
|
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedPassphraseResult
|
||||||
|
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratorResult
|
||||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
||||||
import com.x8bit.bitwarden.ui.platform.base.util.Text
|
import com.x8bit.bitwarden.ui.platform.base.util.Text
|
||||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||||
|
@ -73,7 +74,8 @@ class MasterPasswordGeneratorViewModel @Inject constructor(
|
||||||
private fun handleBackAction() = sendEvent(MasterPasswordGeneratorEvent.NavigateBack)
|
private fun handleBackAction() = sendEvent(MasterPasswordGeneratorEvent.NavigateBack)
|
||||||
|
|
||||||
private fun handleSavePasswordAction() {
|
private fun handleSavePasswordAction() {
|
||||||
// TODO [PM-10692](https://bitwarden.atlassian.net/browse/PM-10692)
|
generatorRepository.emitGeneratorResult(GeneratorResult.Password(state.generatedPassword))
|
||||||
|
sendEvent(MasterPasswordGeneratorEvent.NavigateBackToRegistration)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handlePreventLockoutAction() =
|
private fun handlePreventLockoutAction() =
|
||||||
|
@ -163,6 +165,11 @@ sealed class MasterPasswordGeneratorEvent {
|
||||||
* Show a Snackbar message.
|
* Show a Snackbar message.
|
||||||
*/
|
*/
|
||||||
data class ShowSnackbar(val text: Text) : MasterPasswordGeneratorEvent()
|
data class ShowSnackbar(val text: Text) : MasterPasswordGeneratorEvent()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigate back to the complete registration screen.
|
||||||
|
*/
|
||||||
|
data object NavigateBackToRegistration : MasterPasswordGeneratorEvent()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -20,6 +20,9 @@ import com.x8bit.bitwarden.data.platform.manager.model.CompleteRegistrationData
|
||||||
import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
|
import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
|
||||||
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
|
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
|
||||||
import com.x8bit.bitwarden.data.platform.repository.util.FakeEnvironmentRepository
|
import com.x8bit.bitwarden.data.platform.repository.util.FakeEnvironmentRepository
|
||||||
|
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
|
||||||
|
import com.x8bit.bitwarden.data.tools.generator.repository.GeneratorRepository
|
||||||
|
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratorResult
|
||||||
import com.x8bit.bitwarden.ui.auth.feature.completeregistration.CompleteRegistrationAction.BackClick
|
import com.x8bit.bitwarden.ui.auth.feature.completeregistration.CompleteRegistrationAction.BackClick
|
||||||
import com.x8bit.bitwarden.ui.auth.feature.completeregistration.CompleteRegistrationAction.ConfirmPasswordInputChange
|
import com.x8bit.bitwarden.ui.auth.feature.completeregistration.CompleteRegistrationAction.ConfirmPasswordInputChange
|
||||||
import com.x8bit.bitwarden.ui.auth.feature.completeregistration.CompleteRegistrationAction.Internal.ReceivePasswordStrengthResult
|
import com.x8bit.bitwarden.ui.auth.feature.completeregistration.CompleteRegistrationAction.Internal.ReceivePasswordStrengthResult
|
||||||
|
@ -61,6 +64,10 @@ class CompleteRegistrationViewModelTest : BaseViewModelTest() {
|
||||||
every { getFeatureFlag(FlagKey.OnboardingFlow) } returns false
|
every { getFeatureFlag(FlagKey.OnboardingFlow) } returns false
|
||||||
every { getFeatureFlagFlow(FlagKey.OnboardingFlow) } returns mutableFeatureFlagFlow
|
every { getFeatureFlagFlow(FlagKey.OnboardingFlow) } returns mutableFeatureFlagFlow
|
||||||
}
|
}
|
||||||
|
private val mutableGeneratorResultFlow = bufferedMutableSharedFlow<GeneratorResult>()
|
||||||
|
private val generatorRepository = mockk<GeneratorRepository>(relaxed = true) {
|
||||||
|
every { generatorResultFlow } returns mutableGeneratorResultFlow
|
||||||
|
}
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
|
@ -95,14 +102,14 @@ class CompleteRegistrationViewModelTest : BaseViewModelTest() {
|
||||||
environmentRepository = fakeEnvironmentRepository,
|
environmentRepository = fakeEnvironmentRepository,
|
||||||
specialCircumstanceManager = specialCircumstanceManager,
|
specialCircumstanceManager = specialCircumstanceManager,
|
||||||
featureFlagManager = featureFlagManager,
|
featureFlagManager = featureFlagManager,
|
||||||
|
generatorRepository = generatorRepository,
|
||||||
)
|
)
|
||||||
viewModel.onCleared()
|
viewModel.onCleared()
|
||||||
assertTrue(specialCircumstanceManager.specialCircumstance == null)
|
assertTrue(specialCircumstanceManager.specialCircumstance == null)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `Password below 12 chars should have non-valid state`() =
|
fun `Password below 12 chars should have non-valid state`() = runTest {
|
||||||
runTest {
|
|
||||||
val input = "abcdefghikl"
|
val input = "abcdefghikl"
|
||||||
coEvery {
|
coEvery {
|
||||||
mockAuthRepository.getPasswordStrength(EMAIL, input)
|
mockAuthRepository.getPasswordStrength(EMAIL, input)
|
||||||
|
@ -114,8 +121,7 @@ class CompleteRegistrationViewModelTest : BaseViewModelTest() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `Passwords not matching should have non-valid state`() =
|
fun `Passwords not matching should have non-valid state`() = runTest {
|
||||||
runTest {
|
|
||||||
coEvery {
|
coEvery {
|
||||||
mockAuthRepository.getPasswordStrength(EMAIL, PASSWORD)
|
mockAuthRepository.getPasswordStrength(EMAIL, PASSWORD)
|
||||||
} returns PasswordStrengthResult.Error
|
} returns PasswordStrengthResult.Error
|
||||||
|
@ -344,8 +350,7 @@ class CompleteRegistrationViewModelTest : BaseViewModelTest() {
|
||||||
)
|
)
|
||||||
} returns RegisterResult.WeakPassword
|
} returns RegisterResult.WeakPassword
|
||||||
}
|
}
|
||||||
val initialState = VALID_INPUT_STATE
|
val initialState = VALID_INPUT_STATE.copy(
|
||||||
.copy(
|
|
||||||
passwordStrengthState = PasswordStrengthState.WEAK_1,
|
passwordStrengthState = PasswordStrengthState.WEAK_1,
|
||||||
isCheckDataBreachesToggled = true,
|
isCheckDataBreachesToggled = true,
|
||||||
)
|
)
|
||||||
|
@ -418,6 +423,47 @@ class CompleteRegistrationViewModelTest : BaseViewModelTest() {
|
||||||
coVerify { mockAuthRepository.getPasswordStrength(EMAIL, PASSWORD) }
|
coVerify { mockAuthRepository.getPasswordStrength(EMAIL, PASSWORD) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Empty PasswordInputChange update should result in password strength being NONE`() =
|
||||||
|
runTest {
|
||||||
|
val viewModel = createCompleteRegistrationViewModel(
|
||||||
|
completeRegistrationState = DEFAULT_STATE.copy(
|
||||||
|
passwordInput = PASSWORD,
|
||||||
|
passwordStrengthState = PasswordStrengthState.STRONG,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
viewModel.trySendAction(PasswordInputChange(""))
|
||||||
|
val expectedStrengthUpdateState = DEFAULT_STATE.copy(
|
||||||
|
passwordInput = "",
|
||||||
|
passwordStrengthState = PasswordStrengthState.NONE,
|
||||||
|
)
|
||||||
|
viewModel.stateFlow.test {
|
||||||
|
assertEquals(expectedStrengthUpdateState, awaitItem())
|
||||||
|
}
|
||||||
|
coVerify(exactly = 0) { mockAuthRepository.getPasswordStrength(EMAIL, PASSWORD) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `Internal GeneratedPasswordResult update passwordInput and confirmPasswordInput and call getPasswordStrength`() =
|
||||||
|
runTest {
|
||||||
|
coEvery {
|
||||||
|
mockAuthRepository.getPasswordStrength(EMAIL, PASSWORD)
|
||||||
|
} returns PasswordStrengthResult.Error
|
||||||
|
val viewModel = createCompleteRegistrationViewModel()
|
||||||
|
mutableGeneratorResultFlow.emit(GeneratorResult.Password(PASSWORD))
|
||||||
|
viewModel.stateFlow.test {
|
||||||
|
assertEquals(
|
||||||
|
DEFAULT_STATE.copy(
|
||||||
|
passwordInput = PASSWORD,
|
||||||
|
confirmPasswordInput = PASSWORD,
|
||||||
|
),
|
||||||
|
awaitItem(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
coVerify { mockAuthRepository.getPasswordStrength(EMAIL, PASSWORD) }
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `CheckDataBreachesToggle should change isCheckDataBreachesToggled`() = runTest {
|
fun `CheckDataBreachesToggle should change isCheckDataBreachesToggled`() = runTest {
|
||||||
val viewModel = createCompleteRegistrationViewModel()
|
val viewModel = createCompleteRegistrationViewModel()
|
||||||
|
@ -565,8 +611,7 @@ class CompleteRegistrationViewModelTest : BaseViewModelTest() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `CreateAccountClick with no email not should show dialog`() =
|
fun `CreateAccountClick with no email not should show dialog`() = runTest {
|
||||||
runTest {
|
|
||||||
coEvery {
|
coEvery {
|
||||||
mockAuthRepository.getPasswordStrength("", PASSWORD)
|
mockAuthRepository.getPasswordStrength("", PASSWORD)
|
||||||
} returns PasswordStrengthResult.Error
|
} returns PasswordStrengthResult.Error
|
||||||
|
@ -580,8 +625,9 @@ class CompleteRegistrationViewModelTest : BaseViewModelTest() {
|
||||||
dialog = CompleteRegistrationDialog.Error(
|
dialog = CompleteRegistrationDialog.Error(
|
||||||
BasicDialogState.Shown(
|
BasicDialogState.Shown(
|
||||||
title = R.string.an_error_has_occurred.asText(),
|
title = R.string.an_error_has_occurred.asText(),
|
||||||
message = R.string.validation_field_required
|
message = R.string.validation_field_required.asText(
|
||||||
.asText(R.string.email_address.asText()),
|
R.string.email_address.asText(),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -594,8 +640,7 @@ class CompleteRegistrationViewModelTest : BaseViewModelTest() {
|
||||||
private fun createCompleteRegistrationViewModel(
|
private fun createCompleteRegistrationViewModel(
|
||||||
completeRegistrationState: CompleteRegistrationState? = DEFAULT_STATE,
|
completeRegistrationState: CompleteRegistrationState? = DEFAULT_STATE,
|
||||||
authRepository: AuthRepository = mockAuthRepository,
|
authRepository: AuthRepository = mockAuthRepository,
|
||||||
): CompleteRegistrationViewModel =
|
): CompleteRegistrationViewModel = CompleteRegistrationViewModel(
|
||||||
CompleteRegistrationViewModel(
|
|
||||||
savedStateHandle = SavedStateHandle(
|
savedStateHandle = SavedStateHandle(
|
||||||
mapOf(
|
mapOf(
|
||||||
"state" to completeRegistrationState,
|
"state" to completeRegistrationState,
|
||||||
|
@ -605,6 +650,7 @@ class CompleteRegistrationViewModelTest : BaseViewModelTest() {
|
||||||
environmentRepository = fakeEnvironmentRepository,
|
environmentRepository = fakeEnvironmentRepository,
|
||||||
specialCircumstanceManager = specialCircumstanceManager,
|
specialCircumstanceManager = specialCircumstanceManager,
|
||||||
featureFlagManager = featureFlagManager,
|
featureFlagManager = featureFlagManager,
|
||||||
|
generatorRepository = generatorRepository,
|
||||||
)
|
)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
@ -20,6 +20,7 @@ import org.junit.Test
|
||||||
class MasterPasswordGeneratorScreenTest : BaseComposeTest() {
|
class MasterPasswordGeneratorScreenTest : BaseComposeTest() {
|
||||||
private var onNavigateBackCalled = false
|
private var onNavigateBackCalled = false
|
||||||
private var onNavigateToPreventLockoutCalled = false
|
private var onNavigateToPreventLockoutCalled = false
|
||||||
|
private var navigateBackWithPasswordCalled = false
|
||||||
private val mutableEventFlow = bufferedMutableSharedFlow<MasterPasswordGeneratorEvent>()
|
private val mutableEventFlow = bufferedMutableSharedFlow<MasterPasswordGeneratorEvent>()
|
||||||
private val mutableStateFlow = MutableStateFlow(
|
private val mutableStateFlow = MutableStateFlow(
|
||||||
value = MasterPasswordGeneratorState(generatedPassword = "-"),
|
value = MasterPasswordGeneratorState(generatedPassword = "-"),
|
||||||
|
@ -35,6 +36,7 @@ class MasterPasswordGeneratorScreenTest : BaseComposeTest() {
|
||||||
MasterPasswordGeneratorScreen(
|
MasterPasswordGeneratorScreen(
|
||||||
onNavigateBack = { onNavigateBackCalled = true },
|
onNavigateBack = { onNavigateBackCalled = true },
|
||||||
onNavigateToPreventLockout = { onNavigateToPreventLockoutCalled = true },
|
onNavigateToPreventLockout = { onNavigateToPreventLockoutCalled = true },
|
||||||
|
onNavigateBackWithPassword = { navigateBackWithPasswordCalled = true },
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -42,7 +44,7 @@ class MasterPasswordGeneratorScreenTest : BaseComposeTest() {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `Generated password field state should update with ViewModel state`() {
|
fun `Generated password field state should update with ViewModel state`() {
|
||||||
val updatedValue = "soup-r-stronk-pazzwerd"
|
val updatedValue = PASSWORD_INPUT
|
||||||
mutableStateFlow.update { it.copy(generatedPassword = updatedValue) }
|
mutableStateFlow.update { it.copy(generatedPassword = updatedValue) }
|
||||||
|
|
||||||
composeTestRule
|
composeTestRule
|
||||||
|
@ -104,4 +106,15 @@ class MasterPasswordGeneratorScreenTest : BaseComposeTest() {
|
||||||
|
|
||||||
verify { viewModel.trySendAction(MasterPasswordGeneratorAction.PreventLockoutClickAction) }
|
verify { viewModel.trySendAction(MasterPasswordGeneratorAction.PreventLockoutClickAction) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Verify navigating back with password invokes the lambda`() {
|
||||||
|
mutableEventFlow.tryEmit(
|
||||||
|
MasterPasswordGeneratorEvent.NavigateBackToRegistration,
|
||||||
|
)
|
||||||
|
|
||||||
|
assertTrue(navigateBackWithPasswordCalled)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val PASSWORD_INPUT = "password1234"
|
||||||
|
|
|
@ -124,6 +124,21 @@ class MasterPasswordGeneratorViewModelTest : BaseViewModelTest() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `NavigateBackWithPassword event is sent when SavePasswordClickAction is handled`() =
|
||||||
|
runTest {
|
||||||
|
val viewModel = createViewModel(
|
||||||
|
initialState = MasterPasswordGeneratorState(generatedPassword = "saved-pw"),
|
||||||
|
)
|
||||||
|
viewModel.eventFlow.test {
|
||||||
|
viewModel.trySendAction(MasterPasswordGeneratorAction.SavePasswordClickAction)
|
||||||
|
assertEquals(
|
||||||
|
MasterPasswordGeneratorEvent.NavigateBackToRegistration,
|
||||||
|
awaitItem(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// region helpers
|
// region helpers
|
||||||
|
|
||||||
private fun createViewModel(
|
private fun createViewModel(
|
||||||
|
|
Loading…
Add table
Reference in a new issue