diff options
Diffstat (limited to 'android/app/src')
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" |
