From 440d7091502a1b274dd2b6e87f638eaeb8320c3d Mon Sep 17 00:00:00 2001 From: Devin Morgan Date: Tue, 5 Dec 2023 09:59:06 -0500 Subject: [PATCH] [FX-722] Add support for `derivedCardInfo.usState` to `ElementState` for `ForagePANEditText` (#133) ElementState previously only cared about values that were common to both PAN end PIN. We have reached a point where the states will need to diverage. * Update unit tests for usState support * Make ElementState an interface instead of data class However, Forage X had a conversation around this where we said we would be OK with risk of changing the type of `ElementState` to something other than `data class`. This opened the door for us to make `ElementState` an interface, which is decoupled from the actual Data Transfer Objects used to ferry the state of the ForageElement. The rest of the changes in this commit follow from making `ElementState` an interface. One upside of this approach is that we get rid of `PinDetails`, which was annoying --- .../joinforage/forage/android/ForageSDK.kt | 3 +- .../android/core/element/ElementEvents.kt | 4 +- .../core/element/state/ElementState.kt | 61 ++++++- .../core/element/state/ElementStateManager.kt | 40 ++--- .../element/state/PanElementStateManager.kt | 37 +++- .../element/state/PinElementStateManager.kt | 15 +- .../forage/android/model/StateIIN.kt | 165 ++++++++++++------ .../android/ui/AbstractForageElement.kt | 5 +- .../forage/android/ui/ForageElement.kt | 6 +- .../forage/android/ui/ForagePANEditText.kt | 8 +- .../forage/android/ui/ForagePINEditText.kt | 8 +- .../forage/android/ui/VaultWrapper.kt | 3 +- .../element/state/ElementStateManagerTest.kt | 25 +-- .../state/PanElementStateManagerTest.kt | 28 ++- .../state/PinElementStateManagerTest.kt | 8 +- 15 files changed, 285 insertions(+), 131 deletions(-) diff --git a/forage-android/src/main/java/com/joinforage/forage/android/ForageSDK.kt b/forage-android/src/main/java/com/joinforage/forage/android/ForageSDK.kt index 9c94b4e8..46666d59 100644 --- a/forage-android/src/main/java/com/joinforage/forage/android/ForageSDK.kt +++ b/forage-android/src/main/java/com/joinforage/forage/android/ForageSDK.kt @@ -1,6 +1,7 @@ package com.joinforage.forage.android import com.joinforage.forage.android.core.EnvConfig +import com.joinforage.forage.android.core.element.state.ElementState import com.joinforage.forage.android.core.telemetry.CustomerPerceivedResponseMonitor import com.joinforage.forage.android.core.telemetry.EventOutcome import com.joinforage.forage.android.core.telemetry.Log @@ -23,7 +24,7 @@ import java.util.UUID */ class ForageSDK : ForageSDKInterface { - private fun _getForageConfigOrThrow(element: AbstractForageElement): ForageConfig { + private fun _getForageConfigOrThrow(element: AbstractForageElement): ForageConfig { val context = element.getForageConfig() return context ?: throw ForageConfigNotSetException( """ diff --git a/forage-android/src/main/java/com/joinforage/forage/android/core/element/ElementEvents.kt b/forage-android/src/main/java/com/joinforage/forage/android/core/element/ElementEvents.kt index 14c90c84..b98f1df7 100644 --- a/forage-android/src/main/java/com/joinforage/forage/android/core/element/ElementEvents.kt +++ b/forage-android/src/main/java/com/joinforage/forage/android/core/element/ElementEvents.kt @@ -1,6 +1,4 @@ package com.joinforage.forage.android.core.element -import com.joinforage.forage.android.core.element.state.ElementState - internal typealias SimpleElementListener = () -> Unit -internal typealias StatefulElementListener = (state: ElementState) -> Unit +internal typealias StatefulElementListener = (state: T) -> Unit diff --git a/forage-android/src/main/java/com/joinforage/forage/android/core/element/state/ElementState.kt b/forage-android/src/main/java/com/joinforage/forage/android/core/element/state/ElementState.kt index 470e7339..ce4158df 100644 --- a/forage-android/src/main/java/com/joinforage/forage/android/core/element/state/ElementState.kt +++ b/forage-android/src/main/java/com/joinforage/forage/android/core/element/state/ElementState.kt @@ -1,16 +1,29 @@ package com.joinforage.forage.android.core.element.state import com.joinforage.forage.android.core.element.ElementValidationError +import com.joinforage.forage.android.model.USState -data class ElementState( - val isFocused: Boolean, - val isBlurred: Boolean, - val isEmpty: Boolean, - val isValid: Boolean, - val isComplete: Boolean, +interface ElementState { + val isFocused: Boolean + val isBlurred: Boolean + val isEmpty: Boolean + val isValid: Boolean + val isComplete: Boolean val validationError: ElementValidationError? -) -internal val INITIAL_ELEMENT_STATE = ElementState( +} + +interface PinElementState : ElementState + +data class PinElementStateDto( + override val isFocused: Boolean, + override val isBlurred: Boolean, + override val isEmpty: Boolean, + override val isValid: Boolean, + override val isComplete: Boolean, + override val validationError: ElementValidationError? +) : PinElementState + +internal val INITIAL_PIN_ELEMENT_STATE = PinElementStateDto( isFocused = false, isBlurred = true, isEmpty = true, @@ -18,3 +31,35 @@ internal val INITIAL_ELEMENT_STATE = ElementState( isComplete = false, validationError = null ) + +interface DerivedCardInfo { + val usState: USState? +} + +data class DerivedCardInfoDto( + override val usState: USState? = null +) : DerivedCardInfo + +interface PanElementState : ElementState { + val derivedCardInfo: DerivedCardInfo // the interface not the DTO +} + +data class PanElementStateDto( + override val isFocused: Boolean, + override val isBlurred: Boolean, + override val isEmpty: Boolean, + override val isValid: Boolean, + override val isComplete: Boolean, + override val validationError: ElementValidationError?, + override val derivedCardInfo: DerivedCardInfoDto +) : PanElementState + +internal val INITIAL_PAN_ELEMENT_STATE = PanElementStateDto( + isFocused = false, + isBlurred = true, + isEmpty = true, + isValid = true, + isComplete = false, + validationError = null, + derivedCardInfo = DerivedCardInfoDto() +) diff --git a/forage-android/src/main/java/com/joinforage/forage/android/core/element/state/ElementStateManager.kt b/forage-android/src/main/java/com/joinforage/forage/android/core/element/state/ElementStateManager.kt index a51f7c48..58884fac 100644 --- a/forage-android/src/main/java/com/joinforage/forage/android/core/element/state/ElementStateManager.kt +++ b/forage-android/src/main/java/com/joinforage/forage/android/core/element/state/ElementStateManager.kt @@ -4,9 +4,12 @@ import com.joinforage.forage.android.core.element.ElementValidationError import com.joinforage.forage.android.core.element.SimpleElementListener import com.joinforage.forage.android.core.element.StatefulElementListener -internal abstract class ElementStateManager( - private var isFocused: Boolean, - private var isBlurred: Boolean, +internal abstract class ElementStateManager( + // private because only this class should house focus / blur logic + private var _isFocused: Boolean, + private var _isBlurred: Boolean, + + // internal because subclasses will define the logic for these internal var isEmpty: Boolean, internal var isValid: Boolean, internal var isComplete: Boolean, @@ -14,27 +17,24 @@ internal abstract class ElementStateManager( ) { private var onFocusEventListener: SimpleElementListener? = null private var onBlurEventListener: SimpleElementListener? = null - internal var onChangeEventListener: StatefulElementListener? = null + internal var onChangeEventListener: StatefulElementListener? = null + + internal val isFocused + get() = _isFocused - internal constructor(state: ElementState) : this( - isFocused = state.isFocused, - isBlurred = state.isBlurred, + internal val isBlurred + get() = _isBlurred + + internal constructor(state: T) : this( + _isFocused = state.isFocused, + _isBlurred = state.isBlurred, isEmpty = state.isEmpty, isValid = state.isValid, isComplete = state.isComplete, validationError = state.validationError ) - fun getState(): ElementState { - return ElementState( - isFocused = isFocused, - isBlurred = isBlurred, - isEmpty = isEmpty, - isValid = isValid, - isComplete = isComplete, - validationError = validationError - ) - } + internal abstract fun getState(): T fun setOnFocusEventListener(l: SimpleElementListener) { onFocusEventListener = l @@ -44,13 +44,13 @@ internal abstract class ElementStateManager( onBlurEventListener = l } - fun setOnChangeEventListener(l: StatefulElementListener) { + fun setOnChangeEventListener(l: StatefulElementListener) { onChangeEventListener = l } fun changeFocus(hasFocus: Boolean) { - isFocused = hasFocus - isBlurred = !hasFocus + _isFocused = hasFocus + _isBlurred = !hasFocus if (hasFocus) { onFocusEventListener?.invoke() } else { diff --git a/forage-android/src/main/java/com/joinforage/forage/android/core/element/state/PanElementStateManager.kt b/forage-android/src/main/java/com/joinforage/forage/android/core/element/state/PanElementStateManager.kt index 9f8d6784..025738a8 100644 --- a/forage-android/src/main/java/com/joinforage/forage/android/core/element/state/PanElementStateManager.kt +++ b/forage-android/src/main/java/com/joinforage/forage/android/core/element/state/PanElementStateManager.kt @@ -7,6 +7,7 @@ import com.joinforage.forage.android.core.element.TooLongEbtPanError import com.joinforage.forage.android.model.hasInvalidStateIIN import com.joinforage.forage.android.model.isCorrectLength import com.joinforage.forage.android.model.missingStateIIN +import com.joinforage.forage.android.model.queryForStateIIN import com.joinforage.forage.android.model.tooLongForStateIIN import com.joinforage.forage.android.model.tooShortForStateIIN @@ -76,7 +77,11 @@ internal class BalanceCheckErrorCard : WhitelistedCards("5", 14) internal class NonProdValidEbtCard : WhitelistedCards("9", 4) internal class EmptyEbtCashBalanceCard : WhitelistedCards("654321") -internal class PanElementStateManager(state: ElementState, private val validators: Array) : ElementStateManager(state) { +internal class PanElementStateManager( + state: PanElementState, + private val validators: Array +) : ElementStateManager(state) { + private var derivedCardInfo = DerivedCardInfoDto() private fun checkIsValid(cardNumber: String): Boolean { return validators.any { it.checkIfValid(cardNumber) } @@ -89,6 +94,21 @@ internal class PanElementStateManager(state: ElementState, private val validator .map { it.checkForValidationError(cardNumber) } .firstOrNull { it != null } } + private fun getDerivedCardInfo(cardNumber: String): DerivedCardInfoDto { + return DerivedCardInfoDto(queryForStateIIN(cardNumber)?.publicEnum) + } + + override fun getState(): PanElementState { + return PanElementStateDto( + isFocused = this.isFocused, + isBlurred = this.isBlurred, + isEmpty = this.isEmpty, + isValid = this.isValid, + isComplete = this.isComplete, + validationError = this.validationError, + derivedCardInfo = this.derivedCardInfo + ) + } fun handleChangeEvent(rawInput: String) { // because the input may be formatted, we need to @@ -97,10 +117,13 @@ internal class PanElementStateManager(state: ElementState, private val validator // check to see if any of the validators believe the // card to be valid - this.isValid = checkIsValid(newCardNumber) - this.isComplete = checkIfComplete(newCardNumber) - this.validationError = checkForValidationError(newCardNumber) - this.isEmpty = newCardNumber.isEmpty() + isEmpty = newCardNumber.isEmpty() + isValid = checkIsValid(newCardNumber) + isComplete = checkIfComplete(newCardNumber) + validationError = checkForValidationError(newCardNumber) + + // update state details based on newCardNumber + derivedCardInfo = getDerivedCardInfo(newCardNumber) // invoke the registered listener with the updated state onChangeEventListener?.invoke(getState()) @@ -109,14 +132,14 @@ internal class PanElementStateManager(state: ElementState, private val validator companion object { fun forEmptyInput(): PanElementStateManager { return PanElementStateManager( - INITIAL_ELEMENT_STATE, + INITIAL_PAN_ELEMENT_STATE, arrayOf(StrictEbtValidator()) ) } fun NON_PROD_forEmptyInput(): PanElementStateManager { return PanElementStateManager( - INITIAL_ELEMENT_STATE, + INITIAL_PAN_ELEMENT_STATE, arrayOf( StrictEbtValidator(), PaymentCaptureErrorCard(), diff --git a/forage-android/src/main/java/com/joinforage/forage/android/core/element/state/PinElementStateManager.kt b/forage-android/src/main/java/com/joinforage/forage/android/core/element/state/PinElementStateManager.kt index b38f4f29..497fa2f9 100644 --- a/forage-android/src/main/java/com/joinforage/forage/android/core/element/state/PinElementStateManager.kt +++ b/forage-android/src/main/java/com/joinforage/forage/android/core/element/state/PinElementStateManager.kt @@ -3,7 +3,18 @@ package com.joinforage.forage.android.core.element.state import com.joinforage.forage.android.core.element.IncompleteEbtPinError import com.joinforage.forage.android.core.element.WrongEbtPinError -internal class PinElementStateManager(state: ElementState) : ElementStateManager(state) { +internal class PinElementStateManager(state: PinElementState) : ElementStateManager(state) { + + override fun getState(): PinElementState { + return PinElementStateDto( + isFocused = this.isFocused, + isBlurred = this.isBlurred, + isEmpty = this.isEmpty, + isValid = this.isValid, + isComplete = this.isComplete, + validationError = this.validationError + ) + } // this function is used for after the submit event // happens and we learn that the PIN was not correct @@ -34,7 +45,7 @@ internal class PinElementStateManager(state: ElementState) : ElementStateManager companion object { fun forEmptyInput(): PinElementStateManager { - return PinElementStateManager(INITIAL_ELEMENT_STATE) + return PinElementStateManager(INITIAL_PIN_ELEMENT_STATE) } } } diff --git a/forage-android/src/main/java/com/joinforage/forage/android/model/StateIIN.kt b/forage-android/src/main/java/com/joinforage/forage/android/model/StateIIN.kt index fbda280a..6f4e3d55 100644 --- a/forage-android/src/main/java/com/joinforage/forage/android/model/StateIIN.kt +++ b/forage-android/src/main/java/com/joinforage/forage/android/model/StateIIN.kt @@ -2,63 +2,120 @@ package com.joinforage.forage.android.model internal const val STATE_INN_LENGTH = 6 +enum class USState(val abbreviation: String) { + ALABAMA("AL"), + ALASKA("AK"), + ARIZONA("AZ"), + ARKANSAS("AR"), + CALIFORNIA("CA"), + COLORADO("CO"), + CONNECTICUT("CT"), + DELAWARE("DE"), + DISTRICT_OF_COLUMBIA("DC"), + FLORIDA("FL"), + GEORGIA("GA"), + GUAM("GU"), + HAWAII("HI"), + IDAHO("ID"), + ILLINOIS("IL"), + INDIANA("IN"), + IOWA("IA"), + KANSAS("KS"), + KENTUCKY("KY"), + LOUISIANA("LA"), + MAINE("ME"), + MARYLAND("MD"), + MASSACHUSETTS("MA"), + MICHIGAN("MI"), + MINNESOTA("MN"), + MISSISSIPPI("MS"), + MISSOURI("MO"), + MONTANA("MT"), + NEBRASKA("NE"), + NEVADA("NV"), + NEW_HAMPSHIRE("NH"), + NEW_JERSEY("NJ"), + NEW_MEXICO("NM"), + NEW_YORK("NY"), + NORTH_CAROLINA("NC"), + NORTH_DAKOTA("ND"), + OHIO("OH"), + OKLAHOMA("OK"), + OREGON("OR"), + PENNSYLVANIA("PA"), + RHODE_ISLAND("RI"), + SOUTH_CAROLINA("SC"), + SOUTH_DAKOTA("SD"), + TENNESSEE("TN"), + TEXAS("TX"), + US_VIRGIN_ISLANDS("VI"), + UTAH("UT"), + VERMONT("VT"), + VIRGINIA("VA"), + WASHINGTON("WA"), + WEST_VIRGINIA("WV"), + WISCONSIN("WI"), + WYOMING("WY") +} + internal enum class StateIIN( val iin: String, - val panLength: Int + val panLength: Int, + val publicEnum: USState ) { - ALABAMA("507680", 16), - ALASKA("507695", 16), - ARIZONA("507706", 16), - ARKANSAS("610093", 16), - CALIFORNIA("507719", 16), - COLORADO("507681", 16), - CONNECTICUT("600890", 18), - DELAWARE("507713", 16), - DISTRICT_OF_COLUMBIA("507707", 16), - FLORIDA("508139", 16), - GEORGIA("508148", 16), - GUAM("578036", 16), - HAWAII("507698", 16), - IDAHO("507692", 16), - ILLINOIS("601453", 16), - INDIANA("507704", 16), - IOWA("627485", 19), - KANSAS("601413", 16), - KENTUCKY("507709", 16), - LOUISIANA("504476", 16), - MAINE("507703", 19), - MARYLAND("600528", 16), - MASSACHUSETTS("600875", 18), - MICHIGAN("507711", 16), - MINNESOTA("610423", 16), - MISSISSIPPI("507718", 16), - MISSOURI("507683", 16), - MONTANA("507714", 16), - NEBRASKA("507716", 16), - NEVADA("507715", 16), - NEW_HAMPSHIRE("507701", 16), - NEW_JERSEY("610434", 16), - NEW_MEXICO("586616", 16), - NEW_YORK("600486", 19), - NORTH_CAROLINA("508161", 16), - NORTH_DAKOTA("508132", 16), - OHIO("507700", 16), - OKLAHOMA("508147", 16), - OREGON("507693", 16), - PENNSYLVANIA("600760", 19), - RHODE_ISLAND("507682", 16), - SOUTH_CAROLINA("610470", 16), - SOUTH_DAKOTA("508132", 16), - TENNESSEE("507702", 16), - TEXAS("610098", 19), - US_VIRGIN_ISLANDS("507721", 16), - UTAH("601036", 16), - VERMONT("507705", 16), - VIRGINIA("622044", 16), - WASHINGTON("507710", 16), - WEST_VIRGINIA("507720", 16), - WISCONSIN("507708", 16), - WYOMING("505349", 16) + ALABAMA("507680", 16, USState.ALABAMA), + ALASKA("507695", 16, USState.ALASKA), + ARIZONA("507706", 16, USState.ARIZONA), + ARKANSAS("610093", 16, USState.ARKANSAS), + CALIFORNIA("507719", 16, USState.CALIFORNIA), + COLORADO("507681", 16, USState.COLORADO), + CONNECTICUT("600890", 18, USState.CONNECTICUT), + DELAWARE("507713", 16, USState.DELAWARE), + DISTRICT_OF_COLUMBIA("507707", 16, USState.DISTRICT_OF_COLUMBIA), + FLORIDA("508139", 16, USState.FLORIDA), + GEORGIA("508148", 16, USState.GEORGIA), + GUAM("578036", 16, USState.GUAM), + HAWAII("507698", 16, USState.HAWAII), + IDAHO("507692", 16, USState.IDAHO), + ILLINOIS("601453", 16, USState.ILLINOIS), + INDIANA("507704", 16, USState.INDIANA), + IOWA("627485", 19, USState.IOWA), + KANSAS("601413", 16, USState.KANSAS), + KENTUCKY("507709", 16, USState.KENTUCKY), + LOUISIANA("504476", 16, USState.LOUISIANA), + MAINE("507703", 19, USState.MAINE), + MARYLAND("600528", 16, USState.MARYLAND), + MASSACHUSETTS("600875", 18, USState.MASSACHUSETTS), + MICHIGAN("507711", 16, USState.MICHIGAN), + MINNESOTA("610423", 16, USState.MINNESOTA), + MISSISSIPPI("507718", 16, USState.MISSISSIPPI), + MISSOURI("507683", 16, USState.MISSOURI), + MONTANA("507714", 16, USState.MONTANA), + NEBRASKA("507716", 16, USState.NEBRASKA), + NEVADA("507715", 16, USState.NEVADA), + NEW_HAMPSHIRE("507701", 16, USState.NEW_HAMPSHIRE), + NEW_JERSEY("610434", 16, USState.NEW_JERSEY), + NEW_MEXICO("586616", 16, USState.NEW_MEXICO), + NEW_YORK("600486", 19, USState.NEW_YORK), + NORTH_CAROLINA("508161", 16, USState.NORTH_CAROLINA), + NORTH_DAKOTA("508132", 16, USState.NORTH_DAKOTA), + OHIO("507700", 16, USState.OHIO), + OKLAHOMA("508147", 16, USState.OKLAHOMA), + OREGON("507693", 16, USState.OREGON), + PENNSYLVANIA("600760", 19, USState.PENNSYLVANIA), + RHODE_ISLAND("507682", 16, USState.RHODE_ISLAND), + SOUTH_CAROLINA("610470", 16, USState.SOUTH_CAROLINA), + SOUTH_DAKOTA("508132", 16, USState.SOUTH_DAKOTA), + TENNESSEE("507702", 16, USState.TENNESSEE), + TEXAS("610098", 19, USState.TEXAS), + US_VIRGIN_ISLANDS("507721", 16, USState.US_VIRGIN_ISLANDS), + UTAH("601036", 16, USState.UTAH), + VERMONT("507705", 16, USState.VERMONT), + VIRGINIA("622044", 16, USState.VIRGINIA), + WASHINGTON("507710", 16, USState.WASHINGTON), + WEST_VIRGINIA("507720", 16, USState.WEST_VIRGINIA), + WISCONSIN("507708", 16, USState.WISCONSIN), + WYOMING("505349", 16, USState.WYOMING) } internal fun missingStateIIN(cardNumber: String): Boolean { diff --git a/forage-android/src/main/java/com/joinforage/forage/android/ui/AbstractForageElement.kt b/forage-android/src/main/java/com/joinforage/forage/android/ui/AbstractForageElement.kt index 9673e450..c0d381d6 100644 --- a/forage-android/src/main/java/com/joinforage/forage/android/ui/AbstractForageElement.kt +++ b/forage-android/src/main/java/com/joinforage/forage/android/ui/AbstractForageElement.kt @@ -4,12 +4,13 @@ import android.content.Context import android.util.AttributeSet import android.widget.LinearLayout import com.joinforage.forage.android.core.StopgapGlobalState +import com.joinforage.forage.android.core.element.state.ElementState -abstract class AbstractForageElement( +abstract class AbstractForageElement( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : LinearLayout(context, attrs, defStyleAttr), ForageElement { +) : LinearLayout(context, attrs, defStyleAttr), ForageElement { private var _forageConfig: ForageConfig? = null diff --git a/forage-android/src/main/java/com/joinforage/forage/android/ui/ForageElement.kt b/forage-android/src/main/java/com/joinforage/forage/android/ui/ForageElement.kt index 7573fe49..b24752f6 100644 --- a/forage-android/src/main/java/com/joinforage/forage/android/ui/ForageElement.kt +++ b/forage-android/src/main/java/com/joinforage/forage/android/ui/ForageElement.kt @@ -21,7 +21,7 @@ data class ForageConfig( * both the ForagePANEditText and the ForagePINEditText satisfy * ForageElement */ -interface ForageElement { +interface ForageElement { var typeface: Typeface? fun setForageConfig(forageConfig: ForageConfig) @@ -36,8 +36,8 @@ interface ForageElement { fun setBoxStrokeWidth(boxStrokeWidth: Int) fun setBoxStrokeWidthFocused(boxStrokeWidth: Int) - fun getElementState(): ElementState + fun getElementState(): T fun setOnFocusEventListener(l: SimpleElementListener) fun setOnBlurEventListener(l: SimpleElementListener) - fun setOnChangeEventListener(l: StatefulElementListener) + fun setOnChangeEventListener(l: StatefulElementListener) } diff --git a/forage-android/src/main/java/com/joinforage/forage/android/ui/ForagePANEditText.kt b/forage-android/src/main/java/com/joinforage/forage/android/ui/ForagePANEditText.kt index 7fa8290c..21208532 100644 --- a/forage-android/src/main/java/com/joinforage/forage/android/ui/ForagePANEditText.kt +++ b/forage-android/src/main/java/com/joinforage/forage/android/ui/ForagePANEditText.kt @@ -14,7 +14,7 @@ import com.joinforage.forage.android.R import com.joinforage.forage.android.core.EnvConfig import com.joinforage.forage.android.core.element.SimpleElementListener import com.joinforage.forage.android.core.element.StatefulElementListener -import com.joinforage.forage.android.core.element.state.ElementState +import com.joinforage.forage.android.core.element.state.PanElementState import com.joinforage.forage.android.core.element.state.PanElementStateManager import com.joinforage.forage.android.core.telemetry.Log @@ -25,7 +25,7 @@ class ForagePANEditText @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = R.attr.foragePanEditTextStyle -) : AbstractForageElement(context, attrs, defStyleAttr) { +) : AbstractForageElement(context, attrs, defStyleAttr) { private val textInputEditText: TextInputEditText private val textInputLayout: TextInputLayout @@ -214,11 +214,11 @@ class ForagePANEditText @JvmOverloads constructor( manager.setOnBlurEventListener(l) restartFocusChangeListener() } - override fun setOnChangeEventListener(l: StatefulElementListener) { + override fun setOnChangeEventListener(l: StatefulElementListener) { manager.setOnChangeEventListener(l) } - override fun getElementState(): ElementState { + override fun getElementState(): PanElementState { return manager.getState() } diff --git a/forage-android/src/main/java/com/joinforage/forage/android/ui/ForagePINEditText.kt b/forage-android/src/main/java/com/joinforage/forage/android/ui/ForagePINEditText.kt index 6bfeadde..88de366c 100644 --- a/forage-android/src/main/java/com/joinforage/forage/android/ui/ForagePINEditText.kt +++ b/forage-android/src/main/java/com/joinforage/forage/android/ui/ForagePINEditText.kt @@ -18,7 +18,7 @@ import com.joinforage.forage.android.collect.VGSPinCollector import com.joinforage.forage.android.core.EnvConfig import com.joinforage.forage.android.core.element.SimpleElementListener import com.joinforage.forage.android.core.element.StatefulElementListener -import com.joinforage.forage.android.core.element.state.ElementState +import com.joinforage.forage.android.core.element.state.PinElementState import com.joinforage.forage.android.core.telemetry.Log import com.launchdarkly.sdk.android.LDConfig import com.verygoodsecurity.vgscollect.widget.VGSEditText @@ -27,7 +27,7 @@ class ForagePINEditText @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = R.attr.foragePanEditTextStyle -) : AbstractForageElement(context, attrs, defStyleAttr) { +) : AbstractForageElement(context, attrs, defStyleAttr) { private val _linearLayout: LinearLayout private val btVaultWrapper: BTVaultWrapper private val vgsVaultWrapper: VGSVaultWrapper @@ -137,11 +137,11 @@ class ForagePINEditText @JvmOverloads constructor( override fun setOnBlurEventListener(l: SimpleElementListener) { vault.setOnBlurEventListener(l) } - override fun setOnChangeEventListener(l: StatefulElementListener) { + override fun setOnChangeEventListener(l: StatefulElementListener) { vault.setOnChangeEventListener(l) } - override fun getElementState(): ElementState { + override fun getElementState(): PinElementState { return vault.manager.getState() } diff --git a/forage-android/src/main/java/com/joinforage/forage/android/ui/VaultWrapper.kt b/forage-android/src/main/java/com/joinforage/forage/android/ui/VaultWrapper.kt index 4e0fba9a..de5bd643 100644 --- a/forage-android/src/main/java/com/joinforage/forage/android/ui/VaultWrapper.kt +++ b/forage-android/src/main/java/com/joinforage/forage/android/ui/VaultWrapper.kt @@ -10,6 +10,7 @@ import android.widget.FrameLayout import com.basistheory.android.view.TextElement import com.joinforage.forage.android.core.element.SimpleElementListener import com.joinforage.forage.android.core.element.StatefulElementListener +import com.joinforage.forage.android.core.element.state.PinElementState import com.joinforage.forage.android.core.element.state.PinElementStateManager import com.verygoodsecurity.vgscollect.widget.VGSEditText @@ -76,7 +77,7 @@ internal abstract class VaultWrapper @JvmOverloads constructor( manager.setOnBlurEventListener(l) } - fun setOnChangeEventListener(l: StatefulElementListener) { + fun setOnChangeEventListener(l: StatefulElementListener) { manager.setOnChangeEventListener(l) } } diff --git a/forage-android/src/test/java/com/joinforage/forage/android/core/element/state/ElementStateManagerTest.kt b/forage-android/src/test/java/com/joinforage/forage/android/core/element/state/ElementStateManagerTest.kt index 20af636e..628ece96 100644 --- a/forage-android/src/test/java/com/joinforage/forage/android/core/element/state/ElementStateManagerTest.kt +++ b/forage-android/src/test/java/com/joinforage/forage/android/core/element/state/ElementStateManagerTest.kt @@ -10,12 +10,13 @@ class ElementStateManagerTest { fun testGetState() { val manager = PanElementStateManager.forEmptyInput() val state = manager.getState() - assertThat(state.isFocused).isEqualTo(INITIAL_ELEMENT_STATE.isFocused) - assertThat(state.isBlurred).isEqualTo(INITIAL_ELEMENT_STATE.isBlurred) - assertThat(state.isEmpty).isEqualTo(INITIAL_ELEMENT_STATE.isEmpty) - assertThat(state.isValid).isEqualTo(INITIAL_ELEMENT_STATE.isValid) - assertThat(state.isComplete).isEqualTo(INITIAL_ELEMENT_STATE.isComplete) - assertThat(state.validationError).isEqualTo(INITIAL_ELEMENT_STATE.validationError) + assertThat(state.isFocused).isEqualTo(INITIAL_PAN_ELEMENT_STATE.isFocused) + assertThat(state.isBlurred).isEqualTo(INITIAL_PAN_ELEMENT_STATE.isBlurred) + assertThat(state.isEmpty).isEqualTo(INITIAL_PAN_ELEMENT_STATE.isEmpty) + assertThat(state.isValid).isEqualTo(INITIAL_PAN_ELEMENT_STATE.isValid) + assertThat(state.isComplete).isEqualTo(INITIAL_PAN_ELEMENT_STATE.isComplete) + assertThat(state.validationError).isEqualTo(INITIAL_PAN_ELEMENT_STATE.validationError) + assertThat(state.derivedCardInfo).isEqualTo(INITIAL_PAN_ELEMENT_STATE.derivedCardInfo) } @Test @@ -68,17 +69,17 @@ class ElementStateManagerTest { state = manager.getState() assertThat(state.isFocused).isTrue assertThat(state.isBlurred).isFalse - assertThat(state.isEmpty).isEqualTo(INITIAL_ELEMENT_STATE.isEmpty) - assertThat(state.isValid).isEqualTo(INITIAL_ELEMENT_STATE.isValid) - assertThat(state.isComplete).isEqualTo(INITIAL_ELEMENT_STATE.isComplete) + assertThat(state.isEmpty).isEqualTo(INITIAL_PIN_ELEMENT_STATE.isEmpty) + assertThat(state.isValid).isEqualTo(INITIAL_PIN_ELEMENT_STATE.isValid) + assertThat(state.isComplete).isEqualTo(INITIAL_PIN_ELEMENT_STATE.isComplete) // unfocus manager.changeFocus(false) state = manager.getState() assertThat(state.isFocused).isFalse assertThat(state.isBlurred).isTrue - assertThat(state.isEmpty).isEqualTo(INITIAL_ELEMENT_STATE.isEmpty) - assertThat(state.isValid).isEqualTo(INITIAL_ELEMENT_STATE.isValid) - assertThat(state.isComplete).isEqualTo(INITIAL_ELEMENT_STATE.isComplete) + assertThat(state.isEmpty).isEqualTo(INITIAL_PIN_ELEMENT_STATE.isEmpty) + assertThat(state.isValid).isEqualTo(INITIAL_PIN_ELEMENT_STATE.isValid) + assertThat(state.isComplete).isEqualTo(INITIAL_PIN_ELEMENT_STATE.isComplete) } } diff --git a/forage-android/src/test/java/com/joinforage/forage/android/core/element/state/PanElementStateManagerTest.kt b/forage-android/src/test/java/com/joinforage/forage/android/core/element/state/PanElementStateManagerTest.kt index 2012d8b1..6fa9c8be 100644 --- a/forage-android/src/test/java/com/joinforage/forage/android/core/element/state/PanElementStateManagerTest.kt +++ b/forage-android/src/test/java/com/joinforage/forage/android/core/element/state/PanElementStateManagerTest.kt @@ -4,6 +4,7 @@ import com.joinforage.forage.android.core.element.IncompleteEbtPanError import com.joinforage.forage.android.core.element.InvalidEbtPanError import com.joinforage.forage.android.core.element.StatefulElementListener import com.joinforage.forage.android.core.element.TooLongEbtPanError +import com.joinforage.forage.android.model.USState import org.assertj.core.api.Assertions.assertThat import org.junit.Test @@ -18,6 +19,7 @@ class StrictForEmptyInputTest { assertThat(state.isValid).isTrue assertThat(state.isComplete).isFalse assertThat(state.validationError).isNull() + assertThat(state.derivedCardInfo).isEqualTo(DerivedCardInfoDto()) } @Test @@ -34,6 +36,7 @@ class StrictForEmptyInputTest { assertThat(state.isValid).isTrue assertThat(state.isComplete).isFalse assertThat(state.validationError).isEqualTo(IncompleteEbtPanError) + assertThat(state.derivedCardInfo).isEqualTo(DerivedCardInfoDto()) } @Test @@ -46,6 +49,7 @@ class StrictForEmptyInputTest { assertThat(state.isValid).isFalse assertThat(state.isComplete).isFalse assertThat(state.validationError).isEqualTo(InvalidEbtPanError) + assertThat(state.derivedCardInfo).isEqualTo(DerivedCardInfoDto()) } @Test @@ -58,6 +62,7 @@ class StrictForEmptyInputTest { assertThat(state.isValid).isTrue assertThat(state.isComplete).isFalse assertThat(state.validationError).isEqualTo(IncompleteEbtPanError) + assertThat(state.derivedCardInfo).isEqualTo(DerivedCardInfoDto(USState.MAINE)) } @Test @@ -70,6 +75,7 @@ class StrictForEmptyInputTest { assertThat(state.isValid).isTrue assertThat(state.isComplete).isTrue assertThat(state.validationError).isNull() + assertThat(state.derivedCardInfo).isEqualTo(DerivedCardInfoDto(USState.MAINE)) } @Test @@ -85,6 +91,7 @@ class StrictForEmptyInputTest { assertThat(state.isValid).isFalse assertThat(state.isComplete).isFalse assertThat(state.validationError).isEqualTo(TooLongEbtPanError) + assertThat(state.derivedCardInfo).isEqualTo(DerivedCardInfoDto(USState.MAINE)) } } @@ -99,6 +106,7 @@ class DEV_ONLY_IntegrationTests { assertThat(state.isValid).isTrue assertThat(state.isComplete).isTrue assertThat(state.validationError).isNull() + assertThat(state.derivedCardInfo).isEqualTo(DerivedCardInfoDto(USState.MAINE)) } @Test @@ -110,6 +118,7 @@ class DEV_ONLY_IntegrationTests { assertThat(state.isValid).isTrue assertThat(state.isComplete).isTrue + assertThat(state.derivedCardInfo).isEqualTo(DerivedCardInfoDto()) } @Test @@ -121,6 +130,7 @@ class DEV_ONLY_IntegrationTests { assertThat(state.isValid).isTrue assertThat(state.isComplete).isTrue + assertThat(state.derivedCardInfo).isEqualTo(DerivedCardInfoDto()) } @Test @@ -132,6 +142,7 @@ class DEV_ONLY_IntegrationTests { assertThat(state.isValid).isTrue assertThat(state.isComplete).isTrue + assertThat(state.derivedCardInfo).isEqualTo(DerivedCardInfoDto()) } @Test @@ -143,6 +154,7 @@ class DEV_ONLY_IntegrationTests { assertThat(state.isValid).isTrue assertThat(state.isComplete).isTrue + assertThat(state.derivedCardInfo).isEqualTo(DerivedCardInfoDto()) } @Test @@ -154,6 +166,7 @@ class DEV_ONLY_IntegrationTests { assertThat(state.isValid).isTrue assertThat(state.isComplete).isFalse assertThat(state.validationError).isNull() + assertThat(state.derivedCardInfo).isEqualTo(DerivedCardInfoDto()) } @Test @@ -165,6 +178,7 @@ class DEV_ONLY_IntegrationTests { assertThat(state.isValid).isFalse assertThat(state.isComplete).isFalse assertThat(state.validationError).isEqualTo(InvalidEbtPanError) + assertThat(state.derivedCardInfo).isEqualTo(DerivedCardInfoDto()) } } @@ -259,8 +273,8 @@ class PanHandleChangeEventTest { @Test fun `valid card 16-digit card number passes correct state to callback`() { val manager = PanElementStateManager.forEmptyInput() - var state: ElementState = manager.getState() - val callback: StatefulElementListener = { newState -> state = newState } + var state: PanElementState = manager.getState() + val callback: StatefulElementListener = { newState -> state = newState } manager.setOnChangeEventListener(callback) manager.handleChangeEvent("5076807890123456") @@ -272,6 +286,7 @@ class PanHandleChangeEventTest { assertThat(state.isValid).isTrue assertThat(state.isComplete).isTrue assertThat(state.validationError).isNull() + assertThat(state.derivedCardInfo).isEqualTo(DerivedCardInfoDto(USState.ALABAMA)) } @Test @@ -279,8 +294,8 @@ class PanHandleChangeEventTest { val manager = PanElementStateManager.forEmptyInput() var callbackAInvoked = false var callbackBInvoked = false - val callbackA: StatefulElementListener = { callbackAInvoked = true } - val callbackB: StatefulElementListener = { callbackBInvoked = true } + val callbackA: StatefulElementListener = { callbackAInvoked = true } + val callbackB: StatefulElementListener = { callbackBInvoked = true } manager.setOnChangeEventListener(callbackA) manager.setOnChangeEventListener(callbackB) @@ -294,8 +309,8 @@ class PanHandleChangeEventTest { @Test fun `strips all non-digit characters before processing`() { val manager = PanElementStateManager.forEmptyInput() - var state: ElementState = manager.getState() - val callback: StatefulElementListener = { newState -> state = newState } + var state: PanElementState = manager.getState() + val callback: StatefulElementListener = { newState -> state = newState } val validStringContaminatedByOtherChars = "!@# $%^ &*()_+<>? abcd5076807890123456" manager.setOnChangeEventListener(callback) @@ -305,5 +320,6 @@ class PanHandleChangeEventTest { assertThat(state.isValid).isTrue assertThat(state.isComplete).isTrue assertThat(state.validationError).isNull() + assertThat(state.derivedCardInfo).isEqualTo(DerivedCardInfoDto(USState.ALABAMA)) } } diff --git a/forage-android/src/test/java/com/joinforage/forage/android/core/element/state/PinElementStateManagerTest.kt b/forage-android/src/test/java/com/joinforage/forage/android/core/element/state/PinElementStateManagerTest.kt index 8ec68bed..c615b7b7 100644 --- a/forage-android/src/test/java/com/joinforage/forage/android/core/element/state/PinElementStateManagerTest.kt +++ b/forage-android/src/test/java/com/joinforage/forage/android/core/element/state/PinElementStateManagerTest.kt @@ -65,8 +65,8 @@ class PinHandleChangeEventTest { @Test fun `completed pin passes correct state to callback`() { val manager = PinElementStateManager.forEmptyInput() - var state: ElementState = manager.getState() - val callback: StatefulElementListener = { newState -> state = newState } + var state: PinElementState = manager.getState() + val callback: StatefulElementListener = { newState -> state = newState } manager.setOnChangeEventListener(callback) manager.handleChangeEvent(isComplete = true, isEmpty = false) @@ -85,8 +85,8 @@ class PinHandleChangeEventTest { val manager = PinElementStateManager.forEmptyInput() var callbackAInvoked = false var callbackBInvoked = false - val callbackA: StatefulElementListener = { callbackAInvoked = true } - val callbackB: StatefulElementListener = { callbackBInvoked = true } + val callbackA: StatefulElementListener = { callbackAInvoked = true } + val callbackB: StatefulElementListener = { callbackBInvoked = true } manager.setOnChangeEventListener(callbackA) manager.setOnChangeEventListener(callbackB)