BIT-74: Add Login with Device screen (#438)

This commit is contained in:
Caleb Derosier
2023-12-28 15:55:12 -06:00
committed by GitHub
parent 57f9fbc996
commit 2cd698c6b2
13 changed files with 724 additions and 8 deletions

View File

@@ -46,6 +46,7 @@ class LoginScreenTest : BaseComposeTest() {
}
private var onNavigateBackCalled = false
private var onNavigateToEnterpriseSignOnCalled = false
private var onNavigateToLoginWithDeviceCalled = false
private val mutableEventFlow = MutableSharedFlow<LoginEvent>(
extraBufferCapacity = Int.MAX_VALUE,
)
@@ -61,6 +62,7 @@ class LoginScreenTest : BaseComposeTest() {
LoginScreen(
onNavigateBack = { onNavigateBackCalled = true },
onNavigateToEnterpriseSignOn = { onNavigateToEnterpriseSignOnCalled = true },
onNavigateToLoginWithDevice = { onNavigateToLoginWithDeviceCalled = true },
viewModel = viewModel,
intentHandler = intentHandler,
)
@@ -273,6 +275,12 @@ class LoginScreenTest : BaseComposeTest() {
mutableEventFlow.tryEmit(LoginEvent.NavigateToEnterpriseSignOn)
assertTrue(onNavigateToEnterpriseSignOnCalled)
}
@Test
fun `NavigateToLoginWithDevice should call onNavigateToLoginWithDevice`() {
mutableEventFlow.tryEmit(LoginEvent.NavigateToLoginWithDevice)
assertTrue(onNavigateToLoginWithDeviceCalled)
}
}
private val ACTIVE_ACCOUNT_SUMMARY = AccountSummary(

View File

@@ -317,6 +317,19 @@ class LoginViewModelTest : BaseViewModelTest() {
}
}
@Test
fun `LoginWithDeviceButtonClick should emit NavigateToLoginWithDevice`() = runTest {
val viewModel = createViewModel()
viewModel.eventFlow.test {
viewModel.actionChannel.trySend(LoginAction.LoginWithDeviceButtonClick)
assertEquals(DEFAULT_STATE, viewModel.stateFlow.value)
assertEquals(
LoginEvent.NavigateToLoginWithDevice,
awaitItem(),
)
}
}
@Test
fun `SingleSignOnClick should emit NavigateToEnterpriseSignOn`() = runTest {
val viewModel = createViewModel()

View File

@@ -0,0 +1,116 @@
package com.x8bit.bitwarden.ui.auth.feature.loginwithdevice
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performScrollTo
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
import com.x8bit.bitwarden.ui.platform.base.util.asText
import com.x8bit.bitwarden.ui.util.isProgressBar
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import junit.framework.TestCase
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import org.junit.Before
import org.junit.Test
class LoginWithDeviceScreenTest : BaseComposeTest() {
private var onNavigateBackCalled = false
private val mutableEventFlow = MutableSharedFlow<LoginWithDeviceEvent>(
extraBufferCapacity = Int.MAX_VALUE,
)
private val mutableStateFlow = MutableStateFlow(DEFAULT_STATE)
private val viewModel = mockk<LoginWithDeviceViewModel>(relaxed = true) {
every { eventFlow } returns mutableEventFlow
every { stateFlow } returns mutableStateFlow
}
@Before
fun setup() {
composeTestRule.setContent {
LoginWithDeviceScreen(
onNavigateBack = { onNavigateBackCalled = true },
viewModel = viewModel,
)
}
}
@Test
fun `close button click should send CloseButtonClick action`() {
composeTestRule.onNodeWithContentDescription("Close").performClick()
verify {
viewModel.trySendAction(LoginWithDeviceAction.CloseButtonClick)
}
}
@Test
fun `resend notification click should send ResendNotificationClick action`() {
composeTestRule.onNodeWithText("Resend notification").performClick()
verify {
viewModel.trySendAction(LoginWithDeviceAction.ResendNotificationClick)
}
}
@Test
fun `view all log in options click should send ViewAllLogInOptionsClick action`() {
composeTestRule.onNodeWithText("View all log in options").performScrollTo().performClick()
verify {
viewModel.trySendAction(LoginWithDeviceAction.ViewAllLogInOptionsClick)
}
}
@Test
fun `NavigateBack should call onNavigateBack`() {
mutableEventFlow.tryEmit(LoginWithDeviceEvent.NavigateBack)
TestCase.assertTrue(onNavigateBackCalled)
}
@Test
fun `progress bar should be displayed according to state`() {
mutableStateFlow.update {
it.copy(viewState = LoginWithDeviceState.ViewState.Loading)
}
composeTestRule.onNode(isProgressBar).assertIsDisplayed()
mutableStateFlow.update {
it.copy(viewState = LoginWithDeviceState.ViewState.Error("Failure".asText()))
}
composeTestRule.onNode(isProgressBar).assertDoesNotExist()
mutableStateFlow.update {
it.copy(viewState = DEFAULT_STATE.viewState)
}
composeTestRule.onNode(isProgressBar).assertDoesNotExist()
}
@Test
fun `error should be displayed according to state`() {
val errorMessage = "error"
mutableStateFlow.update {
it.copy(viewState = LoginWithDeviceState.ViewState.Loading)
}
composeTestRule.onNodeWithText(errorMessage).assertDoesNotExist()
mutableStateFlow.update {
it.copy(viewState = LoginWithDeviceState.ViewState.Error(errorMessage.asText()))
}
composeTestRule.onNodeWithText(errorMessage).assertIsDisplayed()
mutableStateFlow.update {
it.copy(viewState = DEFAULT_STATE.viewState)
}
composeTestRule.onNodeWithText(errorMessage).assertDoesNotExist()
}
companion object {
private val DEFAULT_STATE = LoginWithDeviceState(
viewState = LoginWithDeviceState.ViewState.Content(
fingerprintPhrase = "alabster-drinkable-mystified-rapping-irrigate",
),
)
}
}

View File

@@ -0,0 +1,72 @@
package com.x8bit.bitwarden.ui.auth.feature.loginwithdevice
import androidx.lifecycle.SavedStateHandle
import app.cash.turbine.test
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
class LoginWithDeviceViewModelTest : BaseViewModelTest() {
private val savedStateHandle = SavedStateHandle()
@Test
fun `initial state should be correct`() = runTest {
val viewModel = createViewModel()
viewModel.stateFlow.test {
assertEquals(DEFAULT_STATE, awaitItem())
}
}
@Test
fun `CloseButtonClick should emit NavigateBack`() = runTest {
val viewModel = createViewModel()
viewModel.eventFlow.test {
viewModel.actionChannel.trySend(LoginWithDeviceAction.CloseButtonClick)
assertEquals(
LoginWithDeviceEvent.NavigateBack,
awaitItem(),
)
}
}
@Test
fun `ResendNotificationClick should emit ShowToast`() = runTest {
val viewModel = createViewModel()
viewModel.eventFlow.test {
viewModel.actionChannel.trySend(LoginWithDeviceAction.ResendNotificationClick)
assertEquals(DEFAULT_STATE, viewModel.stateFlow.value)
assertEquals(
LoginWithDeviceEvent.ShowToast("Not yet implemented."),
awaitItem(),
)
}
}
@Test
fun `ViewAllLogInOptionsClick should emit NavigateBack`() = runTest {
val viewModel = createViewModel()
viewModel.eventFlow.test {
viewModel.actionChannel.trySend(LoginWithDeviceAction.ViewAllLogInOptionsClick)
assertEquals(DEFAULT_STATE, viewModel.stateFlow.value)
assertEquals(
LoginWithDeviceEvent.NavigateBack,
awaitItem(),
)
}
}
private fun createViewModel(): LoginWithDeviceViewModel =
LoginWithDeviceViewModel(
savedStateHandle = savedStateHandle,
)
companion object {
private val DEFAULT_STATE = LoginWithDeviceState(
viewState = LoginWithDeviceState.ViewState.Content(
fingerprintPhrase = "alabster-drinkable-mystified-rapping-irrigate",
),
)
}
}