summaryrefslogtreecommitdiffhomepage
path: root/android/app/src/test
diff options
context:
space:
mode:
authorAlbin <albin@mullvad.net>2021-10-13 18:14:45 +0200
committerAlbin <albin@mullvad.net>2021-12-16 11:38:25 +0100
commit6c4d31dfc59772e9a80d0260fee34cf03d5c05f5 (patch)
tree8418020b852073a6dfb0c4c44fabdb1586404d11 /android/app/src/test
parent4dcdb1306d23a8e831389130fdef452cae4dc809 (diff)
downloadmullvadvpn-6c4d31dfc59772e9a80d0260fee34cf03d5c05f5.tar.xz
mullvadvpn-6c4d31dfc59772e9a80d0260fee34cf03d5c05f5.zip
Split Android project and app module
The purpose of this is to: * Comply better with the default Android project structure (see https://developer.android.com/studio/build). * Avoid conflicts between project and app dependencies and plugins.
Diffstat (limited to 'android/app/src/test')
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/TestCoroutineRule.kt24
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/TestUtils.kt11
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/applist/ApplicationsIconManagerTest.kt98
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/applist/ApplicationsProviderTest.kt129
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/di/AppModuleTest.kt49
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/relaylist/RelayNameComparatorTest.kt97
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SplitTunnelingViewModelTest.kt238
7 files changed, 646 insertions, 0 deletions
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/TestCoroutineRule.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/TestCoroutineRule.kt
new file mode 100644
index 0000000000..1acdf9e577
--- /dev/null
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/TestCoroutineRule.kt
@@ -0,0 +1,24 @@
+package net.mullvad.mullvadvpn
+
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.test.TestCoroutineDispatcher
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.setMain
+import org.junit.rules.TestWatcher
+import org.junit.runner.Description
+
+class TestCoroutineRule(
+ val testDispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher()
+) : TestWatcher() {
+
+ override fun starting(description: Description?) {
+ super.starting(description)
+ Dispatchers.setMain(testDispatcher)
+ }
+
+ override fun finished(description: Description?) {
+ super.finished(description)
+ Dispatchers.resetMain()
+ testDispatcher.cleanupTestCoroutines()
+ }
+}
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/TestUtils.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/TestUtils.kt
new file mode 100644
index 0000000000..4c4f043c06
--- /dev/null
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/TestUtils.kt
@@ -0,0 +1,11 @@
+package net.mullvad.mullvadvpn
+
+import kotlin.test.assertTrue
+
+fun <T> assertLists(expected: List<T>, actual: List<T>, message: String? = null) = assertTrue(
+ expected.size == actual.size && expected.containsAll(actual) && actual.containsAll(expected),
+ message ?: """Expected list should have same size and contains same items.
+ | Expected(${expected.size}): $expected
+ | Actual(${actual.size}) : $actual
+ """.trimMargin()
+)
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/applist/ApplicationsIconManagerTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/applist/ApplicationsIconManagerTest.kt
new file mode 100644
index 0000000000..e6d43621a1
--- /dev/null
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/applist/ApplicationsIconManagerTest.kt
@@ -0,0 +1,98 @@
+package net.mullvad.mullvadvpn.applist
+
+import android.content.pm.PackageManager
+import android.graphics.drawable.Drawable
+import android.os.Looper
+import io.mockk.every
+import io.mockk.mockk
+import io.mockk.mockkStatic
+import io.mockk.unmockkAll
+import io.mockk.verify
+import kotlin.test.assertEquals
+import kotlin.test.assertFails
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+
+class ApplicationsIconManagerTest {
+ private val mockedPackageManager = mockk<PackageManager>()
+ private val mockedMainLooper = mockk<Looper>()
+ private val testSubject = ApplicationsIconManager(mockedPackageManager)
+
+ @Before
+ fun setUp() {
+ mockkStatic(Looper::class)
+ every { Looper.getMainLooper() } returns mockedMainLooper
+ }
+
+ @After
+ fun tearDown() {
+ unmockkAll()
+ }
+
+ @Test
+ fun test_first_time_load_icon_from_PM() {
+ val testPackageName = "test"
+ val mockedDrawable = mockk<Drawable>()
+ every { mockedPackageManager.getApplicationIcon(testPackageName) } returns mockedDrawable
+ every { mockedMainLooper.isCurrentThread } returns false
+
+ val result = testSubject.getAppIcon(testPackageName)
+
+ assertEquals(mockedDrawable, result)
+ verify {
+ mockedMainLooper.isCurrentThread
+ mockedPackageManager.getApplicationIcon(testPackageName)
+ }
+ }
+
+ @Test
+ fun test_second_time_load_icon_from_cache() {
+ val testPackageName = "test"
+ val mockedDrawable = mockk<Drawable>()
+ every { mockedPackageManager.getApplicationIcon(testPackageName) } returns mockedDrawable
+ every { mockedMainLooper.isCurrentThread } returns false
+
+ val result = testSubject.getAppIcon(testPackageName)
+ val result2 = testSubject.getAppIcon(testPackageName)
+
+ assertEquals(mockedDrawable, result)
+ assertEquals(mockedDrawable, result2)
+ verify(exactly = 2) {
+ mockedMainLooper.isCurrentThread
+ }
+ verify(exactly = 1) {
+ mockedPackageManager.getApplicationIcon(testPackageName)
+ }
+ }
+
+ @Test
+ fun test_second_time_load_icon_from_PM_after_clear() {
+ val testPackageName = "test"
+ val mockedDrawable = mockk<Drawable>()
+ every { mockedPackageManager.getApplicationIcon(testPackageName) } returns mockedDrawable
+ every { mockedMainLooper.isCurrentThread } returns false
+
+ val result = testSubject.getAppIcon(testPackageName)
+ testSubject.dispose()
+ val result2 = testSubject.getAppIcon(testPackageName)
+
+ assertEquals(mockedDrawable, result)
+ assertEquals(mockedDrawable, result2)
+ verify(exactly = 2) {
+ mockedMainLooper.isCurrentThread
+ mockedPackageManager.getApplicationIcon(testPackageName)
+ }
+ }
+
+ @Test
+ fun test_throw_exception_when_invoke_from_MainThread() {
+ val testPackageName = "test"
+ every { mockedMainLooper.isCurrentThread } returns true
+
+ assertFails("Should not be called from MainThread") {
+ testSubject.getAppIcon(testPackageName)
+ }
+ verify { mockedMainLooper.isCurrentThread }
+ }
+}
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/applist/ApplicationsProviderTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/applist/ApplicationsProviderTest.kt
new file mode 100644
index 0000000000..e1a9e37ac4
--- /dev/null
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/applist/ApplicationsProviderTest.kt
@@ -0,0 +1,129 @@
+package net.mullvad.mullvadvpn.applist
+
+import android.Manifest
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import io.mockk.every
+import io.mockk.mockk
+import io.mockk.unmockkAll
+import io.mockk.verifyAll
+import net.mullvad.mullvadvpn.assertLists
+import org.junit.After
+import org.junit.Test
+
+class ApplicationsProviderTest {
+ private val mockedPackageManager = mockk<PackageManager>()
+ private val selfPackageName = "self_package_name"
+ private val testSubject = ApplicationsProvider(mockedPackageManager, selfPackageName)
+ private val internet = Manifest.permission.INTERNET
+
+ @After
+ fun tearDown() {
+ unmockkAll()
+ }
+
+ @Test
+ fun test_get_apps() {
+ val launchWithInternetPackageName = "launch_with_internet_package_name"
+ val launchWithoutInternetPackageName = "launch_without_internet_package_name"
+ val nonLaunchWithInternetPackageName = "non_launch_with_internet_package_name"
+ val nonLaunchWithoutInternetPackageName = "non_launch_without_internet_package_name"
+ val leanbackLaunchWithInternetPackageName = "leanback_launch_with_internet_package_name"
+ val leanbackLaunchWithoutInternetPackageName =
+ "leanback_launch_without_internet_package_name"
+
+ every {
+ mockedPackageManager.getInstalledApplications(PackageManager.GET_META_DATA)
+ } returns listOf(
+ createApplicationInfo(launchWithInternetPackageName, launch = true, internet = true),
+ createApplicationInfo(launchWithoutInternetPackageName, launch = true),
+ createApplicationInfo(nonLaunchWithInternetPackageName, internet = true),
+ createApplicationInfo(nonLaunchWithoutInternetPackageName),
+ createApplicationInfo(
+ leanbackLaunchWithInternetPackageName,
+ leanback = true,
+ internet = true
+ ),
+ createApplicationInfo(leanbackLaunchWithoutInternetPackageName, leanback = true),
+ createApplicationInfo(selfPackageName, internet = true, launch = true)
+ )
+
+ val result = testSubject.getAppsList()
+ val expected = listOf(
+ AppData(launchWithInternetPackageName, 0, launchWithInternetPackageName),
+ AppData(nonLaunchWithInternetPackageName, 0, nonLaunchWithInternetPackageName, true),
+ AppData(leanbackLaunchWithInternetPackageName, 0, leanbackLaunchWithInternetPackageName)
+ )
+
+ assertLists(expected, result)
+
+ verifyAll {
+ mockedPackageManager.getInstalledApplications(PackageManager.GET_META_DATA)
+
+ listOf(
+ launchWithInternetPackageName,
+ launchWithoutInternetPackageName,
+ nonLaunchWithInternetPackageName,
+ nonLaunchWithoutInternetPackageName,
+ leanbackLaunchWithInternetPackageName,
+ leanbackLaunchWithoutInternetPackageName,
+ selfPackageName
+ ).forEach { packageName ->
+ mockedPackageManager.checkPermission(internet, packageName)
+ }
+
+ listOf(
+ launchWithInternetPackageName,
+ nonLaunchWithInternetPackageName,
+ leanbackLaunchWithInternetPackageName
+ ).forEach { packageName ->
+ mockedPackageManager.getLaunchIntentForPackage(packageName)
+ }
+
+ listOf(
+ nonLaunchWithInternetPackageName,
+ leanbackLaunchWithInternetPackageName,
+ ).forEach { packageName ->
+ mockedPackageManager.getLeanbackLaunchIntentForPackage(packageName)
+ }
+ }
+ }
+
+ private fun createApplicationInfo(
+ packageName: String,
+ launch: Boolean = false,
+ leanback: Boolean = false,
+ internet: Boolean = false,
+ systemApp: Boolean = false
+ ): ApplicationInfo {
+ val mockApplicationInfo = mockk<ApplicationInfo>()
+
+ mockApplicationInfo.packageName = packageName
+ mockApplicationInfo.icon = 0
+
+ every { mockApplicationInfo.loadLabel(mockedPackageManager) } returns packageName
+
+ every {
+ mockedPackageManager.getLaunchIntentForPackage(packageName)
+ } returns if (launch || systemApp)
+ mockk()
+ else
+ null
+
+ every {
+ mockedPackageManager.getLeanbackLaunchIntentForPackage(packageName)
+ } returns if (leanback || systemApp)
+ mockk()
+ else
+ null
+
+ every {
+ mockedPackageManager.checkPermission(Manifest.permission.INTERNET, packageName)
+ } returns if (internet)
+ PackageManager.PERMISSION_GRANTED
+ else
+ PackageManager.PERMISSION_DENIED
+
+ return mockApplicationInfo
+ }
+}
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/di/AppModuleTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/di/AppModuleTest.kt
new file mode 100644
index 0000000000..c30a63fedf
--- /dev/null
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/di/AppModuleTest.kt
@@ -0,0 +1,49 @@
+package net.mullvad.mullvadvpn.di
+
+import android.os.Messenger
+import io.mockk.mockk
+import io.mockk.unmockkAll
+import kotlin.test.assertEquals
+import net.mullvad.mullvadvpn.ipc.Event
+import net.mullvad.mullvadvpn.ipc.MessageDispatcher
+import net.mullvad.mullvadvpn.ui.serviceconnection.SplitTunneling
+import org.junit.After
+import org.junit.Rule
+import org.junit.Test
+import org.koin.core.parameter.parametersOf
+import org.koin.core.qualifier.named
+import org.koin.core.scope.Scope
+import org.koin.test.KoinTest
+import org.koin.test.KoinTestRule
+
+class AppModuleTest : KoinTest {
+
+ @get:Rule
+ val koinTestRule = KoinTestRule.create {
+ modules(appModule)
+ }
+
+ @After
+ fun tearDown() {
+ unmockkAll()
+ }
+
+ @Test
+ fun test_scope_linking() {
+ val appsScope: Scope = getKoin().createScope(APPS_SCOPE, named(APPS_SCOPE))
+ val serviceConnectionScope = getKoin().createScope(
+ SERVICE_CONNECTION_SCOPE,
+ named(SERVICE_CONNECTION_SCOPE)
+ )
+
+ appsScope.linkTo(serviceConnectionScope)
+
+ val mockedMessenger = mockk<Messenger>()
+ val mockedEventMessageHandler = mockk<MessageDispatcher<Event>>(relaxed = true)
+ val serviceConnectionSplitTunneling = serviceConnectionScope.get<SplitTunneling>(
+ parameters = { parametersOf(mockedMessenger, mockedEventMessageHandler) }
+ )
+
+ assertEquals(appsScope.get<SplitTunneling>(), serviceConnectionSplitTunneling)
+ }
+}
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/relaylist/RelayNameComparatorTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/relaylist/RelayNameComparatorTest.kt
new file mode 100644
index 0000000000..a3c96349d9
--- /dev/null
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/relaylist/RelayNameComparatorTest.kt
@@ -0,0 +1,97 @@
+package net.mullvad.mullvadvpn.relaylist
+
+import io.mockk.mockk
+import io.mockk.unmockkAll
+import org.junit.After
+import org.junit.Assert.assertTrue
+import org.junit.Test
+
+class RelayNameComparatorTest {
+
+ private val mockedCity = mockk<RelayCity>(relaxed = true)
+
+ @After
+ fun tearDown() {
+ unmockkAll()
+ }
+
+ @Test
+ fun test_compare_respect_numbers_in_name() {
+ val relay9 = Relay(mockedCity, "se9-wireguard", false)
+ val relay10 = Relay(mockedCity, "se10-wireguard", false)
+
+ relay9 assertOrderBothDirection relay10
+ }
+
+ @Test
+ fun test_compare_same_name() {
+ val relay9a = Relay(mockedCity, "se9-wireguard", false)
+ val relay9b = Relay(mockedCity, "se9-wireguard", false)
+
+ assertTrue(RelayNameComparator.compare(relay9a, relay9b) == 0)
+ assertTrue(RelayNameComparator.compare(relay9b, relay9a) == 0)
+ }
+
+ @Test
+ fun test_compare_only_numbers_in_name() {
+ val relay001 = Relay(mockedCity, "001", false)
+ val relay1 = Relay(mockedCity, "1", false)
+ val relay3 = Relay(mockedCity, "3", false)
+ val relay100 = Relay(mockedCity, "100", false)
+
+ relay001 assertOrderBothDirection relay1
+ relay001 assertOrderBothDirection relay3
+ relay1 assertOrderBothDirection relay3
+ relay3 assertOrderBothDirection relay100
+ }
+
+ @Test
+ fun test_compare_without_numbers_in_name() {
+ val relay9a = Relay(mockedCity, "se-wireguard", false)
+ val relay9b = Relay(mockedCity, "se-wireguard", false)
+
+ assertTrue(RelayNameComparator.compare(relay9a, relay9b) == 0)
+ assertTrue(RelayNameComparator.compare(relay9b, relay9a) == 0)
+ }
+
+ @Test
+ fun test_compare_with_trailing_zeros_in_name() {
+ val relay001 = Relay(mockedCity, "se001-wireguard", false)
+ val relay005 = Relay(mockedCity, "se005-wireguard", false)
+
+ relay001 assertOrderBothDirection relay005
+ }
+
+ @Test
+ fun test_compare_prefix_and_numbers() {
+ val relayAr2 = Relay(mockedCity, "ar2-wireguard", false)
+ val relayAr8 = Relay(mockedCity, "ar8-wireguard", false)
+ val relaySe5 = Relay(mockedCity, "se5-wireguard", false)
+ val relaySe10 = Relay(mockedCity, "se10-wireguard", false)
+
+ relayAr2 assertOrderBothDirection relayAr8
+ relayAr8 assertOrderBothDirection relaySe5
+ relaySe5 assertOrderBothDirection relaySe10
+ }
+
+ @Test
+ fun test_compare_suffix_and_numbers() {
+ val relay2c = Relay(mockedCity, "se2-cloud", false)
+ val relay2w = Relay(mockedCity, "se2-wireguard", false)
+
+ relay2c assertOrderBothDirection relay2w
+ }
+
+ @Test
+ fun test_compare_different_length() {
+ val relay22a = Relay(mockedCity, "se22", false)
+ val relay22b = Relay(mockedCity, "se22-wireguard", false)
+
+ relay22a assertOrderBothDirection relay22b
+ }
+
+ private infix fun Relay.assertOrderBothDirection(other: Relay) {
+ assertTrue(RelayNameComparator.compare(this, other) < 0)
+ assertTrue(RelayNameComparator.compare(other, this) > 0)
+ }
+}
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
new file mode 100644
index 0000000000..ac229ba3fb
--- /dev/null
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SplitTunnelingViewModelTest.kt
@@ -0,0 +1,238 @@
+package net.mullvad.mullvadvpn.viewmodel
+
+import androidx.annotation.StringRes
+import androidx.lifecycle.viewModelScope
+import io.mockk.Runs
+import io.mockk.every
+import io.mockk.just
+import io.mockk.mockk
+import io.mockk.unmockkAll
+import io.mockk.verify
+import io.mockk.verifyAll
+import java.util.concurrent.TimeUnit
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.test.runBlockingTest
+import net.mullvad.mullvadvpn.R
+import net.mullvad.mullvadvpn.TestCoroutineRule
+import net.mullvad.mullvadvpn.applist.AppData
+import net.mullvad.mullvadvpn.applist.ApplicationsProvider
+import net.mullvad.mullvadvpn.applist.ViewIntent
+import net.mullvad.mullvadvpn.assertLists
+import net.mullvad.mullvadvpn.model.ListItemData
+import net.mullvad.mullvadvpn.model.WidgetState
+import net.mullvad.mullvadvpn.ui.serviceconnection.SplitTunneling
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.Timeout
+
+class SplitTunnelingViewModelTest {
+ @get:Rule
+ val testCoroutineRule = TestCoroutineRule()
+
+ @get:Rule
+ val timeout = Timeout(3000L, TimeUnit.MILLISECONDS)
+ private val mockedApplicationsProvider = mockk<ApplicationsProvider>()
+ private val mockedSplitTunneling = mockk<SplitTunneling>()
+ private lateinit var testSubject: SplitTunnelingViewModel
+
+ @Before
+ fun setup() {
+ every { mockedSplitTunneling.enabled } returns true
+ }
+
+ @After
+ fun tearDown() {
+ testSubject.viewModelScope.coroutineContext.cancel()
+ unmockkAll()
+ }
+
+ @Test
+ fun test_has_progress_on_start() = runBlockingTest(testCoroutineRule.testDispatcher) {
+ initTestSubject(emptyList())
+ val actualList: List<ListItemData> = testSubject.listItems.first()
+
+ val initialExpectedList = listOf(
+ createTextItem(R.string.split_tunneling_description),
+ createDivider(0),
+ createProgressItem()
+ )
+
+ assertLists(initialExpectedList, actualList)
+
+ verify(exactly = 1) {
+ mockedApplicationsProvider.getAppsList()
+ }
+ }
+
+ @Test
+ fun test_empty_app_list() = runBlockingTest(testCoroutineRule.testDispatcher) {
+ initTestSubject(emptyList())
+ testSubject.processIntent(ViewIntent.ViewIsReady)
+ val actualList = testSubject.listItems.first()
+ val expectedList = listOf(createTextItem(R.string.split_tunneling_description))
+ assertLists(expectedList, actualList)
+ }
+
+ @Test
+ fun test_apps_list_delivered() = runBlockingTest(testCoroutineRule.testDispatcher) {
+ val appExcluded = AppData("test.excluded", 0, "testName1")
+ val appNotExcluded = AppData("test.not.excluded", 0, "testName2")
+ every { mockedSplitTunneling.isAppExcluded(appExcluded.packageName) } returns true
+ every { mockedSplitTunneling.isAppExcluded(appNotExcluded.packageName) } returns false
+
+ initTestSubject(listOf(appExcluded, appNotExcluded))
+ testSubject.processIntent(ViewIntent.ViewIsReady)
+
+ val actualList = testSubject.listItems.first()
+ val expectedList = listOf(
+ createTextItem(R.string.split_tunneling_description),
+ createDivider(0),
+ createMainItem(R.string.exclude_applications),
+ createApplicationItem(appExcluded, true),
+ createDivider(1),
+ createSwitchItem(R.string.show_system_apps, false),
+ createMainItem(R.string.all_applications),
+ createApplicationItem(appNotExcluded, false),
+ )
+
+ assertLists(expectedList, actualList)
+ verifyAll {
+ mockedSplitTunneling.enabled
+ mockedSplitTunneling.isAppExcluded(appExcluded.packageName)
+ mockedSplitTunneling.isAppExcluded(appNotExcluded.packageName)
+ }
+ }
+
+ @Test
+ fun test_remove_app_from_excluded() = runBlockingTest(testCoroutineRule.testDispatcher) {
+ val app = AppData("test", 0, "testName")
+ every { mockedSplitTunneling.isAppExcluded(app.packageName) } returns true
+ every { mockedSplitTunneling.includeApp(app.packageName) } just Runs
+
+ initTestSubject(listOf(app))
+ testSubject.processIntent(ViewIntent.ViewIsReady)
+
+ val listBeforeAction = testSubject.listItems.first()
+ val expectedListBeforeAction = listOf(
+ createTextItem(R.string.split_tunneling_description),
+ createDivider(0),
+ createMainItem(R.string.exclude_applications),
+ createApplicationItem(app, true),
+ )
+
+ assertLists(expectedListBeforeAction, listBeforeAction)
+
+ val item = listBeforeAction.first { it.identifier == app.packageName }
+ testSubject.processIntent(ViewIntent.ChangeApplicationGroup(item))
+
+ val itemsAfterAction = testSubject.listItems.first()
+ val expectedList = listOf(
+ createTextItem(R.string.split_tunneling_description),
+ createDivider(1),
+ createSwitchItem(R.string.show_system_apps, false),
+ createMainItem(R.string.all_applications),
+ createApplicationItem(app, false),
+ )
+
+ assertLists(expectedList, itemsAfterAction)
+
+ verifyAll {
+ mockedSplitTunneling.enabled
+ mockedSplitTunneling.isAppExcluded(app.packageName)
+ mockedSplitTunneling.includeApp(app.packageName)
+ }
+ }
+
+ @Test
+ fun test_add_app_to_excluded() = runBlockingTest(testCoroutineRule.testDispatcher) {
+ val app = AppData("test", 0, "testName")
+ every { mockedSplitTunneling.isAppExcluded(app.packageName) } returns false
+ every { mockedSplitTunneling.excludeApp(app.packageName) } just Runs
+ initTestSubject(listOf(app))
+ testSubject.processIntent(ViewIntent.ViewIsReady)
+
+ val listBeforeAction = testSubject.listItems.first()
+ val expectedListBeforeAction = listOf(
+ createTextItem(R.string.split_tunneling_description),
+ createDivider(1),
+ createSwitchItem(R.string.show_system_apps, false),
+ createMainItem(R.string.all_applications),
+ createApplicationItem(app, false),
+ )
+
+ assertLists(expectedListBeforeAction, listBeforeAction)
+
+ val item = listBeforeAction.first { it.identifier == app.packageName }
+ testSubject.processIntent(ViewIntent.ChangeApplicationGroup(item))
+
+ val itemsAfterAction = testSubject.listItems.first()
+ val expectedList = listOf(
+ createTextItem(R.string.split_tunneling_description),
+ createDivider(0),
+ createMainItem(R.string.exclude_applications),
+ createApplicationItem(app, true),
+ )
+
+ assertLists(expectedList, itemsAfterAction)
+
+ verifyAll {
+ mockedSplitTunneling.enabled
+ mockedSplitTunneling.isAppExcluded(app.packageName)
+ mockedSplitTunneling.excludeApp(app.packageName)
+ }
+ }
+
+ private fun initTestSubject(appList: List<AppData>) {
+ every { mockedApplicationsProvider.getAppsList() } returns appList
+ testSubject = SplitTunnelingViewModel(
+ mockedApplicationsProvider,
+ mockedSplitTunneling,
+ testCoroutineRule.testDispatcher
+ )
+ }
+
+ private fun createApplicationItem(
+ appData: AppData,
+ checked: Boolean
+ ): ListItemData = ListItemData.build(appData.packageName) {
+ type = ListItemData.APPLICATION
+ text = appData.name
+ iconRes = appData.iconRes
+ action = ListItemData.ItemAction(appData.packageName)
+ widget = WidgetState.ImageState(
+ if (checked) R.drawable.ic_icons_remove else R.drawable.ic_icons_add
+ )
+ }
+
+ private fun createDivider(id: Int): ListItemData = ListItemData.build("space_$id") {
+ type = ListItemData.DIVIDER
+ }
+
+ private fun createMainItem(@StringRes text: Int): ListItemData =
+ ListItemData.build("header_$text") {
+ type = ListItemData.ACTION
+ textRes = text
+ }
+
+ private fun createTextItem(@StringRes text: Int): ListItemData =
+ ListItemData.build("text_$text") {
+ type = ListItemData.PLAIN
+ textRes = text
+ action = ListItemData.ItemAction(text.toString())
+ }
+
+ private fun createProgressItem(): ListItemData = ListItemData.build(identifier = "progress") {
+ type = ListItemData.PROGRESS
+ }
+
+ private fun createSwitchItem(@StringRes text: Int, checked: Boolean): ListItemData =
+ ListItemData.build(identifier = "switch_$text") {
+ type = ListItemData.ACTION
+ textRes = text
+ action = ListItemData.ItemAction(text.toString())
+ widget = WidgetState.SwitchState(checked)
+ }
+}