@Test public void inputSingleDigit_whenAtFirstCharacterButTextNotEmpty_doesNotPrependZero() { mExpiryDateEditText.append("1"); // This case can occur when the user moves the cursor behind the already-entered text. mExpiryDateEditText.setSelection(0); mExpiryDateEditText.getEditableText().replace(0, 0, "3", 0, 1); assertEquals("31", mExpiryDateEditText.getText().toString()); assertEquals(1, mExpiryDateEditText.getSelectionStart()); }
@Test public void afterAddingFinalDigit_whenGoingFromInvalidToValid_callsListener() { if (Calendar.getInstance().get(Calendar.YEAR) > 2059) { fail("Update the code with a date that is still valid. Also, hello from the past."); } mExpiryDateEditText.append("1"); mExpiryDateEditText.append("2"); mExpiryDateEditText.append("5"); verifyZeroInteractions(mExpiryDateEditListener); mExpiryDateEditText.append("9"); assertTrue(mExpiryDateEditText.isDateValid()); verify(mExpiryDateEditListener, times(1)).onExpiryDateComplete(); }
/** * Override of {@link View#isEnabled()} that returns {@code true} only * if all three sub-controls are enabled. * * @return {@code true} if the card number field, expiry field, and cvc field are enabled, * {@code false} otherwise */ @Override public boolean isEnabled() { return mCardNumberEditText.isEnabled() && mExpiryDateEditText.isEnabled() && mCvcNumberEditText.isEnabled(); }
@Override public void onInitializeAccessibilityNodeInfo(@NonNull AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); String accLabel = getResources().getString( R.string.acc_label_expiry_date_node, getText() ); info.setText(accLabel); }
int cursorPosition = updateSelectionIndex( formattedDate.length(), latestChangeStart, setText(formattedDate); setSelection(cursorPosition); ignoreChanges = false;
@Test public void inputZero_whenEmpty_doesNotShowErrorState() { mExpiryDateEditText.append("0"); assertFalse(mExpiryDateEditText.getShouldShowError()); assertEquals("0", mExpiryDateEditText.getText().toString()); }
@Test public void inputMultipleValidDigits_whenEmpty_doesNotPrependZeroOrShowErrorState() { mExpiryDateEditText.append("11"); String text = mExpiryDateEditText.getText().toString(); assertEquals("11/", text); assertFalse(mExpiryDateEditText.getShouldShowError()); assertEquals(3, mExpiryDateEditText.getSelectionStart()); }
@Test public void getValidDateFields_whenDateIsValidFormatButExpired_returnsNull() { // This test will be invalid if run after the year 2050. Please update the code. assertTrue(Calendar.getInstance().get(Calendar.YEAR) < 2080); mExpiryDateEditText.append("12"); mExpiryDateEditText.append("12"); // 12/12 is an invalid date until 2080, at which point it will be interpreted as 12/2112 assertNull(mExpiryDateEditText.getValidDateFields()); }
/** * Validates all fields and shows error messages if appropriate. * * @return {@code true} if all shown fields are valid, {@code false} otherwise */ public boolean validateAllFields() { boolean cardNumberIsValid = CardUtils.isValidCardNumber(mCardNumberEditText.getCardNumber()); boolean expiryIsValid = mExpiryDateEditText.getValidDateFields() != null && mExpiryDateEditText.isDateValid(); boolean cvcIsValid = isCvcLengthValid(); mCardNumberEditText.setShouldShowError(!cardNumberIsValid); mExpiryDateEditText.setShouldShowError(!expiryIsValid); mCvcEditText.setShouldShowError(!cvcIsValid); boolean postalCodeIsValidOrGone; if (mShouldShowPostalCode) { postalCodeIsValidOrGone = isPostalCodeMaximalLength(true, mPostalCodeEditText.getText().toString()); mPostalCodeEditText.setShouldShowError(!postalCodeIsValidOrGone); } else { postalCodeIsValidOrGone = true; } return cardNumberIsValid && expiryIsValid && cvcIsValid && postalCodeIsValidOrGone; }
@Test public void deleteWhenEmpty_fromCvc_shiftsToExpiry() { mCardMultilineWidget.setCardInputListener(mFullCardListener); mNoZipCardMultilineWidget.setCardInputListener(mNoZipCardListener); mFullGroup.expiryDateEditText.append("12"); mFullGroup.expiryDateEditText.append("50"); reset(mFullCardListener); assertTrue(mFullGroup.cvcEditText.hasFocus()); ViewTestUtils.sendDeleteKeyEvent(mFullGroup.cvcEditText); verify(mFullCardListener, times(1)).onFocusChange(FOCUS_EXPIRY); assertTrue(mFullGroup.expiryDateEditText.hasFocus()); assertEquals("12/5", mFullGroup.expiryDateEditText.getText().toString()); mNoZipGroup.expiryDateEditText.append("12"); mNoZipGroup.expiryDateEditText.append("50"); reset(mNoZipCardListener); assertTrue(mNoZipGroup.cvcEditText.hasFocus()); ViewTestUtils.sendDeleteKeyEvent(mNoZipGroup.cvcEditText); verify(mNoZipCardListener, times(1)).onFocusChange(FOCUS_EXPIRY); assertTrue(mNoZipGroup.expiryDateEditText.hasFocus()); assertEquals("12/5", mNoZipGroup.expiryDateEditText.getText().toString()); }
@Test public void inputSingleDigit_whenDigitIsTooLargeForMonth_prependsZero() { mExpiryDateEditText.append("4"); assertEquals("04/", mExpiryDateEditText.getText().toString()); assertEquals(3, mExpiryDateEditText.getSelectionStart()); }
mExpiryDateEditText.setErrorColor(mErrorColorInt); mCvcNumberEditText.setErrorColor(mErrorColorInt); mExpiryDateEditText.setOnFocusChangeListener(new OnFocusChangeListener() { @Override public void onFocusChange(View v, boolean hasFocus) { mExpiryDateEditText.setDeleteEmptyListener( new BackUpFieldDeleteListener(mCardNumberEditText)); mExpiryDateEditText.setExpiryDateEditListener( new ExpiryDateEditText.ExpiryDateEditListener() { @Override
/** * Clear all text fields in the CardInputWidget. */ public void clear() { if (mCardNumberEditText.hasFocus() || mExpiryDateEditText.hasFocus() || mCvcNumberEditText.hasFocus() || this.hasFocus()) { mCardNumberEditText.requestFocus(); } mCvcNumberEditText.setText(""); mExpiryDateEditText.setText(""); mCardNumberEditText.setText(""); }
@Before public void setup() { MockitoAnnotations.initMocks(this); ActivityController<CardInputTestActivity> activityController = Robolectric.buildActivity(CardInputTestActivity.class).create().start(); mExpiryDateEditText = activityController.get().getExpiryDateEditText(); mExpiryDateEditText.setText(""); mExpiryDateEditText.setExpiryDateEditListener(mExpiryDateEditListener); }
@Test public void updateSelectionIndex_whenInputVeryLong_respectMaxInputLength() { assertEquals(5, mExpiryDateEditText.updateSelectionIndex(6, 4, 2, 5)); }
/** * Clear all entered data and hide all error messages. */ public void clear() { mCardNumberEditText.setText(""); mExpiryDateEditText.setText(""); mCvcEditText.setText(""); mPostalCodeEditText.setText(""); mCardNumberEditText.setShouldShowError(false); mExpiryDateEditText.setShouldShowError(false); mCvcEditText.setShouldShowError(false); mPostalCodeEditText.setShouldShowError(false); updateBrand(Card.UNKNOWN); }
@Test public void onCompleteCardNumber_whenValid_shiftsFocusToExpiryDate() { mCardMultilineWidget.setCardInputListener(mFullCardListener); mNoZipCardMultilineWidget.setCardInputListener(mNoZipCardListener); mFullGroup.cardNumberEditText.setText(VALID_VISA_WITH_SPACES); verify(mFullCardListener, times(1)).onCardComplete(); verify(mFullCardListener, times(1)).onFocusChange(FOCUS_EXPIRY); assertTrue(mFullGroup.expiryDateEditText.hasFocus()); mNoZipGroup.cardNumberEditText.setText(VALID_AMEX_WITH_SPACES); verify(mNoZipCardListener, times(1)).onCardComplete(); verify(mNoZipCardListener, times(1)).onFocusChange(FOCUS_EXPIRY); assertTrue(mNoZipGroup.expiryDateEditText.hasFocus()); }
/** * Gets a {@link Card} object from the user input, if all fields are valid. If not, returns * {@code null}. * * @return a valid {@link Card} object based on user input, or {@code null} if any field is * invalid */ @Nullable public Card getCard() { String cardNumber = mCardNumberEditText.getCardNumber(); int[] cardDate = mExpiryDateEditText.getValidDateFields(); if (cardNumber == null || cardDate == null || cardDate.length != 2) { return null; } // CVC/CVV is the only field not validated by the entry control itself, so we check here. String cvcValue = mCvcNumberEditText.getText().toString().trim(); if (!isCvcLengthValid()) { return null; } return new Card(cardNumber, cardDate[0], cardDate[1], cvcValue) .addLoggingToken(LOGGING_TOKEN); }
/** * Gets the expiry date displayed on this control if it is valid, or {@code null} if it is not. * The return value is given as a length-2 {@code int} array, where the first entry is the * two-digit month (from 01-12) and the second entry is the four-digit year (2017, not 17). * * @return an {@code int} array of the form {month, year} if the date is valid, or {@code null} * if it is not */ @Nullable @Size(2) public int[] getValidDateFields() { if (!mIsDateValid) { return null; } final int[] monthYearPair = new int[2]; final String rawNumericInput = getText().toString().replaceAll("/", ""); final String[] dateFields = DateUtils.separateDateStringParts(rawNumericInput); try { monthYearPair[0] = Integer.parseInt(dateFields[0]); final int twoDigitYear = Integer.parseInt(dateFields[1]); final int fourDigitYear = DateUtils.convertTwoDigitYearToFour(twoDigitYear); monthYearPair[1] = fourDigitYear; } catch (NumberFormatException numEx) { // Given that the date should already be valid when getting to this method, we should // not his this exception. Returning null to indicate error if we do. return null; } return monthYearPair; }