Skip to content

Commit

Permalink
Merge pull request #1907 from Adyen/feature/pay-by-bank-us-stored-test
Browse files Browse the repository at this point in the history
Pay by Bank US - Stored Delegate Tests
  • Loading branch information
ozgur00 authored Dec 2, 2024
2 parents 76966c5 + d5a0b4a commit 4d658f0
Show file tree
Hide file tree
Showing 5 changed files with 186 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,12 @@ import com.adyen.checkout.paybybankus.R
import com.adyen.checkout.paybybankus.internal.ui.model.PayByBankUSBrandLogo
import com.adyen.checkout.paybybankus.internal.ui.model.PayByBankUSOutputData
import com.adyen.checkout.ui.core.internal.ui.ButtonComponentViewType
import com.adyen.checkout.ui.core.internal.ui.ButtonDelegate
import com.adyen.checkout.ui.core.internal.ui.ComponentViewType
import com.adyen.checkout.ui.core.internal.ui.PaymentComponentUIEvent
import com.adyen.checkout.ui.core.internal.ui.PaymentComponentUIState
import com.adyen.checkout.ui.core.internal.ui.SubmitHandler
import com.adyen.checkout.ui.core.internal.ui.UIStateDelegate
import com.adyen.checkout.ui.core.internal.ui.model.LogoTextItem
import com.adyen.checkout.ui.core.internal.ui.model.LogoTextItem.LogoItem
import com.adyen.checkout.ui.core.internal.ui.model.LogoTextItem.TextItem
Expand All @@ -45,7 +47,7 @@ internal class DefaultPayByBankUSDelegate(
override val componentParams: ButtonComponentParams,
private val analyticsManager: AnalyticsManager,
private val submitHandler: SubmitHandler<PayByBankUSComponentState>,
) : PayByBankUSDelegate {
) : PayByBankUSDelegate, ButtonDelegate, UIStateDelegate {

private val _outputDataFlow = MutableStateFlow(createOutputData())
override val outputDataFlow: Flow<PayByBankUSOutputData> = _outputDataFlow
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,12 @@ package com.adyen.checkout.paybybankus.internal
import com.adyen.checkout.components.core.internal.ui.PaymentComponentDelegate
import com.adyen.checkout.paybybankus.PayByBankUSComponentState
import com.adyen.checkout.paybybankus.internal.ui.model.PayByBankUSOutputData
import com.adyen.checkout.ui.core.internal.ui.ButtonDelegate
import com.adyen.checkout.ui.core.internal.ui.UIStateDelegate
import com.adyen.checkout.ui.core.internal.ui.ViewProvidingDelegate
import kotlinx.coroutines.flow.Flow

internal interface PayByBankUSDelegate :
PaymentComponentDelegate<PayByBankUSComponentState>,
ViewProvidingDelegate,
ButtonDelegate,
UIStateDelegate {
ViewProvidingDelegate {

val outputData: PayByBankUSOutputData

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,19 @@ import com.adyen.checkout.components.core.internal.PaymentObserverRepository
import com.adyen.checkout.components.core.internal.analytics.AnalyticsManager
import com.adyen.checkout.components.core.internal.analytics.GenericEvents
import com.adyen.checkout.components.core.internal.ui.model.ButtonComponentParams
import com.adyen.checkout.components.core.internal.util.bufferedChannel
import com.adyen.checkout.components.core.paymentmethod.PayByBankUSPaymentMethod
import com.adyen.checkout.core.AdyenLogLevel
import com.adyen.checkout.core.internal.util.adyenLog
import com.adyen.checkout.paybybankus.PayByBankUSComponentState
import com.adyen.checkout.paybybankus.internal.ui.model.PayByBankUSOutputData
import com.adyen.checkout.ui.core.internal.ui.ButtonComponentViewType
import com.adyen.checkout.ui.core.internal.ui.ComponentViewType
import com.adyen.checkout.ui.core.internal.ui.PaymentComponentUIEvent
import com.adyen.checkout.ui.core.internal.ui.PaymentComponentUIState
import com.adyen.checkout.ui.core.internal.ui.SubmitHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.receiveAsFlow

@Suppress("TooManyFunctions")
internal class StoredPayByBankUSDelegate(
Expand All @@ -37,7 +39,6 @@ internal class StoredPayByBankUSDelegate(
private val order: OrderRequest?,
override val componentParams: ButtonComponentParams,
private val analyticsManager: AnalyticsManager,
private val submitHandler: SubmitHandler<PayByBankUSComponentState>,
) : PayByBankUSDelegate {

private val _outputDataFlow = MutableStateFlow(createOutputData())
Expand All @@ -52,13 +53,35 @@ internal class StoredPayByBankUSDelegate(
private val _viewFlow: MutableStateFlow<ComponentViewType?> = MutableStateFlow(null)
override val viewFlow: Flow<ComponentViewType?> = _viewFlow

override val submitFlow: Flow<PayByBankUSComponentState> = submitHandler.submitFlow

override val uiStateFlow: Flow<PaymentComponentUIState> = submitHandler.uiStateFlow
override val uiEventFlow: Flow<PaymentComponentUIEvent> = submitHandler.uiEventFlow
private val submitChannel = bufferedChannel<PayByBankUSComponentState>()
override val submitFlow: Flow<PayByBankUSComponentState> = submitChannel.receiveAsFlow()

override fun initialize(coroutineScope: CoroutineScope) {
submitHandler.initialize(coroutineScope, componentStateFlow)
initializeAnalytics(coroutineScope)

componentStateFlow.onEach {
onState(it)
}.launchIn(coroutineScope)
}

private fun initializeAnalytics(coroutineScope: CoroutineScope) {
adyenLog(AdyenLogLevel.VERBOSE) { "initializeAnalytics" }
analyticsManager.initialize(this, coroutineScope)

val event = GenericEvents.rendered(
component = storedPaymentMethod.type.orEmpty(),
isStoredPaymentMethod = true,
)
analyticsManager.trackEvent(event)
}

private fun onState(achDirectDebitComponentState: PayByBankUSComponentState) {
if (achDirectDebitComponentState.isValid) {
val event = GenericEvents.submit(storedPaymentMethod.type.orEmpty())
analyticsManager.trackEvent(event)

submitChannel.trySend(achDirectDebitComponentState)
}
}

override fun getPaymentMethodType(): String {
Expand Down Expand Up @@ -109,20 +132,8 @@ internal class StoredPayByBankUSDelegate(
)
}

override fun onSubmit() {
val event = GenericEvents.submit(storedPaymentMethod.type.orEmpty())
analyticsManager.trackEvent(event)

val state = _componentStateFlow.value
submitHandler.onSubmit(state)
}

override fun isConfirmationRequired(): Boolean = _viewFlow.value is ButtonComponentViewType

override fun shouldShowSubmitButton(): Boolean = isConfirmationRequired() && componentParams.isSubmitButtonVisible

override fun setInteractionBlocked(isInteractionBlocked: Boolean) {
submitHandler.setInteractionBlocked(isInteractionBlocked)
// no ops
}

override fun onCleared() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,6 @@ constructor(
storedPaymentMethod = storedPaymentMethod,
order = order,
analyticsManager = analyticsManager,
submitHandler = SubmitHandler(savedStateHandle),
)

val genericActionDelegate =
Expand Down Expand Up @@ -405,7 +404,6 @@ constructor(
storedPaymentMethod = storedPaymentMethod,
order = checkoutSession.order,
analyticsManager = analyticsManager,
submitHandler = SubmitHandler(savedStateHandle),
)

val genericActionDelegate =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/*
* Copyright (c) 2024 Adyen N.V.
*
* This file is open source and available under the MIT license. See the LICENSE file for more info.
*
* Created by ozgur on 28/11/2024.
*/

package com.adyen.checkout.paybybankus.internal.ui

import com.adyen.checkout.components.core.Amount
import com.adyen.checkout.components.core.CheckoutConfiguration
import com.adyen.checkout.components.core.OrderRequest
import com.adyen.checkout.components.core.StoredPaymentMethod
import com.adyen.checkout.components.core.internal.PaymentObserverRepository
import com.adyen.checkout.components.core.internal.analytics.GenericEvents
import com.adyen.checkout.components.core.internal.analytics.TestAnalyticsManager
import com.adyen.checkout.components.core.internal.ui.model.ButtonComponentParamsMapper
import com.adyen.checkout.components.core.internal.ui.model.CommonComponentParamsMapper
import com.adyen.checkout.core.Environment
import com.adyen.checkout.paybybankus.PayByBankUSConfiguration
import com.adyen.checkout.paybybankus.getPayByBankUSConfiguration
import com.adyen.checkout.paybybankus.internal.PayByBankUSDelegate
import com.adyen.checkout.paybybankus.internal.StoredPayByBankUSDelegate
import com.adyen.checkout.paybybankus.payByBankUS
import com.adyen.checkout.test.LoggingExtension
import com.adyen.checkout.test.extensions.test
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.mockito.junit.jupiter.MockitoExtension
import java.util.Locale

@OptIn(ExperimentalCoroutinesApi::class)
@ExtendWith(MockitoExtension::class, LoggingExtension::class)
class StoredPayByBankUSDelegateTest {

private lateinit var analyticsManager: TestAnalyticsManager
private lateinit var delegate: PayByBankUSDelegate

@BeforeEach
fun beforeEach() {
analyticsManager = TestAnalyticsManager()
delegate = createPayByBankUSDelegate()
}

@Test
fun `when delegate is initialized, then state is valid`() = runTest {
val testFlow = delegate.componentStateFlow.test(testScheduler)
with(testFlow.latestValue) {
assertTrue(isInputValid)
assertTrue(isReady)
assertTrue(isValid)
}
}

@Test
fun `when delegate is initialized, then submit handler onSubmit is called`() = runTest {
val testFlow = delegate.submitFlow.test(testScheduler)

delegate.initialize(CoroutineScope(UnconfinedTestDispatcher()))

assertEquals(delegate.componentStateFlow.first(), testFlow.latestValue)
}

@Nested
inner class AnalyticsTest {

@Test
fun `when delegate is initialized then analytics manager is initialized`() {
delegate.initialize(CoroutineScope(UnconfinedTestDispatcher()))

analyticsManager.assertIsInitialized()
}

@Test
fun `when delegate is initialized, then render event is tracked`() {
delegate.initialize(CoroutineScope(UnconfinedTestDispatcher()))

val expectedEvent = GenericEvents.rendered(
component = TEST_PAYMENT_METHOD_TYPE,
isStoredPaymentMethod = true,
)
analyticsManager.assertHasEventEquals(expectedEvent)
}

@Test
fun `when delegeate is initialized, the component state is submitted, then submit event is tracked`() {
delegate.initialize(CoroutineScope(UnconfinedTestDispatcher()))

val expectedEvent = GenericEvents.submit(TEST_PAYMENT_METHOD_TYPE)
analyticsManager.assertLastEventEquals(expectedEvent)
}

@Test
fun `when delegate is cleared then analytics manager is cleared`() {
delegate.onCleared()

analyticsManager.assertIsCleared()
}
}

private fun createPayByBankUSDelegate(
configuration: CheckoutConfiguration = createCheckoutConfiguration(),
) = StoredPayByBankUSDelegate(
observerRepository = PaymentObserverRepository(),
componentParams = ButtonComponentParamsMapper(CommonComponentParamsMapper()).mapToParams(
checkoutConfiguration = configuration,
deviceLocale = Locale.US,
dropInOverrideParams = null,
componentSessionParams = null,
componentConfiguration = configuration.getPayByBankUSConfiguration(),
),
storedPaymentMethod = StoredPaymentMethod(
id = STORED_ID,
type = TEST_PAYMENT_METHOD_TYPE,
),
order = TEST_ORDER,
analyticsManager = analyticsManager,
)

private fun createCheckoutConfiguration(
amount: Amount? = null,
configuration: PayByBankUSConfiguration.Builder.() -> Unit = {}
) = CheckoutConfiguration(
shopperLocale = Locale.US,
environment = Environment.TEST,
clientKey = TEST_CLIENT_KEY,
amount = amount,
) {
payByBankUS(configuration)
}

companion object {
private const val TEST_CLIENT_KEY = "test_qwertyuiopasdfghjklzxcvbnmqwerty"
private val TEST_ORDER = OrderRequest("PSP", "ORDER_DATA")
private const val STORED_ID = "Stored_id"
private const val TEST_PAYMENT_METHOD_TYPE = "TEST_PAYMENT_METHOD_TYPE"
}
}

0 comments on commit 4d658f0

Please sign in to comment.