diff options
| author | David Göransson <david.goransson@mullvad.net> | 2025-03-31 09:36:52 +0200 |
|---|---|---|
| committer | Jonatan Rhodin <jonatan.rhodin@mullvad.net> | 2025-04-10 17:29:33 +0200 |
| commit | 042820a80d994a09a58dfcdc7dce1ee1d891ac39 (patch) | |
| tree | b35efcceebded11d98cec4adc08a52febb2eedf8 /android/app/src/test | |
| parent | 1c5712f028250920fe34ce7686c77a7d80da9481 (diff) | |
| download | mullvadvpn-042820a80d994a09a58dfcdc7dce1ee1d891ac39.tar.xz mullvadvpn-042820a80d994a09a58dfcdc7dce1ee1d891ac39.zip | |
Implement quick access to active features
- Add Daita: Multihop feature indicator
- Make feature indicators clickable
- Add animations when accessing the features through the indicators
- Rework VpnSettings in order to support navigating to a feature in the
list
Diffstat (limited to 'android/app/src/test')
5 files changed, 135 insertions, 28 deletions
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/DaitaViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/DaitaViewModelTest.kt index 8eb9770826..dcb06c76a7 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/DaitaViewModelTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/DaitaViewModelTest.kt @@ -2,12 +2,14 @@ package net.mullvad.mullvadvpn.viewmodel import app.cash.turbine.test import arrow.core.right +import com.ramcosta.composedestinations.generated.navargs.toSavedStateHandle import io.mockk.coEvery import io.mockk.coVerify import io.mockk.every import io.mockk.mockk import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.runTest +import net.mullvad.mullvadvpn.compose.screen.DaitaNavArgs import net.mullvad.mullvadvpn.compose.state.DaitaUiState import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule import net.mullvad.mullvadvpn.lib.model.Settings @@ -27,7 +29,11 @@ class DaitaViewModelTest { @BeforeEach fun setUp() { every { mockSettingsRepository.settingsUpdates } returns settings - viewModel = DaitaViewModel(mockSettingsRepository) + viewModel = + DaitaViewModel( + mockSettingsRepository, + savedStateHandle = DaitaNavArgs().toSavedStateHandle(), + ) } @Test diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/MultihopViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/MultihopViewModelTest.kt index c51d7e9f48..6332666c22 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/MultihopViewModelTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/MultihopViewModelTest.kt @@ -2,12 +2,14 @@ package net.mullvad.mullvadvpn.viewmodel import app.cash.turbine.test import arrow.core.Either +import com.ramcosta.composedestinations.generated.navargs.toSavedStateHandle import io.mockk.coEvery import io.mockk.coVerify import io.mockk.every import io.mockk.mockk import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.runTest +import net.mullvad.mullvadvpn.compose.screen.MultihopNavArgs import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule import net.mullvad.mullvadvpn.lib.model.Constraint import net.mullvad.mullvadvpn.lib.model.WireguardConstraints @@ -32,7 +34,10 @@ class MultihopViewModelTest { wireguardConstraints multihopViewModel = - MultihopViewModel(wireguardConstraintsRepository = mockWireguardConstraintsRepository) + MultihopViewModel( + wireguardConstraintsRepository = mockWireguardConstraintsRepository, + savedStateHandle = MultihopNavArgs().toSavedStateHandle(), + ) } @Test diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ServerIpOverridesViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ServerIpOverridesViewModelTest.kt index 484a24fc29..d0d0a0a69c 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ServerIpOverridesViewModelTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ServerIpOverridesViewModelTest.kt @@ -6,6 +6,7 @@ import androidx.lifecycle.viewModelScope import app.cash.turbine.test import arrow.core.left import arrow.core.right +import com.ramcosta.composedestinations.generated.navargs.toSavedStateHandle import io.mockk.coEvery import io.mockk.coVerify import io.mockk.every @@ -18,6 +19,7 @@ import kotlin.test.assertEquals import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.runTest +import net.mullvad.mullvadvpn.compose.screen.ServerIpOverridesNavArgs import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule import net.mullvad.mullvadvpn.lib.model.RelayOverride import net.mullvad.mullvadvpn.lib.model.SettingsPatchError @@ -46,6 +48,7 @@ class ServerIpOverridesViewModelTest { ServerIpOverridesViewModel( relayOverridesRepository = mockRelayOverridesRepository, contentResolver = mockContentResolver, + savedStateHandle = ServerIpOverridesNavArgs().toSavedStateHandle(), ) } @@ -57,13 +60,13 @@ class ServerIpOverridesViewModelTest { @Test fun `ensure state is loading by default`() = runTest { - viewModel.uiState.test { assertEquals(ServerIpOverridesUiState.Loading, awaitItem()) } + viewModel.uiState.test { assertEquals(ServerIpOverridesUiState.Loading(), awaitItem()) } } @Test fun `when server ip overrides are empty ui state overrides should be inactive`() = runTest { viewModel.uiState.test { - assertEquals(ServerIpOverridesUiState.Loading, awaitItem()) + assertEquals(ServerIpOverridesUiState.Loading(), awaitItem()) relayOverrides.emit(emptyList()) assertEquals(ServerIpOverridesUiState.Loaded(false), awaitItem()) } diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SplitTunnelingViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SplitTunnelingViewModelTest.kt index 63a0907629..bfd1cb055c 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SplitTunnelingViewModelTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SplitTunnelingViewModelTest.kt @@ -3,6 +3,7 @@ package net.mullvad.mullvadvpn.viewmodel import androidx.lifecycle.viewModelScope import app.cash.turbine.test import arrow.core.right +import com.ramcosta.composedestinations.generated.navargs.toSavedStateHandle import io.mockk.coEvery import io.mockk.coVerify import io.mockk.every @@ -18,6 +19,7 @@ import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import net.mullvad.mullvadvpn.applist.AppData import net.mullvad.mullvadvpn.applist.ApplicationsProvider +import net.mullvad.mullvadvpn.compose.screen.SplitTunnelingNavArgs import net.mullvad.mullvadvpn.compose.state.SplitTunnelingUiState import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule import net.mullvad.mullvadvpn.lib.model.AppId @@ -187,6 +189,7 @@ class SplitTunnelingViewModelTest { SplitTunnelingViewModel( mockedApplicationsProvider, mockedSplitTunnelingRepository, + savedStateHandle = SplitTunnelingNavArgs().toSavedStateHandle(), UnconfinedTestDispatcher(), ) } diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModelTest.kt index c91d6d9a20..2dd2475ba2 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModelTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModelTest.kt @@ -3,6 +3,7 @@ package net.mullvad.mullvadvpn.viewmodel import androidx.lifecycle.viewModelScope import app.cash.turbine.test import arrow.core.right +import com.ramcosta.composedestinations.generated.navargs.toSavedStateHandle import io.mockk.Awaits import io.mockk.Runs import io.mockk.coEvery @@ -13,24 +14,31 @@ import io.mockk.mockk import io.mockk.unmockkAll import io.mockk.verify import kotlin.test.assertEquals -import kotlin.test.assertIs +import kotlin.test.assertTrue import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest +import net.mullvad.mullvadvpn.compose.screen.VpnSettingsNavArgs +import net.mullvad.mullvadvpn.compose.state.VpnSettingItem import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule import net.mullvad.mullvadvpn.lib.model.Constraint import net.mullvad.mullvadvpn.lib.model.DaitaSettings import net.mullvad.mullvadvpn.lib.model.IpVersion import net.mullvad.mullvadvpn.lib.model.Mtu +import net.mullvad.mullvadvpn.lib.model.ObfuscationMode +import net.mullvad.mullvadvpn.lib.model.ObfuscationSettings import net.mullvad.mullvadvpn.lib.model.Port import net.mullvad.mullvadvpn.lib.model.PortRange import net.mullvad.mullvadvpn.lib.model.QuantumResistantState import net.mullvad.mullvadvpn.lib.model.RelayConstraints import net.mullvad.mullvadvpn.lib.model.RelaySettings import net.mullvad.mullvadvpn.lib.model.Settings +import net.mullvad.mullvadvpn.lib.model.ShadowsocksSettings +import net.mullvad.mullvadvpn.lib.model.SplitTunnelSettings import net.mullvad.mullvadvpn.lib.model.TunnelOptions +import net.mullvad.mullvadvpn.lib.model.Udp2TcpObfuscationSettings import net.mullvad.mullvadvpn.lib.model.WireguardConstraints import net.mullvad.mullvadvpn.lib.model.WireguardTunnelOptions import net.mullvad.mullvadvpn.repository.AutoStartAndConnectOnBootRepository @@ -41,6 +49,7 @@ import net.mullvad.mullvadvpn.usecase.SystemVpnSettingsAvailableUseCase import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertInstanceOf import org.junit.jupiter.api.extension.ExtendWith @ExperimentalCoroutinesApi @@ -70,12 +79,13 @@ class VpnSettingsViewModelTest { viewModel = VpnSettingsViewModel( - repository = mockSettingsRepository, + settingsRepository = mockSettingsRepository, systemVpnSettingsUseCase = mockSystemVpnSettingsUseCase, relayListRepository = mockRelayListRepository, dispatcher = UnconfinedTestDispatcher(), autoStartAndConnectOnBootRepository = mockAutoStartAndConnectOnBootRepository, wireguardConstraintsRepository = mockWireguardConstraintsRepository, + savedStateHandle = VpnSettingsNavArgs().toSavedStateHandle(), ) } @@ -86,6 +96,11 @@ class VpnSettingsViewModelTest { } @Test + fun `initial state should be loading`() = runTest { + viewModel.uiState.test { assertEquals(VpnSettingsUiState.Loading(), awaitItem()) } + } + + @Test fun `onSelectCustomTcpOverUdpPort should invoke setCustomObfuscationPort on SettingsRepository`() = runTest { val customPort = Port(5001) @@ -112,20 +127,8 @@ class VpnSettingsViewModelTest { } @Test - fun `quantumResistant should be Off in uiState in initial state`() = runTest { - // Arrange - val expectedResistantState = QuantumResistantState.Off - - // Act, Assert - viewModel.uiState.test { - assertEquals(expectedResistantState, awaitItem().quantumResistant) - } - } - - @Test fun `when SettingsRepository emits quantumResistant On uiState should emit quantumResistant On`() = runTest { - val defaultResistantState = QuantumResistantState.Off val expectedResistantState = QuantumResistantState.On val mockSettings: Settings = mockk(relaxed = true) val mockTunnelOptions: TunnelOptions = mockk(relaxed = true) @@ -144,9 +147,17 @@ class VpnSettingsViewModelTest { Constraint.Any viewModel.uiState.test { - assertEquals(defaultResistantState, awaitItem().quantumResistant) + assertEquals(VpnSettingsUiState.Loading(), awaitItem()) mockSettingsUpdate.value = mockSettings - assertEquals(expectedResistantState, awaitItem().quantumResistant) + val content = awaitItem() + assertInstanceOf<VpnSettingsUiState.Content>(content) + + assertTrue( + content.settings + .filterIsInstance<VpnSettingItem.QuantumItem>() + .first { it.quantumResistantState == QuantumResistantState.On } + .selected + ) } } @@ -179,10 +190,23 @@ class VpnSettingsViewModelTest { // Act, Assert viewModel.uiState.test { - assertIs<Constraint.Any>(awaitItem().selectedWireguardPort) + assertInstanceOf<VpnSettingsUiState.Loading>(awaitItem()) + mockSettingsUpdate.value = mockSettings - assertEquals(expectedPort.value, awaitItem().customWireguardPort) - assertEquals(expectedPort, awaitItem().selectedWireguardPort) + + with(awaitItem()) { + assertInstanceOf<VpnSettingsUiState.Content>(this) + val customPortSetting = + settings + .filterIsInstance< + VpnSettingItem.WireguardPortItem.WireguardPortCustom + >() + .first() + + // Port should be what we expect and be selected + assertEquals(expectedPort.value.value, customPortSetting.customPort!!.value) + assertTrue(customPortSetting.selected) + } } } @@ -218,7 +242,14 @@ class VpnSettingsViewModelTest { every { mockSystemVpnSettingsUseCase() } returns systemVpnSettingsAvailable viewModel.uiState.test { - assertEquals(systemVpnSettingsAvailable, awaitItem().systemVpnSettingsAvailable) + assertInstanceOf<VpnSettingsUiState.Loading>(awaitItem()) + mockSettingsUpdate.value = dummySettings + + val content = awaitItem() + assertInstanceOf<VpnSettingsUiState.Content>(content) + assertTrue( + content.settings.any { it is VpnSettingItem.AutoConnectAndLockdownMode } + ) } } @@ -232,7 +263,12 @@ class VpnSettingsViewModelTest { // Assert viewModel.uiState.test { - assertEquals(connectOnStart, awaitItem().autoStartAndConnectOnBoot) + assertInstanceOf<VpnSettingsUiState.Loading>(awaitItem()) + + mockSettingsUpdate.value = dummySettings + val content = awaitItem() + assertInstanceOf<VpnSettingsUiState.Content>(content) + assertTrue(content.settings.any { it is VpnSettingItem.ConnectDeviceOnStartUpSetting }) } } @@ -263,7 +299,7 @@ class VpnSettingsViewModelTest { ipVersion every { mockSettings.tunnelOptions.wireguard } returns WireguardTunnelOptions( - mtu = Mtu(0), + mtu = null, quantumResistant = QuantumResistantState.Off, daitaSettings = DaitaSettings(enabled = false, directOnly = false), ) @@ -272,10 +308,18 @@ class VpnSettingsViewModelTest { // Act, Assert viewModel.uiState.test { - // Default value + // Loading value awaitItem() mockSettingsUpdate.value = mockSettings - assertEquals(ipVersion, awaitItem().deviceIpVersion) + val content = awaitItem() + assertInstanceOf<VpnSettingsUiState.Content>(content) + assertEquals( + ipVersion, + content.settings + .filterIsInstance<VpnSettingItem.DeviceIpVersionItem>() + .first { it.selected } + .constraint, + ) } } @@ -291,4 +335,50 @@ class VpnSettingsViewModelTest { // Assert coVerify(exactly = 1) { mockWireguardConstraintsRepository.setDeviceIpVersion(targetState) } } + + companion object { + val dummySettings: Settings = + Settings( + relaySettings = + RelaySettings( + relayConstraints = + RelayConstraints( + wireguardConstraints = + WireguardConstraints( + port = Constraint.Any, + isMultihopEnabled = false, + entryLocation = Constraint.Any, + ipVersion = Constraint.Any, + ), + providers = Constraint.Any, + ownership = Constraint.Any, + location = Constraint.Any, + ) + ), + obfuscationSettings = + ObfuscationSettings( + selectedObfuscationMode = ObfuscationMode.Auto, + udp2tcp = Udp2TcpObfuscationSettings(Constraint.Any), + shadowsocks = ShadowsocksSettings(Constraint.Any), + ), + customLists = emptyList(), + allowLan = false, + tunnelOptions = + TunnelOptions( + wireguard = + WireguardTunnelOptions( + mtu = null, + quantumResistant = QuantumResistantState.Auto, + daitaSettings = DaitaSettings(enabled = false, directOnly = false), + ), + dnsOptions = mockk(relaxed = true), + genericOptions = mockk(relaxed = true), + ), + relayOverrides = emptyList(), + showBetaReleases = false, + splitTunnelSettings = + SplitTunnelSettings(enabled = false, excludedApps = emptySet()), + apiAccessMethodSettings = emptyList(), + ) + } } |
