diff --git a/packages/cfd/src/Containers/investor-password-manager/__tests__/investor-password-manager.spec.tsx b/packages/cfd/src/Containers/investor-password-manager/__tests__/investor-password-manager.spec.tsx new file mode 100644 index 000000000000..ee817c7ed501 --- /dev/null +++ b/packages/cfd/src/Containers/investor-password-manager/__tests__/investor-password-manager.spec.tsx @@ -0,0 +1,138 @@ +import React from 'react'; +import { screen, render, waitFor, fireEvent } from '@testing-library/react'; +import InvestorPasswordManager from '../investor-password-manager'; +import { localize } from '@deriv/translations'; + +type TValidLengthOptions = { + min?: number; + max?: number; +}; + +jest.mock('@deriv/shared/src/services/ws-methods', () => ({ + __esModule: true, + default: 'mockedDefaultExport', + WS: { + verifyEmail: jest.fn(() => Promise.resolve()), + }, +})); + +const validLengthMock = (value = '', options: TValidLengthOptions) => + (options.min ? value.length >= options.min : true) && (options.max ? value.length <= options.max : true); + +const mock_errors = { + password: () => localize('Password should have lower and uppercase English letters with numbers.'), + repeated_chars_are_easy: () => localize('Repeats like "aaa" are easy to guess'), + repeated_patterns_are_easy: () => localize('Repeats like "abcabcabc" are only slightly harder to guess than "abc"'), + recent_years_are_easy: () => localize('Recent years are easy to guess'), +}; + +jest.mock('@deriv/shared/src/utils/validation/declarative-validation-rules.ts', () => ({ + getErrorMessages: jest.fn(() => ({ + password_warnings: mock_errors, + })), + validLength: jest.fn((value, options) => validLengthMock(value, options)), +})); + +describe(' ', () => { + const mock_props = { + error_message_investor: 'Forgot your password? Please reset your password.', + is_submit_success_investor: false, + multi_step_ref: { current: { goNextStep: jest.fn(), goPrevStep: jest.fn() } }, + onSubmit: jest.fn(), + setPasswordType: jest.fn(() => 'investor'), + toggleModal: jest.fn(), + validatePassword: jest.fn(), + }; + + it('should render the correct texts ', async () => { + render(); + expect(await screen.findByText(/new investor password/i)).toBeInTheDocument(); + expect( + screen.getByText( + /use this password to grant viewing access to another user. While they may view your trading account, they will not be able to trade or take any other actions/i + ) + ).toBeInTheDocument(); + expect( + screen.getByText( + /if this is the first time you try to create a password, or you have forgotten your password, please reset it/i + ) + ).toBeInTheDocument(); + expect(screen.getByText(/current investor password/i)).toBeInTheDocument(); + expect(screen.getByText(/new investor password/i)).toBeInTheDocument(); + }); + + it('should fill the password field and trigger the appropriate error message for repeated password pattern', async () => { + render(); + expect(await screen.findByText(/new investor password/i)).toBeInTheDocument(); + const new_investor = screen.getByLabelText(/new investor password/i); + await waitFor(() => { + fireEvent.change(new_investor, { target: { value: 'abcabcabc' } }); + }); + expect( + await screen.findByText(/repeats like "abcabcabc" are only slightly harder to guess than "abc"/i) + ).toBeInTheDocument(); + }); + + it('should fill the password field and trigger the appropriate error message for repeated characters', async () => { + render(); + expect(await screen.findByText(/new investor password/i)).toBeInTheDocument(); + const new_investor = screen.getByLabelText(/new investor password/i); + await waitFor(() => { + fireEvent.change(new_investor, { target: { value: 'aaaaa' } }); + }); + expect(await screen.findByText(/repeats like "aaa" are easy to guess/i)).toBeInTheDocument(); + }); + + it('should fill the password field and trigger the appropriate error message for using recent years', async () => { + render(); + expect(await screen.findByText(/new investor password/i)).toBeInTheDocument(); + const new_investor = screen.getByLabelText(/new investor password/i); + await waitFor(() => { + fireEvent.change(new_investor, { target: { value: '1996' } }); + }); + + expect(await screen.findByText(/recent years are easy to guess/i)).toBeInTheDocument(); + }); + + it('should fill the password field and trigger the appropriate message for strong and valid password', async () => { + render(); + expect(await screen.findByText(/new investor password/i)).toBeInTheDocument(); + const new_investor = screen.getByLabelText(/new investor password/i); + await waitFor(() => { + fireEvent.change(new_investor, { target: { value: 'Qzzxcc!lopi1' } }); + expect( + screen.getAllByText( + /strong passwords contain at least 8 characters, combine uppercase and lowercase letters and numbers/i + )[0] + ).toBeInTheDocument(); + }); + }); + + it('should fill the password fields and trigger the appropriate message and enable the change password button', async () => { + const mockOnSubmit = jest.fn(); + render(); + expect(await screen.findByText(/new investor password/i)).toBeInTheDocument(); + const current_investor = screen.getByLabelText(/current investor password/i); + const new_investor = screen.getByLabelText(/new investor password/i); + const change_investor_password_btn = screen.getByText(/change investor password/i); + await waitFor(() => { + fireEvent.change(current_investor, { target: { value: 'Testing1234' } }); + fireEvent.change(new_investor, { target: { value: 'Qzzxcc!lopi1' } }); + expect( + screen.getAllByText( + /strong passwords contain at least 8 characters, combine uppercase and lowercase letters and numbers/i + )[0] + ).toBeInTheDocument(); + expect(change_investor_password_btn).toBeEnabled(); + fireEvent.click(change_investor_password_btn); + expect(screen.getByTestId('dt_error_message_investor')).toBeInTheDocument(); + expect(mockOnSubmit).toHaveBeenCalled(); + }); + }); + + it('should render success message if the user clicks on create or reset investor passwords', async () => { + render(); + expect(screen.getByText(/your investor password has been changed/i)).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /ok/i })).toBeInTheDocument(); + }); +}); diff --git a/packages/cfd/src/Containers/investor-password-manager/cfd-password-success-message.tsx b/packages/cfd/src/Containers/investor-password-manager/cfd-password-success-message.tsx new file mode 100644 index 000000000000..f69b9b32c50f --- /dev/null +++ b/packages/cfd/src/Containers/investor-password-manager/cfd-password-success-message.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { Text, Button, Icon } from '@deriv/components'; +import { localize, Localize } from '@deriv/translations'; +import { TCFDPasswordSuccessMessage } from '../props.types'; + +const CFDPasswordSuccessMessage = ({ toggleModal, is_investor }: TCFDPasswordSuccessMessage) => ( +
+ + + {is_investor ? ( + + ) : ( + + )} + + +
+); + +export default CFDPasswordSuccessMessage; diff --git a/packages/cfd/src/Containers/investor-password-manager/index.ts b/packages/cfd/src/Containers/investor-password-manager/index.ts new file mode 100644 index 000000000000..90455a081996 --- /dev/null +++ b/packages/cfd/src/Containers/investor-password-manager/index.ts @@ -0,0 +1,3 @@ +import InvestorPasswordManager from './investor-password-manager'; + +export default InvestorPasswordManager; diff --git a/packages/cfd/src/Containers/investor-password-manager.tsx b/packages/cfd/src/Containers/investor-password-manager/investor-password-manager.tsx similarity index 82% rename from packages/cfd/src/Containers/investor-password-manager.tsx rename to packages/cfd/src/Containers/investor-password-manager/investor-password-manager.tsx index ff9d8355eb2c..7602cca80901 100644 --- a/packages/cfd/src/Containers/investor-password-manager.tsx +++ b/packages/cfd/src/Containers/investor-password-manager/investor-password-manager.tsx @@ -1,26 +1,20 @@ -import PropTypes from 'prop-types'; import React from 'react'; import { Field, Form, Formik, FieldProps } from 'formik'; -import { PasswordInput, PasswordMeter, Text, Button, Icon } from '@deriv/components'; +import { PasswordInput, PasswordMeter, Text, Button } from '@deriv/components'; import { localize, Localize } from '@deriv/translations'; import { getErrorMessages } from '@deriv/shared'; -import { TCFDPasswordSuccessMessage, TInvestorPasswordManager, TPasswordManagerModalFormValues } from './props.types'; +import CFDPasswordSuccessMessage from './cfd-password-success-message'; +import { TMultiStepRefProps, TPasswordManagerModalFormValues } from '../props.types'; -const CFDPasswordSuccessMessage = ({ toggleModal, is_investor }: TCFDPasswordSuccessMessage) => ( -
- - - {is_investor ? ( - - ) : ( - - )} - - -
-); +type TInvestorPasswordManagerProps = { + error_message_investor: string; + is_submit_success_investor: boolean; + multi_step_ref: React.MutableRefObject; + onSubmit: (values: TPasswordManagerModalFormValues) => Promise; + setPasswordType: (value: string) => void; + toggleModal: () => void; + validatePassword: (values: { old_password: string; new_password: string; password_type: string }) => void | object; +}; const InvestorPasswordManager = ({ error_message_investor, @@ -30,7 +24,7 @@ const InvestorPasswordManager = ({ setPasswordType, toggleModal, validatePassword, -}: TInvestorPasswordManager) => { +}: TInvestorPasswordManagerProps) => { if (is_submit_success_investor) { return ; } @@ -135,14 +129,4 @@ const InvestorPasswordManager = ({ ); }; -InvestorPasswordManager.propTypes = { - error_message_investor: PropTypes.string, - is_submit_success_investor: PropTypes.bool, - multi_step_ref: PropTypes.object, - onSubmit: PropTypes.func, - setPasswordType: PropTypes.func, - toggleModal: PropTypes.func, - validatePassword: PropTypes.func, -}; - export default InvestorPasswordManager; diff --git a/packages/cfd/src/Containers/props.types.ts b/packages/cfd/src/Containers/props.types.ts index b2d0694d2cdf..e81ce81d470b 100644 --- a/packages/cfd/src/Containers/props.types.ts +++ b/packages/cfd/src/Containers/props.types.ts @@ -106,16 +106,6 @@ export type TMultiStepRefProps = { goPrevStep: () => void; }; -export type TInvestorPasswordManager = { - error_message_investor: string; - is_submit_success_investor: boolean; - multi_step_ref: React.MutableRefObject; - onSubmit: (values: TPasswordManagerModalFormValues) => Promise; - setPasswordType: (value: string) => void; - toggleModal: () => void; - validatePassword: (values: { old_password: string; new_password: string; password_type: string }) => void | object; -}; - export type TCountdownComponent = { count_from: number; onTimeout: () => void;