summaryrefslogtreecommitdiffhomepage
path: root/android/app/src
diff options
context:
space:
mode:
authorsaber safavi <saber.safavi@codic.se>2023-11-17 13:52:55 +0100
committerAlbin <albin@mullvad.net>2023-11-27 17:19:33 +0100
commit836a7937974141aa4ad55750556e5d607c956665 (patch)
treecf499ed6dc34f582cb70181f8ee84c58389370b0 /android/app/src
parent2430bd0f34f0352ffc31ee8598b9fd38a250aa84 (diff)
downloadmullvadvpn-836a7937974141aa4ad55750556e5d607c956665.tar.xz
mullvadvpn-836a7937974141aa4ad55750556e5d607c956665.zip
Add view model and ui test
Diffstat (limited to 'android/app/src')
-rw-r--r--android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/RedeemVoucherDialogTest.kt149
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/RedeemVoucherDialog.kt5
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/test/ComposeTestTagConstants.kt3
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VoucherDialogViewModel.kt3
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/utils/VoucherRegexHelperParameterizedTest.kt45
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/VoucherDialogViewModelTest.kt93
6 files changed, 277 insertions, 21 deletions
diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/RedeemVoucherDialogTest.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/RedeemVoucherDialogTest.kt
new file mode 100644
index 0000000000..c07cb1aa6b
--- /dev/null
+++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/RedeemVoucherDialogTest.kt
@@ -0,0 +1,149 @@
+package net.mullvad.mullvadvpn.compose.screen
+
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.performTextInput
+import io.mockk.mockk
+import io.mockk.mockkObject
+import io.mockk.verify
+import net.mullvad.mullvadvpn.compose.setContentWithTheme
+import net.mullvad.mullvadvpn.compose.state.VoucherDialogState
+import net.mullvad.mullvadvpn.compose.state.VoucherDialogUiState
+import net.mullvad.mullvadvpn.compose.test.VOUCHER_INPUT_TEST_TAG
+import net.mullvad.mullvadvpn.util.VoucherRegexHelper
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+class RedeemVoucherDialogTest {
+ @get:Rule val composeTestRule = createComposeRule()
+
+ @Before
+ fun setup() {
+ mockkObject(VoucherRegexHelper)
+ }
+
+ @Test
+ fun testDismissDialog() {
+ // Arrange
+ val mockedClickHandler: (Boolean) -> Unit = mockk(relaxed = true)
+ composeTestRule.setContentWithTheme {
+ RedeemVoucherDialogScreen(
+ uiState = VoucherDialogUiState.INITIAL,
+ onVoucherInputChange = {},
+ onRedeem = {},
+ onDismiss = mockedClickHandler
+ )
+ }
+
+ // Act
+ composeTestRule.onNodeWithText(CANCEL_BUTTON_TEXT).performClick()
+
+ // Assert
+ verify { mockedClickHandler.invoke(false) }
+ }
+
+ @Test
+ fun testDismissDialogAfterSuccessfulRedeem() {
+ // Arrange
+ val mockedClickHandler: (Boolean) -> Unit = mockk(relaxed = true)
+ composeTestRule.setContentWithTheme {
+ RedeemVoucherDialogScreen(
+ uiState =
+ VoucherDialogUiState(voucherViewModelState = VoucherDialogState.Success(0)),
+ onVoucherInputChange = {},
+ onRedeem = {},
+ onDismiss = mockedClickHandler
+ )
+ }
+
+ // Act
+ composeTestRule.onNodeWithText(GOT_IT_BUTTON_TEXT).performClick()
+
+ // Assert
+ verify { mockedClickHandler.invoke(true) }
+ }
+
+ @Test
+ fun testInsertVoucher() {
+ // Arrange
+ val mockedClickHandler: (String) -> Unit = mockk(relaxed = true)
+ composeTestRule.setContentWithTheme {
+ RedeemVoucherDialogScreen(
+ uiState = VoucherDialogUiState(),
+ onVoucherInputChange = mockedClickHandler,
+ onRedeem = {},
+ onDismiss = {}
+ )
+ }
+
+ // Act
+ composeTestRule.onNodeWithTag(VOUCHER_INPUT_TEST_TAG).performTextInput(DUMMY_VOUCHER)
+
+ // Assert
+ verify { mockedClickHandler.invoke(DUMMY_VOUCHER) }
+ }
+
+ @Test
+ fun testVerifyingState() {
+ // Arrange
+ composeTestRule.setContentWithTheme {
+ RedeemVoucherDialogScreen(
+ uiState =
+ VoucherDialogUiState(voucherViewModelState = VoucherDialogState.Verifying),
+ onVoucherInputChange = {},
+ onRedeem = {},
+ onDismiss = {}
+ )
+ }
+
+ // Assert
+ composeTestRule.onNodeWithText("Verifying voucher…").assertExists()
+ }
+
+ @Test
+ fun testSuccessState() {
+ // Arrange
+ composeTestRule.setContentWithTheme {
+ RedeemVoucherDialogScreen(
+ uiState =
+ VoucherDialogUiState(voucherViewModelState = VoucherDialogState.Success(0)),
+ onVoucherInputChange = {},
+ onRedeem = {},
+ onDismiss = {}
+ )
+ }
+
+ // Assert
+ composeTestRule.onNodeWithText("Voucher was successfully redeemed.").assertExists()
+ }
+
+ @Test
+ fun testErrorState() {
+ // Arrange
+ composeTestRule.setContentWithTheme {
+ RedeemVoucherDialogScreen(
+ uiState =
+ VoucherDialogUiState(
+ voucherViewModelState = VoucherDialogState.Error(ERROR_MESSAGE)
+ ),
+ onVoucherInputChange = {},
+ onRedeem = {},
+ onDismiss = {}
+ )
+ }
+
+ // Assert
+ composeTestRule.onNodeWithText(ERROR_MESSAGE).assertExists()
+ }
+
+ companion object {
+ private const val REDEEM_BUTTON_TEXT = "Redeem"
+ private const val CANCEL_BUTTON_TEXT = "Cancel"
+ private const val GOT_IT_BUTTON_TEXT = "Got it!"
+ private const val DUMMY_VOUCHER = "DUMMY____VOUCHER"
+ private const val ERROR_MESSAGE = "error_message"
+ }
+}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/RedeemVoucherDialog.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/RedeemVoucherDialog.kt
index 29d4807b42..1c48a8a64a 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/RedeemVoucherDialog.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/RedeemVoucherDialog.kt
@@ -14,6 +14,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource
@@ -29,6 +30,7 @@ import net.mullvad.mullvadvpn.compose.button.VariantButton
import net.mullvad.mullvadvpn.compose.component.MullvadCircularProgressIndicatorSmall
import net.mullvad.mullvadvpn.compose.state.VoucherDialogState
import net.mullvad.mullvadvpn.compose.state.VoucherDialogUiState
+import net.mullvad.mullvadvpn.compose.test.VOUCHER_INPUT_TEST_TAG
import net.mullvad.mullvadvpn.compose.textfield.CustomTextField
import net.mullvad.mullvadvpn.compose.util.MAX_VOUCHER_LENGTH
import net.mullvad.mullvadvpn.compose.util.vouchersVisualTransformation
@@ -230,7 +232,8 @@ private fun EnterVoucherBody(
keyboardType = KeyboardType.Password,
placeholderText = stringResource(id = R.string.voucher_hint),
visualTransformation = vouchersVisualTransformation(),
- isDigitsOnlyAllowed = false
+ isDigitsOnlyAllowed = false,
+ modifier = Modifier.testTag(VOUCHER_INPUT_TEST_TAG)
)
Spacer(modifier = Modifier.height(Dimens.smallPadding))
Row(
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/test/ComposeTestTagConstants.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/test/ComposeTestTagConstants.kt
index 01d434a117..e3cb4faa5b 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/test/ComposeTestTagConstants.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/test/ComposeTestTagConstants.kt
@@ -31,3 +31,6 @@ const val PLAY_PAYMENT_INFO_ICON_TEST_TAG = "play_payment_info_icon_test_tag"
const val LOGIN_TITLE_TEST_TAG = "login_title_test_tag"
const val LOGIN_INPUT_TEST_TAG = "login_input_test_tag"
+
+// VoucherDialog
+const val VOUCHER_INPUT_TEST_TAG = "voucher_input_test_tag"
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VoucherDialogViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VoucherDialogViewModel.kt
index a3bf687f42..802245bb1d 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VoucherDialogViewModel.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VoucherDialogViewModel.kt
@@ -15,7 +15,6 @@ import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import net.mullvad.mullvadvpn.R
-import net.mullvad.mullvadvpn.compose.state.LoginUiState
import net.mullvad.mullvadvpn.compose.state.VoucherDialogState
import net.mullvad.mullvadvpn.compose.state.VoucherDialogUiState
import net.mullvad.mullvadvpn.constant.VOUCHER_LENGTH
@@ -33,7 +32,7 @@ class VoucherDialogViewModel(
) : ViewModel() {
private val vmState = MutableStateFlow<VoucherDialogState>(VoucherDialogState.Default)
- private val voucherInput = MutableStateFlow(LoginUiState.INITIAL.accountNumberInput)
+ private val voucherInput = MutableStateFlow("")
private val _shared: SharedFlow<ServiceConnectionContainer> =
serviceConnectionManager.connectionState
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/utils/VoucherRegexHelperParameterizedTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/utils/VoucherRegexHelperParameterizedTest.kt
new file mode 100644
index 0000000000..60db08b85f
--- /dev/null
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/utils/VoucherRegexHelperParameterizedTest.kt
@@ -0,0 +1,45 @@
+package net.mullvad.mullvadvpn.utils
+
+import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule
+import net.mullvad.mullvadvpn.util.VoucherRegexHelper
+import org.hamcrest.CoreMatchers.equalTo
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+private const val IS_ACCEPTED_FORMAT = true
+private const val IS_UNACCEPTED_FORMAT = false
+
+@RunWith(Parameterized::class)
+class VoucherRegexHelperParameterizedTest(
+ private val isValid: Boolean,
+ private val voucher: String
+) {
+ @get:Rule val testCoroutineRule = TestCoroutineRule()
+
+ @Test
+ fun testVoucherFormat() {
+ assertThat(VoucherRegexHelper.validate(voucher), equalTo(isValid))
+ }
+
+ companion object {
+ @JvmStatic
+ @Parameterized.Parameters
+ fun data(): Collection<Array<Any>> =
+ listOf(
+ arrayOf(IS_ACCEPTED_FORMAT, "1"),
+ arrayOf(IS_ACCEPTED_FORMAT, "a"),
+ arrayOf(IS_ACCEPTED_FORMAT, "A"),
+ arrayOf(IS_ACCEPTED_FORMAT, "AAAA"),
+ arrayOf(IS_ACCEPTED_FORMAT, "AAAABBBB11112222"),
+ arrayOf(IS_ACCEPTED_FORMAT, "AAAA BBBB 1111 2222"),
+ arrayOf(IS_ACCEPTED_FORMAT, "AAAA-AAAA-1111-2222\r"),
+ arrayOf(IS_ACCEPTED_FORMAT, "AAAA-AAAA-1111-2222\n"),
+ arrayOf(IS_UNACCEPTED_FORMAT, "@"),
+ arrayOf(IS_UNACCEPTED_FORMAT, "AAAABBBBCCCCDDDD\t"),
+ arrayOf(IS_UNACCEPTED_FORMAT, "AAAA_BBBB_CCCC_DDDD")
+ )
+ }
+}
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/VoucherDialogViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/VoucherDialogViewModelTest.kt
index bf87799d87..01ce6659cb 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/VoucherDialogViewModelTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/VoucherDialogViewModelTest.kt
@@ -1,22 +1,29 @@
package net.mullvad.mullvadvpn.viewmodel
import android.content.res.Resources
+import app.cash.turbine.test
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.mockk
-import io.mockk.mockkStatic
import io.mockk.unmockkAll
+import kotlin.test.assertEquals
+import kotlin.test.assertIs
+import kotlin.test.assertTrue
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
+import net.mullvad.mullvadvpn.compose.state.VoucherDialogState
import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule
+import net.mullvad.mullvadvpn.model.VoucherSubmission
import net.mullvad.mullvadvpn.model.VoucherSubmissionError
import net.mullvad.mullvadvpn.model.VoucherSubmissionResult
import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionContainer
import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager
+import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionState
import net.mullvad.mullvadvpn.ui.serviceconnection.VoucherRedeemer
+import net.mullvad.mullvadvpn.ui.serviceconnection.voucherRedeemer
import org.junit.After
import org.junit.Before
-import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
@@ -24,21 +31,19 @@ class VoucherDialogViewModelTest {
@get:Rule val testCoroutineRule = TestCoroutineRule()
private val mockServiceConnectionManager: ServiceConnectionManager = mockk()
- private val mockVoucherRedeemer: VoucherRedeemer = mockk()
private val mockServiceConnectionContainer: ServiceConnectionContainer = mockk()
- private val mockResources: Resources = mockk()
+ private val mockVoucherSubmission: VoucherSubmission = mockk()
+ private val serviceConnectionState =
+ MutableStateFlow<ServiceConnectionState>(ServiceConnectionState.Disconnected)
- private val mockVoucherSubmissionErrorResult: VoucherSubmissionResult =
- VoucherSubmissionResult.Error(VoucherSubmissionError.OtherError)
+ private val mockVoucherRedeemer: VoucherRedeemer = mockk()
+ private val mockResources: Resources = mockk()
private lateinit var viewModel: VoucherDialogViewModel
@Before
fun setUp() {
- mockkStatic(CACHE_EXTENSION_CLASS)
- every { mockServiceConnectionManager.connectionState.value.readyContainer() } returns
- mockServiceConnectionContainer
- every { mockServiceConnectionContainer.voucherRedeemer } returns mockVoucherRedeemer
+ every { mockServiceConnectionManager.connectionState } returns serviceConnectionState
viewModel =
VoucherDialogViewModel(
@@ -53,21 +58,73 @@ class VoucherDialogViewModelTest {
}
@Test
- @Ignore("TODO: Fix this failing test and then enable it again.")
- fun test_submit_invalid_voucher() = runTest {
+ fun testSubmitVoucher() = runTest {
+ val voucher = DUMMY_INVALID_VOUCHER
+ val dummyStringResource = DUMMY_STRING_RESOURCE
+
+ // Arrange
+ every { mockServiceConnectionManager.voucherRedeemer() } returns mockVoucherRedeemer
+ every { mockVoucherSubmission.timeAdded } returns 0
+ coEvery { mockVoucherRedeemer.submit(voucher) } returns
+ VoucherSubmissionResult.Ok(mockVoucherSubmission)
+
+ // Act
+ assertIs<VoucherDialogState.Default>(viewModel.uiState.value.voucherViewModelState)
+ viewModel.onRedeem(voucher)
+
+ // Assert
+ coVerify(exactly = 1) { mockVoucherRedeemer.submit(voucher) }
+ }
+
+ @Test
+ fun testInsertInvalidVoucher() = runTest {
+ val voucher = DUMMY_INVALID_VOUCHER
+ val dummyStringResource = DUMMY_STRING_RESOURCE
+
+ // Arrange
+ every { mockServiceConnectionManager.voucherRedeemer() } returns mockVoucherRedeemer
+ every { mockResources.getString(any()) } returns dummyStringResource
+ every { mockVoucherSubmission.timeAdded } returns 0
+ coEvery { mockVoucherRedeemer.submit(voucher) } returns
+ VoucherSubmissionResult.Error(VoucherSubmissionError.OtherError)
+
+ // Act, Assert
+ viewModel.uiState.test {
+ assertEquals(viewModel.uiState.value, awaitItem())
+ serviceConnectionState.value =
+ ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
+ viewModel.onRedeem(voucher)
+ assertTrue { awaitItem().voucherViewModelState is VoucherDialogState.Verifying }
+ assertTrue { awaitItem().voucherViewModelState is VoucherDialogState.Error }
+ }
+ }
+
+ @Test
+ fun testInsertValidVoucher() = runTest {
val voucher = DUMMY_VALID_VOUCHER
val dummyStringResource = DUMMY_STRING_RESOURCE
+
// Arrange
+ every { mockServiceConnectionManager.voucherRedeemer() } returns mockVoucherRedeemer
every { mockResources.getString(any()) } returns dummyStringResource
- coEvery { mockVoucherRedeemer.submit(voucher) } returns mockVoucherSubmissionErrorResult
+ every { mockVoucherSubmission.timeAdded } returns 0
+ coEvery { mockVoucherRedeemer.submit(voucher) } returns
+ VoucherSubmissionResult.Ok(VoucherSubmission(0, DUMMY_STRING_RESOURCE))
+
// Act, Assert
- viewModel.onRedeem(voucher)
- coVerify(exactly = 1) { mockVoucherRedeemer.submit(voucher) }
+ viewModel.uiState.test {
+ assertEquals(viewModel.uiState.value, awaitItem())
+ serviceConnectionState.value =
+ ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
+ viewModel.onRedeem(voucher)
+ assertTrue { awaitItem().voucherViewModelState is VoucherDialogState.Verifying }
+ assertTrue { awaitItem().voucherViewModelState is VoucherDialogState.Success }
+ }
}
companion object {
- private const val CACHE_EXTENSION_CLASS = "net.mullvad.mullvadvpn.util.CacheExtensionsKt"
- private const val DUMMY_VALID_VOUCHER = "DUMMY_VALID_VOUCHER"
- private const val DUMMY_STRING_RESOURCE = "DUMMY_STRING_RESOURCE"
+ private const val DUMMY_VALID_VOUCHER = "dummy_valid_voucher"
+ private const val DUMMY_INVALID_VOUCHER = "dummy_invalid_voucher"
+ private const val DUMMY_STRING_RESOURCE = "dummy_string_resource"
}
}