diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/auth/AuthNavigation.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/auth/AuthNavigation.kt index c0796e6c05..477c3b8de6 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/auth/AuthNavigation.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/auth/AuthNavigation.kt @@ -11,8 +11,6 @@ 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 import com.x8bit.bitwarden.ui.auth.feature.enterprisesignon.navigateToEnterpriseSignOn import com.x8bit.bitwarden.ui.auth.feature.environment.environmentDestination @@ -61,18 +59,6 @@ fun NavGraphBuilder.authGraph( navigation( startDestination = LandingRoute, ) { - createAccountDestination( - onNavigateBack = { navController.popBackStack() }, - onNavigateToLogin = { emailAddress, captchaToken -> - navController.navigateToLogin( - emailAddress = emailAddress, - captchaToken = captchaToken, - navOptions = navOptions { - popUpTo(route = LandingRoute) - }, - ) - }, - ) startRegistrationDestination( onNavigateBack = { navController.popBackStack() }, onNavigateToCompleteRegistration = { emailAddress, verificationToken -> @@ -121,7 +107,6 @@ fun NavGraphBuilder.authGraph( ) setPasswordDestination() landingDestination( - onNavigateToCreateAccount = { navController.navigateToCreateAccount() }, onNavigateToLogin = { emailAddress -> navController.navigateToLogin( emailAddress = emailAddress, @@ -135,7 +120,6 @@ fun NavGraphBuilder.authGraph( onNavigateToPreAuthSettings = { navController.navigateToPreAuthSettings() }, ) welcomeDestination( - onNavigateToCreateAccount = { navController.navigateToCreateAccount() }, onNavigateToLogin = { navController.navigateToLanding() }, onNavigateToStartRegistration = { navController.navigateToStartRegistration() }, ) diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/createaccount/CreateAccountNavigation.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/createaccount/CreateAccountNavigation.kt deleted file mode 100644 index 1888724d99..0000000000 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/createaccount/CreateAccountNavigation.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.x8bit.bitwarden.ui.auth.feature.createaccount - -import androidx.navigation.NavController -import androidx.navigation.NavGraphBuilder -import androidx.navigation.NavOptions -import com.bitwarden.ui.platform.base.util.composableWithSlideTransitions -import kotlinx.serialization.Serializable - -/** - * The type-safe route for the create account screen. - */ -@Serializable -data object CreateAccountRoute - -/** - * Navigate to the create account screen. - */ -fun NavController.navigateToCreateAccount(navOptions: NavOptions? = null) { - this.navigate(route = CreateAccountRoute, navOptions = navOptions) -} - -/** - * Add the create account screen to the nav graph. - */ -fun NavGraphBuilder.createAccountDestination( - onNavigateBack: () -> Unit, - onNavigateToLogin: (emailAddress: String, captchaToken: String?) -> Unit, -) { - composableWithSlideTransitions { - CreateAccountScreen( - onNavigateBack = onNavigateBack, - onNavigateToLogin = onNavigateToLogin, - ) - } -} diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/createaccount/CreateAccountScreen.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/createaccount/CreateAccountScreen.kt deleted file mode 100644 index cad98ab98b..0000000000 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/createaccount/CreateAccountScreen.kt +++ /dev/null @@ -1,329 +0,0 @@ -package com.x8bit.bitwarden.ui.auth.feature.createaccount - -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.navigationBarsPadding -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBarDefaults -import androidx.compose.material3.rememberTopAppBarState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.platform.testTag -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.semantics.CustomAccessibilityAction -import androidx.compose.ui.semantics.customActions -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.text.input.KeyboardType -import androidx.compose.ui.unit.dp -import androidx.core.net.toUri -import androidx.hilt.navigation.compose.hiltViewModel -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.bitwarden.ui.platform.base.util.EventsEffect -import com.bitwarden.ui.platform.base.util.annotatedStringResource -import com.bitwarden.ui.platform.base.util.standardHorizontalMargin -import com.bitwarden.ui.platform.components.appbar.BitwardenTopAppBar -import com.bitwarden.ui.platform.components.button.BitwardenTextButton -import com.bitwarden.ui.platform.components.model.CardStyle -import com.bitwarden.ui.platform.components.toggle.BitwardenSwitch -import com.bitwarden.ui.platform.components.util.rememberVectorPainter -import com.bitwarden.ui.platform.resource.BitwardenDrawable -import com.bitwarden.ui.platform.resource.BitwardenString -import com.bitwarden.ui.platform.theme.BitwardenTheme -import com.x8bit.bitwarden.ui.auth.feature.completeregistration.PasswordStrengthIndicator -import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.AcceptPoliciesToggle -import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.CheckDataBreachesToggle -import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.CloseClick -import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.ConfirmPasswordInputChange -import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.ContinueWithBreachedPasswordClick -import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.EmailInputChange -import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.ErrorDialogDismiss -import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.PasswordHintChange -import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.PasswordInputChange -import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.PrivacyPolicyClick -import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.SubmitClick -import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.TermsClick -import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountEvent.NavigateToPrivacyPolicy -import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountEvent.NavigateToTerms -import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenBasicDialog -import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenLoadingDialog -import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenTwoButtonDialog -import com.x8bit.bitwarden.ui.platform.components.field.BitwardenPasswordField -import com.x8bit.bitwarden.ui.platform.components.field.BitwardenTextField -import com.x8bit.bitwarden.ui.platform.components.scaffold.BitwardenScaffold -import com.x8bit.bitwarden.ui.platform.composition.LocalIntentManager -import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager - -/** - * Top level composable for the create account screen. - */ -@OptIn(ExperimentalMaterial3Api::class) -@Suppress("LongMethod") -@Composable -fun CreateAccountScreen( - onNavigateBack: () -> Unit, - onNavigateToLogin: (emailAddress: String, captchaToken: String?) -> Unit, - intentManager: IntentManager = LocalIntentManager.current, - viewModel: CreateAccountViewModel = hiltViewModel(), -) { - val state by viewModel.stateFlow.collectAsStateWithLifecycle() - EventsEffect(viewModel) { event -> - when (event) { - is NavigateToPrivacyPolicy -> { - intentManager.launchUri("https://bitwarden.com/privacy/".toUri()) - } - - is NavigateToTerms -> { - intentManager.launchUri("https://bitwarden.com/terms/".toUri()) - } - - is CreateAccountEvent.NavigateBack -> onNavigateBack.invoke() - - is CreateAccountEvent.NavigateToCaptcha -> { - intentManager.startCustomTabsActivity(uri = event.uri) - } - - is CreateAccountEvent.NavigateToLogin -> { - onNavigateToLogin( - event.email, - event.captchaToken, - ) - } - } - } - - // Show dialog if needed: - when (val dialog = state.dialog) { - is CreateAccountDialog.Error -> { - BitwardenBasicDialog( - title = dialog.title?.invoke(), - message = dialog.message(), - throwable = dialog.error, - onDismissRequest = remember(viewModel) { - { viewModel.trySendAction(ErrorDialogDismiss) } - }, - ) - } - - is CreateAccountDialog.HaveIBeenPwned -> { - BitwardenTwoButtonDialog( - title = dialog.title(), - message = dialog.message(), - confirmButtonText = stringResource(id = BitwardenString.yes), - dismissButtonText = stringResource(id = BitwardenString.no), - onConfirmClick = remember(viewModel) { - { viewModel.trySendAction(ContinueWithBreachedPasswordClick) } - }, - onDismissClick = remember(viewModel) { - { viewModel.trySendAction(ErrorDialogDismiss) } - }, - onDismissRequest = remember(viewModel) { - { viewModel.trySendAction(ErrorDialogDismiss) } - }, - ) - } - - CreateAccountDialog.Loading -> { - BitwardenLoadingDialog(text = stringResource(id = BitwardenString.create_account)) - } - - null -> Unit - } - - val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) - BitwardenScaffold( - modifier = Modifier - .fillMaxSize() - .nestedScroll(scrollBehavior.nestedScrollConnection), - topBar = { - BitwardenTopAppBar( - title = stringResource(id = BitwardenString.create_account), - scrollBehavior = scrollBehavior, - navigationIcon = rememberVectorPainter(id = BitwardenDrawable.ic_close), - navigationIconContentDescription = stringResource(id = BitwardenString.close), - onNavigationIconClick = remember(viewModel) { - { viewModel.trySendAction(CloseClick) } - }, - actions = { - BitwardenTextButton( - label = stringResource(id = BitwardenString.submit), - onClick = remember(viewModel) { - { viewModel.trySendAction(SubmitClick) } - }, - modifier = Modifier.testTag("SubmitButton"), - ) - }, - ) - }, - ) { - Column( - modifier = Modifier - .fillMaxSize() - .verticalScroll(rememberScrollState()), - ) { - Spacer(modifier = Modifier.height(height = 12.dp)) - BitwardenTextField( - label = stringResource(id = BitwardenString.email_address), - value = state.emailInput, - onValueChange = remember(viewModel) { - { viewModel.trySendAction(EmailInputChange(it)) } - }, - keyboardType = KeyboardType.Email, - textFieldTestTag = "EmailAddressEntry", - cardStyle = CardStyle.Full, - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin(), - ) - Spacer(modifier = Modifier.height(height = 8.dp)) - var showPassword by rememberSaveable { mutableStateOf(false) } - BitwardenPasswordField( - label = stringResource(id = BitwardenString.master_password), - showPassword = showPassword, - showPasswordChange = { showPassword = it }, - value = state.passwordInput, - onValueChange = remember(viewModel) { - { viewModel.trySendAction(PasswordInputChange(it)) } - }, - showPasswordTestTag = "PasswordVisibilityToggle", - supportingContent = { - PasswordStrengthIndicator( - modifier = Modifier.fillMaxWidth(), - state = state.passwordStrengthState, - currentCharacterCount = state.passwordInput.length, - ) - Text( - modifier = Modifier.fillMaxWidth(), - text = state.passwordLengthLabel(), - style = BitwardenTheme.typography.bodySmall, - color = BitwardenTheme.colorScheme.text.secondary, - ) - }, - passwordFieldTestTag = "MasterPasswordEntry", - cardStyle = CardStyle.Top(dividerPadding = 0.dp), - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin(), - ) - BitwardenPasswordField( - label = stringResource(id = BitwardenString.retype_master_password), - value = state.confirmPasswordInput, - showPassword = showPassword, - showPasswordChange = { showPassword = it }, - onValueChange = remember(viewModel) { - { viewModel.trySendAction(ConfirmPasswordInputChange(it)) } - }, - showPasswordTestTag = "ConfirmPasswordVisibilityToggle", - passwordFieldTestTag = "ConfirmMasterPasswordEntry", - cardStyle = CardStyle.Middle(dividerPadding = 0.dp), - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin(), - ) - BitwardenTextField( - label = stringResource(id = BitwardenString.master_password_hint), - value = state.passwordHintInput, - onValueChange = remember(viewModel) { - { viewModel.trySendAction(PasswordHintChange(it)) } - }, - supportingText = stringResource( - id = BitwardenString.master_password_hint_description), - textFieldTestTag = "MasterPasswordHintLabel", - cardStyle = CardStyle.Bottom, - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin(), - ) - Spacer(modifier = Modifier.height(height = 8.dp)) - BitwardenSwitch( - label = stringResource( - id = BitwardenString.check_known_data_breaches_for_this_password), - isChecked = state.isCheckDataBreachesToggled, - onCheckedChange = remember(viewModel) { - { newState -> - viewModel.trySendAction(CheckDataBreachesToggle(newState = newState)) - } - }, - cardStyle = CardStyle.Top(), - modifier = Modifier - .testTag("CheckExposedMasterPasswordToggle") - .fillMaxWidth() - .standardHorizontalMargin(), - ) - TermsAndPrivacySwitch( - isChecked = state.isAcceptPoliciesToggled, - onCheckedChange = remember(viewModel) { - { viewModel.trySendAction(AcceptPoliciesToggle(it)) } - }, - onTermsClick = remember(viewModel) { - { viewModel.trySendAction(TermsClick) } - }, - onPrivacyPolicyClick = remember(viewModel) { - { viewModel.trySendAction(PrivacyPolicyClick) } - }, - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin(), - ) - Spacer(modifier = Modifier.height(height = 16.dp)) - Spacer(modifier = Modifier.navigationBarsPadding()) - } - } -} - -@Composable -private fun TermsAndPrivacySwitch( - isChecked: Boolean, - onCheckedChange: (Boolean) -> Unit, - onTermsClick: () -> Unit, - onPrivacyPolicyClick: () -> Unit, - modifier: Modifier = Modifier, -) { - val strTerms = stringResource(id = BitwardenString.terms_of_service) - val strPrivacy = stringResource(id = BitwardenString.privacy_policy) - BitwardenSwitch( - modifier = modifier.semantics(mergeDescendants = true) { - customActions = listOf( - CustomAccessibilityAction( - label = strTerms, - action = { - onTermsClick() - true - }, - ), - CustomAccessibilityAction( - label = strPrivacy, - action = { - onPrivacyPolicyClick() - true - }, - ), - ) - }, - label = annotatedStringResource( - id = BitwardenString - .by_activating_this_switch_you_agree_to_the_terms_of_service_and_privacy_policy, - onAnnotationClick = { - when (it) { - "termsOfService" -> onTermsClick() - "privacyPolicy" -> onPrivacyPolicyClick() - } - }, - ), - isChecked = isChecked, - contentDescription = "AcceptPoliciesToggle", - onCheckedChange = onCheckedChange, - cardStyle = CardStyle.Bottom, - ) -} diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/createaccount/CreateAccountViewModel.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/createaccount/CreateAccountViewModel.kt deleted file mode 100644 index 1fb393f3ce..0000000000 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/createaccount/CreateAccountViewModel.kt +++ /dev/null @@ -1,595 +0,0 @@ -package com.x8bit.bitwarden.ui.auth.feature.createaccount - -import android.net.Uri -import android.os.Parcelable -import androidx.lifecycle.SavedStateHandle -import androidx.lifecycle.viewModelScope -import com.bitwarden.ui.platform.base.BaseViewModel -import com.bitwarden.ui.platform.base.util.isValidEmail -import com.bitwarden.ui.platform.resource.BitwardenString -import com.bitwarden.ui.util.Text -import com.bitwarden.ui.util.asText -import com.bitwarden.ui.util.concat -import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength -import com.x8bit.bitwarden.data.auth.repository.AuthRepository -import com.x8bit.bitwarden.data.auth.repository.model.PasswordStrengthResult -import com.x8bit.bitwarden.data.auth.repository.model.RegisterResult -import com.x8bit.bitwarden.data.auth.repository.util.CaptchaCallbackTokenResult -import com.x8bit.bitwarden.data.auth.repository.util.generateUriForCaptcha -import com.x8bit.bitwarden.ui.auth.feature.completeregistration.PasswordStrengthState -import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.AcceptPoliciesToggle -import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.CheckDataBreachesToggle -import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.ConfirmPasswordInputChange -import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.ContinueWithBreachedPasswordClick -import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.EmailInputChange -import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.Internal.ReceivePasswordStrengthResult -import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.PasswordHintChange -import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.PasswordInputChange -import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.PrivacyPolicyClick -import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.SubmitClick -import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.TermsClick -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.Job -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.flow.update -import kotlinx.coroutines.launch -import kotlinx.parcelize.Parcelize -import javax.inject.Inject - -private const val KEY_STATE = "state" -private const val MIN_PASSWORD_LENGTH = 12 - -/** - * Models logic for the create account screen. - */ -@Suppress("TooManyFunctions") -@HiltViewModel -class CreateAccountViewModel @Inject constructor( - savedStateHandle: SavedStateHandle, - private val authRepository: AuthRepository, -) : BaseViewModel( - initialState = savedStateHandle[KEY_STATE] - ?: CreateAccountState( - emailInput = "", - passwordInput = "", - confirmPasswordInput = "", - passwordHintInput = "", - isAcceptPoliciesToggled = false, - isCheckDataBreachesToggled = true, - dialog = null, - passwordStrengthState = PasswordStrengthState.NONE, - ), -) { - - /** - * Keeps track of async request to get password strength. Should be cancelled - * when user input changes. - */ - private var passwordStrengthJob: Job = Job().apply { complete() } - - init { - // As state updates, write to saved state handle: - stateFlow - .onEach { savedStateHandle[KEY_STATE] = it } - .launchIn(viewModelScope) - authRepository - .captchaTokenResultFlow - .onEach { - sendAction( - CreateAccountAction.Internal.ReceiveCaptchaToken( - tokenResult = it, - ), - ) - } - .launchIn(viewModelScope) - } - - override fun handleAction(action: CreateAccountAction) { - when (action) { - is SubmitClick -> handleSubmitClick() - is ConfirmPasswordInputChange -> handleConfirmPasswordInputChanged(action) - is EmailInputChange -> handleEmailInputChanged(action) - is PasswordHintChange -> handlePasswordHintChanged(action) - is PasswordInputChange -> handlePasswordInputChanged(action) - is CreateAccountAction.CloseClick -> handleCloseClick() - is CreateAccountAction.ErrorDialogDismiss -> handleDialogDismiss() - is AcceptPoliciesToggle -> handleAcceptPoliciesToggle(action) - is CheckDataBreachesToggle -> handleCheckDataBreachesToggle(action) - is PrivacyPolicyClick -> handlePrivacyPolicyClick() - is TermsClick -> handleTermsClick() - is CreateAccountAction.Internal.ReceiveRegisterResult -> { - handleReceiveRegisterAccountResult(action) - } - - is CreateAccountAction.Internal.ReceiveCaptchaToken -> { - handleReceiveCaptchaToken(action) - } - - ContinueWithBreachedPasswordClick -> handleContinueWithBreachedPasswordClick() - is ReceivePasswordStrengthResult -> handlePasswordStrengthResult(action) - } - } - - private fun handlePasswordStrengthResult(action: ReceivePasswordStrengthResult) { - when (val result = action.result) { - is PasswordStrengthResult.Success -> { - val updatedState = when (result.passwordStrength) { - PasswordStrength.LEVEL_0 -> PasswordStrengthState.WEAK_1 - PasswordStrength.LEVEL_1 -> PasswordStrengthState.WEAK_2 - PasswordStrength.LEVEL_2 -> PasswordStrengthState.WEAK_3 - PasswordStrength.LEVEL_3 -> PasswordStrengthState.GOOD - PasswordStrength.LEVEL_4 -> PasswordStrengthState.STRONG - } - mutableStateFlow.update { oldState -> - oldState.copy( - passwordStrengthState = updatedState, - ) - } - } - - is PasswordStrengthResult.Error -> { - // Leave UI the same - } - } - } - - private fun handleReceiveCaptchaToken( - action: CreateAccountAction.Internal.ReceiveCaptchaToken, - ) { - when (val result = action.tokenResult) { - is CaptchaCallbackTokenResult.MissingToken -> { - mutableStateFlow.update { - it.copy( - dialog = CreateAccountDialog.Error( - title = BitwardenString.an_error_has_occurred.asText(), - message = BitwardenString.captcha_failed.asText(), - ), - ) - } - } - - is CaptchaCallbackTokenResult.Success -> { - submitRegisterAccountRequest( - shouldCheckForDataBreaches = false, - shouldIgnorePasswordStrength = true, - captchaToken = result.token, - ) - } - } - } - - @Suppress("LongMethod", "MaxLineLength") - private fun handleReceiveRegisterAccountResult( - action: CreateAccountAction.Internal.ReceiveRegisterResult, - ) { - when (val registerAccountResult = action.registerResult) { - is RegisterResult.CaptchaRequired -> { - mutableStateFlow.update { it.copy(dialog = null) } - sendEvent( - CreateAccountEvent.NavigateToCaptcha( - uri = generateUriForCaptcha(captchaId = registerAccountResult.captchaId), - ), - ) - } - - is RegisterResult.Error -> { - mutableStateFlow.update { - it.copy( - dialog = CreateAccountDialog.Error( - title = BitwardenString.an_error_has_occurred.asText(), - message = registerAccountResult.errorMessage?.asText() - ?: BitwardenString.generic_error_message.asText(), - error = registerAccountResult.error, - ), - ) - } - } - - is RegisterResult.Success -> { - mutableStateFlow.update { it.copy(dialog = null) } - sendEvent( - CreateAccountEvent.NavigateToLogin( - email = state.emailInput, - captchaToken = registerAccountResult.captchaToken, - ), - ) - } - - RegisterResult.DataBreachFound -> { - mutableStateFlow.update { - it.copy( - dialog = CreateAccountDialog.HaveIBeenPwned( - title = BitwardenString.exposed_master_password.asText(), - message = BitwardenString.password_found_in_a_data_breach_alert_description.asText(), - ), - ) - } - } - - RegisterResult.DataBreachAndWeakPassword -> { - mutableStateFlow.update { - it.copy( - dialog = CreateAccountDialog.HaveIBeenPwned( - title = BitwardenString.weak_and_exposed_master_password.asText(), - message = BitwardenString.weak_password_identified_and_found_in_a_data_breach_alert_description.asText(), - ), - ) - } - } - - RegisterResult.WeakPassword -> { - mutableStateFlow.update { - it.copy( - dialog = CreateAccountDialog.HaveIBeenPwned( - title = BitwardenString.weak_master_password.asText(), - message = BitwardenString.weak_password_identified_use_a_strong_password_to_protect_your_account.asText(), - ), - ) - } - } - } - } - - private fun handlePrivacyPolicyClick() = sendEvent(CreateAccountEvent.NavigateToPrivacyPolicy) - - private fun handleTermsClick() = sendEvent(CreateAccountEvent.NavigateToTerms) - - private fun handleAcceptPoliciesToggle(action: AcceptPoliciesToggle) { - mutableStateFlow.update { - it.copy(isAcceptPoliciesToggled = action.newState) - } - } - - private fun handleCheckDataBreachesToggle(action: CheckDataBreachesToggle) { - mutableStateFlow.update { - it.copy(isCheckDataBreachesToggled = action.newState) - } - } - - private fun handleDialogDismiss() { - mutableStateFlow.update { - it.copy(dialog = null) - } - } - - private fun handleCloseClick() { - sendEvent(CreateAccountEvent.NavigateBack) - } - - private fun handleEmailInputChanged(action: EmailInputChange) { - mutableStateFlow.update { it.copy(emailInput = action.input) } - } - - private fun handlePasswordHintChanged(action: PasswordHintChange) { - mutableStateFlow.update { it.copy(passwordHintInput = action.input) } - } - - 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.emailInput, - password = action.input, - ) - trySendAction(ReceivePasswordStrengthResult(result)) - } - } - } - - private fun handleConfirmPasswordInputChanged(action: ConfirmPasswordInputChange) { - mutableStateFlow.update { it.copy(confirmPasswordInput = action.input) } - } - - @Suppress("LongMethod") - private fun handleSubmitClick() = when { - state.emailInput.isBlank() -> { - mutableStateFlow.update { - it.copy( - dialog = CreateAccountDialog.Error( - title = BitwardenString.an_error_has_occurred.asText(), - message = BitwardenString.validation_field_required - .asText(BitwardenString.email_address.asText()), - ), - ) - } - } - - !state.emailInput.isValidEmail() -> { - mutableStateFlow.update { - it.copy( - dialog = CreateAccountDialog.Error( - title = BitwardenString.an_error_has_occurred.asText(), - message = BitwardenString.invalid_email.asText(), - ), - ) - } - } - - state.passwordInput.length < MIN_PASSWORD_LENGTH -> { - mutableStateFlow.update { - it.copy( - dialog = CreateAccountDialog.Error( - title = BitwardenString.an_error_has_occurred.asText(), - message = BitwardenString.master_password_length_val_message_x - .asText(MIN_PASSWORD_LENGTH), - ), - ) - } - } - - state.passwordInput != state.confirmPasswordInput -> { - mutableStateFlow.update { - it.copy( - dialog = CreateAccountDialog.Error( - title = BitwardenString.an_error_has_occurred.asText(), - message = BitwardenString.master_password_confirmation_val_message.asText(), - ), - ) - } - } - - !state.isAcceptPoliciesToggled -> { - mutableStateFlow.update { - it.copy( - dialog = CreateAccountDialog.Error( - title = BitwardenString.an_error_has_occurred.asText(), - message = BitwardenString.accept_policies_error.asText(), - ), - ) - } - } - - else -> { - submitRegisterAccountRequest( - shouldCheckForDataBreaches = state.isCheckDataBreachesToggled, - shouldIgnorePasswordStrength = false, - captchaToken = null, - ) - } - } - - private fun handleContinueWithBreachedPasswordClick() { - submitRegisterAccountRequest( - shouldCheckForDataBreaches = false, - shouldIgnorePasswordStrength = true, - captchaToken = null, - ) - } - - private fun submitRegisterAccountRequest( - shouldCheckForDataBreaches: Boolean, - shouldIgnorePasswordStrength: Boolean, - captchaToken: String?, - ) { - mutableStateFlow.update { - it.copy(dialog = CreateAccountDialog.Loading) - } - viewModelScope.launch { - val result = authRepository.register( - shouldCheckDataBreaches = shouldCheckForDataBreaches, - isMasterPasswordStrong = shouldIgnorePasswordStrength || - state.isMasterPasswordStrong, - email = state.emailInput, - masterPassword = state.passwordInput, - masterPasswordHint = state.passwordHintInput.ifBlank { null }, - captchaToken = captchaToken, - ) - sendAction( - CreateAccountAction.Internal.ReceiveRegisterResult( - registerResult = result, - ), - ) - } - } -} - -/** - * UI state for the create account screen. - */ -@Parcelize -data class CreateAccountState( - val emailInput: String, - val passwordInput: String, - val confirmPasswordInput: String, - val passwordHintInput: String, - val isCheckDataBreachesToggled: Boolean, - val isAcceptPoliciesToggled: Boolean, - val dialog: CreateAccountDialog?, - val passwordStrengthState: PasswordStrengthState, -) : Parcelable { - - val passwordLengthLabel: Text - // Have to concat a few strings here, resulting string is: - // Important: Your master password cannot be recovered if you forget it! 12 - // characters minimum - @Suppress("MaxLineLength") - get() = BitwardenString.important.asText() - .concat( - ": ".asText(), - BitwardenString.your_master_password_cannot_be_recovered_if_you_forget_it_x_characters_minimum - .asText(MIN_PASSWORD_LENGTH), - ) - - /** - * Whether or not the provided master password is considered strong. - */ - val isMasterPasswordStrong: Boolean - get() = when (passwordStrengthState) { - PasswordStrengthState.NONE, - PasswordStrengthState.WEAK_1, - PasswordStrengthState.WEAK_2, - PasswordStrengthState.WEAK_3, - -> false - - PasswordStrengthState.GOOD, - PasswordStrengthState.STRONG, - -> true - } -} - -/** - * Models dialogs that can be displayed on the create account screen. - */ -sealed class CreateAccountDialog : Parcelable { - /** - * Loading dialog. - */ - @Parcelize - data object Loading : CreateAccountDialog() - - /** - * Confirm the user wants to continue with potentially breached password. - * - * @param title The title for the HaveIBeenPwned dialog. - * @param message The message for the HaveIBeenPwned dialog. - */ - @Parcelize - data class HaveIBeenPwned( - val title: Text, - val message: Text, - ) : CreateAccountDialog() - - /** - * General error dialog with an OK button. - */ - @Parcelize - data class Error( - val title: Text?, - val message: Text, - val error: Throwable? = null, - ) : CreateAccountDialog() -} - -/** - * Models events for the create account screen. - */ -sealed class CreateAccountEvent { - - /** - * Navigate back to previous screen. - */ - data object NavigateBack : CreateAccountEvent() - - /** - * Navigates to the captcha verification screen. - */ - data class NavigateToCaptcha(val uri: Uri) : CreateAccountEvent() - - /** - * Navigates to the login screen bypassing captcha with token. - */ - data class NavigateToLogin( - val email: String, - val captchaToken: String?, - ) : CreateAccountEvent() - - /** - * Navigate to terms and conditions. - */ - data object NavigateToTerms : CreateAccountEvent() - - /** - * Navigate to privacy policy. - */ - data object NavigateToPrivacyPolicy : CreateAccountEvent() -} - -/** - * Models actions for the create account screen. - */ -sealed class CreateAccountAction { - /** - * User clicked submit. - */ - data object SubmitClick : CreateAccountAction() - - /** - * User clicked close. - */ - data object CloseClick : CreateAccountAction() - - /** - * User clicked "Yes" when being asked if they are sure they want to use a breached password. - */ - data object ContinueWithBreachedPasswordClick : CreateAccountAction() - - /** - * Email input changed. - */ - data class EmailInputChange(val input: String) : CreateAccountAction() - - /** - * Password input changed. - */ - data class PasswordInputChange(val input: String) : CreateAccountAction() - - /** - * Confirm password input changed. - */ - data class ConfirmPasswordInputChange(val input: String) : CreateAccountAction() - - /** - * Password hint input changed. - */ - data class PasswordHintChange(val input: String) : CreateAccountAction() - - /** - * User dismissed the error dialog. - */ - data object ErrorDialogDismiss : CreateAccountAction() - - /** - * User tapped check data breaches toggle. - */ - data class CheckDataBreachesToggle(val newState: Boolean) : CreateAccountAction() - - /** - * User tapped accept policies toggle. - */ - data class AcceptPoliciesToggle(val newState: Boolean) : CreateAccountAction() - - /** - * User tapped privacy policy link. - */ - data object PrivacyPolicyClick : CreateAccountAction() - - /** - * User tapped terms link. - */ - data object TermsClick : CreateAccountAction() - - /** - * Models actions that the [CreateAccountViewModel] itself might send. - */ - sealed class Internal : CreateAccountAction() { - /** - * Indicates a captcha callback token has been received. - */ - data class ReceiveCaptchaToken( - val tokenResult: CaptchaCallbackTokenResult, - ) : Internal() - - /** - * Indicates a [RegisterResult] has been received. - */ - data class ReceiveRegisterResult( - val registerResult: RegisterResult, - ) : Internal() - - /** - * Indicates a password strength result has been received. - */ - data class ReceivePasswordStrengthResult( - val result: PasswordStrengthResult, - ) : Internal() - } -} diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/landing/LandingNavigation.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/landing/LandingNavigation.kt index 670ec7448c..45bfabbaad 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/landing/LandingNavigation.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/landing/LandingNavigation.kt @@ -23,7 +23,6 @@ fun NavController.navigateToLanding(navOptions: NavOptions? = null) { * Add the Landing screen to the nav graph. */ fun NavGraphBuilder.landingDestination( - onNavigateToCreateAccount: () -> Unit, onNavigateToLogin: (emailAddress: String) -> Unit, onNavigateToEnvironment: () -> Unit, onNavigateToStartRegistration: () -> Unit, @@ -31,7 +30,6 @@ fun NavGraphBuilder.landingDestination( ) { composableWithStayTransitions { LandingScreen( - onNavigateToCreateAccount = onNavigateToCreateAccount, onNavigateToLogin = onNavigateToLogin, onNavigateToEnvironment = onNavigateToEnvironment, onNavigateToStartRegistration = onNavigateToStartRegistration, diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/landing/LandingScreen.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/landing/LandingScreen.kt index ee85ad7520..9ee1b59afd 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/landing/LandingScreen.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/landing/LandingScreen.kt @@ -65,7 +65,6 @@ import kotlinx.collections.immutable.toImmutableList @Composable @Suppress("LongMethod") fun LandingScreen( - onNavigateToCreateAccount: () -> Unit, onNavigateToLogin: (emailAddress: String) -> Unit, onNavigateToEnvironment: () -> Unit, onNavigateToStartRegistration: () -> Unit, @@ -76,7 +75,6 @@ fun LandingScreen( val snackbarHostState = rememberBitwardenSnackbarHostState() EventsEffect(viewModel = viewModel) { event -> when (event) { - LandingEvent.NavigateToCreateAccount -> onNavigateToCreateAccount() is LandingEvent.NavigateToLogin -> onNavigateToLogin(event.emailAddress) LandingEvent.NavigateToEnvironment -> onNavigateToEnvironment() LandingEvent.NavigateToStartRegistration -> onNavigateToStartRegistration() diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/landing/LandingViewModel.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/landing/LandingViewModel.kt index 581677ec66..ee5c204f0d 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/landing/LandingViewModel.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/landing/LandingViewModel.kt @@ -3,7 +3,6 @@ package com.x8bit.bitwarden.ui.auth.feature.landing import android.os.Parcelable import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope -import com.bitwarden.core.data.manager.model.FlagKey import com.bitwarden.data.repository.model.Environment import com.bitwarden.ui.platform.base.BackgroundEvent import com.bitwarden.ui.platform.base.BaseViewModel @@ -14,7 +13,6 @@ import com.bitwarden.ui.util.asText import com.x8bit.bitwarden.data.auth.repository.AuthRepository import com.x8bit.bitwarden.data.auth.repository.model.LogoutReason import com.x8bit.bitwarden.data.auth.repository.model.UserState -import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository import com.x8bit.bitwarden.data.vault.repository.VaultRepository import com.x8bit.bitwarden.ui.platform.components.model.AccountSummary @@ -41,7 +39,6 @@ class LandingViewModel @Inject constructor( private val authRepository: AuthRepository, private val vaultRepository: VaultRepository, private val environmentRepository: EnvironmentRepository, - private val featureFlagManager: FeatureFlagManager, snackbarRelayManager: SnackbarRelayManager, savedStateHandle: SavedStateHandle, ) : BaseViewModel( @@ -215,13 +212,7 @@ class LandingViewModel @Inject constructor( } private fun handleCreateAccountClicked() { - val navigationEvent = - if (featureFlagManager.getFeatureFlag(key = FlagKey.EmailVerification)) { - LandingEvent.NavigateToStartRegistration - } else { - LandingEvent.NavigateToCreateAccount - } - sendEvent(navigationEvent) + sendEvent(LandingEvent.NavigateToStartRegistration) } private fun handleDialogDismiss() { @@ -326,11 +317,6 @@ data class LandingState( * Models events for the landing screen. */ sealed class LandingEvent { - /** - * Navigates to the Create Account screen. - */ - data object NavigateToCreateAccount : LandingEvent() - /** * Navigates to the Start Registration screen. */ diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/welcome/WelcomeNavigation.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/welcome/WelcomeNavigation.kt index a522b54232..6b5bb330f0 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/welcome/WelcomeNavigation.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/welcome/WelcomeNavigation.kt @@ -23,13 +23,11 @@ fun NavController.navigateToWelcome(navOptions: NavOptions? = null) { * Add the Welcome screen to the nav graph. */ fun NavGraphBuilder.welcomeDestination( - onNavigateToCreateAccount: () -> Unit, onNavigateToLogin: () -> Unit, onNavigateToStartRegistration: () -> Unit, ) { composableWithStayTransitions { WelcomeScreen( - onNavigateToCreateAccount = onNavigateToCreateAccount, onNavigateToLogin = onNavigateToLogin, onNavigateToStartRegistration = onNavigateToStartRegistration, ) diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/welcome/WelcomeScreen.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/welcome/WelcomeScreen.kt index cf54467c07..beb5bb0789 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/welcome/WelcomeScreen.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/welcome/WelcomeScreen.kt @@ -63,7 +63,6 @@ private val HORIZONTAL_MARGIN_MEDIUM: Dp = 128.dp */ @Composable fun WelcomeScreen( - onNavigateToCreateAccount: () -> Unit, onNavigateToLogin: () -> Unit, onNavigateToStartRegistration: () -> Unit, viewModel: WelcomeViewModel = hiltViewModel(), @@ -78,7 +77,6 @@ fun WelcomeScreen( scope.launch { pagerState.animateScrollToPage(event.index) } } - WelcomeEvent.NavigateToCreateAccount -> onNavigateToCreateAccount() WelcomeEvent.NavigateToLogin -> onNavigateToLogin() WelcomeEvent.NavigateToStartRegistration -> onNavigateToStartRegistration() } diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/welcome/WelcomeViewModel.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/welcome/WelcomeViewModel.kt index 6f1d7660c4..8e0d3e4017 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/welcome/WelcomeViewModel.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/auth/feature/welcome/WelcomeViewModel.kt @@ -1,11 +1,9 @@ package com.x8bit.bitwarden.ui.auth.feature.welcome import android.os.Parcelable -import com.bitwarden.core.data.manager.model.FlagKey import com.bitwarden.ui.platform.base.BaseViewModel import com.bitwarden.ui.platform.resource.BitwardenDrawable import com.bitwarden.ui.platform.resource.BitwardenString -import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.update import kotlinx.parcelize.Parcelize @@ -15,9 +13,7 @@ import javax.inject.Inject * Manages application state for the welcome screen. */ @HiltViewModel -class WelcomeViewModel @Inject constructor( - private val featureFlagManager: FeatureFlagManager, -) : +class WelcomeViewModel @Inject constructor() : BaseViewModel( initialState = WelcomeState( index = 0, @@ -48,12 +44,7 @@ class WelcomeViewModel @Inject constructor( } private fun handleCreateAccountClick() { - val event = if (featureFlagManager.getFeatureFlag(FlagKey.EmailVerification)) { - WelcomeEvent.NavigateToStartRegistration - } else { - WelcomeEvent.NavigateToCreateAccount - } - sendEvent(event) + sendEvent(WelcomeEvent.NavigateToStartRegistration) } private fun handleLoginClick() { @@ -130,11 +121,6 @@ sealed class WelcomeEvent { val index: Int, ) : WelcomeEvent() - /** - * Navigates to the create account screen. - */ - data object NavigateToCreateAccount : WelcomeEvent() - /** * Navigates to the login screen. */ diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/debugmenu/DebugMenuScreen.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/debugmenu/DebugMenuScreen.kt index 377ec0a343..1ff95ab6d2 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/debugmenu/DebugMenuScreen.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/debugmenu/DebugMenuScreen.kt @@ -263,7 +263,7 @@ private fun FeatureFlagContent_preview() { BitwardenTheme { FeatureFlagContent( featureFlagMap = persistentMapOf( - FlagKey.EmailVerification to true, + FlagKey.DummyBoolean to true, ), onValueChange = { _, _ -> }, onResetValues = { }, diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/debugmenu/components/FeatureFlagListItems.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/debugmenu/components/FeatureFlagListItems.kt index df02ecd23b..4273e2a969 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/debugmenu/components/FeatureFlagListItems.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/debugmenu/components/FeatureFlagListItems.kt @@ -26,7 +26,6 @@ fun FlagKey.ListItemContent( } FlagKey.BitwardenAuthenticationEnabled, - FlagKey.EmailVerification, FlagKey.CredentialExchangeProtocolImport, FlagKey.CredentialExchangeProtocolExport, FlagKey.CipherKeyEncryption, @@ -74,7 +73,6 @@ private fun FlagKey.getDisplayLabel(): String = when (this) { FlagKey.DummyString, -> this.keyName - FlagKey.EmailVerification -> stringResource(BitwardenString.email_verification) FlagKey.CredentialExchangeProtocolImport -> stringResource(BitwardenString.cxp_import) FlagKey.CredentialExchangeProtocolExport -> stringResource(BitwardenString.cxp_export) FlagKey.CipherKeyEncryption -> stringResource(BitwardenString.cipher_key_encryption) diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/manager/FeatureFlagManagerTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/manager/FeatureFlagManagerTest.kt index ef5758aed2..9fa687678d 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/manager/FeatureFlagManagerTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/manager/FeatureFlagManagerTest.kt @@ -1,11 +1,11 @@ package com.x8bit.bitwarden.data.platform.manager import app.cash.turbine.test +import com.bitwarden.core.data.manager.model.FlagKey import com.bitwarden.data.datasource.disk.model.ServerConfig import com.bitwarden.network.model.ConfigResponseJson import com.bitwarden.network.model.ConfigResponseJson.EnvironmentJson import com.bitwarden.network.model.ConfigResponseJson.ServerJson -import com.bitwarden.core.data.manager.model.FlagKey import com.x8bit.bitwarden.data.platform.repository.util.FakeServerConfigRepository import com.x8bit.bitwarden.data.platform.util.isServerVersionAtLeast import kotlinx.coroutines.test.runTest @@ -130,7 +130,7 @@ class FeatureFlagManagerTest { ) val flagValue = manager.getFeatureFlag( - key = FlagKey.EmailVerification, + key = FlagKey.DummyBoolean, forceRefresh = false, ) assertFalse(flagValue) @@ -226,7 +226,7 @@ class FeatureFlagManagerTest { fakeServerConfigRepository.serverConfigValue = null val flagValue = manager.getFeatureFlag( - key = FlagKey.EmailVerification, + key = FlagKey.DummyBoolean, forceRefresh = false, ) diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/repository/DebugMenuRepositoryTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/repository/DebugMenuRepositoryTest.kt index 415f430c87..4723611130 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/repository/DebugMenuRepositoryTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/repository/DebugMenuRepositoryTest.kt @@ -1,6 +1,7 @@ package com.x8bit.bitwarden.data.platform.repository import app.cash.turbine.test +import com.bitwarden.core.data.manager.model.FlagKey import com.bitwarden.data.datasource.disk.model.ServerConfig import com.bitwarden.data.repository.ServerConfigRepository import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource @@ -8,7 +9,6 @@ import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson import com.x8bit.bitwarden.data.platform.datasource.disk.FeatureFlagOverrideDiskSource import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource -import com.bitwarden.core.data.manager.model.FlagKey import io.mockk.every import io.mockk.just import io.mockk.mockk @@ -99,8 +99,8 @@ class DebugMenuRepositoryTest { debugMenuRepository.resetFeatureFlagOverrides() verify(exactly = 1) { mockFeatureFlagOverrideDiskSource.saveFeatureFlag( - FlagKey.EmailVerification, - FlagKey.EmailVerification.defaultValue, + FlagKey.CredentialExchangeProtocolImport, + FlagKey.CredentialExchangeProtocolImport.defaultValue, ) } debugMenuRepository.featureFlagOverridesUpdatedFlow.test { diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/auth/feature/createaccount/CreateAccountScreenTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/auth/feature/createaccount/CreateAccountScreenTest.kt deleted file mode 100644 index 40595915a8..0000000000 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/auth/feature/createaccount/CreateAccountScreenTest.kt +++ /dev/null @@ -1,319 +0,0 @@ -package com.x8bit.bitwarden.ui.auth.feature.createaccount - -import android.net.Uri -import androidx.compose.ui.test.assertCountEquals -import androidx.compose.ui.test.assertIsDisplayed -import androidx.compose.ui.test.assertIsOff -import androidx.compose.ui.test.assertIsOn -import androidx.compose.ui.test.filterToOne -import androidx.compose.ui.test.hasAnyAncestor -import androidx.compose.ui.test.isDialog -import androidx.compose.ui.test.onAllNodesWithContentDescription -import androidx.compose.ui.test.onAllNodesWithText -import androidx.compose.ui.test.onNodeWithContentDescription -import androidx.compose.ui.test.onNodeWithText -import androidx.compose.ui.test.performClick -import androidx.compose.ui.test.performScrollTo -import androidx.compose.ui.test.performTextInput -import androidx.core.net.toUri -import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow -import com.bitwarden.ui.util.asText -import com.bitwarden.ui.util.performCustomAccessibilityAction -import com.x8bit.bitwarden.ui.auth.feature.completeregistration.PasswordStrengthState -import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.AcceptPoliciesToggle -import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.CheckDataBreachesToggle -import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.CloseClick -import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.ConfirmPasswordInputChange -import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.EmailInputChange -import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.PasswordHintChange -import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.PasswordInputChange -import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.SubmitClick -import com.x8bit.bitwarden.ui.platform.base.BitwardenComposeTest -import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager -import io.mockk.every -import io.mockk.just -import io.mockk.mockk -import io.mockk.runs -import io.mockk.verify -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.update -import org.junit.Assert.assertTrue -import org.junit.Before -import org.junit.Test - -class CreateAccountScreenTest : BitwardenComposeTest() { - - private var onNavigateBackCalled = false - private var onNavigateToLoginCalled = false - - private val intentManager = mockk(relaxed = true) { - every { startCustomTabsActivity(any()) } just runs - every { startActivity(any()) } just runs - } - - private val mutableStateFlow = MutableStateFlow(DEFAULT_STATE) - private val mutableEventFlow = bufferedMutableSharedFlow() - private val viewModel = mockk(relaxed = true) { - every { stateFlow } returns mutableStateFlow - every { eventFlow } returns mutableEventFlow - every { trySendAction(any()) } just runs - } - - @Before - fun setup() { - setContent( - intentManager = intentManager, - ) { - CreateAccountScreen( - onNavigateBack = { onNavigateBackCalled = true }, - onNavigateToLogin = { _, _ -> onNavigateToLoginCalled = true }, - viewModel = viewModel, - ) - } - } - - @Test - fun `app bar submit click should send SubmitClick action`() { - composeTestRule.onNodeWithText("Submit").performClick() - verify { viewModel.trySendAction(SubmitClick) } - } - - @Test - fun `close click should send CloseClick action`() { - composeTestRule.onNodeWithContentDescription("Close").performClick() - verify { viewModel.trySendAction(CloseClick) } - } - - @Test - fun `check data breaches click should send CheckDataBreachesToggle action`() { - composeTestRule - .onNodeWithText("Check known data breaches for this password") - .performScrollTo() - .performClick() - verify { viewModel.trySendAction(CheckDataBreachesToggle(true)) } - } - - @Test - fun `accept policies should be toggled on or off according to the state`() { - composeTestRule - .onNodeWithText("By activating this switch, you agree", substring = true) - .assertIsOff() - - mutableStateFlow.update { it.copy(isAcceptPoliciesToggled = true) } - - composeTestRule - .onNodeWithText("By activating this switch, you agree", substring = true) - .assertIsOn() - } - - @Test - fun `accept policies click should send AcceptPoliciesToggle action`() { - composeTestRule - .onNodeWithText("By activating this switch, you agree", substring = true) - .performScrollTo() - .performClick() - verify { viewModel.trySendAction(AcceptPoliciesToggle(true)) } - } - - @Test - fun `NavigateBack event should invoke navigate back lambda`() { - mutableEventFlow.tryEmit(CreateAccountEvent.NavigateBack) - assertTrue(onNavigateBackCalled) - } - - @Test - fun `NavigateToLogin event should invoke navigate login lambda`() { - mutableEventFlow.tryEmit(CreateAccountEvent.NavigateToLogin(email = "", captchaToken = "")) - assertTrue(onNavigateToLoginCalled) - } - - @Test - fun `NavigateToCaptcha event should invoke intent manager`() { - val mockUri = mockk() - mutableEventFlow.tryEmit(CreateAccountEvent.NavigateToCaptcha(uri = mockUri)) - verify { - intentManager.startCustomTabsActivity(mockUri) - } - } - - @Test - fun `NavigateToPrivacyPolicy event should invoke intent manager`() { - mutableEventFlow.tryEmit(CreateAccountEvent.NavigateToPrivacyPolicy) - verify { - intentManager.launchUri("https://bitwarden.com/privacy/".toUri()) - } - } - - @Test - fun `NavigateToTerms event should invoke intent manager`() { - mutableEventFlow.tryEmit(CreateAccountEvent.NavigateToTerms) - verify { - intentManager.launchUri("https://bitwarden.com/terms/".toUri()) - } - } - - @Test - fun `email input change should send EmailInputChange action`() { - composeTestRule.onNodeWithText("Email address").performTextInput(TEST_INPUT) - verify { viewModel.trySendAction(EmailInputChange(TEST_INPUT)) } - } - - @Test - fun `password input change should send PasswordInputChange action`() { - composeTestRule.onNodeWithText("Master password").performTextInput(TEST_INPUT) - verify { viewModel.trySendAction(PasswordInputChange(TEST_INPUT)) } - } - - @Test - fun `confirm password input change should send ConfirmPasswordInputChange action`() { - composeTestRule.onNodeWithText("Re-type master password").performTextInput(TEST_INPUT) - verify { viewModel.trySendAction(ConfirmPasswordInputChange(TEST_INPUT)) } - } - - @Test - fun `password hint input change should send PasswordHintChange action`() { - composeTestRule - .onNodeWithText("Master password hint (optional)") - .performTextInput(TEST_INPUT) - verify { viewModel.trySendAction(PasswordHintChange(TEST_INPUT)) } - } - - @Test - fun `clicking OK on the error dialog should send ErrorDialogDismiss action`() { - mutableStateFlow.update { - it.copy( - dialog = CreateAccountDialog.Error( - title = "title".asText(), - message = "message".asText(), - ), - ) - } - composeTestRule - .onAllNodesWithText(text = "Okay") - .filterToOne(hasAnyAncestor(isDialog())) - .performClick() - verify { viewModel.trySendAction(CreateAccountAction.ErrorDialogDismiss) } - } - - @Test - fun `clicking No on the HIBP dialog should send ErrorDialogDismiss action`() { - mutableStateFlow.update { - it.copy(dialog = createHaveIBeenPwned()) - } - composeTestRule - .onAllNodesWithText("No") - .filterToOne(hasAnyAncestor(isDialog())) - .performClick() - verify { viewModel.trySendAction(CreateAccountAction.ErrorDialogDismiss) } - } - - @Test - fun `clicking Yes on the HIBP dialog should send ContinueWithBreachedPasswordClick action`() { - mutableStateFlow.update { - it.copy(dialog = createHaveIBeenPwned()) - } - composeTestRule - .onAllNodesWithText("Yes") - .filterToOne(hasAnyAncestor(isDialog())) - .performClick() - verify { viewModel.trySendAction(CreateAccountAction.ContinueWithBreachedPasswordClick) } - } - - @Test - fun `when BasicDialogState is Shown should show dialog`() { - mutableStateFlow.update { - it.copy( - dialog = CreateAccountDialog.Error( - title = "title".asText(), - message = "message".asText(), - ), - ) - } - composeTestRule.onNode(isDialog()).assertIsDisplayed() - } - - @Test - fun `password strength should change as state changes`() { - mutableStateFlow.update { - DEFAULT_STATE.copy(passwordStrengthState = PasswordStrengthState.WEAK_1) - } - composeTestRule.onNodeWithText("Weak").assertIsDisplayed() - - mutableStateFlow.update { - DEFAULT_STATE.copy(passwordStrengthState = PasswordStrengthState.WEAK_2) - } - composeTestRule.onNodeWithText("Weak").assertIsDisplayed() - - mutableStateFlow.update { - DEFAULT_STATE.copy(passwordStrengthState = PasswordStrengthState.WEAK_3) - } - composeTestRule.onNodeWithText("Weak").assertIsDisplayed() - - mutableStateFlow.update { - DEFAULT_STATE.copy(passwordStrengthState = PasswordStrengthState.GOOD) - } - composeTestRule.onNodeWithText("Good").assertIsDisplayed() - - mutableStateFlow.update { - DEFAULT_STATE.copy(passwordStrengthState = PasswordStrengthState.STRONG) - } - composeTestRule.onNodeWithText("Strong").assertIsDisplayed() - } - - @Test - fun `toggling one password field visibility should toggle the other`() { - // should start with 2 Show buttons: - composeTestRule - .onAllNodesWithContentDescription("Show") - .assertCountEquals(2)[0] - .performClick() - - // after clicking there should be no Show buttons: - composeTestRule - .onAllNodesWithContentDescription("Show") - .assertCountEquals(0) - - // and there should be 2 hide buttons now, and we'll click the second one: - composeTestRule - .onAllNodesWithContentDescription("Hide") - .assertCountEquals(2)[1] - .performClick() - - // then there should be two show buttons again - composeTestRule - .onAllNodesWithContentDescription("Show") - .assertCountEquals(2) - } - - @Test - fun `terms of service click should send TermsClick action`() { - composeTestRule - .onNodeWithText(text = "Terms of Service", substring = true) - .performScrollTo() - .performCustomAccessibilityAction("Terms of Service") - verify { viewModel.trySendAction(CreateAccountAction.TermsClick) } - } - - @Test - fun `privacy policy click should send PrivacyPolicyClick action`() { - composeTestRule - .onNodeWithText(text = "Privacy Policy", substring = true) - .performScrollTo() - .performCustomAccessibilityAction("Privacy Policy") - verify { viewModel.trySendAction(CreateAccountAction.PrivacyPolicyClick) } - } - - companion object { - private const val TEST_INPUT = "input" - private val DEFAULT_STATE = CreateAccountState( - emailInput = "", - passwordInput = "", - confirmPasswordInput = "", - passwordHintInput = "", - isCheckDataBreachesToggled = false, - isAcceptPoliciesToggled = false, - dialog = null, - passwordStrengthState = PasswordStrengthState.NONE, - ) - } -} diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/auth/feature/createaccount/CreateAccountTestUtil.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/auth/feature/createaccount/CreateAccountTestUtil.kt deleted file mode 100644 index 04e4331bb8..0000000000 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/auth/feature/createaccount/CreateAccountTestUtil.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.x8bit.bitwarden.ui.auth.feature.createaccount - -import com.bitwarden.ui.platform.resource.BitwardenString -import com.bitwarden.ui.util.Text -import com.bitwarden.ui.util.asText - -/** - * Creates a mock [CreateAccountDialog.HaveIBeenPwned]. - */ -fun createHaveIBeenPwned( - title: Text = BitwardenString.weak_and_exposed_master_password.asText(), - message: Text = BitwardenString - .weak_password_identified_and_found_in_a_data_breach_alert_description - .asText(), -): CreateAccountDialog.HaveIBeenPwned = - CreateAccountDialog.HaveIBeenPwned( - title = title, - message = message, - ) diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/auth/feature/createaccount/CreateAccountViewModelTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/auth/feature/createaccount/CreateAccountViewModelTest.kt deleted file mode 100644 index 62387afe21..0000000000 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/auth/feature/createaccount/CreateAccountViewModelTest.kt +++ /dev/null @@ -1,708 +0,0 @@ -package com.x8bit.bitwarden.ui.auth.feature.createaccount - -import android.net.Uri -import androidx.lifecycle.SavedStateHandle -import app.cash.turbine.test -import com.bitwarden.ui.platform.base.BaseViewModelTest -import com.bitwarden.ui.platform.resource.BitwardenString -import com.bitwarden.ui.util.asText -import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength.LEVEL_0 -import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength.LEVEL_1 -import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength.LEVEL_2 -import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength.LEVEL_3 -import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength.LEVEL_4 -import com.x8bit.bitwarden.data.auth.repository.AuthRepository -import com.x8bit.bitwarden.data.auth.repository.model.PasswordStrengthResult -import com.x8bit.bitwarden.data.auth.repository.model.RegisterResult -import com.x8bit.bitwarden.data.auth.repository.util.generateUriForCaptcha -import com.x8bit.bitwarden.ui.auth.feature.completeregistration.PasswordStrengthState -import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.AcceptPoliciesToggle -import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.CloseClick -import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.ConfirmPasswordInputChange -import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.EmailInputChange -import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.Internal.ReceivePasswordStrengthResult -import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.PasswordHintChange -import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.PasswordInputChange -import io.mockk.coEvery -import io.mockk.coVerify -import io.mockk.every -import io.mockk.mockk -import io.mockk.mockkStatic -import io.mockk.unmockkStatic -import kotlinx.coroutines.flow.emptyFlow -import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test - -@Suppress("LargeClass") -class CreateAccountViewModelTest : BaseViewModelTest() { - - /** - * Saved state handle that has valid inputs. Useful for tests that want to test things - * after the user has entered all valid inputs. - */ - private val validInputHandle = SavedStateHandle(mapOf("state" to VALID_INPUT_STATE)) - - private val mockAuthRepository = mockk { - every { captchaTokenResultFlow } returns flowOf() - } - - @BeforeEach - fun setUp() { - mockkStatic(::generateUriForCaptcha) - } - - @AfterEach - fun tearDown() { - unmockkStatic(::generateUriForCaptcha) - } - - @Test - fun `initial state should be correct`() { - val viewModel = CreateAccountViewModel( - savedStateHandle = SavedStateHandle(), - authRepository = mockAuthRepository, - ) - assertEquals(DEFAULT_STATE, viewModel.stateFlow.value) - } - - @Test - fun `initial state should pull from saved state handle when present`() { - val savedState = CreateAccountState( - emailInput = "email", - passwordInput = "password", - confirmPasswordInput = "confirmPassword", - passwordHintInput = "hint", - isCheckDataBreachesToggled = false, - isAcceptPoliciesToggled = false, - dialog = null, - passwordStrengthState = PasswordStrengthState.NONE, - ) - val handle = SavedStateHandle(mapOf("state" to savedState)) - val viewModel = CreateAccountViewModel( - savedStateHandle = handle, - authRepository = mockAuthRepository, - ) - assertEquals(savedState, viewModel.stateFlow.value) - } - - @Test - fun `SubmitClick with blank email should show email required`() = runTest { - val viewModel = CreateAccountViewModel( - savedStateHandle = SavedStateHandle(), - authRepository = mockAuthRepository, - ) - val input = "a" - viewModel.trySendAction(EmailInputChange(input)) - val expectedState = DEFAULT_STATE.copy( - emailInput = input, - dialog = CreateAccountDialog.Error( - title = BitwardenString.an_error_has_occurred.asText(), - message = BitwardenString.invalid_email.asText(), - ), - ) - viewModel.trySendAction(CreateAccountAction.SubmitClick) - viewModel.stateFlow.test { - assertEquals(expectedState, awaitItem()) - } - } - - @Test - fun `SubmitClick with invalid email should show invalid email`() = runTest { - val viewModel = CreateAccountViewModel( - savedStateHandle = SavedStateHandle(), - authRepository = mockAuthRepository, - ) - val input = " " - viewModel.trySendAction(EmailInputChange(input)) - val expectedState = DEFAULT_STATE.copy( - emailInput = input, - dialog = CreateAccountDialog.Error( - title = BitwardenString.an_error_has_occurred.asText(), - message = BitwardenString.validation_field_required - .asText(BitwardenString.email_address.asText()), - ), - ) - viewModel.trySendAction(CreateAccountAction.SubmitClick) - viewModel.stateFlow.test { - assertEquals(expectedState, awaitItem()) - } - } - - @Test - fun `SubmitClick with password below 12 chars should show password length dialog`() = runTest { - val input = "abcdefghikl" - coEvery { - mockAuthRepository.getPasswordStrength("test@test.com", input) - } returns PasswordStrengthResult.Error(error = Throwable("Fail!")) - val viewModel = CreateAccountViewModel( - savedStateHandle = SavedStateHandle(), - authRepository = mockAuthRepository, - ) - viewModel.trySendAction(EmailInputChange(EMAIL)) - viewModel.trySendAction(PasswordInputChange(input)) - val expectedState = DEFAULT_STATE.copy( - emailInput = EMAIL, - passwordInput = input, - dialog = CreateAccountDialog.Error( - title = BitwardenString.an_error_has_occurred.asText(), - message = BitwardenString.master_password_length_val_message_x.asText(12), - ), - ) - viewModel.trySendAction(CreateAccountAction.SubmitClick) - viewModel.stateFlow.test { - assertEquals(expectedState, awaitItem()) - } - } - - @Test - fun `SubmitClick with passwords not matching should show password match dialog`() = runTest { - val input = "testtesttesttest" - coEvery { - mockAuthRepository.getPasswordStrength("test@test.com", input) - } returns PasswordStrengthResult.Error(error = Throwable("Fail!")) - val viewModel = CreateAccountViewModel( - savedStateHandle = SavedStateHandle(), - authRepository = mockAuthRepository, - ) - viewModel.trySendAction(EmailInputChange("test@test.com")) - viewModel.trySendAction(PasswordInputChange(input)) - val expectedState = DEFAULT_STATE.copy( - emailInput = "test@test.com", - passwordInput = input, - dialog = CreateAccountDialog.Error( - title = BitwardenString.an_error_has_occurred.asText(), - message = BitwardenString.master_password_confirmation_val_message.asText(), - ), - ) - viewModel.trySendAction(CreateAccountAction.SubmitClick) - viewModel.stateFlow.test { - assertEquals(expectedState, awaitItem()) - } - } - - @Test - fun `SubmitClick without policies accepted should show accept policies error`() = runTest { - val password = "testtesttesttest" - coEvery { - mockAuthRepository.getPasswordStrength("test@test.com", password) - } returns PasswordStrengthResult.Error(error = Throwable("Fail!")) - val viewModel = CreateAccountViewModel( - savedStateHandle = SavedStateHandle(), - authRepository = mockAuthRepository, - ) - viewModel.trySendAction(EmailInputChange("test@test.com")) - viewModel.trySendAction(PasswordInputChange(password)) - viewModel.trySendAction(ConfirmPasswordInputChange(password)) - val expectedState = DEFAULT_STATE.copy( - emailInput = "test@test.com", - passwordInput = password, - confirmPasswordInput = password, - dialog = CreateAccountDialog.Error( - title = BitwardenString.an_error_has_occurred.asText(), - message = BitwardenString.accept_policies_error.asText(), - ), - ) - viewModel.trySendAction(CreateAccountAction.SubmitClick) - viewModel.stateFlow.test { - assertEquals(expectedState, awaitItem()) - } - } - - @Test - fun `SubmitClick with all inputs valid should show and hide loading dialog`() = runTest { - val repo = mockk { - every { captchaTokenResultFlow } returns flowOf() - coEvery { - register( - email = EMAIL, - masterPassword = PASSWORD, - masterPasswordHint = null, - captchaToken = null, - shouldCheckDataBreaches = false, - isMasterPasswordStrong = true, - ) - } returns RegisterResult.Success(captchaToken = "mock_token") - } - val viewModel = CreateAccountViewModel( - savedStateHandle = validInputHandle, - authRepository = repo, - ) - viewModel.stateEventFlow(backgroundScope) { stateFlow, eventFlow -> - assertEquals(VALID_INPUT_STATE, stateFlow.awaitItem()) - viewModel.trySendAction(CreateAccountAction.SubmitClick) - assertEquals( - VALID_INPUT_STATE.copy(dialog = CreateAccountDialog.Loading), - stateFlow.awaitItem(), - ) - assertEquals( - CreateAccountEvent.NavigateToLogin( - email = EMAIL, - captchaToken = "mock_token", - ), - eventFlow.awaitItem(), - ) - // Make sure loading dialog is hidden: - assertEquals(VALID_INPUT_STATE, stateFlow.awaitItem()) - } - } - - @Test - fun `SubmitClick register returns error should update errorDialogState`() = runTest { - val error = Throwable("Fail!") - val repo = mockk { - every { captchaTokenResultFlow } returns flowOf() - coEvery { - register( - email = EMAIL, - masterPassword = PASSWORD, - masterPasswordHint = null, - captchaToken = null, - shouldCheckDataBreaches = false, - isMasterPasswordStrong = true, - ) - } returns RegisterResult.Error(errorMessage = "mock_error", error = error) - } - val viewModel = CreateAccountViewModel( - savedStateHandle = validInputHandle, - authRepository = repo, - ) - viewModel.stateFlow.test { - assertEquals(VALID_INPUT_STATE, awaitItem()) - viewModel.trySendAction(CreateAccountAction.SubmitClick) - assertEquals( - VALID_INPUT_STATE.copy(dialog = CreateAccountDialog.Loading), - awaitItem(), - ) - assertEquals( - VALID_INPUT_STATE.copy( - dialog = CreateAccountDialog.Error( - title = BitwardenString.an_error_has_occurred.asText(), - message = "mock_error".asText(), - error = error, - ), - ), - awaitItem(), - ) - } - } - - @Test - fun `SubmitClick register returns CaptchaRequired should emit NavigateToCaptcha`() = runTest { - val mockkUri = mockk() - every { - generateUriForCaptcha(captchaId = "mock_captcha_id") - } returns mockkUri - val repo = mockk { - every { captchaTokenResultFlow } returns flowOf() - coEvery { - register( - email = EMAIL, - masterPassword = PASSWORD, - masterPasswordHint = null, - captchaToken = null, - shouldCheckDataBreaches = false, - isMasterPasswordStrong = true, - ) - } returns RegisterResult.CaptchaRequired(captchaId = "mock_captcha_id") - } - val viewModel = CreateAccountViewModel( - savedStateHandle = validInputHandle, - authRepository = repo, - ) - viewModel.eventFlow.test { - viewModel.trySendAction(CreateAccountAction.SubmitClick) - assertEquals( - CreateAccountEvent.NavigateToCaptcha(uri = mockkUri), - awaitItem(), - ) - } - } - - @Test - fun `SubmitClick register returns Success should emit NavigateToLogin`() = runTest { - val mockkUri = mockk() - every { - generateUriForCaptcha(captchaId = "mock_captcha_id") - } returns mockkUri - val repo = mockk { - every { captchaTokenResultFlow } returns flowOf() - coEvery { - register( - email = EMAIL, - masterPassword = PASSWORD, - masterPasswordHint = null, - captchaToken = null, - shouldCheckDataBreaches = false, - isMasterPasswordStrong = true, - ) - } returns RegisterResult.Success(captchaToken = "mock_captcha_token") - } - val viewModel = CreateAccountViewModel( - savedStateHandle = validInputHandle, - authRepository = repo, - ) - viewModel.eventFlow.test { - viewModel.trySendAction(CreateAccountAction.SubmitClick) - assertEquals( - CreateAccountEvent.NavigateToLogin( - email = EMAIL, - captchaToken = "mock_captcha_token", - ), - awaitItem(), - ) - } - } - - @Test - fun `ContinueWithBreachedPasswordClick should call repository with checkDataBreaches false`() { - val repo = mockk { - every { captchaTokenResultFlow } returns flowOf() - coEvery { - register( - email = EMAIL, - masterPassword = PASSWORD, - masterPasswordHint = null, - captchaToken = null, - shouldCheckDataBreaches = false, - isMasterPasswordStrong = true, - ) - } returns RegisterResult.Error(errorMessage = null, error = null) - } - val viewModel = CreateAccountViewModel( - savedStateHandle = validInputHandle, - authRepository = repo, - ) - viewModel.trySendAction(CreateAccountAction.ContinueWithBreachedPasswordClick) - coVerify { - repo.register( - email = EMAIL, - masterPassword = PASSWORD, - masterPasswordHint = null, - captchaToken = null, - shouldCheckDataBreaches = false, - isMasterPasswordStrong = true, - ) - } - } - - @Test - fun `SubmitClick register returns ShowDataBreaches should show HaveIBeenPwned dialog`() = - runTest { - mockAuthRepository.apply { - every { captchaTokenResultFlow } returns flowOf() - coEvery { - register( - email = EMAIL, - masterPassword = PASSWORD, - masterPasswordHint = null, - captchaToken = null, - shouldCheckDataBreaches = true, - isMasterPasswordStrong = true, - ) - } returns RegisterResult.DataBreachFound - } - val initialState = VALID_INPUT_STATE.copy( - isCheckDataBreachesToggled = true, - ) - val viewModel = createCreateAccountViewModel(createAccountState = initialState) - viewModel.trySendAction(CreateAccountAction.SubmitClick) - viewModel.stateFlow.test { - assertEquals( - initialState.copy( - dialog = createHaveIBeenPwned( - title = BitwardenString.exposed_master_password.asText(), - message = BitwardenString - .password_found_in_a_data_breach_alert_description - .asText(), - ), - ), - awaitItem(), - ) - } - } - - @Test - @Suppress("MaxLineLength") - fun `SubmitClick register returns DataBreachAndWeakPassword should show HaveIBeenPwned dialog`() = - runTest { - mockAuthRepository.apply { - every { captchaTokenResultFlow } returns emptyFlow() - coEvery { - register( - email = EMAIL, - masterPassword = PASSWORD, - masterPasswordHint = null, - captchaToken = null, - shouldCheckDataBreaches = true, - isMasterPasswordStrong = false, - ) - } returns RegisterResult.DataBreachAndWeakPassword - } - val initialState = VALID_INPUT_STATE.copy( - passwordStrengthState = PasswordStrengthState.WEAK_1, - isCheckDataBreachesToggled = true, - ) - - val viewModel = createCreateAccountViewModel(createAccountState = initialState) - viewModel.trySendAction(CreateAccountAction.SubmitClick) - viewModel.stateFlow.test { - assertEquals( - initialState.copy(dialog = createHaveIBeenPwned()), - awaitItem(), - ) - } - } - - @Test - @Suppress("MaxLineLength") - fun `SubmitClick register returns WeakPassword should show HaveIBeenPwned dialog`() = - runTest { - mockAuthRepository.apply { - every { captchaTokenResultFlow } returns flowOf() - coEvery { - register( - email = EMAIL, - masterPassword = PASSWORD, - masterPasswordHint = null, - captchaToken = null, - shouldCheckDataBreaches = true, - isMasterPasswordStrong = false, - ) - } returns RegisterResult.WeakPassword - } - val initialState = VALID_INPUT_STATE - .copy( - passwordStrengthState = PasswordStrengthState.WEAK_1, - isCheckDataBreachesToggled = true, - ) - val viewModel = createCreateAccountViewModel(createAccountState = initialState) - viewModel.trySendAction(CreateAccountAction.SubmitClick) - viewModel.stateFlow.test { - assertEquals( - initialState.copy( - dialog = createHaveIBeenPwned( - title = BitwardenString.weak_master_password.asText(), - message = BitwardenString.weak_password_identified_use_a_strong_password_to_protect_your_account.asText(), - ), - ), - awaitItem(), - ) - } - } - - @Test - fun `CloseClick should emit NavigateBack`() = runTest { - val viewModel = CreateAccountViewModel( - savedStateHandle = SavedStateHandle(), - authRepository = mockAuthRepository, - ) - viewModel.eventFlow.test { - viewModel.trySendAction(CloseClick) - assertEquals(CreateAccountEvent.NavigateBack, awaitItem()) - } - } - - @Test - fun `PrivacyPolicyClick should emit NavigatePrivacyPolicy`() = runTest { - val viewModel = CreateAccountViewModel( - savedStateHandle = SavedStateHandle(), - authRepository = mockAuthRepository, - ) - viewModel.eventFlow.test { - viewModel.trySendAction(CreateAccountAction.PrivacyPolicyClick) - assertEquals(CreateAccountEvent.NavigateToPrivacyPolicy, awaitItem()) - } - } - - @Test - fun `TermsClick should emit NavigateToTerms`() = runTest { - val viewModel = CreateAccountViewModel( - savedStateHandle = SavedStateHandle(), - authRepository = mockAuthRepository, - ) - viewModel.eventFlow.test { - viewModel.trySendAction(CreateAccountAction.TermsClick) - assertEquals(CreateAccountEvent.NavigateToTerms, awaitItem()) - } - } - - @Test - fun `ConfirmPasswordInputChange update passwordInput`() = runTest { - val viewModel = CreateAccountViewModel( - savedStateHandle = SavedStateHandle(), - authRepository = mockAuthRepository, - ) - viewModel.trySendAction(ConfirmPasswordInputChange("input")) - viewModel.stateFlow.test { - assertEquals(DEFAULT_STATE.copy(confirmPasswordInput = "input"), awaitItem()) - } - } - - @Test - fun `EmailInputChange update passwordInput`() = runTest { - val viewModel = CreateAccountViewModel( - savedStateHandle = SavedStateHandle(), - authRepository = mockAuthRepository, - ) - viewModel.trySendAction(EmailInputChange("input")) - viewModel.stateFlow.test { - assertEquals(DEFAULT_STATE.copy(emailInput = "input"), awaitItem()) - } - } - - @Test - fun `PasswordHintChange update passwordInput`() = runTest { - val viewModel = CreateAccountViewModel( - savedStateHandle = SavedStateHandle(), - authRepository = mockAuthRepository, - ) - viewModel.trySendAction(PasswordHintChange("input")) - viewModel.stateFlow.test { - assertEquals(DEFAULT_STATE.copy(passwordHintInput = "input"), awaitItem()) - } - } - - @Test - fun `PasswordInputChange update passwordInput and call getPasswordStrength`() = runTest { - coEvery { - mockAuthRepository.getPasswordStrength("", "input") - } returns PasswordStrengthResult.Error(error = Throwable("Fail!")) - val viewModel = CreateAccountViewModel( - savedStateHandle = SavedStateHandle(), - authRepository = mockAuthRepository, - ) - viewModel.trySendAction(PasswordInputChange("input")) - viewModel.stateFlow.test { - assertEquals(DEFAULT_STATE.copy(passwordInput = "input"), awaitItem()) - } - coVerify { mockAuthRepository.getPasswordStrength("", "input") } - } - - @Test - fun `CheckDataBreachesToggle should change isCheckDataBreachesToggled`() = runTest { - val viewModel = CreateAccountViewModel( - savedStateHandle = SavedStateHandle(), - authRepository = mockAuthRepository, - ) - viewModel.trySendAction(CreateAccountAction.CheckDataBreachesToggle(true)) - viewModel.stateFlow.test { - assertEquals(DEFAULT_STATE.copy(isCheckDataBreachesToggled = true), awaitItem()) - } - } - - @Test - fun `AcceptPoliciesToggle should change isAcceptPoliciesToggled`() = runTest { - val viewModel = CreateAccountViewModel( - savedStateHandle = SavedStateHandle(), - authRepository = mockAuthRepository, - ) - viewModel.trySendAction(AcceptPoliciesToggle(true)) - viewModel.stateFlow.test { - assertEquals(DEFAULT_STATE.copy(isAcceptPoliciesToggled = true), awaitItem()) - } - } - - @Test - fun `ReceivePasswordStrengthResult should update password strength state`() = runTest { - val viewModel = CreateAccountViewModel( - savedStateHandle = SavedStateHandle(), - authRepository = mockAuthRepository, - ) - viewModel.stateFlow.test { - assertEquals( - DEFAULT_STATE.copy( - passwordStrengthState = PasswordStrengthState.NONE, - ), - awaitItem(), - ) - - viewModel.trySendAction( - ReceivePasswordStrengthResult(PasswordStrengthResult.Success(LEVEL_0)), - ) - assertEquals( - DEFAULT_STATE.copy( - passwordStrengthState = PasswordStrengthState.WEAK_1, - ), - awaitItem(), - ) - - viewModel.trySendAction( - ReceivePasswordStrengthResult(PasswordStrengthResult.Success(LEVEL_1)), - ) - assertEquals( - DEFAULT_STATE.copy( - passwordStrengthState = PasswordStrengthState.WEAK_2, - ), - awaitItem(), - ) - - viewModel.trySendAction( - ReceivePasswordStrengthResult(PasswordStrengthResult.Success(LEVEL_2)), - ) - assertEquals( - DEFAULT_STATE.copy( - passwordStrengthState = PasswordStrengthState.WEAK_3, - ), - awaitItem(), - ) - - viewModel.trySendAction( - ReceivePasswordStrengthResult(PasswordStrengthResult.Success(LEVEL_3)), - ) - assertEquals( - DEFAULT_STATE.copy( - passwordStrengthState = PasswordStrengthState.GOOD, - ), - awaitItem(), - ) - - viewModel.trySendAction( - ReceivePasswordStrengthResult(PasswordStrengthResult.Success(LEVEL_4)), - ) - assertEquals( - DEFAULT_STATE.copy( - passwordStrengthState = PasswordStrengthState.STRONG, - ), - awaitItem(), - ) - } - } - - private fun createCreateAccountViewModel( - createAccountState: CreateAccountState? = null, - authRepository: AuthRepository = mockAuthRepository, - ): CreateAccountViewModel = - CreateAccountViewModel( - savedStateHandle = SavedStateHandle(mapOf("state" to createAccountState)), - authRepository = authRepository, - ) - - companion object { - private const val PASSWORD = "longenoughtpassword" - private const val EMAIL = "test@test.com" - private val DEFAULT_STATE = CreateAccountState( - passwordInput = "", - emailInput = "", - confirmPasswordInput = "", - passwordHintInput = "", - isCheckDataBreachesToggled = true, - isAcceptPoliciesToggled = false, - dialog = null, - passwordStrengthState = PasswordStrengthState.NONE, - ) - private val VALID_INPUT_STATE = CreateAccountState( - passwordInput = PASSWORD, - emailInput = EMAIL, - confirmPasswordInput = PASSWORD, - passwordHintInput = "", - isCheckDataBreachesToggled = false, - isAcceptPoliciesToggled = true, - dialog = null, - passwordStrengthState = PasswordStrengthState.GOOD, - ) - } -} diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/auth/feature/landing/LandingScreenTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/auth/feature/landing/LandingScreenTest.kt index 427ab615e5..51bb791634 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/auth/feature/landing/LandingScreenTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/auth/feature/landing/LandingScreenTest.kt @@ -47,7 +47,6 @@ import org.junit.jupiter.api.Assertions.assertTrue class LandingScreenTest : BitwardenComposeTest() { private var capturedEmail: String? = null - private var onNavigateToCreateAccountCalled = false private var onNavigateToLoginCalled = false private var onNavigateToEnvironmentCalled = false private var onNavigateToStartRegistrationCalled = false @@ -64,7 +63,6 @@ class LandingScreenTest : BitwardenComposeTest() { fun setUp() { setContent { LandingScreen( - onNavigateToCreateAccount = { onNavigateToCreateAccountCalled = true }, onNavigateToLogin = { capturedEmail -> this.capturedEmail = capturedEmail onNavigateToLoginCalled = true @@ -316,12 +314,6 @@ class LandingScreenTest : BitwardenComposeTest() { } } - @Test - fun `NavigateToCreateAccount event should call onNavigateToCreateAccount`() { - mutableEventFlow.tryEmit(LandingEvent.NavigateToCreateAccount) - assertTrue(onNavigateToCreateAccountCalled) - } - @Test fun `NavigateToLogin event should call onNavigateToLogin`() { val testEmail = "test@test.com" diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/auth/feature/landing/LandingViewModelTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/auth/feature/landing/LandingViewModelTest.kt index d5a238fb9b..8b5c61b61e 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/auth/feature/landing/LandingViewModelTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/auth/feature/landing/LandingViewModelTest.kt @@ -12,9 +12,7 @@ import com.x8bit.bitwarden.data.auth.repository.AuthRepository import com.x8bit.bitwarden.data.auth.repository.model.LogoutReason import com.x8bit.bitwarden.data.auth.repository.model.UserState import com.x8bit.bitwarden.data.auth.repository.model.VaultUnlockType -import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager import com.x8bit.bitwarden.data.platform.manager.model.FirstTimeState -import com.bitwarden.core.data.manager.model.FlagKey import com.x8bit.bitwarden.data.platform.repository.util.FakeEnvironmentRepository import com.x8bit.bitwarden.data.vault.repository.VaultRepository import com.x8bit.bitwarden.ui.platform.components.model.AccountSummary @@ -49,9 +47,6 @@ class LandingViewModelTest : BaseViewModelTest() { getSnackbarDataFlow(SnackbarRelay.ENVIRONMENT_SAVED) } returns mutableSnackbarSharedFlow } - private val featureFlagManager: FeatureFlagManager = mockk(relaxed = true) { - every { getFeatureFlag(FlagKey.EmailVerification) } returns false - } @Test fun `initial state should be correct when there is no remembered email`() = runTest { @@ -405,31 +400,17 @@ class LandingViewModelTest : BaseViewModelTest() { } @Test - fun `CreateAccountClick should emit NavigateToCreateAccount`() = runTest { + fun `CreateAccountClick should emit NavigateToStartRegistration`() = runTest { val viewModel = createViewModel() viewModel.eventFlow.test { viewModel.trySendAction(LandingAction.CreateAccountClick) assertEquals( - LandingEvent.NavigateToCreateAccount, + LandingEvent.NavigateToStartRegistration, awaitItem(), ) } } - @Test - fun `When feature is enabled CreateAccountClick should emit NavigateToStartRegistration`() = - runTest { - every { featureFlagManager.getFeatureFlag(FlagKey.EmailVerification) } returns true - val viewModel = createViewModel() - viewModel.eventFlow.test { - viewModel.trySendAction(LandingAction.CreateAccountClick) - assertEquals( - LandingEvent.NavigateToStartRegistration, - awaitItem(), - ) - } - } - @Test fun `DialogDismiss should clear the active dialog`() { val initialState = DEFAULT_STATE.copy( @@ -623,7 +604,6 @@ class LandingViewModelTest : BaseViewModelTest() { }, vaultRepository = vaultRepository, environmentRepository = fakeEnvironmentRepository, - featureFlagManager = featureFlagManager, snackbarRelayManager = snackbarRelayManager, savedStateHandle = savedStateHandle, ) diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/auth/feature/welcome/WelcomeScreenTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/auth/feature/welcome/WelcomeScreenTest.kt index 2824888f0c..528245ff61 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/auth/feature/welcome/WelcomeScreenTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/auth/feature/welcome/WelcomeScreenTest.kt @@ -17,7 +17,6 @@ import org.robolectric.annotation.Config class WelcomeScreenTest : BitwardenComposeTest() { private var onNavigateToStartRegistrationCalled = false - private var onNavigateToCreateAccountCalled = false private var onNavigateToLoginCalled = false private val mutableStateFlow = MutableStateFlow(DEFAULT_STATE) private val mutableEventFlow = bufferedMutableSharedFlow() @@ -30,7 +29,6 @@ class WelcomeScreenTest : BitwardenComposeTest() { fun setUp() { setContent { WelcomeScreen( - onNavigateToCreateAccount = { onNavigateToCreateAccountCalled = true }, onNavigateToLogin = { onNavigateToLoginCalled = true }, onNavigateToStartRegistration = { onNavigateToStartRegistrationCalled = true }, viewModel = viewModel, @@ -85,12 +83,6 @@ class WelcomeScreenTest : BitwardenComposeTest() { .assertIsDisplayed() } - @Test - fun `NavigateToCreateAccount event should call onNavigateToCreateAccount`() { - mutableEventFlow.tryEmit(WelcomeEvent.NavigateToCreateAccount) - assertTrue(onNavigateToCreateAccountCalled) - } - @Test fun `NavigateToLogin event should call onNavigateToLogin`() { mutableEventFlow.tryEmit(WelcomeEvent.NavigateToLogin) diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/auth/feature/welcome/WelcomeViewModelTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/auth/feature/welcome/WelcomeViewModelTest.kt index 75265abaa2..c3876175c0 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/auth/feature/welcome/WelcomeViewModelTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/auth/feature/welcome/WelcomeViewModelTest.kt @@ -2,21 +2,15 @@ package com.x8bit.bitwarden.ui.auth.feature.welcome import app.cash.turbine.test import com.bitwarden.ui.platform.base.BaseViewModelTest -import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager -import com.bitwarden.core.data.manager.model.FlagKey -import io.mockk.every -import io.mockk.mockk import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test class WelcomeViewModelTest : BaseViewModelTest() { - private val featureFlagManager = mockk() - @Test fun `initial state should be correct`() = runTest { - val viewModel = WelcomeViewModel(featureFlagManager = featureFlagManager) + val viewModel = createViewModel() viewModel.stateFlow.test { assertEquals( @@ -28,7 +22,7 @@ class WelcomeViewModelTest : BaseViewModelTest() { @Test fun `PagerSwipe should update state`() = runTest { - val viewModel = WelcomeViewModel(featureFlagManager = featureFlagManager) + val viewModel = createViewModel() val newIndex = 2 viewModel.trySendAction(WelcomeAction.PagerSwipe(index = newIndex)) @@ -43,7 +37,7 @@ class WelcomeViewModelTest : BaseViewModelTest() { @Test fun `DotClick should update state and emit UpdatePager`() = runTest { - val viewModel = WelcomeViewModel(featureFlagManager = featureFlagManager) + val viewModel = createViewModel() val newIndex = 2 viewModel.trySendAction(WelcomeAction.DotClick(index = newIndex)) @@ -62,41 +56,22 @@ class WelcomeViewModelTest : BaseViewModelTest() { } } - @Suppress("MaxLineLength") @Test - fun `CreateAccountClick should emit NavigateToCreateAccount when email verification is disabled`() = - runTest { - val viewModel = WelcomeViewModel(featureFlagManager = featureFlagManager) - every { featureFlagManager.getFeatureFlag(FlagKey.EmailVerification) } returns false - viewModel.trySendAction(WelcomeAction.CreateAccountClick) + fun `CreateAccountClick should emit NavigateToStartRegistration`() = runTest { + val viewModel = createViewModel() + viewModel.trySendAction(WelcomeAction.CreateAccountClick) - viewModel.eventFlow.test { - assertEquals( - WelcomeEvent.NavigateToCreateAccount, - awaitItem(), - ) - } - } - - @Suppress("MaxLineLength") - @Test - fun `CreateAccountClick should emit NavigateToStartRegistration when email verification is enabled`() = - runTest { - val viewModel = WelcomeViewModel(featureFlagManager = featureFlagManager) - every { featureFlagManager.getFeatureFlag(FlagKey.EmailVerification) } returns true - viewModel.trySendAction(WelcomeAction.CreateAccountClick) - - viewModel.eventFlow.test { - assertEquals( - WelcomeEvent.NavigateToStartRegistration, - awaitItem(), - ) - } + viewModel.eventFlow.test { + assertEquals( + WelcomeEvent.NavigateToStartRegistration, + awaitItem(), + ) } + } @Test fun `LoginClick should emit NavigateToLogin`() = runTest { - val viewModel = WelcomeViewModel(featureFlagManager = featureFlagManager) + val viewModel = createViewModel() viewModel.trySendAction(WelcomeAction.LoginClick) @@ -107,6 +82,8 @@ class WelcomeViewModelTest : BaseViewModelTest() { ) } } + + private fun createViewModel(): WelcomeViewModel = WelcomeViewModel() } private val DEFAULT_STATE = WelcomeState( diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/feature/debugmenu/DebugMenuScreenTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/feature/debugmenu/DebugMenuScreenTest.kt index 8d33b52164..3f85604bfe 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/feature/debugmenu/DebugMenuScreenTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/feature/debugmenu/DebugMenuScreenTest.kt @@ -5,8 +5,8 @@ import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performScrollTo -import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow import com.bitwarden.core.data.manager.model.FlagKey +import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow import com.x8bit.bitwarden.ui.platform.base.BitwardenComposeTest import io.mockk.every import io.mockk.mockk @@ -85,13 +85,13 @@ class DebugMenuScreenTest : BitwardenComposeTest() { mutableStateFlow.tryEmit( DebugMenuState( featureFlags = persistentMapOf( - FlagKey.EmailVerification to true, + FlagKey.CredentialExchangeProtocolImport to true, ), ), ) composeTestRule - .onNodeWithText("Email Verification", ignoreCase = true) + .onNodeWithText("CXP Import", ignoreCase = true) .assertExists() } @@ -100,18 +100,18 @@ class DebugMenuScreenTest : BitwardenComposeTest() { mutableStateFlow.tryEmit( DebugMenuState( featureFlags = persistentMapOf( - FlagKey.EmailVerification to true, + FlagKey.CredentialExchangeProtocolImport to true, ), ), ) composeTestRule - .onNodeWithText("Email Verification", ignoreCase = true) + .onNodeWithText("CXP Import", ignoreCase = true) .performClick() verify(exactly = 1) { viewModel.trySendAction( DebugMenuAction.UpdateFeatureFlag( - FlagKey.EmailVerification, + FlagKey.CredentialExchangeProtocolImport, false, ), ) diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/feature/debugmenu/DebugMenuViewModelTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/feature/debugmenu/DebugMenuViewModelTest.kt index fa26215747..c2cbaaf9fd 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/feature/debugmenu/DebugMenuViewModelTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/feature/debugmenu/DebugMenuViewModelTest.kt @@ -1,11 +1,11 @@ package com.x8bit.bitwarden.ui.platform.feature.debugmenu import app.cash.turbine.test +import com.bitwarden.core.data.manager.model.FlagKey import com.bitwarden.ui.platform.base.BaseViewModelTest import com.x8bit.bitwarden.data.auth.repository.AuthRepository import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager import com.x8bit.bitwarden.data.platform.manager.LogsManager -import com.bitwarden.core.data.manager.model.FlagKey import com.x8bit.bitwarden.data.platform.repository.DebugMenuRepository import com.x8bit.bitwarden.data.util.assertCoroutineThrows import io.mockk.coEvery @@ -101,10 +101,10 @@ class DebugMenuViewModelTest : BaseViewModelTest() { fun `handleUpdateFeatureFlag should update the feature flag via the repository`() { val viewModel = createViewModel() viewModel.trySendAction( - DebugMenuAction.UpdateFeatureFlag(FlagKey.EmailVerification, false), + DebugMenuAction.UpdateFeatureFlag(FlagKey.CipherKeyEncryption, false), ) verify(exactly = 1) { - mockDebugMenuRepository.updateFeatureFlag(FlagKey.EmailVerification, false) + mockDebugMenuRepository.updateFeatureFlag(FlagKey.CipherKeyEncryption, false) } } @@ -144,7 +144,6 @@ class DebugMenuViewModelTest : BaseViewModelTest() { } private val DEFAULT_MAP_VALUE: ImmutableMap, Any> = persistentMapOf( - FlagKey.EmailVerification to true, FlagKey.CredentialExchangeProtocolImport to true, FlagKey.CredentialExchangeProtocolExport to true, FlagKey.RestrictCipherItemDeletion to true, @@ -153,7 +152,6 @@ private val DEFAULT_MAP_VALUE: ImmutableMap, Any> = persistentMapOf ) private val UPDATED_MAP_VALUE: ImmutableMap, Any> = persistentMapOf( - FlagKey.EmailVerification to false, FlagKey.CredentialExchangeProtocolImport to false, FlagKey.CredentialExchangeProtocolExport to false, FlagKey.RestrictCipherItemDeletion to false, diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/debugmenu/components/FeatureFlagListItems.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/debugmenu/components/FeatureFlagListItems.kt index 73dcc12f7f..efe9294778 100644 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/debugmenu/components/FeatureFlagListItems.kt +++ b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/debugmenu/components/FeatureFlagListItems.kt @@ -26,7 +26,6 @@ fun FlagKey.ListItemContent( FlagKey.CipherKeyEncryption, FlagKey.CredentialExchangeProtocolExport, FlagKey.CredentialExchangeProtocolImport, - FlagKey.EmailVerification, FlagKey.RemoveCardPolicy, FlagKey.RestrictCipherItemDeletion, FlagKey.UserManagedPrivilegedApps, @@ -67,7 +66,6 @@ private fun FlagKey.getDisplayLabel(): String = when (this) { FlagKey.DummyString, -> this.keyName - FlagKey.EmailVerification -> stringResource(BitwardenString.email_verification) FlagKey.CredentialExchangeProtocolImport -> stringResource(BitwardenString.cxp_import) FlagKey.CredentialExchangeProtocolExport -> stringResource(BitwardenString.cxp_export) FlagKey.CipherKeyEncryption -> stringResource(BitwardenString.cipher_key_encryption) diff --git a/core/src/main/kotlin/com/bitwarden/core/data/manager/model/FlagKey.kt b/core/src/main/kotlin/com/bitwarden/core/data/manager/model/FlagKey.kt index 63e5a354a0..8054527f0a 100644 --- a/core/src/main/kotlin/com/bitwarden/core/data/manager/model/FlagKey.kt +++ b/core/src/main/kotlin/com/bitwarden/core/data/manager/model/FlagKey.kt @@ -30,7 +30,6 @@ sealed class FlagKey { */ val activePasswordManagerFlags: List> by lazy { listOf( - EmailVerification, CredentialExchangeProtocolImport, CredentialExchangeProtocolExport, RestrictCipherItemDeletion, @@ -40,14 +39,6 @@ sealed class FlagKey { } } - /** - * Data object holding the key for Email Verification feature. - */ - data object EmailVerification : FlagKey() { - override val keyName: String = "email-verification" - override val defaultValue: Boolean = false - } - /** * Data object holding hte feature flag key for the Credential Exchange Protocol (CXP) import * feature. diff --git a/core/src/test/kotlin/com/bitwarden/core/data/manager/model/FlagKeyTest.kt b/core/src/test/kotlin/com/bitwarden/core/data/manager/model/FlagKeyTest.kt index af0aca7f45..5e1d16ae44 100644 --- a/core/src/test/kotlin/com/bitwarden/core/data/manager/model/FlagKeyTest.kt +++ b/core/src/test/kotlin/com/bitwarden/core/data/manager/model/FlagKeyTest.kt @@ -8,10 +8,6 @@ class FlagKeyTest { @Test fun `Feature flags have the correct key name set`() { - assertEquals( - FlagKey.EmailVerification.keyName, - "email-verification", - ) assertEquals( FlagKey.CredentialExchangeProtocolImport.keyName, "cxp-import-mobile", @@ -46,7 +42,6 @@ class FlagKeyTest { fun `All feature flags have the correct default value set`() { assertTrue( listOf( - FlagKey.EmailVerification, FlagKey.CredentialExchangeProtocolImport, FlagKey.CredentialExchangeProtocolExport, FlagKey.CipherKeyEncryption, diff --git a/ui/src/main/res/values/strings.xml b/ui/src/main/res/values/strings.xml index 0c826a789c..fe88bf17ac 100644 --- a/ui/src/main/res/values/strings.xml +++ b/ui/src/main/res/values/strings.xml @@ -420,7 +420,6 @@ Scanning will happen automatically. Invalid password Password does not meet organization requirements. Please check the policy information and try again. Loading - Terms of Service and Privacy Policy have not been acknowledged. Terms of Service Privacy Policy Passkey management @@ -609,8 +608,6 @@ Do you want to switch to this account? Language The language has been changed to %1$s. Please restart the app to see the change Default (System) - Important - Your master password cannot be recovered if you forget it! %1$s characters minimum. Weak Master Password Weak password identified. Use a strong password to protect your account. Are you sure you want to use a weak password? Weak @@ -707,7 +704,6 @@ Do you want to switch to this account? Create account on: We sent an email to %1$s. By continuing, you agree to the Terms of Service and Privacy Policy - By activating this switch, you agree to the Terms of Service and Privacy Policy Unsubscribe Check your email Open email app diff --git a/ui/src/main/res/values/strings_non_localized.xml b/ui/src/main/res/values/strings_non_localized.xml index 84b10e23dc..abc43f075d 100644 --- a/ui/src/main/res/values/strings_non_localized.xml +++ b/ui/src/main/res/values/strings_non_localized.xml @@ -8,7 +8,6 @@ .json (%1$s) - Email Verification Feature Flags: Debug Menu Reset values