diff --git a/packages/components/src/components/contract-card/contract-card-items/__tests__/accumulator-card-body.spec.tsx b/packages/components/src/components/contract-card/contract-card-items/__tests__/accumulator-card-body.spec.tsx new file mode 100644 index 000000000000..12d7fd69f4aa --- /dev/null +++ b/packages/components/src/components/contract-card/contract-card-items/__tests__/accumulator-card-body.spec.tsx @@ -0,0 +1,53 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import AccumulatorCardBody from '../accumulator-card-body'; + +type TAccumulatorCardBody = React.ComponentProps; + +describe('', () => { + const mock_props: TAccumulatorCardBody = { + contract_info: { + buy_price: 123, + sell_price: 234, + profit: 111, + contract_id: 12345, + is_valid_to_sell: 1, + status: 'sold', + is_settleable: 1, + is_expired: 1, + }, + contract_update: { + take_profit: { + order_amount: 300, + }, + }, + getCardLabels: () => ({ + CURRENT_STAKE: 'Current stake:', + INITIAL_STAKE: 'Initial stake:', + TAKE_PROFIT: 'Take profit:', + TOTAL_PROFIT_LOSS: 'Total profit/loss:', + }), + is_sold: 1, + status: 'profit', + currency: 'USD', + }; + it('should display all contract card items, label, and values', () => { + render(); + expect(screen.getByText('Initial stake:')).toBeInTheDocument(); + expect(screen.getByText('123.00')).toBeInTheDocument(); + expect(screen.getByText('Current stake:')).toBeInTheDocument(); + expect(screen.getByText('234.00')).toBeInTheDocument(); + expect(screen.getByText('Total profit/loss:')).toBeInTheDocument(); + expect(screen.getByText('111.00')).toBeInTheDocument(); + expect(screen.getByText('Take profit:')).toBeInTheDocument(); + expect(screen.getByText('300.00')).toBeInTheDocument(); + }); + + it('should display Take profit: label and - as value when take_profit is not available', () => { + if (mock_props?.contract_update?.take_profit?.order_amount) + mock_props.contract_update.take_profit.order_amount = null; + render(); + expect(screen.getByText('Take profit:')).toBeInTheDocument(); + expect(screen.getByText('-')).toBeInTheDocument(); + }); +}); diff --git a/packages/components/src/components/contract-card/contract-card-items/__tests__/tick-counter-bar.spec.js b/packages/components/src/components/contract-card/contract-card-items/__tests__/tick-counter-bar.spec.tsx similarity index 100% rename from packages/components/src/components/contract-card/contract-card-items/__tests__/tick-counter-bar.spec.js rename to packages/components/src/components/contract-card/contract-card-items/__tests__/tick-counter-bar.spec.tsx diff --git a/packages/components/src/components/contract-card/contract-card-items/accumulator-card-body.jsx b/packages/components/src/components/contract-card/contract-card-items/accumulator-card-body.tsx similarity index 76% rename from packages/components/src/components/contract-card/contract-card-items/accumulator-card-body.jsx rename to packages/components/src/components/contract-card/contract-card-items/accumulator-card-body.tsx index 0a7f1cda3c9d..7c0153eee830 100644 --- a/packages/components/src/components/contract-card/contract-card-items/accumulator-card-body.jsx +++ b/packages/components/src/components/contract-card/contract-card-items/accumulator-card-body.tsx @@ -1,13 +1,35 @@ import classNames from 'classnames'; -import PropTypes from 'prop-types'; import React from 'react'; import { isCryptocurrency, getLimitOrderAmount, isValidToSell } from '@deriv/shared'; +import { TContractInfo } from '@deriv/shared/src/utils/contract/contract-types'; import ContractCardItem from './contract-card-item'; import ToggleCardDialog from './toggle-card-dialog'; import Icon from '../../icon'; import MobileWrapper from '../../mobile-wrapper'; import Money from '../../money'; import { ResultStatusIcon } from '../result-overlay/result-overlay'; +import { ContractUpdate } from '@deriv/api-types'; +import { TToastConfig } from '../../types/contract.types'; +import { TGetCardLables } from '../../types/common.types'; + +type TAccumulatorCardBody = { + addToast: (toast_config: TToastConfig) => void; + connectWithContractUpdate?: React.ComponentProps['connectWithContractUpdate']; + contract_info: TContractInfo; + contract_update?: ContractUpdate; + currency: Required['currency']; + current_focus?: string | null; + error_message_alignment?: string; + getCardLabels: TGetCardLables; + getContractById: React.ComponentProps['getContractById']; + indicative?: number; + is_sold: boolean; + onMouseLeave: () => void; + removeToast: (toast_id: string) => void; + setCurrentFocus: (value: string) => void; + status?: string; + is_positions?: boolean; +}; const AccumulatorCardBody = ({ addToast, @@ -26,11 +48,16 @@ const AccumulatorCardBody = ({ setCurrentFocus, status, is_positions, -}) => { +}: TAccumulatorCardBody) => { const { buy_price, profit, limit_order, sell_price } = contract_info; const { take_profit } = getLimitOrderAmount(contract_update || limit_order); const is_valid_to_sell = isValidToSell(contract_info); const { CURRENT_STAKE, INITIAL_STAKE, TAKE_PROFIT, TOTAL_PROFIT_LOSS } = getCardLabels(); + let is_won, is_loss; + if (profit) { + is_won = +profit > 0; + is_loss = +profit < 0; + } return ( @@ -41,8 +68,8 @@ const AccumulatorCardBody = ({
0, - 'dc-contract-card--loss': +profit < 0, + 'dc-contract-card--profit': is_won, + 'dc-contract-card--loss': is_loss, })} > @@ -59,8 +86,8 @@ const AccumulatorCardBody = ({ 0} + is_loss={is_loss} + is_won={is_won} >
- 0} /> +
)} @@ -107,23 +134,4 @@ const AccumulatorCardBody = ({ ); }; -AccumulatorCardBody.propTypes = { - addToast: PropTypes.func, - connectWithContractUpdate: PropTypes.func, - contract_info: PropTypes.object, - contract_update: PropTypes.object, - currency: PropTypes.string, - current_focus: PropTypes.string, - error_message_alignment: PropTypes.string, - getCardLabels: PropTypes.func, - getContractById: PropTypes.func, - indicative: PropTypes.number, - is_positions: PropTypes.bool, - is_sold: PropTypes.bool, - onMouseLeave: PropTypes.func, - removeToast: PropTypes.func, - setCurrentFocus: PropTypes.func, - status: PropTypes.string, -}; - export default React.memo(AccumulatorCardBody); diff --git a/packages/components/src/components/contract-card/contract-card-items/contract-card-body.tsx b/packages/components/src/components/contract-card/contract-card-items/contract-card-body.tsx index dad028c0ded2..9ccf910a4274 100644 --- a/packages/components/src/components/contract-card/contract-card-items/contract-card-body.tsx +++ b/packages/components/src/components/contract-card/contract-card-items/contract-card-body.tsx @@ -122,7 +122,7 @@ const ContractCardBody = ({ {...toggle_card_dialog_props} /> ); - } else if (is_accumulator) { + } else if (is_accumulator && indicative !== null) { card_body = ( ( +type TTickCounterBar = { + current_tick?: number; + label: string; + max_ticks_duration?: number; +}; +const TickCounterBar = ({ current_tick, label, max_ticks_duration }: TTickCounterBar) => (
@@ -12,10 +16,4 @@ const TickCounterBar = ({ current_tick, label, max_ticks_duration }) => (
); -TickCounterBar.propTypes = { - current_tick: PropTypes.number, - label: PropTypes.string, - max_ticks_duration: PropTypes.number, -}; - export default React.memo(TickCounterBar); diff --git a/packages/components/src/components/contract-card/result-overlay/result-overlay.tsx b/packages/components/src/components/contract-card/result-overlay/result-overlay.tsx index 17f3f4dee848..0762e3eff3aa 100644 --- a/packages/components/src/components/contract-card/result-overlay/result-overlay.tsx +++ b/packages/components/src/components/contract-card/result-overlay/result-overlay.tsx @@ -20,7 +20,7 @@ type TResultOverlayProps = { type TResultStatusIcon = { getCardLabels: TGetCardLables; - is_contract_won: boolean; + is_contract_won?: boolean; }; export const ResultStatusIcon = ({ getCardLabels, is_contract_won }: TResultStatusIcon) => ( diff --git a/packages/components/src/components/div100vh-container/div100vh-container.tsx b/packages/components/src/components/div100vh-container/div100vh-container.tsx index d33c67384078..6bcf1a60361a 100644 --- a/packages/components/src/components/div100vh-container/div100vh-container.tsx +++ b/packages/components/src/components/div100vh-container/div100vh-container.tsx @@ -16,7 +16,7 @@ import Div100vh from 'react-div-100vh'; type TDiv100vhContainer = { id?: string; - height_offset: string; + height_offset?: string; is_bypassed?: boolean; is_disabled?: boolean; max_height_offset?: string; diff --git a/packages/stores/src/mockStore.ts b/packages/stores/src/mockStore.ts index 1203723b8634..01aa244cbce4 100644 --- a/packages/stores/src/mockStore.ts +++ b/packages/stores/src/mockStore.ts @@ -132,8 +132,8 @@ const mock = (): TStores & { is_mock: boolean } => { is_logged_in: false, is_logging_in: false, is_pending_proof_of_ownership: false, - is_switching: false, is_single_currency: false, + is_switching: false, is_tnc_needed: false, is_trading_experience_incomplete: false, is_virtual: false, diff --git a/packages/stores/types.ts b/packages/stores/types.ts index 770e29b4380c..4fba8d9adb93 100644 --- a/packages/stores/types.ts +++ b/packages/stores/types.ts @@ -313,8 +313,8 @@ type TClientStore = { is_logging_in: boolean; is_low_risk: boolean; is_pending_proof_of_ownership: boolean; - is_switching: boolean; is_single_currency: boolean; + is_switching: boolean; is_tnc_needed: boolean; is_trading_experience_incomplete: boolean; is_virtual: boolean; diff --git a/packages/trader/src/Assets/Trading/Categories/__tests__/accumulator-trade-description.spec.tsx b/packages/trader/src/Assets/Trading/Categories/__tests__/accumulator-trade-description.spec.tsx new file mode 100644 index 000000000000..2ef6ff401b10 --- /dev/null +++ b/packages/trader/src/Assets/Trading/Categories/__tests__/accumulator-trade-description.spec.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import AccumulatorTradeDescription from '../accumulator-trade-description'; +import userEvent from '@testing-library/user-event'; + +describe('', () => { + it('Ensure content of component is rendered properly', () => { + render(); + expect( + screen.getByText( + /Your stake will continue to grow as long as the current spot price remains within a specified/i + ) + ).toBeInTheDocument(); + }); + it('Ensure clicking on definition works', () => { + const onClick = jest.fn(); + render(); + const glossary_definition = screen.getByText(/growth rate/i); + userEvent.click(glossary_definition); + expect(onClick).toHaveBeenCalled(); + }); +}); diff --git a/packages/trader/src/Modules/Contract/Components/AccumulatorsStats/__tests__/accumulators-stats-manual-modal.spec.js b/packages/trader/src/Modules/Contract/Components/AccumulatorsStats/__tests__/accumulators-stats-manual-modal.spec.tsx similarity index 71% rename from packages/trader/src/Modules/Contract/Components/AccumulatorsStats/__tests__/accumulators-stats-manual-modal.spec.js rename to packages/trader/src/Modules/Contract/Components/AccumulatorsStats/__tests__/accumulators-stats-manual-modal.spec.tsx index cda3fb3e2cd1..867f80904998 100644 --- a/packages/trader/src/Modules/Contract/Components/AccumulatorsStats/__tests__/accumulators-stats-manual-modal.spec.js +++ b/packages/trader/src/Modules/Contract/Components/AccumulatorsStats/__tests__/accumulators-stats-manual-modal.spec.tsx @@ -1,18 +1,29 @@ import React from 'react'; -import { fireEvent, render, screen, within } from '@testing-library/react'; +import { render, screen, within } from '@testing-library/react'; import { AccumulatorsStatsManualModal } from '../accumulators-stats-manual-modal'; +import userEvent from '@testing-library/user-event'; + +type TModal = React.ComponentType<{ + children: React.ReactNode; + is_open: boolean; + title: string; + toggleModal: () => void; +}> & { + Body?: React.ComponentType<{ + children: React.ReactNode; + }>; +}; jest.mock('@deriv/components', () => { const original_module = jest.requireActual('@deriv/components'); - const Modal = jest.fn( - ({ children, is_open, title, toggleModal }) => - is_open && ( -
-

{title}

-
IcCross
- {children} -
- ) + const Modal: TModal = jest.fn(({ children, is_open, title, toggleModal }) => + is_open ? ( +
+

{title}

+
IcCross
+ {children} +
+ ) : null ); Modal.Body = jest.fn(({ children }) =>
{children}
); return { @@ -32,7 +43,7 @@ describe('AccumulatorsStatsManualModal', () => { modal_root_el.setAttribute('id', 'modal_root'); document.body.appendChild(modal_root_el); - let props; + let props: React.ComponentProps; beforeEach(() => { props = { title: 'Stats', @@ -47,12 +58,12 @@ describe('AccumulatorsStatsManualModal', () => { it('should open when info icon (IcInfoOutline) is clicked', () => { const { rerender } = render(); const info_icon = screen.getByText('IcInfoOutline'); - fireEvent.click(info_icon); + userEvent.click(info_icon); expect(props.toggleManual).toBeCalled(); expect(props.is_manual_open).toBeTruthy(); rerender(); - expect(screen.getByRole('heading', 'Stats')).toBeInTheDocument(); + expect(screen.getByRole('heading', { name: 'Stats' })).toBeInTheDocument(); expect(screen.getByTestId('dt_accumulators_stats_manual_video')).toBeInTheDocument(); expect(screen.getByText(/stats show the history of consecutive tick counts/i)).toBeInTheDocument(); }); @@ -60,7 +71,7 @@ describe('AccumulatorsStatsManualModal', () => { props.is_manual_open = true; const { rerender } = render(); const close_icon = within(screen.getByTestId('modal')).getByText('IcCross'); - fireEvent.click(close_icon); + userEvent.click(close_icon); expect(props.toggleManual).toBeCalled(); expect(props.is_manual_open).toBeFalsy(); diff --git a/packages/trader/src/Modules/Contract/Components/AccumulatorsStats/__tests__/accumulators-stats.spec.js b/packages/trader/src/Modules/Contract/Components/AccumulatorsStats/__tests__/accumulators-stats.spec.tsx similarity index 78% rename from packages/trader/src/Modules/Contract/Components/AccumulatorsStats/__tests__/accumulators-stats.spec.js rename to packages/trader/src/Modules/Contract/Components/AccumulatorsStats/__tests__/accumulators-stats.spec.tsx index 2788d8435905..5467b52fcfef 100644 --- a/packages/trader/src/Modules/Contract/Components/AccumulatorsStats/__tests__/accumulators-stats.spec.js +++ b/packages/trader/src/Modules/Contract/Components/AccumulatorsStats/__tests__/accumulators-stats.spec.tsx @@ -1,9 +1,10 @@ import React from 'react'; -import { fireEvent, render, screen, within } from '@testing-library/react'; +import { render, screen, within } from '@testing-library/react'; import { isDesktop, isMobile } from '@deriv/shared'; import AccumulatorsStats, { ROW_SIZES } from '../accumulators-stats'; import { TraderProviders } from '../../../../../trader-providers'; import { mockStore } from '@deriv/stores'; +import userEvent from '@testing-library/user-event'; const mock_connect_props = { modules: { @@ -29,34 +30,34 @@ describe('AccumulatorsStats', () => { modal_root_el.setAttribute('id', 'modal_root'); beforeEach(() => { - isMobile.mockReturnValue(false); - isDesktop.mockReturnValue(true); + (isMobile as jest.Mock).mockReturnValue(false); + (isDesktop as jest.Mock).mockReturnValue(true); }); it('should render as expandable', () => { - const { container } = render(, { + render(, { wrapper: ({ children }) => ( {children} ), }); - expect(container.querySelector('.accordion-toggle-arrow')).toBeInTheDocument(); + expect(screen.getByTestId('dt_accordion-toggle-arrow')).toBeInTheDocument(); }); it('should render as non-expandable', () => { - const { container } = render(, { + render(, { wrapper: ({ children }) => ( {children} ), }); - expect(container.querySelector('.accordion-toggle-arrow')).not.toBeInTheDocument(); + expect(screen.queryByTestId('dt_accordion-toggle-arrow')).not.toBeInTheDocument(); }); it('should show manual after info icon is clicked', () => { - const { container } = render(, { + render(, { container: document.body.appendChild(modal_root_el), wrapper: ({ children }) => ( {children} ), }); - fireEvent.click(container.querySelector('.info')); + userEvent.click(screen.getByTestId('dt_ic_info_icon')); expect(screen.getByTestId('dt_accumulators_stats_manual_video')).toBeInTheDocument(); }); it('should render partial history values (tick counters) when initially collapsed in desktop', () => { @@ -68,8 +69,8 @@ describe('AccumulatorsStats', () => { expect(screen.getAllByTestId('dt_accu_stats_history_counter').length).toEqual(ROW_SIZES.DESKTOP_COLLAPSED); }); it('should render partial history values (tick counters) when initially collapsed in mobile', () => { - isMobile.mockReturnValue(true); - isDesktop.mockReturnValue(false); + (isMobile as jest.Mock).mockReturnValue(true); + (isDesktop as jest.Mock).mockReturnValue(false); render(, { wrapper: ({ children }) => ( {children} @@ -77,23 +78,24 @@ describe('AccumulatorsStats', () => { }); expect(screen.getAllByTestId('dt_accu_stats_history_counter').length).toEqual(ROW_SIZES.MOBILE_COLLAPSED); }); + it('should expand in desktop when accordion_toggle_arrow is clicked', () => { - const { container } = render(, { + render(, { wrapper: ({ children }) => ( {children} ), }); expect(screen.getAllByTestId('dt_accu_stats_history_counter').length).toEqual(ROW_SIZES.DESKTOP_COLLAPSED); - fireEvent.click(container.querySelector('.accordion-toggle-arrow')); + userEvent.click(screen.getByTestId('dt_accordion-toggle-arrow')); const row = screen.getAllByTestId('dt_accu_stats_history_row')[0]; expect(within(row).getAllByTestId('dt_accu_stats_history_counter').length).toEqual(ROW_SIZES.DESKTOP_EXPANDED); expect(screen.getAllByTestId('dt_accu_stats_history_counter').length).toEqual(20); }); it('should show MobileDialog with full "Stay in history" in mobile when accordion_toggle_arrow is clicked', () => { - isMobile.mockReturnValue(true); - isDesktop.mockReturnValue(false); - const { container } = render(, { + (isMobile as jest.Mock).mockReturnValue(true); + (isDesktop as jest.Mock).mockReturnValue(false); + render(, { container: document.body.appendChild(modal_root_el), wrapper: ({ children }) => ( {children} @@ -101,8 +103,8 @@ describe('AccumulatorsStats', () => { }); expect(screen.getAllByTestId('dt_accu_stats_history_counter').length).toEqual(ROW_SIZES.MOBILE_COLLAPSED); - fireEvent.click(container.querySelector('.accordion-toggle-arrow')); - const mobile_dialog = document.body.querySelector('.dc-mobile-dialog__accumulators-stats'); + userEvent.click(screen.getByTestId('dt_accordion-toggle-arrow')); + const mobile_dialog = screen.getByTestId('dt_mobile_dialog'); const row = within(mobile_dialog).getAllByTestId('dt_accu_stats_history_row')[0]; expect(within(row).getAllByTestId('dt_accu_stats_history_counter').length).toEqual(ROW_SIZES.MOBILE_EXPANDED); expect(within(mobile_dialog).getAllByTestId('dt_accu_stats_history_counter').length).toEqual(20); diff --git a/packages/trader/src/Modules/Contract/Components/AccumulatorsStats/__tests__/ticks-history-counter.spec.js b/packages/trader/src/Modules/Contract/Components/AccumulatorsStats/__tests__/ticks-history-counter.spec.tsx similarity index 71% rename from packages/trader/src/Modules/Contract/Components/AccumulatorsStats/__tests__/ticks-history-counter.spec.js rename to packages/trader/src/Modules/Contract/Components/AccumulatorsStats/__tests__/ticks-history-counter.spec.tsx index d18c3215ee88..6f41c13740f8 100644 --- a/packages/trader/src/Modules/Contract/Components/AccumulatorsStats/__tests__/ticks-history-counter.spec.js +++ b/packages/trader/src/Modules/Contract/Components/AccumulatorsStats/__tests__/ticks-history-counter.spec.tsx @@ -3,9 +3,11 @@ import { render, screen } from '@testing-library/react'; import TicksHistoryCounter from '../ticks-history-counter'; describe('TicksHistoryCounter', () => { - let mock_props; + let mock_props: React.ComponentProps; beforeEach(() => { mock_props = { + progress_dots_testid: 'dt_accumulators-stats__progress-dots', + ticks_history_counter_testid: 'dt_accu_stats_history_counter', has_progress_dots: false, value: 1234, }; @@ -15,10 +17,10 @@ describe('TicksHistoryCounter', () => { render(); const accu_stats_wrapper = screen.getByTestId('dt_accu_stats_history_counter'); - expect(accu_stats_wrapper).toHaveTextContent(1234); + expect(accu_stats_wrapper).toHaveTextContent('1234'); expect(accu_stats_wrapper).toHaveClass('accumulators-stats__history-counter'); expect(accu_stats_wrapper).not.toHaveClass('accumulators-stats__history-counter--emphasized'); - expect(accu_stats_wrapper.firstElementChild).not.toBeInTheDocument(); + expect(screen.queryByTestId('dt_accumulators-stats__progress-dots')).not.toBeInTheDocument(); }); it('should render TicksHistoryCounter with dots and not highlighted', () => { @@ -27,10 +29,10 @@ describe('TicksHistoryCounter', () => { render(); const accu_stats_wrapper = screen.getByTestId('dt_accu_stats_history_counter'); - expect(accu_stats_wrapper).toHaveTextContent(1234); + expect(accu_stats_wrapper).toHaveTextContent('1234'); expect(accu_stats_wrapper).toHaveClass('accumulators-stats__history-counter'); expect(accu_stats_wrapper).not.toHaveClass('accumulators-stats__history-counter--emphasized'); - expect(accu_stats_wrapper.firstElementChild).toHaveClass('accumulators-stats__progress-dots'); + expect(screen.getByTestId('dt_accumulators-stats__progress-dots')).toBeInTheDocument(); }); it('should render TicksHistoryCounter with dots and highlighted', () => { @@ -41,9 +43,9 @@ describe('TicksHistoryCounter', () => { render(); const accu_stats_wrapper = screen.getByTestId('dt_accu_stats_history_counter'); - expect(accu_stats_wrapper).toHaveTextContent(0); + expect(accu_stats_wrapper).toHaveTextContent('0'); expect(accu_stats_wrapper).toHaveClass('accumulators-stats__history-counter'); expect(accu_stats_wrapper).toHaveClass('accumulators-stats__history-counter--emphasized'); - expect(accu_stats_wrapper.firstElementChild).toHaveClass('accumulators-stats__progress-dots'); + expect(screen.getByTestId('dt_accumulators-stats__progress-dots')).toBeInTheDocument(); }); }); diff --git a/packages/trader/src/Modules/Contract/Components/AccumulatorsStats/accumulators-stats-manual-modal.jsx b/packages/trader/src/Modules/Contract/Components/AccumulatorsStats/accumulators-stats-manual-modal.tsx similarity index 83% rename from packages/trader/src/Modules/Contract/Components/AccumulatorsStats/accumulators-stats-manual-modal.jsx rename to packages/trader/src/Modules/Contract/Components/AccumulatorsStats/accumulators-stats-manual-modal.tsx index d956577f37dd..d3737b2b6485 100644 --- a/packages/trader/src/Modules/Contract/Components/AccumulatorsStats/accumulators-stats-manual-modal.jsx +++ b/packages/trader/src/Modules/Contract/Components/AccumulatorsStats/accumulators-stats-manual-modal.tsx @@ -1,16 +1,29 @@ -import PropTypes from 'prop-types'; import React from 'react'; import { Icon, Loading, Modal, Text } from '@deriv/components'; import { localize } from '@deriv/translations'; import { getUrlBase, isMobile } from '@deriv/shared'; import 'Sass/app/modules/contract/accumulators-stats.scss'; -const AccumulatorsStatsManualModal = ({ icon_classname, is_dark_theme, is_manual_open, title, toggleManual }) => { +type TAccumulatorsStatsManualModal = { + icon_classname: string; + is_dark_theme?: boolean; + is_manual_open: boolean; + title: string; + toggleManual: () => void; +}; + +const AccumulatorsStatsManualModal = ({ + icon_classname, + is_dark_theme, + is_manual_open, + title, + toggleManual, +}: TAccumulatorsStatsManualModal) => { const [is_loading, setIsLoading] = React.useState(true); const is_mobile = isMobile(); // memoize file paths for videos and open the modal only after we get them const getVideoSource = React.useCallback( - extension => { + (extension: string) => { return getUrlBase( `/public/videos/accumulators_manual_${is_mobile ? 'mobile' : 'desktop'}${ is_dark_theme ? '_dark' : '' @@ -24,7 +37,13 @@ const AccumulatorsStatsManualModal = ({ icon_classname, is_dark_theme, is_manual return ( - + { +const AccumulatorsStats = observer(({ is_expandable = true }: TAccumulatorStats) => { const { ui } = useStore(); const { ticks_history_stats = {} } = useTraderStore(); const { is_dark_mode_on: is_dark_theme } = ui; @@ -26,10 +28,10 @@ const AccumulatorsStats = observer(({ is_expandable = true }) => { const [is_collapsed, setIsCollapsed] = React.useState(true); const [is_manual_open, setIsManualOpen] = React.useState(false); const widget_title = localize('Stats'); - const ticks_history = ticks_history_stats?.ticks_stayed_in || []; + const ticks_history = ticks_history_stats?.ticks_stayed_in ?? []; const history_text_size = isDesktop() || !is_collapsed ? 'xxs' : 'xxxs'; - const rows = ticks_history.reduce((acc, _el, index) => { + const rows = ticks_history.reduce((acc: number[][], _el, index) => { const desktop_row_size = is_collapsed ? ROW_SIZES.DESKTOP_COLLAPSED : ROW_SIZES.DESKTOP_EXPANDED; const mobile_row_size = is_collapsed ? ROW_SIZES.MOBILE_COLLAPSED : ROW_SIZES.MOBILE_EXPANDED; const row_size = isDesktop() ? desktop_row_size : mobile_row_size; @@ -60,7 +62,14 @@ const AccumulatorsStats = observer(({ is_expandable = true }) => { {!is_collapsed ? (
{localize('Number of ticks')}
) : ( - rows[0]?.map((el, i) => ) + rows[0]?.map((el, i) => ( + + )) )}
@@ -87,14 +96,11 @@ const AccumulatorsStats = observer(({ is_expandable = true }) => { icon={is_collapsed ? 'IcArrowUp' : 'IcArrowDown'} onClick={() => setIsCollapsed(!is_collapsed)} className='accordion-toggle-arrow' + data_testid='dt_accordion-toggle-arrow' /> )}
); }); -AccumulatorsStats.propTypes = { - is_expandable: PropTypes.bool, -}; - export default AccumulatorsStats; diff --git a/packages/trader/src/Modules/Contract/Components/AccumulatorsStats/expanded-ticks-history.tsx b/packages/trader/src/Modules/Contract/Components/AccumulatorsStats/expanded-ticks-history.tsx index 4cc7edf26952..79547ecd9e1e 100644 --- a/packages/trader/src/Modules/Contract/Components/AccumulatorsStats/expanded-ticks-history.tsx +++ b/packages/trader/src/Modules/Contract/Components/AccumulatorsStats/expanded-ticks-history.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import PropTypes from 'prop-types'; import { Text } from '@deriv/components'; import TicksHistoryCounter from './ticks-history-counter'; @@ -18,6 +17,7 @@ const ExpandedTicksHistory = ({ history_text_size, rows }: TExpandedTicksHistory > {row.map((counter, idx) => ( ); -ExpandedTicksHistory.propTypes = { - history_text_size: PropTypes.string, - rows: PropTypes.array, -}; - export default React.memo(ExpandedTicksHistory); diff --git a/packages/trader/src/Modules/Contract/Components/AccumulatorsStats/index.js b/packages/trader/src/Modules/Contract/Components/AccumulatorsStats/index.js index b514d9cb5570..a922d41b5515 100644 --- a/packages/trader/src/Modules/Contract/Components/AccumulatorsStats/index.js +++ b/packages/trader/src/Modules/Contract/Components/AccumulatorsStats/index.js @@ -1,3 +1,3 @@ -import AccumulatorsStats from './accumulators-stats.jsx'; +import AccumulatorsStats from './accumulators-stats'; export default AccumulatorsStats; diff --git a/packages/trader/src/Modules/Contract/Components/AccumulatorsStats/ticks-history-counter.tsx b/packages/trader/src/Modules/Contract/Components/AccumulatorsStats/ticks-history-counter.tsx index a776571c66d8..44c24314f2e9 100644 --- a/packages/trader/src/Modules/Contract/Components/AccumulatorsStats/ticks-history-counter.tsx +++ b/packages/trader/src/Modules/Contract/Components/AccumulatorsStats/ticks-history-counter.tsx @@ -3,22 +3,30 @@ import classNames from 'classnames'; type TTicksHistoryCounter = { has_progress_dots: boolean; + progress_dots_testid?: string; should_emphasize_last_counter?: boolean; + ticks_history_counter_testid?: string; value: number; }; -const TicksHistoryCounter = ({ has_progress_dots, value, should_emphasize_last_counter }: TTicksHistoryCounter) => { +const TicksHistoryCounter = ({ + has_progress_dots, + progress_dots_testid, + should_emphasize_last_counter, + ticks_history_counter_testid, + value, +}: TTicksHistoryCounter) => { const should_highlight_last_counter = should_emphasize_last_counter && has_progress_dots && value === 0; return (
{value} {has_progress_dots && ( -
+
{[1, 2, 3].map(dot => { return ; })} diff --git a/packages/trader/src/Modules/SmartChart/Components/Markers/__tests__/accumulators-chart-elements.spec.js b/packages/trader/src/Modules/SmartChart/Components/Markers/__tests__/accumulators-chart-elements.spec.tsx similarity index 74% rename from packages/trader/src/Modules/SmartChart/Components/Markers/__tests__/accumulators-chart-elements.spec.js rename to packages/trader/src/Modules/SmartChart/Components/Markers/__tests__/accumulators-chart-elements.spec.tsx index 44b75558ec6f..530ee9a0236a 100644 --- a/packages/trader/src/Modules/SmartChart/Components/Markers/__tests__/accumulators-chart-elements.spec.js +++ b/packages/trader/src/Modules/SmartChart/Components/Markers/__tests__/accumulators-chart-elements.spec.tsx @@ -5,19 +5,30 @@ import AccumulatorsChartElements from '../accumulators-chart-elements'; jest.mock('App/Components/Elements/PositionsDrawer/helpers/positions-helper.js', () => ({ filterByContractType: jest.fn(() => true), })); -jest.mock('../accumulators-profit-loss-tooltip.jsx', () => () =>
AccumulatorsProfitLossTooltip
); -jest.mock('../marker.jsx', () => () =>
Spot-emphasizing ChartMarker
); +jest.mock('../accumulators-profit-loss-tooltip', () => jest.fn(() =>
AccumulatorsProfitLossTooltip
)); +jest.mock('../marker.jsx', () => jest.fn(() =>
Spot-emphasizing ChartMarker
)); describe('AccumulatorsChartElements', () => { const mock_props = { all_positions: [ - { contract_info: { underlying: 'test symbol', contract_type: 'ACCU', entry_spot: 9454.1, contract_id: 1 } }, + { + contract_info: { + underlying: 'test symbol', + contract_type: 'ACCU', + entry_spot: 9454.1, + contract_id: 1, + shortcode: 'test', + profit: 100, + }, + }, { contract_info: { underlying: 'test symbol', contract_type: 'ACCU', entry_spot: 9467.78, contract_id: 2, + shortcode: 'test', + profit: 120, }, }, ], @@ -30,7 +41,6 @@ describe('AccumulatorsChartElements', () => { it('should render AccumulatorsChartElements without Spot-emphasizing ChartMarker', () => { render(); - const tooltip_arr = screen.getAllByText('AccumulatorsProfitLossTooltip'); expect(tooltip_arr.length).toBe(2); expect(screen.queryByText('Spot-emphasizing ChartMarker')).not.toBeInTheDocument(); diff --git a/packages/trader/src/Modules/SmartChart/Components/Markers/__tests__/accumulators-profit-loss-text.spec.js b/packages/trader/src/Modules/SmartChart/Components/Markers/__tests__/accumulators-profit-loss-text.spec.tsx similarity index 100% rename from packages/trader/src/Modules/SmartChart/Components/Markers/__tests__/accumulators-profit-loss-text.spec.js rename to packages/trader/src/Modules/SmartChart/Components/Markers/__tests__/accumulators-profit-loss-text.spec.tsx diff --git a/packages/trader/src/Modules/SmartChart/Components/Markers/__tests__/accumulators-profit-loss-tooltip.spec.js b/packages/trader/src/Modules/SmartChart/Components/Markers/__tests__/accumulators-profit-loss-tooltip.spec.tsx similarity index 84% rename from packages/trader/src/Modules/SmartChart/Components/Markers/__tests__/accumulators-profit-loss-tooltip.spec.js rename to packages/trader/src/Modules/SmartChart/Components/Markers/__tests__/accumulators-profit-loss-tooltip.spec.tsx index 45039fe21a14..65202bf289c7 100644 --- a/packages/trader/src/Modules/SmartChart/Components/Markers/__tests__/accumulators-profit-loss-tooltip.spec.js +++ b/packages/trader/src/Modules/SmartChart/Components/Markers/__tests__/accumulators-profit-loss-tooltip.spec.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; -import AccumulatorsProfitLossTooltip from '../accumulators-profit-loss-tooltip.jsx'; +import AccumulatorsProfitLossTooltip from '../accumulators-profit-loss-tooltip'; jest.mock('Modules/SmartChart', () => ({ ...jest.requireActual('Modules/SmartChart'), @@ -9,7 +9,7 @@ jest.mock('Modules/SmartChart', () => ({ jest.mock('../accumulators-profit-loss-text', () => () => 'AccumulatorsProfitLossText'); describe('AccumulatorsProfitLossTooltip', () => { - const props = { + const props: React.ComponentProps = { className: 'profit-loss-tooltip', currency: 'USD', current_spot: 6468.95, @@ -47,9 +47,7 @@ describe('AccumulatorsProfitLossTooltip', () => { const spot_el = screen.getByTestId('dt_accumulator_tooltip_spot'); expect(spot_el).toBeInTheDocument(); expect(spot_el).toHaveClass('profit-loss-tooltip__spot-circle'); - expect(spot_el.closest('div')).toHaveClass('profit-loss-tooltip'); expect(screen.getByText(profit_text)).toBeInTheDocument(); - expect(screen.getByText(profit_text).closest('div')).not.toHaveClass('profit-loss-tooltip__content-exit'); - expect(screen.getByText(profit_text).closest('div')).toHaveClass('profit-loss-tooltip__content-enter-active'); + expect(screen.getByText('0.15 USD')).toBeInTheDocument(); }); }); diff --git a/packages/trader/src/Modules/SmartChart/Components/Markers/accumulators-chart-elements.jsx b/packages/trader/src/Modules/SmartChart/Components/Markers/accumulators-chart-elements.tsx similarity index 68% rename from packages/trader/src/Modules/SmartChart/Components/Markers/accumulators-chart-elements.jsx rename to packages/trader/src/Modules/SmartChart/Components/Markers/accumulators-chart-elements.tsx index 77d9763e5e88..8ec25b78dd94 100644 --- a/packages/trader/src/Modules/SmartChart/Components/Markers/accumulators-chart-elements.jsx +++ b/packages/trader/src/Modules/SmartChart/Components/Markers/accumulators-chart-elements.tsx @@ -1,9 +1,26 @@ import { filterByContractType } from 'App/Components/Elements/PositionsDrawer/helpers/positions-helper.js'; -import PropTypes from 'prop-types'; import React from 'react'; -import AccumulatorsProfitLossTooltip from './accumulators-profit-loss-tooltip.jsx'; +import AccumulatorsProfitLossTooltip from './accumulators-profit-loss-tooltip'; +import { ProposalOpenContract } from '@deriv/api-types'; import ChartMarker from './marker.jsx'; +type TPositions = { + contract_info: Omit< + React.ComponentProps, + 'className' | 'alignment' | 'should_show_profit_text' + > & + Required>; +}; + +type TAccumulatorsChartElements = { + all_positions: TPositions[]; + current_spot?: number | null; + current_spot_time: number; + has_crossed_accu_barriers: boolean; + should_show_profit_text: React.ComponentProps['should_show_profit_text']; + symbol: string; +}; + const AccumulatorsChartElements = ({ all_positions, current_spot, @@ -11,7 +28,7 @@ const AccumulatorsChartElements = ({ has_crossed_accu_barriers, should_show_profit_text, symbol, -}) => { +}: TAccumulatorsChartElements) => { const accumulators_positions = all_positions.filter( ({ contract_info }) => contract_info && symbol === contract_info.underlying && filterByContractType(contract_info, 'accumulator') @@ -41,13 +58,4 @@ const AccumulatorsChartElements = ({ ); }; -AccumulatorsChartElements.propTypes = { - all_positions: PropTypes.array, - current_spot: PropTypes.number, - current_spot_time: PropTypes.number, - has_crossed_accu_barriers: PropTypes.bool, - should_show_profit_text: PropTypes.bool, - symbol: PropTypes.string, -}; - export default React.memo(AccumulatorsChartElements); diff --git a/packages/trader/src/Modules/SmartChart/Components/Markers/accumulators-profit-loss-text.jsx b/packages/trader/src/Modules/SmartChart/Components/Markers/accumulators-profit-loss-text.tsx similarity index 73% rename from packages/trader/src/Modules/SmartChart/Components/Markers/accumulators-profit-loss-text.jsx rename to packages/trader/src/Modules/SmartChart/Components/Markers/accumulators-profit-loss-text.tsx index 4ab9195b8a8a..8da3cdc21ce3 100644 --- a/packages/trader/src/Modules/SmartChart/Components/Markers/accumulators-profit-loss-text.jsx +++ b/packages/trader/src/Modules/SmartChart/Components/Markers/accumulators-profit-loss-text.tsx @@ -1,15 +1,23 @@ -import PropTypes from 'prop-types'; import React from 'react'; import { Text } from '@deriv/components'; import { formatMoney, getCurrencyDisplayCode, isMobile } from '@deriv/shared'; import { FastMarker } from 'Modules/SmartChart'; import classNames from 'classnames'; +import { TRef } from './accumulators-profit-loss-tooltip'; +import { ProposalOpenContract } from '@deriv/api-types'; + +type TProposalOpenContractProfit = Required>; + +type TAccumulatorsProfitLossText = Pick & + TProposalOpenContractProfit & { + className?: string; + }; const ACTIONS = { INC: 'increment', DEC: 'decrement', ADD10: 'add10', -}; +} as const; const AccumulatorsProfitLossText = ({ current_spot, @@ -17,25 +25,30 @@ const AccumulatorsProfitLossText = ({ currency, className = 'sc-accumulators-profit-loss-text', profit, -}) => { +}: TAccumulatorsProfitLossText) => { const [is_fading_in, setIsFadingIn] = React.useState(false); const [is_sliding, setIsSliding] = React.useState(false); - const formatted_profit = formatMoney(currency, profit, true, 0, 0); - const prev_profit = React.useRef(formatted_profit); - const prev_profit_tenth = +prev_profit.current?.split('.')[1][0]; + const formatted_profit = formatMoney(currency ?? '', profit, true, 0, 0); + const prev_profit = React.useRef(+formatted_profit); + const prev_profit_tenth = +prev_profit.current?.toFixed(2).split('.')[1][0]; const [current_profit_tenth, setCurrentProfitTenth] = React.useState(prev_profit_tenth); - const profit_tenth_ref = React.useRef(); - const interval_id_ref = React.useRef(null); - const fading_in_timeout_id = React.useRef(); - const sliding_timeout_id = React.useRef(); + const profit_tenth_ref = React.useRef(0); + const interval_id_ref = React.useRef>(); + const fading_in_timeout_id = React.useRef>(); + const sliding_timeout_id = React.useRef>(); const profit_portions_array = formatted_profit.split('.'); - const profit_whole_number = profit_portions_array[0]; + const profit_whole_number = +profit_portions_array[0]; const profit_tenth = +profit_portions_array[1][0]; const profit_hundredths = +profit_portions_array[1].slice(1); const won = profit >= 0; const sign = profit > 0 ? '+' : ''; - const runThroughTenthDigit = (action, interval_ms, start, end) => { + const runThroughTenthDigit = ( + action: typeof ACTIONS[keyof typeof ACTIONS], + interval_ms: number, + start: number, + end: number + ) => { clearInterval(interval_id_ref.current); const interval_id = setInterval(() => { if (action === ACTIONS.INC && profit_tenth_ref.current < end) { @@ -66,7 +79,7 @@ const AccumulatorsProfitLossText = ({ }, 300); } if (profit !== 0) { - const updateTenth = (start, end) => { + const updateTenth = (start: number, end: number) => { const delta = Math.abs(end - start); profit_tenth_ref.current = start; if (start < end) { @@ -86,16 +99,18 @@ const AccumulatorsProfitLossText = ({ }; }, [profit, prev_profit_tenth, profit_tenth]); - const onRef = ref => { + const onRef = (ref: TRef | null): void => { if (ref) { if (!current_spot) { // this call will hide the marker: ref.setPosition({ epoch: null, price: null }); } - ref.setPosition({ - epoch: +current_spot_time, - price: +current_spot, - }); + if (current_spot && current_spot_time) { + ref.setPosition({ + epoch: +current_spot_time, + price: +current_spot, + }); + } } }; @@ -121,12 +136,4 @@ const AccumulatorsProfitLossText = ({ ); }; -AccumulatorsProfitLossText.propTypes = { - className: PropTypes.string, - currency: PropTypes.string, - current_spot: PropTypes.number, - current_spot_time: PropTypes.number, - profit: PropTypes.number, -}; - export default React.memo(AccumulatorsProfitLossText); diff --git a/packages/trader/src/Modules/SmartChart/Components/Markers/accumulators-profit-loss-tooltip.jsx b/packages/trader/src/Modules/SmartChart/Components/Markers/accumulators-profit-loss-tooltip.tsx similarity index 77% rename from packages/trader/src/Modules/SmartChart/Components/Markers/accumulators-profit-loss-tooltip.jsx rename to packages/trader/src/Modules/SmartChart/Components/Markers/accumulators-profit-loss-tooltip.tsx index c9afec06cad8..a8a685ae56d6 100644 --- a/packages/trader/src/Modules/SmartChart/Components/Markers/accumulators-profit-loss-tooltip.jsx +++ b/packages/trader/src/Modules/SmartChart/Components/Markers/accumulators-profit-loss-tooltip.tsx @@ -1,4 +1,3 @@ -import PropTypes from 'prop-types'; import React from 'react'; import classNames from 'classnames'; import { CSSTransition } from 'react-transition-group'; @@ -6,13 +5,31 @@ import { Money, Text } from '@deriv/components'; import { localize } from '@deriv/translations'; import { FastMarker } from 'Modules/SmartChart'; import AccumulatorsProfitLossText from './accumulators-profit-loss-text'; +import { ProposalOpenContract } from '@deriv/api-types'; import { isMobile } from '@deriv/shared'; +type TPickProposalOpenContract = Pick< + ProposalOpenContract, + 'current_spot' | 'current_spot_time' | 'currency' | 'exit_tick' | 'exit_tick_time' | 'high_barrier' | 'is_sold' +>; + +type TAccumulatorsProfitLossText = React.ComponentProps; + +type TAccumulatorsProfitLossTooltip = { + alignment?: string; + should_show_profit_text?: boolean; +} & TPickProposalOpenContract & + TAccumulatorsProfitLossText; + +export type TRef = { + setPosition: (position: { epoch: number | null; price: number | null }) => void; +}; + const AccumulatorsProfitLossTooltip = ({ alignment = 'right', + className = 'sc-accumulators-profit-loss-tooltip', current_spot, current_spot_time, - className = 'sc-accumulators-profit-loss-tooltip', currency, exit_tick, exit_tick_time, @@ -20,10 +37,10 @@ const AccumulatorsProfitLossTooltip = ({ is_sold, profit, should_show_profit_text, -}) => { +}: TAccumulatorsProfitLossTooltip) => { const [is_tooltip_open, setIsTooltipOpen] = React.useState(false); const won = profit >= 0; - const tooltip_timeout = React.useRef(null); + const tooltip_timeout = React.useRef>(); React.useEffect(() => { return () => { @@ -38,7 +55,7 @@ const AccumulatorsProfitLossTooltip = ({ } }, [is_sold]); - const onCloseDelayed = duration => + const onCloseDelayed = (duration: number) => setTimeout(() => { setIsTooltipOpen(false); }, duration); @@ -55,16 +72,18 @@ const AccumulatorsProfitLossTooltip = ({ : ['top', 'bottom'].find(el => el !== alignment); }, [alignment]); - const onRef = ref => { + const onRef = (ref: TRef | null): void => { if (ref) { if (!exit_tick) { // this call will hide the marker: ref.setPosition({ epoch: null, price: null }); } - ref.setPosition({ - epoch: +exit_tick_time, - price: +exit_tick, - }); + if (exit_tick_time && exit_tick) { + ref.setPosition({ + epoch: +exit_tick_time, + price: +exit_tick, + }); + } } }; @@ -109,18 +128,4 @@ const AccumulatorsProfitLossTooltip = ({ ) : null; }; -AccumulatorsProfitLossTooltip.propTypes = { - alignment: PropTypes.string, - current_spot: PropTypes.number, - current_spot_time: PropTypes.number, - className: PropTypes.string, - currency: PropTypes.string, - exit_tick: PropTypes.number, - exit_tick_time: PropTypes.number, - high_barrier: PropTypes.string, - is_sold: PropTypes.number, - profit: PropTypes.number, - should_show_profit_text: PropTypes.bool, -}; - export default React.memo(AccumulatorsProfitLossTooltip); diff --git a/packages/trader/src/Modules/Trading/Components/Form/RadioGroupWithInfoMobile/__tests__/radio-group-with-info-mobile.spec.js b/packages/trader/src/Modules/Trading/Components/Form/RadioGroupWithInfoMobile/__tests__/radio-group-with-info-mobile.spec.js deleted file mode 100644 index 1c3dbda1e238..000000000000 --- a/packages/trader/src/Modules/Trading/Components/Form/RadioGroupWithInfoMobile/__tests__/radio-group-with-info-mobile.spec.js +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react'; -import { render, screen } from '@testing-library/react'; -import RadioGroupWithInfoMobile from '../radio-group-with-info-mobile'; - -describe('RadioGroupWithInfoMobile', () => { - const props = { - items_list: [ - { name: 'test name 1', value: 'test value 1' }, - { name: 'test name 2', value: 'test value 2' }, - { name: 'test name 3', value: 'test value 3' }, - ], - contract_name: 'test_contract', - current_value_object: { name: 'test name 2', value: 'test value 2' }, - onChange: jest.fn(), - info: 'test info message', - toggleModal: jest.fn(), - }; - - it('should render AccumulatorsProfitLossText', async () => { - render(); - - const radio_options_arr_1 = screen.getAllByRole('radio'); - expect(radio_options_arr_1.length).toBe(3); - expect(radio_options_arr_1[1].closest('label')).toHaveClass('dc-radio-group__item--selected'); - expect(radio_options_arr_1[0].closest('label')).not.toHaveClass('dc-radio-group__item--selected'); - expect(radio_options_arr_1[2].closest('label')).not.toHaveClass('dc-radio-group__item--selected'); - expect(radio_options_arr_1[1].value).toBe('test value 2'); - }); -}); diff --git a/packages/trader/src/Modules/Trading/Components/Form/RadioGroupWithInfoMobile/__tests__/radio-group-with-info-mobile.spec.tsx b/packages/trader/src/Modules/Trading/Components/Form/RadioGroupWithInfoMobile/__tests__/radio-group-with-info-mobile.spec.tsx new file mode 100644 index 000000000000..274601f0c2a2 --- /dev/null +++ b/packages/trader/src/Modules/Trading/Components/Form/RadioGroupWithInfoMobile/__tests__/radio-group-with-info-mobile.spec.tsx @@ -0,0 +1,54 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import RadioGroupWithInfoMobile from '../radio-group-with-info-mobile'; +import userEvent from '@testing-library/user-event'; + +describe('RadioGroupWithInfoMobile', () => { + const props: React.ComponentProps = { + items_list: [ + { text: 'test name 1', value: 1 }, + { text: 'test name 2', value: 2 }, + { text: 'test name 3', value: 3 }, + ], + contract_name: 'test_contract', + current_value_object: { name: 'test name 2', value: 2 }, + onChange: jest.fn(), + info: 'test info message', + toggleModal: jest.fn(), + }; + + it('should render all radio buttons with their respective text and value', () => { + render(); + + const radio_options_arr_1 = screen.getAllByRole('radio'); + expect(radio_options_arr_1.length).toBe(3); + expect(radio_options_arr_1[0].value).toBe('1'); + expect(radio_options_arr_1[1].value).toBe('2'); + expect(radio_options_arr_1[2].value).toBe('3'); + expect(screen.getByText(/test name 1/i)).toBeInTheDocument(); + expect(screen.getByText(/test name 2/i)).toBeInTheDocument(); + expect(screen.getByText(/test name 3/i)).toBeInTheDocument(); + expect(radio_options_arr_1[1].checked).toBeTruthy(); + expect(radio_options_arr_1[0].checked).toBeFalsy(); + expect(radio_options_arr_1[2].checked).toBeFalsy(); + }); + it('second radio option should be selected', () => { + render(); + + const radio_options_arr_1 = screen.getAllByRole('radio'); + expect(radio_options_arr_1[1].checked).toBeTruthy(); + expect(radio_options_arr_1[0].checked).toBeFalsy(); + expect(radio_options_arr_1[2].checked).toBeFalsy(); + }); + it('first radio option should be selected when user selects and modal should be toggled after clicking', () => { + render(); + + const radio_options_arr_1 = screen.getAllByRole('radio'); + userEvent.click(radio_options_arr_1[0]); + expect(props.onChange).toHaveBeenCalled(); + expect(props.toggleModal).toHaveBeenCalled(); + expect(radio_options_arr_1[0].checked).toBeTruthy(); + expect(radio_options_arr_1[1].checked).toBeFalsy(); + expect(radio_options_arr_1[2].checked).toBeFalsy(); + }); +}); diff --git a/packages/trader/src/Modules/Trading/Components/Form/RadioGroupWithInfoMobile/index.js b/packages/trader/src/Modules/Trading/Components/Form/RadioGroupWithInfoMobile/index.js index ae969f050bbb..5c490e3ee24b 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/RadioGroupWithInfoMobile/index.js +++ b/packages/trader/src/Modules/Trading/Components/Form/RadioGroupWithInfoMobile/index.js @@ -1,3 +1,3 @@ -import RadioGroupWithInfoMobile from './radio-group-with-info-mobile.jsx'; +import RadioGroupWithInfoMobile from './radio-group-with-info-mobile'; export default RadioGroupWithInfoMobile; diff --git a/packages/trader/src/Modules/Trading/Components/Form/RadioGroupWithInfoMobile/radio-group-with-info-mobile.jsx b/packages/trader/src/Modules/Trading/Components/Form/RadioGroupWithInfoMobile/radio-group-with-info-mobile.tsx similarity index 62% rename from packages/trader/src/Modules/Trading/Components/Form/RadioGroupWithInfoMobile/radio-group-with-info-mobile.jsx rename to packages/trader/src/Modules/Trading/Components/Form/RadioGroupWithInfoMobile/radio-group-with-info-mobile.tsx index 8d8c45345722..8e135beb16b3 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/RadioGroupWithInfoMobile/radio-group-with-info-mobile.jsx +++ b/packages/trader/src/Modules/Trading/Components/Form/RadioGroupWithInfoMobile/radio-group-with-info-mobile.tsx @@ -1,6 +1,30 @@ -import React from 'react'; +import React, { ChangeEvent } from 'react'; import { RadioGroup, Popover } from '@deriv/components'; +type TItemsList = { + text: string; + value: number; +}; + +type TRadioGroupWithInfoMobile = { + items_list?: TItemsList[]; + contract_name: string; + current_value_object: { + name: string; + value: number; + }; + onChange: (event: { + target: { + name: string; + value: number; + }; + }) => void; + info: React.ComponentProps['message']; + is_tooltip_disabled?: boolean; + popover_alignment?: React.ComponentProps['alignment']; + toggleModal: () => void; +}; + const RadioGroupWithInfoMobile = ({ items_list, contract_name, @@ -10,8 +34,8 @@ const RadioGroupWithInfoMobile = ({ is_tooltip_disabled = false, popover_alignment = 'right', toggleModal, -}) => { - const onValueChange = e => { +}: TRadioGroupWithInfoMobile) => { + const onValueChange = (e: ChangeEvent) => { onChange({ target: { name: current_value_object.name, @@ -30,7 +54,7 @@ const RadioGroupWithInfoMobile = ({ icon='info' id={`dt_${contract_name}-stake__tooltip`} is_bubble_hover_enabled - zIndex={9999} + zIndex='9999' message={info} />
@@ -40,8 +64,8 @@ const RadioGroupWithInfoMobile = ({ selected={!Number.isNaN(current_value_object.value) ? current_value_object.value?.toString() : ''} onToggle={onValueChange} > - {items_list.map(({ text, value }) => ( - + {items_list?.map(({ text, value }) => ( + ))} diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Accumulator/__tests__/accumulator.spec.js b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Accumulator/__tests__/accumulator.spec.tsx similarity index 75% rename from packages/trader/src/Modules/Trading/Components/Form/TradeParams/Accumulator/__tests__/accumulator.spec.js rename to packages/trader/src/Modules/Trading/Components/Form/TradeParams/Accumulator/__tests__/accumulator.spec.tsx index 68eba184dc93..65fb93b50a5d 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Accumulator/__tests__/accumulator.spec.js +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Accumulator/__tests__/accumulator.spec.tsx @@ -27,7 +27,7 @@ describe('Accumulator', () => { expect(screen.getByText('3%')).toBeInTheDocument(); expect(screen.getByText('4%')).toBeInTheDocument(); expect(screen.getByText('5%')).toBeInTheDocument(); - expect(screen.getByText('1%').getAttribute('class')).toContain('--selected'); + expect(screen.getByText('1%')).toHaveClass('number-selector__selection--selected'); }); it('3% growth_rate should be selected when 0.03 is a currently selected and stored growth_rate value', () => { @@ -37,8 +37,18 @@ describe('Accumulator', () => { {children} ), }); - expect(screen.getByText('3%').getAttribute('class')).toContain('--selected'); - expect(screen.getByText('1%').getAttribute('class')).not.toContain('--selected'); + expect(screen.getByText('3%')).toHaveClass('number-selector__selection--selected'); + expect(screen.getByText('1%')).not.toHaveClass('number-selector__selection--selected'); + }); + + it('component should return null if accumulator_range_list is empty', () => { + mock_connect_props.modules.trade.accumulator_range_list = []; + const { container } = render(, { + wrapper: ({ children }) => ( + {children} + ), + }); + expect(container).toBeEmptyDOMElement(); }); it('should not render the component if accumulator_range_list is empty', () => { diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Accumulator/__tests__/accumulators-info-display.spec.js b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Accumulator/__tests__/accumulators-info-display.spec.tsx similarity index 100% rename from packages/trader/src/Modules/Trading/Components/Form/TradeParams/Accumulator/__tests__/accumulators-info-display.spec.js rename to packages/trader/src/Modules/Trading/Components/Form/TradeParams/Accumulator/__tests__/accumulators-info-display.spec.tsx diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Accumulator/accumulator.jsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Accumulator/accumulator.tsx similarity index 92% rename from packages/trader/src/Modules/Trading/Components/Form/TradeParams/Accumulator/accumulator.jsx rename to packages/trader/src/Modules/Trading/Components/Form/TradeParams/Accumulator/accumulator.tsx index fa2dc8d861bc..8fd0b337f918 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Accumulator/accumulator.jsx +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Accumulator/accumulator.tsx @@ -19,7 +19,7 @@ const Accumulator = observer(() => { } = useTraderStore(); // splitting accumulator_range_list into rows containing 5 values each: - const arr_arr_numbers = accumulator_range_list.reduce((acc, _el, index) => { + const arr_arr_numbers: number[][] | undefined = accumulator_range_list?.reduce((acc: number[][], _el, index) => { if (index % 5 === 0) { acc.push(accumulator_range_list.slice(index, index + 5)); } @@ -27,7 +27,7 @@ const Accumulator = observer(() => { }, []); const has_error_or_not_loaded = proposal_info?.ACCU?.has_error || !proposal_info?.ACCU?.id || isEmptyObject(proposal_info); - if (!accumulator_range_list.length) return null; + if (!accumulator_range_list?.length) return null; return (
{ +type TAccumulatorsAmountMobile = { + is_nativepicker: boolean; +}; + +const AccumulatorsAmountMobile = observer(({ is_nativepicker }: TAccumulatorsAmountMobile) => { const { ui, client } = useStore(); const { current_focus, setCurrentFocus } = ui; const { is_single_currency } = client; @@ -46,8 +49,4 @@ const AccumulatorsAmountMobile = observer(({ is_nativepicker }) => { ); }); -AccumulatorsAmountMobile.propTypes = { - is_nativepicker: PropTypes.bool, -}; - export default AccumulatorsAmountMobile; diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Accumulator/accumulators-info-display.jsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Accumulator/accumulators-info-display.tsx similarity index 100% rename from packages/trader/src/Modules/Trading/Components/Form/TradeParams/Accumulator/accumulators-info-display.jsx rename to packages/trader/src/Modules/Trading/Components/Form/TradeParams/Accumulator/accumulators-info-display.tsx diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Multiplier/__tests__/widgets.spec.jsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Multiplier/__tests__/widgets.spec.jsx index 1b1b3a8bbc18..e61b5e3069ce 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Multiplier/__tests__/widgets.spec.jsx +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Multiplier/__tests__/widgets.spec.jsx @@ -15,7 +15,7 @@ const default_mock_store = { }, }; -jest.mock('Modules/Trading/Containers/radio-group-options-modal.jsx', () => +jest.mock('Modules/Trading/Containers/radio-group-options-modal', () => jest.fn(prop => (
RadioGroupOptionsModal component diff --git a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Multiplier/widgets.jsx b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Multiplier/widgets.jsx index b00d980223f8..9aec863c016d 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Multiplier/widgets.jsx +++ b/packages/trader/src/Modules/Trading/Components/Form/TradeParams/Multiplier/widgets.jsx @@ -4,7 +4,7 @@ import { Money, Text, Popover } from '@deriv/components'; import { useTraderStore } from 'Stores/useTraderStores'; import { observer } from '@deriv/stores'; import MultiplierAmountModal from 'Modules/Trading/Containers/Multiplier/multiplier-amount-modal.jsx'; -import RadioGroupOptionsModal from 'Modules/Trading/Containers/radio-group-options-modal.jsx'; +import RadioGroupOptionsModal from 'Modules/Trading/Containers/radio-group-options-modal'; import MultipliersExpiration from 'Modules/Trading/Components/Form/TradeParams/Multiplier/expiration.jsx'; import MultipliersExpirationModal from 'Modules/Trading/Components/Form/TradeParams/Multiplier/expiration-modal.jsx'; import MultipliersInfo from 'Modules/Trading/Components/Form/TradeParams/Multiplier/info.jsx'; diff --git a/packages/trader/src/Modules/Trading/Components/Form/screen-small.jsx b/packages/trader/src/Modules/Trading/Components/Form/screen-small.jsx index b8eabe84d4ff..a322cf443648 100644 --- a/packages/trader/src/Modules/Trading/Components/Form/screen-small.jsx +++ b/packages/trader/src/Modules/Trading/Components/Form/screen-small.jsx @@ -12,8 +12,8 @@ import { AccumulatorOptionsWidget, MultiplierOptionsWidget, } from 'Modules/Trading/Components/Form/TradeParams/Multiplier/widgets.jsx'; -import AccumulatorsAmountMobile from 'Modules/Trading/Components/Form/TradeParams/Accumulator/accumulators-amount-mobile.jsx'; -import AccumulatorsInfoDisplay from 'Modules/Trading/Components/Form/TradeParams/Accumulator/accumulators-info-display.jsx'; +import AccumulatorsAmountMobile from 'Modules/Trading/Components/Form/TradeParams/Accumulator/accumulators-amount-mobile'; +import AccumulatorsInfoDisplay from 'Modules/Trading/Components/Form/TradeParams/Accumulator/accumulators-info-display'; import { BarrierMobile, LastDigitMobile } from 'Modules/Trading/Containers/trade-params-mobile.jsx'; import ContractType from 'Modules/Trading/Containers/contract-type'; import MobileWidget from 'Modules/Trading/Components/Elements/mobile-widget.jsx'; diff --git a/packages/trader/src/Modules/Trading/Containers/__tests__/radio-group-options-modal.spec.tsx b/packages/trader/src/Modules/Trading/Containers/__tests__/radio-group-options-modal.spec.tsx new file mode 100644 index 000000000000..cc8a706eed73 --- /dev/null +++ b/packages/trader/src/Modules/Trading/Containers/__tests__/radio-group-options-modal.spec.tsx @@ -0,0 +1,80 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import RadioGroupOptionsModal from '../radio-group-options-modal'; +import { mockStore } from '@deriv/stores'; +import TraderProviders from '../../../../trader-providers'; + +jest.mock('../Multiplier/multiplier-options.jsx', () => jest.fn(() => 'mockedMultiplierOptions')); +jest.mock('Modules/Trading/Components/Form/RadioGroupWithInfoMobile', () => + jest.fn(() => 'MockedRadioGroupWithInfoMobile') +); + +type TModal = React.FC<{ + children: React.ReactNode; + is_open: boolean; + toggleModal: () => void; + modal_title: string; +}>; + +jest.mock('@deriv/components', () => { + const original_module = jest.requireActual('@deriv/components'); + const Modal: TModal = jest.fn(({ children, is_open, toggleModal, modal_title }) => { + if (is_open) + return ( +
+

{modal_title}

+ IcCross + {children} +
+ ); + + return null; + }); + + return { + ...original_module, + Modal, + }; +}); + +const mocked_props = { + is_open: true, + toggleModal: jest.fn(), + modal_title: 'Multiplier', +}; + +const mock_connect_props = { + modules: { + trade: { + accumulator_range_list: [0.01, 0.02, 0.03, 0.04, 0.05], + growth_rate: 1, + onChange: jest.fn(), + proposal_info: { + ACCU: { + has_error: false, + id: false, + }, + }, + }, + }, +}; + +describe('', () => { + it('should render mockedMultiplierOptions when modal_title is Multiplier', () => { + render(, { + wrapper: ({ children }) => ( + {children} + ), + }); + expect(screen.getByText(/mockedmultiplieroptions/i)).toBeInTheDocument(); + }); + it('should render something when modal_title is not Multiplier', () => { + mocked_props.modal_title = 'accumulator'; + render(, { + wrapper: ({ children }) => ( + {children} + ), + }); + expect(screen.getByText(/mockedradiogroupwithinfomobile/i)).toBeInTheDocument(); + }); +}); diff --git a/packages/trader/src/Modules/Trading/Containers/radio-group-options-modal.jsx b/packages/trader/src/Modules/Trading/Containers/radio-group-options-modal.tsx similarity index 88% rename from packages/trader/src/Modules/Trading/Containers/radio-group-options-modal.jsx rename to packages/trader/src/Modules/Trading/Containers/radio-group-options-modal.tsx index d3c294d48fe3..5fc7a59d04fe 100644 --- a/packages/trader/src/Modules/Trading/Containers/radio-group-options-modal.jsx +++ b/packages/trader/src/Modules/Trading/Containers/radio-group-options-modal.tsx @@ -5,13 +5,16 @@ import { useTraderStore } from 'Stores/useTraderStores'; import { getGrowthRatePercentage, getTickSizeBarrierPercentage, isEmptyObject } from '@deriv/shared'; import MultiplierOptions from 'Modules/Trading/Containers/Multiplier/multiplier-options.jsx'; import RadioGroupWithInfoMobile from 'Modules/Trading/Components/Form/RadioGroupWithInfoMobile'; -import { observer, useStore } from '@deriv/stores'; +import { observer } from '@deriv/stores'; -const RadioGroupOptionsModal = observer(({ is_open, modal_title, toggleModal }) => { +type TRadioGroupOptionsModal = { + is_open: boolean; + modal_title: string; + toggleModal: () => void; +}; + +const RadioGroupOptionsModal = observer(({ is_open, modal_title, toggleModal }: TRadioGroupOptionsModal) => { const { accumulator_range_list, growth_rate, onChange, tick_size_barrier, proposal_info } = useTraderStore(); - const { - ui: { enableApp, disableApp }, - } = useStore(); // Fix to prevent iOS from zooming in erratically on quick taps usePreventIOSZoom(); @@ -23,11 +26,9 @@ const RadioGroupOptionsModal = observer(({ is_open, modal_title, toggleModal }) {modal_title === localize('Multiplier') ? ( + // @ts-expect-error should be gone after MultiplierOptions is converted to typescript ) : ( ({ + items_list={accumulator_range_list?.map(value => ({ text: `${getGrowthRatePercentage(value)}%`, value, }))} diff --git a/packages/trader/src/Modules/Trading/Containers/trade-params.jsx b/packages/trader/src/Modules/Trading/Containers/trade-params.jsx index 344f8f55bceb..c3f7ec38d983 100644 --- a/packages/trader/src/Modules/Trading/Containers/trade-params.jsx +++ b/packages/trader/src/Modules/Trading/Containers/trade-params.jsx @@ -7,11 +7,11 @@ import BarrierSelector from 'Modules/Trading/Components/Form/TradeParams/Turbos/ import Duration from 'Modules/Trading/Components/Form/TradeParams/Duration'; import LastDigit from 'Modules/Trading/Components/Form/TradeParams/last-digit.jsx'; import CancelDeal from 'Modules/Trading/Components/Form/TradeParams/Multiplier/cancel-deal.jsx'; -import Accumulator from 'Modules/Trading/Components/Form/TradeParams/Accumulator/accumulator.jsx'; +import Accumulator from 'Modules/Trading/Components/Form/TradeParams/Accumulator/accumulator'; import StopLoss from 'Modules/Trading/Components/Form/TradeParams/Multiplier/stop-loss.jsx'; import TakeProfit from 'Modules/Trading/Components/Form/TradeParams/Multiplier/take-profit.jsx'; import Expiration from 'Modules/Trading/Components/Form/TradeParams/Multiplier/expiration.jsx'; -import AccumulatorsInfoDisplay from 'Modules/Trading/Components/Form/TradeParams/Accumulator/accumulators-info-display.jsx'; +import AccumulatorsInfoDisplay from 'Modules/Trading/Components/Form/TradeParams/Accumulator/accumulators-info-display'; import Strike from 'Modules/Trading/Components/Form/TradeParams/strike.jsx'; import TradeTypeTabs from 'Modules/Trading/Components/Form/TradeParams/trade-type-tabs'; import { observer } from '@deriv/stores'; diff --git a/packages/trader/src/Modules/Trading/Containers/trade.jsx b/packages/trader/src/Modules/Trading/Containers/trade.jsx index e9fb3433109b..c01c0eed96ed 100644 --- a/packages/trader/src/Modules/Trading/Containers/trade.jsx +++ b/packages/trader/src/Modules/Trading/Containers/trade.jsx @@ -9,7 +9,7 @@ import Test from './test.jsx'; import { ChartBottomWidgets, ChartTopWidgets, DigitsWidget } from './chart-widgets.jsx'; import FormLayout from '../Components/Form/form-layout'; import AllMarkers from '../../SmartChart/Components/all-markers.jsx'; -import AccumulatorsChartElements from '../../SmartChart/Components/Markers/accumulators-chart-elements.jsx'; +import AccumulatorsChartElements from '../../SmartChart/Components/Markers/accumulators-chart-elements'; import ToolbarWidgets from '../../SmartChart/Components/toolbar-widgets.jsx'; import { useTraderStore } from 'Stores/useTraderStores'; import { observer, useStore } from '@deriv/stores';