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(),
+ )
}
}