Skip to content

Commit 6564b3a

Browse files
authored
GRWT-3133 / Kate / [DTrader-V2] Tech Debt: Refactor Stake component (#17620)
* refactor: create new stake component * feat: add function for forming proposal request body * feat: add be and fe validation * refactor: decomposition * chore: uncommited trade param * chore: structure refactoring * feat: handle turbos and vanillas cases * refactor: rely on is_fetching flag * feat: add error handling in snackbar * revert: extra non-aligned file from master * feat: add logic for multipliers * refactor: stake delais structure * feat: add another proposal for rise fall * fix: types * revert: extra non-aligned file from master * feat: add logic for exctraction payout and max payout * feat: add rise fall * refactor: improve snackbar behaviour * chore: exctract function to utils * refactor: add tests for stake * refactor: add tests for payout function * chore: replace array method * chore: spelling * fix: reset commission and stop out * refactor: usereducer, created hook for fetching data and exctract logic for handling response * refactor: extract logic for state initiation into a function * refactor: handle more cases with fe error * feat: add max length restriction * refactor: rewrite with the usage of reducer * chore: add string wrapper
1 parent 2a4fac4 commit 6564b3a

File tree

14 files changed

+876
-451
lines changed

14 files changed

+876
-451
lines changed

packages/trader/src/AppV2/Components/TradeParameters/Stake/__tests__/stake.spec.tsx

Lines changed: 43 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
import React from 'react';
2-
import { render, screen } from '@testing-library/react';
3-
import userEvent from '@testing-library/user-event';
2+
43
import { CONTRACT_TYPES, TRADE_TYPES } from '@deriv/shared';
54
import { mockStore } from '@deriv/stores';
5+
import { render, screen } from '@testing-library/react';
6+
import userEvent from '@testing-library/user-event';
7+
8+
import { useDtraderQuery } from 'AppV2/Hooks/useDtraderQuery';
69
import ModulesProvider from 'Stores/Providers/modules-providers';
10+
711
import TraderProviders from '../../../../../trader-providers';
812
import Stake from '../stake';
913

@@ -29,6 +33,24 @@ jest.mock('AppV2/Hooks/useContractsForCompany', () => ({
2933
},
3034
})),
3135
}));
36+
jest.mock('@deriv/shared', () => ({
37+
...jest.requireActual('@deriv/shared'),
38+
WS: {
39+
send: jest.fn(),
40+
authorized: {
41+
send: jest.fn(),
42+
},
43+
},
44+
}));
45+
jest.mock('AppV2/Hooks/useDtraderQuery', () => ({
46+
...jest.requireActual('AppV2/Hooks/useDtraderQuery'),
47+
useDtraderQuery: jest.fn(() => ({
48+
data: {
49+
proposal: {},
50+
error: {},
51+
},
52+
})),
53+
}));
3254

