mirror of
https://github.com/bitwarden/android.git
synced 2024-11-24 10:25:57 +03:00
BIT-543: Add Remember Me functionality to Landing Screen (#104)
Co-authored-by: Brian Yencho <brian@livefront.com>
This commit is contained in:
parent
c7ab805f91
commit
5a2a2f93f3
12 changed files with 306 additions and 28 deletions
|
@ -0,0 +1,11 @@
|
|||
package com.x8bit.bitwarden.data.auth.datasource.disk
|
||||
|
||||
/**
|
||||
* Primary access point for disk information.
|
||||
*/
|
||||
interface AuthDiskSource {
|
||||
/**
|
||||
* The currently persisted saved email address (or `null` if not set).
|
||||
*/
|
||||
var rememberedEmailAddress: String?
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package com.x8bit.bitwarden.data.auth.datasource.disk
|
||||
|
||||
import android.content.SharedPreferences
|
||||
|
||||
private const val REMEMBERED_EMAIL_ADDRESS_KEY = "bwPreferencesStorage:rememberedEmail"
|
||||
|
||||
/**
|
||||
* Primary implementation of [AuthDiskSource].
|
||||
*/
|
||||
class AuthDiskSourceImpl(
|
||||
private val sharedPreferences: SharedPreferences,
|
||||
) : AuthDiskSource {
|
||||
override var rememberedEmailAddress: String?
|
||||
get() = sharedPreferences.getString(REMEMBERED_EMAIL_ADDRESS_KEY, null)
|
||||
set(value) {
|
||||
sharedPreferences
|
||||
.edit()
|
||||
.putString(REMEMBERED_EMAIL_ADDRESS_KEY, value)
|
||||
.apply()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package com.x8bit.bitwarden.data.auth.datasource.disk.di
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSourceImpl
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Provides persistence-related dependencies in the auth package.
|
||||
*/
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object DiskModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideAuthDiskSource(
|
||||
sharedPreferences: SharedPreferences,
|
||||
): AuthDiskSource =
|
||||
AuthDiskSourceImpl(sharedPreferences = sharedPreferences)
|
||||
}
|
|
@ -21,6 +21,11 @@ interface AuthRepository {
|
|||
*/
|
||||
val captchaTokenResultFlow: Flow<CaptchaCallbackTokenResult>
|
||||
|
||||
/**
|
||||
* The currently persisted saved email address (or `null` if not set).
|
||||
*/
|
||||
var rememberedEmailAddress: String?
|
||||
|
||||
/**
|
||||
* Attempt to login with the given email and password. Updated access token will be reflected
|
||||
* in [authStateFlow].
|
||||
|
|
|
@ -2,6 +2,7 @@ package com.x8bit.bitwarden.data.auth.repository
|
|||
|
||||
import com.bitwarden.core.Kdf
|
||||
import com.bitwarden.sdk.Client
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthState
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.GetTokenResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.GetTokenResponseJson.CaptchaRequired
|
||||
|
@ -21,6 +22,8 @@ import kotlinx.coroutines.flow.asStateFlow
|
|||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
private const val REMEMBERED_EMAIL_ADDRESS_KEY = "bwPreferencesStorage:rememberedEmail"
|
||||
|
||||
/**
|
||||
* Default implementation of [AuthRepository].
|
||||
*/
|
||||
|
@ -29,6 +32,7 @@ class AuthRepositoryImpl @Inject constructor(
|
|||
private val accountsService: AccountsService,
|
||||
private val identityService: IdentityService,
|
||||
private val bitwardenSdkClient: Client,
|
||||
private val authDiskSource: AuthDiskSource,
|
||||
private val authTokenInterceptor: AuthTokenInterceptor,
|
||||
) : AuthRepository {
|
||||
|
||||
|
@ -40,6 +44,12 @@ class AuthRepositoryImpl @Inject constructor(
|
|||
override val captchaTokenResultFlow: Flow<CaptchaCallbackTokenResult> =
|
||||
mutableCaptchaTokenFlow.asSharedFlow()
|
||||
|
||||
override var rememberedEmailAddress: String?
|
||||
get() = authDiskSource.rememberedEmailAddress
|
||||
set(value) {
|
||||
authDiskSource.rememberedEmailAddress = value
|
||||
}
|
||||
|
||||
override suspend fun login(
|
||||
email: String,
|
||||
password: String,
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
package com.x8bit.bitwarden.data.platform.datasource.di
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Provides dependencies related to encryption / decryption / secure generation.
|
||||
*/
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object PreferenceModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideDefaultSharedPreferences(
|
||||
application: Application,
|
||||
): SharedPreferences = application.getSharedPreferences(null, Context.MODE_PRIVATE)
|
||||
}
|
|
@ -32,10 +32,8 @@ import androidx.compose.ui.graphics.ColorFilter
|
|||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
|
||||
|
@ -43,7 +41,6 @@ import com.x8bit.bitwarden.ui.platform.components.BitwardenFilledButton
|
|||
import com.x8bit.bitwarden.ui.platform.components.BitwardenSwitch
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenTextButton
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenTextField
|
||||
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
||||
|
||||
/**
|
||||
* The top level composable for the Landing screen.
|
||||
|
@ -234,15 +231,3 @@ private fun RegionSelector(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun LandingScreen_preview() {
|
||||
BitwardenTheme {
|
||||
LandingScreen(
|
||||
onNavigateToCreateAccount = {},
|
||||
onNavigateToLogin = { _, _ -> },
|
||||
viewModel = LandingViewModel(SavedStateHandle()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package com.x8bit.bitwarden.ui.auth.feature.landing
|
|||
import android.os.Parcelable
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
|
@ -18,13 +19,14 @@ private const val KEY_STATE = "state"
|
|||
*/
|
||||
@HiltViewModel
|
||||
class LandingViewModel @Inject constructor(
|
||||
private val authRepository: AuthRepository,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : BaseViewModel<LandingState, LandingEvent, LandingAction>(
|
||||
initialState = savedStateHandle[KEY_STATE]
|
||||
?: LandingState(
|
||||
emailInput = "",
|
||||
isContinueButtonEnabled = false,
|
||||
isRememberMeEnabled = false,
|
||||
emailInput = authRepository.rememberedEmailAddress.orEmpty(),
|
||||
isContinueButtonEnabled = authRepository.rememberedEmailAddress != null,
|
||||
isRememberMeEnabled = authRepository.rememberedEmailAddress != null,
|
||||
selectedRegion = LandingState.RegionOption.BITWARDEN_US,
|
||||
),
|
||||
) {
|
||||
|
@ -61,8 +63,14 @@ class LandingViewModel @Inject constructor(
|
|||
if (mutableStateFlow.value.emailInput.isBlank()) {
|
||||
return
|
||||
}
|
||||
|
||||
val email = mutableStateFlow.value.emailInput
|
||||
val isRememberMeEnabled = mutableStateFlow.value.isRememberMeEnabled
|
||||
val selectedRegionLabel = mutableStateFlow.value.selectedRegion.label
|
||||
|
||||
// Update the remembered email address
|
||||
authRepository.rememberedEmailAddress = email.takeUnless { !isRememberMeEnabled }
|
||||
|
||||
sendEvent(LandingEvent.NavigateToLogin(email, selectedRegionLabel))
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
package com.x8bit.bitwarden.data.auth.datasource.disk
|
||||
|
||||
import com.x8bit.bitwarden.data.platform.base.FakeSharedPreferences
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertNull
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class AuthDiskSourceTest {
|
||||
private val fakeSharedPreferences = FakeSharedPreferences()
|
||||
|
||||
private val authDiskSource = AuthDiskSourceImpl(
|
||||
sharedPreferences = fakeSharedPreferences,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `rememberedEmailAddress should pull from and update SharedPreferences`() {
|
||||
val rememberedEmailKey = "bwPreferencesStorage:rememberedEmail"
|
||||
|
||||
// Shared preferences and the repository start with the same value.
|
||||
assertNull(authDiskSource.rememberedEmailAddress)
|
||||
assertNull(fakeSharedPreferences.getString(rememberedEmailKey, null))
|
||||
|
||||
// Updating the repository updates shared preferences
|
||||
authDiskSource.rememberedEmailAddress = "remembered@gmail.com"
|
||||
assertEquals(
|
||||
"remembered@gmail.com",
|
||||
fakeSharedPreferences.getString(rememberedEmailKey, null),
|
||||
)
|
||||
|
||||
// Update SharedPreferences updates the repository
|
||||
fakeSharedPreferences.edit().putString(rememberedEmailKey, null).apply()
|
||||
assertNull(authDiskSource.rememberedEmailAddress)
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ package com.x8bit.bitwarden.data.auth.repository
|
|||
import app.cash.turbine.test
|
||||
import com.bitwarden.core.Kdf
|
||||
import com.bitwarden.sdk.Client
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthState
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.GetTokenResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.LoginResult
|
||||
|
@ -19,6 +20,7 @@ import io.mockk.mockk
|
|||
import io.mockk.verify
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertNull
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
|
@ -27,6 +29,7 @@ class AuthRepositoryTest {
|
|||
private val accountsService: AccountsService = mockk()
|
||||
private val identityService: IdentityService = mockk()
|
||||
private val authInterceptor = mockk<AuthTokenInterceptor>()
|
||||
private val fakeAuthDiskSource = FakeAuthDiskSource()
|
||||
private val mockBitwardenSdk = mockk<Client> {
|
||||
coEvery {
|
||||
auth().hashPassword(
|
||||
|
@ -41,6 +44,7 @@ class AuthRepositoryTest {
|
|||
accountsService = accountsService,
|
||||
identityService = identityService,
|
||||
bitwardenSdkClient = mockBitwardenSdk,
|
||||
authDiskSource = fakeAuthDiskSource,
|
||||
authTokenInterceptor = authInterceptor,
|
||||
)
|
||||
|
||||
|
@ -49,6 +53,21 @@ class AuthRepositoryTest {
|
|||
clearMocks(identityService, accountsService, authInterceptor)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `rememberedEmailAddress should pull from and update AuthDiskSource`() {
|
||||
// AuthDiskSource and the repository start with the same value.
|
||||
assertNull(repository.rememberedEmailAddress)
|
||||
assertNull(fakeAuthDiskSource.rememberedEmailAddress)
|
||||
|
||||
// Updating the repository updates AuthDiskSource
|
||||
repository.rememberedEmailAddress = "remembered@gmail.com"
|
||||
assertEquals("remembered@gmail.com", fakeAuthDiskSource.rememberedEmailAddress)
|
||||
|
||||
// Updating AuthDiskSource updates the repository
|
||||
fakeAuthDiskSource.rememberedEmailAddress = null
|
||||
assertNull(repository.rememberedEmailAddress)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `login when pre login fails should return Error with no message`() = runTest {
|
||||
coEvery {
|
||||
|
@ -197,3 +216,7 @@ class AuthRepositoryTest {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class FakeAuthDiskSource : AuthDiskSource {
|
||||
override var rememberedEmailAddress: String? = null
|
||||
}
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
package com.x8bit.bitwarden.data.platform.base
|
||||
|
||||
import android.content.SharedPreferences
|
||||
|
||||
/**
|
||||
* A faked implementation of [SharedPreferences] that is backed by an internal, memory-based map.
|
||||
*/
|
||||
class FakeSharedPreferences : SharedPreferences {
|
||||
private val sharedPreferences: MutableMap<String, Any?> = mutableMapOf()
|
||||
|
||||
override fun contains(key: String): Boolean =
|
||||
sharedPreferences.containsKey(key)
|
||||
|
||||
override fun edit(): SharedPreferences.Editor = Editor()
|
||||
|
||||
override fun getAll(): Map<String, *> = sharedPreferences
|
||||
|
||||
override fun getBoolean(key: String, defaultValue: Boolean): Boolean =
|
||||
getValue(key, defaultValue)
|
||||
|
||||
override fun getFloat(key: String, defaultValue: Float): Float =
|
||||
getValue(key, defaultValue)
|
||||
|
||||
override fun getInt(key: String, defaultValue: Int): Int =
|
||||
getValue(key, defaultValue)
|
||||
|
||||
override fun getLong(key: String, defaultValue: Long): Long =
|
||||
getValue(key, defaultValue)
|
||||
|
||||
override fun getString(key: String, defaultValue: String?): String? =
|
||||
getValue(key, defaultValue)
|
||||
|
||||
override fun getStringSet(key: String, defaultValue: Set<String>?): Set<String>? =
|
||||
getValue(key, defaultValue)
|
||||
|
||||
override fun registerOnSharedPreferenceChangeListener(
|
||||
listener: SharedPreferences.OnSharedPreferenceChangeListener,
|
||||
) {
|
||||
throw NotImplementedError(
|
||||
"registerOnSharedPreferenceChangeListener is not currently implemented.",
|
||||
)
|
||||
}
|
||||
|
||||
override fun unregisterOnSharedPreferenceChangeListener(
|
||||
listener: SharedPreferences.OnSharedPreferenceChangeListener,
|
||||
) {
|
||||
throw NotImplementedError(
|
||||
"unregisterOnSharedPreferenceChangeListener is not currently implemented.",
|
||||
)
|
||||
}
|
||||
|
||||
private inline fun <reified T> getValue(
|
||||
key: String,
|
||||
defaultValue: T,
|
||||
): T = sharedPreferences[key] as? T ?: defaultValue
|
||||
|
||||
inner class Editor : SharedPreferences.Editor {
|
||||
private val pendingSharedPreferences = sharedPreferences.toMutableMap()
|
||||
|
||||
override fun apply() {
|
||||
sharedPreferences.apply {
|
||||
clear()
|
||||
putAll(pendingSharedPreferences)
|
||||
}
|
||||
}
|
||||
|
||||
override fun clear(): SharedPreferences.Editor =
|
||||
apply { pendingSharedPreferences.clear() }
|
||||
|
||||
override fun commit(): Boolean {
|
||||
apply()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun putBoolean(key: String, value: Boolean): SharedPreferences.Editor =
|
||||
putValue(key, value)
|
||||
|
||||
override fun putFloat(key: String, value: Float): SharedPreferences.Editor =
|
||||
putValue(key, value)
|
||||
|
||||
override fun putInt(key: String, value: Int): SharedPreferences.Editor =
|
||||
putValue(key, value)
|
||||
|
||||
override fun putLong(key: String, value: Long): SharedPreferences.Editor =
|
||||
putValue(key, value)
|
||||
|
||||
override fun putString(key: String, value: String?): SharedPreferences.Editor =
|
||||
putValue(key, value)
|
||||
|
||||
override fun putStringSet(key: String, value: Set<String>?): SharedPreferences.Editor =
|
||||
putValue(key, value)
|
||||
|
||||
override fun remove(key: String): SharedPreferences.Editor =
|
||||
apply { pendingSharedPreferences.remove(key) }
|
||||
|
||||
private inline fun <reified T> putValue(
|
||||
key: String,
|
||||
value: T,
|
||||
): SharedPreferences.Editor = apply { pendingSharedPreferences[key] = value }
|
||||
}
|
||||
}
|
|
@ -3,20 +3,37 @@ package com.x8bit.bitwarden.ui.auth.feature.landing
|
|||
import androidx.lifecycle.SavedStateHandle
|
||||
import app.cash.turbine.test
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
||||
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 LandingViewModelTest : BaseViewModelTest() {
|
||||
|
||||
@Test
|
||||
fun `initial state should be correct`() = runTest {
|
||||
val viewModel = LandingViewModel(SavedStateHandle())
|
||||
fun `initial state should be correct when there is no remembered email`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(DEFAULT_STATE, awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `initial state should be correct when there is a remembered email`() = runTest {
|
||||
val rememberedEmail = "remembered@gmail.com"
|
||||
val viewModel = createViewModel(rememberedEmail = rememberedEmail)
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(
|
||||
emailInput = rememberedEmail,
|
||||
isContinueButtonEnabled = true,
|
||||
isRememberMeEnabled = true,
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `initial state should pull from saved state handle when present`() = runTest {
|
||||
val expectedState = DEFAULT_STATE.copy(
|
||||
|
@ -25,7 +42,7 @@ class LandingViewModelTest : BaseViewModelTest() {
|
|||
isRememberMeEnabled = true,
|
||||
)
|
||||
val handle = SavedStateHandle(mapOf("state" to expectedState))
|
||||
val viewModel = LandingViewModel(handle)
|
||||
val viewModel = createViewModel(savedStateHandle = handle)
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(expectedState, awaitItem())
|
||||
}
|
||||
|
@ -33,7 +50,7 @@ class LandingViewModelTest : BaseViewModelTest() {
|
|||
|
||||
@Test
|
||||
fun `ContinueButtonClick should emit NavigateToLogin`() = runTest {
|
||||
val viewModel = LandingViewModel(SavedStateHandle())
|
||||
val viewModel = createViewModel()
|
||||
viewModel.trySendAction(LandingAction.EmailInputChanged("input"))
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.actionChannel.trySend(LandingAction.ContinueButtonClick)
|
||||
|
@ -46,7 +63,7 @@ class LandingViewModelTest : BaseViewModelTest() {
|
|||
|
||||
@Test
|
||||
fun `ContinueButtonClick with empty input should do nothing`() = runTest {
|
||||
val viewModel = LandingViewModel(SavedStateHandle())
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.actionChannel.trySend(LandingAction.ContinueButtonClick)
|
||||
}
|
||||
|
@ -54,7 +71,7 @@ class LandingViewModelTest : BaseViewModelTest() {
|
|||
|
||||
@Test
|
||||
fun `CreateAccountClick should emit NavigateToCreateAccount`() = runTest {
|
||||
val viewModel = LandingViewModel(SavedStateHandle())
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.actionChannel.trySend(LandingAction.CreateAccountClick)
|
||||
assertEquals(
|
||||
|
@ -66,7 +83,7 @@ class LandingViewModelTest : BaseViewModelTest() {
|
|||
|
||||
@Test
|
||||
fun `RememberMeToggle should update value of isRememberMeToggled`() = runTest {
|
||||
val viewModel = LandingViewModel(SavedStateHandle())
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.actionChannel.trySend(LandingAction.RememberMeToggle(true))
|
||||
assertEquals(
|
||||
|
@ -79,7 +96,7 @@ class LandingViewModelTest : BaseViewModelTest() {
|
|||
@Test
|
||||
fun `EmailInputUpdated should update value of email input and continue button state`() =
|
||||
runTest {
|
||||
val viewModel = LandingViewModel(SavedStateHandle())
|
||||
val viewModel = createViewModel()
|
||||
viewModel.stateFlow.test {
|
||||
// Ignore initial state
|
||||
awaitItem()
|
||||
|
@ -109,7 +126,7 @@ class LandingViewModelTest : BaseViewModelTest() {
|
|||
@Test
|
||||
fun `RegionOptionSelect should update value of selected region`() = runTest {
|
||||
val inputRegion = LandingState.RegionOption.BITWARDEN_EU
|
||||
val viewModel = LandingViewModel(SavedStateHandle())
|
||||
val viewModel = createViewModel()
|
||||
viewModel.stateFlow.test {
|
||||
awaitItem()
|
||||
viewModel.trySendAction(LandingAction.RegionOptionSelect(inputRegion))
|
||||
|
@ -120,6 +137,20 @@ class LandingViewModelTest : BaseViewModelTest() {
|
|||
}
|
||||
}
|
||||
|
||||
//region Helper methods
|
||||
|
||||
private fun createViewModel(
|
||||
rememberedEmail: String? = null,
|
||||
savedStateHandle: SavedStateHandle = SavedStateHandle(),
|
||||
): LandingViewModel = LandingViewModel(
|
||||
authRepository = mockk(relaxed = true) {
|
||||
every { rememberedEmailAddress } returns rememberedEmail
|
||||
},
|
||||
savedStateHandle = savedStateHandle,
|
||||
)
|
||||
|
||||
//endregion Helper methods
|
||||
|
||||
companion object {
|
||||
private val DEFAULT_STATE = LandingState(
|
||||
emailInput = "",
|
||||
|
|
Loading…
Reference in a new issue