mirror of
https://github.com/bitwarden/android.git
synced 2026-06-01 10:16:47 -05:00
BIT-927: Add auto-fill UI (#208)
This commit is contained in:
@@ -1,46 +1,233 @@
|
||||
package com.x8bit.bitwarden.ui.platform.feature.settings.autofill
|
||||
|
||||
import androidx.compose.ui.test.assert
|
||||
import androidx.compose.ui.test.assertIsOff
|
||||
import androidx.compose.ui.test.assertIsOn
|
||||
import androidx.compose.ui.test.filterToOne
|
||||
import androidx.compose.ui.test.hasAnyAncestor
|
||||
import androidx.compose.ui.test.isDialog
|
||||
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 com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
class AutoFillScreenTest : BaseComposeTest() {
|
||||
|
||||
@Test
|
||||
fun `on back click should send BackClick`() {
|
||||
val viewModel: AutoFillViewModel = mockk {
|
||||
every { eventFlow } returns emptyFlow()
|
||||
every { trySendAction(AutoFillAction.BackClick) } returns Unit
|
||||
}
|
||||
private var onNavigateBackCalled = false
|
||||
|
||||
private val mutableEventFlow = MutableSharedFlow<AutoFillEvent>(
|
||||
extraBufferCapacity = Int.MAX_VALUE,
|
||||
)
|
||||
private val mutableStateFlow = MutableStateFlow(DEFAULT_STATE)
|
||||
private val viewModel = mockk<AutoFillViewModel>(relaxed = true) {
|
||||
every { eventFlow } returns mutableEventFlow
|
||||
every { stateFlow } returns mutableStateFlow
|
||||
}
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
composeTestRule.setContent {
|
||||
AutoFillScreen(
|
||||
onNavigateBack = { onNavigateBackCalled = true },
|
||||
viewModel = viewModel,
|
||||
onNavigateBack = { },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on auto fill services toggle should send AutoFillServicesClick`() {
|
||||
composeTestRule
|
||||
.onNodeWithText("Auto-fill services")
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
verify { viewModel.trySendAction(AutoFillAction.AutoFillServicesClick(true)) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `auto fill services should be toggled on or off according to state`() {
|
||||
composeTestRule
|
||||
.onNodeWithText("Auto-fill services")
|
||||
.performScrollTo()
|
||||
.assertIsOff()
|
||||
mutableStateFlow.update { it.copy(isAutoFillServicesEnabled = true) }
|
||||
composeTestRule
|
||||
.onNodeWithText("Auto-fill services")
|
||||
.performScrollTo()
|
||||
.assertIsOn()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on use inline auto fill toggle should send UseInlineAutofillClick`() {
|
||||
composeTestRule
|
||||
.onNodeWithText("Use inline autofill")
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
verify { viewModel.trySendAction(AutoFillAction.UseInlineAutofillClick(true)) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `use inline autofill should be toggled on or off according to state`() {
|
||||
composeTestRule
|
||||
.onNodeWithText("Use inline autofill")
|
||||
.performScrollTo()
|
||||
.assertIsOff()
|
||||
mutableStateFlow.update { it.copy(isUseInlineAutoFillEnabled = true) }
|
||||
composeTestRule
|
||||
.onNodeWithText("Use inline autofill")
|
||||
.performScrollTo()
|
||||
.assertIsOn()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on use accessibility toggle should send UseAccessibilityClick`() {
|
||||
composeTestRule
|
||||
.onNodeWithText("Use accessibility")
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
verify { viewModel.trySendAction(AutoFillAction.UseAccessibilityClick(true)) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `use accessibility should be toggled on or off according to state`() {
|
||||
composeTestRule
|
||||
.onNodeWithText("Use accessibility")
|
||||
.performScrollTo()
|
||||
.assertIsOff()
|
||||
mutableStateFlow.update { it.copy(isUseAccessibilityEnabled = true) }
|
||||
composeTestRule
|
||||
.onNodeWithText("Use accessibility")
|
||||
.performScrollTo()
|
||||
.assertIsOn()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on use draw over toggle should send UseDrawOverClick`() {
|
||||
composeTestRule
|
||||
.onNodeWithText("Use draw-over")
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
verify { viewModel.trySendAction(AutoFillAction.UseDrawOverClick(true)) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `use draw-over should be toggled on or off according to state`() {
|
||||
composeTestRule
|
||||
.onNodeWithText("Use draw-over")
|
||||
.performScrollTo()
|
||||
.assertIsOff()
|
||||
mutableStateFlow.update { it.copy(isUseDrawOverEnabled = true) }
|
||||
composeTestRule
|
||||
.onNodeWithText("Use draw-over")
|
||||
.performScrollTo()
|
||||
.assertIsOn()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on copy TOTP automatically toggle should send CopyTotpAutomaticallyClick`() {
|
||||
composeTestRule
|
||||
.onNodeWithText("Copy TOTP automatically")
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
verify { viewModel.trySendAction(AutoFillAction.CopyTotpAutomaticallyClick(true)) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `copy TOTP automatically should be toggled on or off according to state`() {
|
||||
composeTestRule
|
||||
.onNodeWithText("Copy TOTP automatically")
|
||||
.performScrollTo()
|
||||
.assertIsOff()
|
||||
mutableStateFlow.update { it.copy(isCopyTotpAutomaticallyEnabled = true) }
|
||||
composeTestRule
|
||||
.onNodeWithText("Copy TOTP automatically")
|
||||
.performScrollTo()
|
||||
.assertIsOn()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on ask to add login toggle should send AskToAddLoginClick`() {
|
||||
composeTestRule
|
||||
.onNodeWithText("Ask to add login")
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
verify { viewModel.trySendAction(AutoFillAction.AskToAddLoginClick(true)) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ask to add login should be toggled on or off according to state`() {
|
||||
composeTestRule
|
||||
.onNodeWithText("Ask to add login")
|
||||
.performScrollTo()
|
||||
.assertIsOff()
|
||||
mutableStateFlow.update { it.copy(isAskToAddLoginEnabled = true) }
|
||||
composeTestRule
|
||||
.onNodeWithText("Ask to add login")
|
||||
.performScrollTo()
|
||||
.assertIsOn()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on default URI match detection toggle should display dialog`() {
|
||||
composeTestRule
|
||||
.onNodeWithText("Default URI match detection")
|
||||
.performScrollTo()
|
||||
.assert(!hasAnyAncestor(isDialog()))
|
||||
.performClick()
|
||||
composeTestRule
|
||||
.onAllNodesWithText("Default URI match detection")
|
||||
.filterToOne(hasAnyAncestor(isDialog()))
|
||||
.assertExists()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `default URI match detection add login should be updated on or off according to state`() {
|
||||
composeTestRule
|
||||
.onNodeWithText("Default")
|
||||
.assertExists()
|
||||
composeTestRule
|
||||
.onNodeWithText("Starts with")
|
||||
.assertDoesNotExist()
|
||||
mutableStateFlow.update {
|
||||
it.copy(uriDetectionMethod = AutoFillState.UriDetectionMethod.STARTS_WITH)
|
||||
}
|
||||
composeTestRule
|
||||
.onNodeWithText("Default")
|
||||
.assertDoesNotExist()
|
||||
composeTestRule
|
||||
.onNodeWithText("Starts with")
|
||||
.assertExists()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on back click should send BackClick`() {
|
||||
composeTestRule.onNodeWithContentDescription("Back").performClick()
|
||||
verify { viewModel.trySendAction(AutoFillAction.BackClick) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on NavigateAbout should call onNavigateToAutoFill`() {
|
||||
var haveCalledNavigateBack = false
|
||||
val viewModel = mockk<AutoFillViewModel> {
|
||||
every { eventFlow } returns flowOf(AutoFillEvent.NavigateBack)
|
||||
}
|
||||
composeTestRule.setContent {
|
||||
AutoFillScreen(
|
||||
viewModel = viewModel,
|
||||
onNavigateBack = { haveCalledNavigateBack = true },
|
||||
)
|
||||
}
|
||||
assertTrue(haveCalledNavigateBack)
|
||||
fun `on NavigateBack should call onNavigateBack`() {
|
||||
mutableEventFlow.tryEmit(AutoFillEvent.NavigateBack)
|
||||
assertTrue(onNavigateBackCalled)
|
||||
}
|
||||
}
|
||||
|
||||
private val DEFAULT_STATE: AutoFillState = AutoFillState(
|
||||
isAskToAddLoginEnabled = false,
|
||||
isAutoFillServicesEnabled = false,
|
||||
isCopyTotpAutomaticallyEnabled = false,
|
||||
isUseAccessibilityEnabled = false,
|
||||
isUseDrawOverEnabled = false,
|
||||
isUseInlineAutoFillEnabled = false,
|
||||
uriDetectionMethod = AutoFillState.UriDetectionMethod.DEFAULT,
|
||||
)
|
||||
|
||||
@@ -1,19 +1,147 @@
|
||||
package com.x8bit.bitwarden.ui.platform.feature.settings.autofill
|
||||
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import app.cash.turbine.test
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class AutoFillViewModelTest : BaseViewModelTest() {
|
||||
|
||||
@Test
|
||||
fun `initial state should be correct when not set`() {
|
||||
val viewModel = createViewModel(state = null)
|
||||
assertEquals(DEFAULT_STATE, viewModel.stateFlow.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `initial state should be correct when set`() {
|
||||
val state = DEFAULT_STATE.copy(
|
||||
isAutoFillServicesEnabled = true,
|
||||
uriDetectionMethod = AutoFillState.UriDetectionMethod.REGULAR_EXPRESSION,
|
||||
)
|
||||
val viewModel = createViewModel(state = state)
|
||||
assertEquals(state, viewModel.stateFlow.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on AskToAddLoginClick should emit ShowToast`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(AutoFillAction.AskToAddLoginClick(true))
|
||||
assertEquals(AutoFillEvent.ShowToast("Not yet implemented.".asText()), awaitItem())
|
||||
}
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(isAskToAddLoginEnabled = true),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on AutoFillServicesClick should emit ShowToast`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(AutoFillAction.AutoFillServicesClick(true))
|
||||
assertEquals(AutoFillEvent.ShowToast("Not yet implemented.".asText()), awaitItem())
|
||||
}
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(isAutoFillServicesEnabled = true),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on BackClick should emit NavigateBack`() = runTest {
|
||||
val viewModel = AutoFillViewModel()
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(AutoFillAction.BackClick)
|
||||
assertEquals(AutoFillEvent.NavigateBack, awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on CopyTotpAutomaticallyClick should update the isCopyTotpAutomaticallyEnabled state`() =
|
||||
runTest {
|
||||
val viewModel = createViewModel()
|
||||
val isEnabled = true
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(AutoFillAction.CopyTotpAutomaticallyClick(isEnabled))
|
||||
assertEquals(AutoFillEvent.ShowToast("Not yet implemented.".asText()), awaitItem())
|
||||
}
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(isCopyTotpAutomaticallyEnabled = isEnabled),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on UseAccessibilityClick should emit ShowToast`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(AutoFillAction.UseAccessibilityClick(true))
|
||||
assertEquals(AutoFillEvent.ShowToast("Not yet implemented.".asText()), awaitItem())
|
||||
}
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(isUseAccessibilityEnabled = true),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on UseDrawOverClick should emit ShowToast`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(AutoFillAction.UseDrawOverClick(true))
|
||||
assertEquals(AutoFillEvent.ShowToast("Not yet implemented.".asText()), awaitItem())
|
||||
}
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(isUseDrawOverEnabled = true),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on UseInlineAutofillClick should emit ShowToast`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(AutoFillAction.UseInlineAutofillClick(true))
|
||||
assertEquals(AutoFillEvent.ShowToast("Not yet implemented.".asText()), awaitItem())
|
||||
}
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(isUseInlineAutoFillEnabled = true),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on UriDetectionMethodSelect should emit ShowToast`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
val method = AutoFillState.UriDetectionMethod.EXACT
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(AutoFillAction.UriDetectionMethodSelect(method))
|
||||
assertEquals(AutoFillEvent.ShowToast("Not yet implemented.".asText()), awaitItem())
|
||||
}
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(uriDetectionMethod = method),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createViewModel(
|
||||
state: AutoFillState? = DEFAULT_STATE,
|
||||
): AutoFillViewModel = AutoFillViewModel(
|
||||
savedStateHandle = SavedStateHandle().apply { set("state", state) },
|
||||
)
|
||||
}
|
||||
|
||||
private val DEFAULT_STATE: AutoFillState = AutoFillState(
|
||||
isAskToAddLoginEnabled = false,
|
||||
isAutoFillServicesEnabled = false,
|
||||
isCopyTotpAutomaticallyEnabled = false,
|
||||
isUseAccessibilityEnabled = false,
|
||||
isUseDrawOverEnabled = false,
|
||||
isUseInlineAutoFillEnabled = false,
|
||||
uriDetectionMethod = AutoFillState.UriDetectionMethod.DEFAULT,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user