3355
describe('Stake', () => {
3456
let default_mock_store: ReturnType<typeof mockStore>;
@@ -45,14 +67,12 @@ describe('Stake', () => {
4567
currency: 'USD',
4668
proposal_info: {
4769
[CONTRACT_TYPES.CALL]: {
48-
id: '53e8cb91-8c13-60a3-289f-778e8386367c',
4970
has_error: false,
5071
message:
5172
'Win payout if Volatility 100 (1s) Index is strictly higher than entry spot at 5 minutes after contract start time.',
5273
payout: 19.55,
5374
},
5475
[CONTRACT_TYPES.PUT]: {
55-
id: '2b5dd806-7505-8af7-1bbb-5e24ac48bbbc',
5676
has_error: false,
5777
message:
5878
'Win payout if Volatility 100 (1s) Index is strictly lower than entry spot at 5 minutes after contract start time.',
@@ -63,14 +83,11 @@ describe('Stake', () => {
6383
[CONTRACT_TYPES.CALL]: 'Higher',
6484
[CONTRACT_TYPES.PUT]: 'Lower',
6585
},
66-
validation_errors: { amount: [] },
86+
trade_type_tab: 'CALL',
6787
validation_params: {
6888
[CONTRACT_TYPES.CALL]: { payout: { max: '50000.00' } },
6989
[CONTRACT_TYPES.PUT]: { payout: { max: '50000.00' } },
7090
},
71-
v2_params_initial_values: {
72-
stake: 10,
73-
},
7491
},
7592
},
7693
}))
@@ -84,15 +101,6 @@ describe('Stake', () => {
84101
</TraderProviders>
85102
);
86103

87-
it('switches basis to stake if it is different', () => {
88-
default_mock_store.modules.trade.basis = 'payout';
89-
render(<MockedStake />);
90-
91-
expect(default_mock_store.modules.trade.onChange).toHaveBeenCalledWith({
92-
target: { name: 'basis', value: 'stake' },
93-
});
94-
});
95-
96104
it('renders trade param with "Stake" label and input with a value equal to the current stake amount value', () => {
97105
render(<MockedStake />);
98106
const { amount, currency } = default_mock_store.modules.trade;
@@ -109,16 +117,19 @@ describe('Stake', () => {
109117

110118
expect(screen.getByTestId('dt-actionsheet-overlay')).toBeInTheDocument();
111119
expect(screen.getByPlaceholderText(input_placeholder)).toBeInTheDocument();
112-
expect(screen.getAllByText(/payout/i)).toHaveLength(3);
120+
expect(screen.getAllByText(/payout/i)).toHaveLength(2);
113121
expect(screen.getByRole('button', { name: save_button_label })).toBeInTheDocument();
114122
});
115123

116-
it('calls onChange when stake input changes', async () => {
124+
it('calls onChange if user clicks on Save', async () => {
117125
render(<MockedStake />);
126+
118127
await userEvent.click(screen.getByText(stake_param_label));
119-
await userEvent.type(screen.getByPlaceholderText(input_placeholder), '0');
128+
await userEvent.type(screen.getByPlaceholderText(input_placeholder), '10');
129+
await userEvent.click(screen.getByRole('button', { name: save_button_label }));
130+
120131
expect(default_mock_store.modules.trade.onChange).toHaveBeenCalledWith({
121-
target: { name: 'amount', value: '100' },
132+
target: { name: 'amount', value: 10 },
122133
});
123134
});
124135

@@ -157,14 +168,12 @@ describe('Stake', () => {
157168
is_multiplier: true,
158169
proposal_info: {
159170
[CONTRACT_TYPES.MULTIPLIER.UP]: {
160-
id: '3b09df15-b0b7-70a8-15c9-fad8e2bda5ef',
161171
has_error: false,
162172
message:
163173
"If you select 'Up', your total profit/loss will be the percentage increase in Volatility 100 (1s) Index, multiplied by 1000, minus commissions.",
164174
payout: 0,
165175
},
166176
[CONTRACT_TYPES.MULTIPLIER.DOWN]: {
167-
id: '873af3a9-a0da-5486-d1f9-fce8206e6ba2',
168177
has_error: false,
169178
message:
170179
"If you select 'Down', your total profit/loss will be the percentage decrease in Volatility 100 (1s) Index, multiplied by 1000, minus commissions.",
@@ -202,84 +211,25 @@ describe('Stake', () => {
202211
expect(screen.getByText('Commission')).toBeInTheDocument();
203212
});
204213

205-
it('calls setV2ParamsInitialValues if v2_params_initial_values.stake !== amount on mount and on Save button click if no error', async () => {
206-
default_mock_store.modules.trade.amount = '30';
207-
render(<MockedStake />);
208-
await userEvent.click(screen.getByText(stake_param_label));
209-
await userEvent.type(screen.getByPlaceholderText(input_placeholder), '0');
210-
211-
expect(default_mock_store.modules.trade.setV2ParamsInitialValues).toHaveBeenCalledTimes(2);
212-
213-
await userEvent.click(screen.getByRole('button', { name: save_button_label }));
214-
expect(default_mock_store.modules.trade.setV2ParamsInitialValues).toHaveBeenCalledTimes(3);
215-
});
216-
217-
it('calls onChange on component mount if v2_params_initial_values.stake is not equal to amount', () => {
218-
default_mock_store.modules.trade.amount = '30';
219-
render(<MockedStake />);
220-
expect(default_mock_store.modules.trade.onChange).toHaveBeenCalledWith({
221-
target: { name: 'amount', value: 10 },
222-
});
223-
});
224-
225214
it('shows error in case of a validation error if input is non-empty', async () => {
226215
const error_text = "Please enter a stake amount that's at least 0.35.";
216+
default_mock_store.modules.trade.contract_type = TRADE_TYPES.HIGH_LOW;
217+
default_mock_store.modules.trade.trade_type_tab = 'CALL';
227218
default_mock_store.modules.trade.proposal_info = {
228-
PUT: { id: '', has_error: true, message: error_text },
229-
CALL: { id: '', has_error: true, message: error_text },
219+
CALL: { has_error: true, message: error_text, error_field: 'amount' },
230220
};
231-
default_mock_store.modules.trade.validation_errors.amount = [error_text];
232-
default_mock_store.modules.trade.amount = 0;
233-
234-
render(<MockedStake />);
235-
await userEvent.click(screen.getByText(stake_param_label));
236-
expect(screen.getByText(error_text)).toBeInTheDocument();
237-
expect(screen.getAllByText('- USD')).toHaveLength(2);
238-
});
239-
240-
it('shows max payout error with the least current payout when both of the 2 contract types exceed max payout', async () => {
241-
const error_text_rise = 'Minimum stake of 0.35 and maximum payout of 50000.00. Current payout is 50631.97.';
242-
const error_text_fall = 'Minimum stake of 0.35 and maximum payout of 50000.00. Current payout is 50513.21.';
243-
default_mock_store.modules.trade.proposal_info = {
244-
CALL: { id: '', has_error: true, message: error_text_rise, error_field: 'amount' },
245-
PUT: { id: '', has_error: true, message: error_text_fall, error_field: 'amount' },
246-
};
247-
default_mock_store.modules.trade.validation_errors.amount = [error_text_fall];
248-
default_mock_store.modules.trade.amount = '26500';
249-
250-
render(<MockedStake />);
251-
await userEvent.click(screen.getByText(stake_param_label));
252-
253-
expect(screen.getByText(error_text_fall)).toBeInTheDocument();
254-
expect(screen.queryByText('- USD')).not.toBeInTheDocument();
255-
});
256221

257-
it('does not show max payout error if one of the 2 contract types satisfies max payout', async () => {
258-
const error_text_rise = 'Minimum stake of 0.35 and maximum payout of 50000.00. Current payout is 50058.77.';
259-
const success_text_fall =
260-
'Win payout if Volatility 100 (1s) Index is strictly lower than entry spot at 5 minutes after contract start time.';
261-
default_mock_store.modules.trade.proposal_info = {
262-
CALL: { id: '', has_error: true, message: error_text_rise },
263-
PUT: {
264-
id: 'b608baf2-ba5d-00e0-8035-9af5c0769664',
265-
has_error: false,
266-
message: success_text_fall,
267-
payout: 49942.7,
222+
(useDtraderQuery as jest.Mock).mockReturnValue({
223+
data: {
224+
error: { has_error: true, message: error_text, details: { error_field: 'amount' } },
225+
proposal: {},
268226
},
269-
};
270-
default_mock_store.modules.trade.amount = '26200';
271-
272-
render(<MockedStake />);
273-
await userEvent.click(screen.getByText(stake_param_label));
274-
275-
expect(screen.queryByText(error_text_rise)).not.toBeInTheDocument();
276-
});
277-
278-
it('sets default stake if available_contract_types object contains it', () => {
279-
default_mock_store.modules.trade.contract_type = TRADE_TYPES.VANILLA.CALL;
227+
});
280228
render(<MockedStake />);
281229

282-
expect(default_mock_store.modules.trade.setDefaultStake).toHaveBeenCalledWith(10);
230+
await userEvent.click(screen.getByText(stake_param_label));
231+
expect(screen.getByText(error_text)).toBeInTheDocument();
232+
expect(screen.getByText('- USD')).toBeInTheDocument();
283233
});
284234

285235
it('disables trade param if is_market_closed == true', () => {
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Stake from './stake';
2+
23
import './stake.scss';
34

45
export default Stake;

packages/trader/src/AppV2/Components/TradeParameters/Stake/stake-details.tsx

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,95 +1,95 @@
11
import React from 'react';
22
import clsx from 'clsx';
3-
import { Text } from '@deriv-com/quill-ui';
3+
44
import { formatMoney, getCurrencyDisplayCode, getTradeTypeName, TRADE_TYPES } from '@deriv/shared';
55
import { Localize } from '@deriv/translations';
6+
import { Text } from '@deriv-com/quill-ui';
7+
68
import { TTradeStore } from 'Types';
79

8-
type TStakeDetailsProps = Pick<
9-
TTradeStore,
10-
'commission' | 'contract_type' | 'currency' | 'has_stop_loss' | 'is_multiplier' | 'stop_out'
11-
> & {
10+
type TStakeDetailsProps = Pick<TTradeStore, 'contract_type' | 'currency' | 'has_stop_loss' | 'is_multiplier'> & {
1211
contract_types: string[];
1312
details: {
13+
commission?: string | number;
14+
error_1?: string;
15+
error_2?: string;
1416
first_contract_payout: number;
17+
is_first_payout_exceeded?: boolean;
18+
is_second_payout_exceeded?: boolean;
1519
max_payout: string | number;
1620
max_stake: string | number;
1721
min_stake: string | number;
1822
second_contract_payout: number;
23+
stop_out?: number | string;
1924
};
2025
is_loading_proposal: boolean;
21-
is_max_payout_exceeded: boolean;
26+
is_empty?: boolean;
2227
should_show_payout_details: boolean;
23-
stake_error: string;
2428
};
2529

2630
const StakeDetails = ({
27-
commission,
2831
contract_type,
2932
contract_types,
3033
currency,
3134
details,
3235
has_stop_loss,
3336
is_loading_proposal,
3437
is_multiplier,
35-
is_max_payout_exceeded,
38+
is_empty,
3639
should_show_payout_details,
37-
stake_error,
38-
stop_out,
3940
}: TStakeDetailsProps) => {
4041
const [displayed_values, setDisplayedValues] = React.useState({
41-
max_payout: '',
42+
is_first_payout_exceeded: false,
43+
is_second_payout_exceeded: false,
4244
commission: '',
4345
first_contract_payout: '',
46+
max_payout: '',
4447
second_contract_payout: '',
4548
stop_out: '',
4649
});
4750

4851
React.useEffect(() => {
4952
const getDisplayedValue = (new_value?: number | string, current_value?: string) => {
50-
return ((current_value === '-' && is_loading_proposal) || stake_error) && !is_max_payout_exceeded
53+
return (current_value === '-' && is_loading_proposal) || !new_value || is_empty
5154
? '-'
5255
: formatMoney(currency, Number(new_value), true);
5356
};
5457

5558
const {
5659
commission: commission_value,
5760
first_contract_payout,
61+
is_first_payout_exceeded,
62+
is_second_payout_exceeded,
5863
second_contract_payout,
5964
stop_out: stop_out_value,
6065
max_payout,
6166
} = displayed_values;
62-
const new_commission = getDisplayedValue(Math.abs(Number(commission)), commission_value);
67+
const new_commission = getDisplayedValue(Math.abs(Number(details.commission)), commission_value);
6368
const new_payout_1 = getDisplayedValue(details.first_contract_payout, first_contract_payout);
6469
const new_payout_2 = getDisplayedValue(details.second_contract_payout, second_contract_payout);
65-
const new_stop_out = getDisplayedValue(Math.abs(Number(stop_out)), stop_out_value);
70+
const new_stop_out = getDisplayedValue(Math.abs(Number(details.stop_out)), stop_out_value);
6671
const new_max_payout = getDisplayedValue(details.max_payout, max_payout);
6772

6873
if (
6974
commission_value !== new_commission ||
7075
first_contract_payout !== new_payout_1 ||
76+
displayed_values.is_first_payout_exceeded !== is_first_payout_exceeded ||
77+
displayed_values.is_second_payout_exceeded !== is_second_payout_exceeded ||
7178
second_contract_payout !== new_payout_2 ||
7279
stop_out_value !== new_stop_out ||
7380
max_payout !== new_max_payout
7481
) {
7582
setDisplayedValues({
7683
commission: new_commission,
7784
first_contract_payout: new_payout_1,
85+
is_first_payout_exceeded,
86+
is_second_payout_exceeded,
7887
second_contract_payout: new_payout_2,
7988
stop_out: new_stop_out,
8089
max_payout: new_max_payout,
8190
});
8291
}
83-
}, [
84-
commission,
85-
currency,
86-
details,
87-
displayed_values,
88-
is_loading_proposal,
89-
is_max_payout_exceeded,
90-
stake_error,
91-
stop_out,
92-
]);
92+
}, [currency, details, displayed_values, is_loading_proposal, is_empty]);
9393

9494
const payout_title = <Localize i18n_default_text='Payout' />;
9595
const content = [
@@ -114,7 +114,7 @@ const StakeDetails = ({
114114
}),
115115
is_displayed: !!contract_types.length && should_show_payout_details,
116116
label: payout_title,
117-
has_error: details.first_contract_payout > +details.max_payout && is_max_payout_exceeded,
117+
has_error: details.is_first_payout_exceeded,
118118
value: displayed_values.first_contract_payout,
119119
},
120120
{
@@ -123,7 +123,7 @@ const StakeDetails = ({
123123
}),
124124
is_displayed: contract_types.length > 1 && should_show_payout_details,
125125
label: payout_title,
126-
has_error: details.second_contract_payout > +details.max_payout && is_max_payout_exceeded,
126+
has_error: details.is_second_payout_exceeded,
127127
value: displayed_values.second_contract_payout,
128128
},
129129
];

0 commit comments

Comments
 (0)