diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavScreen.kt index 5c0859e749..35c1556710 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavScreen.kt @@ -1,3 +1,6 @@ +// TODO: Add tests for this (PM-21252) +@file:OmitFromCoverage + package com.x8bit.bitwarden.ui.platform.feature.rootnav import androidx.activity.compose.LocalActivity @@ -13,6 +16,7 @@ import androidx.navigation.NavDestination import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.navOptions +import com.bitwarden.core.annotation.OmitFromCoverage import com.x8bit.bitwarden.ui.auth.feature.accountsetup.SETUP_AUTO_FILL_AS_ROOT_ROUTE import com.x8bit.bitwarden.ui.auth.feature.accountsetup.SETUP_COMPLETE_ROUTE import com.x8bit.bitwarden.ui.auth.feature.accountsetup.SETUP_UNLOCK_AS_ROOT_ROUTE diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/vaultunlockednavbar/VaultUnlockedNavBarScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/vaultunlockednavbar/VaultUnlockedNavBarScreen.kt index b27a23eb59..2ab0960618 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/vaultunlockednavbar/VaultUnlockedNavBarScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/vaultunlockednavbar/VaultUnlockedNavBarScreen.kt @@ -1,3 +1,6 @@ +// TODO: Add tests for this (PM-21252) +@file:OmitFromCoverage + package com.x8bit.bitwarden.ui.platform.feature.vaultunlockednavbar import androidx.compose.foundation.layout.WindowInsets @@ -19,6 +22,7 @@ import androidx.navigation.NavOptions import androidx.navigation.compose.NavHost import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.navOptions +import com.bitwarden.core.annotation.OmitFromCoverage import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect import com.x8bit.bitwarden.ui.platform.components.model.NavigationItem import com.x8bit.bitwarden.ui.platform.components.model.ScaffoldNavigationData diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavScreenTest.kt deleted file mode 100644 index 44fc4cf403..0000000000 --- a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/rootnav/RootNavScreenTest.kt +++ /dev/null @@ -1,259 +0,0 @@ -package com.x8bit.bitwarden.ui.platform.feature.rootnav - -import androidx.navigation.navOptions -import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData -import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest -import com.x8bit.bitwarden.ui.platform.base.FakeNavHostController -import io.mockk.every -import io.mockk.mockk -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.emptyFlow -import kotlinx.coroutines.test.runTest -import org.junit.Assert.assertFalse -import org.junit.Assert.assertTrue -import org.junit.Before -import org.junit.Test -import java.time.Clock -import java.time.Instant -import java.time.ZoneOffset - -class RootNavScreenTest : BaseComposeTest() { - private val fakeNavHostController = FakeNavHostController() - private val rootNavStateFlow = MutableStateFlow(RootNavState.Splash) - private val viewModel = mockk { - every { eventFlow } returns emptyFlow() - every { stateFlow } returns rootNavStateFlow - } - - private val expectedNavOptions = navOptions { - // When changing root navigation state, pop everything else off the back stack: - popUpTo(fakeNavHostController.graphId) { - inclusive = false - saveState = false - } - launchSingleTop = true - restoreState = false - } - - private var isSplashScreenRemoved: Boolean = false - - @Before - fun setup() { - setContent { - RootNavScreen( - viewModel = viewModel, - navController = fakeNavHostController, - onSplashScreenRemoved = { isSplashScreenRemoved = true }, - ) - } - } - - @Test - fun `initial route should be splash`() { - composeTestRule.runOnIdle { - fakeNavHostController.assertCurrentRoute("splash") - } - } - - @Test - fun `when root nav destination changes, navigation should follow`() = runTest { - composeTestRule.runOnIdle { - fakeNavHostController.assertCurrentRoute("splash") - } - assertFalse(isSplashScreenRemoved) - - // Make sure navigating to Auth works as expected: - rootNavStateFlow.value = RootNavState.Auth - composeTestRule.runOnIdle { - fakeNavHostController.assertLastNavigation( - route = "auth_graph", - navOptions = expectedNavOptions, - ) - } - assertTrue(isSplashScreenRemoved) - - // Make sure navigating to Auth with the welcome route works as expected: - rootNavStateFlow.value = RootNavState.AuthWithWelcome - composeTestRule.runOnIdle { - fakeNavHostController.assertLastNavigation( - route = "welcome", - navOptions = expectedNavOptions, - ) - } - - // Make sure navigating to complete registration route works as expected: - rootNavStateFlow.value = RootNavState.CompleteOngoingRegistration( - email = "example@email.com", - verificationToken = "verificationToken", - fromEmail = true, - timestamp = FIXED_CLOCK.millis(), - ) - composeTestRule.runOnIdle { - fakeNavHostController.assertLastNavigation( - route = "complete_registration/example@email.com/verificationToken/true", - ) - } - - // Make sure navigating to expired registration link route works as expected: - rootNavStateFlow.value = RootNavState.ExpiredRegistrationLink - composeTestRule.runOnIdle { - fakeNavHostController.assertLastNavigation( - route = "expired_registration_link", - ) - } - - // Make sure navigating to vault locked works as expected: - rootNavStateFlow.value = RootNavState.VaultLocked - composeTestRule.runOnIdle { - fakeNavHostController.assertLastNavigation( - route = "vault_unlock/STANDARD", - navOptions = expectedNavOptions, - ) - } - - // Make sure navigating to reset password works as expected: - rootNavStateFlow.value = RootNavState.ResetPassword - composeTestRule.runOnIdle { - fakeNavHostController.assertLastNavigation( - route = "reset_password", - navOptions = expectedNavOptions, - ) - } - - // Make sure navigating to set password works as expected: - rootNavStateFlow.value = RootNavState.SetPassword - composeTestRule.runOnIdle { - fakeNavHostController.assertLastNavigation( - route = "set_password", - navOptions = expectedNavOptions, - ) - } - - // Make sure navigating to set password works as expected: - rootNavStateFlow.value = RootNavState.TrustedDevice - composeTestRule.runOnIdle { - fakeNavHostController.assertLastNavigation( - route = "trusted_device_graph", - navOptions = expectedNavOptions, - ) - } - - // Make sure navigating to vault unlocked works as expected: - rootNavStateFlow.value = RootNavState.VaultUnlocked(activeUserId = "userId") - composeTestRule.runOnIdle { - fakeNavHostController.assertLastNavigation( - route = "vault_unlocked_graph", - navOptions = expectedNavOptions, - ) - } - - // Make sure navigating to vault unlocked for new totp works as expected: - rootNavStateFlow.value = RootNavState.VaultUnlockedForNewTotp(activeUserId = "userId") - composeTestRule.runOnIdle { - fakeNavHostController.assertLastNavigation( - route = "vault_item_listing_as_root/login", - navOptions = expectedNavOptions, - ) - } - - // Make sure navigating to vault unlocked for new sends works as expected: - rootNavStateFlow.value = RootNavState.VaultUnlockedForNewSend - composeTestRule.runOnIdle { - fakeNavHostController.assertLastNavigation( - route = "add_send_item/add", - navOptions = expectedNavOptions, - ) - } - - // Make sure navigating to vault unlocked for autofill save works as expected: - rootNavStateFlow.value = RootNavState.VaultUnlockedForAutofillSave( - autofillSaveItem = mockk(), - ) - composeTestRule.runOnIdle { - fakeNavHostController.assertLastNavigation( - route = "vault_add_edit_item/add", - navOptions = expectedNavOptions, - ) - } - - // Make sure navigating to vault unlocked for autofill works as expected: - rootNavStateFlow.value = RootNavState.VaultUnlockedForAutofillSelection( - activeUserId = "userId", - type = AutofillSelectionData.Type.LOGIN, - ) - composeTestRule.runOnIdle { - fakeNavHostController.assertLastNavigation( - route = "vault_item_listing_as_root/login", - navOptions = expectedNavOptions, - ) - } - - // Make sure navigating to vault unlocked for Fido2Save works as expected: - rootNavStateFlow.value = RootNavState.VaultUnlockedForFido2Save( - activeUserId = "activeUserId", - fido2CreateCredentialRequest = mockk(), - ) - composeTestRule.runOnIdle { - fakeNavHostController.assertLastNavigation( - route = "vault_item_listing_as_root/login", - navOptions = expectedNavOptions, - ) - } - - // Make sure navigating to vault unlocked for Fido2Assertion works as expected: - rootNavStateFlow.value = RootNavState.VaultUnlockedForFido2Assertion( - activeUserId = "activeUserId", - fido2CredentialAssertionRequest = mockk(), - ) - composeTestRule.runOnIdle { - fakeNavHostController.assertLastNavigation( - route = "vault_item_listing_as_root/login", - navOptions = expectedNavOptions, - ) - } - - // Make sure navigating to vault unlocked for Fido2GetCredentials works as expected: - rootNavStateFlow.value = RootNavState.VaultUnlockedForFido2GetCredentials( - activeUserId = "activeUserId", - fido2GetCredentialsRequest = mockk(), - ) - composeTestRule.runOnIdle { - fakeNavHostController.assertLastNavigation( - route = "vault_item_listing_as_root/login", - navOptions = expectedNavOptions, - ) - } - - // Make sure navigating to account lock setup works as expected: - rootNavStateFlow.value = RootNavState.OnboardingAccountLockSetup - composeTestRule.runOnIdle { - fakeNavHostController.assertLastNavigation( - route = "setup_unlock_as_root/true", - navOptions = expectedNavOptions, - ) - } - - // Make sure navigating to account autofill setup works as expected: - rootNavStateFlow.value = RootNavState.OnboardingAutoFillSetup - composeTestRule.runOnIdle { - fakeNavHostController.assertLastNavigation( - route = "setup_auto_fill_as_root/true", - navOptions = expectedNavOptions, - ) - } - - // Make sure navigating to account setup complete works as expected: - rootNavStateFlow.value = RootNavState.OnboardingStepsComplete - composeTestRule.runOnIdle { - fakeNavHostController.assertLastNavigation( - route = "setup_complete", - navOptions = expectedNavOptions, - ) - } - } -} - -private val FIXED_CLOCK: Clock = Clock.fixed( - Instant.parse("2023-10-27T12:00:00Z"), - ZoneOffset.UTC, -) diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/vaultunlockednavbar/VaultUnlockedNavBarScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/vaultunlockednavbar/VaultUnlockedNavBarScreenTest.kt deleted file mode 100644 index a51e4f61a2..0000000000 --- a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/vaultunlockednavbar/VaultUnlockedNavBarScreenTest.kt +++ /dev/null @@ -1,250 +0,0 @@ -package com.x8bit.bitwarden.ui.platform.feature.vaultunlockednavbar - -import androidx.compose.ui.test.onNodeWithText -import androidx.compose.ui.test.performClick -import androidx.navigation.navOptions -import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow -import com.x8bit.bitwarden.R -import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest -import com.x8bit.bitwarden.ui.platform.base.FakeNavHostController -import io.mockk.every -import io.mockk.mockk -import io.mockk.verify -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.update -import org.junit.Before -import org.junit.Test - -class VaultUnlockedNavBarScreenTest : BaseComposeTest() { - private val fakeNavHostController = FakeNavHostController() - private val mutableEventFlow = bufferedMutableSharedFlow() - private val mutableStateFlow = MutableStateFlow(DEFAULT_STATE) - val viewModel = mockk(relaxed = true) { - every { eventFlow } returns mutableEventFlow - every { stateFlow } returns mutableStateFlow - } - - private val expectedNavOptions = navOptions { - // When changing root navigation state, pop everything else off the back stack: - popUpTo(fakeNavHostController.graphId) { - inclusive = false - saveState = true - } - launchSingleTop = true - restoreState = true - } - - @Before - fun setup() { - composeTestRule.apply { - setContent { - VaultUnlockedNavBarScreen( - viewModel = viewModel, - navController = fakeNavHostController, - onNavigateToVaultAddItem = {}, - onNavigateToVaultItem = {}, - onNavigateToVaultEditItem = {}, - onNavigateToAddSend = {}, - onNavigateToEditSend = {}, - onNavigateToDeleteAccount = {}, - onNavigateToExportVault = {}, - onNavigateToFolders = {}, - onNavigateToPasswordHistory = {}, - onNavigateToPendingRequests = {}, - onNavigateToSearchVault = {}, - onNavigateToSearchSend = {}, - onNavigateToSetupAutoFillScreen = {}, - onNavigateToSetupUnlockScreen = {}, - onNavigateToImportLogins = {}, - onNavigateToAddFolderScreen = {}, - onNavigateToFlightRecorder = {}, - onNavigateToRecordedLogs = {}, - ) - } - } - } - - @Test - fun `vault tab click should send VaultTabClick action`() { - composeTestRule.onNodeWithText("My vault").performClick() - verify { viewModel.trySendAction(VaultUnlockedNavBarAction.VaultTabClick) } - } - - @Test - fun `NavigateToVaultScreen should navigate to VaultScreen`() { - mutableEventFlow.tryEmit(VaultUnlockedNavBarEvent.NavigateToSendScreen) - composeTestRule.runOnIdle { fakeNavHostController.assertCurrentRoute("send_graph") } - mutableEventFlow.tryEmit( - VaultUnlockedNavBarEvent.NavigateToVaultScreen( - labelRes = R.string.my_vault, - contentDescRes = R.string.my_vault, - ), - ) - composeTestRule.runOnIdle { - fakeNavHostController.assertLastNavigation( - route = "vault_graph", - navOptions = expectedNavOptions, - ) - } - } - - @Test - fun `NavigateToVaultScreen shortcut event should navigate to VaultScreen`() { - mutableEventFlow.tryEmit(VaultUnlockedNavBarEvent.NavigateToSendScreen) - composeTestRule.runOnIdle { fakeNavHostController.assertCurrentRoute("send_graph") } - mutableEventFlow.tryEmit( - VaultUnlockedNavBarEvent.Shortcut.NavigateToVaultScreen( - labelRes = R.string.my_vault, - contentDescRes = R.string.my_vault, - ), - ) - composeTestRule.runOnIdle { - fakeNavHostController.assertLastNavigation( - route = "vault_graph", - navOptions = expectedNavOptions, - ) - } - } - - @Test - fun `NavigateToSettingsScreen shortcut event should navigate to SettingsScreen`() { - mutableEventFlow.tryEmit(VaultUnlockedNavBarEvent.NavigateToSendScreen) - composeTestRule.runOnIdle { fakeNavHostController.assertCurrentRoute("send_graph") } - mutableEventFlow.tryEmit( - VaultUnlockedNavBarEvent.Shortcut.NavigateToSettingsScreen, - ) - composeTestRule.runOnIdle { - fakeNavHostController.assertLastNavigation( - route = "settings_graph", - navOptions = expectedNavOptions, - ) - } - } - - @Test - fun `send tab click should send SendTabClick action`() { - composeTestRule.onNodeWithText("Send").performClick() - verify { viewModel.trySendAction(VaultUnlockedNavBarAction.SendTabClick) } - } - - @Test - fun `NavigateToSendScreen should navigate to SendScreen`() { - composeTestRule.apply { - runOnIdle { fakeNavHostController.assertCurrentRoute("vault_graph") } - mutableEventFlow.tryEmit(VaultUnlockedNavBarEvent.NavigateToSendScreen) - runOnIdle { - fakeNavHostController.assertLastNavigation( - route = "send_graph", - navOptions = expectedNavOptions, - ) - } - } - } - - @Test - fun `generator tab click should send GeneratorTabClick action`() { - composeTestRule.onNodeWithText("Generator").performClick() - verify { viewModel.trySendAction(VaultUnlockedNavBarAction.GeneratorTabClick) } - } - - @Test - fun `NavigateToGeneratorScreen should navigate to GeneratorScreen`() { - composeTestRule.apply { - runOnIdle { fakeNavHostController.assertCurrentRoute("vault_graph") } - mutableEventFlow.tryEmit(VaultUnlockedNavBarEvent.NavigateToGeneratorScreen) - runOnIdle { - fakeNavHostController.assertLastNavigation( - route = "generator_graph", - navOptions = expectedNavOptions, - ) - } - } - } - - @Test - fun `NavigateToGeneratorScreen shortcut event should navigate to GeneratorScreen`() { - composeTestRule.apply { - runOnIdle { fakeNavHostController.assertCurrentRoute("vault_graph") } - mutableEventFlow.tryEmit(VaultUnlockedNavBarEvent.Shortcut.NavigateToGeneratorScreen) - runOnIdle { - fakeNavHostController.assertLastNavigation( - route = "generator_graph", - navOptions = expectedNavOptions, - ) - } - } - } - - @Test - fun `settings tab click should send SendTabClick action`() { - composeTestRule.onNodeWithText("Settings").performClick() - verify { viewModel.trySendAction(VaultUnlockedNavBarAction.SettingsTabClick) } - } - - @Test - fun `NavigateToSettingsScreen should navigate to SettingsScreen`() { - composeTestRule.apply { - runOnIdle { fakeNavHostController.assertCurrentRoute("vault_graph") } - mutableEventFlow.tryEmit(VaultUnlockedNavBarEvent.NavigateToSettingsScreen) - runOnIdle { - fakeNavHostController.assertLastNavigation( - route = "settings_graph", - navOptions = expectedNavOptions, - ) - } - } - } - - @Test - fun `vault nav bar should update according to state`() { - composeTestRule.onNodeWithText("My vault").assertExists() - composeTestRule.onNodeWithText("Vaults").assertDoesNotExist() - - mutableStateFlow.tryEmit( - VaultUnlockedNavBarState( - vaultNavBarLabelRes = R.string.vaults, - vaultNavBarContentDescriptionRes = R.string.vaults, - notificationState = VaultUnlockedNavBarNotificationState( - settingsTabNotificationCount = 0, - ), - ), - ) - - composeTestRule.onNodeWithText("My vault").assertDoesNotExist() - composeTestRule.onNodeWithText("Vaults").assertExists() - } - - @Suppress("MaxLineLength") - @Test - fun `settings tab notification count should update according to state and show correct count`() { - mutableStateFlow.update { - it.copy( - notificationState = VaultUnlockedNavBarNotificationState( - settingsTabNotificationCount = 1, - ), - ) - } - composeTestRule - .onNodeWithText("1", useUnmergedTree = true) - .assertExists() - - mutableStateFlow.update { - it.copy( - notificationState = VaultUnlockedNavBarNotificationState( - settingsTabNotificationCount = 0, - ), - ) - } - composeTestRule - .onNodeWithText("1", useUnmergedTree = true) - .assertDoesNotExist() - } -} - -private val DEFAULT_STATE = VaultUnlockedNavBarState( - vaultNavBarLabelRes = R.string.my_vault, - vaultNavBarContentDescriptionRes = R.string.my_vault, - notificationState = VaultUnlockedNavBarNotificationState( - settingsTabNotificationCount = 0, - ), -) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 366cac4e16..47e33b63fd 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -18,7 +18,7 @@ androidxCore = "1.16.0" androidxCredentials = "1.5.0" androidxHiltNavigationCompose = "1.2.0" androidxLifecycle = "2.8.7" -androidxNavigation = "2.8.0" +androidxNavigation = "2.8.9" androidxRoom = "2.7.1" androidXSecurityCrypto = "1.1.0-alpha06" androidxSplash = "1.1.0-rc01"