Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Akmal / feat: add main trade type page #75

Merged
merged 12 commits into from
Aug 7, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@
border: var(--core-spacing-75) solid var(--core-color-solid-slate-75);
background-color: var(--core-color-solid-slate-75);
}

button {
background-color: transparent;
border: none;
}
}

&-category {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ export type TDraggableListCategory = {
export type TDraggableListProps = {
categories: TDraggableListCategory[];
onRightIconClick: (item: TDraggableListItem) => void;
onSave?: () => void;
onAction?: () => void;
onDrag?: (categories: TDraggableListCategory[]) => void;
};

const DraggableList: React.FC<TDraggableListProps> = ({ categories, onRightIconClick, onSave, onDrag }) => {
const DraggableList: React.FC<TDraggableListProps> = ({ categories, onRightIconClick, onAction, onDrag }) => {
const [category_list, setCategoryList] = useState(categories);
const [draggedItemId, setDraggedItemId] = useState<string | null>(null);

Expand Down Expand Up @@ -79,15 +79,15 @@ const DraggableList: React.FC<TDraggableListProps> = ({ categories, onRightIconC
<div key={category.id} className='draggable-list-category'>
<div className='draggable-list-category-header'>
<Text size='sm' bold className='draggable-list-category-header-title'>
{category.title}
{category?.title}
</Text>
{onSave && (
{onAction && (
<Text
size='sm'
bold
underlined
className='draggable-list-category-header-button'
onClick={onSave}
onClick={onAction}
>
{category.button_title || <Localize i18n_default_text='Done' />}
</Text>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import TradeTypeListItem from '../trade-type-list-item';

describe('TradeTypeListItem', () => {
it('renders with default right icon', () => {
render(<TradeTypeListItem title="Test Title" />);
render(<TradeTypeListItem title='Test Title' />);

expect(screen.getByText('Test Title')).toBeInTheDocument();
expect(screen.getByRole('img')).toBeInTheDocument();
Expand All @@ -15,13 +15,7 @@ describe('TradeTypeListItem', () => {
const custom_left_icon = <span>Custom Left Icon</span>;
const custom_right_icon = <span>Custom Right Icon</span>;

render(
<TradeTypeListItem
title="Test Title"
leftIcon={custom_left_icon}
rightIcon={custom_right_icon}
/>
);
render(<TradeTypeListItem title='Test Title' leftIcon={custom_left_icon} rightIcon={custom_right_icon} />);

expect(screen.getByText('Custom Left Icon')).toBeInTheDocument();
expect(screen.getByText('Custom Right Icon')).toBeInTheDocument();
Expand All @@ -31,10 +25,10 @@ describe('TradeTypeListItem', () => {
const handle_left_icon_click = jest.fn();

render(
<TradeTypeListItem
title="Test Title"
leftIcon={<span>Left Icon</span>}
onLeftIconClick={handle_left_icon_click}
<TradeTypeListItem
title='Test Title'
leftIcon={<span>Left Icon</span>}
onLeftIconClick={handle_left_icon_click}
/>
);

Expand All @@ -47,7 +41,7 @@ describe('TradeTypeListItem', () => {
it('calls onRightIconClick when right icon is clicked', async () => {
const handle_right_icon_click = jest.fn();

render(<TradeTypeListItem title="Test Title" onRightIconClick={handle_right_icon_click} />);
render(<TradeTypeListItem title='Test Title' onRightIconClick={handle_right_icon_click} />);

const right_icon = screen.getByRole('img');
await userEvent.click(right_icon);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,35 +1,53 @@
import React from 'react';
import { StandaloneCirclePlusFillIcon } from '@deriv/quill-icons';
import clsx from 'clsx';

type TTradeTypeListItemProps = {
title: string;
selected?: boolean;
leftIcon?: React.ReactNode;
rightIcon?: React.ReactNode;
onLeftIconClick?: () => void;
onRightIconClick?: () => void;
onTradeTypeClick?: (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
};

const TradeTypeListItem: React.FC<TTradeTypeListItemProps> = ({
title,
selected,
leftIcon,
rightIcon,
onLeftIconClick,
onRightIconClick,
onTradeTypeClick,
}) => {
const default_icon = <StandaloneCirclePlusFillIcon fill='var(--core-color-solid-green-700)' iconSize='sm' />;

return (
<div className='trade-type-list-item'>
<button
className={clsx('trade-type-list-item', { 'trade-type-list-item--selected': selected })}
onClick={onTradeTypeClick}
>
{leftIcon && (
<button className='trade-type-list-item__left-icon' onClick={onLeftIconClick}>
<button
className='trade-type-list-item__left-icon'
data-testid='dt_trade_type_list_item_left_icon'
onClick={onLeftIconClick}
>
{leftIcon}
</button>
)}
<div className='trade-type-list-item__title'>{title}</div>
<button className='trade-type-list-item__icon' onClick={onRightIconClick}>
{rightIcon || default_icon}
</button>
</div>
{onRightIconClick && (
<button
className='trade-type-list-item__icon'
data-testid='dt_trade_type_list_item_right_icon'
onClick={onRightIconClick}
>
{rightIcon || default_icon}
</button>
)}
</button>
);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,48 @@
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--core-spacing-200) var(--core-spacing-300);
padding: var(--core-spacing-200) var(--core-spacing-800);
height: var(--core-spacing-2400);
border-radius: var(--core-spacing-500);
background-color: transparent;
border: none;
width: 100%;

&__title {
font-size: var(--core-fontSize-75);
}

&--selected {
background-color: var(--core-color-solid-slate-1400);
color: var(--core-color-solid-slate-50);
}

button {
background-color: transparent;
border: none;
}
}

&-category {
margin-bottom: var(--core-spacing-1000);
padding-bottom: var(--core-spacing-400);
border-bottom: var(--core-borderWidth-75) solid var(--core-color-opacity-black-100);

&-header {
display: flex;
justify-content: space-between;
padding: 0 var(--core-spacing-600) 0 var(--core-spacing-800);

&-title, &-button {
margin-bottom: var(--core-spacing-100);
}
}

&__title {
font-size: var(--core-fontSize-75);
}

&__droppable-area {
display: flex;
flex-direction: column;
padding: var(--core-spacing-500);
&__items {
margin-top: var(--core-spacing-400);;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useState } from 'react';
import TradeTypeListItem from './trade-type-list-item';
import { Text } from '@deriv-com/quill-ui';
import './trade-type-list.scss';
import { Localize } from '@deriv/translations';

type TTradeTypeItem = {
id: string;
Expand All @@ -12,15 +13,29 @@ type TTradeTypeItem = {
type TTradeTypeCategory = {
id: string;
title?: string;
button_title?: string;
items: TTradeTypeItem[];
};

type TTradeTypeListProps = {
categories: TTradeTypeCategory[];
onRightIconClick: (item: TTradeTypeItem) => void;
categories?: TTradeTypeCategory[];
selected_item?: string;
selectable?: boolean;
onRightIconClick?: (item: TTradeTypeItem) => void;
onTradeTypeClick?: (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
onAction?: () => void;
should_show_title?: boolean;
};

const TradeTypeList: React.FC<TTradeTypeListProps> = ({ categories, onRightIconClick }) => {
const TradeTypeList: React.FC<TTradeTypeListProps> = ({
categories,
selected_item,
selectable,
onRightIconClick,
onTradeTypeClick,
onAction,
should_show_title = true,
}) => {
const [category_list, setCategoryList] = useState(categories);

React.useEffect(() => {
Expand All @@ -29,15 +44,33 @@ const TradeTypeList: React.FC<TTradeTypeListProps> = ({ categories, onRightIconC

return (
<div>
{category_list.map(category => (
{category_list?.map(category => (
<div key={category.id} className='trade-type-list-category'>
<Text size='sm' bold className='draggable-list-category-title'>
{category?.title}
</Text>
<div className='trade-type-list-category__droppable-area'>
{category.items.map(item => (
<div>
<TradeTypeListItem title={item.title} onRightIconClick={() => onRightIconClick(item)} />
<div className='trade-type-list-category-header'>
<Text size='sm' bold className='trade-type-list-category-header-title'>
{should_show_title && category?.title}
</Text>
{onAction && (
<Text
size='sm'
bold
underlined
className='trade-type-list-category-header-button'
onClick={onAction}
>
{category.button_title || <Localize i18n_default_text='Customize' />}
</Text>
)}
</div>
<div className='trade-type-list-category__items'>
{category.items.map((item: TTradeTypeItem) => (
<div key={item.id}>
<TradeTypeListItem
title={item.title}
selected={!!selectable && item.id === selected_item}
onRightIconClick={onRightIconClick && (() => onRightIconClick(item))}
onTradeTypeClick={onTradeTypeClick}
/>
</div>
))}
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { mockStore } from '@deriv/stores';
import TradeTypes from '../trade-types';
import TraderProviders from '../../../../trader-providers';
import { getTradeTypesList } from 'AppV2/Utils/trade-types-utils';

jest.mock('AppV2/Utils/trade-types-utils');

const mockGetTradeTypesList = getTradeTypesList as jest.MockedFunction<typeof getTradeTypesList>;

const contract_types_list = {
rise_fall: {
name: 'Rise/Fall',
categories: [
{ text: 'Rise', value: 'rise' },
{ text: 'Fall', value: 'fall' },
],
},
vanilla: {
name: 'Vanilla',
categories: [
{ text: 'Vanilla Call', value: 'vanilla_call' },
{ text: 'Vanilla Put', value: 'vanilla_put' },
],
},
};

const default_mock_store = {
modules: {
trade: {
contract_type: 'rise_fall',
contract_types_list,
onMount: jest.fn(),
onUnmount: jest.fn(),
},
},
};

const mockTradeTypes = (mocked_store = mockStore(default_mock_store)) => {
return (
<TraderProviders store={mocked_store}>
<TradeTypes
onTradeTypeSelect={jest.fn()}
trade_types={mockGetTradeTypesList(default_mock_store.modules.trade.contract_types_list)}
contract_type='rise_fall'
/>
</TraderProviders>
);
};

describe('TradeTypes', () => {
beforeEach(() => {
mockGetTradeTypesList.mockReturnValue([
{ value: 'rise', text: 'Rise' },
{ value: 'fall', text: 'Fall' },
{ value: 'vanilla_call', text: 'Vanilla Call' },
{ value: 'vanilla_put', text: 'Vanilla Put' },
]);
});

it('should render the TradeTypes component with pinned and other trade types', () => {
render(mockTradeTypes());

expect(screen.getByText('View all')).toBeInTheDocument();
expect(screen.getByText('Rise')).toBeInTheDocument();
});

it('should open ActionSheet when View all button is clicked', async () => {
render(mockTradeTypes());

await userEvent.click(screen.getByText('View all'));

expect(screen.getByText('Trade types')).toBeInTheDocument();
expect(screen.getByText('Fall')).toBeInTheDocument();
});

it('should handle adding and removing pinned trade types', async () => {
render(mockTradeTypes());

await userEvent.click(screen.getByText('View all'));
akmal-deriv marked this conversation as resolved.
Show resolved Hide resolved
await userEvent.click(screen.getByText('Customize'));
const addButton = screen.getAllByTestId('dt_trade_type_list_item_right_icon')[0];
await userEvent.click(addButton);

const removeButton = screen.getAllByTestId('dt_draggable_list_item_icon')[0];
await userEvent.click(removeButton);

expect(screen.getByText('Trade types')).toBeInTheDocument();
});

it('should mount and unmount correctly', () => {
const { unmount } = render(mockTradeTypes());

expect(default_mock_store.modules.trade.onMount).toHaveBeenCalled();
unmount();
expect(default_mock_store.modules.trade.onUnmount).toHaveBeenCalled();
});
});
Loading
Loading