BIT-1488: Track changes in autofill status and send users to settings (#663)

This commit is contained in:
Brian Yencho authored and Álison Fernandes committed 2024-06-20 17:08:07 +01:00
1 parent 3851f88828
commit 94108bcb5d
11 files changed
+324 -7

No files matched your search

@@ -1,11 +1,14 @@
package com.x8bit.bitwarden.data.platform.repository
import android.view.autofill.AutofillManager
import app.cash.turbine.test
import com.bitwarden.core.DerivePinKeyResponse
import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson
import com.x8bit.bitwarden.data.auth.datasource.disk.util.FakeAuthDiskSource
import com.x8bit.bitwarden.data.platform.base.FakeDispatcherManager
import com.x8bit.bitwarden.data.platform.datasource.disk.util.FakeSettingsDiskSource
import com.x8bit.bitwarden.data.platform.manager.AppForegroundManager
import com.x8bit.bitwarden.data.platform.manager.model.AppForegroundState
import com.x8bit.bitwarden.data.platform.repository.model.VaultTimeout
import com.x8bit.bitwarden.data.platform.repository.model.VaultTimeoutAction
import com.x8bit.bitwarden.data.platform.util.asSuccess
@@ -14,7 +17,12 @@ import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.model.AppLang
import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.model.AppTheme
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
import io.mockk.runs
import io.mockk.verify
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse
@@ -23,11 +31,25 @@ import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
class SettingsRepositoryTest {
private val autofillManager: AutofillManager = mockk {
every { hasEnabledAutofillServices() } answers { isAutofillEnabledAndSupported }
every { isAutofillSupported } answers { isAutofillEnabledAndSupported }
every { isEnabled } answers { isAutofillEnabledAndSupported }
every { disableAutofillServices() } just runs
}
private val mutableAppForegroundStateFlow = MutableStateFlow(AppForegroundState.BACKGROUNDED)
private val appForegroundManager: AppForegroundManager = mockk {
every { appForegroundStateFlow } returns mutableAppForegroundStateFlow
}
private val fakeAuthDiskSource = FakeAuthDiskSource()
private val fakeSettingsDiskSource = FakeSettingsDiskSource()
private val vaultSdkSource: VaultSdkSource = mockk()
private var isAutofillEnabledAndSupported = false
private val settingsRepository = SettingsRepositoryImpl(
autofillManager = autofillManager,
appForegroundManager = appForegroundManager,
authDiskSource = fakeAuthDiskSource,
settingsDiskSource = fakeSettingsDiskSource,
vaultSdkSource = vaultSdkSource,
@@ -389,6 +411,53 @@ class SettingsRepositoryTest {
)
}
@Suppress("MaxLineLength")
@Test
fun `isAutofillEnabledStateFlow should emit updates if necessary when the app foreground state changes and disableAutofill is called`() =
runTest {
settingsRepository.isAutofillEnabledStateFlow.test {
assertFalse(awaitItem())
// An update is received when both the autofill state and foreground state change
isAutofillEnabledAndSupported = true
mutableAppForegroundStateFlow.value = AppForegroundState.FOREGROUNDED
assertTrue(awaitItem())
// An update is not received when only the foreground state changes
mutableAppForegroundStateFlow.value = AppForegroundState.BACKGROUNDED
expectNoEvents()
// An update is not received when only the autofill state changes
isAutofillEnabledAndSupported = false
expectNoEvents()
// An update is received after both states have changed
mutableAppForegroundStateFlow.value = AppForegroundState.FOREGROUNDED
assertFalse(awaitItem())
// Calling disableAutofill will result in an emission of false
isAutofillEnabledAndSupported = true
mutableAppForegroundStateFlow.value = AppForegroundState.BACKGROUNDED
assertTrue(awaitItem())
settingsRepository.disableAutofill()
assertFalse(awaitItem())
}
}
@Suppress("MaxLineLength")
@Test
fun `disableAutofill should trigger an emission of false from isAutofillEnabledStateFlow and disable autofill with the OS`() {
// Start in a state where autofill is enabled
isAutofillEnabledAndSupported = true
mutableAppForegroundStateFlow.value = AppForegroundState.FOREGROUNDED
assertTrue(settingsRepository.isAutofillEnabledStateFlow.value)
settingsRepository.disableAutofill()
assertFalse(settingsRepository.isAutofillEnabledStateFlow.value)
verify { autofillManager.disableAutofillServices() }
}
@Test
fun `getPullToRefreshEnabledFlow should react to changes in SettingsDiskSource`() = runTest {
val userId = "userId"
@@ -1,6 +1,7 @@
package com.x8bit.bitwarden.ui.platform.feature.settings.autofill
import androidx.compose.ui.test.assert
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsOff
import androidx.compose.ui.test.assertIsOn
import androidx.compose.ui.test.filterToOne
@@ -13,6 +14,8 @@ import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performScrollTo
import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
import com.x8bit.bitwarden.ui.util.assertNoDialogExists
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
@@ -24,6 +27,7 @@ import org.junit.Test
class AutoFillScreenTest : BaseComposeTest() {
private var isSystemSettingsRequestSuccess = false
private var onNavigateBackCalled = false
private val mutableEventFlow = bufferedMutableSharedFlow<AutoFillEvent>()
@@ -32,6 +36,9 @@ class AutoFillScreenTest : BaseComposeTest() {
every { eventFlow } returns mutableEventFlow
every { stateFlow } returns mutableStateFlow
}
private val intentManager: IntentManager = mockk {
every { startSystemAutofillSettingsActivity() } answers { isSystemSettingsRequestSuccess }
}
@Before
fun setUp() {
@@ -39,10 +46,63 @@ class AutoFillScreenTest : BaseComposeTest() {
AutoFillScreen(
onNavigateBack = { onNavigateBackCalled = true },
viewModel = viewModel,
intentManager = intentManager,
)
}
}
@Suppress("MaxLineLength")
@Test
fun `on NavigateToAutofillSettings should attempt to navigate to system settings and not show the fallback dialog when result is a success`() {
isSystemSettingsRequestSuccess = true
mutableEventFlow.tryEmit(AutoFillEvent.NavigateToAutofillSettings)
verify {
intentManager.startSystemAutofillSettingsActivity()
}
composeTestRule.assertNoDialogExists()
}
@Suppress("MaxLineLength")
@Test
fun `on NavigateToAutofillSettings should attempt to navigate to system settings and show the fallback dialog when result is not a success`() {
isSystemSettingsRequestSuccess = false
mutableEventFlow.tryEmit(AutoFillEvent.NavigateToAutofillSettings)
verify {
intentManager.startSystemAutofillSettingsActivity()
}
composeTestRule
.onAllNodesWithText(
"We were unable to automatically open the Android autofill settings menu for " +
"you. You can navigate to the autofill settings menu manually from Android " +
"Settings>System>Languages and input>Advanced>Autofill service.",
)
.filterToOne(hasAnyAncestor(isDialog()))
.assertIsDisplayed()
composeTestRule
.onAllNodesWithText("Ok")
.filterToOne(hasAnyAncestor(isDialog()))
.assertIsDisplayed()
}
@Test
fun `on autofill settings fallback dialog Ok click should dismiss the dialog`() {
isSystemSettingsRequestSuccess = false
mutableEventFlow.tryEmit(AutoFillEvent.NavigateToAutofillSettings)
composeTestRule
.onAllNodesWithText("Ok")
.filterToOne(hasAnyAncestor(isDialog()))
.assertIsDisplayed()
.performClick()
composeTestRule.assertNoDialogExists()
}
@Test
fun `on auto fill services toggle should send AutoFillServicesClick`() {
composeTestRule
@@ -10,15 +10,19 @@ import io.mockk.just
import io.mockk.mockk
import io.mockk.runs
import io.mockk.verify
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
class AutoFillViewModelTest : BaseViewModelTest() {
private val mutableIsAutofillEnabledStateFlow = MutableStateFlow(false)
private val settingsRepository: SettingsRepository = mockk() {
every { isInlineAutofillEnabled } returns true
every { isInlineAutofillEnabled = any() } just runs
every { isAutofillEnabledStateFlow } returns mutableIsAutofillEnabledStateFlow
every { disableAutofill() } just runs
}
@Test
@@ -29,6 +33,7 @@ class AutoFillViewModelTest : BaseViewModelTest() {
@Test
fun `initial state should be correct when set`() {
mutableIsAutofillEnabledStateFlow.value = true
val state = DEFAULT_STATE.copy(
isAutoFillServicesEnabled = true,
uriDetectionMethod = AutoFillState.UriDetectionMethod.REGULAR_EXPRESSION,
@@ -37,6 +42,26 @@ class AutoFillViewModelTest : BaseViewModelTest() {
assertEquals(state, viewModel.stateFlow.value)
}
@Test
fun `changes in autofill enabled status should update the state`() {
val viewModel = createViewModel()
assertEquals(DEFAULT_STATE, viewModel.stateFlow.value)
mutableIsAutofillEnabledStateFlow.value = true
assertEquals(
DEFAULT_STATE.copy(isAutoFillServicesEnabled = true),
viewModel.stateFlow.value,
)
mutableIsAutofillEnabledStateFlow.value = false
assertEquals(
DEFAULT_STATE.copy(isAutoFillServicesEnabled = false),
viewModel.stateFlow.value,
)
}
@Test
fun `on AskToAddLoginClick should emit ShowToast`() = runTest {
val viewModel = createViewModel()
@@ -51,14 +76,30 @@ class AutoFillViewModelTest : BaseViewModelTest() {
}
@Test
fun `on AutoFillServicesClick should emit ShowToast`() = runTest {
fun `on AutoFillServicesClick with false should disable autofill`() {
val viewModel = createViewModel()
viewModel.trySendAction(AutoFillAction.AutoFillServicesClick(false))
verify {
settingsRepository.disableAutofill()
}
assertEquals(
DEFAULT_STATE,
viewModel.stateFlow.value,
)
}
@Test
fun `on AutoFillServicesClick with true should emit NavigateToAutofillSettings`() = runTest {
val viewModel = createViewModel()
viewModel.eventFlow.test {
viewModel.trySendAction(AutoFillAction.AutoFillServicesClick(true))
assertEquals(AutoFillEvent.ShowToast("Not yet implemented.".asText()), awaitItem())
assertEquals(
AutoFillEvent.NavigateToAutofillSettings,
awaitItem(),
)
}
assertEquals(
DEFAULT_STATE.copy(isAutoFillServicesEnabled = true),
DEFAULT_STATE,
viewModel.stateFlow.value,
)
}