fix: insufficient balance error doesn't reset validation after balance has been fulfilled (#2336)

* fix: live validation in deal ticket - reset validation after market state or account balance changes

* fix: live validation in deal ticket - reset fix lint error

* fix: live validation in deal ticket - adjust failing int test
This commit is contained in:
macqbat 2022-12-07 08:45:30 +01:00 committed by GitHub
parent 5db3b9c0b9
commit 46a85b8e65
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 194 additions and 64 deletions

View File

@ -7,6 +7,7 @@ import { generateEstimateOrder } from '../support/mocks/generate-fees';
import { aliasQuery, mockConnectWallet } from '@vegaprotocol/cypress';
import { testOrder } from '../support/deal-ticket-transaction';
import type { OrderSubmission } from '@vegaprotocol/wallet';
import { generateAccounts } from '../support/mocks/generate-accounts';
const orderSizeField = 'order-size';
const orderPriceField = 'order-price';
@ -622,55 +623,91 @@ describe('suspended market validation', { tags: '@regression' }, () => {
});
describe('account validation', { tags: '@regression' }, () => {
beforeEach(() => {
cy.mockTradingPage();
cy.mockGQL((req) => {
aliasQuery(
req,
'EstimateOrder',
generateEstimateOrder({
estimateOrder: {
marginLevels: {
__typename: 'MarginLevels',
initialLevel: '1000000000',
describe('zero balance error', () => {
beforeEach(() => {
cy.mockTradingPage();
cy.mockGQL((req) => {
aliasQuery(
req,
'Accounts',
generateAccounts({
party: {
accountsConnection: {
edges: [
{
node: {
type: Schema.AccountType.ACCOUNT_TYPE_GENERAL,
balance: '0',
market: null,
asset: {
__typename: 'Asset',
id: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c',
},
},
},
],
},
},
},
})
);
})
);
});
cy.mockGQLSubscription();
cy.visit('/#/markets/market-0');
cy.connectVegaWallet();
cy.wait('@Market');
});
it('should show an error if your balance is zero', () => {
cy.getByTestId('place-order').should('not.be.disabled');
cy.getByTestId('place-order').click();
cy.getByTestId('place-order').should('be.disabled');
//7002-SORD-003
cy.getByTestId('dealticket-error-message-zero-balance').should(
'have.text',
'Insufficient balance. Deposit ' + 'tBTC'
);
cy.getByTestId('deal-ticket-deposit-dialog-button').should('exist');
});
cy.mockGQLSubscription();
cy.visit('/#/markets/market-0');
cy.connectVegaWallet();
cy.wait('@Market');
});
it('should show an error if your balance is zero', () => {
cy.getByTestId('place-order').should('not.be.disabled');
cy.getByTestId('place-order').click();
cy.getByTestId('place-order').should('be.disabled');
//7002-SORD-003
cy.getByTestId('dealticket-error-message-zero-balance').should(
'have.text',
'Insufficient balance. Deposit ' + 'tBTC'
);
cy.getByTestId('deal-ticket-deposit-dialog-button').should('exist');
});
it('should display info and button for deposit', () => {
//7002-SORD-003
// warning should show immediately
cy.getByTestId('dealticket-warning-margin').should(
'contain.text',
'You may not have enough margin available to open this position'
);
cy.getByTestId('dealticket-warning-margin').should(
'contain.text',
'10,000.00 tBTC currently required, 1,000.00 tBTC available'
);
cy.getByTestId('deal-ticket-deposit-dialog-button').click();
cy.getByTestId('dialog-content')
.find('h1')
.eq(0)
.should('have.text', 'Deposit');
describe('not enough balance warning', () => {
beforeEach(() => {
cy.mockTradingPage();
cy.mockGQL((req) => {
aliasQuery(
req,
'EstimateOrder',
generateEstimateOrder({
estimateOrder: {
marginLevels: {
__typename: 'MarginLevels',
initialLevel: '1000000000',
},
},
})
);
});
cy.mockGQLSubscription();
cy.visit('/#/markets/market-0');
cy.connectVegaWallet();
cy.wait('@Market');
});
it('should display info and button for deposit', () => {
//7002-SORD-003
// warning should show immediately
cy.getByTestId('dealticket-warning-margin').should(
'contain.text',
'You may not have enough margin available to open this position'
);
cy.getByTestId('dealticket-warning-margin').should(
'contain.text',
'10,000.00 tBTC currently required, 1,000.00 tBTC available'
);
cy.getByTestId('deal-ticket-deposit-dialog-button').click();
cy.getByTestId('dialog-content')
.find('h1')
.eq(0)
.should('have.text', 'Deposit');
});
});
});

View File

@ -1,6 +1,12 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { VegaWalletContext } from '@vegaprotocol/wallet';
import { fireEvent, render, screen, act } from '@testing-library/react';
import {
fireEvent,
render,
screen,
act,
waitFor,
} from '@testing-library/react';
import { generateMarket } from '../../test-helpers';
import { DealTicket } from './deal-ticket';
import { Schema } from '@vegaprotocol/types';
@ -9,6 +15,14 @@ import type { MockedResponse } from '@apollo/client/testing';
import { MockedProvider } from '@apollo/client/testing';
import type { ChainIdQuery } from '@vegaprotocol/react-helpers';
import { ChainIdDocument, addDecimal } from '@vegaprotocol/react-helpers';
import * as utils from '../../utils';
let mockHasNoBalance = false;
jest.mock('../../hooks/use-has-no-balance', () => {
return {
useHasNoBalance: () => mockHasNoBalance,
};
});
const market = generateMarket();
const submit = jest.fn();
@ -31,7 +45,7 @@ function generateJsx(order?: OrderSubmissionBody['orderSubmission']) {
};
return (
<MockedProvider mocks={[chainIdMock]}>
<VegaWalletContext.Provider value={{} as any}>
<VegaWalletContext.Provider value={{ pubKey: mockChainId } as any}>
<DealTicket
defaultOrder={order}
market={market}
@ -45,7 +59,11 @@ function generateJsx(order?: OrderSubmissionBody['orderSubmission']) {
describe('DealTicket', () => {
beforeEach(() => window.localStorage.clear());
afterEach(() => window.localStorage.clear());
afterEach(() => {
window.localStorage.clear();
jest.clearAllMocks();
});
it('should display ticket defaults', () => {
render(generateJsx());
@ -165,4 +183,48 @@ describe('DealTicket', () => {
Schema.OrderTimeInForce.TIME_IN_FORCE_IOC
);
});
it('validation should be reset', async () => {
mockHasNoBalance = true;
jest.spyOn(utils, 'validateMarketState').mockReturnValue('Wrong state');
jest
.spyOn(utils, 'validateMarketTradingMode')
.mockReturnValue('Wrong trading mode');
const { rerender } = render(generateJsx());
await act(async () => {
fireEvent.click(screen.getByTestId('place-order'));
});
await waitFor(async () => {
expect(
await screen.getByTestId('dealticket-error-message-summary')
).toHaveTextContent('Wrong state');
});
jest.spyOn(utils, 'validateMarketState').mockReturnValue(true);
await act(async () => {
rerender(generateJsx());
});
await act(async () => {
fireEvent.click(screen.getByTestId('place-order'));
});
await waitFor(async () => {
expect(
await screen.getByTestId('dealticket-error-message-zero-balance')
).toHaveTextContent('Insufficient balance.');
});
mockHasNoBalance = false;
await act(async () => {
rerender(generateJsx());
});
await act(async () => {
fireEvent.click(screen.getByTestId('place-order'));
});
await waitFor(async () => {
expect(
await screen.getByTestId('dealticket-error-message-summary')
).toHaveTextContent('Wrong trading mode');
});
});
});

View File

@ -23,7 +23,7 @@ import {
validateType,
} from '../../utils';
import { ZeroBalanceError } from '../deal-ticket-validation/zero-balance-error';
import { AccountValidationType } from '../../constants';
import { SummaryValidationType } from '../../constants';
import { useHasNoBalance } from '../../hooks/use-has-no-balance';
import type { MarketDealTicket } from '@vegaprotocol/market-list';
@ -55,18 +55,42 @@ export const DealTicket = ({
handleSubmit,
watch,
setError,
clearErrors,
formState: { errors },
} = useForm<DealTicketFormFields>({
defaultValues: persistedOrder || getDefaultOrder(market),
});
const order = watch();
// When order state changes persist it in local storage
useEffect(() => setPersistedOrder(order), [order, setPersistedOrder]);
const marketStateError = validateMarketState(market.data.marketState);
const hasNoBalance = useHasNoBalance(
market.tradableInstrument.instrument.product.settlementAsset.id
);
const marketTradingModeError = validateMarketTradingMode(
market.data.marketTradingMode
);
useEffect(() => {
if (
(!hasNoBalance &&
errors.summary?.type === SummaryValidationType.NoCollateral) ||
(marketStateError === true &&
errors.summary?.type === SummaryValidationType.MarketState) ||
(marketTradingModeError === true &&
errors.summary?.type === SummaryValidationType.TradingMode)
) {
clearErrors('summary');
}
}, [
hasNoBalance,
marketStateError,
marketTradingModeError,
clearErrors,
errors.summary?.message,
errors.summary?.type,
]);
// When order state changes persist it in local storage
useEffect(() => setPersistedOrder(order), [order, setPersistedOrder]);
const onSubmit = useCallback(
(order: OrderSubmissionBody['orderSubmission']) => {
@ -75,22 +99,27 @@ export const DealTicket = ({
return;
}
const marketStateError = validateMarketState(market.data.marketState);
if (marketStateError !== true) {
setError('summary', { message: marketStateError });
setError('summary', {
message: marketStateError,
type: SummaryValidationType.MarketState,
});
return;
}
if (hasNoBalance) {
setError('summary', { message: AccountValidationType.NoCollateral });
setError('summary', {
message: SummaryValidationType.NoCollateral,
type: SummaryValidationType.NoCollateral,
});
return;
}
const marketTradingModeError = validateMarketTradingMode(
market.data.marketTradingMode
);
if (marketTradingModeError !== true) {
setError('summary', { message: marketTradingModeError });
setError('summary', {
message: marketTradingModeError,
type: SummaryValidationType.TradingMode,
});
return;
}
@ -110,8 +139,8 @@ export const DealTicket = ({
hasNoBalance,
market.positionDecimalPlaces,
market.decimalPlaces,
market.data.marketState,
market.data.marketTradingMode,
marketStateError,
marketTradingModeError,
setError,
]
);
@ -210,7 +239,7 @@ const SummaryMessage = memo(
market,
order,
});
if (errorMessage === AccountValidationType.NoCollateral) {
if (errorMessage === SummaryValidationType.NoCollateral) {
return (
<ZeroBalanceError
asset={market.tradableInstrument.instrument.product.settlementAsset}

View File

@ -30,6 +30,8 @@ export enum MarketModeValidationType {
Auction = 'Auction',
}
export enum AccountValidationType {
export enum SummaryValidationType {
NoCollateral = 'NoCollateral',
TradingMode = 'MarketTradingMode',
MarketState = 'MarketState',
}