chore(trading): make top traded default sort for market selector (#4637)
This commit is contained in:
parent
9209074332
commit
e41aff88b1
@ -262,9 +262,7 @@ describe('MarketSelector', () => {
|
|||||||
await userEvent.click(screen.getByTestId('sort-trigger'));
|
await userEvent.click(screen.getByTestId('sort-trigger'));
|
||||||
const options = screen.getAllByTestId(/sort-item/);
|
const options = screen.getAllByTestId(/sort-item/);
|
||||||
expect(options.map((o) => o.textContent?.trim())).toEqual(
|
expect(options.map((o) => o.textContent?.trim())).toEqual(
|
||||||
Object.entries(Sort)
|
Object.entries(Sort).map(([key]) => SortTypeMapping[key as SortType])
|
||||||
.filter(([key]) => key !== Sort.None)
|
|
||||||
.map(([key]) => SortTypeMapping[key as SortType])
|
|
||||||
);
|
);
|
||||||
await userEvent.click(screen.getByTestId('sort-item-Gained'));
|
await userEvent.click(screen.getByTestId('sort-item-Gained'));
|
||||||
expect(
|
expect(
|
||||||
|
@ -40,7 +40,7 @@ export const MarketSelector = ({
|
|||||||
const [filter, setFilter] = useState<Filter>({
|
const [filter, setFilter] = useState<Filter>({
|
||||||
searchTerm: '',
|
searchTerm: '',
|
||||||
product: Product.All,
|
product: Product.All,
|
||||||
sort: Sort.None,
|
sort: Sort.TopTraded,
|
||||||
assets: [],
|
assets: [],
|
||||||
});
|
});
|
||||||
const allProducts = filter.product === Product.All;
|
const allProducts = filter.product === Product.All;
|
||||||
@ -53,7 +53,7 @@ export const MarketSelector = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-testid="market-selector">
|
<div data-testid="market-selector">
|
||||||
<div className="pt-2 px-2 mb-2">
|
<div className="px-2 pt-2 mb-2">
|
||||||
<ProductSelector
|
<ProductSelector
|
||||||
product={filter.product}
|
product={filter.product}
|
||||||
onSelect={(product) => {
|
onSelect={(product) => {
|
||||||
@ -106,9 +106,6 @@ export const MarketSelector = ({
|
|||||||
currentSort={filter.sort}
|
currentSort={filter.sort}
|
||||||
onSelect={(sort) => {
|
onSelect={(sort) => {
|
||||||
setFilter((curr) => {
|
setFilter((curr) => {
|
||||||
if (curr.sort === sort) {
|
|
||||||
return { ...curr, sort: Sort.None };
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
...curr,
|
...curr,
|
||||||
sort,
|
sort,
|
||||||
@ -294,9 +291,9 @@ const List = ({
|
|||||||
|
|
||||||
const Skeleton = () => {
|
const Skeleton = () => {
|
||||||
return (
|
return (
|
||||||
<div className="mb-2 px-2">
|
<div className="px-2 mb-2">
|
||||||
<div className="bg-vega-light-100 dark:bg-vega-dark-100 rounded-lg p-4">
|
<div className="p-4 rounded-lg bg-vega-light-100 dark:bg-vega-dark-100">
|
||||||
<div className="w-full h-3 bg-vega-light-200 dark:bg-vega-dark-200 mb-2" />
|
<div className="w-full h-3 mb-2 bg-vega-light-200 dark:bg-vega-dark-200" />
|
||||||
<div className="w-2/3 h-3 bg-vega-light-200 dark:bg-vega-dark-200" />
|
<div className="w-2/3 h-3 bg-vega-light-200 dark:bg-vega-dark-200" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { t } from '@vegaprotocol/i18n';
|
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
@ -11,7 +10,6 @@ import {
|
|||||||
} from '@vegaprotocol/ui-toolkit';
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
|
|
||||||
export const Sort = {
|
export const Sort = {
|
||||||
None: 'None',
|
|
||||||
Gained: 'Gained',
|
Gained: 'Gained',
|
||||||
Lost: 'Lost',
|
Lost: 'Lost',
|
||||||
New: 'New',
|
New: 'New',
|
||||||
@ -23,17 +21,15 @@ export type SortType = keyof typeof Sort;
|
|||||||
export const SortTypeMapping: {
|
export const SortTypeMapping: {
|
||||||
[key in SortType]: string;
|
[key in SortType]: string;
|
||||||
} = {
|
} = {
|
||||||
[Sort.None]: 'None',
|
[Sort.TopTraded]: 'Top traded',
|
||||||
[Sort.Gained]: 'Top gaining',
|
[Sort.Gained]: 'Top gaining',
|
||||||
[Sort.Lost]: 'Top losing',
|
[Sort.Lost]: 'Top losing',
|
||||||
[Sort.New]: 'New markets',
|
[Sort.New]: 'New markets',
|
||||||
[Sort.TopTraded]: 'Top traded',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const SortIconMapping: {
|
const SortIconMapping: {
|
||||||
[key in SortType]: VegaIconNames;
|
[key in SortType]: VegaIconNames;
|
||||||
} = {
|
} = {
|
||||||
[Sort.None]: null as unknown as VegaIconNames, // not shown in list
|
|
||||||
[Sort.Gained]: VegaIconNames.TREND_UP,
|
[Sort.Gained]: VegaIconNames.TREND_UP,
|
||||||
[Sort.Lost]: VegaIconNames.TREND_DOWN,
|
[Sort.Lost]: VegaIconNames.TREND_DOWN,
|
||||||
[Sort.New]: VegaIconNames.STAR,
|
[Sort.New]: VegaIconNames.STAR,
|
||||||
@ -51,10 +47,8 @@ export const SortDropdown = ({
|
|||||||
<DropdownMenu
|
<DropdownMenu
|
||||||
trigger={
|
trigger={
|
||||||
<DropdownMenuTrigger data-testid="sort-trigger">
|
<DropdownMenuTrigger data-testid="sort-trigger">
|
||||||
<span className="flex justify-between items-center">
|
<span className="flex items-center justify-between gap-1">
|
||||||
{currentSort === SortTypeMapping.None
|
{SortTypeMapping[currentSort]}
|
||||||
? t('Sort')
|
|
||||||
: SortTypeMapping[currentSort]}{' '}
|
|
||||||
<VegaIcon name={VegaIconNames.CHEVRON_DOWN} />
|
<VegaIcon name={VegaIconNames.CHEVRON_DOWN} />
|
||||||
</span>
|
</span>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
@ -65,24 +59,22 @@ export const SortDropdown = ({
|
|||||||
value={currentSort}
|
value={currentSort}
|
||||||
onValueChange={(value) => onSelect(value as SortType)}
|
onValueChange={(value) => onSelect(value as SortType)}
|
||||||
>
|
>
|
||||||
{Object.keys(Sort)
|
{Object.keys(Sort).map((key) => {
|
||||||
.filter((s) => s !== Sort.None)
|
return (
|
||||||
.map((key) => {
|
<DropdownMenuRadioItem
|
||||||
return (
|
inset
|
||||||
<DropdownMenuRadioItem
|
key={key}
|
||||||
inset
|
value={key}
|
||||||
key={key}
|
data-testid={`sort-item-${key}`}
|
||||||
value={key}
|
>
|
||||||
data-testid={`sort-item-${key}`}
|
<span className="flex gap-2">
|
||||||
>
|
<VegaIcon name={SortIconMapping[key as SortType]} />{' '}
|
||||||
<span className="flex gap-2">
|
{SortTypeMapping[key as SortType]}
|
||||||
<VegaIcon name={SortIconMapping[key as SortType]} />{' '}
|
</span>
|
||||||
{SortTypeMapping[key as SortType]}
|
<DropdownMenuItemIndicator />
|
||||||
</span>
|
</DropdownMenuRadioItem>
|
||||||
<DropdownMenuItemIndicator />
|
);
|
||||||
</DropdownMenuRadioItem>
|
})}
|
||||||
);
|
|
||||||
})}
|
|
||||||
</DropdownMenuRadioGroup>
|
</DropdownMenuRadioGroup>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
|
@ -12,7 +12,10 @@ import { useMarketList } from '@vegaprotocol/markets';
|
|||||||
import type { Filter } from '../../components/market-selector';
|
import type { Filter } from '../../components/market-selector';
|
||||||
import { subDays } from 'date-fns';
|
import { subDays } from 'date-fns';
|
||||||
|
|
||||||
jest.mock('@vegaprotocol/markets');
|
jest.mock('@vegaprotocol/markets', () => ({
|
||||||
|
...jest.requireActual('@vegaprotocol/markets'),
|
||||||
|
useMarketList: jest.fn(),
|
||||||
|
}));
|
||||||
const mockUseMarketList = useMarketList as jest.Mock;
|
const mockUseMarketList = useMarketList as jest.Mock;
|
||||||
|
|
||||||
describe('useMarketSelectorList', () => {
|
describe('useMarketSelectorList', () => {
|
||||||
@ -20,7 +23,7 @@ describe('useMarketSelectorList', () => {
|
|||||||
const defaultArgs: Filter = {
|
const defaultArgs: Filter = {
|
||||||
searchTerm: '',
|
searchTerm: '',
|
||||||
product: Product.Future,
|
product: Product.Future,
|
||||||
sort: Sort.None,
|
sort: Sort.TopTraded,
|
||||||
assets: [],
|
assets: [],
|
||||||
};
|
};
|
||||||
return renderHook((args) => useMarketSelectorList(args), {
|
return renderHook((args) => useMarketSelectorList(args), {
|
||||||
@ -109,21 +112,21 @@ describe('useMarketSelectorList', () => {
|
|||||||
rerender({
|
rerender({
|
||||||
searchTerm: '',
|
searchTerm: '',
|
||||||
product: Product.Spot as 'Future',
|
product: Product.Spot as 'Future',
|
||||||
sort: Sort.None,
|
sort: Sort.TopTraded,
|
||||||
assets: [],
|
assets: [],
|
||||||
});
|
});
|
||||||
expect(result.current.markets).toEqual([markets[1]]);
|
expect(result.current.markets).toEqual([markets[1]]);
|
||||||
rerender({
|
rerender({
|
||||||
searchTerm: '',
|
searchTerm: '',
|
||||||
product: Product.Perpetual as 'Future',
|
product: Product.Perpetual as 'Future',
|
||||||
sort: Sort.None,
|
sort: Sort.TopTraded,
|
||||||
assets: [],
|
assets: [],
|
||||||
});
|
});
|
||||||
expect(result.current.markets).toEqual([markets[2]]);
|
expect(result.current.markets).toEqual([markets[2]]);
|
||||||
rerender({
|
rerender({
|
||||||
searchTerm: '',
|
searchTerm: '',
|
||||||
product: Product.All,
|
product: Product.All,
|
||||||
sort: Sort.None,
|
sort: Sort.TopTraded,
|
||||||
assets: [],
|
assets: [],
|
||||||
});
|
});
|
||||||
expect(result.current.markets).toEqual(markets);
|
expect(result.current.markets).toEqual(markets);
|
||||||
@ -189,7 +192,7 @@ describe('useMarketSelectorList', () => {
|
|||||||
const { result, rerender } = setup({
|
const { result, rerender } = setup({
|
||||||
searchTerm: '',
|
searchTerm: '',
|
||||||
product: Product.Future,
|
product: Product.Future,
|
||||||
sort: Sort.None,
|
sort: Sort.TopTraded,
|
||||||
assets: ['asset-0'],
|
assets: ['asset-0'],
|
||||||
});
|
});
|
||||||
expect(result.current.markets).toEqual([markets[0], markets[1]]);
|
expect(result.current.markets).toEqual([markets[0], markets[1]]);
|
||||||
@ -197,7 +200,7 @@ describe('useMarketSelectorList', () => {
|
|||||||
rerender({
|
rerender({
|
||||||
searchTerm: '',
|
searchTerm: '',
|
||||||
product: Product.Future,
|
product: Product.Future,
|
||||||
sort: Sort.None,
|
sort: Sort.TopTraded,
|
||||||
assets: ['asset-0', 'asset-1'],
|
assets: ['asset-0', 'asset-1'],
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -210,7 +213,7 @@ describe('useMarketSelectorList', () => {
|
|||||||
rerender({
|
rerender({
|
||||||
searchTerm: '',
|
searchTerm: '',
|
||||||
product: Product.Future,
|
product: Product.Future,
|
||||||
sort: Sort.None,
|
sort: Sort.TopTraded,
|
||||||
assets: ['asset-0', 'asset-1', 'asset-2'],
|
assets: ['asset-0', 'asset-1', 'asset-2'],
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -220,7 +223,7 @@ describe('useMarketSelectorList', () => {
|
|||||||
rerender({
|
rerender({
|
||||||
searchTerm: '',
|
searchTerm: '',
|
||||||
product: Product.Future,
|
product: Product.Future,
|
||||||
sort: Sort.None,
|
sort: Sort.TopTraded,
|
||||||
assets: ['asset-invalid'],
|
assets: ['asset-invalid'],
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -275,28 +278,28 @@ describe('useMarketSelectorList', () => {
|
|||||||
const { result, rerender } = setup({
|
const { result, rerender } = setup({
|
||||||
searchTerm: 'abc',
|
searchTerm: 'abc',
|
||||||
product: Product.Future,
|
product: Product.Future,
|
||||||
sort: Sort.None,
|
sort: Sort.TopTraded,
|
||||||
assets: [],
|
assets: [],
|
||||||
});
|
});
|
||||||
expect(result.current.markets).toEqual([markets[0]]);
|
expect(result.current.markets).toEqual([markets[0]]);
|
||||||
rerender({
|
rerender({
|
||||||
searchTerm: 'def',
|
searchTerm: 'def',
|
||||||
product: Product.Future,
|
product: Product.Future,
|
||||||
sort: Sort.None,
|
sort: Sort.TopTraded,
|
||||||
assets: [],
|
assets: [],
|
||||||
});
|
});
|
||||||
expect(result.current.markets).toEqual([markets[1], markets[2]]);
|
expect(result.current.markets).toEqual([markets[1], markets[2]]);
|
||||||
rerender({
|
rerender({
|
||||||
searchTerm: 'defg',
|
searchTerm: 'defg',
|
||||||
product: Product.Future,
|
product: Product.Future,
|
||||||
sort: Sort.None,
|
sort: Sort.TopTraded,
|
||||||
assets: [],
|
assets: [],
|
||||||
});
|
});
|
||||||
expect(result.current.markets).toEqual([markets[2]]);
|
expect(result.current.markets).toEqual([markets[2]]);
|
||||||
rerender({
|
rerender({
|
||||||
searchTerm: 'zzz',
|
searchTerm: 'zzz',
|
||||||
product: Product.Future,
|
product: Product.Future,
|
||||||
sort: Sort.None,
|
sort: Sort.TopTraded,
|
||||||
assets: [],
|
assets: [],
|
||||||
});
|
});
|
||||||
expect(result.current.markets).toEqual([]);
|
expect(result.current.markets).toEqual([]);
|
||||||
@ -305,14 +308,14 @@ describe('useMarketSelectorList', () => {
|
|||||||
rerender({
|
rerender({
|
||||||
searchTerm: 'aaa',
|
searchTerm: 'aaa',
|
||||||
product: Product.Future,
|
product: Product.Future,
|
||||||
sort: Sort.None,
|
sort: Sort.TopTraded,
|
||||||
assets: [],
|
assets: [],
|
||||||
});
|
});
|
||||||
expect(result.current.markets).toEqual([markets[0]]);
|
expect(result.current.markets).toEqual([markets[0]]);
|
||||||
rerender({
|
rerender({
|
||||||
searchTerm: 'ggg',
|
searchTerm: 'ggg',
|
||||||
product: Product.Future,
|
product: Product.Future,
|
||||||
sort: Sort.None,
|
sort: Sort.TopTraded,
|
||||||
assets: [],
|
assets: [],
|
||||||
});
|
});
|
||||||
expect(result.current.markets).toEqual([
|
expect(result.current.markets).toEqual([
|
||||||
@ -322,11 +325,15 @@ describe('useMarketSelectorList', () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sorts by state and volume by default', () => {
|
it('sorts by top traded by default', () => {
|
||||||
const markets = [
|
const markets = [
|
||||||
createMarketFragment({
|
createMarketFragment({
|
||||||
id: 'market-0',
|
id: 'market-0',
|
||||||
state: MarketState.STATE_PENDING,
|
state: MarketState.STATE_ACTIVE,
|
||||||
|
// @ts-ignore data not on fragment
|
||||||
|
data: {
|
||||||
|
markPrice: '1',
|
||||||
|
},
|
||||||
// @ts-ignore candles not on fragment
|
// @ts-ignore candles not on fragment
|
||||||
candles: [
|
candles: [
|
||||||
{
|
{
|
||||||
@ -337,30 +344,42 @@ describe('useMarketSelectorList', () => {
|
|||||||
createMarketFragment({
|
createMarketFragment({
|
||||||
id: 'market-1',
|
id: 'market-1',
|
||||||
state: MarketState.STATE_ACTIVE,
|
state: MarketState.STATE_ACTIVE,
|
||||||
|
// @ts-ignore data not on fragment
|
||||||
|
data: {
|
||||||
|
markPrice: '1',
|
||||||
|
},
|
||||||
// @ts-ignore candles not on fragment
|
// @ts-ignore candles not on fragment
|
||||||
candles: [
|
candles: [
|
||||||
{
|
{
|
||||||
volume: '200',
|
volume: '100',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
createMarketFragment({
|
createMarketFragment({
|
||||||
id: 'market-2',
|
id: 'market-2',
|
||||||
state: MarketState.STATE_ACTIVE,
|
state: MarketState.STATE_ACTIVE,
|
||||||
|
// @ts-ignore data not on fragment
|
||||||
|
data: {
|
||||||
|
markPrice: '1',
|
||||||
|
},
|
||||||
// @ts-ignore candles not on fragment
|
// @ts-ignore candles not on fragment
|
||||||
candles: [
|
candles: [
|
||||||
{
|
{
|
||||||
volume: '100',
|
volume: '300',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
createMarketFragment({
|
createMarketFragment({
|
||||||
state: MarketState.STATE_PENDING,
|
|
||||||
id: 'market-3',
|
id: 'market-3',
|
||||||
|
state: MarketState.STATE_ACTIVE,
|
||||||
|
// @ts-ignore data not on fragment
|
||||||
|
data: {
|
||||||
|
markPrice: '1',
|
||||||
|
},
|
||||||
// @ts-ignore candles not on fragment
|
// @ts-ignore candles not on fragment
|
||||||
candles: [
|
candles: [
|
||||||
{
|
{
|
||||||
volume: '100',
|
volume: '400',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
@ -375,14 +394,15 @@ describe('useMarketSelectorList', () => {
|
|||||||
const { result } = setup({
|
const { result } = setup({
|
||||||
searchTerm: '',
|
searchTerm: '',
|
||||||
product: Product.Future,
|
product: Product.Future,
|
||||||
sort: Sort.None,
|
sort: Sort.TopTraded,
|
||||||
assets: [],
|
assets: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result.current.markets).toEqual([
|
expect(result.current.markets).toEqual([
|
||||||
markets[1],
|
markets[3],
|
||||||
markets[2],
|
markets[2],
|
||||||
markets[0],
|
markets[0],
|
||||||
markets[3],
|
markets[1],
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import orderBy from 'lodash/orderBy';
|
import orderBy from 'lodash/orderBy';
|
||||||
import { MarketState } from '@vegaprotocol/types';
|
import { MarketState } from '@vegaprotocol/types';
|
||||||
import {
|
import { calcTradedFactor, useMarketList } from '@vegaprotocol/markets';
|
||||||
calcCandleVolume,
|
|
||||||
calcTradedFactor,
|
|
||||||
useMarketList,
|
|
||||||
} from '@vegaprotocol/markets';
|
|
||||||
import { priceChangePercentage } from '@vegaprotocol/utils';
|
import { priceChangePercentage } from '@vegaprotocol/utils';
|
||||||
import type { Filter } from '../../components/market-selector/market-selector';
|
import type { Filter } from '../../components/market-selector/market-selector';
|
||||||
import { Sort } from './sort-dropdown';
|
import { Sort } from './sort-dropdown';
|
||||||
@ -60,22 +56,6 @@ export const useMarketSelectorList = ({
|
|||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (sort === Sort.None) {
|
|
||||||
// Sort by market state primarily and AtoZ secondarily
|
|
||||||
return orderBy(
|
|
||||||
markets,
|
|
||||||
[
|
|
||||||
(m) => MARKET_TEMPLATE.indexOf(m.state),
|
|
||||||
(m) => {
|
|
||||||
if (!m.candles?.length) return 0;
|
|
||||||
const vol = calcCandleVolume(m.candles);
|
|
||||||
return Number(vol || 0);
|
|
||||||
},
|
|
||||||
],
|
|
||||||
['asc', 'desc']
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sort === Sort.Gained || sort === Sort.Lost) {
|
if (sort === Sort.Gained || sort === Sort.Lost) {
|
||||||
const dir = sort === Sort.Gained ? 'desc' : 'asc';
|
const dir = sort === Sort.Gained ? 'desc' : 'asc';
|
||||||
return orderBy(
|
return orderBy(
|
||||||
|
Loading…
Reference in New Issue
Block a user