diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/__tests__/advanced-duration.spec.tsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/__tests__/advanced-duration.spec.tsx new file mode 100644 index 000000000000..e50ec9112c0c --- /dev/null +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/__tests__/advanced-duration.spec.tsx @@ -0,0 +1,187 @@ +import React from 'react'; +import { screen, render } from '@testing-library/react'; +import TraderProviders from '../../../../../../../trader-providers'; +import AdvancedDuration from '../advanced-duration'; +import { mockStore } from '@deriv/stores'; + +const button_toggle = 'MockedButtonToggle'; +const dropdown = 'MockedDropDown'; +const input_field = 'MockedInputField'; +const range_slider = 'MockedRangeSlider'; +const date_picker = 'MockedDatePicker'; +const time_picker = 'MockedTimePicker'; +const expiry_text = 'MockedExpiryText'; +const duration_range_text = 'MockedDurationRangeText'; + +jest.mock('@deriv/components', () => ({ + ...jest.requireActual('@deriv/components'), + ButtonToggle: jest.fn(() =>
{button_toggle}
), + Dropdown: jest.fn(() =>
{dropdown}
), + InputField: jest.fn(() =>
{input_field}
), +})); +jest.mock('App/Components/Form/RangeSlider', () => jest.fn(() =>
{range_slider}
)); +jest.mock('../../../DatePicker', () => jest.fn(() =>
{date_picker}
)); +jest.mock('../../../TimePicker', () => jest.fn(() =>
{time_picker}
)); +jest.mock('../expiry-text', () => jest.fn(() =>
{expiry_text}
)); +jest.mock('../duration-range-text', () => jest.fn(() =>
{duration_range_text}
)); + +describe('', () => { + let mock_store: ReturnType, default_props: React.ComponentProps; + beforeEach(() => { + mock_store = { + ...mockStore({ + ui: { + current_focus: '', + setCurrentFocus: jest.fn(), + }, + modules: { + trade: { + contract_expiry_type: 'intraday', + duration_min_max: { + daily: { + min: 1234, + max: 2345, + }, + intraday: { + min: 12345, + max: 23456, + }, + }, + validation_errors: {}, + }, + }, + }), + }; + default_props = { + advanced_duration_unit: 't', + advanced_expiry_type: 'duration', + changeDurationUnit: jest.fn(), + duration_t: 10, + duration_units_list: [ + { + text: 'Ticks', + value: 't', + }, + { + text: 'Seconds', + value: 's', + }, + { + text: 'Minutes', + value: 'm', + }, + { + text: 'Hours', + value: 'h', + }, + { + text: 'Days', + value: 'd', + }, + ], + expiry_date: '', + expiry_epoch: 1703057788, + expiry_list: [ + { + text: 'Duration', + value: 'duration', + }, + { + text: 'End time', + value: 'endtime', + }, + ], + expiry_type: 'duration', + getDurationFromUnit: jest.fn(), + number_input_props: { + type: 'number', + is_incrementable: false, + }, + onChange: jest.fn(), + onChangeUiStore: jest.fn(), + server_time: 0, + shared_input_props: { + is_hj_whitelisted: true, + max_value: 86400, + min_value: 15, + onChange: jest.fn(), + }, + start_date: 0, + }; + }); + const renderAdvancedDuration = ( + mock_store: ReturnType, + default_props: React.ComponentProps + ) => { + return render( + + + + ); + }; + it('Should render mocked button toggle if expiry_list is of length > 1', () => { + renderAdvancedDuration(mock_store, default_props); + expect(screen.getByText(button_toggle)).toBeInTheDocument(); + }); + it('Should not render mocked button toggle if expiry_list is of length <= 1', () => { + default_props.expiry_list = [ + { + text: 'Duration', + value: 'duration', + }, + ]; + renderAdvancedDuration(mock_store, default_props); + expect(screen.queryByText(button_toggle)).not.toBeInTheDocument(); + }); + it('Should render mocked trading date and trading time picker if contract is 24 hours and expiry type is endtime', () => { + default_props.expiry_type = 'endtime'; + renderAdvancedDuration(mock_store, default_props); + expect(screen.getByText(date_picker)).toBeInTheDocument(); + expect(screen.getByText(time_picker)).toBeInTheDocument(); + }); + it('Should render mocked expiry text and should not render trading time picker if contract is not 24 hours and expiry type is endtime', () => { + default_props.expiry_type = 'endtime'; + default_props.duration_units_list = [ + { + text: 'Ticks', + value: 't', + }, + { + text: 'Seconds', + value: 's', + }, + { + text: 'Days', + value: 'd', + }, + ]; + renderAdvancedDuration(mock_store, default_props); + expect(screen.getByText(expiry_text)).toBeInTheDocument(); + expect(screen.queryByText(time_picker)).not.toBeInTheDocument(); + }); + it('Should render mocked dropdown if duration_units_list length is > 1', () => { + renderAdvancedDuration(mock_store, default_props); + expect(screen.getByText(dropdown)).toBeInTheDocument(); + }); + it('Should not render mocked dropdown if duration_units_list length is > 0', () => { + default_props.duration_units_list = []; + renderAdvancedDuration(mock_store, default_props); + expect(screen.queryByText(dropdown)).not.toBeInTheDocument(); + }); + it('Should render mocked trading date picker and mocked expiry text if advanced_duration_unit === d & !==t', () => { + default_props.advanced_duration_unit = 'd'; + renderAdvancedDuration(mock_store, default_props); + expect(screen.getByText(duration_range_text)).toBeInTheDocument(); + expect(screen.getByText(expiry_text)).toBeInTheDocument(); + }); + it('Should render mocked trading date picker and mocked expiry text if advanced_duration_unit === t && contract_expiry_type === tick', () => { + mock_store.modules.trade.contract_expiry_type = 'tick'; + renderAdvancedDuration(mock_store, default_props); + expect(screen.getByText(range_slider)).toBeInTheDocument(); + }); + it('Should render mocked mocked input field if advanced_duration_unit is intraday like h or m', () => { + default_props.advanced_duration_unit = 'm'; + renderAdvancedDuration(mock_store, default_props); + expect(screen.getByText(input_field)).toBeInTheDocument(); + }); +}); diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/__tests__/duration-mobile.spec.tsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/__tests__/duration-mobile.spec.tsx new file mode 100644 index 000000000000..58dc082c6f13 --- /dev/null +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/__tests__/duration-mobile.spec.tsx @@ -0,0 +1,106 @@ +import React from 'react'; +import { screen, render } from '@testing-library/react'; +import TraderProviders from '../../../../../../../trader-providers'; +import DurationMobile from '../duration-mobile'; +import { mockStore } from '@deriv/stores'; + +jest.mock('@deriv/components', () => { + return { + ...jest.requireActual('@deriv/components'), + Tabs: jest.fn(({ onTabItemClick, children }) => ( +
+ {children} + +
+ )), + }; +}); + +jest.mock('../duration-ticks-widget-mobile.tsx', () => jest.fn(() => 'MockedDurationTicksWidgetMobile')); +jest.mock('../duration-numbers-widget-mobile.tsx', () => jest.fn(() => 'MockedDurationNumbersWidgetMobile')); + +describe('', () => { + let mock_store: ReturnType, default_props: React.ComponentProps; + beforeEach(() => { + mock_store = { + ...mockStore({ + modules: { + trade: { + duration_units_list: [ + { + text: 'Ticks', + value: 't', + }, + { + text: 'Seconds', + value: 's', + }, + { + text: 'Minutes', + value: 'm', + }, + { + text: 'Hours', + value: 'h', + }, + { + text: 'Days', + value: 'd', + }, + { + text: 'Weeks', + value: 'w', + }, + ], + duration_unit: 't', + basis: 'stake', + duration_min_max: { + daily: { + min: 1234, + max: 2345, + }, + intraday: { + min: 12345, + max: 23456, + }, + }, + validation_errors: {}, + }, + }, + }), + }; + default_props = { + amount_tab_idx: 0, + d_duration: 1, + duration_tab_idx: 1, + expiry_epoch: 1703057788, + h_duration: 1, + has_amount_error: false, + m_duration: 1, + payout_value: 123, + s_duration: 1, + setDurationError: jest.fn(), + setDurationTabIdx: jest.fn(), + setSelectedDuration: jest.fn(), + stake_value: 12, + t_duration: 1, + toggleModal: jest.fn(), + }; + }); + const renderDurationMobile = ( + mock_store: ReturnType, + default_props: React.ComponentProps + ) => { + return render( + + + + ); + }; + it('Should render 1 Ticks Widget, 4 Numbers Widget and mocked date picker', () => { + renderDurationMobile(mock_store, default_props); + expect(screen.getByText(/mockeddurationtickswidgetmobile/i)).toBeInTheDocument(); + expect(screen.getAllByText(/mockeddurationnumberswidgetmobile/i)).toHaveLength(4); + expect(screen.getByText(/pick an end date/i)).toBeInTheDocument(); + }); +}); diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/__tests__/duration-numbers-widget-mobile.spec.tsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/__tests__/duration-numbers-widget-mobile.spec.tsx new file mode 100644 index 000000000000..5e429a14eb5e --- /dev/null +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/__tests__/duration-numbers-widget-mobile.spec.tsx @@ -0,0 +1,118 @@ +import React from 'react'; +import { screen, render } from '@testing-library/react'; +import TraderProviders from '../../../../../../../trader-providers'; +import { mockStore } from '@deriv/stores'; +import DurationNumbersWidgetMobile from '../duration-numbers-widget-mobile'; +import * as durationUtils from '../duration-utils'; +import userEvent from '@testing-library/user-event'; + +jest.mock('../duration-range-text.tsx', () => jest.fn(() =>
MockedDurationRangeText
)); +jest.mock('../expiry-text.tsx', () => jest.fn(() =>
MockedExpiryText
)); +jest.spyOn(durationUtils, 'updateAmountChanges'); +describe('', () => { + let mock_store: ReturnType, + default_props: React.ComponentProps; + beforeEach(() => { + mock_store = { + ...mockStore({ + ui: { + addToast: jest.fn(), + }, + modules: { + trade: { + amount: 123, + basis: 'stake', + duration: 5, + duration_unit: 'd', + duration_min_max: { + daily: { + min: 1234, + max: 2345, + }, + intraday: { + min: 12345, + max: 23456, + }, + }, + onChangeMultiple: jest.fn(), + }, + }, + }), + }; + default_props = { + contract_expiry: 'daily', + duration_unit_option: { + text: 'Minutes', + value: 'm', + }, + duration_values: { + t_duration: 5, + s_duration: 15, + m_duration: 35, + h_duration: 1, + d_duration: 1, + }, + expiry_epoch: 1703242581, + show_expiry: false, + setDurationError: jest.fn(), + basis_option: 'payout', + toggleModal: jest.fn(), + has_amount_error: false, + payout_value: 10, + stake_value: 10, + selected_duration: 5, + setSelectedDuration: jest.fn(), + }; + }); + const renderDurationNumbersWidgetMobile = ( + mock_store: ReturnType, + default_props: React.ComponentProps + ) => { + return render( + + + + ); + }; + it('should render Mocked duration range text', () => { + renderDurationNumbersWidgetMobile(mock_store, default_props); + expect(screen.getByText(/mockeddurationrangetext/i)).toBeInTheDocument(); + }); + it('should render mocked expiry text if show_expiry is true', () => { + default_props.show_expiry = true; + renderDurationNumbersWidgetMobile(mock_store, default_props); + expect(screen.getByText(/mockedexpirytext/i)).toBeInTheDocument(); + }); + it('Should show validation messages if selected_duration is less than minimum', () => { + default_props.selected_duration = 11; + renderDurationNumbersWidgetMobile(mock_store, default_props); + expect(mock_store.ui.addToast).toHaveBeenCalled(); + expect(default_props.setDurationError).toHaveBeenCalledWith(true); + }); + it('Should show validation messages if selected_duration is empty', () => { + default_props.selected_duration = '' as unknown as number; + renderDurationNumbersWidgetMobile(mock_store, default_props); + expect(mock_store.ui.addToast).toHaveBeenCalled(); + expect(default_props.setDurationError).toHaveBeenCalledWith(true); + }); + it('Should call setDurationError with false value and should not call addToast when there is not validation error', () => { + default_props.selected_duration = 21; + renderDurationNumbersWidgetMobile(mock_store, default_props); + expect(mock_store.ui.addToast).not.toHaveBeenCalled(); + expect(default_props.setDurationError).toHaveBeenCalledWith(false); + }); + it('should call updateAmountChanges, onChangeMultiple, and toggleModal if there is has_amount_error is false, duration is changed and user clicks ok', () => { + default_props.selected_duration = 21; + renderDurationNumbersWidgetMobile(mock_store, default_props); + userEvent.click(screen.getByText(/ok/i)); + expect(durationUtils.updateAmountChanges).toHaveBeenCalled(); + expect(default_props.toggleModal).toHaveBeenCalled(); + expect(mock_store.modules.trade.onChangeMultiple).toHaveBeenCalledWith({ + amount: 10, + basis: 'payout', + duration: 21, + duration_unit: 'm', + expiry_type: 'duration', + }); + }); +}); diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/__tests__/duration-ticks-widget-mobile.spec.tsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/__tests__/duration-ticks-widget-mobile.spec.tsx new file mode 100644 index 000000000000..69198adad93f --- /dev/null +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/__tests__/duration-ticks-widget-mobile.spec.tsx @@ -0,0 +1,77 @@ +import React from 'react'; +import { screen, render } from '@testing-library/react'; +import TraderProviders from '../../../../../../../trader-providers'; +import DurationTicksWidgetMobile from '../duration-ticks-widget-mobile'; +import { mockStore } from '@deriv/stores'; +import userEvent from '@testing-library/user-event'; + +describe('', () => { + let mock_store: ReturnType, default_props: React.ComponentProps; + beforeEach(() => { + mock_store = { + ...mockStore({ + modules: { + trade: { + amount: 123, + basis: 'stake', + duration: 5, + duration_unit: 't', + duration_min_max: { + daily: { + min: 1234, + max: 2345, + }, + intraday: { + min: 12345, + max: 23456, + }, + tick: { + min: 1, + max: 10, + }, + }, + onChangeMultiple: jest.fn(), + }, + }, + }), + }; + default_props = { + setDurationError: jest.fn(), + basis_option: 'payout', + toggleModal: jest.fn(), + has_amount_error: false, + payout_value: 10, + stake_value: 10, + selected_duration: 5, + setSelectedDuration: jest.fn(), + }; + }); + const renderDurationTicksWidgetMobile = ( + mock_store: ReturnType, + default_props: React.ComponentProps + ) => { + return render( + + + + ); + }; + it('Should call setDurationError on mount', () => { + renderDurationTicksWidgetMobile(mock_store, default_props); + expect(default_props.setDurationError).toHaveBeenCalled(); + }); + it('Should call onChangeMultiple and toggleModal when setTicksDuration is invoked', () => { + renderDurationTicksWidgetMobile(mock_store, default_props); + const ok_button = screen.getByText('OK'); + userEvent.click(ok_button); + + expect(mock_store.modules.trade.onChangeMultiple).toHaveBeenCalled(); + expect(default_props.toggleModal).toHaveBeenCalled(); + }); + it('should render items on screen', () => { + renderDurationTicksWidgetMobile(mock_store, default_props); + expect(screen.getByText('05')).toBeInTheDocument(); + expect(screen.getByText('Ticks')).toBeInTheDocument(); + expect(screen.getByText('OK')).toBeInTheDocument(); + }); +}); diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/__tests__/duration-toggle.spec.tsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/__tests__/duration-toggle.spec.tsx new file mode 100644 index 000000000000..bf9d5e9213c4 --- /dev/null +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/__tests__/duration-toggle.spec.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import { screen, render } from '@testing-library/react'; +import DurationToggle from '../duration-toggle'; +import userEvent from '@testing-library/user-event'; + +jest.mock('@deriv/components', () => { + return { + ...jest.requireActual('@deriv/components'), + Icon: jest.fn(() =>
MockedIcon
), + }; +}); +describe('', () => { + let mocked_props: React.ComponentProps; + beforeEach(() => { + mocked_props = { + name: 'test_input', + onChange: jest.fn(), + value: false, + }; + }); + + it('Should render toggle button', () => { + render(); + const toggle_button = screen.getByLabelText('Toggle between advanced and simple duration settings'); + expect(toggle_button).toBeInTheDocument(); + expect(toggle_button).toHaveClass('advanced-simple-toggle'); + expect(screen.getByText('MockedIcon')).toBeInTheDocument(); + }); + it('Should call onChange when button is clicked', () => { + render(); + userEvent.click(screen.getByLabelText('Toggle between advanced and simple duration settings')); + expect(mocked_props.onChange).toHaveBeenCalledWith({ target: { name: 'test_input', value: true } }); + }); +}); diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/__tests__/duration-utils.spec.ts b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/__tests__/duration-utils.spec.ts new file mode 100644 index 000000000000..65b3e934f213 --- /dev/null +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/__tests__/duration-utils.spec.ts @@ -0,0 +1,50 @@ +import { updateAmountChanges } from '../duration-utils'; + +type TUpdateAmountChanges = Parameters; + +describe('updateAmountChanges', () => { + let obj: TUpdateAmountChanges[0], + stake_value: TUpdateAmountChanges[1], + payout_value: TUpdateAmountChanges[2], + basis: TUpdateAmountChanges[3], + trade_basis: TUpdateAmountChanges[4], + trade_amount: TUpdateAmountChanges[5]; + beforeEach(() => { + obj = { + basis: '', + amount: 123, + }; + stake_value = 2; + payout_value = 1; + basis = ''; + trade_basis = ''; + trade_amount = 0; + }); + it('should update basis to "stake" and amount to stake_value when basis is "stake" and stake_value is different from trade_amount', () => { + stake_value = 20; + trade_amount = 40; + basis = 'stake'; + updateAmountChanges(obj, stake_value, payout_value, basis, trade_basis, trade_amount); + + expect(obj.basis).toBe('stake'); + expect(obj.amount).toBe(stake_value); + }); + it('should update basis to "payout" and amount to payout_value when basis is "payout" and payout_value is different from trade_amount', () => { + basis = 'payout'; + payout_value = 20; + trade_amount = 40; + updateAmountChanges(obj, stake_value, payout_value, basis, trade_basis, trade_amount); + + expect(obj.basis).toBe('payout'); + expect(obj.amount).toBe(payout_value); + }); + it('should update basis to trade_basis and amount to trade_amount when basis is different from trade_basis', () => { + trade_basis = 'payout'; + basis = 'stake'; + stake_value = 20; + trade_amount = 20; + updateAmountChanges(obj, stake_value, payout_value, basis, trade_basis, trade_amount); + expect(obj.basis).toBe(basis); + expect(obj.amount).toBe(trade_amount); + }); +}); diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/__tests__/duration.spec.tsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/__tests__/duration.spec.tsx new file mode 100644 index 000000000000..a5261b71515f --- /dev/null +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/__tests__/duration.spec.tsx @@ -0,0 +1,120 @@ +import React from 'react'; +import { screen, render } from '@testing-library/react'; +import Duration from '../duration'; +import moment from 'moment'; +import userEvent from '@testing-library/user-event'; + +jest.mock('@deriv/components', () => { + return { + ...jest.requireActual('@deriv/components'), + Dropdown: jest.fn(() => 'MockedDropdown'), + }; +}); + +jest.mock('../advanced-duration.tsx', () => jest.fn(() => 'MockedAdvancedDuration')); +jest.mock('../simple-duration.tsx', () => jest.fn(() => 'MockedSimpleDuration')); +jest.mock('App/Components/Form/RangeSlider', () => jest.fn(() => 'MockedRangeSlider')); + +describe('', () => { + let default_props: React.ComponentProps; + const duration_toggle_label_text = 'Toggle between advanced and simple duration settings'; + beforeEach(() => { + default_props = { + advanced_duration_unit: '', + advanced_expiry_type: '', + contract_type: '', + duration_t: 5, + duration_unit: 'd', + duration_units_list: [ + { + text: 'Ticks', + value: 't', + }, + { + text: 'Seconds', + value: 's', + }, + { + text: 'Minutes', + value: 'm', + }, + { + text: 'Hours', + value: 'h', + }, + { + text: 'Days', + value: 'd', + }, + ], + duration: 12, + expiry_date: '2023-11-22', + expiry_epoch: 1703057788, + expiry_time: '21:00:00', + expiry_type: 'duration', + getDurationFromUnit: jest.fn(), + hasDurationUnit: jest.fn(), + is_advanced_duration: false, + is_minimized: false, + max_value: 1000, + min_value: 10, + onChange: jest.fn(), + onChangeMultiple: jest.fn(), + onChangeUiStore: jest.fn(), + server_time: moment('2023-11-21T14:30:00'), + simple_duration_unit: '', + start_date: 0, + }; + }); + it('Should render Range slider and simple duration widget if is_advanced_duration is false, duration_unit = t and duration_unit_list is length = 1', () => { + default_props.duration_units_list = [ + { + text: 'Ticks', + value: 't', + }, + ]; + default_props.duration_unit = 't'; + render(); + expect(screen.getByText(/mockeddropdown/i)).toBeInTheDocument(); + expect(screen.getByText(/mockedrangeslider/i)).toBeInTheDocument(); + }); + it('Should render Duration toggle if contract_type is not vanilla ', () => { + render(); + expect(screen.getByLabelText(duration_toggle_label_text)).toBeInTheDocument(); + }); + it('Should not render Duration toggle if contract_type is vanilla ', () => { + default_props.contract_type = 'vanillalongcall'; + render(); + expect(screen.queryByLabelText(duration_toggle_label_text)).not.toBeInTheDocument(); + }); + it('Should render AdvanceDuration widget if is_advanced_duration is true and duration_units_list length is > 1', () => { + default_props.is_advanced_duration = true; + render(); + expect(screen.getByText(/mockedadvancedduration/i)).toBeInTheDocument(); + }); + it('Should render SimpleDuration widget if is_advanced_duration is false and duration_units_list length is > 1', () => { + render(); + expect(screen.getByText(/mockedsimpleduration/i)).toBeInTheDocument(); + }); + it('Should call onChangeUiStore and onChangeMultiple when duration toggle is clicked', () => { + default_props.is_advanced_duration = true; + render(); + userEvent.click(screen.getByLabelText(duration_toggle_label_text)); + expect(default_props.onChangeUiStore).toHaveBeenCalled(); + expect(default_props.onChangeMultiple).toHaveBeenCalled(); + }); + it('Should render 12 Days if is_minimized is true and expiry_type is duration', () => { + const text_to_get = '12 Days'; + default_props.is_minimized = true; + render(); + expect(screen.getByText(text_to_get)).toBeInTheDocument(); + expect(screen.getByText(text_to_get)).toHaveClass('fieldset-minimized'); + expect(screen.getByText(text_to_get)).toHaveClass('fieldset-minimized__duration'); + }); + it('Should render expiry date and time if is_minimized is true and expiry_type is not duration', () => { + default_props.expiry_type = 'endtime'; + default_props.is_minimized = true; + render(); + expect(screen.getByText('Wed - 22 Nov, 2023 21:00:00')).toBeInTheDocument(); + }); +}); diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/advanced-duration.tsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/advanced-duration.tsx index a96ad8546e85..4cd012df7197 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/advanced-duration.tsx +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/advanced-duration.tsx @@ -25,7 +25,6 @@ type TAdvancedDuration = Pick< | 'onChangeUiStore' | 'server_time' | 'start_date' - | 'market_open_times' > & { changeDurationUnit: ({ target }: { target: { name: string; value: string } }) => void; expiry_list: { diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/duration-mobile.tsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/duration-mobile.tsx index 029f1cff255b..44766f73e38e 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/duration-mobile.tsx +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/duration-mobile.tsx @@ -1,15 +1,14 @@ import React from 'react'; -import { Tabs, TickPicker, Numpad, RelativeDatepicker } from '@deriv/components'; -import { isEmptyObject, addComma, getDurationMinMaxValues, getUnitMap } from '@deriv/shared'; -import { Localize, localize } from '@deriv/translations'; -import { observer, useStore } from '@deriv/stores'; +import { Tabs, RelativeDatepicker } from '@deriv/components'; +import { getDurationMinMaxValues } from '@deriv/shared'; +import { localize } from '@deriv/translations'; +import { observer } from '@deriv/stores'; import { useTraderStore } from 'Stores/useTraderStores'; -import moment from 'moment'; -import ExpiryText from './expiry-text'; -import DurationRangeText from './duration-range-text'; import type { TTradeParamsMobile } from 'Modules/Trading/Containers/trade-params-mobile'; +import DurationTicksWidgetMobile from './duration-ticks-widget-mobile'; +import DurationNumbersWidgetMobile from './duration-numbers-widget-mobile'; -type TDuration = Pick< +export type TDurationMobile = Pick< TTradeParamsMobile, | 'amount_tab_idx' | 'd_duration' @@ -29,251 +28,9 @@ type TDuration = Pick< expiry_epoch?: string | number; }; -type TNumber = Pick< - TDuration, - | 'expiry_epoch' - | 'has_amount_error' - | 'payout_value' - | 'setDurationError' - | 'setSelectedDuration' - | 'stake_value' - | 'toggleModal' -> & { - basis_option: string; - contract_expiry?: string; - duration_unit_option: ReturnType['duration_units_list'][0]; - duration_values?: Record; - selected_duration: number; - show_expiry?: boolean; -}; - -type TTicks = Omit; - type TDurationUnit = 't' | 's' | 'm' | 'h' | 'd'; -const submit_label = localize('OK'); - -const updateAmountChanges = ( - obj: Record, - stake_value: number, - payout_value: number, - basis: string, - trade_basis: string, - trade_amount: number -) => { - // TODO: Move onChangeMultiple outside of duration and amount - // and unify all trade parameter components to use same onMultipleChange func onSubmit - // Checks if Amount tab was changed to stake and stake value was updated - if (basis === 'stake' && stake_value !== trade_amount) { - obj.basis = 'stake'; - obj.amount = stake_value; - // Checks if Amount tab was changed to payout and payout value was updated - } else if (basis === 'payout' && payout_value !== trade_amount) { - obj.basis = 'payout'; - obj.amount = payout_value; - // Checks if Amount tab was changed but payout or stake value was not updated - } else if (trade_basis !== basis) { - obj.basis = basis; - obj.amount = trade_amount; - } -}; - -const Ticks = observer( - ({ - setDurationError, - basis_option, - toggleModal, - has_amount_error, - payout_value, - stake_value, - selected_duration, - setSelectedDuration, - }: TTicks) => { - const { - duration_min_max, - duration: trade_duration, - duration_unit: trade_duration_unit, - basis: trade_basis, - amount: trade_amount, - onChangeMultiple, - } = useTraderStore(); - React.useEffect(() => { - setDurationError(false); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - const [min_tick, max_tick] = getDurationMinMaxValues(duration_min_max, 'tick', 't'); - - const setTickDuration = (value: { target: { value: number; name: string } }) => { - const { value: duration } = value.target; - const on_change_obj: Record = {}; - - // check for any amount changes from Amount trade params tab before submitting onChange object - if (!has_amount_error) - updateAmountChanges(on_change_obj, stake_value, payout_value, basis_option, trade_basis, trade_amount); - - if (trade_duration !== duration || trade_duration_unit !== 't') { - on_change_obj.duration_unit = 't'; - on_change_obj.duration = duration; - } - - if (!isEmptyObject(on_change_obj)) onChangeMultiple(on_change_obj); - toggleModal(); - }; - - const onTickChange = (tick: number) => setSelectedDuration('t', tick); - const tick_duration = - trade_duration < Number(min_tick) && selected_duration < Number(min_tick) - ? Number(min_tick) - : selected_duration; - return ( -
- -
- ); - } -); - -const Numbers = observer( - ({ - basis_option, - contract_expiry = 'intraday', - duration_unit_option, - duration_values, - expiry_epoch, - has_amount_error, - payout_value, - selected_duration, - setDurationError, - setSelectedDuration, - stake_value, - show_expiry = false, - toggleModal, - }: TNumber) => { - const { ui } = useStore(); - const { addToast } = ui; - const { - duration_min_max, - duration: trade_duration, - duration_unit: trade_duration_unit, - basis: trade_basis, - amount: trade_amount, - onChangeMultiple, - } = useTraderStore(); - const { value: duration_unit } = duration_unit_option; - const [min, max] = getDurationMinMaxValues(duration_min_max, contract_expiry, duration_unit); - const [has_error, setHasError] = React.useState(false); - - const validateDuration = (value: number | string) => { - const localized_message = ( - - ); - if (parseInt(value as string) < Number(min) || Math.trunc(selected_duration) > Number(max)) { - addToast({ key: 'duration_error', content: localized_message, type: 'error', timeout: 2000 }); - setDurationError(true); - setHasError(true); - return 'error'; - } else if (parseInt(value as string) > Number(max)) { - addToast({ key: 'duration_error', content: localized_message, type: 'error', timeout: 2000 }); - setHasError(true); - return 'error'; - } else if (value.toString().length < 1) { - addToast({ key: 'duration_error', content: localized_message, type: 'error', timeout: 2000 }); - setDurationError(true); - setHasError(true); - return false; - } - - setDurationError(false); - setHasError(false); - return true; - }; - - const setDuration = (duration: string | number) => { - const on_change_obj: Record = {}; - - // check for any amount changes from Amount trade params tab before submitting onChange object - if (!has_amount_error) - updateAmountChanges(on_change_obj, stake_value, payout_value, basis_option, trade_basis, trade_amount); - - if (trade_duration !== Number(duration) || trade_duration_unit !== duration_unit) { - on_change_obj.duration_unit = duration_unit; - on_change_obj.duration = duration; - on_change_obj.expiry_type = 'duration'; - } - - if (!isEmptyObject(on_change_obj)) onChangeMultiple(on_change_obj); - toggleModal(); - }; - - const setExpiryDate = (epoch: number, duration: string | number) => { - if (trade_duration_unit !== 'd') { - return moment.utc().add(Number(duration), 'days').format('D MMM YYYY, [23]:[59]:[59] [GMT +0]'); - } - let expiry_date = new Date((epoch - trade_duration * 24 * 60 * 60) * 1000); - if (duration) { - expiry_date = new Date(expiry_date.getTime() + Number(duration) * 24 * 60 * 60 * 1000); - } - - return expiry_date - .toUTCString() - .replace('GMT', 'GMT +0') - .substring(5) - .replace(/(\d{2}) (\w{3} \d{4})/, '$1 $2,'); - }; - - const onNumberChange = (num: number | string) => { - setSelectedDuration(duration_unit, Number(num)); - validateDuration(num); - }; - - const fixed_date = !has_error ? setExpiryDate(Number(expiry_epoch), Number(duration_values?.d_duration)) : ''; - - const { name_plural, name } = getUnitMap()[duration_unit]; - const duration_unit_text = name_plural ?? name; - - return ( -
-
- - {show_expiry && } -
- { - return
{v}
; - }} - pip_size={0} - submit_label={submit_label} - min={Number(min)} - max={Number(max)} - reset_press_interval={350} - reset_value='' - onValidate={validateDuration} - onValueChange={onNumberChange} - /> -
- ); - } -); - -const Duration = observer( +const DurationMobile = observer( ({ amount_tab_idx, d_duration, @@ -290,7 +47,7 @@ const Duration = observer( stake_value, t_duration, toggleModal, - }: TDuration) => { + }: TDurationMobile) => { const { duration_units_list, duration_min_max, duration_unit, basis: trade_basis } = useTraderStore(); const duration_values = { t_duration, @@ -330,7 +87,7 @@ const Duration = observer( case 't': return (
- - - - - & { + basis_option: string; + contract_expiry?: string; + duration_unit_option: ReturnType['duration_units_list'][0]; + duration_values?: Record; + selected_duration: number; + show_expiry?: boolean; +}; + +const DurationNumbersWidgetMobile = observer( + ({ + basis_option, + contract_expiry = 'intraday', + duration_unit_option, + duration_values, + expiry_epoch, + has_amount_error, + payout_value, + selected_duration, + setDurationError, + setSelectedDuration, + stake_value, + show_expiry = false, + toggleModal, + }: TNumber) => { + const { ui } = useStore(); + const { addToast } = ui; + const { + duration_min_max, + duration: trade_duration, + duration_unit: trade_duration_unit, + basis: trade_basis, + amount: trade_amount, + onChangeMultiple, + } = useTraderStore(); + const { value: duration_unit } = duration_unit_option; + const [min, max] = getDurationMinMaxValues(duration_min_max, contract_expiry, duration_unit); + const [has_error, setHasError] = React.useState(false); + const localized_message = ( + + ); + const validateDuration = (value: number | string) => { + if ( + Number(value) < Number(min) || + Math.trunc(selected_duration) > Number(max) || + value.toString().length < 1 + ) { + addToast({ key: 'duration_error', content: localized_message, type: 'error', timeout: 2000 }); + setDurationError(true); + setHasError(true); + return 'error'; + } else if (Number(value) > Number(max)) { + addToast({ key: 'duration_error', content: localized_message, type: 'error', timeout: 2000 }); + setHasError(true); + return 'error'; + } + + setDurationError(false); + setHasError(false); + return true; + }; + + const setDuration = (duration: string | number) => { + const on_change_obj: Partial> = {}; + // check for any amount changes from Amount trade params tab before submitting onChange object + if (!has_amount_error) + updateAmountChanges(on_change_obj, stake_value, payout_value, basis_option, trade_basis, trade_amount); + + if (trade_duration !== Number(duration) || trade_duration_unit !== duration_unit) { + on_change_obj.duration_unit = duration_unit; + on_change_obj.duration = Number(duration); + on_change_obj.expiry_type = 'duration'; + } + + if (!isEmptyObject(on_change_obj)) onChangeMultiple(on_change_obj); + toggleModal(); + }; + + const setExpiryDate = (epoch: number, duration: string | number) => { + if (trade_duration_unit !== 'd') { + return moment.utc().add(Number(duration), 'days').format('D MMM YYYY, [23]:[59]:[59] [GMT +0]'); + } + let expiry_date = new Date((epoch - trade_duration * 24 * 60 * 60) * 1000); + if (duration) { + expiry_date = new Date(expiry_date.getTime() + Number(duration) * 24 * 60 * 60 * 1000); + } + + return expiry_date + .toUTCString() + .replace('GMT', 'GMT +0') + .substring(5) + .replace(/(\d{2}) (\w{3} \d{4})/, '$1 $2,'); + }; + + const onNumberChange = (num: number | string) => { + setSelectedDuration(duration_unit, Number(num)); + validateDuration(num); + }; + + const fixed_date = !has_error ? setExpiryDate(Number(expiry_epoch), Number(duration_values?.d_duration)) : ''; + + const { name_plural, name } = getUnitMap()[duration_unit]; + const duration_unit_text = name_plural ?? name; + + return ( +
+
+ + {show_expiry && } +
+ { + return
{v}
; + }} + pip_size={0} + submit_label={localize('OK')} + min={Number(min)} + max={Number(max)} + reset_press_interval={350} + reset_value='' + onValidate={validateDuration} + onValueChange={onNumberChange} + /> +
+ ); + } +); + +export default DurationNumbersWidgetMobile; diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/duration-ticks-widget-mobile.tsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/duration-ticks-widget-mobile.tsx new file mode 100644 index 000000000000..655519b24892 --- /dev/null +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/duration-ticks-widget-mobile.tsx @@ -0,0 +1,98 @@ +import React from 'react'; +import { observer } from '@deriv/stores'; +import { useTraderStore } from 'Stores/useTraderStores'; +import { updateAmountChanges } from './duration-utils'; +import { getDurationMinMaxValues, isEmptyObject } from '@deriv/shared'; +import { localize } from '@deriv/translations'; +import { TickPicker } from '@deriv/components'; +import { TDurationMobile } from './duration-mobile'; + +type TNumber = Pick< + TDurationMobile, + | 'expiry_epoch' + | 'has_amount_error' + | 'payout_value' + | 'setDurationError' + | 'setSelectedDuration' + | 'stake_value' + | 'toggleModal' +> & { + basis_option: string; + contract_expiry?: string; + duration_unit_option: ReturnType['duration_units_list'][0]; + duration_values?: Record; + selected_duration: number; + show_expiry?: boolean; +}; + +type TDurationTicksWidgetMobile = Omit< + TNumber, + 'expiry_epoch' | 'contract_expiry' | 'duration_unit_option' | 'show_expiry' +>; +const DurationTicksWidgetMobile = observer( + ({ + setDurationError, + basis_option, + toggleModal, + has_amount_error, + payout_value, + stake_value, + selected_duration, + setSelectedDuration, + }: TDurationTicksWidgetMobile) => { + const { + duration_min_max, + duration: trade_duration, + duration_unit: trade_duration_unit, + basis: trade_basis, + amount: trade_amount, + onChangeMultiple, + } = useTraderStore(); + + React.useEffect(() => { + setDurationError(false); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const [min_tick, max_tick] = getDurationMinMaxValues(duration_min_max, 'tick', 't'); + + const setTickDuration = (value: { target: { value: number; name: string } }) => { + const { value: duration } = value.target; + const on_change_obj: Partial> = {}; + + // check for any amount changes from Amount trade params tab before submitting onChange object + if (!has_amount_error) + updateAmountChanges(on_change_obj, stake_value, payout_value, basis_option, trade_basis, trade_amount); + + if (trade_duration !== duration || trade_duration_unit !== 't') { + on_change_obj.duration_unit = 't'; + on_change_obj.duration = duration; + } + + if (!isEmptyObject(on_change_obj)) onChangeMultiple(on_change_obj); + toggleModal(); + }; + + const onTickChange = (tick: number) => setSelectedDuration('t', tick); + const tick_duration = + trade_duration < Number(min_tick) && selected_duration < Number(min_tick) + ? Number(min_tick) + : selected_duration; + return ( +
+ +
+ ); + } +); + +export default DurationTicksWidgetMobile; diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/duration-utils.ts b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/duration-utils.ts new file mode 100644 index 000000000000..5f08822ee193 --- /dev/null +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/duration-utils.ts @@ -0,0 +1,26 @@ +import { useTraderStore } from 'Stores/useTraderStores'; + +export const updateAmountChanges = ( + obj: Partial>, + stake_value: number, + payout_value: number, + basis: string, + trade_basis: string, + trade_amount: number +) => { + // TODO: Move onChangeMultiple outside of duration and amount + // and unify all trade parameter components to use same onMultipleChange func onSubmit + // Checks if Amount tab was changed to stake and stake value was updated + if (basis === 'stake' && stake_value !== trade_amount) { + obj.basis = 'stake'; + obj.amount = stake_value; + // Checks if Amount tab was changed to payout and payout value was updated + } else if (basis === 'payout' && payout_value !== trade_amount) { + obj.basis = 'payout'; + obj.amount = payout_value; + // Checks if Amount tab was changed but payout or stake value was not updated + } else if (trade_basis !== basis) { + obj.basis = basis; + obj.amount = trade_amount; + } +}; diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/duration-wrapper.tsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/duration-wrapper.tsx index 1c60b473d909..ff62ace6ce9e 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/duration-wrapper.tsx +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/duration-wrapper.tsx @@ -30,7 +30,6 @@ const DurationWrapper = observer(({ is_minimized }: TDurationWrapper) => { expiry_epoch, expiry_time, start_date, - market_open_times, onChange, onChangeMultiple, } = useTraderStore(); @@ -52,7 +51,6 @@ const DurationWrapper = observer(({ is_minimized }: TDurationWrapper) => { getDurationFromUnit, is_minimized, is_advanced_duration, - market_open_times, onChange, onChangeMultiple, onChangeUiStore, diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/duration.tsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/duration.tsx index 9deb9ce9f69d..6b0c0c84ca60 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/duration.tsx +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Duration/duration.tsx @@ -29,7 +29,6 @@ export type TDuration = { hasDurationUnit: (duration_type: string, is_advanced: boolean) => boolean; is_advanced_duration: TUIStore['is_advanced_duration']; is_minimized?: boolean; - market_open_times: TTradeStore['market_open_times']; max_value: number | null; min_value: number | null; onChange: TTradeStore['onChange']; @@ -56,7 +55,6 @@ const Duration = ({ hasDurationUnit, is_advanced_duration, is_minimized, - market_open_times, max_value, min_value, onChange, @@ -202,7 +200,6 @@ const Duration = ({ expiry_list={expiry_list} expiry_type={expiry_type} getDurationFromUnit={getDurationFromUnit} - market_open_times={market_open_times} number_input_props={passthrough_props.number_input} onChange={onChange} onChangeUiStore={onChangeUiStore}