mirror of
https://github.com/bitwarden/android.git
synced 2024-10-31 07:05:35 +03:00
BIT-725: Replace "region" concept with Environment (#152)
This commit is contained in:
parent
e4ab70a106
commit
2472648434
10 changed files with 120 additions and 75 deletions
|
@ -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].
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
),
|
),
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in a new issue