Skip to content

Commit

Permalink
feat: add unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
sbycrosz committed Jul 19, 2024
1 parent 96177e9 commit 62a415a
Show file tree
Hide file tree
Showing 9 changed files with 330 additions and 6 deletions.
1 change: 1 addition & 0 deletions jest.setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import '@testing-library/react-native/extend-expect';
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"@evilmartians/lefthook": "^1.5.0",
"@react-native/eslint-config": "^0.73.1",
"@release-it/conventional-changelog": "^5.0.0",
"@testing-library/react-native": "^12.5.1",
"@types/jest": "^29.5.5",
"@types/react": "^18.2.44",
"@types/react-native-flip-card": "^3.5.7",
Expand All @@ -76,6 +77,7 @@
"react": "18.2.0",
"react-native": "0.74.3",
"react-native-builder-bob": "^0.25.0",
"react-test-renderer": "^18.3.1",
"release-it": "^15.0.0",
"typescript": "^5.2.2"
},
Expand All @@ -95,6 +97,9 @@
"modulePathIgnorePatterns": [
"<rootDir>/example/node_modules",
"<rootDir>/lib/"
],
"setupFilesAfterEnv": [
"<rootDir>/jest.setup.ts"
]
},
"commitlint": {
Expand Down
10 changes: 9 additions & 1 deletion src/CreditCardInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ interface Props {
};
onChange: (formData: CreditCardFormData) => void;
onFocusField?: (field: CreditCardFormField) => void;
testID?: string;
}

const s = StyleSheet.create({
Expand Down Expand Up @@ -89,6 +90,7 @@ const CreditCardInput = (props: Props) => {
},
onChange = () => {},
onFocusField = () => {},
testID,
} = props;

const { values, onChangeValue } = useCreditCardForm(onChange);
Expand All @@ -100,7 +102,10 @@ const CreditCardInput = (props: Props) => {
}, [autoFocus]);

