diff --git a/apps/console-lite/src/app/hooks/use-calculate-slippage.ts b/apps/console-lite/src/app/hooks/use-calculate-slippage.ts index ebe91a8d4..13520587a 100644 --- a/apps/console-lite/src/app/hooks/use-calculate-slippage.ts +++ b/apps/console-lite/src/app/hooks/use-calculate-slippage.ts @@ -14,8 +14,7 @@ const useCalculateSlippage = ({ marketId, order }: Props) => { const variables = useMemo(() => ({ marketId }), [marketId]); const { data } = useOrderBookData({ variables, - resolution: 1, - throttleMilliseconds: 10000, + throttleMilliseconds: 5000, }); const volPriceArr = data?.depth[order.side === Side.SIDE_BUY ? 'sell' : 'buy'] || []; diff --git a/libs/market-depth/src/lib/use-orderbook-data.spec.tsx b/libs/market-depth/src/lib/use-orderbook-data.spec.tsx new file mode 100644 index 000000000..df74e69af --- /dev/null +++ b/libs/market-depth/src/lib/use-orderbook-data.spec.tsx @@ -0,0 +1,132 @@ +import { MockedProvider } from '@apollo/client/testing'; +import { renderHook, act } from '@testing-library/react'; +import { MarketTradingMode } from '@vegaprotocol/types'; +import type { + MarketDepth_market, + MarketDepth_market_data, +} from './__generated__'; +import { useOrderBookData } from './use-orderbook-data'; + +const mockData: MarketDepth_market = { + __typename: 'Market', + id: 'marketId', + decimalPlaces: 5, + positionDecimalPlaces: 0, + data: { + __typename: 'MarketData', + staticMidPrice: '7820', + marketTradingMode: MarketTradingMode.TRADING_MODE_CONTINUOUS, + indicativeVolume: '0', + indicativePrice: '0', + bestStaticBidPrice: '7820', + bestStaticOfferPrice: '7821', + market: { + __typename: 'Market', + id: 'marketId', + }, + }, + depth: { + __typename: 'MarketDepth', + lastTrade: { __typename: 'Trade', price: '7846' }, + sell: [ + { + __typename: 'PriceLevel', + price: '7861', + volume: '25631', + numberOfOrders: '4', + }, + ], + buy: [ + { + __typename: 'PriceLevel', + price: '7820', + volume: '28', + numberOfOrders: '1', + }, + ], + sequenceNumber: '1661857812317962664', + }, +}; + +let updateMock: ({ data }: { data: MarketDepth_market }) => boolean; + +const mockUseDataProvider = ({ update }: { update: () => boolean }) => { + updateMock = update; + return { data: mockData, loading: false, error: false }; +}; + +jest.mock('@vegaprotocol/react-helpers', () => ({ + ...jest.requireActual('@vegaprotocol/react-helpers'), + useDataProvider: jest.fn((args) => mockUseDataProvider(args)), +})); + +const modMock = (staticMidPrice: string): MarketDepth_market => { + return { + ...mockData, + data: { + ...mockData.data, + staticMidPrice, + } as MarketDepth_market_data, + }; +}; + +describe('useOrderBookData hook', () => { + it('should return proper data', () => { + const { result } = renderHook( + () => useOrderBookData({ variables: { marketId: 'marketId' } }), + { + wrapper: MockedProvider, + } + ); + expect(result.current.data).toEqual(mockData); + }); + + it('should update data object', () => { + const { result } = renderHook( + () => useOrderBookData({ variables: { marketId: 'marketId' } }), + { + wrapper: MockedProvider, + } + ); + expect(result.current.data?.data?.staticMidPrice).toEqual('7820'); + + const updateMockData = modMock('1111'); + + act(() => { + updateMock({ data: updateMockData }); + }); + expect(result.current.data?.data?.staticMidPrice).toEqual('1111'); + }); + + it('throttling should delay update', async () => { + const { result } = renderHook( + () => + useOrderBookData({ + variables: { marketId: 'marketId' }, + throttleMilliseconds: 500, + }), + { + wrapper: MockedProvider, + } + ); + expect(result.current.data?.data?.staticMidPrice).toEqual('7820'); + + const updateMockData = modMock('2222'); + const updateMockData2 = modMock('3333'); + + await act(async () => { + updateMock({ data: updateMockData }); + updateMock({ data: updateMockData2 }); + }); + + expect(result.current.data?.data?.staticMidPrice).toEqual('2222'); + await new Promise((res) => { + setTimeout(res, 400); + }); + expect(result.current.data?.data?.staticMidPrice).toEqual('2222'); + await new Promise((res) => { + setTimeout(res, 200); + }); + expect(result.current.data?.data?.staticMidPrice).toEqual('3333'); + }); +}); diff --git a/libs/market-depth/src/lib/use-orderbook-data.ts b/libs/market-depth/src/lib/use-orderbook-data.ts index fb3dd55eb..b94e7cded 100644 --- a/libs/market-depth/src/lib/use-orderbook-data.ts +++ b/libs/market-depth/src/lib/use-orderbook-data.ts @@ -1,84 +1,44 @@ -import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { useCallback, useEffect, useRef, useState } from 'react'; import throttle from 'lodash/throttle'; import { useDataProvider } from '@vegaprotocol/react-helpers'; -import { - compactRows, - updateCompactedRows, - mapMarketData, -} from './orderbook-data'; import dataProvider from './market-depth-data-provider'; -import type { OrderbookData } from './orderbook-data'; -import type { MarketDepthSubscription_marketDepthUpdate } from './__generated__/MarketDepthSubscription'; +import type { MarketDepth_market } from './__generated__'; interface Props { variables: { marketId: string }; - resolution: number; throttleMilliseconds?: number; } export const useOrderBookData = ({ variables, - resolution, throttleMilliseconds = 1000, }: Props) => { - const [orderbookData, setOrderbookData] = useState({ - rows: null, - }); - const resolutionRef = useRef(resolution); - const dataRef = useRef({ rows: null }); - const deltaRef = useRef(); + const [orderbookData, setOrderbookData] = useState( + null + ); + const dataRef = useRef(null); const updateOrderbookData = useRef( throttle(() => { - if (!deltaRef.current) { + if (!dataRef.current) { return; } - dataRef.current = { - ...deltaRef.current.market.data, - ...mapMarketData(deltaRef.current.market.data, resolutionRef.current), - rows: updateCompactedRows( - dataRef.current.rows ?? [], - deltaRef.current.sell, - deltaRef.current.buy, - resolutionRef.current - ), - }; - deltaRef.current = undefined; setOrderbookData(dataRef.current); }, throttleMilliseconds) ); const update = useCallback( - ({ delta }: { delta: MarketDepthSubscription_marketDepthUpdate }) => { - if (!dataRef.current.rows) { + ({ data }: { data: MarketDepth_market | null }) => { + if (!data) { return false; } - if (deltaRef.current) { - deltaRef.current.market = delta.market; - if (delta.sell) { - if (deltaRef.current.sell) { - deltaRef.current.sell.push(...delta.sell); - } else { - deltaRef.current.sell = delta.sell; - } - } - if (delta.buy) { - if (deltaRef.current.buy) { - deltaRef.current.buy.push(...delta.buy); - } else { - deltaRef.current.buy = delta.buy; - } - } - } else { - deltaRef.current = delta; - } + dataRef.current = data; updateOrderbookData.current(); return true; }, - // using resolutionRef.current to avoid using resolution as a dependency - it will cause data provider restart on resolution change [] ); - const { data, error, loading, flush } = useDataProvider({ + const { data, error, loading } = useDataProvider({ dataProvider, update, variables, @@ -87,38 +47,22 @@ export const useOrderBookData = ({ useEffect(() => { const throttleRunnner = updateOrderbookData.current; if (!data) { - dataRef.current = { rows: null }; + dataRef.current = null; setOrderbookData(dataRef.current); return; } dataRef.current = { - ...data.data, - rows: compactRows(data.depth.sell, data.depth.buy, resolution), - ...mapMarketData(data.data, resolution), + ...data, }; setOrderbookData(dataRef.current); - return () => { throttleRunnner.cancel(); }; - }, [data, resolution]); - - useEffect(() => { - resolutionRef.current = resolution; - flush(); - }, [resolution, flush]); - - const dataProps = useMemo( - () => ({ - loading, - error, - data, - }), - [data, loading, error] - ); + }, [data]); return { - ...dataProps, - orderbookData, + loading, + error, + data: orderbookData, }; };