From 673637dc4d898018763634246511de5ebed28575 Mon Sep 17 00:00:00 2001 From: Likhith Kolayari <98398322+likhith-deriv@users.noreply.github.com> Date: Tue, 23 May 2023 11:55:26 +0400 Subject: [PATCH] fix: :art: resolved TS migration issues (#27) * fix: :art: resolved TS migration issues * feat: :white_check_mark: added testcaes * refactor: idv forms to useFormikContext to reduce prop drilling (#26) * refactor: idv forms to useFormikContext to reduce prop drilling * refactor: removed unused props from personal-details * feat: resolved the issues with idv testcases * feat: :art: incorporated image change based on status --------- Co-authored-by: Shahzaib --- .../Assets/ic-poi-dob-mismatch-example.svg | 1 + .../Assets/ic-poi-name-mismatch-example.svg | 1 + .../forms/__tests__/idv-form.spec.tsx | 125 +++++++++++++++ .../account/src/Components/forms/idv-form.tsx | 23 +-- .../forms/personal-details-form.jsx | 34 ++-- .../__tests__/personal-details.spec.js | 96 ++++++++++++ .../personal-details/personal-details.jsx | 15 +- .../__tests__/idv-document-submit.spec.tsx | 23 ++- .../idv-document-submit.tsx | 148 ++++++------------ ...oi-confirm-with-example-form-container.tsx | 12 +- .../idv-doc-submit-on-signup.tsx | 32 +--- packages/account/src/Helpers/utils.ts | 17 +- .../account/src/Types/common-prop.type.ts | 39 +++-- .../components/autocomplete/autocomplete.jsx | 3 + 14 files changed, 352 insertions(+), 217 deletions(-) create mode 100644 packages/account/src/Assets/ic-poi-dob-mismatch-example.svg create mode 100644 packages/account/src/Assets/ic-poi-name-mismatch-example.svg create mode 100644 packages/account/src/Components/forms/__tests__/idv-form.spec.tsx diff --git a/packages/account/src/Assets/ic-poi-dob-mismatch-example.svg b/packages/account/src/Assets/ic-poi-dob-mismatch-example.svg new file mode 100644 index 000000000000..7ad66a00fa5b --- /dev/null +++ b/packages/account/src/Assets/ic-poi-dob-mismatch-example.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/account/src/Assets/ic-poi-name-mismatch-example.svg b/packages/account/src/Assets/ic-poi-name-mismatch-example.svg new file mode 100644 index 000000000000..eaaa9453759c --- /dev/null +++ b/packages/account/src/Assets/ic-poi-name-mismatch-example.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/account/src/Components/forms/__tests__/idv-form.spec.tsx b/packages/account/src/Components/forms/__tests__/idv-form.spec.tsx new file mode 100644 index 000000000000..9a7c4e936ec3 --- /dev/null +++ b/packages/account/src/Components/forms/__tests__/idv-form.spec.tsx @@ -0,0 +1,125 @@ +import React from 'react'; +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import IDVForm from '../idv-form'; +import { Formik } from 'formik'; +import { isDesktop, isMobile } from '@deriv/shared'; + +jest.mock('Helpers/utils', () => ({ + ...jest.requireActual('Helpers/utils'), + getDocumentData: jest.fn((country_code, key) => { + const data = { + tc: { + document_1: { + new_display_name: '', + example_format: '5436454364243', + sample_image: '', + }, + document_2: { + new_display_name: '', + example_format: 'A-52431', + sample_image: '', + }, + }, + }; + return data[country_code][key]; + }), +})); + +jest.mock('formik', () => ({ + ...jest.requireActual('formik'), + useFormikContext: jest.fn(() => ({ + values: { + document_type: { + // display_name: 'Test document 1 name', + // format: '5436454364243', + // id: '1', + // value: 'Test document 1 name', + }, + document_number: '', + }, + errors: {}, + touched: {}, + setFieldValue: jest.fn(), + setFieldTouched: jest.fn(), + validateForm: jest.fn(), + validateField: jest.fn(), + handleBlur: jest.fn(), + handleChange: jest.fn(), + })), +})); + +jest.mock('@deriv/shared', () => ({ + ...jest.requireActual('@deriv/shared'), + isDesktop: jest.fn(() => true), + isMobile: jest.fn(() => false), +})); + +describe('', () => { + const mock_props = { + selected_country: { + value: 'tc', + identity: { + services: { + idv: { + documents_supported: { + document_1: { + display_name: 'Test document 1 name', + format: '5436454364243', + }, + document_2: { + display_name: 'Test document 2 name', + format: 'A54321', + }, + }, + has_visual_sample: '1', + }, + }, + }, + }, + }; + + const mock_values = { + document_type: { + display_name: 'Test document 1 name', + format: '5436454364243', + id: '1', + value: 'document_1', + }, + document_number: '5436454364243', + }; + + it('should render IDVForm component', () => { + render(, { + wrapper: ({ children }) => ( + undefined}> + {() => children} + + ), + }); + + const document_type_input = screen.getByLabelText('Choose the document type'); + const document_number_input = screen.getByPlaceholderText('Enter your document number'); + + expect(document_type_input).toBeInTheDocument(); + expect(document_number_input).toBeInTheDocument(); + }); + + it('Should change the document type value when document type is changed', async () => { + render(, { + wrapper: ({ children }) => ( + undefined}> + {() => children} + + ), + }); + + const document_type_input = screen.getByLabelText('Choose the document type'); + + fireEvent.click(document_type_input); + expect(await screen.findByText('Test document 1 name')).toBeInTheDocument(); + fireEvent.blur(document_type_input); + await waitFor(() => { + expect(screen.queryByText('Test document 1 name')).not.toBeInTheDocument(); + }); + }); +}); diff --git a/packages/account/src/Components/forms/idv-form.tsx b/packages/account/src/Components/forms/idv-form.tsx index 48a5a5fd0c1d..4cb31bed1e26 100644 --- a/packages/account/src/Components/forms/idv-form.tsx +++ b/packages/account/src/Components/forms/idv-form.tsx @@ -1,6 +1,6 @@ import React from 'react'; import classNames from 'classnames'; -import { Field, FormikProps, FormikHandlers, FieldProps } from 'formik'; +import { Field, FormikProps, FieldProps, useFormikContext } from 'formik'; import { ResidenceList } from '@deriv/api-types'; import { localize } from '@deriv/translations'; import { formatInput, IDV_NOT_APPLICABLE_OPTION } from '@deriv/shared'; @@ -27,28 +27,18 @@ type TIDVForm = { selected_country: ResidenceList[0]; hide_hint?: boolean; class_name?: string; - can_skip_document_verification: boolean; -} & Partial & - FormikProps; + can_skip_document_verification?: boolean; +}; -const IDVForm = ({ - errors, - touched, - values, - handleBlur, - handleChange, - setFieldValue, - class_name, - selected_country, - hide_hint, - can_skip_document_verification = false, -}: TIDVForm) => { +const IDVForm = ({ class_name, selected_country, hide_hint, can_skip_document_verification = false }: TIDVForm) => { const [document_list, setDocumentList] = React.useState([]); const [document_image, setDocumentImage] = React.useState(null); const [selected_doc, setSelectedDoc] = React.useState(''); const { documents_supported: document_data, has_visual_sample } = selected_country?.identity?.services?.idv ?? {}; + const { errors, touched, values, handleBlur, handleChange, setFieldValue }: FormikProps = + useFormikContext(); const default_document = { id: '', text: '', @@ -156,7 +146,6 @@ const IDVForm = ({
{ +const PersonalDetailsForm = props => { const { is_virtual, is_mf, @@ -51,13 +43,16 @@ const PersonalDetailsForm = ({ is_rendered_for_onfido, should_close_tooltip, setShouldCloseTooltip, + warning_items, + status, } = props; const autocomplete_value = 'none'; - const PoiNameDobExampleIcon = PoiNameDobExample; const [is_tax_residence_popover_open, setIsTaxResidencePopoverOpen] = React.useState(false); const [is_tin_popover_open, setIsTinPopoverOpen] = React.useState(false); + const { errors, touched, values, setFieldValue, handleChange, handleBlur, setFieldTouched } = useFormikContext(); + React.useEffect(() => { if (should_close_tooltip) { handleToolTipStatus(); @@ -65,6 +60,17 @@ const PersonalDetailsForm = ({ } }, [should_close_tooltip, handleToolTipStatus, setShouldCloseTooltip]); + const imageLoader = () => { + switch (status) { + case 'POI_NAME_MISMATCH': + return ; + case 'POI_DOB_MISMATCH': + return ; + default: + return ; + } + }; + const getNameAndDobLabels = () => { const is_asterisk_needed = is_svg || is_mf || is_rendered_for_onfido || is_qualified_for_idv; const first_name_label = is_appstore || is_asterisk_needed ? localize('First name*') : localize('First name'); @@ -118,7 +124,7 @@ const PersonalDetailsForm = ({ )} } + side_note={imageLoader()} >
{'salutation' in values && ( diff --git a/packages/account/src/Components/personal-details/__tests__/personal-details.spec.js b/packages/account/src/Components/personal-details/__tests__/personal-details.spec.js index 210729fc956b..b97303eb4a6a 100644 --- a/packages/account/src/Components/personal-details/__tests__/personal-details.spec.js +++ b/packages/account/src/Components/personal-details/__tests__/personal-details.spec.js @@ -5,6 +5,7 @@ import { fireEvent, render, screen, waitFor, within } from '@testing-library/rea import { isDesktop, isMobile, PlatformContext } from '@deriv/shared'; import { splitValidationResultTypes } from '../../real-account-signup/helpers/utils'; import PersonalDetails from '../personal-details'; +import { shouldShowIdentityInformation, isDocumentTypeValid, isAdditionalDocumentValid } from 'Helpers/utils'; jest.mock('Assets/ic-poi-name-dob-example.svg', () => jest.fn(() => 'PoiNameDobExampleImage')); @@ -26,6 +27,13 @@ jest.mock('../../real-account-signup/helpers/utils.ts', () => ({ })), })); +jest.mock('Helpers/utils', () => ({ + ...jest.requireActual('Helpers/utils'), + isDocumentTypeValid: jest.fn(() => undefined), + shouldShowIdentityInformation: jest.fn(() => false), + isAdditionalDocumentValid: jest.fn(() => undefined), +})); + const mock_warnings = {}; const mock_errors = { account_opening_reason: 'Account opening reason is required.', @@ -744,4 +752,92 @@ describe('', () => { const el_tax_residence = screen.getByTestId('selected_value'); expect(el_tax_residence).toHaveTextContent('Malta'); }); + + it('should validate idv values when a document type is selected', async () => { + shouldShowIdentityInformation.mockReturnValue(true); + const new_props = { + ...props, + is_mf: false, + value: { + ...props.value, + document_type: { + value: 'national_id', + text: 'National ID', + }, + document_number: '123456789', + }, + residence_list: [ + { + value: 'tc', + identity: { + services: { + idv: { + documents_supported: { + document_1: { + display_name: 'Test document 1 name', + format: '5436454364243', + }, + document_2: { + display_name: 'Test document 2 name', + format: 'A54321', + }, + }, + has_visual_sample: true, + }, + }, + }, + }, + ], + }; + renderwithRouter(); + + await waitFor(() => { + expect(isDocumentTypeValid).toHaveBeenCalled(); + expect(isAdditionalDocumentValid).not.toHaveBeenCalled(); + }); + }); + + it('should validate idv values along with additional docuement number when a document type is selected', async () => { + shouldShowIdentityInformation.mockReturnValue(true); + const new_props = { + ...props, + is_mf: false, + value: { + ...props.value, + document_type: { + value: 'national_id', + text: 'National ID', + additional: '12345', + }, + document_number: '123456789', + }, + residence_list: [ + { + value: 'tc', + identity: { + services: { + idv: { + documents_supported: { + document_1: { + display_name: 'Test document 1 name', + format: '5436454364243', + }, + document_2: { + display_name: 'Test document 2 name', + format: 'A54321', + }, + }, + has_visual_sample: true, + }, + }, + }, + }, + ], + }; + renderwithRouter(); + + await waitFor(() => { + expect(isAdditionalDocumentValid).toHaveBeenCalled(); + }); + }); }); diff --git a/packages/account/src/Components/personal-details/personal-details.jsx b/packages/account/src/Components/personal-details/personal-details.jsx index dfd5045bee50..2277b09c17c6 100644 --- a/packages/account/src/Components/personal-details/personal-details.jsx +++ b/packages/account/src/Components/personal-details/personal-details.jsx @@ -136,7 +136,7 @@ const PersonalDetails = ({ onSubmit(getCurrentStep() - 1, values, actions.setSubmitting, goToNextStep); }} > - {({ handleSubmit, errors, setFieldValue, setFieldTouched, touched, values, handleChange, handleBlur }) => ( + {({ handleSubmit, errors, values }) => ( {({ setRef, height }) => (
@@ -201,13 +195,6 @@ const PersonalDetails = ({ })} > jest.fn(() => 'DocumentSubmitLogo')); -jest.mock('Helpers/utils.ts', () => ({ - ...jest.requireActual('Helpers/utils.ts'), +jest.mock('Helpers/utils', () => ({ + ...jest.requireActual('Helpers/utils'), getDocumentData: jest.fn((country_code, key) => { const data = { tc: { @@ -24,7 +25,7 @@ jest.mock('Helpers/utils.ts', () => ({ }; return data[country_code][key]; }), - getRegex: jest.fn(() => /5436454364243/i), + isDocumentNumberValid: jest.fn(() => undefined), })); jest.mock('@deriv/shared', () => ({ @@ -58,8 +59,14 @@ describe('', () => { services: { idv: { documents_supported: { - document_1: { display_name: 'Test document 1 name', format: '5436454364243' }, - document_2: { display_name: 'Test document 2 name', format: 'A54321' }, + document_1: { + display_name: 'Test document 1 name', + format: '5436454364243', + }, + document_2: { + display_name: 'Test document 2 name', + format: 'A54321', + }, }, has_visual_sample: true, }, @@ -129,15 +136,15 @@ describe('', () => { fireEvent.change(document_type_input, { target: { value: 'Test document 2 name' } }); expect(document_number_input).toBeEnabled(); expect(screen.queryByText(/please enter the correct format/i)).not.toBeInTheDocument(); - + (isDocumentNumberValid as jest.Mock).mockReturnValueOnce('please enter your document number'); fireEvent.blur(document_number_input); expect(await screen.findByText(/please enter your document number/i)).toBeInTheDocument(); - fireEvent.keyUp(document_number_input); + (isDocumentNumberValid as jest.Mock).mockReturnValueOnce('please enter the correct format'); fireEvent.change(document_number_input, { target: { value: 'A-32523' } }); expect(await screen.findByText(/please enter the correct format/i)).toBeInTheDocument(); - fireEvent.change(document_number_input, { target: { value: '5436454364243' } }); + fireEvent.change(document_number_input, { target: { value: '5436454364234' } }); await waitFor(() => { expect(screen.queryByText(/please enter the correct format/i)).not.toBeInTheDocument(); expect(screen.queryByText(/please enter a valid ID number/i)).not.toBeInTheDocument(); diff --git a/packages/account/src/Components/poi/idv-document-submit/idv-document-submit.tsx b/packages/account/src/Components/poi/idv-document-submit/idv-document-submit.tsx index 347cb848e396..2737aca31bda 100644 --- a/packages/account/src/Components/poi/idv-document-submit/idv-document-submit.tsx +++ b/packages/account/src/Components/poi/idv-document-submit/idv-document-submit.tsx @@ -1,8 +1,7 @@ import React from 'react'; -import PropTypes from 'prop-types'; import classNames from 'classnames'; import { Button } from '@deriv/components'; -import { Formik } from 'formik'; +import { Form, Formik, FormikHelpers } from 'formik'; import { localize } from '@deriv/translations'; import { WS, @@ -12,23 +11,45 @@ import { isDesktop, removeEmptyPropertiesFromObject, } from '@deriv/shared'; -import { documentAdditionalError, getRegex, validate, makeSettingsRequest, validateName } from 'Helpers/utils'; +import { + validate, + makeSettingsRequest, + validateName, + shouldHideHelperImage, + isDocumentTypeValid, + isAdditionalDocumentValid, + isDocumentNumberValid, +} from 'Helpers/utils'; import FormFooter from 'Components/form-footer'; import BackButtonIcon from 'Assets/ic-poi-back-btn.svg'; import IDVForm from 'Components/forms/idv-form'; import PersonalDetailsForm from 'Components/forms/personal-details-form'; import FormSubHeader from 'Components/form-sub-header'; +import { GetSettings, ResidenceList, IdentityVerificationAddDocumentResponse } from '@deriv/api-types'; +import { TIDVForm, TPersonalDetailsForm } from 'Types'; + +type TIdvDocumentSubmit = { + handleBack: () => void; + handleViewComplete: () => void; + selected_country: ResidenceList[0]; + account_settings: GetSettings; + getChangeableFields: () => Array; +}; + +type TIdvDocumentSubmitForm = TIDVForm & TPersonalDetailsForm; const IdvDocumentSubmit = ({ handleBack, handleViewComplete, selected_country, - is_from_external, account_settings, getChangeableFields, -}) => { +}: TIdvDocumentSubmit) => { const visible_settings = ['first_name', 'last_name', 'date_of_birth']; - const form_initial_values = filterObjProperties(account_settings, visible_settings) || {}; + + const form_initial_values = filterObjProperties(account_settings, visible_settings) as { + [Property in keyof TPersonalDetailsForm]: string; + }; if (form_initial_values.date_of_birth) { form_initial_values.date_of_birth = toMoment(form_initial_values.date_of_birth).format('YYYY-MM-DD'); @@ -36,7 +57,7 @@ const IdvDocumentSubmit = ({ const changeable_fields = [...getChangeableFields()]; - const initial_values = { + const initial_values: TIdvDocumentSubmitForm = { document_type: { id: '', text: '', @@ -48,43 +69,8 @@ const IdvDocumentSubmit = ({ ...form_initial_values, }; - const getExampleFormat = example_format => { - return example_format ? localize('Example: ') + example_format : ''; - }; - - const shouldHideHelperImage = document_id => document_id === IDV_NOT_APPLICABLE_OPTION.id; - - const isDocumentTypeValid = document_type => { - if (!document_type?.text) { - return localize('Please select a document type.'); - } - return undefined; - }; - - const isAdditionalDocumentValid = (document_type, document_additional) => { - const error_message = documentAdditionalError(document_additional, document_type.additional?.format); - if (error_message) { - return localize(error_message) + getExampleFormat(document_type.additional?.example_format); - } - return undefined; - }; - - const isDocumentNumberValid = (document_number, document_type) => { - const is_document_number_invalid = document_number === document_type.example_format; - if (!document_number) { - return localize('Please enter your document number. ') + getExampleFormat(document_type.example_format); - } else if (is_document_number_invalid) { - return localize('Please enter a valid ID number.'); - } - const format_regex = getRegex(document_type.value); - if (!format_regex.test(document_number)) { - return localize('Please enter the correct format. ') + getExampleFormat(document_type.example_format); - } - return undefined; - }; - - const validateFields = values => { - const errors = {}; + const validateFields = (values: TIdvDocumentSubmitForm) => { + const errors: Record = {}; const { document_type, document_number, document_additional } = values; const needs_additional_document = !!document_type.additional; @@ -109,7 +95,15 @@ const IdvDocumentSubmit = ({ return removeEmptyPropertiesFromObject(errors); }; - const submitHandler = async (values, { setSubmitting, setErrors }) => { + const submitHandler = async ( + values: TIdvDocumentSubmitForm, + { + setSubmitting, + setErrors, + }: Pick, 'setSubmitting'> & { + setErrors: (props: Record) => void; + } + ) => { setSubmitting(true); const request = makeSettingsRequest(values, changeable_fields); @@ -138,46 +132,25 @@ const IdvDocumentSubmit = ({ if (submit_data.document_type === IDV_NOT_APPLICABLE_OPTION.id) { return; } - WS.send(submit_data).then(response => { - setSubmitting(false); - if (response.error) { - setErrors({ error_message: response.error.message }); - return; + WS.send(submit_data).then( + (response: IdentityVerificationAddDocumentResponse & { error: { message: string } }) => { + setSubmitting(false); + if (response.error) { + setErrors({ error_message: response.error.message }); + return; + } + handleViewComplete(); } - handleViewComplete(); - }); + ); }; return ( - {({ - dirty, - errors, - handleBlur, - handleChange, - handleSubmit, - isSubmitting, - isValid, - setFieldValue, - setFieldTouched, - touched, - values, - }) => ( -
+ {({ dirty, isSubmitting, isValid, values }) => ( +
- +
-
+ )} ); }; -IdvDocumentSubmit.propTypes = { - account_settings: PropTypes.object, - getChangeableFields: PropTypes.func, - handleBack: PropTypes.func, - handleViewComplete: PropTypes.func, - is_from_external: PropTypes.bool, - selected_country: PropTypes.object, -}; - export default IdvDocumentSubmit; diff --git a/packages/account/src/Components/poi/poi-confirm-with-example-form-container/poi-confirm-with-example-form-container.tsx b/packages/account/src/Components/poi/poi-confirm-with-example-form-container/poi-confirm-with-example-form-container.tsx index 287a2415367d..da3696e33faa 100644 --- a/packages/account/src/Components/poi/poi-confirm-with-example-form-container/poi-confirm-with-example-form-container.tsx +++ b/packages/account/src/Components/poi/poi-confirm-with-example-form-container/poi-confirm-with-example-form-container.tsx @@ -148,17 +148,7 @@ const PoiConfirmWithExampleFormContainer = ({ }) => (
- +