Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BOT] maryia/BOT-1479/[Test Coverage] Folder: bot-web-ui/src/components/self-exclusion #16234

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
import React from 'react';
import { BrowserRouter as Router } from 'react-router-dom';
import { mockStore, StoreProvider } from '@deriv/stores';
import { render, screen, waitFor } from '@testing-library/react';
import { mock_ws } from 'Utils/mock';
import RootStore from 'Stores/root-store';
import { DBotStoreProvider, mockDBotStore } from 'Stores/useDBotStore';
import SelfExclusion, { SelfExclusionForm } from '../self-exclusion';
import { Formik } from 'formik';
import userEvent from '@testing-library/user-event';

jest.mock('@deriv/bot-skeleton/src/scratch/dbot', () => ({}));

jest.mock('@deriv/components', () => ({
...jest.requireActual('@deriv/components'),
Modal: ({ children, is_open }: { children: JSX.Element; is_open: boolean }) => is_open && <div>{children}</div>,
}));
maryia-matskevich-deriv marked this conversation as resolved.
Show resolved Hide resolved

const mocked_props = {
is_onscreen_keyboard_active: true,
is_logged_in: true,
initial_values: {
run_limit: 5,
form_max_losses: 5,
},
api_max_losses: 1000,
onRunButtonClick: jest.fn(),
resetSelfExclusion: jest.fn(),
updateSelfExclusion: jest.fn(),
setRunLimit: jest.fn(),
is_mobile: true,
};

const mocked_prop_self_exclusion = {
onRunButtonClick: jest.fn(),
};
const mockOnSubmit = jest.fn();
const mockValidate = jest.fn();

