From 76a3265bbb01de8d09fab819d7dc08ae6c285ce2 Mon Sep 17 00:00:00 2001 From: Dave Severns <149429124+dseverns-livefront@users.noreply.github.com> Date: Mon, 26 Aug 2024 08:56:28 -0400 Subject: [PATCH] =?UTF-8?q?PM-10692=20pass=20a=20generated=20password=20ba?= =?UTF-8?q?ck=20to=20the=20complete=20registration=20=E2=80=A6=20(#3806)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/auth/feature/auth/AuthNavigation.kt | 4 + .../CompleteRegistrationNavigation.kt | 7 + .../CompleteRegistrationScreen.kt | 3 +- .../CompleteRegistrationViewModel.kt | 101 +++++++---- .../MasterPasswordGeneratorNavigation.kt | 2 + .../MasterPasswordGeneratorScreen.kt | 5 + .../MasterPasswordGeneratorViewModel.kt | 9 +- .../CompleteRegistrationViewModelTest.kt | 160 +++++++++++------- .../MasterPasswordGeneratorScreenTest.kt | 15 +- .../MasterPasswordGeneratorViewModelTest.kt | 15 ++ 10 files changed, 229 insertions(+), 92 deletions(-) diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/auth/AuthNavigation.kt b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/auth/AuthNavigation.kt index 717ee954a..ab5a55076 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/auth/AuthNavigation.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/auth/AuthNavigation.kt @@ -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.completeregistration.completeRegistrationDestination 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.navigateToCreateAccount import com.x8bit.bitwarden.ui.auth.feature.enterprisesignon.enterpriseSignOnDestination @@ -184,6 +185,9 @@ fun NavGraphBuilder.authGraph( masterPasswordGeneratorDestination( onNavigateBack = { navController.popBackStack() }, onNavigateToPreventLockout = { navController.navigateToPreventAccountLockout() }, + onNavigateBackWithPassword = { + navController.popUpToCompleteRegistration() + }, ) } } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/completeregistration/CompleteRegistrationNavigation.kt b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/completeregistration/CompleteRegistrationNavigation.kt index c4ff47807..ff8a22b3d 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/completeregistration/CompleteRegistrationNavigation.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/completeregistration/CompleteRegistrationNavigation.kt @@ -72,3 +72,10 @@ fun NavGraphBuilder.completeRegistrationDestination( ) } } + +/** + * Pop up to the complete registration screen. + */ +fun NavController.popUpToCompleteRegistration() { + popBackStack(route = COMPLETE_REGISTRATION_ROUTE, inclusive = false) +} diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/completeregistration/CompleteRegistrationScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/completeregistration/CompleteRegistrationScreen.kt index b384347ab..f6723da67 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/completeregistration/CompleteRegistrationScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/completeregistration/CompleteRegistrationScreen.kt @@ -216,7 +216,8 @@ private fun CompleteRegistrationContent( callToActionText = stringResource(id = R.string.learn_more), onCardClicked = handler.onMakeStrongPassword, modifier = Modifier - .fillMaxWidth(), + .fillMaxWidth() + .standardHorizontalMargin(), ) } else { LegacyHeaderContent( diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/completeregistration/CompleteRegistrationViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/completeregistration/CompleteRegistrationViewModel.kt index 03bd7acd5..3d83d4f25 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/completeregistration/CompleteRegistrationViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/completeregistration/CompleteRegistrationViewModel.kt @@ -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.model.FlagKey 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.CheckDataBreachesToggle 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 dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach @@ -48,27 +51,27 @@ private const val MIN_PASSWORD_LENGTH = 12 class CompleteRegistrationViewModel @Inject constructor( savedStateHandle: SavedStateHandle, featureFlagManager: FeatureFlagManager, + generatorRepository: GeneratorRepository, private val authRepository: AuthRepository, private val environmentRepository: EnvironmentRepository, private val specialCircumstanceManager: SpecialCircumstanceManager, ) : BaseViewModel( - initialState = savedStateHandle[KEY_STATE] - ?: run { - val args = CompleteRegistrationArgs(savedStateHandle) - CompleteRegistrationState( - userEmail = args.emailAddress, - emailVerificationToken = args.verificationToken, - fromEmail = args.fromEmail, - passwordInput = "", - confirmPasswordInput = "", - passwordHintInput = "", - isCheckDataBreachesToggled = true, - dialog = null, - passwordStrengthState = PasswordStrengthState.NONE, - onboardingEnabled = featureFlagManager.getFeatureFlag(FlagKey.OnboardingFlow), - minimumPasswordLength = MIN_PASSWORD_LENGTH, - ) - }, + initialState = savedStateHandle[KEY_STATE] ?: run { + val args = CompleteRegistrationArgs(savedStateHandle) + CompleteRegistrationState( + userEmail = args.emailAddress, + emailVerificationToken = args.verificationToken, + fromEmail = args.fromEmail, + passwordInput = "", + confirmPasswordInput = "", + passwordHintInput = "", + isCheckDataBreachesToggled = true, + dialog = null, + passwordStrengthState = PasswordStrengthState.NONE, + onboardingEnabled = featureFlagManager.getFeatureFlag(FlagKey.OnboardingFlow), + minimumPasswordLength = MIN_PASSWORD_LENGTH, + ) + }, ) { /** @@ -91,6 +94,15 @@ class CompleteRegistrationViewModel @Inject constructor( } .onEach(::sendAction) .launchIn(viewModelScope) + + generatorRepository + .generatorResultFlow + .filterIsInstance() + .map { + Internal.GeneratedPasswordResult(generatedPassword = it.password) + } + .onEach(::sendAction) + .launchIn(viewModelScope) } @VisibleForTesting @@ -124,9 +136,25 @@ class CompleteRegistrationViewModel @Inject constructor( CompleteRegistrationAction.CallToActionClick -> handleCallToActionClick() 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() { if (!state.fromEmail) { return @@ -262,21 +290,7 @@ class CompleteRegistrationViewModel @Inject constructor( private fun handlePasswordInputChanged(action: PasswordInputChange) { // Update input: mutableStateFlow.update { it.copy(passwordInput = action.input) } - // Update password strength: - 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)) - } - } + checkPasswordStrength(action.input) } 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. */ data class UpdateOnboardingFeatureState(val newValue: Boolean) : Internal() + + /** + * Indicates a generated password has been received. + */ + data class GeneratedPasswordResult(val generatedPassword: String) : Internal() } } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/masterpasswordgenerator/MasterPasswordGeneratorNavigation.kt b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/masterpasswordgenerator/MasterPasswordGeneratorNavigation.kt index 5340b6c7d..44cc5027d 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/masterpasswordgenerator/MasterPasswordGeneratorNavigation.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/masterpasswordgenerator/MasterPasswordGeneratorNavigation.kt @@ -20,6 +20,7 @@ fun NavController.navigateToMasterPasswordGenerator(navOptions: NavOptions? = nu fun NavGraphBuilder.masterPasswordGeneratorDestination( onNavigateBack: () -> Unit, onNavigateToPreventLockout: () -> Unit, + onNavigateBackWithPassword: () -> Unit, ) { composableWithSlideTransitions( route = MASTER_PASSWORD_GENERATOR, @@ -27,6 +28,7 @@ fun NavGraphBuilder.masterPasswordGeneratorDestination( MasterPasswordGeneratorScreen( onNavigateBack = onNavigateBack, onNavigateToPreventLockout = onNavigateToPreventLockout, + onNavigateBackWithPassword = onNavigateBackWithPassword, ) } } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/masterpasswordgenerator/MasterPasswordGeneratorScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/masterpasswordgenerator/MasterPasswordGeneratorScreen.kt index 7802d3a81..2a16fbaea 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/masterpasswordgenerator/MasterPasswordGeneratorScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/masterpasswordgenerator/MasterPasswordGeneratorScreen.kt @@ -53,6 +53,7 @@ import com.x8bit.bitwarden.ui.platform.theme.LocalNonMaterialTypography fun MasterPasswordGeneratorScreen( onNavigateBack: () -> Unit, onNavigateToPreventLockout: () -> Unit, + onNavigateBackWithPassword: () -> Unit, viewModel: MasterPasswordGeneratorViewModel = hiltViewModel(), ) { val state by viewModel.stateFlow.collectAsStateWithLifecycle() @@ -71,6 +72,10 @@ fun MasterPasswordGeneratorScreen( duration = SnackbarDuration.Short, ) } + + is MasterPasswordGeneratorEvent.NavigateBackToRegistration -> { + onNavigateBackWithPassword() + } } } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/masterpasswordgenerator/MasterPasswordGeneratorViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/masterpasswordgenerator/MasterPasswordGeneratorViewModel.kt index ebb17c86a..7a08d99f4 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/masterpasswordgenerator/MasterPasswordGeneratorViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/masterpasswordgenerator/MasterPasswordGeneratorViewModel.kt @@ -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.tools.generator.repository.GeneratorRepository 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.util.Text 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 handleSavePasswordAction() { - // TODO [PM-10692](https://bitwarden.atlassian.net/browse/PM-10692) + generatorRepository.emitGeneratorResult(GeneratorResult.Password(state.generatedPassword)) + sendEvent(MasterPasswordGeneratorEvent.NavigateBackToRegistration) } private fun handlePreventLockoutAction() = @@ -163,6 +165,11 @@ sealed class MasterPasswordGeneratorEvent { * Show a Snackbar message. */ data class ShowSnackbar(val text: Text) : MasterPasswordGeneratorEvent() + + /** + * Navigate back to the complete registration screen. + */ + data object NavigateBackToRegistration : MasterPasswordGeneratorEvent() } /** diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/completeregistration/CompleteRegistrationViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/completeregistration/CompleteRegistrationViewModelTest.kt index e994c9553..6996b3cff 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/completeregistration/CompleteRegistrationViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/completeregistration/CompleteRegistrationViewModelTest.kt @@ -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.SpecialCircumstance 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.ConfirmPasswordInputChange 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 { getFeatureFlagFlow(FlagKey.OnboardingFlow) } returns mutableFeatureFlagFlow } + private val mutableGeneratorResultFlow = bufferedMutableSharedFlow() + private val generatorRepository = mockk(relaxed = true) { + every { generatorResultFlow } returns mutableGeneratorResultFlow + } @BeforeEach fun setUp() { @@ -95,35 +102,34 @@ class CompleteRegistrationViewModelTest : BaseViewModelTest() { environmentRepository = fakeEnvironmentRepository, specialCircumstanceManager = specialCircumstanceManager, featureFlagManager = featureFlagManager, + generatorRepository = generatorRepository, ) viewModel.onCleared() assertTrue(specialCircumstanceManager.specialCircumstance == null) } @Test - fun `Password below 12 chars should have non-valid state`() = - runTest { - val input = "abcdefghikl" - coEvery { - mockAuthRepository.getPasswordStrength(EMAIL, input) - } returns PasswordStrengthResult.Error - val viewModel = createCompleteRegistrationViewModel() - viewModel.trySendAction(PasswordInputChange(input)) + fun `Password below 12 chars should have non-valid state`() = runTest { + val input = "abcdefghikl" + coEvery { + mockAuthRepository.getPasswordStrength(EMAIL, input) + } returns PasswordStrengthResult.Error + val viewModel = createCompleteRegistrationViewModel() + viewModel.trySendAction(PasswordInputChange(input)) - assertFalse(viewModel.stateFlow.value.validSubmissionReady) - } + assertFalse(viewModel.stateFlow.value.validSubmissionReady) + } @Test - fun `Passwords not matching should have non-valid state`() = - runTest { - coEvery { - mockAuthRepository.getPasswordStrength(EMAIL, PASSWORD) - } returns PasswordStrengthResult.Error - val viewModel = createCompleteRegistrationViewModel() - viewModel.trySendAction(PasswordInputChange(PASSWORD)) + fun `Passwords not matching should have non-valid state`() = runTest { + coEvery { + mockAuthRepository.getPasswordStrength(EMAIL, PASSWORD) + } returns PasswordStrengthResult.Error + val viewModel = createCompleteRegistrationViewModel() + viewModel.trySendAction(PasswordInputChange(PASSWORD)) - assertFalse(viewModel.stateFlow.value.validSubmissionReady) - } + assertFalse(viewModel.stateFlow.value.validSubmissionReady) + } @Test fun `CallToActionClick with all inputs valid should show and hide loading dialog`() = runTest { @@ -344,11 +350,10 @@ class CompleteRegistrationViewModelTest : BaseViewModelTest() { ) } returns RegisterResult.WeakPassword } - val initialState = VALID_INPUT_STATE - .copy( - passwordStrengthState = PasswordStrengthState.WEAK_1, - isCheckDataBreachesToggled = true, - ) + val initialState = VALID_INPUT_STATE.copy( + passwordStrengthState = PasswordStrengthState.WEAK_1, + isCheckDataBreachesToggled = true, + ) val viewModel = createCompleteRegistrationViewModel(completeRegistrationState = initialState) viewModel.trySendAction(CompleteRegistrationAction.CallToActionClick) @@ -418,6 +423,47 @@ class CompleteRegistrationViewModelTest : BaseViewModelTest() { 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 fun `CheckDataBreachesToggle should change isCheckDataBreachesToggled`() = runTest { val viewModel = createCompleteRegistrationViewModel() @@ -565,47 +611,47 @@ class CompleteRegistrationViewModelTest : BaseViewModelTest() { } @Test - fun `CreateAccountClick with no email not should show dialog`() = - runTest { - coEvery { - mockAuthRepository.getPasswordStrength("", PASSWORD) - } returns PasswordStrengthResult.Error - val viewModel = createCompleteRegistrationViewModel( - DEFAULT_STATE.copy(userEmail = ""), - ) - viewModel.trySendAction(PasswordInputChange(PASSWORD)) - val expectedState = DEFAULT_STATE.copy( - userEmail = "", - passwordInput = PASSWORD, - dialog = CompleteRegistrationDialog.Error( - BasicDialogState.Shown( - title = R.string.an_error_has_occurred.asText(), - message = R.string.validation_field_required - .asText(R.string.email_address.asText()), + fun `CreateAccountClick with no email not should show dialog`() = runTest { + coEvery { + mockAuthRepository.getPasswordStrength("", PASSWORD) + } returns PasswordStrengthResult.Error + val viewModel = createCompleteRegistrationViewModel( + DEFAULT_STATE.copy(userEmail = ""), + ) + viewModel.trySendAction(PasswordInputChange(PASSWORD)) + val expectedState = DEFAULT_STATE.copy( + userEmail = "", + passwordInput = PASSWORD, + dialog = CompleteRegistrationDialog.Error( + BasicDialogState.Shown( + title = R.string.an_error_has_occurred.asText(), + message = R.string.validation_field_required.asText( + R.string.email_address.asText(), ), ), - ) - viewModel.trySendAction(CompleteRegistrationAction.CallToActionClick) - viewModel.stateFlow.test { - assertEquals(expectedState, awaitItem()) - } + ), + ) + viewModel.trySendAction(CompleteRegistrationAction.CallToActionClick) + viewModel.stateFlow.test { + assertEquals(expectedState, awaitItem()) } + } private fun createCompleteRegistrationViewModel( completeRegistrationState: CompleteRegistrationState? = DEFAULT_STATE, authRepository: AuthRepository = mockAuthRepository, - ): CompleteRegistrationViewModel = - CompleteRegistrationViewModel( - savedStateHandle = SavedStateHandle( - mapOf( - "state" to completeRegistrationState, - ), + ): CompleteRegistrationViewModel = CompleteRegistrationViewModel( + savedStateHandle = SavedStateHandle( + mapOf( + "state" to completeRegistrationState, ), - authRepository = authRepository, - environmentRepository = fakeEnvironmentRepository, - specialCircumstanceManager = specialCircumstanceManager, - featureFlagManager = featureFlagManager, - ) + ), + authRepository = authRepository, + environmentRepository = fakeEnvironmentRepository, + specialCircumstanceManager = specialCircumstanceManager, + featureFlagManager = featureFlagManager, + generatorRepository = generatorRepository, + ) companion object { private const val PASSWORD = "longenoughtpassword" diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/masterpasswordgenerator/MasterPasswordGeneratorScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/masterpasswordgenerator/MasterPasswordGeneratorScreenTest.kt index 6d74948d9..d60e333fe 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/masterpasswordgenerator/MasterPasswordGeneratorScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/masterpasswordgenerator/MasterPasswordGeneratorScreenTest.kt @@ -20,6 +20,7 @@ import org.junit.Test class MasterPasswordGeneratorScreenTest : BaseComposeTest() { private var onNavigateBackCalled = false private var onNavigateToPreventLockoutCalled = false + private var navigateBackWithPasswordCalled = false private val mutableEventFlow = bufferedMutableSharedFlow() private val mutableStateFlow = MutableStateFlow( value = MasterPasswordGeneratorState(generatedPassword = "-"), @@ -35,6 +36,7 @@ class MasterPasswordGeneratorScreenTest : BaseComposeTest() { MasterPasswordGeneratorScreen( onNavigateBack = { onNavigateBackCalled = true }, onNavigateToPreventLockout = { onNavigateToPreventLockoutCalled = true }, + onNavigateBackWithPassword = { navigateBackWithPasswordCalled = true }, viewModel = viewModel, ) } @@ -42,7 +44,7 @@ class MasterPasswordGeneratorScreenTest : BaseComposeTest() { @Test 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) } composeTestRule @@ -104,4 +106,15 @@ class MasterPasswordGeneratorScreenTest : BaseComposeTest() { 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" diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/masterpasswordgenerator/MasterPasswordGeneratorViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/masterpasswordgenerator/MasterPasswordGeneratorViewModelTest.kt index ad0599d8f..da6562045 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/masterpasswordgenerator/MasterPasswordGeneratorViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/masterpasswordgenerator/MasterPasswordGeneratorViewModelTest.kt @@ -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 private fun createViewModel(