mirror of
https://github.com/bitwarden/android.git
synced 2026-03-12 05:04:17 -05:00
BWA-159: Update the ManualCodeEntryScreen to allow scrolling (#5287)
This commit is contained in:
@@ -2,7 +2,6 @@ package com.bitwarden.authenticator.ui.authenticator.feature.manualcodeentry
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.provider.Settings
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -10,8 +9,10 @@ import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.text.ClickableText
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
@@ -25,12 +26,15 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
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.semantics.CustomAccessibilityAction
|
||||
import androidx.compose.ui.semantics.customActions
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.semantics.testTag
|
||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.net.toUri
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.bitwarden.authenticator.R
|
||||
@@ -48,7 +52,9 @@ import com.bitwarden.authenticator.ui.platform.composition.LocalPermissionsManag
|
||||
import com.bitwarden.authenticator.ui.platform.manager.intent.IntentManager
|
||||
import com.bitwarden.authenticator.ui.platform.manager.permissions.PermissionsManager
|
||||
import com.bitwarden.ui.platform.base.util.EventsEffect
|
||||
import com.bitwarden.ui.platform.base.util.toAnnotatedString
|
||||
import com.bitwarden.ui.platform.base.util.annotatedStringResource
|
||||
import com.bitwarden.ui.platform.base.util.spanStyleOf
|
||||
import com.bitwarden.ui.platform.base.util.standardHorizontalMargin
|
||||
import com.bitwarden.ui.platform.resource.BitwardenDrawable
|
||||
|
||||
/**
|
||||
@@ -81,7 +87,7 @@ fun ManualCodeEntryScreen(
|
||||
when (event) {
|
||||
is ManualCodeEntryEvent.NavigateToAppSettings -> {
|
||||
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
|
||||
intent.data = Uri.parse("package:" + context.packageName)
|
||||
intent.data = "package:${context.packageName}".toUri()
|
||||
|
||||
intentManager.startActivity(intent = intent)
|
||||
}
|
||||
@@ -116,32 +122,12 @@ fun ManualCodeEntryScreen(
|
||||
)
|
||||
}
|
||||
|
||||
when (val dialog = state.dialog) {
|
||||
|
||||
is ManualCodeEntryState.DialogState.Error -> {
|
||||
BitwardenBasicDialog(
|
||||
visibilityState = BasicDialogState.Shown(
|
||||
title = dialog.title,
|
||||
message = dialog.message,
|
||||
),
|
||||
onDismissRequest = remember(state) {
|
||||
{ viewModel.trySendAction(ManualCodeEntryAction.DismissDialog) }
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
is ManualCodeEntryState.DialogState.Loading -> {
|
||||
BitwardenLoadingDialog(
|
||||
visibilityState = LoadingDialogState.Shown(
|
||||
dialog.message,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
null -> {
|
||||
Unit
|
||||
}
|
||||
}
|
||||
ManualCodeEntryDialogs(
|
||||
dialog = state.dialog,
|
||||
onDismissRequest = remember(state) {
|
||||
{ viewModel.trySendAction(ManualCodeEntryAction.DismissDialog) }
|
||||
},
|
||||
)
|
||||
|
||||
BitwardenScaffold(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
@@ -157,92 +143,156 @@ fun ManualCodeEntryScreen(
|
||||
)
|
||||
},
|
||||
) { paddingValues ->
|
||||
Column(modifier = Modifier.padding(paddingValues)) {
|
||||
|
||||
Text(
|
||||
text = stringResource(id = R.string.enter_key_manually),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
BitwardenTextField(
|
||||
label = stringResource(id = R.string.name),
|
||||
value = state.issuer,
|
||||
onValueChange = remember(viewModel) {
|
||||
{
|
||||
viewModel.trySendAction(
|
||||
ManualCodeEntryAction.IssuerTextChange(it),
|
||||
)
|
||||
ManualCodeEntryContent(
|
||||
state = state,
|
||||
onNameChange = remember(viewModel) {
|
||||
{ viewModel.trySendAction(ManualCodeEntryAction.IssuerTextChange(it)) }
|
||||
},
|
||||
onKeyChange = remember(viewModel) {
|
||||
{ viewModel.trySendAction(ManualCodeEntryAction.CodeTextChange(it)) }
|
||||
},
|
||||
onSaveLocallyClick = remember(viewModel) {
|
||||
{ viewModel.trySendAction(ManualCodeEntryAction.SaveLocallyClick) }
|
||||
},
|
||||
onSaveToBitwardenClick = remember(viewModel) {
|
||||
{ viewModel.trySendAction(ManualCodeEntryAction.SaveToBitwardenClick) }
|
||||
},
|
||||
onScanQrCodeClick = remember(viewModel) {
|
||||
{
|
||||
if (permissionsManager.checkPermission(Manifest.permission.CAMERA)) {
|
||||
viewModel.trySendAction(ManualCodeEntryAction.ScanQrCodeTextClick)
|
||||
} else {
|
||||
launcher.launch(Manifest.permission.CAMERA)
|
||||
}
|
||||
},
|
||||
modifier = Modifier
|
||||
.semantics { testTag = "NameTextField" }
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
BitwardenPasswordField(
|
||||
singleLine = false,
|
||||
label = stringResource(id = R.string.key),
|
||||
value = state.code,
|
||||
onValueChange = remember(viewModel) {
|
||||
{
|
||||
viewModel.trySendAction(
|
||||
ManualCodeEntryAction.CodeTextChange(it),
|
||||
)
|
||||
}
|
||||
},
|
||||
capitalization = KeyboardCapitalization.Characters,
|
||||
modifier = Modifier
|
||||
.semantics { testTag = "KeyTextField" }
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
SaveManualCodeButtons(
|
||||
state = state.buttonState,
|
||||
onSaveLocallyClick = remember(viewModel) {
|
||||
{
|
||||
viewModel.trySendAction(ManualCodeEntryAction.SaveLocallyClick)
|
||||
}
|
||||
},
|
||||
onSaveToBitwardenClick = remember(viewModel) {
|
||||
{
|
||||
viewModel.trySendAction(ManualCodeEntryAction.SaveToBitwardenClick)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
Text(
|
||||
text = stringResource(id = R.string.cannot_add_authenticator_key),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
vertical = 8.dp,
|
||||
horizontal = 16.dp,
|
||||
),
|
||||
)
|
||||
|
||||
ClickableText(
|
||||
text = stringResource(id = R.string.scan_qr_code).toAnnotatedString(),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
),
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp),
|
||||
onClick = remember(viewModel) {
|
||||
{
|
||||
if (permissionsManager.checkPermission(Manifest.permission.CAMERA)) {
|
||||
viewModel.trySendAction(ManualCodeEntryAction.ScanQrCodeTextClick)
|
||||
} else {
|
||||
launcher.launch(Manifest.permission.CAMERA)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
modifier = Modifier.padding(paddingValues = paddingValues),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ManualCodeEntryContent(
|
||||
state: ManualCodeEntryState,
|
||||
onNameChange: (name: String) -> Unit,
|
||||
onKeyChange: (key: String) -> Unit,
|
||||
onSaveLocallyClick: () -> Unit,
|
||||
onSaveToBitwardenClick: () -> Unit,
|
||||
onScanQrCodeClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(modifier = modifier.verticalScroll(state = rememberScrollState())) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.enter_key_manually),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin(),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(height = 8.dp))
|
||||
BitwardenTextField(
|
||||
label = stringResource(id = R.string.name),
|
||||
value = state.issuer,
|
||||
onValueChange = onNameChange,
|
||||
modifier = Modifier
|
||||
.testTag(tag = "NameTextField")
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin(),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(height = 8.dp))
|
||||
BitwardenPasswordField(
|
||||
singleLine = false,
|
||||
label = stringResource(id = R.string.key),
|
||||
value = state.code,
|
||||
onValueChange = onKeyChange,
|
||||
capitalization = KeyboardCapitalization.Characters,
|
||||
modifier = Modifier
|
||||
.testTag(tag = "KeyTextField")
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin(),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
SaveManualCodeButtons(
|
||||
state = state.buttonState,
|
||||
onSaveLocallyClick = onSaveLocallyClick,
|
||||
onSaveToBitwardenClick = onSaveToBitwardenClick,
|
||||
modifier = Modifier
|
||||
.standardHorizontalMargin()
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(height = 8.dp))
|
||||
Text(
|
||||
text = stringResource(id = R.string.cannot_add_authenticator_key),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin(),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(height = 8.dp))
|
||||
ScanQrCodeText(
|
||||
onClick = onScanQrCodeClick,
|
||||
modifier = Modifier.standardHorizontalMargin(),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(height = 16.dp))
|
||||
Spacer(modifier = Modifier.navigationBarsPadding())
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ScanQrCodeText(
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val accessibilityString = stringResource(id = R.string.scan_qr_code)
|
||||
Text(
|
||||
text = annotatedStringResource(
|
||||
id = R.string.scan_qr_code,
|
||||
emphasisHighlightStyle = spanStyleOf(
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
textStyle = MaterialTheme.typography.bodyMedium,
|
||||
),
|
||||
onAnnotationClick = {
|
||||
when (it) {
|
||||
"scanQrCode" -> onClick()
|
||||
}
|
||||
},
|
||||
),
|
||||
modifier = modifier.semantics {
|
||||
customActions = listOf(
|
||||
CustomAccessibilityAction(
|
||||
label = accessibilityString,
|
||||
action = {
|
||||
onClick()
|
||||
true
|
||||
},
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ManualCodeEntryDialogs(
|
||||
dialog: ManualCodeEntryState.DialogState?,
|
||||
onDismissRequest: () -> Unit,
|
||||
) {
|
||||
when (val dialogString = dialog) {
|
||||
is ManualCodeEntryState.DialogState.Error -> {
|
||||
BitwardenBasicDialog(
|
||||
visibilityState = BasicDialogState.Shown(
|
||||
title = dialogString.title,
|
||||
message = dialogString.message,
|
||||
),
|
||||
onDismissRequest = onDismissRequest,
|
||||
)
|
||||
}
|
||||
|
||||
is ManualCodeEntryState.DialogState.Loading -> {
|
||||
BitwardenLoadingDialog(visibilityState = LoadingDialogState.Shown(dialog.message))
|
||||
}
|
||||
|
||||
null -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,10 @@ package com.bitwarden.authenticator.ui.authenticator.feature.manualcodeentry
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.semantics.testTag
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.bitwarden.authenticator.R
|
||||
import com.bitwarden.authenticator.ui.platform.components.button.BitwardenFilledButton
|
||||
import com.bitwarden.authenticator.ui.platform.components.button.BitwardenFilledTonalButton
|
||||
@@ -20,60 +17,50 @@ import com.bitwarden.authenticator.ui.platform.components.button.BitwardenOutlin
|
||||
* @param state State of the buttons to show.
|
||||
* @param onSaveLocallyClick Callback invoked when the user clicks save locally.
|
||||
* @param onSaveToBitwardenClick Callback invoked when the user clicks save to Bitwarden.
|
||||
* @param modifier The modifier to be applied to the composable.
|
||||
*/
|
||||
@Composable
|
||||
fun SaveManualCodeButtons(
|
||||
state: ManualCodeEntryState.ButtonState,
|
||||
onSaveLocallyClick: () -> Unit,
|
||||
onSaveToBitwardenClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
|
||||
when (state) {
|
||||
ManualCodeEntryState.ButtonState.LocalOnly -> {
|
||||
BitwardenFilledTonalButton(
|
||||
label = stringResource(id = R.string.add_code),
|
||||
onClick = onSaveLocallyClick,
|
||||
modifier = Modifier
|
||||
.semantics { testTag = "AddCodeButton" }
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
modifier = modifier.testTag(tag = "AddCodeButton"),
|
||||
)
|
||||
}
|
||||
|
||||
ManualCodeEntryState.ButtonState.SaveLocallyPrimary -> {
|
||||
Column {
|
||||
Column(modifier = modifier) {
|
||||
BitwardenFilledButton(
|
||||
label = stringResource(id = R.string.save_here),
|
||||
onClick = onSaveLocallyClick,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
BitwardenOutlinedButton(
|
||||
label = stringResource(R.string.save_to_bitwarden),
|
||||
onClick = onSaveToBitwardenClick,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
ManualCodeEntryState.ButtonState.SaveToBitwardenPrimary -> {
|
||||
Column {
|
||||
Column(modifier = modifier) {
|
||||
BitwardenFilledButton(
|
||||
label = stringResource(id = R.string.save_to_bitwarden),
|
||||
onClick = onSaveToBitwardenClick,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
BitwardenOutlinedButton(
|
||||
label = stringResource(R.string.save_here),
|
||||
onClick = onSaveLocallyClick,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<string name="add_item_rotation">Add Item Rotation</string>
|
||||
<string name="scan_a_qr_code">Scan a QR code</string>
|
||||
<string name="enter_a_setup_key">Enter a setup key</string>
|
||||
<string name="scan_qr_code">Scan QR code</string>
|
||||
<string name="scan_qr_code"><annotation link="scanQrCode">Scan QR code</annotation></string>
|
||||
<string name="point_your_camera_at_the_qr_code">Point your camera at the QR code.</string>
|
||||
<string name="cannot_scan_qr_code">Cannot scan QR code.</string>
|
||||
<string name="enter_key_manually">Enter key manually</string>
|
||||
|
||||
@@ -1,12 +1,20 @@
|
||||
package com.bitwarden.authenticator.ui.authenticator.feature.manualcodeentry
|
||||
|
||||
import androidx.compose.ui.test.assert
|
||||
import androidx.compose.ui.test.assertIsDisplayed
|
||||
import androidx.compose.ui.test.hasAnyAncestor
|
||||
import androidx.compose.ui.test.isDialog
|
||||
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.bitwarden.authenticator.ui.platform.base.AuthenticatorComposeTest
|
||||
import com.bitwarden.authenticator.ui.platform.manager.intent.IntentManager
|
||||
import com.bitwarden.authenticator.ui.platform.manager.permissions.FakePermissionManager
|
||||
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
|
||||
import com.bitwarden.ui.util.asText
|
||||
import com.bitwarden.ui.util.assertNoDialogExists
|
||||
import com.bitwarden.ui.util.performCustomAccessibilityAction
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
@@ -14,11 +22,15 @@ import io.mockk.runs
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
class ManualCodeEntryScreenTest : AuthenticatorComposeTest() {
|
||||
|
||||
private var onNavigateBackCalled = false
|
||||
private var onNavigateToQrCodeScreenCalled = false
|
||||
|
||||
private val mutableStateFlow = MutableStateFlow(DEFAULT_STATE)
|
||||
private val mutableEventFlow = bufferedMutableSharedFlow<ManualCodeEntryEvent>()
|
||||
|
||||
@@ -28,7 +40,9 @@ class ManualCodeEntryScreenTest : AuthenticatorComposeTest() {
|
||||
every { trySendAction(any()) } just runs
|
||||
}
|
||||
|
||||
private val intentManager: IntentManager = mockk()
|
||||
private val intentManager: IntentManager = mockk {
|
||||
every { startActivity(intent = any()) } just runs
|
||||
}
|
||||
private val permissionsManager = FakePermissionManager()
|
||||
|
||||
@Before
|
||||
@@ -38,34 +52,70 @@ class ManualCodeEntryScreenTest : AuthenticatorComposeTest() {
|
||||
permissionsManager = permissionsManager,
|
||||
) {
|
||||
ManualCodeEntryScreen(
|
||||
onNavigateBack = {},
|
||||
onNavigateToQrCodeScreen = {},
|
||||
onNavigateBack = { onNavigateBackCalled = true },
|
||||
onNavigateToQrCodeScreen = { onNavigateToQrCodeScreenCalled = true },
|
||||
viewModel = viewModel,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on NavigateBack should call onNavigateBack`() {
|
||||
mutableEventFlow.tryEmit(ManualCodeEntryEvent.NavigateBack)
|
||||
assertTrue(onNavigateBackCalled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on NavigateToQrCodeScreen should call onNavigateToQrCodeScreen`() {
|
||||
mutableEventFlow.tryEmit(ManualCodeEntryEvent.NavigateToQrCodeScreen)
|
||||
assertTrue(onNavigateToQrCodeScreenCalled)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on NavigateToAppSettings should call intentManager`() {
|
||||
mutableEventFlow.tryEmit(ManualCodeEntryEvent.NavigateToAppSettings)
|
||||
verify(exactly = 1) {
|
||||
intentManager.startActivity(intent = any())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on Close click should emit `() {
|
||||
composeTestRule
|
||||
.onNodeWithContentDescription(label = "Close")
|
||||
.performClick()
|
||||
|
||||
verify(exactly = 1) {
|
||||
viewModel.trySendAction(ManualCodeEntryAction.CloseClick)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on Add code click should emit SaveLocallyClick`() {
|
||||
composeTestRule
|
||||
.onNodeWithText("Add code")
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
|
||||
// Make sure save to bitwaren isn't showing:
|
||||
// Make sure save to bitwarden isn't showing:
|
||||
composeTestRule
|
||||
.onNodeWithText("Add code to Bitwarden")
|
||||
.assertDoesNotExist()
|
||||
composeTestRule
|
||||
.onNodeWithText(text = "Save here")
|
||||
.assertDoesNotExist()
|
||||
|
||||
verify { viewModel.trySendAction(ManualCodeEntryAction.SaveLocallyClick) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on Add code to Bitwarden click should emit SaveToBitwardenClick`() {
|
||||
fun `on Save here click should emit SaveToBitwardenClick`() {
|
||||
mutableStateFlow.update {
|
||||
it.copy(buttonState = ManualCodeEntryState.ButtonState.SaveToBitwardenPrimary)
|
||||
}
|
||||
composeTestRule
|
||||
.onNodeWithText("Save to Bitwarden")
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
|
||||
// Make sure locally only save isn't showing:
|
||||
@@ -82,7 +132,7 @@ class ManualCodeEntryScreenTest : AuthenticatorComposeTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on Add code locally click should emit SaveLocallyClick`() {
|
||||
fun `on Save here click should emit SaveLocallyClick`() {
|
||||
mutableStateFlow.update {
|
||||
it.copy(buttonState = ManualCodeEntryState.ButtonState.SaveLocallyPrimary)
|
||||
}
|
||||
@@ -102,6 +152,120 @@ class ManualCodeEntryScreenTest : AuthenticatorComposeTest() {
|
||||
|
||||
verify { viewModel.trySendAction(ManualCodeEntryAction.SaveLocallyClick) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on Scan QR code click with permission should emit ScanQrCodeTextClick`() {
|
||||
permissionsManager.checkPermissionResult = true
|
||||
composeTestRule
|
||||
.onNodeWithText(text = "Scan QR code")
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
|
||||
verify(exactly = 1) {
|
||||
viewModel.trySendAction(ManualCodeEntryAction.ScanQrCodeTextClick)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `on Scan QR code click without permission and permission is not granted should display dialog`() {
|
||||
permissionsManager.checkPermissionResult = false
|
||||
permissionsManager.getPermissionsResult = false
|
||||
composeTestRule
|
||||
.onNodeWithText(text = "Scan QR code")
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText(text = "Enable camera permission to use the scanner")
|
||||
.assert(hasAnyAncestor(isDialog()))
|
||||
.assertIsDisplayed()
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText(text = "No thanks")
|
||||
.assert(hasAnyAncestor(isDialog()))
|
||||
.performClick()
|
||||
|
||||
composeTestRule.assertNoDialogExists()
|
||||
|
||||
verify(exactly = 0) {
|
||||
viewModel.trySendAction(any())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on permission dialog Settings clock should emit SettingsClick`() {
|
||||
permissionsManager.checkPermissionResult = false
|
||||
permissionsManager.getPermissionsResult = false
|
||||
composeTestRule
|
||||
.onNodeWithText(text = "Scan QR code")
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText(text = "Settings")
|
||||
.assert(hasAnyAncestor(isDialog()))
|
||||
.performClick()
|
||||
|
||||
verify(exactly = 1) {
|
||||
viewModel.trySendAction(ManualCodeEntryAction.SettingsClick)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `on Scan QR code click without permission and permission is granted should emit ScanQrCodeTextClick`() {
|
||||
permissionsManager.checkPermissionResult = false
|
||||
permissionsManager.getPermissionsResult = true
|
||||
composeTestRule
|
||||
.onNodeWithText(text = "Scan QR code")
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
|
||||
verify(exactly = 1) {
|
||||
viewModel.trySendAction(ManualCodeEntryAction.ScanQrCodeTextClick)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `on Scan QR code accessibility action without permission and permission is granted should emit ScanQrCodeTextClick`() {
|
||||
permissionsManager.checkPermissionResult = false
|
||||
permissionsManager.getPermissionsResult = true
|
||||
composeTestRule
|
||||
.onNodeWithText(text = "Scan QR code")
|
||||
.performScrollTo()
|
||||
.performCustomAccessibilityAction(label = "Scan QR code")
|
||||
|
||||
verify(exactly = 1) {
|
||||
viewModel.trySendAction(ManualCodeEntryAction.ScanQrCodeTextClick)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on dialog should updates according to state`() {
|
||||
composeTestRule.assertNoDialogExists()
|
||||
val loadingMessage = "Loading!"
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
dialog = ManualCodeEntryState.DialogState.Loading(
|
||||
message = loadingMessage.asText(),
|
||||
),
|
||||
)
|
||||
}
|
||||
composeTestRule.onNodeWithText(text = loadingMessage).assert(hasAnyAncestor(isDialog()))
|
||||
|
||||
val errorMessage = "Error!"
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
dialog = ManualCodeEntryState.DialogState.Error(message = errorMessage.asText()),
|
||||
)
|
||||
}
|
||||
composeTestRule.onNodeWithText(text = errorMessage).assert(hasAnyAncestor(isDialog()))
|
||||
|
||||
mutableStateFlow.update { it.copy(dialog = null) }
|
||||
composeTestRule.assertNoDialogExists()
|
||||
}
|
||||
}
|
||||
|
||||
private val DEFAULT_STATE = ManualCodeEntryState(
|
||||
|
||||
Reference in New Issue
Block a user