diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/login/LoginScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/login/LoginScreen.kt index 8bfd527b42..aa418b4670 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/login/LoginScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/login/LoginScreen.kt @@ -3,28 +3,34 @@ package com.x8bit.bitwarden.ui.auth.feature.login import android.widget.Toast import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Button +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.testTag +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.x8bit.bitwarden.R import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect import com.x8bit.bitwarden.ui.platform.base.util.IntentHandler -import com.x8bit.bitwarden.ui.platform.components.BitwardenTextField +import com.x8bit.bitwarden.ui.platform.components.BitwardenOverflowTopAppBar +import com.x8bit.bitwarden.ui.platform.components.BitwardenFilledButton +import com.x8bit.bitwarden.ui.platform.components.BitwardenOutlinedButtonWithIcon +import com.x8bit.bitwarden.ui.platform.components.BitwardenPasswordField /** * The top level composable for the Login screen. @@ -46,73 +52,118 @@ fun LoginScreen( // TODO Show proper error Dialog Toast.makeText(context, event.messageRes, Toast.LENGTH_SHORT).show() } + + is LoginEvent.ShowToast -> { + Toast.makeText(context, event.message, Toast.LENGTH_SHORT).show() + } } } + val scrollState = rememberScrollState() Column( horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(12.dp), modifier = Modifier .fillMaxSize() .background(MaterialTheme.colorScheme.surface) - .padding(horizontal = 16.dp, vertical = 32.dp), + .verticalScroll(scrollState), ) { - - BitwardenTextField( - modifier = Modifier - .fillMaxWidth() - .testTag("Master password"), - value = state.passwordInput, - onValueChange = { viewModel.trySendAction(LoginAction.PasswordInputChanged(it)) }, - label = stringResource(id = R.string.master_password), + BitwardenOverflowTopAppBar( + title = stringResource(id = R.string.app_name), + navigationIcon = painterResource(id = R.drawable.ic_close), + navigationIconContentDescription = stringResource(id = R.string.close), + onNavigationIconClick = remember(viewModel) { + { viewModel.trySendAction(LoginAction.CloseButtonClick) } + }, + dropdownMenuItemContent = { + DropdownMenuItem( + text = { + Text(text = stringResource(id = R.string.get_password_hint)) + }, + onClick = remember(viewModel) { + { viewModel.trySendAction(LoginAction.MasterPasswordHintClick) } + }, + ) + }, ) - Button( - onClick = { viewModel.trySendAction(LoginAction.LoginButtonClick) }, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp) - .testTag("Login button"), - enabled = state.isLoginButtonEnabled, + Column( + modifier = Modifier.padding(horizontal = 16.dp), ) { - Text( - text = stringResource(id = R.string.log_in_with_master_password), - color = MaterialTheme.colorScheme.onPrimary, - style = MaterialTheme.typography.bodyMedium, - ) - } - Button( - onClick = { viewModel.trySendAction(LoginAction.SingleSignOnClick) }, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp) - .testTag("Single sign-on button"), - enabled = state.isLoginButtonEnabled, - ) { + BitwardenPasswordField( + modifier = Modifier + .fillMaxWidth(), + value = state.passwordInput, + onValueChange = remember(viewModel) { + { viewModel.trySendAction(LoginAction.PasswordInputChanged(it)) } + }, + label = stringResource(id = R.string.master_password), + ) + + // TODO: Need to figure out better handling for very small clickable text (BIT-724) Text( - text = stringResource(id = R.string.log_in_sso), + text = stringResource(id = R.string.get_password_hint), + style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.primary, + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 8.dp) + .clickable { + viewModel.trySendAction(LoginAction.MasterPasswordHintClick) + } + .padding( + vertical = 4.dp, + horizontal = 16.dp, + ), + ) + + BitwardenFilledButton( + label = stringResource(id = R.string.log_in_with_master_password), + onClick = remember(viewModel) { + { viewModel.trySendAction(LoginAction.LoginButtonClick) } + }, + isEnabled = state.isLoginButtonEnabled, + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 12.dp), + ) + + BitwardenOutlinedButtonWithIcon( + label = stringResource(id = R.string.log_in_sso), + icon = painterResource(id = R.drawable.ic_light_bulb), + onClick = + remember(viewModel) { + { viewModel.trySendAction(LoginAction.SingleSignOnClick) } + }, + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 24.dp), + isEnabled = state.isLoginButtonEnabled, + ) + // TODO Get the "login target" from a dropdown (BIT-202) + Text( + text = stringResource( + id = R.string.log_in_attempt_by_x_on_y, + state.emailAddress, + "bitwarden.com", + ), + textAlign = TextAlign.Start, style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 8.dp), + ) + + // TODO: Need to figure out better handling for very small clickable text (BIT-724) + Text( + modifier = Modifier + .clickable { viewModel.trySendAction(LoginAction.NotYouButtonClick) }, + text = stringResource(id = R.string.not_you), + textAlign = TextAlign.Start, + color = MaterialTheme.colorScheme.primary, + style = MaterialTheme.typography.labelLarge, ) } - // TODO Get the "login target" from a dropdown (BIT-202) - Text( - text = stringResource( - id = R.string.log_in_attempt_by_x_on_y, - state.emailAddress, - "bitwarden.com", - ), - color = MaterialTheme.colorScheme.primary, - style = MaterialTheme.typography.bodySmall, - ) - - Text( - modifier = Modifier - .clickable { viewModel.trySendAction(LoginAction.NotYouButtonClick) }, - text = stringResource(id = R.string.not_you), - color = MaterialTheme.colorScheme.primary, - style = MaterialTheme.typography.bodySmall, - ) } } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/login/LoginViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/login/LoginViewModel.kt index 3227c77ecb..8571fd1dc6 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/login/LoginViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/auth/feature/login/LoginViewModel.kt @@ -55,7 +55,9 @@ class LoginViewModel @Inject constructor( override fun handleAction(action: LoginAction) { when (action) { + is LoginAction.CloseButtonClick -> handleCloseButtonClicked() LoginAction.LoginButtonClick -> handleLoginButtonClicked() + LoginAction.MasterPasswordHintClick -> handleMasterPasswordHintClicked() LoginAction.NotYouButtonClick -> handleNotYouButtonClicked() LoginAction.SingleSignOnClick -> handleSingleSignOnClicked() is LoginAction.PasswordInputChanged -> handlePasswordInputChanged(action) @@ -75,6 +77,10 @@ class LoginViewModel @Inject constructor( } } + private fun handleCloseButtonClicked() { + sendEvent(LoginEvent.NavigateBack) + } + private fun handleLoginButtonClicked() { attemptLogin(captchaToken = null) } @@ -103,12 +109,18 @@ class LoginViewModel @Inject constructor( } } + private fun handleMasterPasswordHintClicked() { + // TODO: Navigate to master password hint screen (BIT-72) + sendEvent(LoginEvent.ShowToast("Not yet implemented.")) + } + private fun handleNotYouButtonClicked() { sendEvent(LoginEvent.NavigateBack) } private fun handleSingleSignOnClicked() { // TODO BIT-204 navigate to single sign on + sendEvent(LoginEvent.ShowToast("Not yet implemented.")) } private fun handlePasswordInputChanged(action: LoginAction.PasswordInputChanged) { @@ -144,12 +156,22 @@ sealed class LoginEvent { * Shows an error pop up with a given message */ data class ShowErrorDialog(@StringRes val messageRes: Int) : LoginEvent() + + /** + * Shows a toast with the given [message]. + */ + data class ShowToast(val message: String) : LoginEvent() } /** * Models actions for the login screen. */ sealed class LoginAction { + /** + * Indicates that the top-bar close button was clicked. + */ + data object CloseButtonClick : LoginAction() + /** * Indicates that the Login button has been clicked. */ @@ -160,6 +182,11 @@ sealed class LoginAction { */ data object NotYouButtonClick : LoginAction() + /** + * Indicates that the overflow option for getting a master password hint has been clicked. + */ + data object MasterPasswordHintClick : LoginAction() + /** * Indicates that the Enterprise single sign-on button has been clicked. */ diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/BitwardenFilledButton.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/BitwardenFilledButton.kt index 6186403741..aa1c62b557 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/BitwardenFilledButton.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/BitwardenFilledButton.kt @@ -1,6 +1,6 @@ package com.x8bit.bitwarden.ui.platform.components -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.material3.Button import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text @@ -28,15 +28,15 @@ fun BitwardenFilledButton( onClick = onClick, modifier = modifier, enabled = isEnabled, + contentPadding = PaddingValues( + vertical = 10.dp, + horizontal = 24.dp, + ), ) { Text( text = label, color = MaterialTheme.colorScheme.onPrimary, - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding( - vertical = 10.dp, - horizontal = 24.dp, - ), + style = MaterialTheme.typography.labelLarge, ) } } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/BitwardenOutlinedButtonWithIcon.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/BitwardenOutlinedButtonWithIcon.kt new file mode 100644 index 0000000000..fe07c2b24f --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/BitwardenOutlinedButtonWithIcon.kt @@ -0,0 +1,81 @@ +package com.x8bit.bitwarden.ui.platform.components + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.x8bit.bitwarden.R + +/** + * Represents a Bitwarden-styled filled [OutlinedButton] with an icon. + * + * @param label The label for the button. + * @param icon The icon for the button. + * @param onClick The callback when the button is clicked. + * @param modifier The [Modifier] to be applied to the button. + * @param isEnabled Whether or not the button is enabled. + */ +@Composable +fun BitwardenOutlinedButtonWithIcon( + label: String, + icon: Painter, + onClick: () -> Unit, + modifier: Modifier = Modifier, + isEnabled: Boolean = true, +) { + OutlinedButton( + onClick = onClick, + modifier = modifier + .semantics(mergeDescendants = true) { }, + enabled = isEnabled, + contentPadding = PaddingValues( + vertical = 10.dp, + horizontal = 24.dp, + ), + ) { + Icon( + painter = icon, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary, + modifier = Modifier + .padding(end = 8.dp), + ) + + Text( + text = label, + color = MaterialTheme.colorScheme.primary, + style = MaterialTheme.typography.labelLarge, + ) + } +} + +@Preview +@Composable +private fun BitwardenOutlinedButtonWithIcon_preview_isEnabled() { + BitwardenOutlinedButtonWithIcon( + label = "Label", + icon = painterResource(id = R.drawable.ic_light_bulb), + onClick = {}, + isEnabled = true, + ) +} + +@Preview +@Composable +private fun BitwardenOutlinedButtonWithIcon_preview_isNotEnabled() { + BitwardenOutlinedButtonWithIcon( + label = "Label", + icon = painterResource(id = R.drawable.ic_light_bulb), + onClick = {}, + isEnabled = false, + ) +} diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/BitwardenOverflowTopAppBar.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/BitwardenOverflowTopAppBar.kt new file mode 100644 index 0000000000..085b4fc0d8 --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/BitwardenOverflowTopAppBar.kt @@ -0,0 +1,96 @@ +package com.x8bit.bitwarden.ui.platform.components + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +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.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +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 +import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme + +/** + * 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 single overflow menu in the right with contents defined by the [dropdownMenuItemContent]. It + * is strongly recommended that this content be a stack of [DropdownMenuItem]. + */ +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun BitwardenOverflowTopAppBar( + title: String, + navigationIcon: Painter, + navigationIconContentDescription: String, + onNavigationIconClick: () -> Unit, + dropdownMenuItemContent: @Composable ColumnScope.() -> Unit, +) { + var isOverflowMenuVisible by remember { mutableStateOf(false) } + 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 = { + Box { + IconButton( + onClick = { isOverflowMenuVisible = !isOverflowMenuVisible }, + ) { + Icon( + painter = painterResource(id = R.drawable.ic_more), + contentDescription = stringResource(id = R.string.more), + tint = MaterialTheme.colorScheme.onSurface, + ) + } + DropdownMenu( + expanded = isOverflowMenuVisible, + onDismissRequest = { isOverflowMenuVisible = false }, + content = dropdownMenuItemContent, + ) + } + }, + ) +} + +@Preview +@Composable +private fun BitwardenOverflowTopAppBar_preview() { + BitwardenTheme { + BitwardenOverflowTopAppBar( + title = "Title", + navigationIcon = painterResource(id = R.drawable.ic_close), + navigationIconContentDescription = stringResource(id = R.string.close), + onNavigationIconClick = {}, + dropdownMenuItemContent = {}, + ) + } +} diff --git a/app/src/main/res/drawable/ic_close.xml b/app/src/main/res/drawable/ic_close.xml new file mode 100644 index 0000000000..c46131041b --- /dev/null +++ b/app/src/main/res/drawable/ic_close.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_light_bulb.xml b/app/src/main/res/drawable/ic_light_bulb.xml new file mode 100644 index 0000000000..62471fd649 --- /dev/null +++ b/app/src/main/res/drawable/ic_light_bulb.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_more.xml b/app/src/main/res/drawable/ic_more.xml new file mode 100644 index 0000000000..2aade59574 --- /dev/null +++ b/app/src/main/res/drawable/ic_more.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/login/LoginScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/login/LoginScreenTest.kt index b8199c514a..c9e7bd2d11 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/login/LoginScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/login/LoginScreenTest.kt @@ -1,8 +1,16 @@ package com.x8bit.bitwarden.ui.auth.feature.login import android.content.Intent +import androidx.compose.ui.test.assertCountEquals +import androidx.compose.ui.test.filter +import androidx.compose.ui.test.filterToOne +import androidx.compose.ui.test.hasAnyAncestor +import androidx.compose.ui.test.isPopup +import androidx.compose.ui.test.onAllNodesWithText +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 androidx.compose.ui.test.performTextInput import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest import com.x8bit.bitwarden.ui.platform.base.util.IntentHandler @@ -17,6 +25,30 @@ import org.junit.Test class LoginScreenTest : BaseComposeTest() { + @Test + fun `close button click should send CloseButtonClick action`() { + val viewModel = mockk(relaxed = true) { + every { eventFlow } returns emptyFlow() + every { stateFlow } returns MutableStateFlow( + LoginState( + emailAddress = "", + isLoginButtonEnabled = false, + passwordInput = "", + ), + ) + } + composeTestRule.setContent { + LoginScreen( + onNavigateBack = {}, + viewModel = viewModel, + ) + } + composeTestRule.onNodeWithContentDescription("Close").performClick() + verify { + viewModel.trySendAction(LoginAction.CloseButtonClick) + } + } + @Test fun `Not you text click should send NotYouButtonClick action`() { val viewModel = mockk(relaxed = true) { @@ -35,12 +67,71 @@ class LoginScreenTest : BaseComposeTest() { viewModel = viewModel, ) } - composeTestRule.onNodeWithText("Not you?").performClick() + composeTestRule.onNodeWithText("Not you?").performScrollTo().performClick() verify { viewModel.trySendAction(LoginAction.NotYouButtonClick) } } + @Test + fun `master password hint text click should send MasterPasswordHintClick action`() { + val viewModel = mockk(relaxed = true) { + every { eventFlow } returns emptyFlow() + every { stateFlow } returns MutableStateFlow( + LoginState( + emailAddress = "", + isLoginButtonEnabled = false, + passwordInput = "", + ), + ) + } + composeTestRule.setContent { + LoginScreen( + onNavigateBack = {}, + viewModel = viewModel, + ) + } + composeTestRule.onNodeWithText("Get your master password hint").performClick() + verify { + viewModel.trySendAction(LoginAction.MasterPasswordHintClick) + } + } + + @Test + fun `master password hint option menu click should send MasterPasswordHintClick action`() { + val viewModel = mockk(relaxed = true) { + every { eventFlow } returns emptyFlow() + every { stateFlow } returns MutableStateFlow( + LoginState( + emailAddress = "", + isLoginButtonEnabled = false, + passwordInput = "", + ), + ) + } + composeTestRule.setContent { + LoginScreen( + onNavigateBack = {}, + viewModel = viewModel, + ) + } + // Confirm dropdown version of item is absent + composeTestRule + .onAllNodesWithText("Get your master password hint") + .filter(hasAnyAncestor(isPopup())) + .assertCountEquals(0) + // Open the overflow menu + composeTestRule.onNodeWithContentDescription("More").performClick() + // Click on the password hint item in the dropdown + composeTestRule + .onAllNodesWithText("Get your master password hint") + .filterToOne(hasAnyAncestor(isPopup())) + .performClick() + verify { + viewModel.trySendAction(LoginAction.MasterPasswordHintClick) + } + } + @Test fun `password input change should send PasswordInputChanged action`() { val input = "input" diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/login/LoginViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/login/LoginViewModelTest.kt index e4abbe767a..982c0c9949 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/login/LoginViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/auth/feature/login/LoginViewModelTest.kt @@ -73,6 +73,23 @@ class LoginViewModelTest : BaseViewModelTest() { } } + @Test + fun `CloseButtonClick should emit NavigateBack`() = runTest { + val viewModel = LoginViewModel( + authRepository = mockk { + every { captchaTokenResultFlow } returns flowOf() + }, + savedStateHandle = savedStateHandle, + ) + viewModel.eventFlow.test { + viewModel.actionChannel.trySend(LoginAction.CloseButtonClick) + assertEquals( + LoginEvent.NavigateBack, + awaitItem(), + ) + } + } + @Test fun `LoginButtonClick login returns error should do nothing`() = runTest { // TODO: handle and display errors (BIT-320) @@ -147,7 +164,25 @@ class LoginViewModelTest : BaseViewModelTest() { } @Test - fun `SingleSignOnClick should do nothing`() = runTest { + fun `MasterPasswordHintClick should emit ShowToast`() = runTest { + val viewModel = LoginViewModel( + authRepository = mockk { + every { captchaTokenResultFlow } returns flowOf() + }, + savedStateHandle = savedStateHandle, + ) + viewModel.eventFlow.test { + viewModel.actionChannel.trySend(LoginAction.MasterPasswordHintClick) + assertEquals(DEFAULT_STATE, viewModel.stateFlow.value) + assertEquals( + LoginEvent.ShowToast("Not yet implemented."), + awaitItem(), + ) + } + } + + @Test + fun `SingleSignOnClick should emit ShowToast`() = runTest { val viewModel = LoginViewModel( authRepository = mockk { every { captchaTokenResultFlow } returns flowOf() @@ -157,6 +192,10 @@ class LoginViewModelTest : BaseViewModelTest() { viewModel.eventFlow.test { viewModel.actionChannel.trySend(LoginAction.SingleSignOnClick) assertEquals(DEFAULT_STATE, viewModel.stateFlow.value) + assertEquals( + LoginEvent.ShowToast("Not yet implemented."), + awaitItem(), + ) } }