mirror of
https://github.com/bitwarden/android.git
synced 2025-03-15 18:58:59 +03:00
BIT-1654 Add URI option menu (#877)
This commit is contained in:
parent
a92d9ff823
commit
95b4aaf605
9 changed files with 493 additions and 67 deletions
|
@ -156,22 +156,10 @@ fun LazyListScope.vaultAddEditLoginItems(
|
|||
|
||||
items(loginState.uriList) { uriItem ->
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
BitwardenTextFieldWithActions(
|
||||
label = stringResource(id = R.string.uri),
|
||||
value = uriItem.uri.orEmpty(),
|
||||
onValueChange = {
|
||||
loginItemTypeHandlers.onUriTextChange(uriItem.copy(uri = it))
|
||||
},
|
||||
actions = {
|
||||
BitwardenIconButtonWithResource(
|
||||
iconRes = IconResource(
|
||||
iconPainter = painterResource(id = R.drawable.ic_settings),
|
||||
contentDescription = stringResource(id = R.string.options),
|
||||
),
|
||||
onClick = loginItemTypeHandlers.onUriSettingsClick,
|
||||
)
|
||||
},
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
VaultAddEditUriItem(
|
||||
uriItem = uriItem,
|
||||
onUriValueChange = loginItemTypeHandlers.onUriValueChange,
|
||||
onUriItemRemoved = loginItemTypeHandlers.onRemoveUriClick,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
package com.x8bit.bitwarden.ui.vault.feature.addedit
|
||||
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenBasicDialogRow
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenIconButtonWithResource
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenSelectionDialog
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenSelectionRow
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenTextFieldWithActions
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.IconResource
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.model.UriItem
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.model.UriMatchDisplayType
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.util.toDisplayMatchType
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.util.toUriMatchType
|
||||
|
||||
/**
|
||||
* The URI item displayed to the user.
|
||||
*/
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
fun VaultAddEditUriItem(
|
||||
uriItem: UriItem,
|
||||
onUriItemRemoved: (UriItem) -> Unit,
|
||||
onUriValueChange: (UriItem) -> Unit,
|
||||
) {
|
||||
var shouldShowOptionsDialog by rememberSaveable { mutableStateOf(false) }
|
||||
var shouldShowMatchDialog by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
BitwardenTextFieldWithActions(
|
||||
label = stringResource(id = R.string.uri),
|
||||
value = uriItem.uri.orEmpty(),
|
||||
onValueChange = { onUriValueChange(uriItem.copy(uri = it)) },
|
||||
actions = {
|
||||
BitwardenIconButtonWithResource(
|
||||
iconRes = IconResource(
|
||||
iconPainter = painterResource(id = R.drawable.ic_settings),
|
||||
contentDescription = stringResource(id = R.string.options),
|
||||
),
|
||||
onClick = { shouldShowOptionsDialog = true },
|
||||
)
|
||||
},
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
)
|
||||
|
||||
if (shouldShowOptionsDialog) {
|
||||
BitwardenSelectionDialog(
|
||||
title = stringResource(id = R.string.options),
|
||||
onDismissRequest = { shouldShowOptionsDialog = false },
|
||||
) {
|
||||
BitwardenBasicDialogRow(
|
||||
text = stringResource(id = R.string.match_detection),
|
||||
onClick = {
|
||||
shouldShowOptionsDialog = false
|
||||
shouldShowMatchDialog = true
|
||||
},
|
||||
)
|
||||
BitwardenBasicDialogRow(
|
||||
text = stringResource(id = R.string.remove),
|
||||
onClick = {
|
||||
shouldShowOptionsDialog = false
|
||||
onUriItemRemoved(uriItem)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldShowMatchDialog) {
|
||||
val selectedString = uriItem.match.toDisplayMatchType().text.invoke()
|
||||
|
||||
BitwardenSelectionDialog(
|
||||
title = stringResource(id = R.string.uri_match_detection),
|
||||
onDismissRequest = { shouldShowMatchDialog = false },
|
||||
) {
|
||||
UriMatchDisplayType
|
||||
.entries
|
||||
.forEach { matchType ->
|
||||
BitwardenSelectionRow(
|
||||
text = matchType.text,
|
||||
isSelected = matchType.text.invoke() == selectedString,
|
||||
onClick = {
|
||||
shouldShowMatchDialog = false
|
||||
onUriValueChange(
|
||||
uriItem.copy(match = matchType.toUriMatchType()),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -190,6 +190,7 @@ class VaultAddEditViewModel @Inject constructor(
|
|||
is VaultAddEditAction.Common.CustomFieldActionSelect -> handleCustomFieldActionSelected(
|
||||
action,
|
||||
)
|
||||
|
||||
is VaultAddEditAction.Common.CollectionSelect -> handleCollectionSelect(action)
|
||||
}
|
||||
}
|
||||
|
@ -542,8 +543,8 @@ class VaultAddEditViewModel @Inject constructor(
|
|||
handleLoginPasswordTextInputChange(action)
|
||||
}
|
||||
|
||||
is VaultAddEditAction.ItemType.LoginType.UriTextChange -> {
|
||||
handleLoginUriTextInputChange(action)
|
||||
is VaultAddEditAction.ItemType.LoginType.UriValueChange -> {
|
||||
handleLoginUriValueInputChange(action)
|
||||
}
|
||||
|
||||
is VaultAddEditAction.ItemType.LoginType.OpenUsernameGeneratorClick -> {
|
||||
|
@ -562,8 +563,8 @@ class VaultAddEditViewModel @Inject constructor(
|
|||
handleLoginSetupTotpClick(action)
|
||||
}
|
||||
|
||||
is VaultAddEditAction.ItemType.LoginType.UriSettingsClick -> {
|
||||
handleLoginUriSettingsClick()
|
||||
is VaultAddEditAction.ItemType.LoginType.RemoveUriClick -> {
|
||||
handleLoginRemoveUriClick(action)
|
||||
}
|
||||
|
||||
is VaultAddEditAction.ItemType.LoginType.AddNewUriClick -> {
|
||||
|
@ -596,16 +597,16 @@ class VaultAddEditViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleLoginUriTextInputChange(
|
||||
action: VaultAddEditAction.ItemType.LoginType.UriTextChange,
|
||||
private fun handleLoginUriValueInputChange(
|
||||
action: VaultAddEditAction.ItemType.LoginType.UriValueChange,
|
||||
) {
|
||||
updateLoginContent { loginType ->
|
||||
loginType.copy(
|
||||
uriList = loginType
|
||||
.uriList
|
||||
.map { uriItem ->
|
||||
if (uriItem.id == action.uri.id) {
|
||||
action.uri
|
||||
if (uriItem.id == action.uriItem.id) {
|
||||
action.uriItem
|
||||
} else {
|
||||
uriItem
|
||||
}
|
||||
|
@ -614,6 +615,18 @@ class VaultAddEditViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleLoginRemoveUriClick(
|
||||
action: VaultAddEditAction.ItemType.LoginType.RemoveUriClick,
|
||||
) {
|
||||
updateLoginContent { loginType ->
|
||||
loginType.copy(
|
||||
uriList = loginType.uriList.filter {
|
||||
it != action.uriItem
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleLoginOpenUsernameGeneratorClick() {
|
||||
sendEvent(event = VaultAddEditEvent.NavigateToGeneratorModal(GeneratorMode.Modal.Username))
|
||||
}
|
||||
|
@ -1899,11 +1912,11 @@ sealed class VaultAddEditAction {
|
|||
data class PasswordTextChange(val password: String) : LoginType()
|
||||
|
||||
/**
|
||||
* Fired when the URI text input is changed.
|
||||
* Fired when the URI is changed.
|
||||
*
|
||||
* @property uri The new URI text.
|
||||
* @property uriItem The new URI.
|
||||
*/
|
||||
data class UriTextChange(val uri: UriItem) : LoginType()
|
||||
data class UriValueChange(val uriItem: UriItem) : LoginType()
|
||||
|
||||
/**
|
||||
* Represents the action to set up TOTP.
|
||||
|
@ -1940,9 +1953,9 @@ sealed class VaultAddEditAction {
|
|||
data object OpenPasswordGeneratorClick : LoginType()
|
||||
|
||||
/**
|
||||
* Represents the action of clicking TOTP settings
|
||||
* Represents the action of removing a URI item.
|
||||
*/
|
||||
data object UriSettingsClick : LoginType()
|
||||
data class RemoveUriClick(val uriItem: UriItem) : LoginType()
|
||||
|
||||
/**
|
||||
* Represents the action to add a new URI field.
|
||||
|
@ -2164,8 +2177,8 @@ sealed class VaultAddEditAction {
|
|||
* Indicates that the vault item data has been received.
|
||||
*/
|
||||
data class VaultDataReceive(
|
||||
val vaultData: DataState<VaultData>,
|
||||
val userData: UserState?,
|
||||
val vaultData: DataState<VaultData>,
|
||||
val userData: UserState?,
|
||||
) : Internal()
|
||||
|
||||
/**
|
||||
|
|
|
@ -10,8 +10,8 @@ import com.x8bit.bitwarden.ui.vault.feature.addedit.model.UriItem
|
|||
*
|
||||
* @property onUsernameTextChange Handles the action when the username text is changed.
|
||||
* @property onPasswordTextChange Handles the action when the password text is changed.
|
||||
* @property onUriTextChange Handles the action when the URI text is changed.
|
||||
* reprompt toggle is changed.
|
||||
* @property onRemoveUriClick Handles the action when the URI is removed.
|
||||
* @property onUriValueChange Handles the action when the URI value is changed.
|
||||
* @property onOpenUsernameGeneratorClick Handles the action when the username generator
|
||||
* button is clicked.
|
||||
* @property onPasswordCheckerClick Handles the action when the password checker
|
||||
|
@ -28,14 +28,14 @@ import com.x8bit.bitwarden.ui.vault.feature.addedit.model.UriItem
|
|||
data class VaultAddEditLoginTypeHandlers(
|
||||
val onUsernameTextChange: (String) -> Unit,
|
||||
val onPasswordTextChange: (String) -> Unit,
|
||||
val onUriTextChange: (UriItem) -> Unit,
|
||||
val onRemoveUriClick: (UriItem) -> Unit,
|
||||
val onUriValueChange: (UriItem) -> Unit,
|
||||
val onOpenUsernameGeneratorClick: () -> Unit,
|
||||
val onPasswordCheckerClick: () -> Unit,
|
||||
val onOpenPasswordGeneratorClick: () -> Unit,
|
||||
val onSetupTotpClick: (Boolean) -> Unit,
|
||||
val onCopyTotpKeyClick: (String) -> Unit,
|
||||
val onClearTotpKeyClick: () -> Unit,
|
||||
val onUriSettingsClick: () -> Unit,
|
||||
val onAddNewUriClick: () -> Unit,
|
||||
) {
|
||||
companion object {
|
||||
|
@ -60,9 +60,9 @@ data class VaultAddEditLoginTypeHandlers(
|
|||
VaultAddEditAction.ItemType.LoginType.PasswordTextChange(newPassword),
|
||||
)
|
||||
},
|
||||
onUriTextChange = { newUri ->
|
||||
onUriValueChange = { newUri ->
|
||||
viewModel.trySendAction(
|
||||
VaultAddEditAction.ItemType.LoginType.UriTextChange(newUri),
|
||||
VaultAddEditAction.ItemType.LoginType.UriValueChange(newUri),
|
||||
)
|
||||
},
|
||||
onOpenUsernameGeneratorClick = {
|
||||
|
@ -85,8 +85,12 @@ data class VaultAddEditLoginTypeHandlers(
|
|||
VaultAddEditAction.ItemType.LoginType.SetupTotpClick(isGranted),
|
||||
)
|
||||
},
|
||||
onUriSettingsClick = {
|
||||
viewModel.trySendAction(VaultAddEditAction.ItemType.LoginType.UriSettingsClick)
|
||||
onRemoveUriClick = { uriItem ->
|
||||
viewModel.trySendAction(
|
||||
VaultAddEditAction.ItemType.LoginType.RemoveUriClick(
|
||||
uriItem,
|
||||
),
|
||||
)
|
||||
},
|
||||
onAddNewUriClick = {
|
||||
viewModel.trySendAction(VaultAddEditAction.ItemType.LoginType.AddNewUriClick)
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
package com.x8bit.bitwarden.ui.vault.feature.addedit.model
|
||||
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.Text
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
|
||||
/**
|
||||
* The options displayed to the user when choosing a match type
|
||||
* for their URI.
|
||||
*/
|
||||
@Suppress("MagicNumber")
|
||||
enum class UriMatchDisplayType(
|
||||
val text: Text,
|
||||
) {
|
||||
/**
|
||||
* the default option for when the user has not chosen one.
|
||||
*/
|
||||
DEFAULT(R.string.default_text.asText()),
|
||||
|
||||
/**
|
||||
* The URIs match if their top-level and second-level domains match.
|
||||
*/
|
||||
BASE_DOMAIN(R.string.base_domain.asText()),
|
||||
|
||||
/**
|
||||
* The URIs match if their hostnames (and ports if specified) match.
|
||||
*/
|
||||
HOST(R.string.host.asText()),
|
||||
|
||||
/**
|
||||
* The URIs match if the "test" URI starts with the known URI.
|
||||
*/
|
||||
STARTS_WITH(R.string.starts_with.asText()),
|
||||
|
||||
/**
|
||||
* The URIs match if the "test" URI matches the known URI according to a specified regular
|
||||
* expression for the item.
|
||||
*/
|
||||
REGULAR_EXPRESSION(R.string.reg_ex.asText()),
|
||||
|
||||
/**
|
||||
* The URIs match if they are exactly the same.
|
||||
*/
|
||||
EXACT(R.string.exact.asText()),
|
||||
|
||||
/**
|
||||
* The URIs should never match.
|
||||
*/
|
||||
NEVER(R.string.never.asText()),
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package com.x8bit.bitwarden.ui.vault.feature.addedit.util
|
||||
|
||||
import com.bitwarden.core.UriMatchType
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.model.UriMatchDisplayType
|
||||
|
||||
/**
|
||||
* Method to convert the SDK match type for display to the user.
|
||||
*/
|
||||
fun UriMatchType?.toDisplayMatchType(): UriMatchDisplayType =
|
||||
when (this) {
|
||||
UriMatchType.DOMAIN -> UriMatchDisplayType.BASE_DOMAIN
|
||||
UriMatchType.EXACT -> UriMatchDisplayType.EXACT
|
||||
UriMatchType.HOST -> UriMatchDisplayType.HOST
|
||||
UriMatchType.NEVER -> UriMatchDisplayType.NEVER
|
||||
UriMatchType.REGULAR_EXPRESSION -> UriMatchDisplayType.REGULAR_EXPRESSION
|
||||
UriMatchType.STARTS_WITH -> UriMatchDisplayType.STARTS_WITH
|
||||
null -> UriMatchDisplayType.DEFAULT
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to convert the match display type over to the SDK match type.
|
||||
*/
|
||||
fun UriMatchDisplayType.toUriMatchType(): UriMatchType? =
|
||||
when (this) {
|
||||
UriMatchDisplayType.DEFAULT -> null
|
||||
UriMatchDisplayType.BASE_DOMAIN -> UriMatchType.DOMAIN
|
||||
UriMatchDisplayType.HOST -> UriMatchType.HOST
|
||||
UriMatchDisplayType.STARTS_WITH -> UriMatchType.STARTS_WITH
|
||||
UriMatchDisplayType.REGULAR_EXPRESSION -> UriMatchType.REGULAR_EXPRESSION
|
||||
UriMatchDisplayType.EXACT -> UriMatchType.EXACT
|
||||
UriMatchDisplayType.NEVER -> UriMatchType.NEVER
|
||||
}
|
|
@ -29,6 +29,7 @@ import androidx.compose.ui.test.performScrollTo
|
|||
import androidx.compose.ui.test.performTextClearance
|
||||
import androidx.compose.ui.test.performTextInput
|
||||
import androidx.compose.ui.test.performTouchInput
|
||||
import com.bitwarden.core.UriMatchType
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCipherView
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
|
||||
|
@ -720,7 +721,7 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `in ItemType_Login state changing URI text field should trigger UriTextChange`() {
|
||||
fun `in ItemType_Login state changing URI text field should trigger UriValueChange`() {
|
||||
mutableStateFlow.update { currentState ->
|
||||
updateLoginType(currentState) {
|
||||
copy(uriList = listOf(UriItem("TestId", "URI", null)))
|
||||
|
@ -733,7 +734,7 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
|||
|
||||
verify {
|
||||
viewModel.trySendAction(
|
||||
VaultAddEditAction.ItemType.LoginType.UriTextChange(
|
||||
VaultAddEditAction.ItemType.LoginType.UriValueChange(
|
||||
UriItem("TestId", "TestURI", null),
|
||||
),
|
||||
)
|
||||
|
@ -759,20 +760,184 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
|||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `in ItemType_Login state clicking the URI settings action should trigger UriSettingsClick`() {
|
||||
fun `in ItemType_Login Uri settings dialog should be dismissed on cancel click`() {
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll(text = "URI")
|
||||
.onSiblings()
|
||||
.filterToOne(hasContentDescription(value = "Options"))
|
||||
.performClick()
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Cancel")
|
||||
.assert(hasAnyAncestor(isDialog()))
|
||||
.performClick()
|
||||
|
||||
composeTestRule.assertNoDialogExists()
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `in ItemType_Login Uri settings dialog should send RemoveUriClick action if remove is clicked`() {
|
||||
mutableStateFlow.update { currentState ->
|
||||
updateLoginType(currentState) {
|
||||
copy(uriList = listOf(UriItem("TestId", null, null)))
|
||||
}
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll(text = "URI")
|
||||
.onSiblings()
|
||||
.filterToOne(hasContentDescription(value = "Options"))
|
||||
.performClick()
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Remove")
|
||||
.assert(hasAnyAncestor(isDialog()))
|
||||
.performClick()
|
||||
|
||||
composeTestRule.assertNoDialogExists()
|
||||
|
||||
verify {
|
||||
viewModel.trySendAction(
|
||||
VaultAddEditAction.ItemType.LoginType.UriSettingsClick,
|
||||
VaultAddEditAction.ItemType.LoginType.RemoveUriClick(
|
||||
UriItem(
|
||||
"TestId",
|
||||
null,
|
||||
null,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `in ItemType_Login Uri settings dialog with open match detection click should open list of options`() {
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll(text = "URI")
|
||||
.onSiblings()
|
||||
.filterToOne(hasContentDescription(value = "Options"))
|
||||
.performClick()
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Match detection")
|
||||
.assert(hasAnyAncestor(isDialog()))
|
||||
.performClick()
|
||||
|
||||
composeTestRule
|
||||
.onAllNodesWithText("URI match detection")
|
||||
.filterToOne(hasAnyAncestor(isDialog()))
|
||||
.assertIsDisplayed()
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Default")
|
||||
.assert(hasAnyAncestor(isDialog()))
|
||||
.assertIsDisplayed()
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Base domain")
|
||||
.assert(hasAnyAncestor(isDialog()))
|
||||
.assertIsDisplayed()
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Host")
|
||||
.assert(hasAnyAncestor(isDialog()))
|
||||
.assertIsDisplayed()
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Starts with")
|
||||
.assert(hasAnyAncestor(isDialog()))
|
||||
.assertIsDisplayed()
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Regular expression")
|
||||
.assert(hasAnyAncestor(isDialog()))
|
||||
.assertIsDisplayed()
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Exact")
|
||||
.assert(hasAnyAncestor(isDialog()))
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `in ItemType_Login on URI settings click and on match detection click and option click should emit UriValueChange action`() {
|
||||
mutableStateFlow.update { currentState ->
|
||||
updateLoginType(currentState) {
|
||||
copy(uriList = listOf(UriItem("TestId", null, null)))
|
||||
}
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll(text = "URI")
|
||||
.onSiblings()
|
||||
.filterToOne(hasContentDescription(value = "Options"))
|
||||
.performClick()
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Match detection")
|
||||
.assert(hasAnyAncestor(isDialog()))
|
||||
.performClick()
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("URI match detection")
|
||||
.assert(hasAnyAncestor(isDialog()))
|
||||
.assertIsDisplayed()
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Exact")
|
||||
.assert(hasAnyAncestor(isDialog()))
|
||||
.performClick()
|
||||
|
||||
composeTestRule.assertNoDialogExists()
|
||||
|
||||
verify {
|
||||
viewModel.trySendAction(
|
||||
VaultAddEditAction.ItemType.LoginType.UriValueChange(
|
||||
UriItem(
|
||||
"TestId",
|
||||
null,
|
||||
UriMatchType.EXACT,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `in ItemType_Login on URI settings click and on match detection click and cancel click should dismiss the dialog`() {
|
||||
mutableStateFlow.update { currentState ->
|
||||
updateLoginType(currentState) {
|
||||
copy(uriList = listOf(UriItem("TestId", null, null)))
|
||||
}
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll(text = "URI")
|
||||
.onSiblings()
|
||||
.filterToOne(hasContentDescription(value = "Options"))
|
||||
.performClick()
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Match detection")
|
||||
.assert(hasAnyAncestor(isDialog()))
|
||||
.performClick()
|
||||
|
||||
composeTestRule
|
||||
.onAllNodesWithText("URI match detection")
|
||||
.filterToOne(hasAnyAncestor(isDialog()))
|
||||
.assertIsDisplayed()
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Cancel")
|
||||
.assert(hasAnyAncestor(isDialog()))
|
||||
.performClick()
|
||||
|
||||
composeTestRule.assertNoDialogExists()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `in ItemType_Login state clicking the New URI button should trigger AddNewUriClick`() {
|
||||
composeTestRule
|
||||
|
@ -1884,7 +2049,7 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
|||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `Ownership option should send OwnershipChange action`() {
|
||||
mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES
|
||||
mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES
|
||||
|
||||
updateStateWithOwners()
|
||||
|
||||
|
|
|
@ -882,23 +882,6 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
assertEquals(expectedState, viewModel.stateFlow.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `UriTextChange should update uri in LoginItem`() = runTest {
|
||||
val action = VaultAddEditAction.ItemType.LoginType.UriTextChange(
|
||||
UriItem("testId", "TestUri", null),
|
||||
)
|
||||
|
||||
viewModel.actionChannel.trySend(action)
|
||||
|
||||
val expectedState = createVaultAddItemState(
|
||||
typeContentViewState = createLoginTypeContentViewState(
|
||||
uri = listOf(UriItem("testId", "TestUri", null)),
|
||||
),
|
||||
)
|
||||
|
||||
assertEquals(expectedState, viewModel.stateFlow.value)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `OpenUsernameGeneratorClick should emit NavigateToGeneratorModal with username GeneratorMode`() =
|
||||
|
@ -1110,13 +1093,65 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `UriSettingsClick should emit ShowToast with 'URI Settings' message`() = runTest {
|
||||
val viewModel = createAddVaultItemViewModel()
|
||||
fun `UriValueChange should update URI value in state`() = runTest {
|
||||
val viewModel = createAddVaultItemViewModel(
|
||||
savedStateHandle = createSavedStateHandleWithState(
|
||||
state = createVaultAddItemState(
|
||||
typeContentViewState = createLoginTypeContentViewState(
|
||||
uri = listOf(UriItem("testID", null, null)),
|
||||
),
|
||||
),
|
||||
vaultAddEditType = VaultAddEditType.EditItem(DEFAULT_EDIT_ITEM_ID),
|
||||
),
|
||||
)
|
||||
val expectedState = loginInitialState.copy(
|
||||
viewState = VaultAddEditState.ViewState.Content(
|
||||
common = createCommonContentViewState(),
|
||||
type = createLoginTypeContentViewState(
|
||||
uri = listOf(UriItem("testID", "Test", null)),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.actionChannel.trySend(VaultAddEditAction.ItemType.LoginType.UriSettingsClick)
|
||||
assertEquals(VaultAddEditEvent.ShowToast("URI Settings".asText()), awaitItem())
|
||||
}
|
||||
viewModel.trySendAction(
|
||||
VaultAddEditAction.ItemType.LoginType.UriValueChange(
|
||||
uriItem = UriItem("testID", "Test", null),
|
||||
),
|
||||
)
|
||||
|
||||
assertEquals(expectedState, viewModel.stateFlow.value)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `RemoveUriClick should remove URI value in state`() = runTest {
|
||||
val viewModel = createAddVaultItemViewModel(
|
||||
savedStateHandle = createSavedStateHandleWithState(
|
||||
state = createVaultAddItemState(
|
||||
typeContentViewState = createLoginTypeContentViewState(
|
||||
uri = listOf(UriItem("testID", null, null)),
|
||||
),
|
||||
),
|
||||
vaultAddEditType = VaultAddEditType.EditItem(DEFAULT_EDIT_ITEM_ID),
|
||||
),
|
||||
)
|
||||
|
||||
val expectedState = loginInitialState.copy(
|
||||
viewState = VaultAddEditState.ViewState.Content(
|
||||
common = createCommonContentViewState(),
|
||||
type = createLoginTypeContentViewState(
|
||||
uri = listOf(),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
viewModel.trySendAction(
|
||||
VaultAddEditAction.ItemType.LoginType.RemoveUriClick(
|
||||
uriItem = UriItem("testID", null, null),
|
||||
),
|
||||
)
|
||||
|
||||
assertEquals(expectedState, viewModel.stateFlow.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
package com.x8bit.bitwarden.ui.vault.feature.addedit.util
|
||||
|
||||
import com.bitwarden.core.UriMatchType
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.model.UriMatchDisplayType
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class UriMatchDisplayUtilTest {
|
||||
@Test
|
||||
fun `toDisplayMatchType should correctly convert to UriMatchDisplayType`() {
|
||||
URI_MATCH_TYPE_MAP.forEach {
|
||||
assertEquals(
|
||||
it.key.toDisplayMatchType(),
|
||||
it.value,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toUriMatchType should correctly convert to UriMatchType`() {
|
||||
URI_MATCH_TYPE_MAP.forEach {
|
||||
assertEquals(
|
||||
it.key,
|
||||
it.value.toUriMatchType(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val URI_MATCH_TYPE_MAP: Map<UriMatchType?, UriMatchDisplayType> =
|
||||
mapOf(
|
||||
Pair(null, UriMatchDisplayType.DEFAULT),
|
||||
Pair(UriMatchType.DOMAIN, UriMatchDisplayType.BASE_DOMAIN),
|
||||
Pair(UriMatchType.HOST, UriMatchDisplayType.HOST),
|
||||
Pair(UriMatchType.EXACT, UriMatchDisplayType.EXACT),
|
||||
Pair(UriMatchType.STARTS_WITH, UriMatchDisplayType.STARTS_WITH),
|
||||
Pair(UriMatchType.REGULAR_EXPRESSION, UriMatchDisplayType.REGULAR_EXPRESSION),
|
||||
Pair(UriMatchType.EXACT, UriMatchDisplayType.EXACT),
|
||||
Pair(UriMatchType.NEVER, UriMatchDisplayType.NEVER),
|
||||
)
|
Loading…
Add table
Reference in a new issue