describe('SelfExclusionForm', () => {
let wrapper: ({ children }: { children: JSX.Element }) => JSX.Element, mock_DBot_store: RootStore | undefined;
const mock_store = mockStore({});

beforeEach(() => {
mock_DBot_store = mockDBotStore(mock_store, mock_ws);

wrapper = ({ children }: { children: JSX.Element }) => (
<StoreProvider store={mock_store}>
<DBotStoreProvider ws={mock_ws} mock={mock_DBot_store}>
<Formik initialValues={mocked_props.initial_values} onSubmit={mockOnSubmit} validate={mockValidate}>
<Router>{children}</Router>
</Formik>
</DBotStoreProvider>
</StoreProvider>
);
});

it('check if form elements are present', () => {
render(<SelfExclusionForm {...mocked_props} />, { wrapper });

expect(screen.getByLabelText('Daily loss limit')).toBeInTheDocument();
expect(screen.getByLabelText('Maximum consecutive trades')).toBeInTheDocument();
expect(screen.getByText('Apply and run')).toBeInTheDocument();
expect(screen.getByText('Cancel')).toBeInTheDocument();
});

it('the form should be submit-able and updateSelfExclusion() method should be called upon submission.', async () => {
render(<SelfExclusionForm {...mocked_props} />, { wrapper });

const submit_button = screen.getByRole('button', { name: 'Apply and run' });
userEvent.click(submit_button);

await waitFor(() => {
expect(mocked_props.updateSelfExclusion).toHaveBeenCalled();
});
});

it('the form should be submit-able and updateSelfExclusion() method should be called upon submission, along with displaying the correct error message.', async () => {
render(<SelfExclusionForm {...mocked_props} />, { wrapper });

mocked_props.updateSelfExclusion.mockResolvedValue({ error: { message: 'Simulated error message' } });
const submit_button = screen.getByRole('button', { name: 'Apply and run' });
userEvent.click(submit_button);

await waitFor(() => {
expect(screen.getByText('Simulated error message'));
});
});

it('the form should display the empty values for the "run_limit" and "form_max_losses" fields if they are submitted without being filled in.', async () => {
render(<SelfExclusionForm {...mocked_props} initial_values={{}} />, { wrapper });

const run_limit = screen.getByTestId('run_limit');
const form_max_losses = screen.getByTestId('form_max_losses');

const submit_button = screen.getByRole('button', { name: 'Apply and run' });
userEvent.click(submit_button);
//validation begins when the input field is touched.
userEvent.type(run_limit, ' ');

await waitFor(() => {
expect(run_limit).toHaveValue(' ');
expect(form_max_losses).toHaveValue('');
});
});

it('the form should display the correct error message when submitted with a negative number value in the form_max_losses field.', async () => {
render(<SelfExclusionForm {...mocked_props} />, { wrapper });

const form_max_losses = screen.getByTestId('form_max_losses');
userEvent.type(form_max_losses, '-5');

const submit_button = screen.getByRole('button', { name: 'Apply and run' });
userEvent.click(submit_button);

await waitFor(() => {
expect(screen.getByText('Should be a valid number')).toBeInTheDocument();
});
});

it('the form should display the correct error message when submitted with a value that exceeds the maximum character limit in the form_max_losses field.', async () => {
render(<SelfExclusionForm {...mocked_props} />, { wrapper });

const form_max_losses = screen.getByTestId('form_max_losses');
userEvent.type(form_max_losses, '99999999999991');

const submit_button = screen.getByRole('button', { name: 'Apply and run' });
userEvent.click(submit_button);

await waitFor(() => {
expect(screen.getByText('Please enter a number between 0 and 1000.')).toBeInTheDocument();
});
});

it('should display the correct error message, when the form is submitted with a non-integer value in the run_limit field', async () => {
render(<SelfExclusionForm {...mocked_props} />, { wrapper });

const run_limit = screen.getByTestId('run_limit');
userEvent.type(run_limit, 'text');

const submit_button = screen.getByRole('button', { name: 'Apply and run' });
userEvent.click(submit_button);

await waitFor(() => {
expect(screen.getByText('Should be a valid number')).toBeInTheDocument();
});
});

it('should display the correct error message, when the form is submitted with a decimal value with more than 2 digits after the dot in the form_max_losses field', async () => {
render(<SelfExclusionForm {...mocked_props} />, { wrapper });

const form_max_losses = screen.getByTestId('form_max_losses');
userEvent.type(form_max_losses, '0.12345');

const submit_button = screen.getByRole('button', { name: 'Apply and run' });
userEvent.click(submit_button);

await waitFor(() => {
expect(screen.getByText(/Reached maximum number of decimals/i)).toBeInTheDocument();
});
});

it('the is_restricted value should be falsy when is_logged_in is false', () => {
render(<SelfExclusionForm {...mocked_props} is_logged_in={false} />, { wrapper });

expect(mock_DBot_store?.self_exclusion.is_restricted).toBeFalsy();
});
});

