diff --git a/src/components/CreditCardInput.js b/src/components/CreditCardInput.js index d676ac1b1..aabe0d3c0 100644 --- a/src/components/CreditCardInput.js +++ b/src/components/CreditCardInput.js @@ -13,8 +13,8 @@ export default class CreditCardInput extends Component { const expirationIsValid = new Date(year, month) >= TODAY; this.props.onChange({ expirationMonth: month, expirationYear: year, expirationIsValid }); } - handleCardNumberChange = (cardNumber, cardNumberIsValid) => { - this.props.onChange({ cardNumber, cardNumberIsValid }); + handleCardNumberChange = ({ cardNumber, cardType }, cardNumberIsValid) => { + this.props.onChange({ cardNumber, cardType, cardNumberIsValid }); } render() { diff --git a/src/components/CreditCardNumber.js b/src/components/CreditCardNumber.js index 1494f32f8..837d7f960 100644 --- a/src/components/CreditCardNumber.js +++ b/src/components/CreditCardNumber.js @@ -43,16 +43,19 @@ export default class CreditCardNumber extends Component { setValue = (proposedValue) => { let value = proposedValue.replace(/[^0-9]/g, ''); if (proposedValue === '') { - this.props.onChange(value, false); + this.props.onChange(value, false, undefined); this.setState({ value, cardType: undefined }); return; } const { card, isValid, isPotentiallyValid } = number(value); - let cardType = undefined; - if (card && card.type && includes(this.props.allowedBrands, card.type)) { - cardType = typeToIconName(card.type); + let cardTypeIconName = undefined; + let cardTypeIsAllowed = false; + + if (card && card.type) { + cardTypeIconName = typeToIconName(card.type); + cardTypeIsAllowed = includes(this.props.allowedBrands, card.type); } const typeInfo = cardTypeInfo(value); @@ -70,15 +73,15 @@ export default class CreditCardNumber extends Component { } // Only accept the change if we recognize the card type, and it is/may be valid - if (!this.props.restrictInput || cardType && (isValid || isPotentiallyValid)) { - this.props.onChange(value, isValid); - this.setState({ value, cardType, isValid }); + if (!this.props.restrictInput || cardTypeIsAllowed && (isValid || isPotentiallyValid)) { + this.props.onChange({ cardNumber: value, cardType: card.type }, isValid); + this.setState({ value, cardTypeIconName, isValid }); } } render() { const { placeholder } = this.props; - const { cardType, value } = this.state; + const { cardTypeIconName, value } = this.state; return ( @@ -87,9 +90,9 @@ export default class CreditCardNumber extends Component { placeholder={placeholder} value={value} onChange={this.onInputChange} /> - {cardType && + {cardTypeIconName && - + } @@ -103,7 +106,7 @@ CreditCardNumber.defaultProps = { restrictInput: false, value: '', - onChange: (cardNumber, isValid) => true, // eslint-disable-line no-unused-vars + onChange: (cardNumber, isValid, cardType) => true, // eslint-disable-line no-unused-vars }; CreditCardNumber.propTypes = { allowedBrands: PropTypes.arrayOf(PropTypes.string), diff --git a/test/components/CreditCardNumber.spec.js b/test/components/CreditCardNumber.spec.js index d236ea2c0..f087c46a6 100644 --- a/test/components/CreditCardNumber.spec.js +++ b/test/components/CreditCardNumber.spec.js @@ -2,18 +2,23 @@ import React from 'react'; import assert from 'assert'; import { mount, shallow } from 'enzyme'; +import sinon from 'sinon'; import CreditCardNumber from '../../src/components/CreditCardNumber'; import Icon from '../../src/components/Icon'; const EXAMPLES = { + 'american-express': '378282246310005', 'diners-club': '30569309025904', - amex: '378282246310005', + 'master-card': '5555555555554444', discover: '6011111111111117', jcb: '3530111333300000', - mastercard: '5555555555554444', visa: '4111111111111111', }; +const ICON_MAP = { + 'american-express': 'amex', + 'master-card': 'mastercard', +}; describe('', () => { it('should render no icon, by default', () => { @@ -30,16 +35,22 @@ describe('', () => { assert.equal(component.find(Icon).prop('name'), 'cc-visa'); }); - it('should render correct icons for valid card numbers', () => { + it('should report/render icon for correct cardType for valid numbers', () => { Object.keys(EXAMPLES).forEach(key => { - const expectedIcon = `cc-${key}`; const cardNumber = EXAMPLES[key]; + const onChange = sinon.spy(); - const component = mount(); + const component = mount(); const input = component.find('input'); input.simulate('change', { target: { value: cardNumber } }); - assert.equal(component.find(Icon).prop('name'), expectedIcon); + assert(onChange.called); + const [values, returnedIsValid] = [...onChange.lastCall.args]; + assert.equal(values.cardNumber.replace(/ /g, ''), cardNumber); + assert.equal(values.cardType, key); + assert.equal(returnedIsValid, true); + + assert.equal(component.find(Icon).prop('name'), `cc-${ICON_MAP[key] || key}`); }); }); @@ -65,7 +76,7 @@ describe('', () => { it('restrictInput prop should reject numbers for invalid card types', () => { const component = mount(); const input = component.find('input'); - input.simulate('change', { target: { value: EXAMPLES.mastercard } }); + input.simulate('change', { target: { value: EXAMPLES['master-card'] } }); assert.equal(input.get(0).value, ''); assert.equal(component.find(Icon).length, 0);