Skip to content

Commit a64968a

Browse files
authored
Merge pull request #200 from ameerul-deriv/FEQ-2459-handling-selected-buy/sell-and-orders-tab
Ameerul / FEQ-2459 Handling selected buy/sell and orders tabs
2 parents a17365c + 495112f commit a64968a

File tree

13 files changed

+251
-70
lines changed

13 files changed

+251
-70
lines changed

Diff for: src/components/Modals/FilterModal/__tests__/FilterModal.spec.tsx

+31-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ const mockStore = {
4444
};
4545

4646
jest.mock('@/stores', () => ({
47-
useBuySellFiltersStore: jest.fn(() => mockStore),
47+
useBuySellFiltersStore: jest.fn(selector => (selector ? selector(mockStore) : mockStore)),
4848
}));
4949

5050
jest.mock('@deriv-com/ui', () => ({
@@ -323,4 +323,34 @@ describe('<FilterModal />', () => {
323323

324324
expect(screen.getByText('Filter')).toBeInTheDocument();
325325
});
326+
327+
it('should show LeaveFilterModal when user tries to exit the FilterModal in mobile view', async () => {
328+
mockModalManager.isModalOpenFor.mockImplementation(modalName => modalName === 'LeaveFilterModal');
329+
render(<FilterModal {...mockProps} />);
330+
331+
const toggleSwitch = screen.getByRole('checkbox');
332+
await user.click(toggleSwitch);
333+
334+
const closeIcon = screen.getByTestId('dt_mobile_wrapper_button');
335+
await user.click(closeIcon);
336+
337+
expect(mockModalManager.showModal).toHaveBeenCalledWith('LeaveFilterModal');
338+
expect(screen.getByText('Leave page?')).toBeInTheDocument();
339+
});
340+
341+
it('should call hideModal when user clicks on the cancel button in LeaveFilterModal', async () => {
342+
mockModalManager.isModalOpenFor.mockImplementation(modalName => modalName === 'LeaveFilterModal');
343+
render(<FilterModal {...mockProps} />);
344+
345+
const toggleSwitch = screen.getByRole('checkbox');
346+
await user.click(toggleSwitch);
347+
348+
const closeIcon = screen.getByTestId('dt_mobile_wrapper_button');
349+
await user.click(closeIcon);
350+
351+
const cancelButton = screen.getByRole('button', { name: 'Cancel' });
352+
await user.click(cancelButton);
353+
354+
expect(mockModalManager.hideModal).toHaveBeenCalledWith({ shouldHideAllModals: false });
355+
});
326356
});

Diff for: src/pages/advertiser/screens/AdvertiserAdvertsTable/AdvertiserAdvertsTable.tsx

+19-4
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { useCallback, useEffect, useState } from 'react';
22
import { useHistory, useLocation } from 'react-router-dom';
3+
import { useShallow } from 'zustand/react/shallow';
34
import { BuySellForm } from '@/components';
45
import { ErrorModal, LoadingModal } from '@/components/Modals';
56
import { ADVERT_TYPE, BUY_SELL, BUY_SELL_URL } from '@/constants';
67
import { api } from '@/hooks';
78
import { useIsAdvertiser, useIsAdvertiserBarred, useModalManager, useQueryString } from '@/hooks/custom-hooks';
9+
import { useTabsStore } from '@/stores';
810
import { getLocalizedTabs } from '@/utils/tabs';
911
import { useTranslations } from '@deriv-com/translations';
1012
import { Tab, Tabs } from '@deriv-com/ui';
@@ -29,20 +31,28 @@ const AdvertiserAdvertsTable = ({ advertiserId }: TAdvertiserAdvertsTableProps)
2931
const currency = currencyParam !== null && currencyParam ? currencyParam : undefined;
3032

3133
const { queryString, setQueryString } = useQueryString();
32-
const activeTab = queryString?.tab || ADVERT_TYPE.BUY;
3334

35+
const { activeAdvertisersBuySellTab, setActiveAdvertisersBuySellTab } = useTabsStore(
36+
useShallow(state => ({
37+
activeAdvertisersBuySellTab: state.activeAdvertisersBuySellTab,
38+
setActiveAdvertisersBuySellTab: state.setActiveAdvertisersBuySellTab,
39+
}))
40+
);
3441
const { data: advertInfo, error, isLoading: isLoadingAdvert } = api.advert.useGet({ id: advertId }, !!advertId);
3542
const { data, isFetching, isLoading, loadMoreAdverts } = api.advert.useGetList({
3643
advertiser_id: advertiserId,
37-
counterparty_type: activeTab === ADVERT_TYPE.BUY ? BUY_SELL.BUY : BUY_SELL.SELL,
44+
counterparty_type: activeAdvertisersBuySellTab === ADVERT_TYPE.BUY ? BUY_SELL.BUY : BUY_SELL.SELL,
3845
local_currency: currency,
3946
});
4047
const { data: advertiserInfo } = api.advertiser.useGetInfo() || {};
4148
const isMyAdvert = advertiserInfo?.id === advertiserId;
4249
const isAdvertiser = useIsAdvertiser();
4350
const isAdvertiserBarred = useIsAdvertiserBarred();
4451

45-
const setActiveTab = (index: number) => setQueryString({ tab: TABS[index] });
52+
const setActiveTab = (index: number) => {
53+
setActiveAdvertisersBuySellTab(TABS[index]);
54+
setQueryString({ tab: TABS[index] });
55+
};
4656

4757
const setShowBuySellForm = useCallback(() => {
4858
if (advertInfo) {
@@ -61,6 +71,11 @@ const AdvertiserAdvertsTable = ({ advertiserId }: TAdvertiserAdvertsTableProps)
6171
// eslint-disable-next-line react-hooks/exhaustive-deps
6272
}, [advertInfo, error, isLoadingAdvert]);
6373

74+
useEffect(() => {
75+
if (queryString.tab) setActiveAdvertisersBuySellTab(queryString.tab);
76+
else setQueryString({ tab: activeAdvertisersBuySellTab });
77+
}, [activeAdvertisersBuySellTab, queryString.tab, setActiveAdvertisersBuySellTab, setQueryString]);
78+
6479
useEffect(() => {
6580
const params = new URLSearchParams(location.search);
6681
const advertIdParam = params.get('advert_id');
@@ -78,7 +93,7 @@ const AdvertiserAdvertsTable = ({ advertiserId }: TAdvertiserAdvertsTableProps)
7893
return (
7994
<div className='advertiser-adverts-table'>
8095
<Tabs
81-
activeTab={getLocalizedTabs(localize)[activeTab]}
96+
activeTab={getLocalizedTabs(localize)[activeAdvertisersBuySellTab]}
8297
className='lg:w-80 lg:mt-10'
8398
onChange={setActiveTab}
8499
variant='secondary'

Diff for: src/pages/advertiser/screens/AdvertiserAdvertsTable/__tests__/AdvertiserAdvertsTable.spec.tsx

+29-1
Original file line numberDiff line numberDiff line change
@@ -91,13 +91,27 @@ const mockUseModalManager = {
9191
showModal: jest.fn(),
9292
};
9393

94+
const mockUseQueryString = {
95+
queryString: {},
96+
setQueryString: jest.fn(),
97+
};
98+
9499
jest.mock('@/hooks/custom-hooks', () => ({
95100
...jest.requireActual('@/hooks/custom-hooks'),
96101
useIsAdvertiser: jest.fn(() => true),
97102
useIsAdvertiserBarred: jest.fn(() => false),
98103
useModalManager: jest.fn(() => mockUseModalManager),
99104
usePoiPoaStatus: jest.fn(() => ({ data: { isPoaVerified: true, isPoiVerified: true } })),
100-
useQueryString: jest.fn(() => ({ queryString: {}, setQueryString: jest.fn() })),
105+
useQueryString: jest.fn(() => mockUseQueryString),
106+
}));
107+
108+
const mockTabsStore = {
109+
activeAdvertisersBuySellTab: 'Buy',
110+
setActiveAdvertisersBuySellTab: jest.fn(),
111+
};
112+
113+
jest.mock('@/stores', () => ({
114+
useTabsStore: jest.fn(selector => (selector ? selector(mockTabsStore) : mockTabsStore)),
101115
}));
102116

103117
const mockUseGetList = api.advert.useGetList as jest.Mock;
@@ -113,6 +127,19 @@ describe('<AdvertiserAdvertsTable />', () => {
113127
expect(screen.getByTestId('dt_derivs-loader')).toBeInTheDocument();
114128
});
115129

130+
it('should call setQueryString if queryString is not defined', async () => {
131+
render(<AdvertiserAdvertsTable advertiserId='123' />);
132+
133+
expect(mockUseQueryString.setQueryString).toHaveBeenCalledWith({ tab: 'Buy' });
134+
});
135+
136+
it('should call setActiveAdvertisersBuySellTab if queryString is defined', async () => {
137+
mockUseQueryString.queryString = { tab: 'Buy' };
138+
render(<AdvertiserAdvertsTable advertiserId='123' />);
139+
140+
expect(mockTabsStore.setActiveAdvertisersBuySellTab).toHaveBeenCalledWith('Buy');
141+
});
142+
116143
it('should show There are no adverts yet message if data is empty', () => {
117144
mockUseGetAdvertList = {
118145
...mockUseGetAdvertList,
@@ -184,6 +211,7 @@ describe('<AdvertiserAdvertsTable />', () => {
184211
expect(screen.getByRole('button', { name: 'Sell' })).toHaveClass(activeClass);
185212

186213
expect(screen.getByRole('button', { name: 'Sell USD' })).toBeInTheDocument();
214+
expect(mockTabsStore.setActiveAdvertisersBuySellTab).toHaveBeenCalledWith('Sell');
187215
});
188216

189217
it('should show LoadingModal if isLoading is true and advertId is present', () => {

Diff for: src/pages/buy-sell/screens/BuySellHeader/BuySellHeader.tsx

+33-9
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,30 @@
1+
import { useEffect } from 'react';
12
import { useShallow } from 'zustand/react/shallow';
23
import { Search } from '@/components';
34
import { FilterModal } from '@/components/Modals';
4-
import { getSortByList } from '@/constants';
5-
import { useModalManager } from '@/hooks/custom-hooks';
5+
import { ADVERT_TYPE, getSortByList } from '@/constants';
6+
import { useModalManager, useQueryString } from '@/hooks/custom-hooks';
67
import { GuideTooltip } from '@/pages/guide/components';
7-
import { useBuySellFiltersStore } from '@/stores';
8+
import { useBuySellFiltersStore, useTabsStore } from '@/stores';
89
import { getLocalizedTabs } from '@/utils/tabs';
910
import { LabelPairedBarsFilterMdBoldIcon, LabelPairedBarsFilterSmBoldIcon } from '@deriv/quill-icons';
1011
import { useTranslations } from '@deriv-com/translations';
1112
import { Button, Tab, Tabs, useDevice } from '@deriv-com/ui';
1213
import { CurrencyDropdown, SortDropdown } from '../../components';
1314
import './BuySellHeader.scss';
1415

16+
const TABS = [ADVERT_TYPE.BUY, ADVERT_TYPE.SELL];
17+
1518
type TBuySellHeaderProps = {
16-
activeTab: string;
17-
setActiveTab: (tab: number) => void;
1819
setIsFilterModalOpen: () => void;
1920
setSearchValue: (value: string) => void;
2021
};
2122

22-
const BuySellHeader = ({ activeTab, setActiveTab, setIsFilterModalOpen, setSearchValue }: TBuySellHeaderProps) => {
23+
const BuySellHeader = ({ setIsFilterModalOpen, setSearchValue }: TBuySellHeaderProps) => {
2324
const { hideModal, isModalOpenFor, showModal } = useModalManager({ shouldReinitializeModals: false });
2425
const { localize } = useTranslations();
2526
const { isDesktop, isMobile } = useDevice();
27+
const { queryString, setQueryString } = useQueryString();
2628
const { filteredCurrency, selectedPaymentMethods, setFilteredCurrency, setSortByValue, sortByValue } =
2729
useBuySellFiltersStore(
2830
useShallow(state => ({
@@ -34,13 +36,30 @@ const BuySellHeader = ({ activeTab, setActiveTab, setIsFilterModalOpen, setSearc
3436
}))
3537
);
3638

39+
const { activeBuySellTab, setActiveBuySellTab } = useTabsStore(
40+
useShallow(state => ({
41+
activeBuySellTab: state.activeBuySellTab,
42+
setActiveBuySellTab: state.setActiveBuySellTab,
43+
}))
44+
);
45+
46+
useEffect(() => {
47+
if (queryString.tab) setActiveBuySellTab(queryString.tab);
48+
else setQueryString({ tab: activeBuySellTab });
49+
}, [activeBuySellTab, queryString.tab, setActiveBuySellTab, setQueryString]);
50+
3751
return (
3852
<div className='buy-sell-header' data-testid='dt_buy_sell_header'>
3953
<div className='buy-sell-header__row justify-between'>
4054
<Tabs
4155
TitleFontSize={isMobile ? 'md' : 'sm'}
42-
activeTab={getLocalizedTabs(localize)[activeTab]}
43-
onChange={setActiveTab}
56+
activeTab={getLocalizedTabs(localize)[activeBuySellTab]}
57+
onChange={index => {
58+
setActiveBuySellTab(TABS[index]);
59+
setQueryString({
60+
tab: TABS[index],
61+
});
62+
}}
4463
variant='primary'
4564
wrapperClassName='buy-sell-header__tabs'
4665
>
@@ -74,7 +93,12 @@ const BuySellHeader = ({ activeTab, setActiveTab, setIsFilterModalOpen, setSearc
7493
onClick={() => showModal('FilterModal')}
7594
variant='outlined'
7695
>
77-
{!!selectedPaymentMethods?.length && <div className='buy-sell-header__filter-button__indication' />}
96+
{!!selectedPaymentMethods?.length && (
97+
<div
98+
className='buy-sell-header__filter-button__indication'
99+
data-testid='dt_filter_button_indicator'
100+
/>
101+
)}
78102
</Button>
79103
</div>
80104
{isModalOpenFor('FilterModal') && <FilterModal isModalOpen onRequestClose={hideModal} />}

Diff for: src/pages/buy-sell/screens/BuySellHeader/__tests__/BuySellHeader.spec.tsx

+39-9
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ import userEvent from '@testing-library/user-event';
44
import BuySellHeader from '../BuySellHeader';
55

66
const mockProps = {
7-
activeTab: 'Buy',
8-
setActiveTab: jest.fn(),
97
setIsFilterModalOpen: jest.fn(),
108
setSearchValue: jest.fn(),
119
};
@@ -29,7 +27,7 @@ jest.mock('@/hooks', () => ({
2927
},
3028
}));
3129

32-
const mockStore = {
30+
const mockFiltersStore = {
3331
filteredCurrency: 'IDR',
3432
selectedPaymentMethods: [],
3533
setFilteredCurrency: jest.fn(),
@@ -38,8 +36,14 @@ const mockStore = {
3836
sortByValue: 'rate',
3937
};
4038

39+
const mockTabsStore = {
40+
activeBuySellTab: 'Buy',
41+
setActiveBuySellTab: jest.fn(),
42+
};
43+
4144
jest.mock('@/stores', () => ({
42-
useBuySellFiltersStore: jest.fn(() => mockStore),
45+
useBuySellFiltersStore: jest.fn(selector => (selector ? selector(mockFiltersStore) : mockFiltersStore)),
46+
useTabsStore: jest.fn(selector => (selector ? selector(mockTabsStore) : mockTabsStore)),
4347
}));
4448

4549
const mockUseModalManager = {
@@ -48,9 +52,17 @@ const mockUseModalManager = {
4852
showModal: jest.fn(),
4953
};
5054

55+
const mockUseQueryString = {
56+
queryString: {
57+
tab: 'buy',
58+
},
59+
setQueryString: jest.fn(),
60+
};
61+
5162
jest.mock('@/hooks/custom-hooks', () => ({
5263
...jest.requireActual('@/hooks/custom-hooks'),
5364
useModalManager: jest.fn(() => mockUseModalManager),
65+
useQueryString: jest.fn(() => mockUseQueryString),
5466
}));
5567

5668
jest.mock('../../../components/CurrencyDropdown/CurrencyDropdown', () => jest.fn(() => <div>CurrencyDropdown</div>));
@@ -79,22 +91,32 @@ describe('<BuySellHeader />', () => {
7991
expect(screen.getByRole('combobox', { name: 'Sort by' })).toBeInTheDocument();
8092
});
8193

82-
it('should call setActiveTab when Sell tab is clicked', async () => {
94+
it('should call setActiveBuySellTab and setQueryString when Sell tab is clicked', async () => {
8395
render(<BuySellHeader {...mockProps} />);
8496

8597
const sellTab = screen.getByRole('button', { name: 'Sell' });
8698

8799
await user.click(sellTab);
88-
expect(mockProps.setActiveTab).toHaveBeenCalledWith(1);
100+
expect(mockTabsStore.setActiveBuySellTab).toHaveBeenCalledWith('Sell');
101+
expect(mockUseQueryString.setQueryString).toHaveBeenCalledWith({ tab: 'Sell' });
89102
});
90103

91-
it('should call setActiveTab when Buy tab is clicked', async () => {
104+
it('should call setActiveBuySellTab and setQueryString when Buy tab is clicked', async () => {
92105
render(<BuySellHeader {...mockProps} />);
93106

94107
const buyTab = screen.getByRole('button', { name: 'Buy' });
95108

96109
await user.click(buyTab);
97-
expect(mockProps.setActiveTab).toHaveBeenCalledWith(0);
110+
expect(mockTabsStore.setActiveBuySellTab).toHaveBeenCalledWith('Buy');
111+
expect(mockUseQueryString.setQueryString).toHaveBeenCalledWith({ tab: 'Buy' });
112+
});
113+
114+
it('should call setQueryString if the tab is not set in the query string', () => {
115+
// @ts-expect-error tab can be undefined
116+
mockUseQueryString.queryString.tab = undefined;
117+
render(<BuySellHeader {...mockProps} />);
118+
119+
expect(mockUseQueryString.setQueryString).toHaveBeenCalledWith({ tab: 'Buy' });
98120
});
99121

100122
it('should call setSortDropdownValue when a value is selected from the dropdown', async () => {
@@ -108,7 +130,7 @@ describe('<BuySellHeader />', () => {
108130

109131
await user.click(ratingOption);
110132

111-
expect(mockStore.setSortByValue).toHaveBeenCalledWith('rating');
133+
expect(mockFiltersStore.setSortByValue).toHaveBeenCalledWith('rating');
112134
});
113135

114136
it('should allow users to click on filter button', async () => {
@@ -136,4 +158,12 @@ describe('<BuySellHeader />', () => {
136158

137159
expect(searchInput).toHaveValue('John Doe');
138160
});
161+
162+
it('should show indicator when selectedPaymentMethods is not empty', () => {
163+
// @ts-expect-error mocked selectedPaymentMethods can have a value
164+
mockFiltersStore.selectedPaymentMethods = ['alipay'];
165+
render(<BuySellHeader {...mockProps} />);
166+
167+
expect(screen.getByTestId('dt_filter_button_indicator')).toBeInTheDocument();
168+
});
139169
});

0 commit comments

Comments
 (0)