mirror of
https://github.com/bitwarden/android.git
synced 2024-11-28 13:58:51 +03:00
BIT-556 BIT-190 Update current CreateAccountScreen to match designs (#92)
This commit is contained in:
parent
eedf0b6f91
commit
38155dbefd
9 changed files with 237 additions and 65 deletions
|
@ -22,7 +22,7 @@ fun NavGraphBuilder.authDestinations(navController: NavHostController) {
|
||||||
startDestination = LANDING_ROUTE,
|
startDestination = LANDING_ROUTE,
|
||||||
route = AUTH_ROUTE,
|
route = AUTH_ROUTE,
|
||||||
) {
|
) {
|
||||||
createAccountDestinations()
|
createAccountDestinations(onNavigateBack = { navController.popBackStack() })
|
||||||
landingDestinations(
|
landingDestinations(
|
||||||
onNavigateToCreateAccount = { navController.navigateToCreateAccount() },
|
onNavigateToCreateAccount = { navController.navigateToCreateAccount() },
|
||||||
onNavigateToLogin = { emailAddress, regionLabel ->
|
onNavigateToLogin = { emailAddress, regionLabel ->
|
||||||
|
|
|
@ -17,8 +17,10 @@ fun NavController.navigateToCreateAccount(navOptions: NavOptions? = null) {
|
||||||
/**
|
/**
|
||||||
* Add the create account screen to the nav graph.
|
* Add the create account screen to the nav graph.
|
||||||
*/
|
*/
|
||||||
fun NavGraphBuilder.createAccountDestinations() {
|
fun NavGraphBuilder.createAccountDestinations(
|
||||||
|
onNavigateBack: () -> Unit,
|
||||||
|
) {
|
||||||
composable(route = CREATE_ACCOUNT_ROUTE) {
|
composable(route = CREATE_ACCOUNT_ROUTE) {
|
||||||
CreateAccountScreen()
|
CreateAccountScreen(onNavigateBack)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,21 +2,19 @@ package com.x8bit.bitwarden.ui.auth.feature.createaccount
|
||||||
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.layout.Arrangement.spacedBy
|
import androidx.compose.foundation.layout.Arrangement.spacedBy
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
|
@ -25,8 +23,10 @@ import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.Con
|
||||||
import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.EmailInputChange
|
import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.EmailInputChange
|
||||||
import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.PasswordHintChange
|
import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.PasswordHintChange
|
||||||
import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.PasswordInputChange
|
import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.PasswordInputChange
|
||||||
import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.SubmitClick
|
|
||||||
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.BitwardenFilledButton
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.BitwardenPasswordField
|
||||||
|
import com.x8bit.bitwarden.ui.platform.components.BitwardenTextButtonTopAppBar
|
||||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenTextField
|
import com.x8bit.bitwarden.ui.platform.components.BitwardenTextField
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -35,12 +35,14 @@ import com.x8bit.bitwarden.ui.platform.components.BitwardenTextField
|
||||||
@Suppress("LongMethod")
|
@Suppress("LongMethod")
|
||||||
@Composable
|
@Composable
|
||||||
fun CreateAccountScreen(
|
fun CreateAccountScreen(
|
||||||
|
onNavigateBack: () -> Unit,
|
||||||
viewModel: CreateAccountViewModel = hiltViewModel(),
|
viewModel: CreateAccountViewModel = hiltViewModel(),
|
||||||
) {
|
) {
|
||||||
val state by viewModel.stateFlow.collectAsState()
|
val state by viewModel.stateFlow.collectAsState()
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
EventsEffect(viewModel) { event ->
|
EventsEffect(viewModel) { event ->
|
||||||
when (event) {
|
when (event) {
|
||||||
|
is CreateAccountEvent.NavigateBack -> onNavigateBack.invoke()
|
||||||
is CreateAccountEvent.ShowToast -> {
|
is CreateAccountEvent.ShowToast -> {
|
||||||
Toast.makeText(context, event.text, Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, event.text, Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
|
@ -51,56 +53,60 @@ fun CreateAccountScreen(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.background(MaterialTheme.colorScheme.surface),
|
.background(MaterialTheme.colorScheme.surface),
|
||||||
verticalArrangement = spacedBy(8.dp),
|
|
||||||
) {
|
) {
|
||||||
Row(
|
BitwardenTextButtonTopAppBar(
|
||||||
modifier = Modifier
|
title = stringResource(id = R.string.create_account),
|
||||||
.fillMaxWidth()
|
navigationIcon = painterResource(id = R.drawable.ic_close),
|
||||||
.background(MaterialTheme.colorScheme.primary),
|
navigationIconContentDescription = stringResource(id = R.string.close),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
onNavigationIconClick = remember(viewModel) {
|
||||||
|
{ viewModel.trySendAction(CreateAccountAction.CloseClick) }
|
||||||
|
},
|
||||||
|
buttonText = stringResource(id = R.string.submit),
|
||||||
|
onButtonClick = remember(viewModel) {
|
||||||
|
{ viewModel.trySendAction(CreateAccountAction.SubmitClick) }
|
||||||
|
},
|
||||||
|
isButtonEnabled = state.isSubmitEnabled,
|
||||||
|
)
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp),
|
||||||
|
verticalArrangement = spacedBy(16.dp),
|
||||||
) {
|
) {
|
||||||
Text(
|
|
||||||
modifier = Modifier
|
|
||||||
.weight(1f)
|
|
||||||
.padding(16.dp),
|
|
||||||
text = stringResource(id = R.string.create_account),
|
|
||||||
color = MaterialTheme.colorScheme.onPrimary,
|
|
||||||
style = MaterialTheme.typography.titleLarge,
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
modifier = Modifier
|
|
||||||
.clickable {
|
|
||||||
viewModel.trySendAction(SubmitClick)
|
|
||||||
}
|
|
||||||
.padding(16.dp),
|
|
||||||
text = stringResource(id = R.string.submit),
|
|
||||||
color = MaterialTheme.colorScheme.onPrimary,
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
BitwardenTextField(
|
BitwardenTextField(
|
||||||
label = stringResource(id = R.string.email_address),
|
label = stringResource(id = R.string.email_address),
|
||||||
value = state.emailInput,
|
value = state.emailInput,
|
||||||
onValueChange = { viewModel.trySendAction(EmailInputChange(it)) },
|
onValueChange = remember(viewModel) {
|
||||||
|
{ viewModel.trySendAction(EmailInputChange(it)) }
|
||||||
|
},
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
BitwardenTextField(
|
BitwardenPasswordField(
|
||||||
label = stringResource(id = R.string.master_password),
|
label = stringResource(id = R.string.master_password),
|
||||||
value = state.passwordInput,
|
value = state.passwordInput,
|
||||||
onValueChange = { viewModel.trySendAction(PasswordInputChange(it)) },
|
onValueChange = remember(viewModel) {
|
||||||
|
{ viewModel.trySendAction(PasswordInputChange(it)) }
|
||||||
|
},
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
BitwardenTextField(
|
BitwardenPasswordField(
|
||||||
label = stringResource(id = R.string.retype_master_password),
|
label = stringResource(id = R.string.retype_master_password),
|
||||||
value = state.confirmPasswordInput,
|
value = state.confirmPasswordInput,
|
||||||
onValueChange = { viewModel.trySendAction(ConfirmPasswordInputChange(it)) },
|
onValueChange = remember {
|
||||||
|
{ viewModel.trySendAction(ConfirmPasswordInputChange(it)) }
|
||||||
|
},
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
BitwardenTextField(
|
BitwardenTextField(
|
||||||
label = stringResource(id = R.string.master_password_hint),
|
label = stringResource(id = R.string.master_password_hint),
|
||||||
value = state.passwordHintInput,
|
value = state.passwordHintInput,
|
||||||
onValueChange = { viewModel.trySendAction(PasswordHintChange(it)) },
|
onValueChange = remember { { viewModel.trySendAction(PasswordHintChange(it)) } },
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
)
|
||||||
|
BitwardenFilledButton(
|
||||||
|
label = stringResource(id = R.string.submit),
|
||||||
|
onClick = remember { { viewModel.trySendAction(CreateAccountAction.SubmitClick) } },
|
||||||
|
isEnabled = state.isSubmitEnabled,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,7 @@ class CreateAccountViewModel @Inject constructor(
|
||||||
passwordInput = "",
|
passwordInput = "",
|
||||||
confirmPasswordInput = "",
|
confirmPasswordInput = "",
|
||||||
passwordHintInput = "",
|
passwordHintInput = "",
|
||||||
|
isSubmitEnabled = false,
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@ -48,9 +49,14 @@ class CreateAccountViewModel @Inject constructor(
|
||||||
is EmailInputChange -> handleEmailInputChanged(action)
|
is EmailInputChange -> handleEmailInputChanged(action)
|
||||||
is PasswordHintChange -> handlePasswordHintChanged(action)
|
is PasswordHintChange -> handlePasswordHintChanged(action)
|
||||||
is PasswordInputChange -> handlePasswordInputChanged(action)
|
is PasswordInputChange -> handlePasswordInputChanged(action)
|
||||||
|
is CreateAccountAction.CloseClick -> handleCloseClick()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleCloseClick() {
|
||||||
|
sendEvent(CreateAccountEvent.NavigateBack)
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleEmailInputChanged(action: EmailInputChange) {
|
private fun handleEmailInputChanged(action: EmailInputChange) {
|
||||||
mutableStateFlow.update { it.copy(emailInput = action.input) }
|
mutableStateFlow.update { it.copy(emailInput = action.input) }
|
||||||
}
|
}
|
||||||
|
@ -81,6 +87,7 @@ data class CreateAccountState(
|
||||||
val passwordInput: String,
|
val passwordInput: String,
|
||||||
val confirmPasswordInput: String,
|
val confirmPasswordInput: String,
|
||||||
val passwordHintInput: String,
|
val passwordHintInput: String,
|
||||||
|
val isSubmitEnabled: Boolean,
|
||||||
) : Parcelable
|
) : Parcelable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -88,6 +95,11 @@ data class CreateAccountState(
|
||||||
*/
|
*/
|
||||||
sealed class CreateAccountEvent {
|
sealed class CreateAccountEvent {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigate back to previous screen.
|
||||||
|
*/
|
||||||
|
data object NavigateBack : CreateAccountEvent()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Placeholder event for showing a toast. Can be removed once there are real events.
|
* Placeholder event for showing a toast. Can be removed once there are real events.
|
||||||
*/
|
*/
|
||||||
|
@ -103,6 +115,11 @@ sealed class CreateAccountAction {
|
||||||
*/
|
*/
|
||||||
data object SubmitClick : CreateAccountAction()
|
data object SubmitClick : CreateAccountAction()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User clicked close.
|
||||||
|
*/
|
||||||
|
data object CloseClick : CreateAccountAction()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Email input changed.
|
* Email input changed.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -6,6 +6,7 @@ import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.semantics.semantics
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
@ -26,7 +27,7 @@ fun BitwardenFilledButton(
|
||||||
) {
|
) {
|
||||||
Button(
|
Button(
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
modifier = modifier,
|
modifier = modifier.semantics(mergeDescendants = true) {},
|
||||||
enabled = isEnabled,
|
enabled = isEnabled,
|
||||||
contentPadding = PaddingValues(
|
contentPadding = PaddingValues(
|
||||||
vertical = 10.dp,
|
vertical = 10.dp,
|
||||||
|
@ -35,7 +36,11 @@ fun BitwardenFilledButton(
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = label,
|
text = label,
|
||||||
color = MaterialTheme.colorScheme.onPrimary,
|
color = if (isEnabled) {
|
||||||
|
MaterialTheme.colorScheme.onPrimary
|
||||||
|
} else {
|
||||||
|
MaterialTheme.colorScheme.onSurface.copy(alpha = .38f)
|
||||||
|
},
|
||||||
style = MaterialTheme.typography.labelLarge,
|
style = MaterialTheme.typography.labelLarge,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,14 +21,20 @@ fun BitwardenTextButton(
|
||||||
label: String,
|
label: String,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
isEnabled: Boolean = true,
|
||||||
) {
|
) {
|
||||||
TextButton(
|
TextButton(
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
|
enabled = isEnabled,
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = label,
|
text = label,
|
||||||
color = MaterialTheme.colorScheme.primary,
|
color = if (isEnabled) {
|
||||||
|
MaterialTheme.colorScheme.primary
|
||||||
|
} else {
|
||||||
|
MaterialTheme.colorScheme.onSurface.copy(alpha = .38f)
|
||||||
|
},
|
||||||
style = MaterialTheme.typography.labelLarge,
|
style = MaterialTheme.typography.labelLarge,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(
|
.padding(
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
package com.x8bit.bitwarden.ui.platform.components
|
||||||
|
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TopAppBar
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.graphics.painter.Painter
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import com.x8bit.bitwarden.R
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a Bitwarden styled [TopAppBar] that assumes the following components:
|
||||||
|
*
|
||||||
|
* - a single navigation control in the upper-left defined by [navigationIcon],
|
||||||
|
* [navigationIconContentDescription], and [onNavigationIconClick].
|
||||||
|
* - a [title] in the middle.
|
||||||
|
* - a [BitwardenTextButton] on the right that will display [buttonText] and call [onButtonClick]
|
||||||
|
* when clicked and [isButtonEnabled] is true.
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun BitwardenTextButtonTopAppBar(
|
||||||
|
title: String,
|
||||||
|
navigationIcon: Painter,
|
||||||
|
navigationIconContentDescription: String,
|
||||||
|
onNavigationIconClick: () -> Unit,
|
||||||
|
buttonText: String,
|
||||||
|
onButtonClick: () -> Unit,
|
||||||
|
isButtonEnabled: Boolean,
|
||||||
|
) {
|
||||||
|
TopAppBar(
|
||||||
|
navigationIcon = {
|
||||||
|
IconButton(
|
||||||
|
onClick = { onNavigationIconClick() },
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = navigationIcon,
|
||||||
|
contentDescription = navigationIconContentDescription,
|
||||||
|
tint = MaterialTheme.colorScheme.onSurface,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
actions = {
|
||||||
|
BitwardenTextButton(
|
||||||
|
label = buttonText,
|
||||||
|
onClick = onButtonClick,
|
||||||
|
isEnabled = isButtonEnabled,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun BitwardenTextButtonTopAppBar_preview() {
|
||||||
|
BitwardenTextButtonTopAppBar(
|
||||||
|
title = "Title",
|
||||||
|
navigationIcon = painterResource(id = R.drawable.ic_close),
|
||||||
|
navigationIconContentDescription = stringResource(id = R.string.close),
|
||||||
|
onNavigationIconClick = {},
|
||||||
|
buttonText = "Button",
|
||||||
|
onButtonClick = {},
|
||||||
|
isButtonEnabled = true,
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,8 +1,11 @@
|
||||||
package com.x8bit.bitwarden.ui.auth.feature.createaccount
|
package com.x8bit.bitwarden.ui.auth.feature.createaccount
|
||||||
|
|
||||||
|
import androidx.compose.ui.test.onAllNodesWithText
|
||||||
|
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||||
import androidx.compose.ui.test.onNodeWithText
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
import androidx.compose.ui.test.performClick
|
import androidx.compose.ui.test.performClick
|
||||||
import androidx.compose.ui.test.performTextInput
|
import androidx.compose.ui.test.performTextInput
|
||||||
|
import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.CloseClick
|
||||||
import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.ConfirmPasswordInputChange
|
import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.ConfirmPasswordInputChange
|
||||||
import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.EmailInputChange
|
import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.EmailInputChange
|
||||||
import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.PasswordHintChange
|
import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.PasswordHintChange
|
||||||
|
@ -14,24 +17,67 @@ import io.mockk.mockk
|
||||||
import io.mockk.verify
|
import io.mockk.verify
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.emptyFlow
|
import kotlinx.coroutines.flow.emptyFlow
|
||||||
|
import kotlinx.coroutines.flow.flowOf
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class CreateAccountScreenTest : BaseComposeTest() {
|
class CreateAccountScreenTest : BaseComposeTest() {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `submit click should send SubmitClick action`() {
|
fun `app bar submit click should send SubmitClick action`() {
|
||||||
val viewModel = mockk<CreateAccountViewModel>(relaxed = true) {
|
val viewModel = mockk<CreateAccountViewModel>(relaxed = true) {
|
||||||
every { stateFlow } returns MutableStateFlow(DEFAULT_STATE)
|
every { stateFlow } returns MutableStateFlow(DEFAULT_STATE.copy(isSubmitEnabled = true))
|
||||||
every { eventFlow } returns emptyFlow()
|
every { eventFlow } returns emptyFlow()
|
||||||
every { trySendAction(SubmitClick) } returns Unit
|
every { trySendAction(SubmitClick) } returns Unit
|
||||||
}
|
}
|
||||||
composeTestRule.setContent {
|
composeTestRule.setContent {
|
||||||
CreateAccountScreen(viewModel)
|
CreateAccountScreen(onNavigateBack = {}, viewModel = viewModel)
|
||||||
}
|
}
|
||||||
composeTestRule.onNodeWithText("Submit").performClick()
|
composeTestRule.onAllNodesWithText("Submit")[0].performClick()
|
||||||
verify { viewModel.trySendAction(SubmitClick) }
|
verify { viewModel.trySendAction(SubmitClick) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `bottom button submit click should send SubmitClick action`() {
|
||||||
|
val viewModel = mockk<CreateAccountViewModel>(relaxed = true) {
|
||||||
|
every { stateFlow } returns MutableStateFlow(DEFAULT_STATE.copy(isSubmitEnabled = true))
|
||||||
|
every { eventFlow } returns emptyFlow()
|
||||||
|
every { trySendAction(SubmitClick) } returns Unit
|
||||||
|
}
|
||||||
|
composeTestRule.setContent {
|
||||||
|
CreateAccountScreen(onNavigateBack = {}, viewModel = viewModel)
|
||||||
|
}
|
||||||
|
composeTestRule.onAllNodesWithText("Submit")[1].performClick()
|
||||||
|
verify { viewModel.trySendAction(SubmitClick) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `close click should send CloseClick action`() {
|
||||||
|
val viewModel = mockk<CreateAccountViewModel>(relaxed = true) {
|
||||||
|
every { stateFlow } returns MutableStateFlow(DEFAULT_STATE)
|
||||||
|
every { eventFlow } returns emptyFlow()
|
||||||
|
every { trySendAction(CloseClick) } returns Unit
|
||||||
|
}
|
||||||
|
composeTestRule.setContent {
|
||||||
|
CreateAccountScreen(onNavigateBack = {}, viewModel = viewModel)
|
||||||
|
}
|
||||||
|
composeTestRule.onNodeWithContentDescription("Close").performClick()
|
||||||
|
verify { viewModel.trySendAction(CloseClick) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `NavigateBack event should invoke navigate back lambda`() {
|
||||||
|
var onNavigateBackCalled = false
|
||||||
|
val onNavigateBack = { onNavigateBackCalled = true }
|
||||||
|
val viewModel = mockk<CreateAccountViewModel>(relaxed = true) {
|
||||||
|
every { stateFlow } returns MutableStateFlow(DEFAULT_STATE)
|
||||||
|
every { eventFlow } returns flowOf(CreateAccountEvent.NavigateBack)
|
||||||
|
}
|
||||||
|
composeTestRule.setContent {
|
||||||
|
CreateAccountScreen(onNavigateBack = onNavigateBack, viewModel = viewModel)
|
||||||
|
}
|
||||||
|
assert(onNavigateBackCalled)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `email input change should send EmailInputChange action`() {
|
fun `email input change should send EmailInputChange action`() {
|
||||||
val viewModel = mockk<CreateAccountViewModel>(relaxed = true) {
|
val viewModel = mockk<CreateAccountViewModel>(relaxed = true) {
|
||||||
|
@ -40,7 +86,7 @@ class CreateAccountScreenTest : BaseComposeTest() {
|
||||||
every { trySendAction(EmailInputChange("input")) } returns Unit
|
every { trySendAction(EmailInputChange("input")) } returns Unit
|
||||||
}
|
}
|
||||||
composeTestRule.setContent {
|
composeTestRule.setContent {
|
||||||
CreateAccountScreen(viewModel)
|
CreateAccountScreen(onNavigateBack = {}, viewModel = viewModel)
|
||||||
}
|
}
|
||||||
composeTestRule.onNodeWithText("Email address").performTextInput(TEST_INPUT)
|
composeTestRule.onNodeWithText("Email address").performTextInput(TEST_INPUT)
|
||||||
verify { viewModel.trySendAction(EmailInputChange(TEST_INPUT)) }
|
verify { viewModel.trySendAction(EmailInputChange(TEST_INPUT)) }
|
||||||
|
@ -54,7 +100,7 @@ class CreateAccountScreenTest : BaseComposeTest() {
|
||||||
every { trySendAction(PasswordInputChange("input")) } returns Unit
|
every { trySendAction(PasswordInputChange("input")) } returns Unit
|
||||||
}
|
}
|
||||||
composeTestRule.setContent {
|
composeTestRule.setContent {
|
||||||
CreateAccountScreen(viewModel)
|
CreateAccountScreen(onNavigateBack = {}, viewModel = viewModel)
|
||||||
}
|
}
|
||||||
composeTestRule.onNodeWithText("Master password").performTextInput(TEST_INPUT)
|
composeTestRule.onNodeWithText("Master password").performTextInput(TEST_INPUT)
|
||||||
verify { viewModel.trySendAction(PasswordInputChange(TEST_INPUT)) }
|
verify { viewModel.trySendAction(PasswordInputChange(TEST_INPUT)) }
|
||||||
|
@ -68,7 +114,7 @@ class CreateAccountScreenTest : BaseComposeTest() {
|
||||||
every { trySendAction(ConfirmPasswordInputChange("input")) } returns Unit
|
every { trySendAction(ConfirmPasswordInputChange("input")) } returns Unit
|
||||||
}
|
}
|
||||||
composeTestRule.setContent {
|
composeTestRule.setContent {
|
||||||
CreateAccountScreen(viewModel)
|
CreateAccountScreen(onNavigateBack = {}, viewModel = viewModel)
|
||||||
}
|
}
|
||||||
composeTestRule.onNodeWithText("Re-type master password").performTextInput(TEST_INPUT)
|
composeTestRule.onNodeWithText("Re-type master password").performTextInput(TEST_INPUT)
|
||||||
verify { viewModel.trySendAction(ConfirmPasswordInputChange(TEST_INPUT)) }
|
verify { viewModel.trySendAction(ConfirmPasswordInputChange(TEST_INPUT)) }
|
||||||
|
@ -82,7 +128,7 @@ class CreateAccountScreenTest : BaseComposeTest() {
|
||||||
every { trySendAction(PasswordHintChange("input")) } returns Unit
|
every { trySendAction(PasswordHintChange("input")) } returns Unit
|
||||||
}
|
}
|
||||||
composeTestRule.setContent {
|
composeTestRule.setContent {
|
||||||
CreateAccountScreen(viewModel)
|
CreateAccountScreen(onNavigateBack = {}, viewModel = viewModel)
|
||||||
}
|
}
|
||||||
composeTestRule
|
composeTestRule
|
||||||
.onNodeWithText("Master password hint (optional)")
|
.onNodeWithText("Master password hint (optional)")
|
||||||
|
@ -97,6 +143,7 @@ class CreateAccountScreenTest : BaseComposeTest() {
|
||||||
passwordInput = "",
|
passwordInput = "",
|
||||||
confirmPasswordInput = "",
|
confirmPasswordInput = "",
|
||||||
passwordHintInput = "",
|
passwordHintInput = "",
|
||||||
|
isSubmitEnabled = false,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package com.x8bit.bitwarden.ui.auth.feature.createaccount
|
||||||
|
|
||||||
import androidx.lifecycle.SavedStateHandle
|
import androidx.lifecycle.SavedStateHandle
|
||||||
import app.cash.turbine.test
|
import app.cash.turbine.test
|
||||||
|
import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.CloseClick
|
||||||
import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.ConfirmPasswordInputChange
|
import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.ConfirmPasswordInputChange
|
||||||
import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.EmailInputChange
|
import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.EmailInputChange
|
||||||
import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.PasswordHintChange
|
import com.x8bit.bitwarden.ui.auth.feature.createaccount.CreateAccountAction.PasswordHintChange
|
||||||
|
@ -27,6 +28,7 @@ class CreateAccountViewModelTest : BaseViewModelTest() {
|
||||||
passwordInput = "password",
|
passwordInput = "password",
|
||||||
confirmPasswordInput = "confirmPassword",
|
confirmPasswordInput = "confirmPassword",
|
||||||
passwordHintInput = "hint",
|
passwordHintInput = "hint",
|
||||||
|
isSubmitEnabled = false,
|
||||||
)
|
)
|
||||||
val handle = SavedStateHandle(mapOf("state" to savedState))
|
val handle = SavedStateHandle(mapOf("state" to savedState))
|
||||||
val viewModel = CreateAccountViewModel(handle)
|
val viewModel = CreateAccountViewModel(handle)
|
||||||
|
@ -42,6 +44,15 @@ class CreateAccountViewModelTest : BaseViewModelTest() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `CloseClick should emit NavigateBack`() = runTest {
|
||||||
|
val viewModel = CreateAccountViewModel(SavedStateHandle())
|
||||||
|
viewModel.eventFlow.test {
|
||||||
|
viewModel.actionChannel.trySend(CloseClick)
|
||||||
|
assert(awaitItem() is CreateAccountEvent.NavigateBack)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `ConfirmPasswordInputChange update passwordInput`() = runTest {
|
fun `ConfirmPasswordInputChange update passwordInput`() = runTest {
|
||||||
val viewModel = CreateAccountViewModel(SavedStateHandle())
|
val viewModel = CreateAccountViewModel(SavedStateHandle())
|
||||||
|
@ -84,6 +95,7 @@ class CreateAccountViewModelTest : BaseViewModelTest() {
|
||||||
emailInput = "",
|
emailInput = "",
|
||||||
confirmPasswordInput = "",
|
confirmPasswordInput = "",
|
||||||
passwordHintInput = "",
|
passwordHintInput = "",
|
||||||
|
isSubmitEnabled = false,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue