mirror of
https://github.com/bitwarden/android.git
synced 2025-03-15 18:58:59 +03:00
BIT-930: Add UI for Other screen (#477)
This commit is contained in:
parent
02c8f4bfec
commit
b24c2ba7e7
4 changed files with 492 additions and 27 deletions
|
@ -1,34 +1,55 @@
|
|||
package com.x8bit.bitwarden.ui.platform.feature.settings.other
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
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.padding
|
||||
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
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenFilledTonalButton
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenScaffold
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenSelectionDialog
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenSelectionRow
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenTextRow
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenTopAppBar
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenTwoButtonDialog
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenWideSwitch
|
||||
|
||||
/**
|
||||
* Displays the other screen.
|
||||
*/
|
||||
@Suppress("LongMethod")
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun OtherScreen(
|
||||
onNavigateBack: () -> Unit,
|
||||
viewModel: OtherViewModel = hiltViewModel(),
|
||||
) {
|
||||
val state by viewModel.stateFlow.collectAsState()
|
||||
EventsEffect(viewModel = viewModel) { event ->
|
||||
when (event) {
|
||||
OtherEvent.NavigateBack -> onNavigateBack.invoke()
|
||||
|
@ -58,7 +79,148 @@ fun OtherScreen(
|
|||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState()),
|
||||
) {
|
||||
// TODO: BIT-930 Display Other UI
|
||||
BitwardenWideSwitch(
|
||||
label = stringResource(id = R.string.enable_sync_on_refresh),
|
||||
description = stringResource(id = R.string.enable_sync_on_refresh_description),
|
||||
isChecked = state.allowSyncOnRefresh,
|
||||
onCheckedChange = remember(viewModel) {
|
||||
{ viewModel.trySendAction(OtherAction.AllowSyncToggle(it)) }
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
BitwardenFilledTonalButton(
|
||||
onClick = remember(viewModel) {
|
||||
{ viewModel.trySendAction(OtherAction.SyncNowButtonClick) }
|
||||
},
|
||||
label = stringResource(id = R.string.sync_now),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Start,
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.last_sync),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
modifier = Modifier.padding(start = 16.dp, end = 2.dp),
|
||||
)
|
||||
Text(
|
||||
text = state.lastSyncTime,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
||||
ClearClipboardFrequencyRow(
|
||||
currentSelection = state.clearClipboardFrequency,
|
||||
onFrequencySelection = remember(viewModel) {
|
||||
{ viewModel.trySendAction(OtherAction.ClearClipboardFrequencyChange(it)) }
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
|
||||
ScreenCaptureRow(
|
||||
currentValue = state.allowScreenCapture,
|
||||
onValueChange = remember(viewModel) {
|
||||
{ viewModel.trySendAction(OtherAction.AllowScreenCaptureToggle(it)) }
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ScreenCaptureRow(
|
||||
currentValue: Boolean,
|
||||
onValueChange: (Boolean) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
var shouldShowScreenCaptureConfirmDialog by remember { mutableStateOf(false) }
|
||||
|
||||
BitwardenWideSwitch(
|
||||
label = stringResource(id = R.string.allow_screen_capture),
|
||||
isChecked = currentValue,
|
||||
onCheckedChange = {
|
||||
if (currentValue) {
|
||||
onValueChange(false)
|
||||
} else {
|
||||
shouldShowScreenCaptureConfirmDialog = true
|
||||
}
|
||||
},
|
||||
modifier = modifier,
|
||||
)
|
||||
|
||||
if (shouldShowScreenCaptureConfirmDialog) {
|
||||
BitwardenTwoButtonDialog(
|
||||
title = stringResource(R.string.allow_screen_capture),
|
||||
message = stringResource(R.string.are_you_sure_you_want_to_enable_screen_capture),
|
||||
confirmButtonText = stringResource(R.string.yes),
|
||||
dismissButtonText = stringResource(id = R.string.cancel),
|
||||
onConfirmClick = {
|
||||
onValueChange(true)
|
||||
shouldShowScreenCaptureConfirmDialog = false
|
||||
},
|
||||
onDismissClick = { shouldShowScreenCaptureConfirmDialog = false },
|
||||
onDismissRequest = { shouldShowScreenCaptureConfirmDialog = false },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ClearClipboardFrequencyRow(
|
||||
currentSelection: OtherState.ClearClipboardFrequency,
|
||||
onFrequencySelection: (OtherState.ClearClipboardFrequency) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
var shouldShowClearClipboardDialog by remember { mutableStateOf(false) }
|
||||
|
||||
BitwardenTextRow(
|
||||
text = stringResource(id = R.string.clear_clipboard),
|
||||
description = stringResource(id = R.string.clear_clipboard_description),
|
||||
onClick = { shouldShowClearClipboardDialog = true },
|
||||
modifier = modifier,
|
||||
) {
|
||||
Text(
|
||||
text = currentSelection.text(),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
|
||||
if (shouldShowClearClipboardDialog) {
|
||||
BitwardenSelectionDialog(
|
||||
title = stringResource(id = R.string.clear_clipboard),
|
||||
onDismissRequest = { shouldShowClearClipboardDialog = false },
|
||||
) {
|
||||
OtherState.ClearClipboardFrequency.entries.forEach { option ->
|
||||
BitwardenSelectionRow(
|
||||
text = option.text,
|
||||
isSelected = option == currentSelection,
|
||||
onClick = {
|
||||
shouldShowClearClipboardDialog = false
|
||||
onFrequencySelection(
|
||||
OtherState.ClearClipboardFrequency.entries.first { it == option },
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,95 @@
|
|||
package com.x8bit.bitwarden.ui.platform.feature.settings.other
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.Text
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val KEY_STATE = "state"
|
||||
|
||||
/**
|
||||
* View model for the other screen.
|
||||
*/
|
||||
@HiltViewModel
|
||||
class OtherViewModel @Inject constructor() : BaseViewModel<Unit, OtherEvent, OtherAction>(
|
||||
initialState = Unit,
|
||||
class OtherViewModel @Inject constructor(
|
||||
private val vaultRepo: VaultRepository,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : BaseViewModel<OtherState, OtherEvent, OtherAction>(
|
||||
initialState = savedStateHandle[KEY_STATE]
|
||||
?: OtherState(
|
||||
allowScreenCapture = false,
|
||||
allowSyncOnRefresh = false,
|
||||
clearClipboardFrequency = OtherState.ClearClipboardFrequency.DEFAULT,
|
||||
lastSyncTime = "5/14/2023 4:52 PM",
|
||||
),
|
||||
) {
|
||||
override fun handleAction(action: OtherAction): Unit = when (action) {
|
||||
OtherAction.BackClick -> sendEvent(OtherEvent.NavigateBack)
|
||||
is OtherAction.AllowScreenCaptureToggle -> handleAllowScreenCaptureToggled(action)
|
||||
is OtherAction.AllowSyncToggle -> handleAllowSyncToggled(action)
|
||||
OtherAction.BackClick -> handleBackClicked()
|
||||
is OtherAction.ClearClipboardFrequencyChange -> handleClearClipboardFrequencyChanged(action)
|
||||
OtherAction.SyncNowButtonClick -> handleSyncNowButtonClicked()
|
||||
}
|
||||
|
||||
private fun handleAllowScreenCaptureToggled(action: OtherAction.AllowScreenCaptureToggle) {
|
||||
// TODO BIT-805 implement screen capture setting
|
||||
mutableStateFlow.update { it.copy(allowScreenCapture = action.isScreenCaptureEnabled) }
|
||||
}
|
||||
|
||||
private fun handleAllowSyncToggled(action: OtherAction.AllowSyncToggle) {
|
||||
// TODO BIT-461 hook up to pull-to-refresh feature
|
||||
mutableStateFlow.update { it.copy(allowSyncOnRefresh = action.isSyncEnabled) }
|
||||
}
|
||||
|
||||
private fun handleBackClicked() {
|
||||
sendEvent(OtherEvent.NavigateBack)
|
||||
}
|
||||
|
||||
private fun handleClearClipboardFrequencyChanged(
|
||||
action: OtherAction.ClearClipboardFrequencyChange,
|
||||
) {
|
||||
// TODO BIT-1283 implement clear clipboard setting
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
clearClipboardFrequency = action.clearClipboardFrequency,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSyncNowButtonClicked() {
|
||||
// TODO BIT-1282 add full support and visual feedback
|
||||
vaultRepo.sync()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Models the state of the Other screen.
|
||||
*/
|
||||
@Parcelize
|
||||
data class OtherState(
|
||||
val allowScreenCapture: Boolean,
|
||||
val allowSyncOnRefresh: Boolean,
|
||||
val clearClipboardFrequency: ClearClipboardFrequency,
|
||||
val lastSyncTime: String,
|
||||
) : Parcelable {
|
||||
/**
|
||||
* Represents the different frequencies with which the user clipboard can be cleared.
|
||||
*/
|
||||
enum class ClearClipboardFrequency(val text: Text) {
|
||||
DEFAULT(text = R.string.never.asText()),
|
||||
TEN_SECONDS(text = R.string.ten_seconds.asText()),
|
||||
TWENTY_SECONDS(text = R.string.twenty_seconds.asText()),
|
||||
THIRTY_SECONDS(text = R.string.thirty_seconds.asText()),
|
||||
ONE_MINUTE(text = R.string.one_minute.asText()),
|
||||
TWO_MINUTES(text = R.string.two_minutes.asText()),
|
||||
FIVE_MINUTES(text = R.string.five_minutes.asText()),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,8 +107,34 @@ sealed class OtherEvent {
|
|||
* Models actions for the other screen.
|
||||
*/
|
||||
sealed class OtherAction {
|
||||
/**
|
||||
* Indicates that the user toggled the Allow screen capture switch to [isScreenCaptureEnabled].
|
||||
*/
|
||||
data class AllowScreenCaptureToggle(
|
||||
val isScreenCaptureEnabled: Boolean,
|
||||
) : OtherAction()
|
||||
|
||||
/**
|
||||
* Indicates that the user toggled the Allow sync on refresh switch to [isSyncEnabled].
|
||||
*/
|
||||
data class AllowSyncToggle(
|
||||
val isSyncEnabled: Boolean,
|
||||
) : OtherAction()
|
||||
|
||||
/**
|
||||
* User clicked back button.
|
||||
*/
|
||||
data object BackClick : OtherAction()
|
||||
|
||||
/**
|
||||
* Indicates that the user changed the clear clipboard frequency.
|
||||
*/
|
||||
data class ClearClipboardFrequencyChange(
|
||||
val clearClipboardFrequency: OtherState.ClearClipboardFrequency,
|
||||
) : OtherAction()
|
||||
|
||||
/**
|
||||
* Indicates that the user clicked the Sync Now button.
|
||||
*/
|
||||
data object SyncNowButtonClick : OtherAction()
|
||||
}
|
||||
|
|
|
@ -1,46 +1,134 @@
|
|||
package com.x8bit.bitwarden.ui.platform.feature.settings.other
|
||||
|
||||
import androidx.compose.ui.test.assertIsDisplayed
|
||||
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 com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
|
||||
import com.x8bit.bitwarden.ui.util.assertNoDialogExists
|
||||
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.MutableStateFlow
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
class OtherScreenTest : BaseComposeTest() {
|
||||
|
||||
@Test
|
||||
fun `on back click should send BackClick`() {
|
||||
val viewModel: OtherViewModel = mockk {
|
||||
every { eventFlow } returns emptyFlow()
|
||||
every { trySendAction(OtherAction.BackClick) } returns Unit
|
||||
}
|
||||
composeTestRule.setContent {
|
||||
OtherScreen(
|
||||
viewModel = viewModel,
|
||||
onNavigateBack = { },
|
||||
)
|
||||
}
|
||||
composeTestRule.onNodeWithContentDescription("Back").performClick()
|
||||
verify { viewModel.trySendAction(OtherAction.BackClick) }
|
||||
private var haveCalledNavigateBack = false
|
||||
private val mutableEventFlow = bufferedMutableSharedFlow<OtherEvent>()
|
||||
private val mutableStateFlow = MutableStateFlow(DEFAULT_STATE)
|
||||
private val viewModel = mockk<OtherViewModel>(relaxed = true) {
|
||||
every { eventFlow } returns mutableEventFlow
|
||||
every { stateFlow } returns mutableStateFlow
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on NavigateOther should call onNavigateToOther`() {
|
||||
var haveCalledNavigateBack = false
|
||||
val viewModel = mockk<OtherViewModel> {
|
||||
every { eventFlow } returns flowOf(OtherEvent.NavigateBack)
|
||||
}
|
||||
@Before
|
||||
fun setup() {
|
||||
composeTestRule.setContent {
|
||||
OtherScreen(
|
||||
viewModel = viewModel,
|
||||
onNavigateBack = { haveCalledNavigateBack = true },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on allow screen capture confirm should send AllowScreenCaptureToggle`() {
|
||||
composeTestRule.onNodeWithText("Allow screen capture").performClick()
|
||||
composeTestRule.onNodeWithText("Yes").performClick()
|
||||
composeTestRule.assertNoDialogExists()
|
||||
|
||||
verify { viewModel.trySendAction(OtherAction.AllowScreenCaptureToggle(true)) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on allow screen capture cancel should dismiss dialog`() {
|
||||
composeTestRule.onNodeWithText("Allow screen capture").performClick()
|
||||
composeTestRule
|
||||
.onAllNodesWithText("Cancel")
|
||||
.filterToOne(hasAnyAncestor(isDialog()))
|
||||
.performClick()
|
||||
composeTestRule.assertNoDialogExists()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on allow screen capture row click should display confirm enable screen capture dialog`() {
|
||||
composeTestRule.onNodeWithText("Allow screen capture").performClick()
|
||||
composeTestRule
|
||||
.onAllNodesWithText("Allow screen capture")
|
||||
.filterToOne(hasAnyAncestor(isDialog()))
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on allow sync toggle should send AllowSyncToggle`() {
|
||||
composeTestRule.onNodeWithText("Allow sync on refresh").performClick()
|
||||
verify { viewModel.trySendAction(OtherAction.AllowSyncToggle(true)) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on back click should send BackClick`() {
|
||||
composeTestRule.onNodeWithContentDescription("Back").performClick()
|
||||
verify { viewModel.trySendAction(OtherAction.BackClick) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on clear clipboard row click should show show clipboard selection dialog`() {
|
||||
composeTestRule.onNodeWithText("Clear clipboard").performClick()
|
||||
composeTestRule
|
||||
.onAllNodesWithText("Clear clipboard")
|
||||
.filterToOne(hasAnyAncestor(isDialog()))
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on clear clipboard dialog item click should send ClearClipboardFrequencyChange`() {
|
||||
composeTestRule.onNodeWithText("Clear clipboard").performClick()
|
||||
composeTestRule
|
||||
.onAllNodesWithText("10 seconds")
|
||||
.filterToOne(hasAnyAncestor(isDialog()))
|
||||
.performClick()
|
||||
composeTestRule.assertNoDialogExists()
|
||||
|
||||
verify {
|
||||
viewModel.trySendAction(
|
||||
OtherAction.ClearClipboardFrequencyChange(
|
||||
clearClipboardFrequency = OtherState.ClearClipboardFrequency.TEN_SECONDS,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on clear clipboard dialog cancel should dismiss dialog`() {
|
||||
composeTestRule.onNodeWithText("Clear clipboard").performClick()
|
||||
composeTestRule.onNodeWithText("Cancel").performClick()
|
||||
composeTestRule.assertNoDialogExists()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on sync now button click should send SyncNowButtonClick`() {
|
||||
composeTestRule.onNodeWithText("Sync now").performClick()
|
||||
verify { viewModel.trySendAction(OtherAction.SyncNowButtonClick) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on NavigateBack should call onNavigateBack`() {
|
||||
mutableEventFlow.tryEmit(OtherEvent.NavigateBack)
|
||||
assertTrue(haveCalledNavigateBack)
|
||||
}
|
||||
}
|
||||
|
||||
private val DEFAULT_STATE = OtherState(
|
||||
allowScreenCapture = false,
|
||||
allowSyncOnRefresh = false,
|
||||
clearClipboardFrequency = OtherState.ClearClipboardFrequency.DEFAULT,
|
||||
lastSyncTime = "5/14/2023 4:52 PM",
|
||||
)
|
||||
|
|
|
@ -1,19 +1,131 @@
|
|||
package com.x8bit.bitwarden.ui.platform.feature.settings.other
|
||||
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import app.cash.turbine.test
|
||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.runs
|
||||
import io.mockk.verify
|
||||
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>()
|
||||
|
||||
@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(
|
||||
clearClipboardFrequency = OtherState.ClearClipboardFrequency.FIVE_MINUTES,
|
||||
)
|
||||
val viewModel = createViewModel(state = state)
|
||||
assertEquals(state, viewModel.stateFlow.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on AllowScreenCaptureToggled should update value in state`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
expectNoEvents()
|
||||
}
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(
|
||||
DEFAULT_STATE,
|
||||
awaitItem(),
|
||||
)
|
||||
viewModel.trySendAction(OtherAction.AllowScreenCaptureToggle(true))
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(allowScreenCapture = true),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on AllowSyncToggled should update value in state`() = runTest {
|
||||
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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on BackClick should emit NavigateBack`() = runTest {
|
||||
val viewModel = OtherViewModel()
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(OtherAction.BackClick)
|
||||
assertEquals(OtherEvent.NavigateBack, awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on ClearClipboardFrequencyChange should update state`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(
|
||||
DEFAULT_STATE,
|
||||
awaitItem(),
|
||||
)
|
||||
viewModel.trySendAction(
|
||||
OtherAction.ClearClipboardFrequencyChange(
|
||||
clearClipboardFrequency = OtherState.ClearClipboardFrequency.ONE_MINUTE,
|
||||
),
|
||||
)
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(
|
||||
clearClipboardFrequency = OtherState.ClearClipboardFrequency.ONE_MINUTE,
|
||||
),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on SyncNowButtonClick should sync repo`() = runTest {
|
||||
every { vaultRepository.sync() } just runs
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(OtherAction.SyncNowButtonClick)
|
||||
expectNoEvents()
|
||||
}
|
||||
verify { vaultRepository.sync() }
|
||||
}
|
||||
|
||||
private fun createViewModel(
|
||||
state: OtherState? = null,
|
||||
) = OtherViewModel(
|
||||
vaultRepo = vaultRepository,
|
||||
savedStateHandle = SavedStateHandle().apply {
|
||||
set("state", state)
|
||||
},
|
||||
)
|
||||
|
||||
companion object {
|
||||
private val DEFAULT_STATE = OtherState(
|
||||
allowScreenCapture = false,
|
||||
allowSyncOnRefresh = false,
|
||||
clearClipboardFrequency = OtherState.ClearClipboardFrequency.DEFAULT,
|
||||
lastSyncTime = "5/14/2023 4:52 PM",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue