Settings boilerplate (#170)

This commit is contained in:
David Perez 2023-10-27 10:30:21 -05:00 committed by Álison Fernandes
parent d0e0362771
commit 0af6e7f826
35 changed files with 1205 additions and 35 deletions

View file

@ -5,6 +5,18 @@ import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions
import androidx.navigation.compose.composable
import androidx.navigation.navigation
import com.x8bit.bitwarden.ui.platform.feature.settings.about.aboutDestination
import com.x8bit.bitwarden.ui.platform.feature.settings.about.navigateToAbout
import com.x8bit.bitwarden.ui.platform.feature.settings.accountsecurity.accountSecurityDestination
import com.x8bit.bitwarden.ui.platform.feature.settings.accountsecurity.navigateToAccountSecurity
import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.appearanceDestination
import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.navigateToAppearance
import com.x8bit.bitwarden.ui.platform.feature.settings.autofill.autoFillDestination
import com.x8bit.bitwarden.ui.platform.feature.settings.autofill.navigateToAutoFill
import com.x8bit.bitwarden.ui.platform.feature.settings.other.navigateToOther
import com.x8bit.bitwarden.ui.platform.feature.settings.other.otherDestination
import com.x8bit.bitwarden.ui.platform.feature.settings.vault.navigateToVault
import com.x8bit.bitwarden.ui.platform.feature.settings.vault.vaultDestination
const val SETTINGS_GRAPH_ROUTE: String = "settings_graph"
private const val SETTINGS_ROUTE: String = "settings"
@ -21,10 +33,20 @@ fun NavGraphBuilder.settingsGraph(
) {
composable(SETTINGS_ROUTE) {
SettingsScreen(
onNavigateToAbout = { navController.navigateToAbout() },
onNavigateToAccountSecurity = { navController.navigateToAccountSecurity() },
onNavigateToAppearance = { navController.navigateToAppearance() },
onNavigateToAutoFill = { navController.navigateToAutoFill() },
onNavigateToOther = { navController.navigateToOther() },
onNavigateToVault = { navController.navigateToVault() },
)
}
aboutDestination(onNavigateBack = { navController.popBackStack() })
accountSecurityDestination(onNavigateBack = { navController.popBackStack() })
appearanceDestination(onNavigateBack = { navController.popBackStack() })
autoFillDestination(onNavigateBack = { navController.popBackStack() })
otherDestination(onNavigateBack = { navController.popBackStack() })
vaultDestination(onNavigateBack = { navController.popBackStack() })
}
}

View file

@ -44,12 +44,22 @@ import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SettingsScreen(
onNavigateToAbout: () -> Unit,
onNavigateToAccountSecurity: () -> Unit,
onNavigateToAppearance: () -> Unit,
onNavigateToAutoFill: () -> Unit,
onNavigateToOther: () -> Unit,
onNavigateToVault: () -> Unit,
viewModel: SettingsViewModel = hiltViewModel(),
) {
EventsEffect(viewModel = viewModel) { event ->
when (event) {
SettingsEvent.NavigateAbout -> onNavigateToAbout()
SettingsEvent.NavigateAccountSecurity -> onNavigateToAccountSecurity.invoke()
SettingsEvent.NavigateAppearance -> onNavigateToAppearance()
SettingsEvent.NavigateAutoFill -> onNavigateToAutoFill()
SettingsEvent.NavigateOther -> onNavigateToOther()
SettingsEvent.NavigateVault -> onNavigateToVault()
}
}

View file

@ -26,23 +26,23 @@ class SettingsViewModel @Inject constructor() : BaseViewModel<Unit, SettingsEven
}
Settings.AUTO_FILL -> {
// TODO: BIT-927 Launch auto-fill UI
sendEvent(SettingsEvent.NavigateAutoFill)
}
Settings.VAULT -> {
// TODO: BIT-928 Launch vault UI
sendEvent(SettingsEvent.NavigateVault)
}
Settings.APPEARANCE -> {
// TODO: BIT-929 Launch appearance UI
sendEvent(SettingsEvent.NavigateAppearance)
}
Settings.OTHER -> {
// TODO: BIT-930 Launch other UI
sendEvent(SettingsEvent.NavigateOther)
}
Settings.ABOUT -> {
// TODO: BIT-931 Launch about UI
sendEvent(SettingsEvent.NavigateAbout)
}
}
}
@ -52,10 +52,35 @@ class SettingsViewModel @Inject constructor() : BaseViewModel<Unit, SettingsEven
* Models events for the settings screen.
*/
sealed class SettingsEvent {
/**
* Navigate to the about screen.
*/
data object NavigateAbout : SettingsEvent()
/**
* Navigate to the account security screen.
*/
data object NavigateAccountSecurity : SettingsEvent()
/**
* Navigate to the appearance screen.
*/
data object NavigateAppearance : SettingsEvent()
/**
* Navigate to the auto-fill screen.
*/
data object NavigateAutoFill : SettingsEvent()
/**
* Navigate to the other screen.
*/
data object NavigateOther : SettingsEvent()
/**
* Navigate to the vault screen.
*/
data object NavigateVault : SettingsEvent()
}
/**
@ -75,12 +100,11 @@ sealed class SettingsAction {
*
* @property text The [Text] of the string that represents the label of each setting.
*/
// TODO: BIT-944 Missing correct resources for "Account Security", "Vault", and "Appearance".
enum class Settings(val text: Text) {
ACCOUNT_SECURITY(R.string.security.asText()),
ACCOUNT_SECURITY(R.string.account_security.asText()),
AUTO_FILL(R.string.autofill.asText()),
VAULT(R.string.vaults.asText()),
APPEARANCE(R.string.language.asText()),
VAULT(R.string.vault.asText()),
APPEARANCE(R.string.appearance.asText()),
OTHER(R.string.other.asText()),
ABOUT(R.string.about.asText()),
}

View file

@ -0,0 +1,26 @@
package com.x8bit.bitwarden.ui.platform.feature.settings.about
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions
import androidx.navigation.compose.composable
private const val ABOUT_ROUTE = "settings_about"
/**
* Add settings destinations to the nav graph.
*/
fun NavGraphBuilder.aboutDestination(
onNavigateBack: () -> Unit,
) {
composable(ABOUT_ROUTE) {
AboutScreen(onNavigateBack = onNavigateBack)
}
}
/**
* Navigate to the about screen.
*/
fun NavController.navigateToAbout(navOptions: NavOptions? = null) {
navigate(ABOUT_ROUTE, navOptions)
}

View file

@ -0,0 +1,67 @@
package com.x8bit.bitwarden.ui.platform.feature.settings.about
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
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.Scaffold
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
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.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.BitwardenTopAppBar
/**
* Displays the about screen.
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AboutScreen(
onNavigateBack: () -> Unit,
viewModel: AboutViewModel = hiltViewModel(),
) {
EventsEffect(viewModel = viewModel) { event ->
when (event) {
AboutEvent.NavigateBack -> onNavigateBack.invoke()
}
}
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
Scaffold(
modifier = Modifier
.fillMaxSize()
.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
BitwardenTopAppBar(
title = stringResource(id = R.string.about),
scrollBehavior = scrollBehavior,
navigationIcon = painterResource(id = R.drawable.ic_back),
navigationIconContentDescription = stringResource(id = R.string.back),
onNavigationIconClick = remember(viewModel) {
{ viewModel.trySendAction(AboutAction.BackClick) }
},
)
},
) { innerPadding ->
Column(
Modifier
.padding(innerPadding)
.fillMaxSize()
.background(color = MaterialTheme.colorScheme.surface)
.verticalScroll(rememberScrollState()),
) {
// TODO: BIT-931 Display About UI
}
}
}

View file

@ -0,0 +1,37 @@
package com.x8bit.bitwarden.ui.platform.feature.settings.about
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
/**
* View model for the about screen.
*/
@HiltViewModel
class AboutViewModel @Inject constructor() : BaseViewModel<Unit, AboutEvent, AboutAction>(
initialState = Unit,
) {
override fun handleAction(action: AboutAction): Unit = when (action) {
AboutAction.BackClick -> sendEvent(AboutEvent.NavigateBack)
}
}
/**
* Models events for the about screen.
*/
sealed class AboutEvent {
/**
* Navigate back.
*/
data object NavigateBack : AboutEvent()
}
/**
* Models actions for the about screen.
*/
sealed class AboutAction {
/**
* User clicked back button.
*/
data object BackClick : AboutAction()
}

View file

@ -1,11 +1,11 @@
package com.x8bit.bitwarden.ui.platform.feature.settings
package com.x8bit.bitwarden.ui.platform.feature.settings.accountsecurity
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions
import androidx.navigation.compose.composable
private const val ACCOUNT_SECURITY_ROUTE = "account_security"
private const val ACCOUNT_SECURITY_ROUTE = "settings_account_security"
/**
* Add settings destinations to the nav graph.

View file

@ -1,4 +1,4 @@
package com.x8bit.bitwarden.ui.platform.feature.settings
package com.x8bit.bitwarden.ui.platform.feature.settings.accountsecurity
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
@ -30,7 +30,6 @@ import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
import com.x8bit.bitwarden.ui.platform.base.util.Text
import com.x8bit.bitwarden.ui.platform.base.util.asText
import com.x8bit.bitwarden.ui.platform.components.BitwardenOverflowActionItem
import com.x8bit.bitwarden.ui.platform.components.BitwardenTopAppBar
/**
@ -54,16 +53,13 @@ fun AccountSecurityScreen(
.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
BitwardenTopAppBar(
title = stringResource(id = R.string.account),
title = stringResource(id = R.string.account_security),
scrollBehavior = scrollBehavior,
navigationIcon = painterResource(id = R.drawable.ic_back),
navigationIconContentDescription = stringResource(id = R.string.back),
onNavigationIconClick = remember(viewModel) {
{ viewModel.trySendAction(AccountSecurityAction.BackClick) }
},
actions = {
BitwardenOverflowActionItem()
},
)
},
) { innerPadding ->

View file

@ -1,4 +1,4 @@
package com.x8bit.bitwarden.ui.platform.feature.settings
package com.x8bit.bitwarden.ui.platform.feature.settings.accountsecurity
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel

View file

@ -0,0 +1,26 @@
package com.x8bit.bitwarden.ui.platform.feature.settings.appearance
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions
import androidx.navigation.compose.composable
private const val APPEARANCE_ROUTE = "settings_appearance"
/**
* Add settings destinations to the nav graph.
*/
fun NavGraphBuilder.appearanceDestination(
onNavigateBack: () -> Unit,
) {
composable(APPEARANCE_ROUTE) {
AppearanceScreen(onNavigateBack = onNavigateBack)
}
}
/**
* Navigate to the appearance screen.
*/
fun NavController.navigateToAppearance(navOptions: NavOptions? = null) {
navigate(APPEARANCE_ROUTE, navOptions)
}

View file

@ -0,0 +1,67 @@
package com.x8bit.bitwarden.ui.platform.feature.settings.appearance
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
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.Scaffold
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
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.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.BitwardenTopAppBar
/**
* Displays the appearance screen.
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AppearanceScreen(
onNavigateBack: () -> Unit,
viewModel: AppearanceViewModel = hiltViewModel(),
) {
EventsEffect(viewModel = viewModel) { event ->
when (event) {
AppearanceEvent.NavigateBack -> onNavigateBack.invoke()
}
}
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
Scaffold(
modifier = Modifier
.fillMaxSize()
.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
BitwardenTopAppBar(
title = stringResource(id = R.string.appearance),
scrollBehavior = scrollBehavior,
navigationIcon = painterResource(id = R.drawable.ic_back),
navigationIconContentDescription = stringResource(id = R.string.back),
onNavigationIconClick = remember(viewModel) {
{ viewModel.trySendAction(AppearanceAction.BackClick) }
},
)
},
) { innerPadding ->
Column(
Modifier
.padding(innerPadding)
.fillMaxSize()
.background(color = MaterialTheme.colorScheme.surface)
.verticalScroll(rememberScrollState()),
) {
// TODO: BIT-929 Display Appearance UI
}
}
}

View file

@ -0,0 +1,38 @@
package com.x8bit.bitwarden.ui.platform.feature.settings.appearance
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
/**
* View model for the appearance screen.
*/
@HiltViewModel
class AppearanceViewModel @Inject constructor() :
BaseViewModel<Unit, AppearanceEvent, AppearanceAction>(
initialState = Unit,
) {
override fun handleAction(action: AppearanceAction): Unit = when (action) {
AppearanceAction.BackClick -> sendEvent(AppearanceEvent.NavigateBack)
}
}
/**
* Models events for the appearance screen.
*/
sealed class AppearanceEvent {
/**
* Navigate back.
*/
data object NavigateBack : AppearanceEvent()
}
/**
* Models actions for the appearance screen.
*/
sealed class AppearanceAction {
/**
* User clicked back button.
*/
data object BackClick : AppearanceAction()
}

View file

@ -0,0 +1,26 @@
package com.x8bit.bitwarden.ui.platform.feature.settings.autofill
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions
import androidx.navigation.compose.composable
private const val AUTO_FILL_ROUTE = "settings_auto_fill"
/**
* Add settings destinations to the nav graph.
*/
fun NavGraphBuilder.autoFillDestination(
onNavigateBack: () -> Unit,
) {
composable(AUTO_FILL_ROUTE) {
AutoFillScreen(onNavigateBack = onNavigateBack)
}
}
/**
* Navigate to the auto-fill screen.
*/
fun NavController.navigateToAutoFill(navOptions: NavOptions? = null) {
navigate(AUTO_FILL_ROUTE, navOptions)
}

View file

@ -0,0 +1,66 @@
package com.x8bit.bitwarden.ui.platform.feature.settings.autofill
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
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.Scaffold
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
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.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.BitwardenTopAppBar
/**
* Displays the auto-fill screen.
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AutoFillScreen(
onNavigateBack: () -> Unit,
viewModel: AutoFillViewModel = hiltViewModel(),
) {
EventsEffect(viewModel = viewModel) { event ->
when (event) {
AutoFillEvent.NavigateBack -> onNavigateBack.invoke()
}
}
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
Scaffold(
modifier = Modifier
.fillMaxSize()
.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
BitwardenTopAppBar(
title = stringResource(id = R.string.autofill),
scrollBehavior = scrollBehavior,
navigationIcon = painterResource(id = R.drawable.ic_back),
navigationIconContentDescription = stringResource(id = R.string.back),
onNavigationIconClick = remember(viewModel) {
{ viewModel.trySendAction(AutoFillAction.BackClick) }
},
)
},
) { innerPadding ->
Column(
Modifier
.padding(innerPadding)
.fillMaxSize()
.background(color = MaterialTheme.colorScheme.surface)
.verticalScroll(rememberScrollState()),
) {
// TODO: BIT-927 Display auto-fill UI
}
}
}

View file

@ -0,0 +1,37 @@
package com.x8bit.bitwarden.ui.platform.feature.settings.autofill
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
/**
* View model for the auto-fill screen.
*/
@HiltViewModel
class AutoFillViewModel @Inject constructor() : BaseViewModel<Unit, AutoFillEvent, AutoFillAction>(
initialState = Unit,
) {
override fun handleAction(action: AutoFillAction): Unit = when (action) {
AutoFillAction.BackClick -> sendEvent(AutoFillEvent.NavigateBack)
}
}
/**
* Models events for the auto-fill screen.
*/
sealed class AutoFillEvent {
/**
* Navigate back.
*/
data object NavigateBack : AutoFillEvent()
}
/**
* Models actions for the auto-fill screen.
*/
sealed class AutoFillAction {
/**
* User clicked back button.
*/
data object BackClick : AutoFillAction()
}

View file

@ -0,0 +1,26 @@
package com.x8bit.bitwarden.ui.platform.feature.settings.other
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions
import androidx.navigation.compose.composable
private const val OTHER_ROUTE = "settings_other"
/**
* Add settings destinations to the nav graph.
*/
fun NavGraphBuilder.otherDestination(
onNavigateBack: () -> Unit,
) {
composable(OTHER_ROUTE) {
OtherScreen(onNavigateBack = onNavigateBack)
}
}
/**
* Navigate to the about screen.
*/
fun NavController.navigateToOther(navOptions: NavOptions? = null) {
navigate(OTHER_ROUTE, navOptions)
}

View file

@ -0,0 +1,67 @@
package com.x8bit.bitwarden.ui.platform.feature.settings.other
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
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.Scaffold
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
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.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.BitwardenTopAppBar
/**
* Displays the other screen.
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun OtherScreen(
onNavigateBack: () -> Unit,
viewModel: OtherViewModel = hiltViewModel(),
) {
EventsEffect(viewModel = viewModel) { event ->
when (event) {
OtherEvent.NavigateBack -> onNavigateBack.invoke()
}
}
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
Scaffold(
modifier = Modifier
.fillMaxSize()
.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
BitwardenTopAppBar(
title = stringResource(id = R.string.other),
scrollBehavior = scrollBehavior,
navigationIcon = painterResource(id = R.drawable.ic_back),
navigationIconContentDescription = stringResource(id = R.string.back),
onNavigationIconClick = remember(viewModel) {
{ viewModel.trySendAction(OtherAction.BackClick) }
},
)
},
) { innerPadding ->
Column(
Modifier
.padding(innerPadding)
.fillMaxSize()
.background(color = MaterialTheme.colorScheme.surface)
.verticalScroll(rememberScrollState()),
) {
// TODO: BIT-930 Display Other UI
}
}
}

View file

@ -0,0 +1,37 @@
package com.x8bit.bitwarden.ui.platform.feature.settings.other
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
/**
* View model for the other screen.
*/
@HiltViewModel
class OtherViewModel @Inject constructor() : BaseViewModel<Unit, OtherEvent, OtherAction>(
initialState = Unit,
) {
override fun handleAction(action: OtherAction): Unit = when (action) {
OtherAction.BackClick -> sendEvent(OtherEvent.NavigateBack)
}
}
/**
* Models events for the other screen.
*/
sealed class OtherEvent {
/**
* Navigate back.
*/
data object NavigateBack : OtherEvent()
}
/**
* Models actions for the other screen.
*/
sealed class OtherAction {
/**
* User clicked back button.
*/
data object BackClick : OtherAction()
}

View file

@ -0,0 +1,26 @@
package com.x8bit.bitwarden.ui.platform.feature.settings.vault
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions
import androidx.navigation.compose.composable
private const val VAULT_ROUTE = "settings_vault"
/**
* Add settings destinations to the nav graph.
*/
fun NavGraphBuilder.vaultDestination(
onNavigateBack: () -> Unit,
) {
composable(VAULT_ROUTE) {
VaultScreen(onNavigateBack = onNavigateBack)
}
}
/**
* Navigate to the vault screen.
*/
fun NavController.navigateToVault(navOptions: NavOptions? = null) {
navigate(VAULT_ROUTE, navOptions)
}

View file

@ -0,0 +1,67 @@
package com.x8bit.bitwarden.ui.platform.feature.settings.vault
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
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.Scaffold
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
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.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.BitwardenTopAppBar
/**
* Displays the vault screen.
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun VaultScreen(
onNavigateBack: () -> Unit,
viewModel: VaultViewModel = hiltViewModel(),
) {
EventsEffect(viewModel = viewModel) { event ->
when (event) {
VaultEvent.NavigateBack -> onNavigateBack.invoke()
}
}
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
Scaffold(
modifier = Modifier
.fillMaxSize()
.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
BitwardenTopAppBar(
title = stringResource(id = R.string.vault),
scrollBehavior = scrollBehavior,
navigationIcon = painterResource(id = R.drawable.ic_back),
navigationIconContentDescription = stringResource(id = R.string.back),
onNavigationIconClick = remember(viewModel) {
{ viewModel.trySendAction(VaultAction.BackClick) }
},
)
},
) { innerPadding ->
Column(
Modifier
.padding(innerPadding)
.fillMaxSize()
.background(color = MaterialTheme.colorScheme.surface)
.verticalScroll(rememberScrollState()),
) {
// TODO: BIT-928 Display Vault UI
}
}
}

View file

@ -0,0 +1,37 @@
package com.x8bit.bitwarden.ui.platform.feature.settings.vault
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
/**
* View model for the vault screen.
*/
@HiltViewModel
class VaultViewModel @Inject constructor() : BaseViewModel<Unit, VaultEvent, VaultAction>(
initialState = Unit,
) {
override fun handleAction(action: VaultAction): Unit = when (action) {
VaultAction.BackClick -> sendEvent(VaultEvent.NavigateBack)
}
}
/**
* Models events for the vault screen.
*/
sealed class VaultEvent {
/**
* Navigate back.
*/
data object NavigateBack : VaultEvent()
}
/**
* Models actions for the vault screen.
*/
sealed class VaultAction {
/**
* User clicked back button.
*/
data object BackClick : VaultAction()
}

View file

@ -8,8 +8,8 @@ import io.mockk.mockk
import io.mockk.verify
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flowOf
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.jupiter.api.Assertions.assertTrue
class SettingsScreenTest : BaseComposeTest() {
@ -22,7 +22,12 @@ class SettingsScreenTest : BaseComposeTest() {
composeTestRule.setContent {
SettingsScreen(
viewModel = viewModel,
onNavigateToAbout = { },
onNavigateToAccountSecurity = { },
onNavigateToAppearance = { },
onNavigateToAutoFill = { },
onNavigateToOther = { },
onNavigateToVault = { },
)
}
composeTestRule.onNodeWithText("About").performClick()
@ -40,10 +45,15 @@ class SettingsScreenTest : BaseComposeTest() {
composeTestRule.setContent {
SettingsScreen(
viewModel = viewModel,
onNavigateToAbout = { },
onNavigateToAccountSecurity = { },
onNavigateToAppearance = { },
onNavigateToAutoFill = { },
onNavigateToOther = { },
onNavigateToVault = { },
)
}
composeTestRule.onNodeWithText("Security").performClick()
composeTestRule.onNodeWithText("Account security").performClick()
verify { viewModel.trySendAction(SettingsAction.SettingsClick(Settings.ACCOUNT_SECURITY)) }
}
@ -56,10 +66,15 @@ class SettingsScreenTest : BaseComposeTest() {
composeTestRule.setContent {
SettingsScreen(
viewModel = viewModel,
onNavigateToAbout = { },
onNavigateToAccountSecurity = { },
onNavigateToAppearance = { },
onNavigateToAutoFill = { },
onNavigateToOther = { },
onNavigateToVault = { },
)
}
composeTestRule.onNodeWithText("Language").performClick()
composeTestRule.onNodeWithText("Appearance").performClick()
verify { viewModel.trySendAction(SettingsAction.SettingsClick(Settings.APPEARANCE)) }
}
@ -72,7 +87,12 @@ class SettingsScreenTest : BaseComposeTest() {
composeTestRule.setContent {
SettingsScreen(
viewModel = viewModel,
onNavigateToAbout = { },
onNavigateToAccountSecurity = { },
onNavigateToAppearance = { },
onNavigateToAutoFill = { },
onNavigateToOther = { },
onNavigateToVault = { },
)
}
composeTestRule.onNodeWithText("Auto-fill").performClick()
@ -88,7 +108,12 @@ class SettingsScreenTest : BaseComposeTest() {
composeTestRule.setContent {
SettingsScreen(
viewModel = viewModel,
onNavigateToAbout = { },
onNavigateToAccountSecurity = { },
onNavigateToAppearance = { },
onNavigateToAutoFill = { },
onNavigateToOther = { },
onNavigateToVault = { },
)
}
composeTestRule.onNodeWithText("Other").performClick()
@ -104,13 +129,40 @@ class SettingsScreenTest : BaseComposeTest() {
composeTestRule.setContent {
SettingsScreen(
viewModel = viewModel,
onNavigateToAbout = { },
onNavigateToAccountSecurity = { },
onNavigateToAppearance = { },
onNavigateToAutoFill = { },
onNavigateToOther = { },
onNavigateToVault = { },
)
}
composeTestRule.onNodeWithText("Vaults").performClick()
composeTestRule.onNodeWithText("Vault").performClick()
verify { viewModel.trySendAction(SettingsAction.SettingsClick(Settings.VAULT)) }
}
@Test
fun `on NavigateAbout should call onNavigateToAbout`() {
var haveCalledNavigateToAbout = false
val viewModel = mockk<SettingsViewModel> {
every { eventFlow } returns flowOf(SettingsEvent.NavigateAbout)
}
composeTestRule.setContent {
SettingsScreen(
viewModel = viewModel,
onNavigateToAbout = {
haveCalledNavigateToAbout = true
},
onNavigateToAccountSecurity = { },
onNavigateToAppearance = { },
onNavigateToAutoFill = { },
onNavigateToOther = { },
onNavigateToVault = { },
)
}
assertTrue(haveCalledNavigateToAbout)
}
@Test
fun `on NavigateAccountSecurity should call onNavigateToAccountSecurity`() {
var haveCalledNavigateToAccountSecurity = false
@ -120,11 +172,102 @@ class SettingsScreenTest : BaseComposeTest() {
composeTestRule.setContent {
SettingsScreen(
viewModel = viewModel,
onNavigateToAbout = { },
onNavigateToAccountSecurity = {
haveCalledNavigateToAccountSecurity = true
},
onNavigateToAppearance = { },
onNavigateToAutoFill = { },
onNavigateToOther = { },
onNavigateToVault = { },
)
}
assertTrue(haveCalledNavigateToAccountSecurity)
}
@Test
fun `on NavigateAccountSecurity should call NavigateAppearance`() {
var haveCalledNavigateToAppearance = false
val viewModel = mockk<SettingsViewModel> {
every { eventFlow } returns flowOf(SettingsEvent.NavigateAppearance)
}
composeTestRule.setContent {
SettingsScreen(
viewModel = viewModel,
onNavigateToAbout = { },
onNavigateToAccountSecurity = { },
onNavigateToAppearance = { haveCalledNavigateToAppearance = true },
onNavigateToAutoFill = { },
onNavigateToOther = { },
onNavigateToVault = { },
)
}
assertTrue(haveCalledNavigateToAppearance)
}
@Test
fun `on NavigateAccountSecurity should call onNavigateToAutoFill`() {
var haveCalledNavigateToAutoFill = false
val viewModel = mockk<SettingsViewModel> {
every { eventFlow } returns flowOf(SettingsEvent.NavigateAutoFill)
}
composeTestRule.setContent {
SettingsScreen(
viewModel = viewModel,
onNavigateToAbout = { },
onNavigateToAccountSecurity = { },
onNavigateToAppearance = { },
onNavigateToAutoFill = {
haveCalledNavigateToAutoFill = true
},
onNavigateToOther = { },
onNavigateToVault = { },
)
}
assertTrue(haveCalledNavigateToAutoFill)
}
@Test
fun `on NavigateAccountSecurity should call onNavigateToOther`() {
var haveCalledNavigateToOther = false
val viewModel = mockk<SettingsViewModel> {
every { eventFlow } returns flowOf(SettingsEvent.NavigateOther)
}
composeTestRule.setContent {
SettingsScreen(
viewModel = viewModel,
onNavigateToAbout = { },
onNavigateToAccountSecurity = { },
onNavigateToAppearance = { },
onNavigateToAutoFill = { },
onNavigateToOther = {
haveCalledNavigateToOther = true
},
onNavigateToVault = { },
)
}
assertTrue(haveCalledNavigateToOther)
}
@Test
fun `on NavigateAccountSecurity should call NavigateVault`() {
var haveCalledNavigateToVault = false
val viewModel = mockk<SettingsViewModel> {
every { eventFlow } returns flowOf(SettingsEvent.NavigateVault)
}
composeTestRule.setContent {
SettingsScreen(
viewModel = viewModel,
onNavigateToAbout = { },
onNavigateToAccountSecurity = { },
onNavigateToAppearance = { },
onNavigateToAutoFill = { },
onNavigateToOther = { },
onNavigateToVault = {
haveCalledNavigateToVault = true
},
)
}
assertTrue(haveCalledNavigateToVault)
}
}

View file

@ -9,11 +9,11 @@ import org.junit.jupiter.api.Test
class SettingsViewModelTest : BaseViewModelTest() {
@Test
fun `on SettingsClick with ABOUT should emit nothing`() = runTest {
fun `on SettingsClick with ABOUT should emit NavigateAbout`() = runTest {
val viewModel = SettingsViewModel()
viewModel.eventFlow.test {
viewModel.trySendAction(SettingsAction.SettingsClick(Settings.ABOUT))
expectNoEvents()
assertEquals(SettingsEvent.NavigateAbout, awaitItem())
}
}
@ -27,38 +27,38 @@ class SettingsViewModelTest : BaseViewModelTest() {
}
@Test
fun `on SettingsClick with APPEARANCE should emit nothing`() = runTest {
fun `on SettingsClick with APPEARANCE should emit NavigateAppearance`() = runTest {
val viewModel = SettingsViewModel()
viewModel.eventFlow.test {
viewModel.trySendAction(SettingsAction.SettingsClick(Settings.APPEARANCE))
expectNoEvents()
assertEquals(SettingsEvent.NavigateAppearance, awaitItem())
}
}
@Test
fun `on SettingsClick with AUTO_FILL should emit nothing`() = runTest {
fun `on SettingsClick with AUTO_FILL should emit NavigateAutoFill`() = runTest {
val viewModel = SettingsViewModel()
viewModel.eventFlow.test {
viewModel.trySendAction(SettingsAction.SettingsClick(Settings.AUTO_FILL))
expectNoEvents()
assertEquals(SettingsEvent.NavigateAutoFill, awaitItem())
}
}
@Test
fun `on SettingsClick with OTHER should emit nothing`() = runTest {
fun `on SettingsClick with OTHER should emit NavigateOther`() = runTest {
val viewModel = SettingsViewModel()
viewModel.eventFlow.test {
viewModel.trySendAction(SettingsAction.SettingsClick(Settings.OTHER))
expectNoEvents()
assertEquals(SettingsEvent.NavigateOther, awaitItem())
}
}
@Test
fun `on SettingsClick with VAULT should emit nothing`() = runTest {
fun `on SettingsClick with VAULT should emit NavigateVault`() = runTest {
val viewModel = SettingsViewModel()
viewModel.eventFlow.test {
viewModel.trySendAction(SettingsAction.SettingsClick(Settings.VAULT))
expectNoEvents()
assertEquals(SettingsEvent.NavigateVault, awaitItem())
}
}
}

View file

@ -0,0 +1,46 @@
package com.x8bit.bitwarden.ui.platform.feature.settings.about
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.performClick
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 org.junit.Assert.assertTrue
import org.junit.Test
class AboutScreenTest : BaseComposeTest() {
@Test
fun `on back click should send BackClick`() {
val viewModel: AboutViewModel = mockk {
every { eventFlow } returns emptyFlow()
every { trySendAction(AboutAction.BackClick) } returns Unit
}
composeTestRule.setContent {
AboutScreen(
viewModel = viewModel,
onNavigateBack = { },
)
}
composeTestRule.onNodeWithContentDescription("Back").performClick()
verify { viewModel.trySendAction(AboutAction.BackClick) }
}
@Test
fun `on NavigateAbout should call onNavigateToAbout`() {
var haveCalledNavigateBack = false
val viewModel = mockk<AboutViewModel> {
every { eventFlow } returns flowOf(AboutEvent.NavigateBack)
}
composeTestRule.setContent {
AboutScreen(
viewModel = viewModel,
onNavigateBack = { haveCalledNavigateBack = true },
)
}
assertTrue(haveCalledNavigateBack)
}
}

View file

@ -0,0 +1,19 @@
package com.x8bit.bitwarden.ui.platform.feature.settings.about
import app.cash.turbine.test
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
class AboutViewModelTest : BaseViewModelTest() {
@Test
fun `on BackClick should emit NavigateBack`() = runTest {
val viewModel = AboutViewModel()
viewModel.eventFlow.test {
viewModel.trySendAction(AboutAction.BackClick)
assertEquals(AboutEvent.NavigateBack, awaitItem())
}
}
}

View file

@ -1,4 +1,4 @@
package com.x8bit.bitwarden.ui.platform.feature.settings
package com.x8bit.bitwarden.ui.platform.feature.settings.accountsecurity
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText
@ -9,8 +9,8 @@ import io.mockk.mockk
import io.mockk.verify
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flowOf
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.jupiter.api.Assertions.assertTrue
class AccountSecurityScreenTest : BaseComposeTest() {

View file

@ -1,4 +1,4 @@
package com.x8bit.bitwarden.ui.platform.feature.settings
package com.x8bit.bitwarden.ui.platform.feature.settings.accountsecurity
import app.cash.turbine.test
import com.x8bit.bitwarden.data.auth.repository.AuthRepository

View file

@ -0,0 +1,46 @@
package com.x8bit.bitwarden.ui.platform.feature.settings.appearance
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.performClick
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 org.junit.Assert.assertTrue
import org.junit.Test
class AppearanceScreenTest : BaseComposeTest() {
@Test
fun `on back click should send BackClick`() {
val viewModel: AppearanceViewModel = mockk {
every { eventFlow } returns emptyFlow()
every { trySendAction(AppearanceAction.BackClick) } returns Unit
}
composeTestRule.setContent {
AppearanceScreen(
viewModel = viewModel,
onNavigateBack = { },
)
}
composeTestRule.onNodeWithContentDescription("Back").performClick()
verify { viewModel.trySendAction(AppearanceAction.BackClick) }
}
@Test
fun `on NavigateAbout should call onNavigateToAbout`() {
var haveCalledNavigateBack = false
val viewModel = mockk<AppearanceViewModel> {
every { eventFlow } returns flowOf(AppearanceEvent.NavigateBack)
}
composeTestRule.setContent {
AppearanceScreen(
viewModel = viewModel,
onNavigateBack = { haveCalledNavigateBack = true },
)
}
assertTrue(haveCalledNavigateBack)
}
}

View file

@ -0,0 +1,19 @@
package com.x8bit.bitwarden.ui.platform.feature.settings.appearance
import app.cash.turbine.test
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
class AppearanceViewModelTest : BaseViewModelTest() {
@Test
fun `on BackClick should emit NavigateBack`() = runTest {
val viewModel = AppearanceViewModel()
viewModel.eventFlow.test {
viewModel.trySendAction(AppearanceAction.BackClick)
assertEquals(AppearanceEvent.NavigateBack, awaitItem())
}
}
}

View file

@ -0,0 +1,46 @@
package com.x8bit.bitwarden.ui.platform.feature.settings.autofill
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.performClick
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 org.junit.Assert.assertTrue
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
}
composeTestRule.setContent {
AutoFillScreen(
viewModel = viewModel,
onNavigateBack = { },
)
}
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)
}
}

View file

@ -0,0 +1,19 @@
package com.x8bit.bitwarden.ui.platform.feature.settings.autofill
import app.cash.turbine.test
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
class AutoFillViewModelTest : BaseViewModelTest() {
@Test
fun `on BackClick should emit NavigateBack`() = runTest {
val viewModel = AutoFillViewModel()
viewModel.eventFlow.test {
viewModel.trySendAction(AutoFillAction.BackClick)
assertEquals(AutoFillEvent.NavigateBack, awaitItem())
}
}
}

View file

@ -0,0 +1,46 @@
package com.x8bit.bitwarden.ui.platform.feature.settings.other
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.performClick
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 org.junit.Assert.assertTrue
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) }
}
@Test
fun `on NavigateOther should call onNavigateToOther`() {
var haveCalledNavigateBack = false
val viewModel = mockk<OtherViewModel> {
every { eventFlow } returns flowOf(OtherEvent.NavigateBack)
}
composeTestRule.setContent {
OtherScreen(
viewModel = viewModel,
onNavigateBack = { haveCalledNavigateBack = true },
)
}
assertTrue(haveCalledNavigateBack)
}
}

View file

@ -0,0 +1,19 @@
package com.x8bit.bitwarden.ui.platform.feature.settings.other
import app.cash.turbine.test
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
class OtherViewModelTest : BaseViewModelTest() {
@Test
fun `on BackClick should emit NavigateBack`() = runTest {
val viewModel = OtherViewModel()
viewModel.eventFlow.test {
viewModel.trySendAction(OtherAction.BackClick)
assertEquals(OtherEvent.NavigateBack, awaitItem())
}
}
}

View file

@ -0,0 +1,46 @@
package com.x8bit.bitwarden.ui.platform.feature.settings.vault
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.performClick
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 org.junit.Assert.assertTrue
import org.junit.Test
class VaultScreenTest : BaseComposeTest() {
@Test
fun `on back click should send BackClick`() {
val viewModel: VaultViewModel = mockk {
every { eventFlow } returns emptyFlow()
every { trySendAction(VaultAction.BackClick) } returns Unit
}
composeTestRule.setContent {
VaultScreen(
viewModel = viewModel,
onNavigateBack = { },
)
}
composeTestRule.onNodeWithContentDescription("Back").performClick()
verify { viewModel.trySendAction(VaultAction.BackClick) }
}
@Test
fun `on NavigateAbout should call onNavigateToVault`() {
var haveCalledNavigateBack = false
val viewModel = mockk<VaultViewModel> {
every { eventFlow } returns flowOf(VaultEvent.NavigateBack)
}
composeTestRule.setContent {
VaultScreen(
viewModel = viewModel,
onNavigateBack = { haveCalledNavigateBack = true },
)
}
assertTrue(haveCalledNavigateBack)
}
}

View file

@ -0,0 +1,19 @@
package com.x8bit.bitwarden.ui.platform.feature.settings.vault
import app.cash.turbine.test
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
class VaultViewModelTest : BaseViewModelTest() {
@Test
fun `on BackClick should emit NavigateBack`() = runTest {
val viewModel = VaultViewModel()
viewModel.eventFlow.test {
viewModel.trySendAction(VaultAction.BackClick)
assertEquals(VaultEvent.NavigateBack, awaitItem())
}
}
}