Add support for the pull-to-refresh in settings (#615)

This commit is contained in:
David Perez
2024-01-15 09:41:14 -06:00
committed by GitHub
parent 6143f35572
commit 52d74c97c1
10 changed files with 246 additions and 17 deletions

View File

@@ -232,4 +232,65 @@ class SettingsDiskSourceTest {
)
assertNull(fakeSharedPreferences.getString(vaultTimeoutActionKey, null))
}
@Test
fun `storePullToRefreshEnabled when values are present should pull from SharedPreferences`() {
val pullToRefreshBaseKey = "bwPreferencesStorage:syncOnRefresh"
val mockUserId = "mockUserId"
val pullToRefreshKey = "${pullToRefreshBaseKey}_$mockUserId"
fakeSharedPreferences
.edit()
.putBoolean(pullToRefreshKey, true)
.apply()
assertEquals(true, settingsDiskSource.getPullToRefreshEnabled(userId = mockUserId))
}
@Test
fun `storePullToRefreshEnabled when values are absent should return null`() {
val mockUserId = "mockUserId"
assertNull(settingsDiskSource.getPullToRefreshEnabled(userId = mockUserId))
}
@Test
fun `getPullToRefreshEnabledFlow should react to changes in getPullToRefreshEnabled`() =
runTest {
val mockUserId = "mockUserId"
settingsDiskSource.getPullToRefreshEnabledFlow(userId = mockUserId).test {
// The initial values of the Flow and the property are in sync
assertNull(settingsDiskSource.getPullToRefreshEnabled(userId = mockUserId))
assertNull(awaitItem())
// Updating the disk source updates shared preferences
settingsDiskSource.storePullToRefreshEnabled(
userId = mockUserId,
isPullToRefreshEnabled = true,
)
assertEquals(true, awaitItem())
}
}
@Test
fun `storePullToRefreshEnabled for non-null values should update SharedPreferences`() {
val pullToRefreshBaseKey = "bwPreferencesStorage:syncOnRefresh"
val mockUserId = "mockUserId"
val pullToRefreshKey = "${pullToRefreshBaseKey}_$mockUserId"
settingsDiskSource.storePullToRefreshEnabled(
userId = mockUserId,
isPullToRefreshEnabled = true,
)
assertTrue(fakeSharedPreferences.getBoolean(pullToRefreshKey, false))
}
@Test
fun `storePullToRefreshEnabled for null values should clear SharedPreferences`() {
val pullToRefreshBaseKey = "bwPreferencesStorage:syncOnRefresh"
val mockUserId = "mockUserId"
val pullToRefreshKey = "${pullToRefreshBaseKey}_$mockUserId"
fakeSharedPreferences.edit { putBoolean(pullToRefreshKey, false) }
settingsDiskSource.storePullToRefreshEnabled(
userId = mockUserId,
isPullToRefreshEnabled = null,
)
assertFalse(fakeSharedPreferences.contains(pullToRefreshKey))
}
}

View File

