From 89d70b7a77c1c54f6523fff368f14feec4154695 Mon Sep 17 00:00:00 2001 From: Shaheer Date: Thu, 4 May 2023 17:20:15 +0400 Subject: [PATCH 1/6] test: :test_tube: adds basic testcases for personal details section component --- .../__tests__/personal-details.spec.js | 69 ++++++++++++++++++- .../PersonalDetails/personal-details.jsx | 10 ++- .../shared/src/utils/location/location.ts | 2 +- .../declarative-validation-rules.ts | 6 +- 4 files changed, 81 insertions(+), 6 deletions(-) diff --git a/packages/account/src/Sections/Profile/PersonalDetails/__tests__/personal-details.spec.js b/packages/account/src/Sections/Profile/PersonalDetails/__tests__/personal-details.spec.js index 5908e032cb17..7a860ee0a426 100644 --- a/packages/account/src/Sections/Profile/PersonalDetails/__tests__/personal-details.spec.js +++ b/packages/account/src/Sections/Profile/PersonalDetails/__tests__/personal-details.spec.js @@ -1,6 +1,6 @@ import { jest, test } from '@jest/globals'; import React from 'react'; -import { cleanup, render, waitForElementToBeRemoved, waitFor } from '@testing-library/react'; +import { cleanup, render, waitForElementToBeRemoved, waitFor, screen } from '@testing-library/react'; import { createBrowserHistory } from 'history'; import { Router } from 'react-router'; import { PersonalDetailsForm } from '../personal-details.jsx'; @@ -19,6 +19,39 @@ jest.mock('@deriv/shared/src/services/ws-methods', () => ({ describe('', () => { const history = createBrowserHistory(); + const mock_props = { + authentication_status: {}, + is_eu: true, + is_mf: false, + is_uk: false, + is_svg: false, + is_virtual: false, + residence_list: [{}], + states_list: [], + refreshNotifications: jest.fn(), + showPOAAddressMismatchSuccessNotification: jest.fn(), + showPOAAddressMismatchFailureNotification: jest.fn(), + Notifications: '', + fetchResidenceList: jest.fn(), + fetchStatesList: jest.fn(), + has_residence: false, + account_settings: {}, + getChangeableFields: jest.fn().mockReturnValue([]), + current_landing_company: {}, + history: {}, + is_social_signup: false, + updateAccountStatus: jest.fn(), + has_poa_address_mismatch: false, + is_language_changing: false, + }; + + renderComponent = () => { + render( + + + + ); + }; it('should_render_successfully', async () => { window.HTMLElement.prototype.scrollIntoView = jest.fn(); @@ -54,6 +87,40 @@ describe('', () => { ); }); + it('should render all the personal details fields', async () => { + renderComponent(); + await waitFor(() => { + expect(screen.queryByText('First name*')).toBeInTheDocument(); + expect(screen.queryByText('Last name*')).toBeInTheDocument(); + expect(screen.queryByText('Place of birth*')).toBeInTheDocument(); + expect(screen.queryByText('Date of birth*')).toBeInTheDocument(); + expect(screen.queryByText('Citizenship*')).toBeInTheDocument(); + expect(screen.queryByText('Country of residence*')).toBeInTheDocument(); + expect(screen.queryByText('Phone number*')).toBeInTheDocument(); + expect(screen.queryByText('First line of address*')).toBeInTheDocument(); + expect(screen.queryByText('Second line of address (optional)')).toBeInTheDocument(); + expect(screen.queryByText('Town/City*')).toBeInTheDocument(); + expect(screen.queryByText('State/Province (optional)')).toBeInTheDocument(); + expect(screen.queryByText('Postal/ZIP code')).toBeInTheDocument(); + }); + }); + + it('should display label "Place of birth" without asterisk if is_svg is true', async () => { + mock_props.is_svg = true; + renderComponent(); + await waitFor(() => { + expect(screen.queryByText('Place of birth')).toBeInTheDocument(); + }); + }); + + it('should display label "Citizenship" without asterisk if is_eu is false', async () => { + mock_props.is_eu = false; + renderComponent(); + await waitFor(() => { + expect(screen.queryByText('Citizenship')).toBeInTheDocument(); + }); + }); + test.todo('Personal details component tests for different landing companies'); test.todo('Personal detail update Profile'); }); diff --git a/packages/account/src/Sections/Profile/PersonalDetails/personal-details.jsx b/packages/account/src/Sections/Profile/PersonalDetails/personal-details.jsx index 2d23dff625cf..229c5a534630 100644 --- a/packages/account/src/Sections/Profile/PersonalDetails/personal-details.jsx +++ b/packages/account/src/Sections/Profile/PersonalDetails/personal-details.jsx @@ -449,7 +449,7 @@ export const PersonalDetailsForm = ({ const showForm = show_form => setRestState({ show_form }); const isChangeableField = name => { - return rest_state.changeable_fields.some(field => field === name); + return rest_state.changeable_fields?.some(field => field === name); }; const initializeFormValues = () => { @@ -584,6 +584,7 @@ export const PersonalDetailsForm = ({ 'account-form account-form__personal-details--dashboard': is_appstore, })} onSubmit={handleSubmit} + data-testid='dt_account_personal_details_section' > @@ -652,6 +653,7 @@ export const PersonalDetailsForm = ({ disabled={!isChangeableField('first_name')} error={errors.first_name} id={'first_name'} + data-testid='first_name' /> @@ -682,6 +685,7 @@ export const PersonalDetailsForm = ({ required disabled={!isChangeableField('first_name')} error={errors.first_name} + data-testid='first_name' />
@@ -697,6 +701,7 @@ export const PersonalDetailsForm = ({ required disabled={!isChangeableField('last_name')} error={errors.last_name} + data-testid='last_name' />
@@ -872,6 +877,7 @@ export const PersonalDetailsForm = ({ onBlur={handleBlur} required error={errors.phone} + data-testid='phone' /> @@ -1007,6 +1013,7 @@ export const PersonalDetailsForm = ({ error={errors.address_line_1} required disabled={!isChangeableField('address_line_1')} + data-testid='address_line_1' />
@@ -1040,6 +1047,7 @@ export const PersonalDetailsForm = ({ onBlur={handleBlur} required disabled={!isChangeableField('address_city')} + data-testid='address_city' />
diff --git a/packages/shared/src/utils/location/location.ts b/packages/shared/src/utils/location/location.ts index 98f9d4bc8a0e..e3870cbc1a62 100644 --- a/packages/shared/src/utils/location/location.ts +++ b/packages/shared/src/utils/location/location.ts @@ -15,7 +15,7 @@ export type TLocationList = TType & { export const getLocation = (location_list: TLocationList[], value: string, type: keyof TType) => { const location_obj = location_list.find( - location => location[type === 'text' ? 'value' : 'text'].toLowerCase() === value.toLowerCase() + location => location[type === 'text' ? 'value' : 'text']?.toLowerCase() === value.toLowerCase() ); if (location_obj) return location_obj[type]; diff --git a/packages/shared/src/utils/validation/declarative-validation-rules.ts b/packages/shared/src/utils/validation/declarative-validation-rules.ts index 12e0809b6e69..33b11a68d977 100644 --- a/packages/shared/src/utils/validation/declarative-validation-rules.ts +++ b/packages/shared/src/utils/validation/declarative-validation-rules.ts @@ -25,17 +25,17 @@ export const validAddress = (value: string, options?: TOptions) => { if (options?.is_required && (!value || value.match(/^\s*$/))) { return { is_ok: false, - message: form_error_messages.empty_address(), + message: form_error_messages?.empty_address(), }; } else if (!validLength(value, { min: 0, max: 70 })) { return { is_ok: false, - message: form_error_messages.maxNumber(70), + message: form_error_messages?.maxNumber(70), }; } else if (!/^[\p{L}\p{Nd}\s'.,:;()\u00b0@#/-]{0,70}$/u.test(value)) { return { is_ok: false, - message: form_error_messages.address(), + message: form_error_messages?.address(), }; } return { is_ok: true }; From 708a74520f0c679d9271847e8ecdb37cd3305aa7 Mon Sep 17 00:00:00 2001 From: Shaheer Date: Fri, 5 May 2023 16:20:43 +0400 Subject: [PATCH 2/6] test: :test_tube: required fields error validation --- .../__tests__/personal-details.spec.js | 37 +++++++++++++++---- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/packages/account/src/Sections/Profile/PersonalDetails/__tests__/personal-details.spec.js b/packages/account/src/Sections/Profile/PersonalDetails/__tests__/personal-details.spec.js index 7a860ee0a426..2bb265fead4b 100644 --- a/packages/account/src/Sections/Profile/PersonalDetails/__tests__/personal-details.spec.js +++ b/packages/account/src/Sections/Profile/PersonalDetails/__tests__/personal-details.spec.js @@ -1,6 +1,6 @@ import { jest, test } from '@jest/globals'; import React from 'react'; -import { cleanup, render, waitForElementToBeRemoved, waitFor, screen } from '@testing-library/react'; +import { cleanup, render, waitForElementToBeRemoved, waitFor, screen, fireEvent } from '@testing-library/react'; import { createBrowserHistory } from 'history'; import { Router } from 'react-router'; import { PersonalDetailsForm } from '../personal-details.jsx'; @@ -36,7 +36,9 @@ describe('', () => { fetchStatesList: jest.fn(), has_residence: false, account_settings: {}, - getChangeableFields: jest.fn().mockReturnValue([]), + getChangeableFields: jest + .fn() + .mockReturnValue(['first_name', 'last_name', 'phone', 'address_line_1', 'address_city']), current_landing_company: {}, history: {}, is_social_signup: false, @@ -45,10 +47,14 @@ describe('', () => { is_language_changing: false, }; - renderComponent = () => { + renderComponent = (modified_props = {}) => { + const updated_props = { + ...mock_props, + ...modified_props, + }; render( - + ); }; @@ -106,21 +112,36 @@ describe('', () => { }); it('should display label "Place of birth" without asterisk if is_svg is true', async () => { - mock_props.is_svg = true; - renderComponent(); + renderComponent({ is_svg: true }); await waitFor(() => { expect(screen.queryByText('Place of birth')).toBeInTheDocument(); }); }); it('should display label "Citizenship" without asterisk if is_eu is false', async () => { - mock_props.is_eu = false; - renderComponent(); + renderComponent({ is_eu: false }); await waitFor(() => { expect(screen.queryByText('Citizenship')).toBeInTheDocument(); }); }); + it('should have "required" validation errors on required form fields', async () => { + renderComponent(); + await waitFor(async () => { + const first_name = screen.getByTestId('first_name'); + const last_name = screen.getByTestId('last_name'); + const phone = screen.getByTestId('phone'); + const address_line_1 = screen.getByTestId('address_line_1'); + const address_city = screen.getByTestId('address_city'); + fireEvent.blur(first_name); + fireEvent.blur(last_name); + fireEvent.blur(phone); + fireEvent.blur(address_line_1); + fireEvent.blur(address_city); + }); + expect(screen.getAllByText('This field is required')).toHaveLength(5); + }); + test.todo('Personal details component tests for different landing companies'); test.todo('Personal detail update Profile'); }); From 81dfab5573097847a4bb4e0f5d27044155596e61 Mon Sep 17 00:00:00 2001 From: Shaheer Date: Fri, 5 May 2023 19:15:01 +0400 Subject: [PATCH 3/6] test: :test_tube: basic tests for first and last name fields --- .../__tests__/personal-details.spec.js | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/packages/account/src/Sections/Profile/PersonalDetails/__tests__/personal-details.spec.js b/packages/account/src/Sections/Profile/PersonalDetails/__tests__/personal-details.spec.js index 2bb265fead4b..12bb17a2d451 100644 --- a/packages/account/src/Sections/Profile/PersonalDetails/__tests__/personal-details.spec.js +++ b/packages/account/src/Sections/Profile/PersonalDetails/__tests__/personal-details.spec.js @@ -142,6 +142,56 @@ describe('', () => { expect(screen.getAllByText('This field is required')).toHaveLength(5); }); + it('should display error for 2-50 characters length validation, for First and Last name when entered characters are less than 2', async () => { + renderComponent(); + await waitFor(async () => { + const first_name = screen.getByTestId('first_name'); + const last_name = screen.getByTestId('last_name'); + fireEvent.input(first_name, { target: { value: 'a' } }); + fireEvent.input(last_name, { target: { value: 'b' } }); + }); + expect(screen.getAllByText('You should enter 2-50 characters.')).toHaveLength(2); + }); + + it('should display error for 2-50 characters length validation, for First and Last name when entered characters are more than 50', async () => { + renderComponent(); + await waitFor(async () => { + const first_name = screen.getByTestId('first_name'); + const last_name = screen.getByTestId('last_name'); + fireEvent.input(first_name, { target: { value: 'fifty chars fifty chars fifty chars fifty chars fifty' } }); + fireEvent.input(last_name, { target: { value: 'fifty chars fifty chars fifty chars fifty chars fifty' } }); + }); + expect(screen.getAllByText('You should enter 2-50 characters.')).toHaveLength(2); + }); + + it('should display error for the regex validation, for First and Last name when unacceptable characters are entered', async () => { + renderComponent(); + await waitFor(async () => { + const first_name = screen.getByTestId('first_name'); + const last_name = screen.getByTestId('last_name'); + fireEvent.input(first_name, { target: { value: 'test 3' } }); + fireEvent.input(last_name, { target: { value: 'name_' } }); + expect(screen.getAllByText('Letters, spaces, periods, hyphens, apostrophes only.')).toHaveLength(2); + fireEvent.input(first_name, { target: { value: 'name_' } }); + fireEvent.input(last_name, { target: { value: 'test 3' } }); + expect(screen.getAllByText('Letters, spaces, periods, hyphens, apostrophes only.')).toHaveLength(2); + }); + }); + + it('should not display error for the regex validation, for First and Last name when acceptable characters are entered', async () => { + renderComponent(); + await waitFor(async () => { + const first_name = screen.getByTestId('first_name'); + const last_name = screen.getByTestId('last_name'); + fireEvent.input(first_name, { target: { value: "test-with' chars." } }); + fireEvent.input(last_name, { target: { value: 'Deuxième Prénom résidence' } }); + expect(screen.queryByText('Letters, spaces, periods, hyphens, apostrophes only.')).not.toBeInTheDocument(); + fireEvent.input(first_name, { target: { value: 'Deuxième Prénom résidence' } }); + fireEvent.input(last_name, { target: { value: "test-with' chars." } }); + expect(screen.queryByText('Letters, spaces, periods, hyphens, apostrophes only.')).not.toBeInTheDocument(); + }); + }); + test.todo('Personal details component tests for different landing companies'); test.todo('Personal detail update Profile'); }); From db7aae45129ecfcbff93da9403e000f78506de3d Mon Sep 17 00:00:00 2001 From: Shaheer Date: Mon, 8 May 2023 08:40:17 +0400 Subject: [PATCH 4/6] fix: :test_tube: const function reference circleci fix --- .../Profile/PersonalDetails/__tests__/personal-details.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/account/src/Sections/Profile/PersonalDetails/__tests__/personal-details.spec.js b/packages/account/src/Sections/Profile/PersonalDetails/__tests__/personal-details.spec.js index 12bb17a2d451..4cd4af7b027e 100644 --- a/packages/account/src/Sections/Profile/PersonalDetails/__tests__/personal-details.spec.js +++ b/packages/account/src/Sections/Profile/PersonalDetails/__tests__/personal-details.spec.js @@ -47,7 +47,7 @@ describe('', () => { is_language_changing: false, }; - renderComponent = (modified_props = {}) => { + const renderComponent = (modified_props = {}) => { const updated_props = { ...mock_props, ...modified_props, From ace01ca1af91d26f10ec1a1c25fb2dd6398ed0d3 Mon Sep 17 00:00:00 2001 From: Shaheer Date: Mon, 8 May 2023 09:07:37 +0400 Subject: [PATCH 5/6] refactor: :test_tube: renames data-testid as per convention --- .../__tests__/personal-details.spec.js | 26 +++++++++---------- .../PersonalDetails/personal-details.jsx | 14 +++++----- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/packages/account/src/Sections/Profile/PersonalDetails/__tests__/personal-details.spec.js b/packages/account/src/Sections/Profile/PersonalDetails/__tests__/personal-details.spec.js index 4cd4af7b027e..18ae1d48e3e6 100644 --- a/packages/account/src/Sections/Profile/PersonalDetails/__tests__/personal-details.spec.js +++ b/packages/account/src/Sections/Profile/PersonalDetails/__tests__/personal-details.spec.js @@ -128,11 +128,11 @@ describe('', () => { it('should have "required" validation errors on required form fields', async () => { renderComponent(); await waitFor(async () => { - const first_name = screen.getByTestId('first_name'); - const last_name = screen.getByTestId('last_name'); - const phone = screen.getByTestId('phone'); - const address_line_1 = screen.getByTestId('address_line_1'); - const address_city = screen.getByTestId('address_city'); + const first_name = screen.getByTestId('dt_first_name'); + const last_name = screen.getByTestId('dt_last_name'); + const phone = screen.getByTestId('dt_phone'); + const address_line_1 = screen.getByTestId('dt_address_line_1'); + const address_city = screen.getByTestId('dt_address_city'); fireEvent.blur(first_name); fireEvent.blur(last_name); fireEvent.blur(phone); @@ -145,8 +145,8 @@ describe('', () => { it('should display error for 2-50 characters length validation, for First and Last name when entered characters are less than 2', async () => { renderComponent(); await waitFor(async () => { - const first_name = screen.getByTestId('first_name'); - const last_name = screen.getByTestId('last_name'); + const first_name = screen.getByTestId('dt_first_name'); + const last_name = screen.getByTestId('dt_last_name'); fireEvent.input(first_name, { target: { value: 'a' } }); fireEvent.input(last_name, { target: { value: 'b' } }); }); @@ -156,8 +156,8 @@ describe('', () => { it('should display error for 2-50 characters length validation, for First and Last name when entered characters are more than 50', async () => { renderComponent(); await waitFor(async () => { - const first_name = screen.getByTestId('first_name'); - const last_name = screen.getByTestId('last_name'); + const first_name = screen.getByTestId('dt_first_name'); + const last_name = screen.getByTestId('dt_last_name'); fireEvent.input(first_name, { target: { value: 'fifty chars fifty chars fifty chars fifty chars fifty' } }); fireEvent.input(last_name, { target: { value: 'fifty chars fifty chars fifty chars fifty chars fifty' } }); }); @@ -167,8 +167,8 @@ describe('', () => { it('should display error for the regex validation, for First and Last name when unacceptable characters are entered', async () => { renderComponent(); await waitFor(async () => { - const first_name = screen.getByTestId('first_name'); - const last_name = screen.getByTestId('last_name'); + const first_name = screen.getByTestId('dt_first_name'); + const last_name = screen.getByTestId('dt_last_name'); fireEvent.input(first_name, { target: { value: 'test 3' } }); fireEvent.input(last_name, { target: { value: 'name_' } }); expect(screen.getAllByText('Letters, spaces, periods, hyphens, apostrophes only.')).toHaveLength(2); @@ -181,8 +181,8 @@ describe('', () => { it('should not display error for the regex validation, for First and Last name when acceptable characters are entered', async () => { renderComponent(); await waitFor(async () => { - const first_name = screen.getByTestId('first_name'); - const last_name = screen.getByTestId('last_name'); + const first_name = screen.getByTestId('dt_first_name'); + const last_name = screen.getByTestId('dt_last_name'); fireEvent.input(first_name, { target: { value: "test-with' chars." } }); fireEvent.input(last_name, { target: { value: 'Deuxième Prénom résidence' } }); expect(screen.queryByText('Letters, spaces, periods, hyphens, apostrophes only.')).not.toBeInTheDocument(); diff --git a/packages/account/src/Sections/Profile/PersonalDetails/personal-details.jsx b/packages/account/src/Sections/Profile/PersonalDetails/personal-details.jsx index 229c5a534630..a826ba4db462 100644 --- a/packages/account/src/Sections/Profile/PersonalDetails/personal-details.jsx +++ b/packages/account/src/Sections/Profile/PersonalDetails/personal-details.jsx @@ -653,7 +653,7 @@ export const PersonalDetailsForm = ({ disabled={!isChangeableField('first_name')} error={errors.first_name} id={'first_name'} - data-testid='first_name' + data-testid='dt_first_name' /> @@ -685,7 +685,7 @@ export const PersonalDetailsForm = ({ required disabled={!isChangeableField('first_name')} error={errors.first_name} - data-testid='first_name' + data-testid='dt_first_name' />
@@ -701,7 +701,7 @@ export const PersonalDetailsForm = ({ required disabled={!isChangeableField('last_name')} error={errors.last_name} - data-testid='last_name' + data-testid='dt_last_name' />
@@ -877,7 +877,7 @@ export const PersonalDetailsForm = ({ onBlur={handleBlur} required error={errors.phone} - data-testid='phone' + data-testid='dt_phone' /> @@ -1013,7 +1013,7 @@ export const PersonalDetailsForm = ({ error={errors.address_line_1} required disabled={!isChangeableField('address_line_1')} - data-testid='address_line_1' + data-testid='dt_address_line_1' />
@@ -1047,7 +1047,7 @@ export const PersonalDetailsForm = ({ onBlur={handleBlur} required disabled={!isChangeableField('address_city')} - data-testid='address_city' + data-testid='dt_address_city' />
From 57905d7c94f1d148a0a4a33f07fd74b165be6e19 Mon Sep 17 00:00:00 2001 From: Shaheer Date: Mon, 3 Jul 2023 16:10:41 +0400 Subject: [PATCH 6/6] test: :test_tube: fixes circleci test failure --- .../PersonalDetails/__tests__/personal-details.spec.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/account/src/Sections/Profile/PersonalDetails/__tests__/personal-details.spec.js b/packages/account/src/Sections/Profile/PersonalDetails/__tests__/personal-details.spec.js index 18ae1d48e3e6..5a17ecfcac6b 100644 --- a/packages/account/src/Sections/Profile/PersonalDetails/__tests__/personal-details.spec.js +++ b/packages/account/src/Sections/Profile/PersonalDetails/__tests__/personal-details.spec.js @@ -1,3 +1,4 @@ +import { initFormErrorMessages } from '@deriv/shared'; import { jest, test } from '@jest/globals'; import React from 'react'; import { cleanup, render, waitForElementToBeRemoved, waitFor, screen, fireEvent } from '@testing-library/react'; @@ -126,6 +127,10 @@ describe('', () => { }); it('should have "required" validation errors on required form fields', async () => { + const form_error_messages = { + empty_address: () => 'This field is required', + }; + initFormErrorMessages(form_error_messages); renderComponent(); await waitFor(async () => { const first_name = screen.getByTestId('dt_first_name');