return (
<View style={[s.container, style]}>
<View
style={[s.container, style]}
testID={testID}
>
<View style={[s.numberInput]}>
<Text style={[s.inputLabel, labelStyle]}>{labels.number}</Text>
<TextInput
Expand All @@ -114,6 +119,7 @@ const CreditCardInput = (props: Props) => {
onFocus={() => onFocusField('number')}
autoCorrect={false}
underlineColorAndroid={'transparent'}
testID="CC_NUMBER"
/>
</View>

Expand All @@ -130,6 +136,7 @@ const CreditCardInput = (props: Props) => {
onFocus={() => onFocusField('expiry')}
autoCorrect={false}
underlineColorAndroid={'transparent'}
testID="CC_EXPIRY"
/>
</View>

Expand All @@ -145,6 +152,7 @@ const CreditCardInput = (props: Props) => {
onFocus={() => onFocusField('cvc')}
autoCorrect={false}
underlineColorAndroid={'transparent'}
testID="CC_CVC"
/>
</View>
</View>
Expand Down
10 changes: 9 additions & 1 deletion src/LiteCreditCardInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ interface Props {
};
onChange?: (formData: CreditCardFormData) => void;
onFocusField?: (field: CreditCardFormField) => void;
testID?: string;
}

const s = StyleSheet.create({
Expand Down Expand Up @@ -94,6 +95,7 @@ const LiteCreditCardInput = (props: Props) => {
},
onChange = () => {},
onFocusField = () => {},
testID,
} = props;

const _onChange = (formData: CreditCardFormData): void => {
Expand Down Expand Up @@ -133,7 +135,10 @@ const LiteCreditCardInput = (props: Props) => {
}, [values.type]);

return (
<View style={[s.container, style]}>
<View
style={[s.container, style]}
testID={testID}
>
<View style={[s.leftPart, showRightPart ? s.hidden : s.expanded]}>
<View style={[s.numberInput]}>
<TextInput
Expand All @@ -147,6 +152,7 @@ const LiteCreditCardInput = (props: Props) => {
onFocus={() => onFocusField('number')}
autoCorrect={false}
underlineColorAndroid={'transparent'}
testID="CC_NUMBER"
/>
</View>
</View>
Expand Down Expand Up @@ -193,6 +199,7 @@ const LiteCreditCardInput = (props: Props) => {
onFocus={() => onFocusField('expiry')}
autoCorrect={false}
underlineColorAndroid={'transparent'}
testID="CC_EXPIRY"
/>
</View>

Expand All @@ -208,6 +215,7 @@ const LiteCreditCardInput = (props: Props) => {
onFocus={() => onFocusField('cvc')}
autoCorrect={false}
underlineColorAndroid={'transparent'}
testID="CC_CVC"
/>
</View>
</View>
Expand Down
135 changes: 135 additions & 0 deletions src/__tests__/CreditCardInput.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import {
render,
screen,
userEvent,
within,
} from '@testing-library/react-native';
import CreditCardInput from '../CreditCardInput';

describe('CreditCardInput', () => {
let onChange: ReturnType<typeof jest.fn>;
let user: ReturnType<typeof userEvent.setup>;
let cardInput: ReturnType<typeof within>;

beforeEach(() => {
onChange = jest.fn();

user = userEvent.setup();
render(
<CreditCardInput
onChange={onChange}
testID="CARD_INPUT"
/>
);

cardInput = within(screen.getByTestId('CARD_INPUT'));
});

it('should validate and format valid credit-card information', async () => {
await user.type(cardInput.getByTestId('CC_NUMBER'), '4242424242424242');
await user.type(cardInput.getByTestId('CC_EXPIRY'), '233');
await user.type(cardInput.getByTestId('CC_CVC'), '333');

expect(onChange).toHaveBeenLastCalledWith({
valid: true,
status: {
number: 'valid',
expiry: 'valid',
cvc: 'valid',
},
values: {
number: '4242 4242 4242 4242',
expiry: '02/33',
cvc: '333',
type: 'visa',
},
});
});

it('should ignores non number characters ', async () => {
await user.type(
cardInput.getByTestId('CC_NUMBER'),
'--drop db "users" 4242-4242-4242-4242'
);
await user.type(cardInput.getByTestId('CC_EXPIRY'), '#$!@#!@# 12/33');
await user.type(cardInput.getByTestId('CC_CVC'), 'lorem ipsum 333');

expect(onChange).toHaveBeenLastCalledWith(
expect.objectContaining({
values: expect.objectContaining({
number: '4242 4242 4242 4242',
expiry: '12/33',
cvc: '333',
}),
})
);
});

it('should return validation error for invalid card information', async () => {
await user.type(cardInput.getByTestId('CC_NUMBER'), '5555 5555 5555 4443');
await user.type(cardInput.getByTestId('CC_EXPIRY'), '02 / 99');
await user.type(cardInput.getByTestId('CC_CVC'), '33');

expect(onChange).toHaveBeenLastCalledWith(
expect.objectContaining({
status: {
number: 'invalid', // failed crc
expiry: 'invalid', // too far in the future
cvc: 'incomplete', // cvv is too short
},
})
);
});

it('should return credit card issuer based on card number', async () => {
const numberField = cardInput.getByTestId('CC_NUMBER');

await user.clear(numberField);
await user.type(numberField, '4242424242424242');
expect(onChange).toHaveBeenLastCalledWith(
expect.objectContaining({
values: expect.objectContaining({ type: 'visa' }),
})
);

await user.clear(numberField);
await user.type(numberField, '5555555555554444');
expect(onChange).toHaveBeenLastCalledWith(
expect.objectContaining({
values: expect.objectContaining({ type: 'mastercard' }),
})
);

await user.clear(numberField);
await user.type(numberField, '371449635398431');
expect(onChange).toHaveBeenLastCalledWith(
expect.objectContaining({
values: expect.objectContaining({ type: 'american-express' }),
})
);

await user.clear(numberField);
await user.type(numberField, '6011111111111117');
expect(onChange).toHaveBeenLastCalledWith(
expect.objectContaining({
values: expect.objectContaining({ type: 'discover' }),
})
);

await user.clear(numberField);
await user.type(numberField, '3056930009020004');
expect(onChange).toHaveBeenLastCalledWith(
expect.objectContaining({
values: expect.objectContaining({ type: 'diners-club' }),
})
);

await user.clear(numberField);
await user.type(numberField, '3566002020360505');
expect(onChange).toHaveBeenLastCalledWith(
expect.objectContaining({
values: expect.objectContaining({ type: 'jcb' }),
})
);
});
});
Loading

0 comments on commit 62a415a

Please # to comment.