Skip to content

Commit

Permalink
Replace Handler with CoroutineScope in PaymentMethodsAdapter (#2553)
Browse files Browse the repository at this point in the history
  • Loading branch information
mshafrir-stripe authored Jun 8, 2020
1 parent 354fa71 commit 5a420bb
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 116 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ package com.stripe.android.view
import android.app.Activity
import android.content.Context
import android.content.res.ColorStateList
import android.os.Handler
import android.os.Looper
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
Expand All @@ -15,6 +13,10 @@ import com.stripe.android.R
import com.stripe.android.databinding.GooglePayRowBinding
import com.stripe.android.databinding.MaskedCardRowBinding
import com.stripe.android.model.PaymentMethod
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

/**
* A [RecyclerView.Adapter] that holds a set of [MaskedCardView] items for a given set
Expand All @@ -26,7 +28,8 @@ internal class PaymentMethodsAdapter constructor(
initiallySelectedPaymentMethodId: String? = null,
private val shouldShowGooglePay: Boolean = false,
private val useGooglePay: Boolean = false,
private val canDeletePaymentMethods: Boolean = true
private val canDeletePaymentMethods: Boolean = true,
private val scope: CoroutineScope = CoroutineScope(Dispatchers.Main)
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

internal val paymentMethods = mutableListOf<PaymentMethod>()
Expand All @@ -39,7 +42,6 @@ internal class PaymentMethodsAdapter constructor(
}

internal var listener: Listener? = null
private val handler = Handler(Looper.getMainLooper())
private val googlePayCount = 1.takeIf { shouldShowGooglePay } ?: 0

init {
Expand Down Expand Up @@ -123,9 +125,11 @@ internal class PaymentMethodsAdapter constructor(
}
}

private fun onPositionClicked(position: Int) {
@JvmSynthetic
internal fun onPositionClicked(position: Int) {
updateSelectedPaymentMethod(position)
handler.post {
scope.launch {
delay(0)
listener?.onPaymentMethodClick(getPaymentMethodAtPosition(position))
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,7 @@ class PaymentMethodsActivityTest {
private val listenerArgumentCaptor: KArgumentCaptor<CustomerSession.PaymentMethodsRetrievalListener> = argumentCaptor()

private val context = ApplicationProvider.getApplicationContext<Context>()
private val activityScenarioFactory: ActivityScenarioFactory by lazy {
ActivityScenarioFactory(context)
}
private val activityScenarioFactory = ActivityScenarioFactory(context)

@BeforeTest
fun setup() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,16 @@ import android.content.Context
import android.widget.FrameLayout
import androidx.recyclerview.widget.RecyclerView
import androidx.test.core.app.ApplicationProvider
import com.google.common.truth.Truth.assertThat
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.verify
import com.stripe.android.model.PaymentMethod
import com.stripe.android.model.PaymentMethodFixtures
import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import kotlin.test.assertTrue
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestCoroutineDispatcher
import kotlinx.coroutines.test.TestCoroutineScope
import org.junit.runner.RunWith
import org.mockito.Mockito.times
import org.robolectric.RobolectricTestRunner
Expand All @@ -23,13 +22,15 @@ import org.robolectric.RobolectricTestRunner
* Test class for [PaymentMethodsAdapter]
*/
@RunWith(RobolectricTestRunner::class)
@ExperimentalCoroutinesApi
class PaymentMethodsAdapterTest {
private val adapterDataObserver: RecyclerView.AdapterDataObserver = mock()
private val listener: PaymentMethodsAdapter.Listener = mock()

private val paymentMethodsAdapter: PaymentMethodsAdapter = PaymentMethodsAdapter(ARGS)

private val context = ApplicationProvider.getApplicationContext<Context>()
private val testScope = TestCoroutineScope(TestCoroutineDispatcher())

@BeforeTest
fun setup() {
Expand All @@ -39,21 +40,18 @@ class PaymentMethodsAdapterTest {
@Test
fun setSelection_changesSelection() {
paymentMethodsAdapter.setPaymentMethods(PaymentMethodFixtures.CARD_PAYMENT_METHODS)
assertEquals(4, paymentMethodsAdapter.itemCount)
assertThat(paymentMethodsAdapter.itemCount)
.isEqualTo(4)
verify(adapterDataObserver).onChanged()

paymentMethodsAdapter.selectedPaymentMethodId = paymentMethodsAdapter.paymentMethods[2].id
assertEquals(
PaymentMethodFixtures.CARD_PAYMENT_METHODS[2].id,
requireNotNull(paymentMethodsAdapter.selectedPaymentMethod).id
)
assertThat(requireNotNull(paymentMethodsAdapter.selectedPaymentMethod).id)
.isEqualTo(PaymentMethodFixtures.CARD_PAYMENT_METHODS[2].id)

paymentMethodsAdapter.selectedPaymentMethodId =
PaymentMethodFixtures.CARD_PAYMENT_METHODS[1].id
assertEquals(
PaymentMethodFixtures.CARD_PAYMENT_METHODS[1].id,
paymentMethodsAdapter.selectedPaymentMethod?.id
)
assertThat(PaymentMethodFixtures.CARD_PAYMENT_METHODS[1].id)
.isEqualTo(paymentMethodsAdapter.selectedPaymentMethod?.id)
}

@Test
Expand All @@ -63,45 +61,46 @@ class PaymentMethodsAdapterTest {

paymentMethodsAdapter.setPaymentMethods(singlePaymentMethod)
paymentMethodsAdapter.selectedPaymentMethodId = paymentMethodsAdapter.paymentMethods[0].id
assertEquals(2, paymentMethodsAdapter.itemCount)
assertNotNull(paymentMethodsAdapter.selectedPaymentMethod)
assertThat(paymentMethodsAdapter.itemCount)
.isEqualTo(2)
assertThat(paymentMethodsAdapter.selectedPaymentMethod)
.isNotNull()

assertEquals(
PaymentMethodFixtures.CARD_PAYMENT_METHODS[0].id,
paymentMethodsAdapter.selectedPaymentMethod?.id
)
assertThat(paymentMethodsAdapter.selectedPaymentMethod?.id)
.isEqualTo(PaymentMethodFixtures.CARD_PAYMENT_METHODS[0].id)

paymentMethodsAdapter.setPaymentMethods(PaymentMethodFixtures.CARD_PAYMENT_METHODS)
paymentMethodsAdapter.selectedPaymentMethodId = paymentMethodsAdapter.paymentMethods[2].id
assertEquals(4, paymentMethodsAdapter.itemCount)
assertEquals(
PaymentMethodFixtures.CARD_PAYMENT_METHODS[2].id,
paymentMethodsAdapter.selectedPaymentMethod?.id
)
assertThat(paymentMethodsAdapter.itemCount)
.isEqualTo(4)
assertThat(paymentMethodsAdapter.selectedPaymentMethod?.id)
.isEqualTo(PaymentMethodFixtures.CARD_PAYMENT_METHODS[2].id)
verify(adapterDataObserver, times(2))
.onChanged()
}

@Test
fun updatePaymentMethods_withSelection_updatesPaymentMethodsAndSelectionMaintained() {
paymentMethodsAdapter.setPaymentMethods(PaymentMethodFixtures.CARD_PAYMENT_METHODS)
assertEquals(4, paymentMethodsAdapter.itemCount)
assertThat(paymentMethodsAdapter.itemCount)
.isEqualTo(4)
paymentMethodsAdapter.selectedPaymentMethodId = PaymentMethodFixtures.CARD_PAYMENT_METHODS[2].id
assertNotNull(paymentMethodsAdapter.selectedPaymentMethod)
assertThat(paymentMethodsAdapter.selectedPaymentMethod)
.isNotNull()

paymentMethodsAdapter.setPaymentMethods(PaymentMethodFixtures.CARD_PAYMENT_METHODS)
assertEquals(4, paymentMethodsAdapter.itemCount)
assertEquals(
PaymentMethodFixtures.CARD_PAYMENT_METHODS[2].id,
paymentMethodsAdapter.selectedPaymentMethod?.id
)
assertThat(paymentMethodsAdapter.itemCount)
.isEqualTo(4)
assertThat(paymentMethodsAdapter.selectedPaymentMethod?.id)
.isEqualTo(PaymentMethodFixtures.CARD_PAYMENT_METHODS[2].id)
}

@Test
fun setPaymentMethods_whenNoInitialSpecified_returnsNull() {
paymentMethodsAdapter
paymentMethodsAdapter.setPaymentMethods(PaymentMethodFixtures.CARD_PAYMENT_METHODS)
assertNull(paymentMethodsAdapter.selectedPaymentMethod)
assertThat(paymentMethodsAdapter.selectedPaymentMethod)
.isNull()
}

@Test
Expand All @@ -112,7 +111,8 @@ class PaymentMethodsAdapterTest {
initiallySelectedPaymentMethodId = "pm_1000"
)
adapter.setPaymentMethods(PaymentMethodFixtures.CARD_PAYMENT_METHODS)
assertEquals("pm_1000", adapter.selectedPaymentMethod?.id)
assertThat(adapter.selectedPaymentMethod?.id)
.isEqualTo("pm_1000")
}

@Test
Expand All @@ -124,36 +124,20 @@ class PaymentMethodsAdapterTest {
)
adapter.setPaymentMethods(PaymentMethodFixtures.CARD_PAYMENT_METHODS)

assertEquals(6, adapter.itemCount)

assertEquals(
PaymentMethodsAdapter.ViewType.GooglePay.ordinal,
adapter.getItemViewType(0)
)

assertEquals(
PaymentMethodsAdapter.ViewType.Card.ordinal,
adapter.getItemViewType(1)
)

assertEquals(
PaymentMethodsAdapter.ViewType.Card.ordinal,
adapter.getItemViewType(2)
)

assertEquals(
PaymentMethodsAdapter.ViewType.Card.ordinal,
adapter.getItemViewType(3)
)

assertEquals(
PaymentMethodsAdapter.ViewType.AddCard.ordinal,
adapter.getItemViewType(4)
)

assertEquals(
PaymentMethodsAdapter.ViewType.AddFpx.ordinal,
adapter.getItemViewType(5)
assertThat(adapter.itemCount)
.isEqualTo(6)

assertThat(
adapter.viewTypes
).isEqualTo(
listOf(
PaymentMethodsAdapter.ViewType.GooglePay,
PaymentMethodsAdapter.ViewType.Card,
PaymentMethodsAdapter.ViewType.Card,
PaymentMethodsAdapter.ViewType.Card,
PaymentMethodsAdapter.ViewType.AddCard,
PaymentMethodsAdapter.ViewType.AddFpx
)
)
}

Expand All @@ -166,17 +150,17 @@ class PaymentMethodsAdapterTest {
)
adapter.setPaymentMethods(PaymentMethodFixtures.CARD_PAYMENT_METHODS)

assertEquals(6, adapter.itemCount)
assertThat(adapter.itemCount)
.isEqualTo(6)

assertEquals(
PaymentMethodsAdapter.GOOGLE_PAY_ITEM_ID,
adapter.getItemId(0)
)
assertThat(adapter.getItemId(0))
.isEqualTo(PaymentMethodsAdapter.GOOGLE_PAY_ITEM_ID)

val uniqueItemIds = (1..5)
.map { adapter.getItemId(it) }
.toSet()
assertEquals(5, uniqueItemIds.size)
assertThat(uniqueItemIds)
.hasSize(5)
}

@Test
Expand All @@ -188,32 +172,16 @@ class PaymentMethodsAdapterTest {
)
adapter.setPaymentMethods(PaymentMethodFixtures.CARD_PAYMENT_METHODS)

assertEquals(5, adapter.itemCount)

assertEquals(
PaymentMethodsAdapter.ViewType.Card.ordinal,
adapter.getItemViewType(0)
)

assertEquals(
PaymentMethodsAdapter.ViewType.Card.ordinal,
adapter.getItemViewType(1)
)

assertEquals(
PaymentMethodsAdapter.ViewType.Card.ordinal,
adapter.getItemViewType(2)
)

assertEquals(
PaymentMethodsAdapter.ViewType.AddCard.ordinal,
adapter.getItemViewType(3)
)

assertEquals(
PaymentMethodsAdapter.ViewType.AddFpx.ordinal,
adapter.getItemViewType(4)
)
assertThat(adapter.viewTypes)
.isEqualTo(
listOf(
PaymentMethodsAdapter.ViewType.Card,
PaymentMethodsAdapter.ViewType.Card,
PaymentMethodsAdapter.ViewType.Card,
PaymentMethodsAdapter.ViewType.AddCard,
PaymentMethodsAdapter.ViewType.AddFpx
)
)
}

@Test
Expand All @@ -231,23 +199,36 @@ class PaymentMethodsAdapterTest {
verify(listener).onGooglePayClick()
}

@Test
fun `onPositionClicked() should call listener's onPaymentMethodClick()`() {
val adapter = PaymentMethodsAdapter(
ARGS,
scope = testScope
)

adapter.setPaymentMethods(PaymentMethodFixtures.CARD_PAYMENT_METHODS)
adapter.listener = listener
adapter.onPositionClicked(0)

verify(listener).onPaymentMethodClick(PaymentMethodFixtures.CARD_PAYMENT_METHODS.first())
}

@Test
fun getPosition_withValidPaymentMethod_returnsPosition() {
val adapter = PaymentMethodsAdapter(ARGS, shouldShowGooglePay = true)
adapter.setPaymentMethods(PaymentMethodFixtures.CARD_PAYMENT_METHODS)

assertEquals(
3,
adapter.getPosition(PaymentMethodFixtures.CARD_PAYMENT_METHODS.last())
)
assertThat(adapter.getPosition(PaymentMethodFixtures.CARD_PAYMENT_METHODS.last()))
.isEqualTo(3)
}

@Test
fun getPosition_withInvalidPaymentMethod_returnsNull() {
val adapter = PaymentMethodsAdapter(ARGS, shouldShowGooglePay = true)
adapter.setPaymentMethods(PaymentMethodFixtures.CARD_PAYMENT_METHODS)

assertNull(adapter.getPosition(PaymentMethodFixtures.FPX_PAYMENT_METHOD))
assertThat(adapter.getPosition(PaymentMethodFixtures.FPX_PAYMENT_METHOD))
.isNull()
}

@Test
Expand All @@ -256,19 +237,24 @@ class PaymentMethodsAdapterTest {
adapter.registerAdapterDataObserver(adapterDataObserver)

adapter.setPaymentMethods(PaymentMethodFixtures.CARD_PAYMENT_METHODS)
assertTrue(
adapter.paymentMethods.contains(PaymentMethodFixtures.CARD_PAYMENT_METHODS.last())
)
assertThat(adapter.paymentMethods)
.contains(PaymentMethodFixtures.CARD_PAYMENT_METHODS.last())
adapter.deletePaymentMethod(PaymentMethodFixtures.CARD_PAYMENT_METHODS.last())
assertFalse(
adapter.paymentMethods.contains(PaymentMethodFixtures.CARD_PAYMENT_METHODS.last())
)
assertThat(adapter.paymentMethods)
.doesNotContain(PaymentMethodFixtures.CARD_PAYMENT_METHODS.last())
verify(adapterDataObserver).onItemRangeRemoved(3, 1)
}

private companion object {
private val ARGS =
PaymentMethodsActivityStarter.Args.Builder()
.build()

private val PaymentMethodsAdapter.viewTypes: List<PaymentMethodsAdapter.ViewType>
get() {
return (0 until itemCount).map {
PaymentMethodsAdapter.ViewType.values()[getItemViewType(it)]
}
}
}
}

0 comments on commit 5a420bb

Please sign in to comment.