Skip to content

Commit

Permalink
DTRA-1474 / Kate / Implement Payout per point trade param functionali…
Browse files Browse the repository at this point in the history
…ty (deriv-com#16783)

* feat: create payout per point initial component

* refactor: add initial values to localstorage

* refactor: add tests

* refactor: show unchanged value in the trade param
  • Loading branch information
kate-deriv authored Sep 12, 2024
1 parent 01ad785 commit e2270e8
Show file tree
Hide file tree
Showing 7 changed files with 362 additions and 16 deletions.
2 changes: 2 additions & 0 deletions packages/stores/src/mockStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -752,13 +752,15 @@ const mock = (): TStores & { is_mock: boolean } => {
previous_symbol: '',
proposal_info: {},
purchase_info: {},
payout_choices: [],
requestProposal: jest.fn(),
resetPreviousSymbol: jest.fn(),
sendTradeParamsAnalytics: jest.fn(),
setHoveredBarrier: jest.fn(),
setIsTradeParamsExpanded: jest.fn(),
setTradeTypeTab: jest.fn(),
setV2ParamsInitialValues: jest.fn(),
setPayoutPerPoint: jest.fn(),
stake_boundary: {},
start_date: 0,
stop_loss: 0,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import React from 'react';
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { mockStore } from '@deriv/stores';
import ModulesProvider from 'Stores/Providers/modules-providers';
import TraderProviders from '../../../../../trader-providers';
import PayoutPerPoint from '../payout-per-point';

const payout_per_point_label = 'Payout per point';

jest.mock('@deriv-com/quill-ui', () => ({
...jest.requireActual('@deriv-com/quill-ui'),
WheelPicker: jest.fn(({ data, setSelectedValue }) => (
<div>
<p>WheelPicker</p>
<ul>
{data.map(({ value }: { value: string }) => (
<li key={value}>
<button onClick={() => setSelectedValue(value)}>{value}</button>
</li>
))}
</ul>
</div>
)),
}));

jest.mock('lodash.debounce', () =>
jest.fn(fn => {
fn.cancel = () => null;
return fn;
})
);

describe('PayoutPerPoint', () => {
let default_mock_store: ReturnType<typeof mockStore>;

beforeEach(
() =>
(default_mock_store = mockStore({
modules: {
trade: {
...mockStore({}),
barrier_1: '+1.80',
payout_choices: ['6', '5', '4', '3', '2', '1'],
currency: 'USD',
payout_per_point: '3',
},
},
}))
);

afterEach(() => jest.clearAllMocks());

const mockPayoutPerPoint = () =>
render(
<TraderProviders store={default_mock_store}>
<ModulesProvider store={default_mock_store}>
<PayoutPerPoint is_minimized />
</ModulesProvider>
</TraderProviders>
);
it('should render Skeleton loader if payout_per_point is falsy', () => {
default_mock_store.modules.trade.payout_per_point = '';
mockPayoutPerPoint();

expect(screen.getByTestId('dt_skeleton')).toBeInTheDocument();
expect(screen.queryByText(payout_per_point_label)).not.toBeInTheDocument();
});

it('should render trade param with "Payout per point" label and input with value equal to current payout_per_point value', () => {
mockPayoutPerPoint();

expect(screen.getByText(payout_per_point_label)).toBeInTheDocument();
expect(screen.getByRole('textbox')).toHaveValue('3 USD');
});

it('should open ActionSheet with WheelPicker component, barrier information, "Save" button and text content with definition if user clicks on trade param', () => {
mockPayoutPerPoint();

expect(screen.queryByTestId('dt-actionsheet-overlay')).not.toBeInTheDocument();

userEvent.click(screen.getByText(payout_per_point_label));

expect(screen.getByTestId('dt-actionsheet-overlay')).toBeInTheDocument();
expect(screen.getByText('WheelPicker')).toBeInTheDocument();
expect(screen.getByText('Barrier')).toBeInTheDocument();
expect(screen.getByText('+1.80')).toBeInTheDocument();
expect(screen.getByText('Save')).toBeInTheDocument();
expect(
screen.getByText(
'The amount you choose to receive at expiry for every point of change between the final price and the barrier.'
)
).toBeInTheDocument();
});

it('should not render barrier information if barrier is not defined', () => {
default_mock_store.modules.trade.barrier_1 = undefined;
mockPayoutPerPoint();

userEvent.click(screen.getByText(payout_per_point_label));

expect(screen.getByText('Barrier')).toBeInTheDocument();
expect(screen.queryByText('+1.80')).not.toBeInTheDocument();
});

it('should apply specific className if innerHeight is <= 640px', () => {
const original_height = window.innerHeight;
window.innerHeight = 640;
mockPayoutPerPoint();

userEvent.click(screen.getByText(payout_per_point_label));

expect(screen.getByTestId('dt_carousel')).toHaveClass('payout-per-point__carousel--small');
window.innerHeight = original_height;
});

it('should call setPayoutPerPoint function if user changes selected value', async () => {
jest.useFakeTimers();
mockPayoutPerPoint();

const new_selected_value = default_mock_store.modules.trade.payout_choices[1];
userEvent.click(screen.getByText(payout_per_point_label));
userEvent.click(screen.getByText(new_selected_value));
userEvent.click(screen.getByText('Save'));

await waitFor(() => jest.advanceTimersByTime(200));

expect(default_mock_store.modules.trade.setPayoutPerPoint).toBeCalled();
jest.useRealTimers();
});
});
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import './payout-per-point.scss';
import PayoutPerPoint from './payout-per-point';

export default PayoutPerPoint;
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import React from 'react';
import debounce from 'lodash.debounce';
import { ActionSheet, Text, WheelPicker } from '@deriv-com/quill-ui';
import { Skeleton } from '@deriv/components';
import { Localize } from '@deriv/translations';
import type { TV2ParamsInitialValues } from 'Stores/Modules/Trading/trade-store';

type TPayoutPerPointWheelProps = {
barrier?: string | number;
current_payout_per_point: string;
onPayoutPerPointSelect: (new_value: string | number) => void;
payout_per_point_list: {
value: string;
}[];
setV2ParamsInitialValues: ({ value, name }: { value: number | string; name: keyof TV2ParamsInitialValues }) => void;
};

const onWheelPickerScrollDebounced = debounce(
(new_value: string | number, callback: TPayoutPerPointWheelProps['onPayoutPerPointSelect']) => callback(new_value),
200
);

const PayoutPerPointWheel = ({
barrier,
current_payout_per_point,
onPayoutPerPointSelect,
setV2ParamsInitialValues,
payout_per_point_list,
}: TPayoutPerPointWheelProps) => {
const initial_value_ref = React.useRef<string | number>();
const selected_value_ref = React.useRef<string | number>(current_payout_per_point);

const onSave = () => {
initial_value_ref.current = selected_value_ref.current;
setV2ParamsInitialValues({ value: selected_value_ref.current, name: 'payout_per_point' });
};

React.useEffect(() => {
if (!initial_value_ref.current && current_payout_per_point) {
initial_value_ref.current = current_payout_per_point;
setV2ParamsInitialValues({ value: current_payout_per_point, name: 'payout_per_point' });
}

return () => {
if (initial_value_ref.current && initial_value_ref.current !== selected_value_ref.current) {
onPayoutPerPointSelect(initial_value_ref.current);
}
onWheelPickerScrollDebounced.cancel();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

return (
<React.Fragment>
<ActionSheet.Content className='payout-per-point__wrapper' data-testid='dt_payout-per-point_wrapper'>
<div className='payout-per-point__wheel-picker'>
<WheelPicker
data={payout_per_point_list}
selectedValue={selected_value_ref.current}
setSelectedValue={(new_value: string | number) => {
if (new_value === selected_value_ref.current) return;
selected_value_ref.current = new_value;
onWheelPickerScrollDebounced(new_value, onPayoutPerPointSelect);
}}
/>
</div>
<div className='payout-per-point__barrier'>
<Text color='quill-typography__color--subtle' size='sm'>
<Localize i18n_default_text='Barrier' />
</Text>
<Text size='sm' as='div' className='payout-per-point__barrier__content'>
{barrier ?? <Skeleton width={90} height={14} />}
</Text>
</div>
</ActionSheet.Content>
<ActionSheet.Footer
alignment='vertical'
primaryAction={{
content: <Localize i18n_default_text='Save' />,
onAction: onSave,
}}
/>
</React.Fragment>
);
};

export default PayoutPerPointWheel;
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
$HANDLEBAR_HEIGHT: var(--core-size-1000);
$HEADER_TITLE_HEIGHT: var(--core-size-3200);
$FOOTER_BUTTON_HEIGHT: var(--core-size-4000);

.payout-per-point {
&__carousel {
& > .carousel__item {
height: 100%;
}
.trade-param-definition {
height: calc(480px - $HANDLEBAR_HEIGHT - $HEADER_TITLE_HEIGHT);
}
&--small {
.payout-per-point__wrapper {
height: calc(90dvh - $HANDLEBAR_HEIGHT - $HEADER_TITLE_HEIGHT - $FOOTER_BUTTON_HEIGHT);
max-height: calc(480px - $HANDLEBAR_HEIGHT - $HEADER_TITLE_HEIGHT - $FOOTER_BUTTON_HEIGHT);
}
.trade-param-definition {
height: calc(90dvh - $HANDLEBAR_HEIGHT - $HEADER_TITLE_HEIGHT);
max-height: calc(480px - $HANDLEBAR_HEIGHT - $HEADER_TITLE_HEIGHT);
}
}
}

&__wrapper {
height: calc(480px - $HANDLEBAR_HEIGHT - $HEADER_TITLE_HEIGHT - $FOOTER_BUTTON_HEIGHT);
padding-block: 0;
display: flex;
flex-direction: column;
gap: var(--core-spacing-400);
}

&__wheel-picker {
flex-grow: 1;
min-height: 0;
}

&__barrier {
min-height: var(--core-size-2200);
display: flex;
flex-direction: column;
align-items: center;

&__content {
display: flex;
justify-content: center;
align-items: center;
flex-grow: 1;
}
}
}
Loading

0 comments on commit e2270e8

Please sign in to comment.