chore(trading): positions and collateral tab remove infinitive scroll (#2898)

This commit is contained in:
Maciek 2023-02-13 15:48:44 +01:00 committed by GitHub
parent 8bb6b7a942
commit ee67736ac5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 253 additions and 69 deletions

View File

@ -1,3 +1,5 @@
import { checkSorting } from '@vegaprotocol/cypress';
beforeEach(() => {
cy.mockTradingPage();
cy.mockWeb3Provider();
@ -36,6 +38,96 @@ describe('accounts', { tags: '@smoke' }, () => {
cy.getByTestId('tab-accounts')
.get(tradingAccountRowId)
.find('[col-id="deposited"]')
.should('have.text', '1,000.00');
.should('have.text', '1,001.00');
});
describe('sorting by ag-grid columns should work well', () => {
it('sorting by asset', () => {
cy.getByTestId('Collateral').click();
const marketsSortedDefault = ['tBTC', 'AST0', 'tEURO', 'tDAI', 'tBTC'];
const marketsSortedAsc = ['AST0', 'tBTC', 'tBTC', 'tDAI', 'tEURO'];
const marketsSortedDesc = ['tEURO', 'tDAI', 'tBTC', 'tBTC', 'AST0'];
checkSorting(
'asset.symbol',
marketsSortedDefault,
marketsSortedAsc,
marketsSortedDesc
);
});
it('sorting by total', () => {
cy.getByTestId('Collateral').click();
const marketsSortedDefault = [
'1,000.00002',
'1,001.00',
'1,000.01',
'1,000.01',
'1,000.00001',
];
const marketsSortedAsc = [
'1,000.00001',
'1,000.00002',
'1,000.01',
'1,000.01',
'1,001.00',
];
const marketsSortedDesc = [
'1,001.00',
'1,000.01',
'1,000.01',
'1,000.00002',
'1,000.00001',
];
checkSorting(
'deposited',
marketsSortedDefault,
marketsSortedAsc,
marketsSortedDesc
);
});
it('sorting by used', () => {
cy.getByTestId('Collateral').click();
const marketsSortedDefault = ['0.00', '1.00', '0.01', '0.01', '0.00'];
const marketsSortedAsc = ['0.00', '0.00', '0.01', '0.01', '1.00'];
const marketsSortedDesc = ['1.00', '0.01', '0.01', '0.00', '0.00'];
checkSorting(
'used',
marketsSortedDefault,
marketsSortedAsc,
marketsSortedDesc
);
});
it('sorting by available', () => {
cy.getByTestId('Collateral').click();
const marketsSortedDefault = [
'1,000.00002',
'1,000.00',
'1,000.00',
'1,000.00',
'1,000.00001',
];
const marketsSortedAsc = [
'1,000.00',
'1,000.00',
'1,000.00',
'1,000.00001',
'1,000.00002',
];
const marketsSortedDesc = [
'1,000.00002',
'1,000.00001',
'1,000.00',
'1,000.00',
'1,000.00',
];
checkSorting(
'available',
marketsSortedDefault,
marketsSortedAsc,
marketsSortedDesc
);
});
});
});

View File

@ -1,3 +1,4 @@
import { checkSorting } from '@vegaprotocol/cypress';
import { aliasGQLQuery } from '@vegaprotocol/cypress';
import { marketsDataQuery } from '@vegaprotocol/mock';
@ -61,6 +62,54 @@ describe('positions', { tags: '@smoke' }, () => {
});
});
describe('sorting by ag-grid columns should work well', () => {
it('sorting by Market', () => {
cy.visit('/#/markets/market-0');
const marketsSortedDefault = [
'ACTIVE MARKET',
'Apple Monthly (30 Jun 2022)',
];
const marketsSortedAsc = ['ACTIVE MARKET', 'Apple Monthly (30 Jun 2022)'];
const marketsSortedDesc = [
'Apple Monthly (30 Jun 2022)',
'ACTIVE MARKET',
];
cy.getByTestId('Positions').click();
checkSorting(
'marketName',
marketsSortedDefault,
marketsSortedAsc,
marketsSortedDesc
);
});
it('sorting by notional', () => {
cy.visit('/#/markets/market-0');
const marketsSortedDefault = ['276,761.40348', '46,126.90058'];
const marketsSortedAsc = ['46,126.90058', '276,761.40348'];
const marketsSortedDesc = ['276,761.40348', '46,126.90058'];
cy.getByTestId('Positions').click();
checkSorting(
'notional',
marketsSortedDefault,
marketsSortedAsc,
marketsSortedDesc
);
});
it('sorting by unrealisedPNL', () => {
cy.visit('/#/markets/market-0');
const marketsSortedDefault = ['8.95', '-0.22519'];
const marketsSortedAsc = ['-0.22519', '8.95'];
const marketsSortedDesc = ['8.95', '-0.22519'];
cy.getByTestId('Positions').click();
checkSorting(
'unrealisedPNL',
marketsSortedDefault,
marketsSortedAsc,
marketsSortedDesc
);
});
});
function validatePositionsDisplayed() {
cy.getByTestId('tab-positions').should('be.visible');
cy.getByTestId('tab-positions').within(() => {

View File

@ -66,7 +66,7 @@ describe('withdraw form validation', { tags: '@smoke' }, () => {
// 1002-WITH-004
selectAsset(ASSET_SEPOLIA_TBTC);
cy.getByTestId(useMaximumAmount).click();
cy.get(amountField).should('have.value', '1000.00000');
cy.get(amountField).should('have.value', '1000.00001');
});
});
@ -102,7 +102,10 @@ describe('withdraw actions', { tags: '@regression' }, () => {
'contain.text',
'Balance available'
);
cy.getByTestId('BALANCE_AVAILABLE_value').should('have.text', '1,000.00');
cy.getByTestId('BALANCE_AVAILABLE_value').should(
'have.text',
'1,000.00001'
);
cy.getByTestId('WITHDRAWAL_THRESHOLD_label').should(
'contain.text',
'Delayed withdrawal threshold'

View File

@ -1,26 +1,37 @@
import { act, render, screen, waitFor } from '@testing-library/react';
import {
act,
getAllByRole,
render,
screen,
waitFor,
} from '@testing-library/react';
import * as helpers from '@vegaprotocol/react-helpers';
import type { AccountFields } from './accounts-data-provider';
import { AccountManager } from './accounts-manager';
let mockedUpdate: ({
delta,
data,
}: {
delta?: never;
data: AccountFields[] | null;
}) => boolean;
const mockedUseDataProvider = jest.fn();
jest.mock('@vegaprotocol/react-helpers', () => ({
...jest.requireActual('@vegaprotocol/react-helpers'),
useDataProvider: jest.fn((args) => {
mockedUpdate = args.update;
return {
data: [],
};
}),
useDataProvider: jest.fn(() => mockedUseDataProvider()),
}));
describe('AccountManager', () => {
beforeEach(() => {
mockedUseDataProvider
.mockImplementationOnce((args) => {
return {
data: [],
};
})
.mockImplementationOnce((args) => {
return {
data: [
{ asset: { id: 'a1' }, party: { id: 't1' } },
{ asset: { id: 'a2' }, party: { id: 't2' } },
],
};
});
});
it('change partyId should reload data provider', async () => {
const { rerender } = render(
<AccountManager
@ -47,8 +58,22 @@ describe('AccountManager', () => {
});
it('update method should return proper result', async () => {
let rerenderer: (ui: React.ReactElement) => void;
await act(() => {
render(
const { rerender } = render(
<AccountManager
partyId="partyOne"
onClickAsset={jest.fn}
isReadOnly={false}
/>
);
rerenderer = rerender;
});
await waitFor(() => {
expect(screen.getByText('No accounts')).toBeInTheDocument();
});
await act(() => {
rerenderer(
<AccountManager
partyId="partyOne"
onClickAsset={jest.fn}
@ -56,20 +81,11 @@ describe('AccountManager', () => {
/>
);
});
await waitFor(() => {
expect(screen.getByText('No accounts')).toBeInTheDocument();
});
await act(() => {
expect(mockedUpdate({ data: [] })).toEqual(true);
expect(
mockedUpdate({ data: [{ party: { id: 't1' } }] as AccountFields[] })
).toEqual(false);
expect(
mockedUpdate({ data: [{ party: { id: 't2' } }] as AccountFields[] })
).toEqual(true);
expect(mockedUpdate({ data: [] })).toEqual(false);
expect(mockedUpdate({ data: [] })).toEqual(true);
const container = document.querySelector('.ag-center-cols-container');
await waitFor(() => {
expect(container).toBeInTheDocument();
});
expect(getAllByRole(container as HTMLDivElement, 'row')).toHaveLength(2);
});
});

View File

@ -1,14 +1,9 @@
import {
t,
useDataProvider,
updateGridData,
} from '@vegaprotocol/react-helpers';
import { t, useDataProvider } from '@vegaprotocol/react-helpers';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import type { AgGridReact } from 'ag-grid-react';
import { useRef, useMemo, useCallback, memo } from 'react';
import { useRef, useMemo, memo } from 'react';
import type { AccountFields } from './accounts-data-provider';
import { aggregatedAccountsDataProvider } from './accounts-data-provider';
import type { GetRowsParams } from './accounts-table';
import { AccountTable } from './accounts-table';
interface AccountManagerProps {
@ -27,40 +22,22 @@ export const AccountManager = ({
isReadOnly,
}: AccountManagerProps) => {
const gridRef = useRef<AgGridReact | null>(null);
const dataRef = useRef<AccountFields[] | null>(null);
const variables = useMemo(() => ({ partyId }), [partyId]);
const update = useCallback(
({ data }: { data: AccountFields[] | null }) => {
return updateGridData(dataRef, data, gridRef);
},
[gridRef]
);
const { data, loading, error } = useDataProvider<AccountFields[], never>({
dataProvider: aggregatedAccountsDataProvider,
update,
variables,
});
const getRows = useCallback(
async ({ successCallback, startRow, endRow }: GetRowsParams) => {
const rowsThisBlock = dataRef.current
? dataRef.current.slice(startRow, endRow)
: [];
const lastRow = dataRef.current ? dataRef.current.length : 0;
successCallback(rowsThisBlock, lastRow);
},
[]
);
return (
<div className="relative h-full">
<AccountTable
rowModelType="infinite"
ref={gridRef}
datasource={{ getRows }}
rowData={data}
onClickAsset={onClickAsset}
onClickDeposit={onClickDeposit}
onClickWithdraw={onClickWithdraw}
isReadOnly={isReadOnly}
noRowsOverlayComponent={() => null}
/>
<div className="pointer-events-none absolute inset-0">
<AsyncRenderer

View File

@ -78,6 +78,19 @@ export const accountFields: AccountFieldsFragment[] = [
id: 'asset-id-2',
},
},
{
__typename: 'AccountBalance',
type: Schema.AccountType.ACCOUNT_TYPE_MARGIN,
balance: '100000',
market: {
__typename: 'Market',
id: 'market-3',
},
asset: {
__typename: 'Asset',
id: 'asset-0',
},
},
{
__typename: 'AccountBalance',
type: Schema.AccountType.ACCOUNT_TYPE_GENERAL,
@ -92,7 +105,7 @@ export const accountFields: AccountFieldsFragment[] = [
{
__typename: 'AccountBalance',
type: Schema.AccountType.ACCOUNT_TYPE_GENERAL,
balance: '100000000',
balance: '100000001',
market: null,
asset: {
__typename: 'Asset',
@ -102,7 +115,7 @@ export const accountFields: AccountFieldsFragment[] = [
{
__typename: 'AccountBalance',
type: Schema.AccountType.ACCOUNT_TYPE_GENERAL,
balance: '100000000',
balance: '100000002',
market: null,
asset: {
__typename: 'Asset',

View File

@ -36,3 +36,29 @@ export async function promiseWithTimeout(
return await Promise.race([promise, rejectAfterTimeout(time)]);
}
export const checkSorting = (
column: string,
orderTabDefault: string[],
orderTabAsc: string[],
orderTabDesc: string[]
) => {
checkSortChange(orderTabDefault, column);
cy.get('.ag-header-container').within(() => {
cy.get(`[col-id="${column}"]`).click();
});
checkSortChange(orderTabAsc, column);
cy.get('.ag-header-container').within(() => {
cy.get(`[col-id="${column}"]`).click();
});
checkSortChange(orderTabDesc, column);
};
const checkSortChange = (tabsArr: string[], column: string) => {
cy.get('.ag-center-cols-container').within(() => {
tabsArr.forEach((entry, i) => {
cy.get(`[row-index="${i}"]`).within(() => {
cy.get(`[col-id="${column}"]`).should('have.text', tabsArr[i]);
});
});
});
};

View File

@ -28,6 +28,7 @@ describe('useFillsList Hook', () => {
current: {
api: {
refreshInfiniteCache: mockRefreshAgGridApi,
getModel: () => ({ getType: () => 'infinite' }),
},
} as unknown as AgGridReact,
};

View File

@ -34,6 +34,7 @@ describe('useOrderListData Hook', () => {
current: {
api: {
refreshInfiniteCache: mockRefreshAgGridApi,
getModel: () => ({ getType: () => 'infinite' }),
},
} as unknown as AgGridReact,
};

View File

@ -18,7 +18,7 @@ export const PositionsManager = ({
isReadOnly,
}: PositionsManagerProps) => {
const gridRef = useRef<AgGridReact | null>(null);
const { data, error, loading, getRows } = usePositionsData(partyId, gridRef);
const { data, error, loading } = usePositionsData(partyId, gridRef, true);
const create = useVegaTransactionStore((store) => store.create);
const onClose = ({
marketId,
@ -51,9 +51,8 @@ export const PositionsManager = ({
return (
<div className="h-full relative">
<PositionsTable
rowModelType="infinite"
rowData={data}
ref={gridRef}
datasource={{ getRows }}
onMarketClick={onMarketClick}
onClose={onClose}
noRowsOverlayComponent={() => null}

View File

@ -142,7 +142,10 @@ export const PositionsTable = forwardRef<AgGridReact, Props>(
}: VegaValueGetterParams<Position, 'openVolume'>) => {
return data?.openVolume === undefined
? undefined
: toBigNum(data?.openVolume, data.decimals).toNumber();
: toBigNum(
data?.openVolume,
data.positionDecimalPlaces
).toNumber();
}}
valueFormatter={({
data,

View File

@ -69,6 +69,7 @@ describe('usePositionData Hook', () => {
api: {
refreshInfiniteCache: mockRefreshInfiniteCache,
getRowNode: mockGetRowNode,
getModel: () => ({ getType: () => 'infinite' }),
},
} as unknown as AgGridReact,
};

View File

@ -11,7 +11,8 @@ export const getRowId = ({ data }: { data: Position }) => data.marketId;
export const usePositionsData = (
partyId: string,
gridRef: RefObject<AgGridReact>
gridRef: RefObject<AgGridReact>,
clientSideModel?: boolean
) => {
const variables = useMemo<PositionsMetricsProviderVariables>(
() => ({ partyId }),
@ -20,9 +21,9 @@ export const usePositionsData = (
const dataRef = useRef<Position[] | null>(null);
const update = useCallback(
({ data }: { data: Position[] | null }) => {
return updateGridData(dataRef, data, gridRef);
return clientSideModel ? false : updateGridData(dataRef, data, gridRef);
},
[gridRef]
[gridRef, clientSideModel]
);
const { data, error, loading } = useDataProvider({
dataProvider: positionsMetricsProvider,

View File

@ -12,7 +12,9 @@ export const updateGridData = (
) => {
const rerender = isXOrWasEmpty(dataRef.current, data);
dataRef.current = data;
gridRef.current?.api?.refreshInfiniteCache();
if (gridRef.current?.api?.getModel().getType() === 'infinite') {
gridRef.current?.api?.refreshInfiniteCache();
}
return !rerender;
};