BIT-725: Replace "region" concept with Environment (#152)

This commit is contained in:
Brian Yencho 2023-10-24 11:17:10 -05:00 committed by Álison Fernandes
parent e4ab70a106
commit 2472648434
10 changed files with 120 additions and 75 deletions

View file

@ -27,12 +27,6 @@ interface AuthRepository {
*/ */
var rememberedEmailAddress: String? var rememberedEmailAddress: String?
/**
* The currently selected region label (`null` if not set).
*/
// TODO replace this with a more robust selected region object BIT-725
var selectedRegionLabel: String
/** /**
* Attempt to login with the given email and password. Updated access token will be reflected * Attempt to login with the given email and password. Updated access token will be reflected
* in [authStateFlow]. * in [authStateFlow].

View file

@ -77,9 +77,6 @@ class AuthRepositoryImpl @Inject constructor(
authDiskSource.rememberedEmailAddress = value authDiskSource.rememberedEmailAddress = value
} }
// TODO Handle selected region functionality BIT-725
override var selectedRegionLabel: String = "bitwarden.us"
override suspend fun login( override suspend fun login(
email: String, email: String,
password: String, password: String,

View file

@ -40,6 +40,7 @@ import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.x8bit.bitwarden.R import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.data.platform.repository.model.Environment
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
import com.x8bit.bitwarden.ui.platform.components.BitwardenBasicDialog import com.x8bit.bitwarden.ui.platform.components.BitwardenBasicDialog
import com.x8bit.bitwarden.ui.platform.components.BitwardenFilledButton import com.x8bit.bitwarden.ui.platform.components.BitwardenFilledButton
@ -132,10 +133,10 @@ fun LandingScreen(
Spacer(modifier = Modifier.height(10.dp)) Spacer(modifier = Modifier.height(10.dp))
RegionSelector( EnvironmentSelector(
selectedOption = state.selectedRegion, selectedOption = state.selectedEnvironment.type,
onOptionSelected = remember(viewModel) { onOptionSelected = remember(viewModel) {
{ viewModel.trySendAction(LandingAction.RegionOptionSelect(it)) } { viewModel.trySendAction(LandingAction.EnvironmentTypeSelect(it)) }
}, },
modifier = Modifier modifier = Modifier
.semantics { testTag = "RegionSelectorDropdown" } .semantics { testTag = "RegionSelectorDropdown" }
@ -208,19 +209,19 @@ fun LandingScreen(
* from a list of options. When an option is selected, it invokes the provided callback * from a list of options. When an option is selected, it invokes the provided callback
* and displays the currently selected region on the UI. * and displays the currently selected region on the UI.
* *
* @param selectedOption The currently selected region option. * @param selectedOption The currently selected environment option.
* @param onOptionSelected A callback that gets invoked when a region option is selected * @param onOptionSelected A callback that gets invoked when an environment option is selected
* and passes the selected option as an argument. * and passes the selected option as an argument.
* @param modifier A [Modifier] for the composable. * @param modifier A [Modifier] for the composable.
* *
*/ */
@Composable @Composable
private fun RegionSelector( private fun EnvironmentSelector(
selectedOption: LandingState.RegionOption, selectedOption: Environment.Type,
onOptionSelected: (LandingState.RegionOption) -> Unit, onOptionSelected: (Environment.Type) -> Unit,
modifier: Modifier, modifier: Modifier,
) { ) {
val options = LandingState.RegionOption.values().toList() val options = Environment.Type.values()
var expanded by remember { mutableStateOf(false) } var expanded by remember { mutableStateOf(false) }
Box(modifier = modifier) { Box(modifier = modifier) {
@ -238,7 +239,7 @@ private fun RegionSelector(
modifier = Modifier.padding(end = 12.dp), modifier = Modifier.padding(end = 12.dp),
) )
Text( Text(
text = selectedOption.label, text = selectedOption.label(),
style = MaterialTheme.typography.labelLarge, style = MaterialTheme.typography.labelLarge,
color = MaterialTheme.colorScheme.primary, color = MaterialTheme.colorScheme.primary,
modifier = Modifier.padding(end = 8.dp), modifier = Modifier.padding(end = 8.dp),
@ -256,7 +257,7 @@ private fun RegionSelector(
) { ) {
options.forEach { optionString -> options.forEach { optionString ->
DropdownMenuItem( DropdownMenuItem(
text = { Text(text = optionString.label) }, text = { Text(text = optionString.label()) },
onClick = { onClick = {
expanded = false expanded = false
onOptionSelected(optionString) onOptionSelected(optionString)

View file

@ -5,6 +5,8 @@ import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.x8bit.bitwarden.R import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.data.auth.repository.AuthRepository import com.x8bit.bitwarden.data.auth.repository.AuthRepository
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
import com.x8bit.bitwarden.data.platform.repository.model.Environment
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
import com.x8bit.bitwarden.ui.platform.base.util.asText import com.x8bit.bitwarden.ui.platform.base.util.asText
import com.x8bit.bitwarden.ui.platform.base.util.isValidEmail import com.x8bit.bitwarden.ui.platform.base.util.isValidEmail
@ -24,6 +26,7 @@ private const val KEY_STATE = "state"
@HiltViewModel @HiltViewModel
class LandingViewModel @Inject constructor( class LandingViewModel @Inject constructor(
private val authRepository: AuthRepository, private val authRepository: AuthRepository,
private val environmentRepository: EnvironmentRepository,
savedStateHandle: SavedStateHandle, savedStateHandle: SavedStateHandle,
) : BaseViewModel<LandingState, LandingEvent, LandingAction>( ) : BaseViewModel<LandingState, LandingEvent, LandingAction>(
initialState = savedStateHandle[KEY_STATE] initialState = savedStateHandle[KEY_STATE]
@ -31,15 +34,20 @@ class LandingViewModel @Inject constructor(
emailInput = authRepository.rememberedEmailAddress.orEmpty(), emailInput = authRepository.rememberedEmailAddress.orEmpty(),
isContinueButtonEnabled = authRepository.rememberedEmailAddress != null, isContinueButtonEnabled = authRepository.rememberedEmailAddress != null,
isRememberMeEnabled = authRepository.rememberedEmailAddress != null, isRememberMeEnabled = authRepository.rememberedEmailAddress != null,
selectedRegion = LandingState.RegionOption.BITWARDEN_US, selectedEnvironment = environmentRepository.environment,
errorDialogState = BasicDialogState.Hidden, errorDialogState = BasicDialogState.Hidden,
), ),
) { ) {
init { init {
// As state updates, write to saved state handle: // As state updates:
// - write to saved state handle
// - updated selected environment
stateFlow stateFlow
.onEach { savedStateHandle[KEY_STATE] = it } .onEach {
savedStateHandle[KEY_STATE] = it
environmentRepository.environment = it.selectedEnvironment
}
.launchIn(viewModelScope) .launchIn(viewModelScope)
} }
@ -50,7 +58,7 @@ class LandingViewModel @Inject constructor(
is LandingAction.ErrorDialogDismiss -> handleErrorDialogDismiss() is LandingAction.ErrorDialogDismiss -> handleErrorDialogDismiss()
is LandingAction.RememberMeToggle -> handleRememberMeToggled(action) is LandingAction.RememberMeToggle -> handleRememberMeToggled(action)
is LandingAction.EmailInputChanged -> handleEmailInputUpdated(action) is LandingAction.EmailInputChanged -> handleEmailInputUpdated(action)
is LandingAction.RegionOptionSelect -> handleRegionSelect(action) is LandingAction.EnvironmentTypeSelect -> handleEnvironmentTypeSelect(action)
} }
} }
@ -82,8 +90,6 @@ class LandingViewModel @Inject constructor(
// Update the remembered email address // Update the remembered email address
authRepository.rememberedEmailAddress = email.takeUnless { !isRememberMeEnabled } authRepository.rememberedEmailAddress = email.takeUnless { !isRememberMeEnabled }
// Update the selected region selectedRegionLabel
authRepository.selectedRegionLabel = mutableStateFlow.value.selectedRegion.label
sendEvent(LandingEvent.NavigateToLogin(email)) sendEvent(LandingEvent.NavigateToLogin(email))
} }
@ -102,10 +108,21 @@ class LandingViewModel @Inject constructor(
mutableStateFlow.update { it.copy(isRememberMeEnabled = action.isChecked) } mutableStateFlow.update { it.copy(isRememberMeEnabled = action.isChecked) }
} }
private fun handleRegionSelect(action: LandingAction.RegionOptionSelect) { private fun handleEnvironmentTypeSelect(action: LandingAction.EnvironmentTypeSelect) {
val environment = when (action.environmentType) {
Environment.Type.US -> Environment.Us
Environment.Type.EU -> Environment.Eu
Environment.Type.SELF_HOSTED -> {
// TODO Show dialog for setting selected environment (BIT-330)
Environment.SelfHosted(
environmentUrlData = Environment.Us.environmentUrlData,
)
}
}
mutableStateFlow.update { mutableStateFlow.update {
it.copy( it.copy(
selectedRegion = action.regionOption, selectedEnvironment = environment,
) )
} }
} }
@ -119,18 +136,9 @@ data class LandingState(
val emailInput: String, val emailInput: String,
val isContinueButtonEnabled: Boolean, val isContinueButtonEnabled: Boolean,
val isRememberMeEnabled: Boolean, val isRememberMeEnabled: Boolean,
val selectedRegion: RegionOption, val selectedEnvironment: Environment,
val errorDialogState: BasicDialogState, val errorDialogState: BasicDialogState,
) : Parcelable { ) : Parcelable
/**
* Enumerates the possible region options with their corresponding labels.
*/
enum class RegionOption(val label: String) {
BITWARDEN_US("bitwarden.com"),
BITWARDEN_EU("bitwarden.eu"),
SELF_HOSTED("Self-hosted"),
}
}
/** /**
* Models events for the landing screen. * Models events for the landing screen.
@ -185,7 +193,7 @@ sealed class LandingAction {
/** /**
* Indicates that the selection from the region drop down has changed. * Indicates that the selection from the region drop down has changed.
*/ */
data class RegionOptionSelect( data class EnvironmentTypeSelect(
val regionOption: LandingState.RegionOption, val environmentType: Environment.Type,
) : LandingAction() ) : LandingAction()
} }

View file

@ -147,12 +147,12 @@ fun LoginScreen(
.padding(bottom = 24.dp), .padding(bottom = 24.dp),
isEnabled = state.isLoginButtonEnabled, isEnabled = state.isLoginButtonEnabled,
) )
// TODO Get the "login target" from a dropdown (BIT-202)
Text( Text(
text = stringResource( text = stringResource(
id = R.string.logging_in_as_x_on_y, id = R.string.logging_in_as_x_on_y,
state.emailAddress, state.emailAddress,
state.region, state.environmentLabel(),
), ),
textAlign = TextAlign.Start, textAlign = TextAlign.Start,
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,

View file

@ -11,7 +11,9 @@ import com.x8bit.bitwarden.data.auth.repository.AuthRepository
import com.x8bit.bitwarden.data.auth.repository.model.LoginResult import com.x8bit.bitwarden.data.auth.repository.model.LoginResult
import com.x8bit.bitwarden.data.auth.repository.util.CaptchaCallbackTokenResult import com.x8bit.bitwarden.data.auth.repository.util.CaptchaCallbackTokenResult
import com.x8bit.bitwarden.data.auth.repository.util.generateUriForCaptcha import com.x8bit.bitwarden.data.auth.repository.util.generateUriForCaptcha
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
import com.x8bit.bitwarden.ui.platform.base.util.Text
import com.x8bit.bitwarden.ui.platform.base.util.asText import com.x8bit.bitwarden.ui.platform.base.util.asText
import com.x8bit.bitwarden.ui.platform.components.BasicDialogState import com.x8bit.bitwarden.ui.platform.components.BasicDialogState
import com.x8bit.bitwarden.ui.platform.components.LoadingDialogState import com.x8bit.bitwarden.ui.platform.components.LoadingDialogState
@ -31,6 +33,7 @@ private const val KEY_STATE = "state"
@HiltViewModel @HiltViewModel
class LoginViewModel @Inject constructor( class LoginViewModel @Inject constructor(
private val authRepository: AuthRepository, private val authRepository: AuthRepository,
private val environmentRepository: EnvironmentRepository,
savedStateHandle: SavedStateHandle, savedStateHandle: SavedStateHandle,
) : BaseViewModel<LoginState, LoginEvent, LoginAction>( ) : BaseViewModel<LoginState, LoginEvent, LoginAction>(
initialState = savedStateHandle[KEY_STATE] initialState = savedStateHandle[KEY_STATE]
@ -38,7 +41,7 @@ class LoginViewModel @Inject constructor(
emailAddress = LoginArgs(savedStateHandle).emailAddress, emailAddress = LoginArgs(savedStateHandle).emailAddress,
isLoginButtonEnabled = true, isLoginButtonEnabled = true,
passwordInput = "", passwordInput = "",
region = authRepository.selectedRegionLabel, environmentLabel = environmentRepository.environment.label,
loadingDialogState = LoadingDialogState.Hidden, loadingDialogState = LoadingDialogState.Hidden,
errorDialogState = BasicDialogState.Hidden, errorDialogState = BasicDialogState.Hidden,
captchaToken = LoginArgs(savedStateHandle).captchaToken, captchaToken = LoginArgs(savedStateHandle).captchaToken,
@ -193,7 +196,7 @@ data class LoginState(
val passwordInput: String, val passwordInput: String,
val emailAddress: String, val emailAddress: String,
val captchaToken: String?, val captchaToken: String?,
val region: String, val environmentLabel: Text,
val isLoginButtonEnabled: Boolean, val isLoginButtonEnabled: Boolean,
val loadingDialogState: LoadingDialogState, val loadingDialogState: LoadingDialogState,
val errorDialogState: BasicDialogState, val errorDialogState: BasicDialogState,

View file

@ -1,5 +1,6 @@
package com.x8bit.bitwarden.ui.auth.feature.landing package com.x8bit.bitwarden.ui.auth.feature.landing
import android.app.Application
import androidx.compose.ui.test.assert import androidx.compose.ui.test.assert
import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsEnabled import androidx.compose.ui.test.assertIsEnabled
@ -15,6 +16,8 @@ import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performScrollTo import androidx.compose.ui.test.performScrollTo
import androidx.compose.ui.test.performTextInput import androidx.compose.ui.test.performTextInput
import androidx.test.core.app.ApplicationProvider
import com.x8bit.bitwarden.data.platform.repository.model.Environment
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
import com.x8bit.bitwarden.ui.platform.base.util.asText import com.x8bit.bitwarden.ui.platform.base.util.asText
import com.x8bit.bitwarden.ui.platform.components.BasicDialogState import com.x8bit.bitwarden.ui.platform.components.BasicDialogState
@ -29,6 +32,9 @@ import org.junit.Test
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
class LandingScreenTest : BaseComposeTest() { class LandingScreenTest : BaseComposeTest() {
private val resources
get() = ApplicationProvider.getApplicationContext<Application>().resources
@Test @Test
fun `continue button should be enabled or disabled according to the state`() { fun `continue button should be enabled or disabled according to the state`() {
val mutableStateFlow = MutableStateFlow(DEFAULT_STATE) val mutableStateFlow = MutableStateFlow(DEFAULT_STATE)
@ -218,8 +224,8 @@ class LandingScreenTest : BaseComposeTest() {
} }
@Test @Test
fun `selecting region should send RegionOptionSelect action`() { fun `selecting environment should send EnvironmentOptionSelect action`() {
val selectedRegion = LandingState.RegionOption.BITWARDEN_EU val selectedEnvironment = Environment.Eu
val viewModel = mockk<LandingViewModel>(relaxed = true) { val viewModel = mockk<LandingViewModel>(relaxed = true) {
every { eventFlow } returns emptyFlow() every { eventFlow } returns emptyFlow()
every { stateFlow } returns MutableStateFlow(DEFAULT_STATE) every { stateFlow } returns MutableStateFlow(DEFAULT_STATE)
@ -234,13 +240,17 @@ class LandingScreenTest : BaseComposeTest() {
} }
// Clicking to open dropdown // Clicking to open dropdown
composeTestRule.onNodeWithText(LandingState.RegionOption.BITWARDEN_US.label).performClick() composeTestRule
.onNodeWithText(Environment.Us.label.toString(resources))
.performClick()
// Clicking item from the dropdown menu // Clicking item from the dropdown menu
composeTestRule.onNodeWithText(selectedRegion.label).performClick() composeTestRule
.onNodeWithText(selectedEnvironment.label.toString(resources))
.performClick()
verify { verify {
viewModel.trySendAction(LandingAction.RegionOptionSelect(selectedRegion)) viewModel.trySendAction(LandingAction.EnvironmentTypeSelect(selectedEnvironment.type))
} }
} }
@ -319,7 +329,7 @@ class LandingScreenTest : BaseComposeTest() {
emailInput = "", emailInput = "",
isContinueButtonEnabled = true, isContinueButtonEnabled = true,
isRememberMeEnabled = false, isRememberMeEnabled = false,
selectedRegion = LandingState.RegionOption.BITWARDEN_US, selectedEnvironment = Environment.Us,
errorDialogState = BasicDialogState.Hidden, errorDialogState = BasicDialogState.Hidden,
) )
} }

View file

@ -3,6 +3,7 @@ package com.x8bit.bitwarden.ui.auth.feature.landing
import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.SavedStateHandle
import app.cash.turbine.test import app.cash.turbine.test
import com.x8bit.bitwarden.R import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.data.platform.repository.model.Environment
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
import com.x8bit.bitwarden.ui.platform.base.util.asText import com.x8bit.bitwarden.ui.platform.base.util.asText
import com.x8bit.bitwarden.ui.platform.components.BasicDialogState import com.x8bit.bitwarden.ui.platform.components.BasicDialogState
@ -145,14 +146,14 @@ class LandingViewModelTest : BaseViewModelTest() {
} }
@Test @Test
fun `RegionOptionSelect should update value of selected region`() = runTest { fun `EnvironmentTypeSelect should update value of selected region`() = runTest {
val inputRegion = LandingState.RegionOption.BITWARDEN_EU val inputEnvironment = Environment.Eu
val viewModel = createViewModel() val viewModel = createViewModel()
viewModel.stateFlow.test { viewModel.stateFlow.test {
awaitItem() awaitItem()
viewModel.trySendAction(LandingAction.RegionOptionSelect(inputRegion)) viewModel.trySendAction(LandingAction.EnvironmentTypeSelect(inputEnvironment.type))
assertEquals( assertEquals(
DEFAULT_STATE.copy(selectedRegion = LandingState.RegionOption.BITWARDEN_EU), DEFAULT_STATE.copy(selectedEnvironment = Environment.Eu),
awaitItem(), awaitItem(),
) )
} }
@ -162,11 +163,15 @@ class LandingViewModelTest : BaseViewModelTest() {
private fun createViewModel( private fun createViewModel(
rememberedEmail: String? = null, rememberedEmail: String? = null,
environment: Environment = Environment.Us,
savedStateHandle: SavedStateHandle = SavedStateHandle(), savedStateHandle: SavedStateHandle = SavedStateHandle(),
): LandingViewModel = LandingViewModel( ): LandingViewModel = LandingViewModel(
authRepository = mockk(relaxed = true) { authRepository = mockk(relaxed = true) {
every { rememberedEmailAddress } returns rememberedEmail every { rememberedEmailAddress } returns rememberedEmail
}, },
environmentRepository = mockk(relaxed = true) {
every { this@mockk.environment } returns environment
},
savedStateHandle = savedStateHandle, savedStateHandle = savedStateHandle,
) )
@ -177,7 +182,7 @@ class LandingViewModelTest : BaseViewModelTest() {
emailInput = "", emailInput = "",
isContinueButtonEnabled = false, isContinueButtonEnabled = false,
isRememberMeEnabled = false, isRememberMeEnabled = false,
selectedRegion = LandingState.RegionOption.BITWARDEN_US, selectedEnvironment = Environment.Us,
errorDialogState = BasicDialogState.Hidden, errorDialogState = BasicDialogState.Hidden,
) )
} }

View file

@ -14,6 +14,7 @@ import androidx.compose.ui.test.performScrollTo
import androidx.compose.ui.test.performTextInput import androidx.compose.ui.test.performTextInput
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
import com.x8bit.bitwarden.ui.platform.base.util.IntentHandler import com.x8bit.bitwarden.ui.platform.base.util.IntentHandler
import com.x8bit.bitwarden.ui.platform.base.util.asText
import com.x8bit.bitwarden.ui.platform.components.BasicDialogState import com.x8bit.bitwarden.ui.platform.components.BasicDialogState
import com.x8bit.bitwarden.ui.platform.components.LoadingDialogState import com.x8bit.bitwarden.ui.platform.components.LoadingDialogState
import io.mockk.every import io.mockk.every
@ -37,7 +38,7 @@ class LoginScreenTest : BaseComposeTest() {
captchaToken = null, captchaToken = null,
isLoginButtonEnabled = false, isLoginButtonEnabled = false,
passwordInput = "", passwordInput = "",
region = "", environmentLabel = "".asText(),
loadingDialogState = LoadingDialogState.Hidden, loadingDialogState = LoadingDialogState.Hidden,
errorDialogState = BasicDialogState.Hidden, errorDialogState = BasicDialogState.Hidden,
), ),
@ -65,7 +66,7 @@ class LoginScreenTest : BaseComposeTest() {
captchaToken = null, captchaToken = null,
isLoginButtonEnabled = false, isLoginButtonEnabled = false,
passwordInput = "", passwordInput = "",
region = "", environmentLabel = "".asText(),
loadingDialogState = LoadingDialogState.Hidden, loadingDialogState = LoadingDialogState.Hidden,
errorDialogState = BasicDialogState.Hidden, errorDialogState = BasicDialogState.Hidden,
), ),
@ -93,7 +94,7 @@ class LoginScreenTest : BaseComposeTest() {
captchaToken = null, captchaToken = null,
isLoginButtonEnabled = false, isLoginButtonEnabled = false,
passwordInput = "", passwordInput = "",
region = "", environmentLabel = "".asText(),
loadingDialogState = LoadingDialogState.Hidden, loadingDialogState = LoadingDialogState.Hidden,
errorDialogState = BasicDialogState.Hidden, errorDialogState = BasicDialogState.Hidden,
), ),
@ -121,7 +122,7 @@ class LoginScreenTest : BaseComposeTest() {
captchaToken = null, captchaToken = null,
isLoginButtonEnabled = false, isLoginButtonEnabled = false,
passwordInput = "", passwordInput = "",
region = "", environmentLabel = "".asText(),
loadingDialogState = LoadingDialogState.Hidden, loadingDialogState = LoadingDialogState.Hidden,
errorDialogState = BasicDialogState.Hidden, errorDialogState = BasicDialogState.Hidden,
), ),
@ -161,7 +162,7 @@ class LoginScreenTest : BaseComposeTest() {
captchaToken = null, captchaToken = null,
isLoginButtonEnabled = false, isLoginButtonEnabled = false,
passwordInput = "", passwordInput = "",
region = "", environmentLabel = "".asText(),
loadingDialogState = LoadingDialogState.Hidden, loadingDialogState = LoadingDialogState.Hidden,
errorDialogState = BasicDialogState.Hidden, errorDialogState = BasicDialogState.Hidden,
), ),
@ -190,7 +191,7 @@ class LoginScreenTest : BaseComposeTest() {
captchaToken = null, captchaToken = null,
isLoginButtonEnabled = false, isLoginButtonEnabled = false,
passwordInput = "", passwordInput = "",
region = "", environmentLabel = "".asText(),
loadingDialogState = LoadingDialogState.Hidden, loadingDialogState = LoadingDialogState.Hidden,
errorDialogState = BasicDialogState.Hidden, errorDialogState = BasicDialogState.Hidden,
), ),
@ -219,7 +220,7 @@ class LoginScreenTest : BaseComposeTest() {
captchaToken = null, captchaToken = null,
isLoginButtonEnabled = false, isLoginButtonEnabled = false,
passwordInput = "", passwordInput = "",
region = "", environmentLabel = "".asText(),
loadingDialogState = LoadingDialogState.Hidden, loadingDialogState = LoadingDialogState.Hidden,
errorDialogState = BasicDialogState.Hidden, errorDialogState = BasicDialogState.Hidden,
), ),

View file

@ -8,6 +8,8 @@ import com.x8bit.bitwarden.data.auth.repository.AuthRepository
import com.x8bit.bitwarden.data.auth.repository.model.LoginResult import com.x8bit.bitwarden.data.auth.repository.model.LoginResult
import com.x8bit.bitwarden.data.auth.repository.util.CaptchaCallbackTokenResult import com.x8bit.bitwarden.data.auth.repository.util.CaptchaCallbackTokenResult
import com.x8bit.bitwarden.data.auth.repository.util.generateUriForCaptcha import com.x8bit.bitwarden.data.auth.repository.util.generateUriForCaptcha
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
import com.x8bit.bitwarden.data.platform.repository.model.Environment
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
import com.x8bit.bitwarden.ui.platform.base.util.asText import com.x8bit.bitwarden.ui.platform.base.util.asText
import com.x8bit.bitwarden.ui.platform.components.BasicDialogState import com.x8bit.bitwarden.ui.platform.components.BasicDialogState
@ -29,7 +31,6 @@ class LoginViewModelTest : BaseViewModelTest() {
private val savedStateHandle = SavedStateHandle().also { private val savedStateHandle = SavedStateHandle().also {
it["email_address"] = "test@gmail.com" it["email_address"] = "test@gmail.com"
it["region_label"] = ""
} }
@BeforeEach @BeforeEach
@ -47,7 +48,9 @@ class LoginViewModelTest : BaseViewModelTest() {
val viewModel = LoginViewModel( val viewModel = LoginViewModel(
authRepository = mockk { authRepository = mockk {
every { captchaTokenResultFlow } returns flowOf() every { captchaTokenResultFlow } returns flowOf()
every { selectedRegionLabel } returns "bitwarden.us" },
environmentRepository = mockk {
every { environment } returns Environment.Us
}, },
savedStateHandle = savedStateHandle, savedStateHandle = savedStateHandle,
) )
@ -72,6 +75,9 @@ class LoginViewModelTest : BaseViewModelTest() {
authRepository = mockk { authRepository = mockk {
every { captchaTokenResultFlow } returns flowOf() every { captchaTokenResultFlow } returns flowOf()
}, },
environmentRepository = mockk {
every { environment } returns Environment.Us
},
savedStateHandle = handle, savedStateHandle = handle,
) )
viewModel.stateFlow.test { viewModel.stateFlow.test {
@ -84,7 +90,9 @@ class LoginViewModelTest : BaseViewModelTest() {
val viewModel = LoginViewModel( val viewModel = LoginViewModel(
authRepository = mockk { authRepository = mockk {
every { captchaTokenResultFlow } returns flowOf() every { captchaTokenResultFlow } returns flowOf()
every { selectedRegionLabel } returns "bitwarden.us" },
environmentRepository = mockk {
every { environment } returns Environment.Us
}, },
savedStateHandle = savedStateHandle, savedStateHandle = savedStateHandle,
) )
@ -108,10 +116,13 @@ class LoginViewModelTest : BaseViewModelTest() {
) )
} returns LoginResult.Error(errorMessage = "mock_error") } returns LoginResult.Error(errorMessage = "mock_error")
every { captchaTokenResultFlow } returns flowOf() every { captchaTokenResultFlow } returns flowOf()
every { selectedRegionLabel } returns "bitwarden.us" }
val environmentRepository = mockk<EnvironmentRepository> {
every { environment } returns Environment.Us
} }
val viewModel = LoginViewModel( val viewModel = LoginViewModel(
authRepository = authRepository, authRepository = authRepository,
environmentRepository = environmentRepository,
savedStateHandle = savedStateHandle, savedStateHandle = savedStateHandle,
) )
viewModel.stateFlow.test { viewModel.stateFlow.test {
@ -148,10 +159,12 @@ class LoginViewModelTest : BaseViewModelTest() {
login("test@gmail.com", "", captchaToken = null) login("test@gmail.com", "", captchaToken = null)
} returns LoginResult.Success } returns LoginResult.Success
every { captchaTokenResultFlow } returns flowOf() every { captchaTokenResultFlow } returns flowOf()
every { selectedRegionLabel } returns "bitwarden.us"
} }
val viewModel = LoginViewModel( val viewModel = LoginViewModel(
authRepository = authRepository, authRepository = authRepository,
environmentRepository = mockk {
every { environment } returns Environment.Us
},
savedStateHandle = savedStateHandle, savedStateHandle = savedStateHandle,
) )
viewModel.stateFlow.test { viewModel.stateFlow.test {
@ -186,10 +199,12 @@ class LoginViewModelTest : BaseViewModelTest() {
coEvery { login("test@gmail.com", "", captchaToken = null) } returns coEvery { login("test@gmail.com", "", captchaToken = null) } returns
LoginResult.CaptchaRequired(captchaId = "mock_captcha_id") LoginResult.CaptchaRequired(captchaId = "mock_captcha_id")
every { captchaTokenResultFlow } returns flowOf() every { captchaTokenResultFlow } returns flowOf()
every { selectedRegionLabel } returns "bitwarden.us"
} }
val viewModel = LoginViewModel( val viewModel = LoginViewModel(
authRepository = authRepository, authRepository = authRepository,
environmentRepository = mockk {
every { environment } returns Environment.Us
},
savedStateHandle = savedStateHandle, savedStateHandle = savedStateHandle,
) )
viewModel.eventFlow.test { viewModel.eventFlow.test {
@ -207,7 +222,9 @@ class LoginViewModelTest : BaseViewModelTest() {
val viewModel = LoginViewModel( val viewModel = LoginViewModel(
authRepository = mockk { authRepository = mockk {
every { captchaTokenResultFlow } returns flowOf() every { captchaTokenResultFlow } returns flowOf()
every { selectedRegionLabel } returns "bitwarden.us" },
environmentRepository = mockk {
every { environment } returns Environment.Us
}, },
savedStateHandle = savedStateHandle, savedStateHandle = savedStateHandle,
) )
@ -226,7 +243,9 @@ class LoginViewModelTest : BaseViewModelTest() {
val viewModel = LoginViewModel( val viewModel = LoginViewModel(
authRepository = mockk { authRepository = mockk {
every { captchaTokenResultFlow } returns flowOf() every { captchaTokenResultFlow } returns flowOf()
every { selectedRegionLabel } returns "bitwarden.us" },
environmentRepository = mockk {
every { environment } returns Environment.Us
}, },
savedStateHandle = savedStateHandle, savedStateHandle = savedStateHandle,
) )
@ -245,7 +264,9 @@ class LoginViewModelTest : BaseViewModelTest() {
val viewModel = LoginViewModel( val viewModel = LoginViewModel(
authRepository = mockk { authRepository = mockk {
every { captchaTokenResultFlow } returns flowOf() every { captchaTokenResultFlow } returns flowOf()
every { selectedRegionLabel } returns "bitwarden.us" },
environmentRepository = mockk {
every { environment } returns Environment.Us
}, },
savedStateHandle = savedStateHandle, savedStateHandle = savedStateHandle,
) )
@ -264,7 +285,9 @@ class LoginViewModelTest : BaseViewModelTest() {
val viewModel = LoginViewModel( val viewModel = LoginViewModel(
authRepository = mockk { authRepository = mockk {
every { captchaTokenResultFlow } returns flowOf() every { captchaTokenResultFlow } returns flowOf()
every { selectedRegionLabel } returns "bitwarden.us" },
environmentRepository = mockk {
every { environment } returns Environment.Us
}, },
savedStateHandle = savedStateHandle, savedStateHandle = savedStateHandle,
) )
@ -283,7 +306,6 @@ class LoginViewModelTest : BaseViewModelTest() {
every { captchaTokenResultFlow } returns flowOf( every { captchaTokenResultFlow } returns flowOf(
CaptchaCallbackTokenResult.Success("token"), CaptchaCallbackTokenResult.Success("token"),
) )
every { selectedRegionLabel } returns "bitwarden.us"
coEvery { coEvery {
login( login(
"test@gmail.com", "test@gmail.com",
@ -292,8 +314,12 @@ class LoginViewModelTest : BaseViewModelTest() {
) )
} returns LoginResult.Success } returns LoginResult.Success
} }
val environmentRepository = mockk<EnvironmentRepository> {
every { environment } returns Environment.Us
}
LoginViewModel( LoginViewModel(
authRepository = authRepository, authRepository = authRepository,
environmentRepository = environmentRepository,
savedStateHandle = savedStateHandle, savedStateHandle = savedStateHandle,
) )
coVerify { coVerify {
@ -306,7 +332,7 @@ class LoginViewModelTest : BaseViewModelTest() {
emailAddress = "test@gmail.com", emailAddress = "test@gmail.com",
passwordInput = "", passwordInput = "",
isLoginButtonEnabled = true, isLoginButtonEnabled = true,
region = "bitwarden.us", environmentLabel = Environment.Us.type.label,
loadingDialogState = LoadingDialogState.Hidden, loadingDialogState = LoadingDialogState.Hidden,
errorDialogState = BasicDialogState.Hidden, errorDialogState = BasicDialogState.Hidden,
captchaToken = null, captchaToken = null,