describe('SelfExclusion', () => {
let wrapper: ({ children }: { children: JSX.Element }) => JSX.Element, mock_DBot_store: RootStore | undefined;
const mock_store = mockStore({});

beforeEach(() => {
mock_DBot_store = mockDBotStore(mock_store, mock_ws);
mock_store.client.is_logged_in = true;
mock_DBot_store?.self_exclusion.setIsRestricted(true);

wrapper = ({ children }: { children: JSX.Element }) => (
<StoreProvider store={mock_store}>
<DBotStoreProvider ws={mock_ws} mock={mock_DBot_store}>
<Formik initialValues={mocked_props.initial_values} onSubmit={mockOnSubmit} validate={mockValidate}>
<Router>{children}</Router>
</Formik>
</DBotStoreProvider>
</StoreProvider>
);
});

it('renders the desktop version of SelfExclusionForm component when on desktop, is_restricted and is_logged_in equal true', () => {
mock_store.ui.is_desktop = true;

render(<SelfExclusion {...mocked_prop_self_exclusion} />, { wrapper });

expect(screen.getByTestId('self-exclusion')).toHaveClass('db-self-exclusion');
expect(
screen.queryByRole('button', { name: /Cancel/i }) &&
screen.queryByRole('button', { name: /Apply and run/i })
).toBeInTheDocument();
});

it('renders the desktop version of SelfExclusionForm component when on mobile, is_restricted and is_logged_in equal true, modal opens', () => {
mock_store.ui.is_desktop = false;

render(<SelfExclusion {...mocked_prop_self_exclusion} />, { wrapper });

expect(screen.getByText('Limits')).toBeInTheDocument();
expect(
screen.queryByRole('button', { name: /Cancel/i }) &&
screen.queryByRole('button', { name: /Apply and run/i })
).toBeInTheDocument();
});

it('make is_restricted falsy upon clicking the cancel button', () => {
render(<SelfExclusion {...mocked_prop_self_exclusion} />, { wrapper });

const cancelButton = screen.getByRole('button', { name: /Cancel/i });
userEvent.click(cancelButton);

expect(mock_DBot_store?.self_exclusion.is_restricted).toBeFalsy();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { observer, useStore } from '@deriv/stores';
import { localize } from '@deriv/translations';
import { useDBotStore } from 'Stores/useDBotStore';

const SelfExclusionForm = props => {
export const SelfExclusionForm = props => {
const [max_losses_error, setMaxLossesError] = React.useState('');
const {
is_onscreen_keyboard_active,
Expand Down Expand Up @@ -85,8 +85,8 @@ const SelfExclusionForm = props => {
});

decimal_limit.forEach(item => {
const amount_decimal_array = values[item].toString().split('.')[1];
const amount_decimal_places = amount_decimal_array ? amount_decimal_array.length || 0 : 0;
const amount_decimal_array = values[item]?.toString().split('.')[1];
const amount_decimal_places = amount_decimal_array ? amount_decimal_array.length : 0;
Comment on lines -88 to +89
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe that this additional condition || 0 can be removed because:

When selecting a suitable value, when amount_decimal_array is array (a true value), amount_decimal_array.length should be false; in this case, it should be 0.

By taking the value '123.' (a number with a decimal point but no digits after it), where the result of amount_decimal_array.length is 0, I realized that the first operand will not be returned because, in this case, it is not an array.
Code:
const amount_decimal_places = amount_decimal_array ? amount_decimal_array.length || 0 : 0;
Example:
const amount_decimal_array = '123.'?.toString().split('.')[1];
// amount_decimal_array = '', typeof amount_decimal_array = string, !!amount_decimal_array = false, amount_decimal_array.length = 0
Therefore:
const amount_decimal_places = '' ? 0 || 0 : 0;
const amount_decimal_places = false ? 0 || 0 : 0; // here returns the latest operand

=> That is, there is no case where the highlighted value in this expression is returned:
const amount_decimal_places = amount_decimal_array ? amount_decimal_array.length || [<0>] : 0;

if (amount_decimal_places > 2) {
errors[item] = max_decimal_message;
}
Expand All @@ -103,15 +103,15 @@ const SelfExclusionForm = props => {
};

return (
<div className='db-self-exclusion'>
<div className='db-self-exclusion' data-testid='self-exclusion'>
<div className='db-self-exclusion__content'>
<div className='db-self-exclusion__info'>
{localize('Enter limits to stop your bot from trading when any of these conditions are met.')}
</div>
<Formik initialValues={initial_values} validate={validateFields} onSubmit={onSubmitLimits}>
{({ values, touched, errors, isValid, handleChange }) => {
return (
<Form>
<Form role='form'>
<div className='db-self-exclusion__form-group'>
<Field name='form_max_losses'>
{({ field }) => (
Expand All @@ -126,6 +126,7 @@ const SelfExclusionForm = props => {
hint={localize(
'Limits your potential losses for the day across all Deriv platforms.'
)}
data_testId={field.name}
/>
)}
</Field>
Expand All @@ -145,6 +146,7 @@ const SelfExclusionForm = props => {
hint={localize(
'Maximum number of trades your bot will execute for this run.'
)}
data_testId={field.name}
/>
);
}}
Expand Down
Loading