summaryrefslogtreecommitdiffhomepage
path: root/android
diff options
context:
space:
mode:
Diffstat (limited to 'android')
-rw-r--r--android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/AccountScreenTest.kt234
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/PlayPayment.kt5
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/test/ComposeTestTagConstants.kt3
3 files changed, 237 insertions, 5 deletions
diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/AccountScreenTest.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/AccountScreenTest.kt
index aec6c85595..e997ae29e4 100644
--- a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/AccountScreenTest.kt
+++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/AccountScreenTest.kt
@@ -1,21 +1,33 @@
package net.mullvad.mullvadvpn.compose.screen
+import android.app.Activity
import androidx.compose.material3.ExperimentalMaterial3Api
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 io.mockk.MockKAnnotations
+import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import net.mullvad.mullvadvpn.compose.setContentWithTheme
+import net.mullvad.mullvadvpn.compose.state.PaymentState
+import net.mullvad.mullvadvpn.compose.test.PLAY_PAYMENT_INFO_ICON_TEST_TAG
+import net.mullvad.mullvadvpn.lib.payment.model.PaymentProduct
+import net.mullvad.mullvadvpn.lib.payment.model.PaymentStatus
+import net.mullvad.mullvadvpn.lib.payment.model.ProductId
+import net.mullvad.mullvadvpn.lib.payment.model.ProductPrice
+import net.mullvad.mullvadvpn.lib.payment.model.PurchaseResult
+import net.mullvad.mullvadvpn.util.toPaymentDialogData
import net.mullvad.mullvadvpn.viewmodel.AccountUiState
import net.mullvad.mullvadvpn.viewmodel.AccountViewModel
import org.junit.Before
import org.junit.Rule
import org.junit.Test
+@OptIn(ExperimentalMaterial3Api::class)
class AccountScreenTest {
@get:Rule val composeTestRule = createComposeRule()
@@ -24,12 +36,12 @@ class AccountScreenTest {
MockKAnnotations.init(this)
}
- @OptIn(ExperimentalMaterial3Api::class)
@Test
fun testDefaultState() {
// Arrange
composeTestRule.setContentWithTheme {
AccountScreen(
+ showSitePayment = true,
uiState =
AccountUiState(
deviceName = DUMMY_DEVICE_NAME,
@@ -48,13 +60,13 @@ class AccountScreenTest {
}
}
- @OptIn(ExperimentalMaterial3Api::class)
@Test
fun testManageAccountClick() {
// Arrange
val mockedClickHandler: () -> Unit = mockk(relaxed = true)
composeTestRule.setContentWithTheme {
AccountScreen(
+ showSitePayment = true,
uiState =
AccountUiState(
deviceName = DUMMY_DEVICE_NAME,
@@ -74,13 +86,13 @@ class AccountScreenTest {
verify { mockedClickHandler.invoke() }
}
- @OptIn(ExperimentalMaterial3Api::class)
@Test
fun testRedeemVoucherClick() {
// Arrange
val mockedClickHandler: () -> Unit = mockk(relaxed = true)
composeTestRule.setContentWithTheme {
AccountScreen(
+ showSitePayment = true,
uiState =
AccountUiState(
deviceName = DUMMY_DEVICE_NAME,
@@ -100,13 +112,13 @@ class AccountScreenTest {
verify { mockedClickHandler.invoke() }
}
- @OptIn(ExperimentalMaterial3Api::class)
@Test
fun testLogoutClick() {
// Arrange
val mockedClickHandler: () -> Unit = mockk(relaxed = true)
composeTestRule.setContentWithTheme {
AccountScreen(
+ showSitePayment = true,
uiState =
AccountUiState(
deviceName = DUMMY_DEVICE_NAME,
@@ -126,6 +138,220 @@ class AccountScreenTest {
verify { mockedClickHandler.invoke() }
}
+ @Test
+ fun testShowPurchaseCompleteDialog() {
+ // Arrange
+ composeTestRule.setContentWithTheme {
+ AccountScreen(
+ showSitePayment = true,
+ uiState =
+ AccountUiState.default()
+ .copy(
+ paymentDialogData =
+ PurchaseResult.Completed.Success.toPaymentDialogData()
+ ),
+ uiSideEffect = MutableSharedFlow<AccountViewModel.UiSideEffect>().asSharedFlow(),
+ enterTransitionEndAction = MutableSharedFlow<Unit>().asSharedFlow()
+ )
+ }
+
+ // Assert
+ composeTestRule.onNodeWithText("Time was successfully added").assertExists()
+ }
+
+ @Test
+ fun testShowVerificationErrorDialog() {
+ // Arrange
+ composeTestRule.setContentWithTheme {
+ AccountScreen(
+ showSitePayment = true,
+ uiState =
+ AccountUiState.default()
+ .copy(
+ paymentDialogData =
+ PurchaseResult.Error.VerificationError(null).toPaymentDialogData()
+ ),
+ uiSideEffect = MutableSharedFlow<AccountViewModel.UiSideEffect>().asSharedFlow(),
+ enterTransitionEndAction = MutableSharedFlow<Unit>().asSharedFlow()
+ )
+ }
+
+ // Assert
+ composeTestRule.onNodeWithText("Verifying purchase").assertExists()
+ }
+
+ @Test
+ fun testShowFetchProductsErrorDialog() {
+ // Arrange
+ composeTestRule.setContentWithTheme {
+ AccountScreen(
+ showSitePayment = true,
+ uiState =
+ AccountUiState.default()
+ .copy(
+ paymentDialogData =
+ PurchaseResult.Error.FetchProductsError(ProductId(""), null)
+ .toPaymentDialogData()
+ ),
+ uiSideEffect = MutableSharedFlow<AccountViewModel.UiSideEffect>().asSharedFlow(),
+ enterTransitionEndAction = MutableSharedFlow<Unit>().asSharedFlow()
+ )
+ }
+
+ // Assert
+ composeTestRule.onNodeWithText("Google Play unavailable").assertExists()
+ }
+
+ @Test
+ fun testShowBillingErrorPaymentButton() {
+ // Arrange
+ composeTestRule.setContentWithTheme {
+ AccountScreen(
+ showSitePayment = true,
+ uiState =
+ AccountUiState.default().copy(billingPaymentState = PaymentState.Error.Billing),
+ uiSideEffect = MutableSharedFlow<AccountViewModel.UiSideEffect>().asSharedFlow(),
+ enterTransitionEndAction = MutableSharedFlow<Unit>().asSharedFlow()
+ )
+ }
+
+ // Assert
+ composeTestRule.onNodeWithText("Add 30 days time").assertExists()
+ }
+
+ @Test
+ fun testShowBillingPaymentAvailable() {
+ // Arrange
+ val mockPaymentProduct: PaymentProduct = mockk()
+ every { mockPaymentProduct.price } returns ProductPrice("$10")
+ every { mockPaymentProduct.status } returns null
+ composeTestRule.setContentWithTheme {
+ AccountScreen(
+ showSitePayment = true,
+ uiState =
+ AccountUiState.default()
+ .copy(
+ billingPaymentState =
+ PaymentState.PaymentAvailable(listOf(mockPaymentProduct))
+ ),
+ uiSideEffect = MutableSharedFlow<AccountViewModel.UiSideEffect>().asSharedFlow(),
+ enterTransitionEndAction = MutableSharedFlow<Unit>().asSharedFlow()
+ )
+ }
+
+ // Assert
+ composeTestRule.onNodeWithText("Add 30 days time ($10)").assertExists()
+ }
+
+ @Test
+ fun testShowPendingPayment() {
+ // Arrange
+ val mockPaymentProduct: PaymentProduct = mockk()
+ every { mockPaymentProduct.price } returns ProductPrice("$10")
+ every { mockPaymentProduct.status } returns PaymentStatus.PENDING
+ composeTestRule.setContentWithTheme {
+ AccountScreen(
+ showSitePayment = true,
+ uiState =
+ AccountUiState.default()
+ .copy(
+ billingPaymentState =
+ PaymentState.PaymentAvailable(listOf(mockPaymentProduct))
+ ),
+ uiSideEffect = MutableSharedFlow<AccountViewModel.UiSideEffect>().asSharedFlow(),
+ enterTransitionEndAction = MutableSharedFlow<Unit>().asSharedFlow()
+ )
+ }
+
+ // Assert
+ composeTestRule.onNodeWithText("Google Play payment pending").assertExists()
+ }
+
+ @Test
+ fun testShowPendingPaymentInfoDialog() {
+ // Arrange
+ val mockPaymentProduct: PaymentProduct = mockk()
+ every { mockPaymentProduct.price } returns ProductPrice("$10")
+ every { mockPaymentProduct.status } returns PaymentStatus.PENDING
+ composeTestRule.setContentWithTheme {
+ AccountScreen(
+ showSitePayment = true,
+ uiState =
+ AccountUiState.default()
+ .copy(
+ billingPaymentState =
+ PaymentState.PaymentAvailable(listOf(mockPaymentProduct))
+ ),
+ uiSideEffect = MutableSharedFlow<AccountViewModel.UiSideEffect>().asSharedFlow(),
+ enterTransitionEndAction = MutableSharedFlow<Unit>().asSharedFlow()
+ )
+ }
+
+ // Act
+ composeTestRule.onNodeWithTag(PLAY_PAYMENT_INFO_ICON_TEST_TAG).performClick()
+
+ // Assert
+ composeTestRule
+ .onNodeWithText(
+ "We are currently verifying your purchase, this might take some time. Your time will be added if the verification is successful."
+ )
+ .assertExists()
+ }
+
+ @Test
+ fun testShowVerificationInProgress() {
+ // Arrange
+ val mockPaymentProduct: PaymentProduct = mockk()
+ every { mockPaymentProduct.price } returns ProductPrice("$10")
+ every { mockPaymentProduct.status } returns PaymentStatus.VERIFICATION_IN_PROGRESS
+ composeTestRule.setContentWithTheme {
+ AccountScreen(
+ showSitePayment = true,
+ uiState =
+ AccountUiState.default()
+ .copy(
+ billingPaymentState =
+ PaymentState.PaymentAvailable(listOf(mockPaymentProduct))
+ ),
+ uiSideEffect = MutableSharedFlow<AccountViewModel.UiSideEffect>().asSharedFlow(),
+ enterTransitionEndAction = MutableSharedFlow<Unit>().asSharedFlow()
+ )
+ }
+
+ // Assert
+ composeTestRule.onNodeWithText("Verifying purchase").assertExists()
+ }
+
+ @Test
+ fun testOnPurchaseBillingProductClick() {
+ // Arrange
+ val clickHandler: (ProductId, () -> Activity) -> Unit = mockk(relaxed = true)
+ val mockPaymentProduct: PaymentProduct = mockk()
+ every { mockPaymentProduct.price } returns ProductPrice("$10")
+ every { mockPaymentProduct.productId } returns ProductId("PRODUCT_ID")
+ every { mockPaymentProduct.status } returns null
+ composeTestRule.setContentWithTheme {
+ AccountScreen(
+ showSitePayment = true,
+ uiState =
+ AccountUiState.default()
+ .copy(
+ billingPaymentState =
+ PaymentState.PaymentAvailable(listOf(mockPaymentProduct))
+ ),
+ onPurchaseBillingProductClick = clickHandler,
+ uiSideEffect = MutableSharedFlow<AccountViewModel.UiSideEffect>().asSharedFlow(),
+ enterTransitionEndAction = MutableSharedFlow<Unit>().asSharedFlow()
+ )
+ }
+
+ // Act
+ composeTestRule.onNodeWithText("Add 30 days time ($10)").performClick()
+
+ // Assert
+ verify { clickHandler.invoke(ProductId("PRODUCT_ID"), any()) }
+ }
+
companion object {
private const val DUMMY_DEVICE_NAME = "fake_name"
private const val DUMMY_ACCOUNT_NUMBER = "fake_number"
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/PlayPayment.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/PlayPayment.kt
index a56d3dff74..3f396cf698 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/PlayPayment.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/PlayPayment.kt
@@ -13,12 +13,14 @@ 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.stringResource
import androidx.compose.ui.tooling.preview.Preview
import net.mullvad.mullvadvpn.R
import net.mullvad.mullvadvpn.compose.button.VariantButton
import net.mullvad.mullvadvpn.compose.state.PaymentState
+import net.mullvad.mullvadvpn.compose.test.PLAY_PAYMENT_INFO_ICON_TEST_TAG
import net.mullvad.mullvadvpn.lib.payment.ProductIds
import net.mullvad.mullvadvpn.lib.payment.model.PaymentProduct
import net.mullvad.mullvadvpn.lib.payment.model.PaymentStatus
@@ -156,7 +158,8 @@ fun PlayPayment(
modifier = Modifier.padding(bottom = Dimens.smallPadding)
)
IconButton(
- onClick = onInfoClick
+ onClick = onInfoClick,
+ modifier = Modifier.testTag(PLAY_PAYMENT_INFO_ICON_TEST_TAG)
) {
Icon(
painter = painterResource(id = R.drawable.icon_info),
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 dea9e12a3d..14a42403e1 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
@@ -26,4 +26,7 @@ const val LOCATION_INFO_TEST_TAG = "location_info_test_tag"
const val NOTIFICATION_BANNER = "notification_banner"
const val NOTIFICATION_BANNER_ACTION = "notification_banner_action"
+// PlayPayment
+const val PLAY_PAYMENT_INFO_ICON_TEST_TAG = "play_payment_info_icon_test_tag"
+
const val LOGIN_TITLE_TEST_TAG = "login_title_test_tag"