Skip to content

Commit

Permalink
feat: Publishes change event from CardVerificationCodeElement on bran…
Browse files Browse the repository at this point in the history
…d changes (#30)
  • Loading branch information
dhudec authored Jan 5, 2023
1 parent 06dda5d commit 6e8072a
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,8 @@ class CardVerificationCodeElement @JvmOverloads constructor(
set(value) {
if (value != null && cardNumberElement !== value) {
field = value
super.mask =
cardNumberElement?.cardMetadata?.cvcMask?.let { ElementMask(it) } ?: defaultMask
field?.addChangeEventListener { updateMask() }
onCardNumberChanged(true)
field?.addChangeEventListener { onCardNumberChanged() }
} else {
field = value
}
Expand All @@ -27,7 +26,7 @@ class CardVerificationCodeElement @JvmOverloads constructor(
init {
super.keyboardType = KeyboardType.NUMBER
super.mask = defaultMask
super.validator = RegexValidator("""^\d{3,4}$""")
super.validator = validatorForLength(defaultMask.length)
}

companion object {
Expand All @@ -36,10 +35,27 @@ class CardVerificationCodeElement @JvmOverloads constructor(
val defaultMask = ElementMask(
listOf(digit, digit, digit)
)

fun validatorForLength(length: Int) =
RegexValidator("""^\d{$length}$""")
}

private fun updateMask() {
super.mask =
cardNumberElement?.cardMetadata?.cvcMask?.let { ElementMask(it) } ?: defaultMask
private fun onCardNumberChanged(isInitialConfiguration: Boolean = false) {
val oldMaskLength = super.mask?.length

val updatedMask = cardNumberElement
?.cardMetadata
?.cvcMask
?.let { ElementMask(it) }
?: defaultMask

super.mask = updatedMask
super.validator = validatorForLength(updatedMask.length)

val updatedMaskLength = super.mask?.length

// publish a change event if the mask length changed
if (!isInitialConfiguration && oldMaskLength != updatedMaskLength)
super.publishChangeEvent()
}
}
3 changes: 3 additions & 0 deletions lib/src/main/java/com/basistheory/android/view/TextElement.kt
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,9 @@ open class TextElement @JvmOverloads constructor(
isInternalChange = false
}

protected fun publishChangeEvent()
= publishChangeEvent(editText.editableText)

private fun publishChangeEvent(editable: Editable?) {
val event = createElementChangeEvent(
getText(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ class ElementMask {
)
}

val length: Int
get() = characterMasks.size

internal fun evaluate(text: String?, action: InputAction): String? {
if (text.isNullOrEmpty())
return ""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,8 @@ import org.junit.runner.RunWith
import org.robolectric.Robolectric
import org.robolectric.RobolectricTestRunner
import strikt.api.expectThat
import strikt.assertions.isEqualTo
import strikt.assertions.isFalse
import strikt.assertions.isTrue
import strikt.assertions.single
import strikt.assertions.*
import kotlin.text.get

@RunWith(RobolectricTestRunner::class)
class CardVerificationCodeElementTests {
Expand Down Expand Up @@ -96,4 +94,78 @@ class CardVerificationCodeElementTests {

verify(exactly = 1) { cardNumberElement.addChangeEventListener(any()) }
}

@Test
fun `emits a change event when mask increases from 3 to 4 chars`() {
val changeEvents = mutableListOf<ChangeEvent>()
cvcElement.addChangeEventListener { changeEvents.add(it) }

cardNumberElement.setText("4242424242424242")
cvcElement.cardNumberElement = cardNumberElement
cvcElement.setText("123")

expectThat(changeEvents).single().and {
get { isValid }.isTrue()
get { isEmpty }.isFalse()
get { isComplete }.isTrue()
}

// change the card brand from visa to american-express
cardNumberElement.setText("378282246310005")

expectThat(cvcElement.getText()).isEqualTo("123") // value does not change
expectThat(changeEvents).hasSize(2).last().and {
get { isValid }.isFalse()
get { isEmpty }.isFalse()
get { isComplete }.isFalse()
}
}

@Test
fun `emits a change event when mask decreases from 4 to 3 chars`() {
val changeEvents = mutableListOf<ChangeEvent>()
cvcElement.addChangeEventListener { changeEvents.add(it) }

cardNumberElement.setText("378282246310005")
cvcElement.cardNumberElement = cardNumberElement
cvcElement.setText("1234")

expectThat(changeEvents).single().and {
get { isValid }.isTrue()
get { isEmpty }.isFalse()
get { isComplete }.isTrue()
}

// change the card brand from visa to american-express
cardNumberElement.setText("4242424242424242")

expectThat(cvcElement.getText()).isEqualTo("1234") // value does not change
expectThat(changeEvents).hasSize(2).last().and {
get { isValid }.isFalse()
get { isEmpty }.isFalse()
get { isComplete }.isFalse()
}
}

@Test
fun `does NOT emit a change event when card brand changes without impacting cvc mask`() {
val changeEvents = mutableListOf<ChangeEvent>()
cvcElement.addChangeEventListener { changeEvents.add(it) }

cardNumberElement.setText("4242424242424242")
cvcElement.cardNumberElement = cardNumberElement
cvcElement.setText("1234")

expectThat(changeEvents).single().and {
get { isValid }.isTrue()
get { isEmpty }.isFalse()
get { isComplete }.isTrue()
}

// change the card brand from visa to mastercard
cardNumberElement.setText("5555555555554444")

expectThat(cvcElement.getText()).isEqualTo("123") // value does not change
expectThat(changeEvents).single() // no new change events were published
}
}

0 comments on commit 6e8072a

Please sign in to comment.