From 8a053886985302c17610996a190455c607969e04 Mon Sep 17 00:00:00 2001 From: Marcos Date: Thu, 23 Nov 2023 15:08:30 +0000 Subject: [PATCH] Add welcome screen --- .../ContentScopeScriptsJsMessaging.kt | 13 +++-- .../js/messaging/api/JsMessaging.kt | 3 +- .../impl/SubscriptionsManager.kt | 5 +- .../SubscriptionMessagingInterface.kt | 14 ++++-- .../impl/ui/SubscriptionWebViewViewModel.kt | 49 ++++++++++++++----- .../impl/ui/SubscriptionsWebViewActivity.kt | 22 +++++++-- .../SubscriptionMessagingInterfaceTest.kt | 46 ++++++++++------- .../ui/SubscriptionWebViewViewModelTest.kt | 17 ++++++- 8 files changed, 125 insertions(+), 44 deletions(-) diff --git a/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/messaging/ContentScopeScriptsJsMessaging.kt b/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/messaging/ContentScopeScriptsJsMessaging.kt index d03903007765..aa1ce1d9cdf2 100644 --- a/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/messaging/ContentScopeScriptsJsMessaging.kt +++ b/content-scope-scripts/content-scope-scripts-impl/src/main/java/com/duckduckgo/contentscopescripts/impl/messaging/ContentScopeScriptsJsMessaging.kt @@ -29,9 +29,10 @@ import com.duckduckgo.js.messaging.api.JsMessageHandler import com.duckduckgo.js.messaging.api.JsMessageHelper import com.duckduckgo.js.messaging.api.JsMessaging import com.duckduckgo.js.messaging.api.JsRequestResponse +import com.duckduckgo.js.messaging.api.SubscriptionEvent +import com.duckduckgo.js.messaging.api.SubscriptionEventData import com.squareup.anvil.annotations.ContributesBinding import com.squareup.moshi.Moshi -import java.util.* import javax.inject.Inject import javax.inject.Named import kotlinx.coroutines.runBlocking @@ -86,8 +87,14 @@ class ContentScopeScriptsJsMessaging @Inject constructor( this.webView.addJavascriptInterface(this, coreContentScopeScripts.javascriptInterface) } - override fun sendSubscriptionEvent() { - // NOOP + override fun sendSubscriptionEvent(subscriptionEventData: SubscriptionEventData) { + val subscriptionEvent = SubscriptionEvent( + context, + subscriptionEventData.featureName, + subscriptionEventData.subscriptionName, + subscriptionEventData.params, + ) + jsMessageHelper.sendSubscriptionEvent(subscriptionEvent, callbackName, secret, webView) } override fun onResponse(response: JsCallbackData) { diff --git a/js-messaging/js-messaging-api/src/main/java/com/duckduckgo/js/messaging/api/JsMessaging.kt b/js-messaging/js-messaging-api/src/main/java/com/duckduckgo/js/messaging/api/JsMessaging.kt index 8e5a4dd22673..96a9eff3c031 100644 --- a/js-messaging/js-messaging-api/src/main/java/com/duckduckgo/js/messaging/api/JsMessaging.kt +++ b/js-messaging/js-messaging-api/src/main/java/com/duckduckgo/js/messaging/api/JsMessaging.kt @@ -41,7 +41,7 @@ interface JsMessaging { /** * Method to send a subscription event */ - fun sendSubscriptionEvent() + fun sendSubscriptionEvent(subscriptionEventData: SubscriptionEventData) /** * Context name @@ -139,4 +139,5 @@ sealed class JsRequestResponse { ) : JsRequestResponse() } +data class SubscriptionEventData(val featureName: String, val subscriptionName: String, val params: JSONObject?) data class SubscriptionEvent(val context: String, val featureName: String, val subscriptionName: String, val params: JSONObject?) diff --git a/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/SubscriptionsManager.kt b/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/SubscriptionsManager.kt index 063c20248d9d..e9f5dff3fdc1 100644 --- a/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/SubscriptionsManager.kt +++ b/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/SubscriptionsManager.kt @@ -36,6 +36,7 @@ import com.squareup.moshi.Moshi import dagger.SingleInstanceIn import javax.inject.Inject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow @@ -124,6 +125,7 @@ class RealSubscriptionsManager @Inject constructor( private val _hasSubscription = MutableStateFlow(false) override val hasSubscription = _hasSubscription.asStateFlow().onSubscription { emitHasSubscriptionsValues() } + private var purchaseStateJob: Job? = null private fun isUserAuthenticated(): Boolean = !authDataStore.accessToken.isNullOrBlank() && !authDataStore.authToken.isNullOrBlank() private suspend fun emitHasSubscriptionsValues() { @@ -133,7 +135,8 @@ class RealSubscriptionsManager @Inject constructor( } private suspend fun emitCurrentPurchaseValues() { - coroutineScope.launch(dispatcherProvider.io()) { + purchaseStateJob?.cancel() + purchaseStateJob = coroutineScope.launch(dispatcherProvider.io()) { billingClientWrapper.purchaseState.collect { when (it) { is PurchaseState.Purchased -> checkPurchase() diff --git a/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/messaging/SubscriptionMessagingInterface.kt b/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/messaging/SubscriptionMessagingInterface.kt index 5e22ef2dd578..a5029eeff507 100644 --- a/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/messaging/SubscriptionMessagingInterface.kt +++ b/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/messaging/SubscriptionMessagingInterface.kt @@ -29,6 +29,8 @@ import com.duckduckgo.js.messaging.api.JsMessageHandler import com.duckduckgo.js.messaging.api.JsMessageHelper import com.duckduckgo.js.messaging.api.JsMessaging import com.duckduckgo.js.messaging.api.JsRequestResponse +import com.duckduckgo.js.messaging.api.SubscriptionEvent +import com.duckduckgo.js.messaging.api.SubscriptionEventData import com.duckduckgo.subscriptions.impl.AuthToken import com.duckduckgo.subscriptions.impl.JSONObjectAdapter import com.duckduckgo.subscriptions.impl.SubscriptionsManager @@ -88,8 +90,14 @@ class SubscriptionMessagingInterface @Inject constructor( this.webView.addJavascriptInterface(this, context) } - override fun sendSubscriptionEvent() { - // NOOP + override fun sendSubscriptionEvent(subscriptionEventData: SubscriptionEventData) { + val subscriptionEvent = SubscriptionEvent( + context, + subscriptionEventData.featureName, + subscriptionEventData.subscriptionName, + subscriptionEventData.params, + ) + jsMessageHelper.sendSubscriptionEvent(subscriptionEvent, callbackName, secret, webView) } override fun onResponse(response: JsCallbackData) { @@ -116,7 +124,7 @@ class SubscriptionMessagingInterface @Inject constructor( override val allowedDomains: List = emptyList() override val featureName: String = "useSubscription" - override val methods: List = listOf("subscriptionSelected", "getSubscriptionOptions", "backToSettings") + override val methods: List = listOf("subscriptionSelected", "getSubscriptionOptions", "backToSettings", "activateSubscription") } inner class GetSubscriptionMessage( diff --git a/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/ui/SubscriptionWebViewViewModel.kt b/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/ui/SubscriptionWebViewViewModel.kt index 675848f37a90..caecc9bf74c6 100644 --- a/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/ui/SubscriptionWebViewViewModel.kt +++ b/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/ui/SubscriptionWebViewViewModel.kt @@ -23,6 +23,7 @@ import com.duckduckgo.anvil.annotations.ContributesViewModel import com.duckduckgo.common.utils.DispatcherProvider import com.duckduckgo.di.scopes.ActivityScope import com.duckduckgo.js.messaging.api.JsCallbackData +import com.duckduckgo.js.messaging.api.SubscriptionEventData import com.duckduckgo.subscriptions.impl.CurrentPurchase import com.duckduckgo.subscriptions.impl.JSONObjectAdapter import com.duckduckgo.subscriptions.impl.SubscriptionsConstants.ITR @@ -50,6 +51,9 @@ import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch import org.json.JSONObject @@ -73,18 +77,23 @@ class SubscriptionWebViewViewModel @Inject constructor( val currentPurchaseViewState = _currentPurchaseViewState.asStateFlow() fun start() { - viewModelScope.launch { - subscriptionsManager.currentPurchaseState.collect { - val state = when (it) { - is CurrentPurchase.Failure -> Failure(it.message) - is CurrentPurchase.Success -> Success - is CurrentPurchase.InProgress, CurrentPurchase.PreFlowInProgress -> InProgress - is CurrentPurchase.Recovered -> Recovered - is CurrentPurchase.PreFlowFinished -> Inactive - } - _currentPurchaseViewState.emit(currentPurchaseViewState.value.copy(purchaseState = state)) + subscriptionsManager.currentPurchaseState.onEach { + val state = when (it) { + is CurrentPurchase.Failure -> Failure(it.message) + is CurrentPurchase.Success -> Success( + SubscriptionEventData( + PURCHASE_COMPLETED_FEATURE_NAME, + PURCHASE_COMPLETED_SUBSCRIPTION_NAME, + JSONObject(PURCHASE_COMPLETED_JSON), + ), + ) + is CurrentPurchase.InProgress, CurrentPurchase.PreFlowInProgress -> InProgress + is CurrentPurchase.Recovered -> Recovered + is CurrentPurchase.PreFlowFinished -> Inactive } - } + _currentPurchaseViewState.emit(currentPurchaseViewState.value.copy(purchaseState = state)) + }.flowOn(dispatcherProvider.io()) + .launchIn(viewModelScope) } fun processJsCallbackMessage(featureName: String, method: String, id: String?, data: JSONObject?) { @@ -92,6 +101,7 @@ class SubscriptionWebViewViewModel @Inject constructor( "backToSettings" -> backToSettings() "getSubscriptionOptions" -> id?.let { getSubscriptionOptions(featureName, method, it) } "subscriptionSelected" -> subscriptionSelected(data) + "activateSubscription" -> activateOnAnotherDevice() else -> { // NOOP } @@ -102,7 +112,7 @@ class SubscriptionWebViewViewModel @Inject constructor( viewModelScope.launch(dispatcherProvider.io()) { val id = runCatching { data?.getString("id") }.getOrNull() if (id.isNullOrBlank()) { - _currentPurchaseViewState.emit(currentPurchaseViewState.value.copy(purchaseState = Failure("context"))) + _currentPurchaseViewState.emit(currentPurchaseViewState.value.copy(purchaseState = Failure(""))) } else { command.send(SubscriptionSelected(id)) } @@ -149,6 +159,12 @@ class SubscriptionWebViewViewModel @Inject constructor( } } + private fun activateOnAnotherDevice() { + viewModelScope.launch { + command.send(ActivateOnAnotherDevice) + } + } + private fun backToSettings() { viewModelScope.launch { command.send(BackToSettings) @@ -172,7 +188,7 @@ class SubscriptionWebViewViewModel @Inject constructor( sealed class PurchaseStateView { data object Inactive : PurchaseStateView() data object InProgress : PurchaseStateView() - data object Success : PurchaseStateView() + data class Success(val subscriptionEventData: SubscriptionEventData) : PurchaseStateView() data object Recovered : PurchaseStateView() data class Failure(val message: String) : PurchaseStateView() } @@ -181,5 +197,12 @@ class SubscriptionWebViewViewModel @Inject constructor( data object BackToSettings : Command() data class SendResponseToJs(val data: JsCallbackData) : Command() data class SubscriptionSelected(val id: String) : Command() + data object ActivateOnAnotherDevice : Command() + } + + companion object { + const val PURCHASE_COMPLETED_FEATURE_NAME = "useSubscription" + const val PURCHASE_COMPLETED_SUBSCRIPTION_NAME = "onPurchaseUpdate" + const val PURCHASE_COMPLETED_JSON = """{ type: "completed" }""" } } diff --git a/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/ui/SubscriptionsWebViewActivity.kt b/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/ui/SubscriptionsWebViewActivity.kt index fcdc177af358..cf23463ff7ac 100644 --- a/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/ui/SubscriptionsWebViewActivity.kt +++ b/subscriptions/subscriptions-impl/src/main/java/com/duckduckgo/subscriptions/impl/ui/SubscriptionsWebViewActivity.kt @@ -21,6 +21,7 @@ import android.os.Bundle import android.view.MenuItem import android.webkit.WebChromeClient import android.webkit.WebSettings +import android.webkit.WebViewClient import androidx.lifecycle.Lifecycle import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope @@ -35,16 +36,20 @@ import com.duckduckgo.di.scopes.ActivityScope import com.duckduckgo.js.messaging.api.JsCallbackData import com.duckduckgo.js.messaging.api.JsMessageCallback import com.duckduckgo.js.messaging.api.JsMessaging +import com.duckduckgo.js.messaging.api.SubscriptionEventData import com.duckduckgo.navigation.api.GlobalActivityStarter import com.duckduckgo.navigation.api.getActivityParams import com.duckduckgo.subscriptions.impl.R.string import com.duckduckgo.subscriptions.impl.databinding.ActivitySubscriptionsWebviewBinding +import com.duckduckgo.subscriptions.impl.ui.AddDeviceActivity.Companion.AddDeviceScreenWithEmptyParams import com.duckduckgo.subscriptions.impl.ui.SubscriptionWebViewViewModel.Command +import com.duckduckgo.subscriptions.impl.ui.SubscriptionWebViewViewModel.Command.ActivateOnAnotherDevice import com.duckduckgo.subscriptions.impl.ui.SubscriptionWebViewViewModel.Command.BackToSettings import com.duckduckgo.subscriptions.impl.ui.SubscriptionWebViewViewModel.Command.SendResponseToJs import com.duckduckgo.subscriptions.impl.ui.SubscriptionWebViewViewModel.Command.SubscriptionSelected import com.duckduckgo.subscriptions.impl.ui.SubscriptionWebViewViewModel.PurchaseStateView import com.duckduckgo.user.agent.api.UserAgentProvider +import kotlinx.coroutines.flow.distinctUntilChanged import javax.inject.Inject import javax.inject.Named import kotlinx.coroutines.flow.launchIn @@ -67,6 +72,9 @@ class SubscriptionsWebViewActivity : DuckDuckGoActivity() { @Inject lateinit var userAgent: UserAgentProvider + @Inject + lateinit var globalActivityStarter: GlobalActivityStarter + private val viewModel: SubscriptionWebViewViewModel by bindViewModel() private val binding: ActivitySubscriptionsWebviewBinding by viewBinding() @@ -95,6 +103,7 @@ class SubscriptionsWebViewActivity : DuckDuckGoActivity() { }, ) it.webChromeClient = WebChromeClient() + it.webViewClient = WebViewClient() it.settings.apply { userAgentString = userAgent.userAgent(url) javaScriptEnabled = true @@ -121,7 +130,7 @@ class SubscriptionsWebViewActivity : DuckDuckGoActivity() { .onEach { processCommand(it) } .launchIn(lifecycleScope) - viewModel.currentPurchaseViewState.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED).onEach { + viewModel.currentPurchaseViewState.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED).distinctUntilChanged().onEach { renderPurchaseState(it.purchaseState) }.launchIn(lifecycleScope) } @@ -131,6 +140,7 @@ class SubscriptionsWebViewActivity : DuckDuckGoActivity() { is BackToSettings -> backToSettings() is SendResponseToJs -> sendResponseToJs(command.data) is SubscriptionSelected -> selectSubscription(command.id) + is ActivateOnAnotherDevice -> activateOnAnotherDevice() } } @@ -147,7 +157,7 @@ class SubscriptionsWebViewActivity : DuckDuckGoActivity() { is PurchaseStateView.Success -> { binding.webview.show() binding.progress.gone() - onPurchaseSuccess() + onPurchaseSuccess(purchaseState.subscriptionEventData) } is PurchaseStateView.Recovered -> { binding.webview.show() @@ -177,7 +187,7 @@ class SubscriptionsWebViewActivity : DuckDuckGoActivity() { .show() } - private fun onPurchaseSuccess() { + private fun onPurchaseSuccess(subscriptionEventData: SubscriptionEventData) { TextAlertDialogBuilder(this) .setTitle(getString(string.purchaseCompletedTitle)) .setMessage(getString(string.purchaseCompletedText)) @@ -185,7 +195,7 @@ class SubscriptionsWebViewActivity : DuckDuckGoActivity() { .addEventListener( object : TextAlertDialogBuilder.EventListener() { override fun onPositiveButtonClicked() { - finish() + subscriptionJsMessaging.sendSubscriptionEvent(subscriptionEventData) } }, ) @@ -220,6 +230,10 @@ class SubscriptionsWebViewActivity : DuckDuckGoActivity() { finish() } + private fun activateOnAnotherDevice() { + globalActivityStarter.start(this, AddDeviceScreenWithEmptyParams) + } + override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { android.R.id.home -> { diff --git a/subscriptions/subscriptions-impl/src/test/java/com/duckduckgo/subscriptions/impl/messaging/SubscriptionMessagingInterfaceTest.kt b/subscriptions/subscriptions-impl/src/test/java/com/duckduckgo/subscriptions/impl/messaging/SubscriptionMessagingInterfaceTest.kt index 290de9abd318..534278cb0c28 100644 --- a/subscriptions/subscriptions-impl/src/test/java/com/duckduckgo/subscriptions/impl/messaging/SubscriptionMessagingInterfaceTest.kt +++ b/subscriptions/subscriptions-impl/src/test/java/com/duckduckgo/subscriptions/impl/messaging/SubscriptionMessagingInterfaceTest.kt @@ -185,7 +185,7 @@ class SubscriptionMessagingInterfaceTest { } @Test - fun whenProcessAndGetSubscriptionsIfNoIdDoNothing() = runTest { + fun whenProcessAndGetSubscriptionIfNoIdDoNothing() = runTest { givenInterfaceIsRegistered() givenAuthTokenIsSuccess() @@ -208,12 +208,11 @@ class SubscriptionMessagingInterfaceTest { messagingInterface.process(message, "duckduckgo-android-messaging-secret") - verifyNoInteractions(jsMessageHelper) assertEquals(0, callback.counter) } @Test - fun whenProcessAndBackToSettingsThenCallbackExecutedAndNotResponseSent() = runTest { + fun whenProcessAndBackToSettingsThenCallbackExecuted() = runTest { givenInterfaceIsRegistered() val message = """ @@ -222,24 +221,9 @@ class SubscriptionMessagingInterfaceTest { messagingInterface.process(message, "duckduckgo-android-messaging-secret") - verifyNoInteractions(jsMessageHelper) assertEquals(1, callback.counter) } - @Test - fun whenProcessAndBackToSettingsIfIdDoesNotExistThenDoNothing() = runTest { - givenInterfaceIsRegistered() - - val message = """ - {"context":"subscriptionPages","featureName":"test","method":"backToSettings","params":{}} - """.trimIndent() - - messagingInterface.process(message, "duckduckgo-android-messaging-secret") - - verifyNoInteractions(jsMessageHelper) - assertEquals(0, callback.counter) - } - @Test fun whenProcessAndSetSubscriptionMessageIfFeatureNameDoesNotMatchDoNothing() = runTest { givenInterfaceIsRegistered() @@ -345,6 +329,32 @@ class SubscriptionMessagingInterfaceTest { assertEquals(1, callback.counter) } + @Test + fun whenProcessAndActivateSubscriptionIfFeatureNameDoesNotMatchDoNothing() = runTest { + givenInterfaceIsRegistered() + + val message = """ + {"context":"subscriptionPages","featureName":"test","id":"myId","method":"activateSubscription","params":{}} + """.trimIndent() + + messagingInterface.process(message, "duckduckgo-android-messaging-secret") + + assertEquals(0, callback.counter) + } + + @Test + fun whenProcessAndActivateSubscriptionThenCallbackExecuted() = runTest { + givenInterfaceIsRegistered() + + val message = """ + {"context":"subscriptionPages","featureName":"useSubscription","id":"myId","method":"activateSubscription","params":{}} + """.trimIndent() + + messagingInterface.process(message, "duckduckgo-android-messaging-secret") + + assertEquals(1, callback.counter) + } + private fun givenInterfaceIsRegistered() { messagingInterface.register(webView, callback) whenever(webView.url).thenReturn("https://abrown.duckduckgo.com") diff --git a/subscriptions/subscriptions-impl/src/test/java/com/duckduckgo/subscriptions/impl/ui/SubscriptionWebViewViewModelTest.kt b/subscriptions/subscriptions-impl/src/test/java/com/duckduckgo/subscriptions/impl/ui/SubscriptionWebViewViewModelTest.kt index 20e72dc6df38..9041a7c62832 100644 --- a/subscriptions/subscriptions-impl/src/test/java/com/duckduckgo/subscriptions/impl/ui/SubscriptionWebViewViewModelTest.kt +++ b/subscriptions/subscriptions-impl/src/test/java/com/duckduckgo/subscriptions/impl/ui/SubscriptionWebViewViewModelTest.kt @@ -13,7 +13,9 @@ import com.duckduckgo.subscriptions.impl.SubscriptionsConstants.YEARLY_PLAN import com.duckduckgo.subscriptions.impl.SubscriptionsManager import com.duckduckgo.subscriptions.impl.repository.SubscriptionsRepository import com.duckduckgo.subscriptions.impl.ui.SubscriptionWebViewViewModel.Command +import com.duckduckgo.subscriptions.impl.ui.SubscriptionWebViewViewModel.Companion import com.duckduckgo.subscriptions.impl.ui.SubscriptionWebViewViewModel.PurchaseStateView +import com.duckduckgo.subscriptions.impl.ui.SubscriptionWebViewViewModel.PurchaseStateView.Success import com.duckduckgo.subscriptions.impl.ui.SubscriptionWebViewViewModel.SubscriptionOptionsJson import com.squareup.moshi.JsonAdapter import com.squareup.moshi.Moshi @@ -59,7 +61,12 @@ class SubscriptionWebViewViewModelTest { assertTrue(awaitItem().purchaseState is PurchaseStateView.Failure) flowTest.emit(CurrentPurchase.Success) - assertTrue(awaitItem().purchaseState is PurchaseStateView.Success) + val success = awaitItem().purchaseState + assertTrue(success is Success) + assertEquals(Companion.PURCHASE_COMPLETED_FEATURE_NAME, (success as Success).subscriptionEventData.featureName) + assertEquals(Companion.PURCHASE_COMPLETED_SUBSCRIPTION_NAME, success.subscriptionEventData.subscriptionName) + assertNotNull(success.subscriptionEventData.params) + assertEquals("completed", success.subscriptionEventData.params!!.getString("type")) flowTest.emit(CurrentPurchase.InProgress) assertTrue(awaitItem().purchaseState is PurchaseStateView.InProgress) @@ -141,6 +148,14 @@ class SubscriptionWebViewViewModelTest { } } + @Test + fun whenActivateSubscriptionThenCommandSent() = runTest { + viewModel.commands().test { + viewModel.processJsCallbackMessage("test", "activateSubscription", null, null) + assertTrue(awaitItem() is Command.ActivateOnAnotherDevice) + } + } + private fun getSubscriptionOfferDetails(planId: String): SubscriptionOfferDetails { val subscriptionOfferDetails: SubscriptionOfferDetails = mock() whenever(subscriptionOfferDetails.basePlanId).thenReturn(planId)