kopia lustrzana https://github.com/ryukoposting/Signal-Android
Donations credit card formatting.
rodzic
16cbc971a5
commit
b8e16353ab
|
@ -0,0 +1,52 @@
|
|||
package org.thoughtcrime.securesms.components.settings.app.subscription.donate.card
|
||||
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
|
||||
class CreditCardExpirationTextWatcher : TextWatcher {
|
||||
|
||||
private var isBackspace = false
|
||||
|
||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit
|
||||
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
|
||||
isBackspace = count == 0
|
||||
}
|
||||
|
||||
override fun afterTextChanged(s: Editable) {
|
||||
val text = s.toString()
|
||||
val formattedText = when (text.length) {
|
||||
1 -> formatForSingleCharacter(text)
|
||||
2 -> formatForTwoCharacters(text)
|
||||
else -> text
|
||||
}
|
||||
|
||||
val finalText = if (isBackspace && text.length < formattedText.length && formattedText.endsWith("/")) {
|
||||
formattedText.dropLast(2)
|
||||
} else {
|
||||
formattedText
|
||||
}
|
||||
|
||||
if (finalText != text) {
|
||||
s.replace(0, s.length, finalText)
|
||||
}
|
||||
}
|
||||
|
||||
private fun formatForSingleCharacter(text: String): String {
|
||||
val number = text.toIntOrNull() ?: return text
|
||||
return if (number > 1) {
|
||||
"0$number/"
|
||||
} else {
|
||||
text
|
||||
}
|
||||
}
|
||||
|
||||
private fun formatForTwoCharacters(text: String): String {
|
||||
val number = text.toIntOrNull() ?: return text
|
||||
return if (number <= 12) {
|
||||
"%02d/".format(number)
|
||||
} else {
|
||||
text
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.components.settings.app.subscription.donate.c
|
|||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.core.widget.addTextChangedListener
|
||||
|
@ -13,6 +14,7 @@ import androidx.navigation.fragment.findNavController
|
|||
import androidx.navigation.fragment.navArgs
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.ViewBinderDelegate
|
||||
import org.thoughtcrime.securesms.components.settings.app.subscription.donate.DonateToSignalType
|
||||
import org.thoughtcrime.securesms.databinding.CreditCardFragmentBinding
|
||||
import org.thoughtcrime.securesms.payments.FiatMoneyUtil
|
||||
import org.thoughtcrime.securesms.util.LifecycleDisposable
|
||||
|
@ -26,13 +28,21 @@ class CreditCardFragment : Fragment(R.layout.credit_card_fragment) {
|
|||
private val lifecycleDisposable = LifecycleDisposable()
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
|
||||
binding.title.text = getString(R.string.CreditCardFragment__donation_amount_s, FiatMoneyUtil.format(resources, args.request.fiat))
|
||||
binding.title.text = if (args.request.donateToSignalType == DonateToSignalType.MONTHLY) {
|
||||
getString(
|
||||
R.string.CreditCardFragment__donation_amount_s_per_month,
|
||||
FiatMoneyUtil.format(resources, args.request.fiat, FiatMoneyUtil.formatOptions().trimZerosAfterDecimal())
|
||||
)
|
||||
} else {
|
||||
getString(R.string.CreditCardFragment__donation_amount_s, FiatMoneyUtil.format(resources, args.request.fiat))
|
||||
}
|
||||
|
||||
binding.cardNumber.addTextChangedListener(afterTextChanged = {
|
||||
viewModel.onNumberChanged(it?.toString() ?: "")
|
||||
viewModel.onNumberChanged(it?.toString()?.filter { it != ' ' } ?: "")
|
||||
})
|
||||
|
||||
binding.cardNumber.addTextChangedListener(CreditCardTextWatcher())
|
||||
|
||||
binding.cardNumber.setOnFocusChangeListener { v, hasFocus ->
|
||||
viewModel.onNumberFocusChanged(hasFocus)
|
||||
}
|
||||
|
@ -45,10 +55,21 @@ class CreditCardFragment : Fragment(R.layout.credit_card_fragment) {
|
|||
viewModel.onCodeFocusChanged(hasFocus)
|
||||
}
|
||||
|
||||
binding.cardCvv.setOnEditorActionListener { _, actionId, _ ->
|
||||
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
||||
binding.continueButton.performClick()
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
binding.cardExpiry.addTextChangedListener(afterTextChanged = {
|
||||
viewModel.onExpirationChanged(it?.toString() ?: "")
|
||||
})
|
||||
|
||||
binding.cardExpiry.addTextChangedListener(CreditCardExpirationTextWatcher())
|
||||
|
||||
binding.cardExpiry.setOnFocusChangeListener { v, hasFocus ->
|
||||
viewModel.onExpirationFocusChanged(hasFocus)
|
||||
}
|
||||
|
@ -112,7 +133,13 @@ class CreditCardFragment : Fragment(R.layout.credit_card_fragment) {
|
|||
CreditCardExpirationValidator.Validity.INVALID_MONTH -> ErrorState(messageResId = R.string.CreditCardFragment__invalid_month)
|
||||
CreditCardExpirationValidator.Validity.INVALID_YEAR -> ErrorState(messageResId = R.string.CreditCardFragment__invalid_year)
|
||||
CreditCardExpirationValidator.Validity.POTENTIALLY_VALID -> NO_ERROR
|
||||
CreditCardExpirationValidator.Validity.FULLY_VALID -> NO_ERROR
|
||||
CreditCardExpirationValidator.Validity.FULLY_VALID -> {
|
||||
if (binding.cardExpiry.isFocused) {
|
||||
binding.cardCvv.requestFocus()
|
||||
}
|
||||
|
||||
NO_ERROR
|
||||
}
|
||||
}
|
||||
|
||||
binding.cardExpiryWrapper.error = errorState.resolveErrorText(requireContext())
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
package org.thoughtcrime.securesms.components.settings.app.subscription.donate.card
|
||||
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
|
||||
/**
|
||||
* Formats a credit card by type as the user modifies it.
|
||||
*/
|
||||
class CreditCardTextWatcher : TextWatcher {
|
||||
|
||||
private var isBackspace: Boolean = false
|
||||
|
||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit
|
||||
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
|
||||
isBackspace = count == 0
|
||||
}
|
||||
|
||||
override fun afterTextChanged(s: Editable) {
|
||||
val userInput = s.toString()
|
||||
val normalizedNumber = userInput.filter { it != ' ' }
|
||||
|
||||
val formattedNumber = when (CreditCardType.fromCardNumber(normalizedNumber)) {
|
||||
CreditCardType.AMERICAN_EXPRESS -> applyAmexFormatting(normalizedNumber)
|
||||
CreditCardType.UNIONPAY -> applyUnionPayFormatting(normalizedNumber)
|
||||
CreditCardType.OTHER -> applyOtherFormatting(normalizedNumber)
|
||||
}
|
||||
|
||||
val backspaceHandled = if (isBackspace && formattedNumber.endsWith(' ') && formattedNumber.length > userInput.length) {
|
||||
formattedNumber.dropLast(2)
|
||||
} else {
|
||||
formattedNumber
|
||||
}
|
||||
|
||||
if (userInput != backspaceHandled) {
|
||||
s.replace(0, s.length, backspaceHandled)
|
||||
}
|
||||
}
|
||||
|
||||
private fun applyAmexFormatting(normalizedNumber: String): String {
|
||||
return applyGrouping(normalizedNumber, listOf(4, 6, 5))
|
||||
}
|
||||
|
||||
private fun applyUnionPayFormatting(normalizedNumber: String): String {
|
||||
return when {
|
||||
normalizedNumber.length <= 13 -> applyGrouping(normalizedNumber, listOf(4, 4, 5))
|
||||
normalizedNumber.length <= 16 -> applyGrouping(normalizedNumber, listOf(4, 4, 4, 4))
|
||||
else -> applyGrouping(normalizedNumber, listOf(5, 5, 5, 4))
|
||||
}
|
||||
}
|
||||
|
||||
private fun applyOtherFormatting(normalizedNumber: String): String {
|
||||
return if (normalizedNumber.length <= 16) {
|
||||
applyGrouping(normalizedNumber, listOf(4, 4, 4, 4))
|
||||
} else {
|
||||
applyGrouping(normalizedNumber, listOf(5, 5, 5, 4))
|
||||
}
|
||||
}
|
||||
|
||||
private fun applyGrouping(normalizedNumber: String, groups: List<Int>): String {
|
||||
val maxCardLength = groups.sum()
|
||||
|
||||
return groups.fold(0 to emptyList<String>()) { acc, limit ->
|
||||
val offset = acc.first
|
||||
val section = normalizedNumber.drop(offset).take(limit)
|
||||
val segment = if (limit == section.length && offset + limit != maxCardLength) {
|
||||
"$section "
|
||||
} else {
|
||||
section
|
||||
}
|
||||
|
||||
(offset + limit) to acc.second + segment
|
||||
}.second.filter { it.isNotEmpty() }.joinToString("")
|
||||
}
|
||||
}
|
|
@ -4,27 +4,27 @@
|
|||
android:viewportWidth="32"
|
||||
android:viewportHeight="32">
|
||||
<path
|
||||
android:pathData="M3.368,5L28.632,5A3.368,3.368 0,0 1,32 8.368L32,23.632A3.368,3.368 0,0 1,28.632 27L3.368,27A3.368,3.368 0,0 1,0 23.632L0,8.368A3.368,3.368 0,0 1,3.368 5z"
|
||||
android:pathData="M4.158,6L27.842,6A3.158,3.158 0,0 1,31 9.158L31,22.842A3.158,3.158 0,0 1,27.842 26L4.158,26A3.158,3.158 0,0 1,1 22.842L1,9.158A3.158,3.158 0,0 1,4.158 6z"
|
||||
android:fillColor="#FBFCFE"/>
|
||||
<path
|
||||
android:pathData="M3.368,5L28.632,5A3.368,3.368 0,0 1,32 8.368L32,23.632A3.368,3.368 0,0 1,28.632 27L3.368,27A3.368,3.368 0,0 1,0 23.632L0,8.368A3.368,3.368 0,0 1,3.368 5z"
|
||||
android:pathData="M4.158,6L27.842,6A3.158,3.158 0,0 1,31 9.158L31,22.842A3.158,3.158 0,0 1,27.842 26L4.158,26A3.158,3.158 0,0 1,1 22.842L1,9.158A3.158,3.158 0,0 1,4.158 6z"
|
||||
android:fillColor="#50679F"
|
||||
android:fillAlpha="0.11"/>
|
||||
<path
|
||||
android:pathData="M22.737,8.259L27.79,8.259A0.842,0.842 0,0 1,28.632 9.101L28.632,13.936A0.842,0.842 0,0 1,27.79 14.778L22.737,14.778A0.842,0.842 0,0 1,21.895 13.936L21.895,9.101A0.842,0.842 0,0 1,22.737 8.259z"
|
||||
android:pathData="M22.316,8.963L27.053,8.963A0.789,0.789 0,0 1,27.842 9.752L27.842,14.099A0.789,0.789 0,0 1,27.053 14.889L22.316,14.889A0.789,0.789 0,0 1,21.527 14.099L21.527,9.752A0.789,0.789 0,0 1,22.316 8.963z"
|
||||
android:strokeAlpha="0.4"
|
||||
android:fillColor="#586071"
|
||||
android:fillAlpha="0.4"/>
|
||||
<path
|
||||
android:pathData="M4.183,20.482L7.606,20.482A0.815,0.815 0,0 1,8.421 21.296L8.421,21.296A0.815,0.815 0,0 1,7.606 22.111L4.183,22.111A0.815,0.815 0,0 1,3.369 21.296L3.369,21.296A0.815,0.815 0,0 1,4.183 20.482z"
|
||||
android:pathData="M4.899,20.074L8.154,20.074A0.741,0.741 0,0 1,8.895 20.815L8.895,20.815A0.741,0.741 0,0 1,8.154 21.556L4.899,21.556A0.741,0.741 0,0 1,4.158 20.815L4.158,20.815A0.741,0.741 0,0 1,4.899 20.074z"
|
||||
android:fillColor="#A1ACC4"/>
|
||||
<path
|
||||
android:pathData="M10.92,20.482L14.343,20.482A0.815,0.815 0,0 1,15.158 21.296L15.158,21.296A0.815,0.815 0,0 1,14.343 22.111L10.92,22.111A0.815,0.815 0,0 1,10.105 21.296L10.105,21.296A0.815,0.815 0,0 1,10.92 20.482z"
|
||||
android:pathData="M11.214,20.074L14.47,20.074A0.741,0.741 0,0 1,15.21 20.815L15.21,20.815A0.741,0.741 0,0 1,14.47 21.556L11.214,21.556A0.741,0.741 0,0 1,10.474 20.815L10.474,20.815A0.741,0.741 0,0 1,11.214 20.074z"
|
||||
android:fillColor="#A1ACC4"/>
|
||||
<path
|
||||
android:pathData="M17.657,20.482L21.08,20.482A0.815,0.815 0,0 1,21.894 21.296L21.894,21.296A0.815,0.815 0,0 1,21.08 22.111L17.657,22.111A0.815,0.815 0,0 1,16.842 21.296L16.842,21.296A0.815,0.815 0,0 1,17.657 20.482z"
|
||||
android:pathData="M17.53,20.074L20.785,20.074A0.741,0.741 0,0 1,21.526 20.815L21.526,20.815A0.741,0.741 0,0 1,20.785 21.556L17.53,21.556A0.741,0.741 0,0 1,16.789 20.815L16.789,20.815A0.741,0.741 0,0 1,17.53 20.074z"
|
||||
android:fillColor="#A1ACC4"/>
|
||||
<path
|
||||
android:pathData="M24.394,20.482L27.817,20.482A0.815,0.815 0,0 1,28.632 21.296L28.632,21.296A0.815,0.815 0,0 1,27.817 22.111L24.394,22.111A0.815,0.815 0,0 1,23.579 21.296L23.579,21.296A0.815,0.815 0,0 1,24.394 20.482z"
|
||||
android:pathData="M23.846,20.074L27.102,20.074A0.741,0.741 0,0 1,27.842 20.815L27.842,20.815A0.741,0.741 0,0 1,27.102 21.556L23.846,21.556A0.741,0.741 0,0 1,23.105 20.815L23.105,20.815A0.741,0.741 0,0 1,23.846 20.074z"
|
||||
android:fillColor="#A1ACC4"/>
|
||||
</vector>
|
||||
|
|
|
@ -48,7 +48,10 @@
|
|||
android:layout_marginTop="36dp"
|
||||
android:hint="@string/CreditCardFragment__card_number"
|
||||
app:boxStrokeColor="@color/signal_colorPrimary"
|
||||
app:boxStrokeErrorColor="@color/signal_colorError"
|
||||
app:errorEnabled="true"
|
||||
app:errorIconTint="@color/signal_colorError"
|
||||
app:errorTextColor="@color/signal_colorError"
|
||||
app:hintTextColor="@color/signal_colorPrimary"
|
||||
app:layout_constraintTop_toBottomOf="@id/description">
|
||||
|
||||
|
@ -56,9 +59,11 @@
|
|||
android:id="@+id/card_number"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:digits="0123456789 "
|
||||
android:imeOptions="actionNext"
|
||||
android:inputType="number"
|
||||
android:maxLength="19"
|
||||
android:maxLines="1" />
|
||||
android:maxLines="1"
|
||||
android:nextFocusDown="@id/card_expiry" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
|
@ -71,7 +76,10 @@
|
|||
android:hint="@string/CreditCardFragment__mm_yy"
|
||||
android:paddingEnd="18dp"
|
||||
app:boxStrokeColor="@color/signal_colorPrimary"
|
||||
app:boxStrokeErrorColor="@color/signal_colorError"
|
||||
app:errorEnabled="true"
|
||||
app:errorIconTint="@color/signal_colorError"
|
||||
app:errorTextColor="@color/signal_colorError"
|
||||
app:hintTextColor="@color/signal_colorPrimary"
|
||||
app:layout_constraintEnd_toStartOf="@id/card_cvv_wrapper"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
|
@ -81,6 +89,8 @@
|
|||
android:id="@+id/card_expiry"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:digits="0123456789/"
|
||||
android:imeOptions="actionNext"
|
||||
android:inputType="datetime|date"
|
||||
android:maxLength="5"
|
||||
android:maxLines="1"
|
||||
|
@ -97,7 +107,10 @@
|
|||
android:hint="@string/CreditCardFragment__cvv"
|
||||
android:paddingStart="18dp"
|
||||
app:boxStrokeColor="@color/signal_colorPrimary"
|
||||
app:boxStrokeErrorColor="@color/signal_colorError"
|
||||
app:errorEnabled="true"
|
||||
app:errorIconTint="@color/signal_colorError"
|
||||
app:errorTextColor="@color/signal_colorError"
|
||||
app:hintTextColor="@color/signal_colorPrimary"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/card_expiry_wrapper"
|
||||
|
@ -120,22 +133,11 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="@dimen/dsl_settings_gutter"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:enabled="false"
|
||||
android:text="@string/CreditCardFragment__continue"
|
||||
app:layout_constraintBottom_toTopOf="@id/notice"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/notice"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="@dimen/dsl_settings_gutter"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="20dp"
|
||||
android:textAppearance="@style/Signal.Text.BodyMedium"
|
||||
android:textColor="@color/signal_colorOnSurfaceVariant"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
tools:text="Signal will never sell or trade your information to anyone. More of an explanation if needed." />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -11,5 +11,6 @@
|
|||
app:iconGravity="textStart"
|
||||
app:iconTint="@null"
|
||||
tools:icon="@drawable/credit_card"
|
||||
app:iconSize="32dp"
|
||||
tools:text="Primary button"
|
||||
tools:viewBindingIgnore="true" />
|
|
@ -134,8 +134,10 @@
|
|||
<string name="BlockedUsersActivity__unblock">Unblock</string>
|
||||
|
||||
<!-- CreditCardFragment -->
|
||||
<!-- Title of fragment detailing the donation amount, displayed above the credit card text fields -->
|
||||
<!-- Title of fragment detailing the donation amount for one-time donation, displayed above the credit card text fields -->
|
||||
<string name="CreditCardFragment__donation_amount_s">Donation amount: %1$s</string>
|
||||
<!-- Title of fragment detailing the donation amount for monthly donation, displayed above the credit card text fields -->
|
||||
<string name="CreditCardFragment__donation_amount_s_per_month">Donation amount: %1$s/month</string>
|
||||
<!-- Explanation of how to fill in the form, displayed above the credit card text fields -->
|
||||
<string name="CreditCardFragment__enter_your_card_information_below">Enter your card information below</string>
|
||||
<!-- Displayed as a hint in the card number text field -->
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
package org.thoughtcrime.securesms.components.settings.app.subscription.donate.card
|
||||
|
||||
import android.app.Application
|
||||
import android.text.Editable
|
||||
import android.text.SpannableStringBuilder
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.ParameterizedRobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(ParameterizedRobolectricTestRunner::class)
|
||||
@Config(application = Application::class)
|
||||
class CreditCardExpirationTextWatcherBackspaceTest(
|
||||
private val beforeBackspace: String,
|
||||
private val textWatcherOutput: String
|
||||
) {
|
||||
|
||||
private val testSubject = CreditCardExpirationTextWatcher()
|
||||
|
||||
@Test
|
||||
fun getTextWatcherOutput() {
|
||||
val editable: Editable = SpannableStringBuilder(beforeBackspace.dropLast(1))
|
||||
testSubject.onTextChanged(null, 0, 0, 0)
|
||||
testSubject.afterTextChanged(editable)
|
||||
assertEquals(textWatcherOutput, editable.toString())
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
@ParameterizedRobolectricTestRunner.Parameters(name = "{index}: getTextWatcherOutput(..) = {0}, {1}")
|
||||
fun data(): Iterable<Array<Any>> = arrayListOf(
|
||||
arrayOf("12/23", "12/2"),
|
||||
arrayOf("12/2", "12/"),
|
||||
arrayOf("12/", "1"),
|
||||
arrayOf("1", "")
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package org.thoughtcrime.securesms.components.settings.app.subscription.donate.card
|
||||
|
||||
import android.app.Application
|
||||
import android.text.Editable
|
||||
import android.text.SpannableStringBuilder
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.ParameterizedRobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(ParameterizedRobolectricTestRunner::class)
|
||||
@Config(application = Application::class)
|
||||
class CreditCardExpirationTextWatcherTest(
|
||||
private val userInput: String,
|
||||
private val textWatcherOutput: String
|
||||
) {
|
||||
|
||||
private val testSubject = CreditCardExpirationTextWatcher()
|
||||
|
||||
@Test
|
||||
fun getTextWatcherOutput() {
|
||||
val editable: Editable = SpannableStringBuilder(userInput)
|
||||
testSubject.afterTextChanged(editable)
|
||||
assertEquals(textWatcherOutput, editable.toString())
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
@ParameterizedRobolectricTestRunner.Parameters(name = "{index}: getTextWatcherOutput(..) = {0}, {1}")
|
||||
fun data(): Iterable<Array<Any>> = arrayListOf(
|
||||
arrayOf("0", "0"),
|
||||
arrayOf("1", "1"),
|
||||
arrayOf("12", "12/"),
|
||||
arrayOf("02", "02/"),
|
||||
arrayOf("2", "02/"),
|
||||
arrayOf("12/", "12/"),
|
||||
arrayOf("12/1", "12/1"),
|
||||
arrayOf("15", "15")
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package org.thoughtcrime.securesms.components.settings.app.subscription.donate.card
|
||||
|
||||
import android.app.Application
|
||||
import android.text.Editable
|
||||
import android.text.SpannableStringBuilder
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.ParameterizedRobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(ParameterizedRobolectricTestRunner::class)
|
||||
@Config(application = Application::class)
|
||||
class CreditCardTextWatcherBackspaceTest(
|
||||
private val beforeBackspace: String,
|
||||
private val textWatcherOutput: String
|
||||
) {
|
||||
|
||||
private val testSubject = CreditCardTextWatcher()
|
||||
|
||||
@Test
|
||||
fun getTextWatcherOutput() {
|
||||
val editable: Editable = SpannableStringBuilder(beforeBackspace.dropLast(1))
|
||||
testSubject.onTextChanged(null, 0, 0, 0)
|
||||
testSubject.afterTextChanged(editable)
|
||||
assertEquals(textWatcherOutput, editable.toString())
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
@ParameterizedRobolectricTestRunner.Parameters(name = "{index}: getTextWatcherOutput(..) = {0}, {1}")
|
||||
fun data(): Iterable<Array<Any>> = arrayListOf(
|
||||
arrayOf("1234 ", "123"),
|
||||
arrayOf("1234 5", "1234 ")
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
package org.thoughtcrime.securesms.components.settings.app.subscription.donate.card
|
||||
|
||||
import android.app.Application
|
||||
import android.text.Editable
|
||||
import android.text.SpannableStringBuilder
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.ParameterizedRobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(ParameterizedRobolectricTestRunner::class)
|
||||
@Config(application = Application::class)
|
||||
class CreditCardTextWatcherTest(
|
||||
private val userInput: String,
|
||||
private val textWatcherOutput: String
|
||||
) {
|
||||
|
||||
private val testSubject = CreditCardTextWatcher()
|
||||
|
||||
@Test
|
||||
fun getTextWatcherOutput() {
|
||||
val editable: Editable = SpannableStringBuilder(userInput)
|
||||
testSubject.afterTextChanged(editable)
|
||||
assertEquals(textWatcherOutput, editable.toString())
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
@ParameterizedRobolectricTestRunner.Parameters(name = "{index}: getTextWatcherOutput(..) = {0}, {1}")
|
||||
fun data(): Iterable<Array<Any>> = arrayListOf(
|
||||
// AMEX
|
||||
arrayOf("340", "340"),
|
||||
arrayOf("3400", "3400 "),
|
||||
arrayOf("34000", "3400 0"),
|
||||
arrayOf("3400000000", "3400 000000 "),
|
||||
arrayOf("34000000000", "3400 000000 0"),
|
||||
arrayOf("340000000000000", "3400 000000 00000"),
|
||||
// UNIONPAY
|
||||
arrayOf("620", "620"),
|
||||
arrayOf("6200", "6200 "),
|
||||
arrayOf("62000", "6200 0"),
|
||||
arrayOf("6200000000", "6200 0000 00"),
|
||||
arrayOf("6200000000000", "6200 0000 00000"),
|
||||
arrayOf("620000000000000", "6200 0000 0000 000"),
|
||||
arrayOf("6200000000000000", "6200 0000 0000 0000"),
|
||||
arrayOf("62000000000000000", "62000 00000 00000 00"),
|
||||
// OTHER
|
||||
arrayOf("550", "550"),
|
||||
arrayOf("5500", "5500 "),
|
||||
arrayOf("55000", "5500 0"),
|
||||
arrayOf("5500000000", "5500 0000 00"),
|
||||
arrayOf("55000000000", "5500 0000 000"),
|
||||
arrayOf("550000000000000", "5500 0000 0000 000"),
|
||||
arrayOf("5500000000000000", "5500 0000 0000 0000"),
|
||||
arrayOf("55000000000000000", "55000 00000 00000 00"),
|
||||
)
|
||||
}
|
||||
}
|
Ładowanie…
Reference in New Issue