@@ -19,9 +19,14 @@ class FakeSettingsDiskSource : SettingsDiskSource {
private val mutableVaultTimeoutInMinutesFlowMap =
mutableMapOf<String, MutableSharedFlow<Int?>>()
private val mutablePullToRefreshEnabledFlowMap =
mutableMapOf<String, MutableSharedFlow<Boolean?>>()
private val storedVaultTimeoutActions = mutableMapOf<String, VaultTimeoutAction?>()
private val storedVaultTimeoutInMinutes = mutableMapOf<String, Int?>()
private val storedPullToRefreshEnabled = mutableMapOf<String, Boolean?>()
override var appLanguage: AppLanguage? = null
override fun clearData(userId: String) {
@@ -62,6 +67,18 @@ class FakeSettingsDiskSource : SettingsDiskSource {
getMutableVaultTimeoutActionsFlow(userId = userId).tryEmit(vaultTimeoutAction)
}
override fun getPullToRefreshEnabled(userId: String): Boolean? =
storedPullToRefreshEnabled[userId]
override fun getPullToRefreshEnabledFlow(userId: String): Flow<Boolean?> =
getMutablePullToRefreshEnabledFlow(userId = userId)
.onSubscription { emit(getPullToRefreshEnabled(userId = userId)) }
override fun storePullToRefreshEnabled(userId: String, isPullToRefreshEnabled: Boolean?) {
storedPullToRefreshEnabled[userId] = isPullToRefreshEnabled
getMutablePullToRefreshEnabledFlow(userId = userId).tryEmit(isPullToRefreshEnabled)
}
//region Private helper functions
private fun getMutableVaultTimeoutActionsFlow(
@@ -78,5 +95,12 @@ class FakeSettingsDiskSource : SettingsDiskSource {
bufferedMutableSharedFlow(replay = 1)
}
private fun getMutablePullToRefreshEnabledFlow(
userId: String,
): MutableSharedFlow<Boolean?> =
mutablePullToRefreshEnabledFlowMap.getOrPut(userId) {
bufferedMutableSharedFlow(replay = 1)
}
//endregion Private helper functions
}

View File

@@ -2,11 +2,13 @@ package com.x8bit.bitwarden.data.platform.repository
import app.cash.turbine.test
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson
import com.x8bit.bitwarden.data.platform.base.FakeDispatcherManager
import com.x8bit.bitwarden.data.platform.datasource.disk.util.FakeSettingsDiskSource
import com.x8bit.bitwarden.data.platform.repository.model.VaultTimeout
import com.x8bit.bitwarden.data.platform.repository.model.VaultTimeoutAction
import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.model.AppLanguage
import io.mockk.coEvery
import io.mockk.every
import io.mockk.mockk
import kotlinx.coroutines.test.runTest
@@ -271,6 +273,38 @@ class SettingsRepositoryTest {
)
}
}
@Test
fun `getPullToRefreshEnabledFlow should react to changes in SettingsDiskSource`() = runTest {
val userId = "userId"
val userState = mockk<UserStateJson> {
every { activeUserId } returns userId
}
coEvery { authDiskSource.userState } returns userState
settingsRepository
.getPullToRefreshEnabledFlow()
.test {
assertFalse(awaitItem())
fakeSettingsDiskSource.storePullToRefreshEnabled(
userId = userId,
isPullToRefreshEnabled = true,
)
assertTrue(awaitItem())
fakeSettingsDiskSource.storePullToRefreshEnabled(
userId = userId,
isPullToRefreshEnabled = false,
)
assertFalse(awaitItem())
}
}
@Test
fun `storePullToRefreshEnabled should properly update SettingsDiskSource`() {
val userId = "userId"
every { authDiskSource.userState?.activeUserId } returns userId
settingsRepository.storePullToRefreshEnabled(true)
assertEquals(true, fakeSettingsDiskSource.getPullToRefreshEnabled(userId = userId))
}
}
/**

View File

@@ -2,6 +2,7 @@ package com.x8bit.bitwarden.ui.platform.feature.settings.other
import androidx.lifecycle.SavedStateHandle
import app.cash.turbine.test
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
import io.mockk.every
@@ -9,12 +10,17 @@ 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 OtherViewModelTest : BaseViewModelTest() {
val vaultRepository = mockk<VaultRepository>()
private val mutablePullToRefreshStateFlow = MutableStateFlow(false)
private val settingsRepository = mockk<SettingsRepository> {
every { getPullToRefreshEnabledFlow() } returns mutablePullToRefreshStateFlow
}
private val vaultRepository = mockk<VaultRepository>()
@Test
fun `initial state should be correct when not set`() {
@@ -51,21 +57,18 @@ class OtherViewModelTest : BaseViewModelTest() {
}
@Test
fun `on AllowSyncToggled should update value in state`() = runTest {
fun `on AllowSyncToggled should update value in state`() {
every {
settingsRepository.storePullToRefreshEnabled(isPullToRefreshEnabled = true)
} just runs
val viewModel = createViewModel()
viewModel.eventFlow.test {
expectNoEvents()
}
viewModel.stateFlow.test {
assertEquals(
DEFAULT_STATE,
awaitItem(),
)
viewModel.trySendAction(OtherAction.AllowSyncToggle(true))
assertEquals(
DEFAULT_STATE.copy(allowSyncOnRefresh = true),
awaitItem(),
)
viewModel.trySendAction(OtherAction.AllowSyncToggle(true))
assertEquals(
DEFAULT_STATE.copy(allowSyncOnRefresh = true),
viewModel.stateFlow.value,
)
verify(exactly = 1) {
settingsRepository.storePullToRefreshEnabled(isPullToRefreshEnabled = true)
}
}
@@ -114,6 +117,7 @@ class OtherViewModelTest : BaseViewModelTest() {
private fun createViewModel(
state: OtherState? = null,
) = OtherViewModel(
settingsRepo = settingsRepository,
vaultRepo = vaultRepository,
savedStateHandle = SavedStateHandle().apply {
set("state", state)