summaryrefslogtreecommitdiffhomepage
path: root/android/app/src/test
diff options
context:
space:
mode:
authorDavid Göransson <david.goransson@mullvad.net>2025-03-31 09:36:52 +0200
committerJonatan Rhodin <jonatan.rhodin@mullvad.net>2025-04-10 17:29:33 +0200
commit042820a80d994a09a58dfcdc7dce1ee1d891ac39 (patch)
treeb35efcceebded11d98cec4adc08a52febb2eedf8 /android/app/src/test
parent1c5712f028250920fe34ce7686c77a7d80da9481 (diff)
downloadmullvadvpn-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')
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/DaitaViewModelTest.kt8
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/MultihopViewModelTest.kt7
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ServerIpOverridesViewModelTest.kt7
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SplitTunnelingViewModelTest.kt3
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModelTest.kt138
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(),
+ )
+ }
}