BIT-698: Add landing email validation (#143)

This commit is contained in:
Brian Yencho
2023-10-23 10:10:58 -05:00
committed by GitHub
parent e0025ac3d5
commit 906980497f
4 changed files with 141 additions and 8 deletions

View File

@@ -1,15 +1,23 @@
package com.x8bit.bitwarden.ui.auth.feature.landing
import androidx.compose.ui.test.assert
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsEnabled
import androidx.compose.ui.test.assertIsNotEnabled
import androidx.compose.ui.test.assertIsOff
import androidx.compose.ui.test.assertIsOn
import androidx.compose.ui.test.assertTextEquals
import androidx.compose.ui.test.filterToOne
import androidx.compose.ui.test.hasAnyAncestor
import androidx.compose.ui.test.isDialog
import androidx.compose.ui.test.onAllNodesWithText
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performScrollTo
import androidx.compose.ui.test.performTextInput
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
import com.x8bit.bitwarden.ui.platform.base.util.asText
import com.x8bit.bitwarden.ui.platform.components.BasicDialogState
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
@@ -21,7 +29,6 @@ import org.junit.Test
import org.junit.jupiter.api.Assertions.assertEquals
class LandingScreenTest : BaseComposeTest() {
@Test
fun `continue button should be enabled or disabled according to the state`() {
val mutableStateFlow = MutableStateFlow(DEFAULT_STATE)
@@ -237,12 +244,83 @@ class LandingScreenTest : BaseComposeTest() {
}
}
@Test
fun `error dialog should be shown or hidden according to the state`() {
val mutableStateFlow = MutableStateFlow(DEFAULT_STATE)
val viewModel = mockk<LandingViewModel>(relaxed = true) {
every { eventFlow } returns emptyFlow()
every { stateFlow } returns mutableStateFlow
}
composeTestRule.setContent {
LandingScreen(
onNavigateToCreateAccount = {},
onNavigateToLogin = { _ -> },
viewModel = viewModel,
)
}
composeTestRule.onNode(isDialog()).assertDoesNotExist()
mutableStateFlow.update {
it.copy(
errorDialogState = BasicDialogState.Shown(
title = "Error dialog title".asText(),
message = "Error dialog message".asText(),
),
)
}
composeTestRule.onNode(isDialog()).assertIsDisplayed()
composeTestRule
.onNodeWithText("Error dialog title")
.assert(hasAnyAncestor(isDialog()))
.assertIsDisplayed()
composeTestRule
.onNodeWithText("Error dialog message")
.assert(hasAnyAncestor(isDialog()))
.assertIsDisplayed()
composeTestRule
.onNodeWithText("Ok")
.assert(hasAnyAncestor(isDialog()))
.assertIsDisplayed()
}
@Test
fun `error dialog OK click should send ErrorDialogDismiss action`() {
val viewModel = mockk<LandingViewModel>(relaxed = true) {
every { eventFlow } returns emptyFlow()
every { stateFlow } returns MutableStateFlow(
DEFAULT_STATE.copy(
errorDialogState = BasicDialogState.Shown(
title = "title".asText(),
message = "message".asText(),
),
),
)
every { trySendAction(LandingAction.ErrorDialogDismiss) } returns Unit
}
composeTestRule.setContent {
LandingScreen(
onNavigateToCreateAccount = {},
onNavigateToLogin = { _ -> },
viewModel = viewModel,
)
}
composeTestRule
.onAllNodesWithText("Ok")
.filterToOne(hasAnyAncestor(isDialog()))
.performClick()
verify { viewModel.trySendAction(LandingAction.ErrorDialogDismiss) }
}
companion object {
val DEFAULT_STATE = LandingState(
emailInput = "",
isContinueButtonEnabled = true,
isRememberMeEnabled = false,
selectedRegion = LandingState.RegionOption.BITWARDEN_US,
errorDialogState = BasicDialogState.Hidden,
)
}
}

View File

@@ -2,7 +2,10 @@ package com.x8bit.bitwarden.ui.auth.feature.landing
import androidx.lifecycle.SavedStateHandle
import app.cash.turbine.test
import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
import com.x8bit.bitwarden.ui.platform.base.util.asText
import com.x8bit.bitwarden.ui.platform.components.BasicDialogState
import io.mockk.every
import io.mockk.mockk
import kotlinx.coroutines.test.runTest
@@ -49,23 +52,41 @@ class LandingViewModelTest : BaseViewModelTest() {
}
@Test
fun `ContinueButtonClick should emit NavigateToLogin`() = runTest {
fun `ContinueButtonClick with valid email should emit NavigateToLogin`() = runTest {
val validEmail = "email@bitwarden.com"
val viewModel = createViewModel()
viewModel.trySendAction(LandingAction.EmailInputChanged("input"))
viewModel.trySendAction(LandingAction.EmailInputChanged(validEmail))
viewModel.eventFlow.test {
viewModel.actionChannel.trySend(LandingAction.ContinueButtonClick)
assertEquals(
LandingEvent.NavigateToLogin("input"),
LandingEvent.NavigateToLogin(validEmail),
awaitItem(),
)
}
}
@Test
fun `ContinueButtonClick with empty input should do nothing`() = runTest {
fun `ContinueButtonClick with invalid email should display an error dialog`() = runTest {
val invalidEmail = "bitwarden.com"
val viewModel = createViewModel()
viewModel.eventFlow.test {
viewModel.trySendAction(LandingAction.EmailInputChanged(invalidEmail))
val initialState = DEFAULT_STATE.copy(
emailInput = invalidEmail,
isContinueButtonEnabled = true,
)
viewModel.stateFlow.test {
assertEquals(initialState, awaitItem())
viewModel.actionChannel.trySend(LandingAction.ContinueButtonClick)
assertEquals(
initialState.copy(
errorDialogState = BasicDialogState.Shown(
title = R.string.an_error_has_occurred.asText(),
message = R.string.invalid_email.asText(),
),
),
awaitItem(),
)
}
}
@@ -157,6 +178,7 @@ class LandingViewModelTest : BaseViewModelTest() {
isContinueButtonEnabled = false,
isRememberMeEnabled = false,
selectedRegion = LandingState.RegionOption.BITWARDEN_US,
errorDialogState = BasicDialogState.Hidden,
)
}
}