Compare commits
13
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
379aba5438 | ||
|
|
06720f890f | ||
|
|
58ce016d4d | ||
|
|
ac16eb06f5 | ||
|
|
3319871617 | ||
|
|
37e850304a | ||
|
|
655131c744 | ||
|
|
f1cb1b9408 | ||
|
|
891ac527a2 | ||
|
|
546087fb9f | ||
|
|
f7a82d33ba | ||
|
|
d85f413e41 | ||
|
|
c7d0025b4f |
@@ -12,6 +12,7 @@ import {
|
||||
SPECIAL_CASE_NETWORK,
|
||||
SPECIAL_CASE_NETWORK_ID,
|
||||
} from '../../links/party-link/party-link';
|
||||
import { txSignatureToDeterministicId } from '../lib/deterministic-ids';
|
||||
|
||||
type Transfer = components['schemas']['commandsv1Transfer'];
|
||||
|
||||
@@ -63,10 +64,16 @@ export const TxDetailsTransfer = ({
|
||||
return (
|
||||
<>
|
||||
<TableWithTbody className="mb-8" allowWrap={true}>
|
||||
<TableRow modifier="bordered">
|
||||
<TableRow modifier="bordered" data-testid="type">
|
||||
<TableCell {...sharedHeaderProps}>{t('Type')}</TableCell>
|
||||
<TableCell>{getTypeLabelForTransfer(transfer)}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow modifier="bordered" data-testid="id">
|
||||
<TableCell {...sharedHeaderProps}>{t('Transfer ID')}</TableCell>
|
||||
<TableCell>
|
||||
{txSignatureToDeterministicId(txData.signature.value)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TxDetailsShared
|
||||
txData={txData}
|
||||
pubKey={pubKey}
|
||||
@@ -74,7 +81,7 @@ export const TxDetailsTransfer = ({
|
||||
hideTypeRow={true}
|
||||
/>
|
||||
{from ? (
|
||||
<TableRow modifier="bordered">
|
||||
<TableRow modifier="bordered" data-testid="from">
|
||||
<TableCell>{t('From')}</TableCell>
|
||||
<TableCell>
|
||||
<PartyLink id={from} />
|
||||
@@ -82,7 +89,7 @@ export const TxDetailsTransfer = ({
|
||||
</TableRow>
|
||||
) : null}
|
||||
{transfer.to ? (
|
||||
<TableRow modifier="bordered">
|
||||
<TableRow modifier="bordered" data-testid="to">
|
||||
<TableCell>{t('To')}</TableCell>
|
||||
<TableCell>
|
||||
<PartyLink id={transfer.to} />
|
||||
@@ -90,7 +97,7 @@ export const TxDetailsTransfer = ({
|
||||
</TableRow>
|
||||
) : null}
|
||||
{transfer.asset && transfer.amount ? (
|
||||
<TableRow modifier="bordered">
|
||||
<TableRow modifier="bordered" data-testid="amount">
|
||||
<TableCell>{t('Amount')}</TableCell>
|
||||
<TableCell>
|
||||
<SizeInAsset assetId={transfer.asset} size={transfer.amount} />
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
import { getTypeLabelForTransfer } from './details/tx-transfer';
|
||||
import type { BlockExplorerTransactionResult } from '../../routes/types/block-explorer-response';
|
||||
import type { TendermintBlocksResponse } from '../../routes/blocks/tendermint-blocks-response';
|
||||
import type { components } from '../../../types/explorer';
|
||||
|
||||
import {
|
||||
TxDetailsTransfer,
|
||||
getTypeLabelForTransfer,
|
||||
} from './details/tx-transfer';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { render } from '@testing-library/react';
|
||||
|
||||
type Transfer = components['schemas']['commandsv1Transfer'];
|
||||
|
||||
describe('TX: Transfer: getLabelForTransfer', () => {
|
||||
@@ -56,3 +65,70 @@ describe('TX: Transfer: getLabelForTransfer', () => {
|
||||
expect(getTypeLabelForTransfer(mock)).toEqual('Transfer');
|
||||
});
|
||||
});
|
||||
|
||||
describe('TxDetailsTransfer', () => {
|
||||
const mockBlockData = {
|
||||
result: {
|
||||
block: {
|
||||
header: {
|
||||
height: '123',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const mockTxData: Partial<BlockExplorerTransactionResult> = {
|
||||
hash: 'test',
|
||||
submitter:
|
||||
'e1943eea46fed576cf2be42972f3c5515ad3d0ac7ac013f56677c12a53a1b3ed',
|
||||
command: {
|
||||
nonce: '5188810881378065222',
|
||||
blockHeight: '14951513',
|
||||
transfer: {
|
||||
fromAccountType: 'ACCOUNT_TYPE_GENERAL',
|
||||
to: '78432a2808f20b18a46ccc6a917bdc4d63c2b9e7007f777bdcab5a9f462c5ba6',
|
||||
toAccountType: 'ACCOUNT_TYPE_GENERAL',
|
||||
asset:
|
||||
'dd20590509d30d20bdbbe64dc1090c1140c7690121a9b9940bc66f62dfa2e599',
|
||||
amount: '4800000000',
|
||||
reference: '',
|
||||
oneOff: {
|
||||
deliverOn: '0',
|
||||
},
|
||||
},
|
||||
},
|
||||
signature: {
|
||||
value:
|
||||
'610c2e196a7d4fed4413b9e82af267b1ff3e30e943df3a3d28096fd60604d430d752fbaf6dd4f84d496be78885bb6118f40560bff7832c06bd7a3d67b718b700',
|
||||
},
|
||||
};
|
||||
|
||||
it('renders basic transfer details', () => {
|
||||
const { getByTestId } = render(
|
||||
<MockedProvider>
|
||||
<MemoryRouter>
|
||||
<TxDetailsTransfer
|
||||
txData={mockTxData as BlockExplorerTransactionResult}
|
||||
pubKey={mockTxData.command.submitter}
|
||||
blockData={mockBlockData as TendermintBlocksResponse}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</MockedProvider>
|
||||
);
|
||||
|
||||
const id = getByTestId('id');
|
||||
expect(id.children[0].textContent).toEqual('Transfer ID');
|
||||
expect(id.children[1].textContent).toEqual(
|
||||
'51f3bab5eb2637651012507a64d497790a734248792c16e5cf36df8984074fbd'
|
||||
);
|
||||
|
||||
const type = getByTestId('type');
|
||||
expect(type.children[1].textContent).toEqual('Transfer');
|
||||
|
||||
const from = getByTestId('from');
|
||||
expect(from.children[1].textContent).toEqual(mockTxData.submitter);
|
||||
|
||||
const to = getByTestId('to');
|
||||
expect(to.children[1].textContent).toEqual(mockTxData.command.transfer.to);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -48,8 +48,8 @@ describe('capsule - without MultiSign', { tags: '@slow' }, () => {
|
||||
cy.get('@markets').then((markets) => {
|
||||
cy.wrap(markets[0]).as('market');
|
||||
});
|
||||
cy.setOnBoardingViewed();
|
||||
cy.visit('/#/portfolio');
|
||||
cy.connectVegaWallet();
|
||||
});
|
||||
|
||||
it('can deposit', function () {
|
||||
@@ -70,6 +70,8 @@ describe('capsule - without MultiSign', { tags: '@slow' }, () => {
|
||||
cy.getByTestId('deposit-button').click();
|
||||
connectEthereumWallet('Unknown');
|
||||
selectAsset(btcName);
|
||||
cy.get('[data-testid="rich-select-option"]').eq(btcName).click();
|
||||
|
||||
cy.getByTestId('approve-default').should(
|
||||
'contain.text',
|
||||
`Before you can make a deposit of your chosen asset, ${btcSymbol}, you need to approve its use in your Ethereum wallet`
|
||||
@@ -120,7 +122,7 @@ describe('capsule - without MultiSign', { tags: '@slow' }, () => {
|
||||
cy.get('[data-testid="pathname-/portfolio"]').should('exist');
|
||||
|
||||
cy.getByTestId(collateralTab).click();
|
||||
cy.getByTestId('open-transfer').click();
|
||||
cy.getByTestId('open-transfer').eq(1).click();
|
||||
cy.getByTestId('transfer-form').should('be.visible');
|
||||
cy.getByTestId('transfer-form').find('[name="toAddress"]').select(1);
|
||||
cy.get('select option')
|
||||
@@ -147,7 +149,8 @@ describe('capsule - without MultiSign', { tags: '@slow' }, () => {
|
||||
// 0003-WTXN-011
|
||||
cy.getByTestId('Withdrawals').click();
|
||||
cy.getByTestId('withdraw-dialog-button').click();
|
||||
selectAsset(0);
|
||||
selectAsset(btcName);
|
||||
cy.get('[data-testid="rich-select-option"]').eq(btcName).click();
|
||||
cy.get(amountField).focus();
|
||||
cy.get(amountField).clear().type('1');
|
||||
cy.getByTestId('submit-withdrawal').click();
|
||||
@@ -180,24 +183,21 @@ describe('capsule', { tags: '@slow', testIsolation: true }, () => {
|
||||
cy.get('@markets').then((markets) => {
|
||||
cy.wrap(markets[0]).as('market');
|
||||
});
|
||||
cy.setOnBoardingViewed();
|
||||
cy.setVegaWallet();
|
||||
});
|
||||
|
||||
it('shows node health', function () {
|
||||
// 0006-NETW-010
|
||||
const regex = /^Operational\d+$/;
|
||||
const market = this.market;
|
||||
cy.visit(`/#/markets/${market.id}`);
|
||||
cy.getByTestId('node-health-trigger').realHover();
|
||||
cy.getByTestId('node-health')
|
||||
.children()
|
||||
.first()
|
||||
.should('contain.text', 'Operational')
|
||||
.then(($el) => {
|
||||
const blockHeight = parseInt($el.text());
|
||||
// block height will increase over the course of the test run so best
|
||||
// we can do here is check that its showing something sensible
|
||||
expect(blockHeight).to.be.greaterThan(0);
|
||||
});
|
||||
.invoke('text')
|
||||
.should('match', regex);
|
||||
cy.getByTestId('node-health')
|
||||
.children()
|
||||
.eq(1)
|
||||
@@ -239,7 +239,6 @@ describe('capsule', { tags: '@slow', testIsolation: true }, () => {
|
||||
.should('contain.text', order.size);
|
||||
|
||||
cy.getByTestId(openOrdersTab).click();
|
||||
cy.getByTestId('edit', txTimeout).should('contain.text', 'Edit');
|
||||
cy.getByTestId('tab-open-orders').within(() => {
|
||||
cy.get('.ag-center-cols-container')
|
||||
.children()
|
||||
@@ -280,8 +279,7 @@ describe('capsule', { tags: '@slow', testIsolation: true }, () => {
|
||||
cy.visit(`/#/markets/${market.id}`);
|
||||
cy.getByTestId(toastCloseBtn, txTimeout).click();
|
||||
cy.getByTestId(openOrdersTab).click();
|
||||
cy.getByTestId('edit', txTimeout).should('be.visible');
|
||||
cy.getByTestId('edit').first().should('be.visible').click();
|
||||
cy.getByTestId('edit').first().click();
|
||||
cy.getByTestId('dialog-title').should('contain.text', 'Edit order');
|
||||
cy.get('#limitPrice').focus().clear().type(newPrice);
|
||||
cy.getByTestId('edit-order').find('[type="submit"]').click();
|
||||
@@ -350,6 +348,7 @@ describe('capsule', { tags: '@slow', testIsolation: true }, () => {
|
||||
cy.getByTestId('withdraw-dialog-button').click();
|
||||
connectEthereumWallet('Unknown');
|
||||
selectAsset(btcName);
|
||||
cy.get('[data-testid="rich-select-option"]').eq(btcName).click();
|
||||
cy.get(amountField).clear().type('1');
|
||||
cy.getByTestId('submit-withdrawal').click();
|
||||
cy.getByTestId(toastContent, txTimeout).should(
|
||||
@@ -437,6 +436,7 @@ describe('capsule', { tags: '@slow', testIsolation: true }, () => {
|
||||
cy.getByTestId('deposit-button').click();
|
||||
connectEthereumWallet('Unknown');
|
||||
selectAsset(btcName);
|
||||
cy.get('[data-testid="rich-select-option"]').eq(btcName).click();
|
||||
cy.contains('Deposits of tBTC not approved').should('not.exist');
|
||||
cy.contains('Use maximum').should('be.visible');
|
||||
cy.get(amountField).clear().type('20000000');
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import {
|
||||
TIFlist,
|
||||
orderPriceField,
|
||||
orderSizeField,
|
||||
orderTIFDropDown,
|
||||
placeOrderBtn,
|
||||
toggleLimit,
|
||||
toggleMarket,
|
||||
} from '../support/deal-ticket';
|
||||
import { aliasGQLQuery } from '@vegaprotocol/cypress';
|
||||
import { accountsQuery } from '@vegaprotocol/mock';
|
||||
|
||||
describe('suspended market validation', { tags: '@regression' }, () => {
|
||||
before(() => {
|
||||
cy.setVegaWallet();
|
||||
cy.mockTradingPage(
|
||||
Schema.MarketState.STATE_SUSPENDED,
|
||||
Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION,
|
||||
Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY_TARGET_NOT_MET
|
||||
);
|
||||
const accounts = accountsQuery();
|
||||
cy.mockGQL((req) => {
|
||||
aliasGQLQuery(req, 'Accounts', accounts);
|
||||
});
|
||||
cy.mockSubscription();
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.wait('@Markets');
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.setVegaWallet();
|
||||
});
|
||||
|
||||
it('should show warning for market order', function () {
|
||||
cy.getByTestId(toggleMarket).click();
|
||||
// 7002-SORD-060
|
||||
cy.getByTestId(placeOrderBtn).should('be.enabled');
|
||||
cy.getByTestId(placeOrderBtn).click();
|
||||
cy.getByTestId('deal-ticket-error-message-type').should(
|
||||
'have.text',
|
||||
'This market is in auction until it reaches sufficient liquidity. Only limit orders are permitted when market is in auction'
|
||||
);
|
||||
});
|
||||
|
||||
it('should show info for allowed TIF', function () {
|
||||
cy.getByTestId(toggleLimit).click();
|
||||
cy.getByTestId(orderPriceField).clear().type('0.1');
|
||||
cy.getByTestId(orderSizeField).clear().type('1');
|
||||
cy.getByTestId(placeOrderBtn).should('be.enabled');
|
||||
cy.getByTestId(placeOrderBtn).click();
|
||||
cy.getByTestId('deal-ticket-warning-auction').should(
|
||||
'have.text',
|
||||
'Any orders placed now will not trade until the auction ends'
|
||||
);
|
||||
});
|
||||
|
||||
it('should show warning for not allowed TIF', function () {
|
||||
cy.getByTestId(toggleLimit).click();
|
||||
cy.getByTestId(orderTIFDropDown).select(
|
||||
TIFlist.filter((item) => item.code === 'FOK')[0].value
|
||||
);
|
||||
cy.getByTestId(placeOrderBtn).should('be.enabled');
|
||||
cy.getByTestId(placeOrderBtn).click();
|
||||
cy.getByTestId('deal-ticket-error-message-tif').should(
|
||||
'have.text',
|
||||
'This market is in auction until it reaches sufficient liquidity. Until the auction ends, you can only place GFA, GTT, or GTC limit orders'
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,110 +0,0 @@
|
||||
import { aliasGQLQuery } from '@vegaprotocol/cypress';
|
||||
import { fillsQuery } from '@vegaprotocol/mock';
|
||||
|
||||
const tabFills = 'tab-fills';
|
||||
|
||||
describe('fills', { tags: '@regression' }, () => {
|
||||
// 7005-FILL-001
|
||||
// 7005-FILL-002
|
||||
// 7005-FILL-003
|
||||
// 7005-FILL-004
|
||||
// 7005-FILL-005
|
||||
// 7005-FILL-006
|
||||
// 7005-FILL-007
|
||||
// 7005-FILL-008
|
||||
|
||||
beforeEach(() => {
|
||||
// Ensure page loads with correct key
|
||||
cy.window().then((window) => {
|
||||
cy.wrap(
|
||||
window.localStorage.setItem(
|
||||
'vega_wallet_key',
|
||||
Cypress.env('VEGA_PUBLIC_KEY')
|
||||
)
|
||||
);
|
||||
});
|
||||
cy.setVegaWallet();
|
||||
cy.mockTradingPage();
|
||||
cy.mockGQL((req) => {
|
||||
aliasGQLQuery(
|
||||
req,
|
||||
'Fills',
|
||||
fillsQuery({}, Cypress.env('VEGA_PUBLIC_KEY'))
|
||||
);
|
||||
});
|
||||
cy.mockSubscription();
|
||||
});
|
||||
|
||||
it('renders fills on portfolio page', () => {
|
||||
cy.visit('/#/portfolio');
|
||||
cy.get('[data-testid="pathname-/portfolio"]').should('exist');
|
||||
cy.getByTestId('Fills').click();
|
||||
validateFillsDisplayed();
|
||||
});
|
||||
|
||||
it('renders fills on trading tab', () => {
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.getByTestId('Fills').click();
|
||||
validateFillsDisplayed();
|
||||
});
|
||||
|
||||
function validateFillsDisplayed() {
|
||||
cy.getByTestId(tabFills).should('be.visible');
|
||||
cy.getByTestId(tabFills).contains('Market');
|
||||
cy.getByTestId(tabFills)
|
||||
.get(
|
||||
'[role="gridcell"][col-id="market.tradableInstrument.instrument.code"]'
|
||||
)
|
||||
.each(($marketSymbol) => {
|
||||
cy.wrap($marketSymbol).invoke('text').should('not.be.empty');
|
||||
});
|
||||
cy.getByTestId(tabFills).contains('Size');
|
||||
cy.get(`[col-id='size']`).eq(1).should('contain.text', '+');
|
||||
cy.get(`[col-id='size']`).eq(2).should('contain.text', '-');
|
||||
cy.getByTestId(tabFills)
|
||||
.get('[role="gridcell"][col-id="size"]')
|
||||
.each(($amount) => {
|
||||
cy.wrap($amount).invoke('text').should('not.be.empty');
|
||||
});
|
||||
cy.getByTestId(tabFills).contains('Price');
|
||||
cy.getByTestId(tabFills)
|
||||
.get('[role="gridcell"][col-id="price"]')
|
||||
.each(($prices) => {
|
||||
cy.wrap($prices).invoke('text').should('not.be.empty');
|
||||
});
|
||||
cy.getByTestId(tabFills).contains('Notional');
|
||||
cy.getByTestId(tabFills)
|
||||
.get('[role="gridcell"][col-id="price_1"]')
|
||||
.each(($total) => {
|
||||
cy.wrap($total).invoke('text').should('not.be.empty');
|
||||
});
|
||||
cy.getByTestId(tabFills).contains('Role');
|
||||
cy.getByTestId(tabFills)
|
||||
.get('[role="gridcell"][col-id="aggressor"]')
|
||||
.each(($role) => {
|
||||
cy.wrap($role)
|
||||
.invoke('text')
|
||||
.then((text) => {
|
||||
const roles = ['Maker', 'Taker', '-'];
|
||||
expect(roles.indexOf(text.trim())).to.be.greaterThan(-1);
|
||||
});
|
||||
});
|
||||
cy.getByTestId(tabFills).contains('Fee');
|
||||
cy.getByTestId(tabFills)
|
||||
.get(
|
||||
'[role="gridcell"][col-id="market.tradableInstrument.instrument.product"]'
|
||||
)
|
||||
.each(($fees) => {
|
||||
cy.wrap($fees).invoke('text').should('not.be.empty');
|
||||
});
|
||||
cy.getByTestId(tabFills).contains('Date');
|
||||
const dateTimeRegex =
|
||||
/(\d{1,2})\/(\d{1,2})\/(\d{4}), (\d{1,2}):(\d{1,2}):(\d{1,2})/gm;
|
||||
cy.get('[col-id="createdAt"]').each(($tradeDateTime, index) => {
|
||||
if (index != 0) {
|
||||
//ignore header
|
||||
cy.wrap($tradeDateTime).invoke('text').should('match', dateTimeRegex);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1,119 +0,0 @@
|
||||
import { selectAsset } from '../support/helpers';
|
||||
|
||||
const amountField = 'input[name="amount"]';
|
||||
const includeTransferFeeRadioBtn = 'include-transfer-fee';
|
||||
const manageVegaWallet = 'manage-vega-wallet';
|
||||
const toAddressField = '[name="toAddress"]';
|
||||
const totalTransferfee = 'total-transfer-fee';
|
||||
const transferAmount = 'transfer-amount';
|
||||
const transferForm = 'transfer-form';
|
||||
const transferFee = 'transfer-fee';
|
||||
const walletTransfer = 'wallet-transfer';
|
||||
|
||||
const ASSET_SEPOLIA_TBTC = 2;
|
||||
|
||||
describe.skip(
|
||||
'transfer fees',
|
||||
{ tags: '@regression', testIsolation: true },
|
||||
() => {
|
||||
beforeEach(() => {
|
||||
cy.mockWeb3Provider();
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.setVegaWallet();
|
||||
|
||||
cy.visit('/');
|
||||
cy.getByTestId(manageVegaWallet).click();
|
||||
cy.getByTestId(walletTransfer).click();
|
||||
|
||||
cy.wait('@Assets');
|
||||
cy.wait('@Accounts');
|
||||
|
||||
cy.mockVegaWalletTransaction();
|
||||
});
|
||||
|
||||
it('transfer fees tooltips', () => {
|
||||
// 1003-TRAN-015
|
||||
// 1003-TRAN-016
|
||||
// 1003-TRAN-017
|
||||
// 1003-TRAN-018
|
||||
// 1003-TRAN-019
|
||||
cy.getByTestId(transferForm);
|
||||
cy.contains('Enter manually').click();
|
||||
cy.getByTestId(transferForm)
|
||||
.find(toAddressField)
|
||||
.type(
|
||||
'7f9cf07d3a9905b1a61a1069f7a758855da428bc0f4a97de87f48644bfc25535'
|
||||
);
|
||||
selectAsset(ASSET_SEPOLIA_TBTC);
|
||||
cy.getByTestId(transferForm)
|
||||
.find(amountField)
|
||||
.type('1', { delay: 100, force: true });
|
||||
|
||||
/// Check Include Transfer Fee tooltip
|
||||
cy.get('label[for="include-transfer-fee"] div').realHover();
|
||||
cy.get('[data-side="bottom"] div')
|
||||
.should('be.visible')
|
||||
.should('not.be.empty');
|
||||
|
||||
//Check Transfer Fee tooltip
|
||||
cy.contains('div', 'Transfer fee').realHover();
|
||||
cy.get('[data-side="bottom"] div')
|
||||
.should('be.visible')
|
||||
.should('not.be.empty');
|
||||
|
||||
//Check Amount to be transferred tooltip
|
||||
cy.contains('div', 'Amount to be transferred').realHover();
|
||||
cy.get('[data-side="bottom"] div')
|
||||
.should('be.visible')
|
||||
.should('not.be.empty');
|
||||
|
||||
//Check Total amount (with fee) tooltip
|
||||
cy.contains('div', 'Total amount (with fee)').realHover();
|
||||
cy.get('[data-side="bottom"] div')
|
||||
.should('be.visible')
|
||||
.should('not.be.empty');
|
||||
});
|
||||
|
||||
it('transfer fees', () => {
|
||||
// 1003-TRAN-020
|
||||
// 1003-TRAN-021
|
||||
// 1003-TRAN-022
|
||||
// 1003-TRAN-023
|
||||
cy.getByTestId(transferForm);
|
||||
cy.contains('Enter manually').click();
|
||||
|
||||
cy.getByTestId(transferForm)
|
||||
.find(toAddressField)
|
||||
.type(
|
||||
'7f9cf07d3a9905b1a61a1069f7a758855da428bc0f4a97de87f48644bfc25535'
|
||||
);
|
||||
selectAsset(ASSET_SEPOLIA_TBTC);
|
||||
cy.getByTestId(includeTransferFeeRadioBtn).should('be.disabled');
|
||||
|
||||
cy.getByTestId(transferForm)
|
||||
.find(amountField)
|
||||
.type('1', { delay: 100, force: true });
|
||||
|
||||
cy.getByTestId(transferFee)
|
||||
.should('be.visible')
|
||||
.should('contain.text', '0.01');
|
||||
cy.getByTestId(transferAmount)
|
||||
.should('be.visible')
|
||||
.should('contain.text', '1.00');
|
||||
cy.getByTestId(totalTransferfee)
|
||||
.should('be.visible')
|
||||
.should('contain.text', '1.01');
|
||||
cy.getByTestId(includeTransferFeeRadioBtn).click();
|
||||
cy.getByTestId(transferFee)
|
||||
.should('be.visible')
|
||||
.should('contain.text', '0.01');
|
||||
cy.getByTestId(transferAmount)
|
||||
.should('be.visible')
|
||||
.should('contain.text', '0.99');
|
||||
cy.getByTestId(totalTransferfee)
|
||||
.should('be.visible')
|
||||
.should('contain.text', '1.00');
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -1,131 +0,0 @@
|
||||
import { connectEthereumWallet } from '../support/ethereum-wallet';
|
||||
import { selectAsset } from '../support/helpers';
|
||||
|
||||
const formFieldError = 'input-error-text';
|
||||
const toAddressField = 'input[name="to"]';
|
||||
const amountField = 'input[name="amount"]';
|
||||
const useMaximumAmount = 'use-maximum';
|
||||
const submitWithdrawBtn = 'submit-withdrawal';
|
||||
const ethAddressValue = Cypress.env('ETHEREUM_WALLET_ADDRESS');
|
||||
|
||||
const ASSET_SEPOLIA_TBTC = 2;
|
||||
const ASSET_EURO = 1;
|
||||
|
||||
describe('withdraw form validation', { tags: '@smoke' }, () => {
|
||||
before(() => {
|
||||
cy.mockWeb3Provider();
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.setVegaWallet();
|
||||
|
||||
cy.visit('/#/portfolio');
|
||||
cy.getByTestId('Withdrawals').click();
|
||||
|
||||
cy.getByTestId('Withdraw').click(); // sidebar item
|
||||
|
||||
// It also requires connection Ethereum wallet
|
||||
connectEthereumWallet('MetaMask');
|
||||
|
||||
cy.wait('@Accounts');
|
||||
cy.wait('@Assets');
|
||||
});
|
||||
|
||||
it('empty fields', () => {
|
||||
cy.getByTestId(submitWithdrawBtn).click();
|
||||
|
||||
cy.getByTestId(formFieldError).should('contain.text', 'Required');
|
||||
// only 2 despite 3 fields because the ethereum address will be auto populated
|
||||
cy.getByTestId(formFieldError).should('have.length', 2);
|
||||
|
||||
// Test for Ethereum address
|
||||
cy.get(toAddressField).should('have.value', ethAddressValue);
|
||||
});
|
||||
it('min amount', () => {
|
||||
// 1002-WITH-010
|
||||
selectAsset(ASSET_SEPOLIA_TBTC);
|
||||
cy.get(amountField).clear().type('0');
|
||||
cy.getByTestId(submitWithdrawBtn).click();
|
||||
cy.get('[data-testid="input-error-text"]').should(
|
||||
'contain.text',
|
||||
'Value is below minimum'
|
||||
);
|
||||
});
|
||||
it('max amount', () => {
|
||||
// 1002-WITH-005
|
||||
// 1002-WITH-008
|
||||
selectAsset(ASSET_EURO); // Will be above maximum because the vega wallet doesn't have any collateral
|
||||
cy.get(amountField).clear().type('1001', { delay: 100 });
|
||||
cy.getByTestId(submitWithdrawBtn).click();
|
||||
cy.get('[data-testid="input-error-text"]').should(
|
||||
'contain.text',
|
||||
'Insufficient amount in account'
|
||||
);
|
||||
});
|
||||
|
||||
it('can set amount using use maximum button', () => {
|
||||
// 1002-WITH-004
|
||||
selectAsset(ASSET_SEPOLIA_TBTC);
|
||||
cy.getByTestId(useMaximumAmount).click();
|
||||
cy.get(amountField).should('have.value', '1000.00001');
|
||||
});
|
||||
});
|
||||
|
||||
describe(
|
||||
'withdraw actions',
|
||||
{ tags: '@regression', testIsolation: true },
|
||||
() => {
|
||||
// this is extremely ugly hack, but setting it properly in contract is too much effort for such simple validation
|
||||
|
||||
// 1002-WITH-018
|
||||
|
||||
const withdrawalThreshold =
|
||||
Cypress.env('VEGA_ENV') === 'CUSTOM' ? '0.00' : '100.00';
|
||||
before(() => {
|
||||
cy.mockWeb3Provider();
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.setVegaWallet();
|
||||
|
||||
cy.visit('/#/portfolio');
|
||||
|
||||
cy.wait('@Accounts');
|
||||
cy.wait('@Assets');
|
||||
|
||||
cy.getByTestId('Withdrawals').click();
|
||||
|
||||
cy.getByTestId('Withdraw').click();
|
||||
|
||||
// It also requires connection Ethereum wallet
|
||||
connectEthereumWallet('MetaMask');
|
||||
|
||||
cy.mockVegaWalletTransaction();
|
||||
});
|
||||
|
||||
it('triggers transaction when submitted', () => {
|
||||
// 1002-WITH-002
|
||||
// 1002-WITH-003
|
||||
selectAsset(ASSET_SEPOLIA_TBTC);
|
||||
cy.getByTestId('BALANCE_AVAILABLE_label').should(
|
||||
'contain.text',
|
||||
'Balance available'
|
||||
);
|
||||
cy.getByTestId('BALANCE_AVAILABLE_value').should(
|
||||
'have.text',
|
||||
'1,000.00001'
|
||||
);
|
||||
cy.getByTestId('WITHDRAWAL_THRESHOLD_label').should(
|
||||
'contain.text',
|
||||
'Delayed withdrawal threshold'
|
||||
);
|
||||
cy.getByTestId('WITHDRAWAL_THRESHOLD_value').should(
|
||||
'contain.text',
|
||||
withdrawalThreshold
|
||||
);
|
||||
cy.getByTestId('DELAY_TIME_label').should('contain.text', 'Delay time');
|
||||
cy.getByTestId('DELAY_TIME_value').should('have.text', 'None');
|
||||
cy.get(amountField).clear().type('10');
|
||||
cy.getByTestId(submitWithdrawBtn).click();
|
||||
cy.getByTestId('toast').should('contain.text', 'Awaiting confirmation');
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -8,22 +8,17 @@ export const LandingBanner = () => {
|
||||
<div className="">
|
||||
<div
|
||||
aria-hidden
|
||||
className="absolute top-64 right-[220px] md:right-[340px] max-sm:hidden"
|
||||
className="absolute top-20 right-[120px] md:right-[240px] max-sm:hidden"
|
||||
>
|
||||
<AnimatedDudeWithWire />
|
||||
</div>
|
||||
<div className="pt-32 sm:w-[50%]">
|
||||
<div className="pt-20 sm:w-[50%]">
|
||||
<h1 className="text-6xl font-alpha calt mb-10">
|
||||
{t('Earn commission & stake rewards')}
|
||||
</h1>
|
||||
<p className="text-lg mb-10">
|
||||
{t(
|
||||
'Invite friends and earn commission in the form of Vega rewards from the trading fees they pay. Stake those rewards to earn multipliers on future rewards.'
|
||||
)}
|
||||
</p>
|
||||
<p className="text-lg">
|
||||
{t(
|
||||
'Any friends that join using the code will receive discounts off trading fees.'
|
||||
'Invite friends and earn rewards from the trading fees they pay. Stake those rewards to earn multipliers on future rewards.'
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,7 @@ import classNames from 'classnames';
|
||||
import type { HTMLAttributes } from 'react';
|
||||
import { SKY_BACKGROUND } from './constants';
|
||||
import { Outlet } from 'react-router-dom';
|
||||
import { TinyScroll } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
export const Layout = ({
|
||||
className,
|
||||
@@ -28,8 +29,10 @@ export const LayoutWithSky = ({
|
||||
...props
|
||||
}: HTMLAttributes<HTMLDivElement>) => {
|
||||
return (
|
||||
<div className={classNames('h-full overflow-auto', SKY_BACKGROUND)}>
|
||||
<TinyScroll
|
||||
className={classNames('max-h-full overflow-auto', SKY_BACKGROUND)}
|
||||
>
|
||||
<Layout className={className} {...props} />
|
||||
</div>
|
||||
</TinyScroll>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -27,7 +27,7 @@ export const LayoutWithSidebar = ({
|
||||
<div className={gridClasses}>
|
||||
<div className="col-span-full">{header}</div>
|
||||
<main
|
||||
className={classNames('col-start-1 col-end-1', {
|
||||
className={classNames('col-start-1 col-end-1 overflow-hidden', {
|
||||
'lg:col-end-3': !sidebarOpen,
|
||||
'hidden lg:block lg:col-end-2': sidebarOpen,
|
||||
})}
|
||||
|
||||
@@ -55,10 +55,14 @@ export const routerConfig: RouteObject[] = compact([
|
||||
FLAGS.REFERRALS
|
||||
? {
|
||||
path: AppRoutes.REFERRALS,
|
||||
element: <LayoutWithSky />,
|
||||
element: <LayoutWithSidebar sidebar={<PortfolioSidebar />} />,
|
||||
children: [
|
||||
{
|
||||
element: <Referrals />,
|
||||
element: (
|
||||
<LayoutWithSky>
|
||||
<Referrals />
|
||||
</LayoutWithSky>
|
||||
),
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 947 KiB After Width: | Height: | Size: 419 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 947 KiB After Width: | Height: | Size: 419 KiB |
@@ -33,7 +33,7 @@ import BigNumber from 'bignumber.js';
|
||||
import classNames from 'classnames';
|
||||
import { AccountsActionsDropdown } from './accounts-actions-dropdown';
|
||||
|
||||
const colorClass = (percentageUsed: number, neutral = false) => {
|
||||
const colorClass = (percentageUsed: number) => {
|
||||
return classNames('text-right', {
|
||||
'text-vega-orange': percentageUsed >= 75 && percentageUsed < 90,
|
||||
'text-vega-red': percentageUsed >= 90,
|
||||
@@ -210,7 +210,7 @@ export const AccountTable = ({
|
||||
},
|
||||
cellClass: ({ data }) => {
|
||||
const percentageUsed = percentageValue(data?.used, data?.total);
|
||||
return colorClass(percentageUsed, true);
|
||||
return colorClass(percentageUsed);
|
||||
},
|
||||
valueFormatter: ({
|
||||
value,
|
||||
@@ -270,7 +270,8 @@ export const AccountTable = ({
|
||||
onClickDeposit && onClickDeposit(assetId);
|
||||
}}
|
||||
>
|
||||
<VegaIcon name={VegaIconNames.DEPOSIT} /> {t('Deposit')}
|
||||
<VegaIcon name={VegaIconNames.DEPOSIT} size={14} />{' '}
|
||||
{t('Deposit')}
|
||||
</TradingButton>
|
||||
</CenteredGridCellWrapper>
|
||||
);
|
||||
|
||||
@@ -1,31 +1,83 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { render, screen, within } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { CandlesMenu } from './candles-menu';
|
||||
import {
|
||||
useCandlesChartSettingsStore,
|
||||
DEFAULT_CHART_SETTINGS,
|
||||
} from './use-candles-chart-settings';
|
||||
import { Overlay, Study, overlayLabels, studyLabels } from 'pennant';
|
||||
|
||||
describe('CandlesMenu', () => {
|
||||
it('should render with the correct default studies', async () => {
|
||||
render(<CandlesMenu />);
|
||||
|
||||
const openDropdown = async () => {
|
||||
await userEvent.click(
|
||||
screen.getByRole('button', {
|
||||
name: 'Studies',
|
||||
name: 'Indicators',
|
||||
})
|
||||
);
|
||||
expect(await screen.findByRole('menu')).toBeInTheDocument();
|
||||
expect(screen.getByText('Volume')).toHaveAttribute('data-state', 'checked');
|
||||
expect(screen.getByText('MACD')).toHaveAttribute('data-state', 'checked');
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
// clear store each time to avoid conditional testing of defaults
|
||||
useCandlesChartSettingsStore.setState({ overlays: [], studies: [] });
|
||||
});
|
||||
|
||||
it('should render with the correct default overlays', async () => {
|
||||
it.each(Object.values(Overlay))('can set %s overlay', async (overlay) => {
|
||||
render(<CandlesMenu />);
|
||||
|
||||
await openDropdown();
|
||||
|
||||
const menu = within(await screen.findByRole('menu'));
|
||||
|
||||
await userEvent.click(menu.getByText(overlayLabels[overlay as Overlay]));
|
||||
|
||||
// re-open the dropdown
|
||||
await openDropdown();
|
||||
|
||||
expect(screen.getByText(overlayLabels[overlay as Overlay])).toHaveAttribute(
|
||||
'data-state',
|
||||
'checked'
|
||||
);
|
||||
});
|
||||
|
||||
it.each(Object.values(Study))('can set %s study', async (study) => {
|
||||
render(<CandlesMenu />);
|
||||
|
||||
await openDropdown();
|
||||
|
||||
const menu = within(await screen.findByRole('menu'));
|
||||
|
||||
await userEvent.click(menu.getByText(studyLabels[study as Study]));
|
||||
|
||||
// re-open the dropdown
|
||||
await openDropdown();
|
||||
|
||||
expect(screen.getByText(studyLabels[study as Study])).toHaveAttribute(
|
||||
'data-state',
|
||||
'checked'
|
||||
);
|
||||
});
|
||||
|
||||
it('should render with the correct default studies and overlays', async () => {
|
||||
useCandlesChartSettingsStore.setState(DEFAULT_CHART_SETTINGS);
|
||||
|
||||
render(<CandlesMenu />);
|
||||
|
||||
await userEvent.click(
|
||||
screen.getByRole('button', {
|
||||
name: 'Overlays',
|
||||
name: 'Indicators',
|
||||
})
|
||||
);
|
||||
expect(await screen.findByRole('menu')).toBeInTheDocument();
|
||||
expect(screen.getByText('Moving average')).toHaveAttribute(
|
||||
const menu = within(await screen.findByRole('menu'));
|
||||
|
||||
expect(menu.getByText(studyLabels.volume)).toHaveAttribute(
|
||||
'data-state',
|
||||
'checked'
|
||||
);
|
||||
expect(menu.getByText(studyLabels.macd)).toHaveAttribute(
|
||||
'data-state',
|
||||
'checked'
|
||||
);
|
||||
expect(menu.getByText(overlayLabels.movingAverage)).toHaveAttribute(
|
||||
'data-state',
|
||||
'checked'
|
||||
);
|
||||
|
||||
@@ -107,7 +107,7 @@ export const CandlesMenu = () => {
|
||||
trigger={
|
||||
<TradingDropdownTrigger className={triggerClasses}>
|
||||
<TradingButton {...triggerButtonProps}>
|
||||
{t('Overlays')}
|
||||
{t('Indicators')}
|
||||
</TradingButton>
|
||||
</TradingDropdownTrigger>
|
||||
}
|
||||
@@ -132,18 +132,6 @@ export const CandlesMenu = () => {
|
||||
<TradingDropdownItemIndicator />
|
||||
</TradingDropdownCheckboxItem>
|
||||
))}
|
||||
</TradingDropdownContent>
|
||||
</TradingDropdown>
|
||||
<TradingDropdown
|
||||
trigger={
|
||||
<TradingDropdownTrigger className={triggerClasses}>
|
||||
<TradingButton {...triggerButtonProps}>
|
||||
{t('Studies')}
|
||||
</TradingButton>
|
||||
</TradingDropdownTrigger>
|
||||
}
|
||||
>
|
||||
<TradingDropdownContent align={contentAlign}>
|
||||
{Object.values(Study).map((study) => (
|
||||
<TradingDropdownCheckboxItem
|
||||
key={study}
|
||||
|
||||
@@ -24,7 +24,7 @@ const STUDY_ORDER: Study[] = [
|
||||
Study.VOLUME,
|
||||
];
|
||||
|
||||
const DEFAULT_CHART_SETTINGS = {
|
||||
export const DEFAULT_CHART_SETTINGS = {
|
||||
interval: Interval.I15M,
|
||||
type: ChartType.CANDLE,
|
||||
overlays: [Overlay.MOVING_AVERAGE],
|
||||
|
||||
@@ -259,11 +259,9 @@ export const DealTicketMarginDetails = ({
|
||||
? liquidationEstimateWorstCaseIncludingBuyOrders
|
||||
: liquidationEstimateWorstCaseIncludingSellOrders;
|
||||
|
||||
// The estimate order query API gives us the liquidation price in formatted by asset decimals.
|
||||
// We need to calculate it with asset decimals, but display it with market decimals precision until the API changes.
|
||||
liquidationPriceEstimate = formatValue(
|
||||
liquidationEstimateWorstCase.toString(),
|
||||
assetDecimals,
|
||||
market.decimalPlaces,
|
||||
undefined,
|
||||
market.decimalPlaces
|
||||
);
|
||||
@@ -276,7 +274,7 @@ export const DealTicketMarginDetails = ({
|
||||
? liquidationEstimateBestCase
|
||||
: liquidationEstimateWorstCase
|
||||
).toString(),
|
||||
assetDecimals,
|
||||
market.decimalPlaces,
|
||||
undefined,
|
||||
market.decimalPlaces
|
||||
);
|
||||
@@ -308,7 +306,7 @@ export const DealTicketMarginDetails = ({
|
||||
key={'value-dropdown'}
|
||||
className="flex items-center justify-between w-full gap-2"
|
||||
>
|
||||
<div className="flex items-center gap-1 text-left">
|
||||
<div className="flex items-center text-left gap-1">
|
||||
<Tooltip description={MARGIN_DIFF_TOOLTIP_TEXT(assetSymbol)}>
|
||||
<span className="text-muted">{t('Margin required')}</span>
|
||||
</Tooltip>
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
Tooltip,
|
||||
TradingButton as Button,
|
||||
Pill,
|
||||
ExternalLink,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
import { useOpenVolume } from '@vegaprotocol/positions';
|
||||
@@ -77,6 +78,7 @@ import { DealTicketSizeIceberg } from './deal-ticket-size-iceberg';
|
||||
import noop from 'lodash/noop';
|
||||
import { isNonPersistentOrder } from '../../utils/time-in-force-persistance';
|
||||
import { KeyValue } from './key-value';
|
||||
import { DocsLinks } from '@vegaprotocol/environment';
|
||||
|
||||
export const REDUCE_ONLY_TOOLTIP =
|
||||
'"Reduce only" will ensure that this order will not increase the size of an open position. When the order is matched, it will only trade enough volume to bring your open volume towards 0 but never change the direction of your position. If applied to a limit order that is not instantly filled, the order will be stopped.';
|
||||
@@ -273,7 +275,9 @@ export const DealTicket = ({
|
||||
|
||||
const assetSymbol = getAsset(market).symbol;
|
||||
|
||||
const assetUnit = getQuoteName(market);
|
||||
const baseQuote = getBaseQuoteUnit(
|
||||
market.tradableInstrument.instrument.metadata.tags
|
||||
);
|
||||
|
||||
const summaryError = useMemo(() => {
|
||||
if (!pubKey) {
|
||||
@@ -412,7 +416,7 @@ export const DealTicket = ({
|
||||
id="order-size"
|
||||
className="w-full"
|
||||
type="number"
|
||||
appendElement={assetUnit && <Pill size="xs">{assetUnit}</Pill>}
|
||||
appendElement={baseQuote && <Pill size="xs">{baseQuote}</Pill>}
|
||||
step={sizeStep}
|
||||
min={sizeStep}
|
||||
data-testid="order-size"
|
||||
@@ -548,15 +552,20 @@ export const DealTicket = ({
|
||||
render={({ field }) => (
|
||||
<Tooltip
|
||||
description={
|
||||
<span>
|
||||
{disablePostOnlyCheckbox
|
||||
? t(
|
||||
'"Post only" can not be used on "Fill or Kill" or "Immediate or Cancel" orders.'
|
||||
)
|
||||
: t(
|
||||
'"Post only" will ensure the order is not filled immediately but is placed on the order book as a passive order. When the order is processed it is either stopped (if it would not be filled immediately), or placed in the order book as a passive order until the price taker matches with it.'
|
||||
)}
|
||||
</span>
|
||||
<>
|
||||
<span>
|
||||
{disablePostOnlyCheckbox
|
||||
? t(
|
||||
'"Post only" can not be used on "Fill or Kill" or "Immediate or Cancel" orders.'
|
||||
)
|
||||
: t(
|
||||
'"Post only" will ensure the order is not filled immediately but is placed on the order book as a passive order. When the order is processed it is either stopped (if it would not be filled immediately), or placed in the order book as a passive order until the price taker matches with it.'
|
||||
)}
|
||||
</span>{' '}
|
||||
<ExternalLink href={DocsLinks?.POST_REDUCE_ONLY}>
|
||||
{t('Find out more')}
|
||||
</ExternalLink>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<div>
|
||||
@@ -580,13 +589,18 @@ export const DealTicket = ({
|
||||
render={({ field }) => (
|
||||
<Tooltip
|
||||
description={
|
||||
<span>
|
||||
{disableReduceOnlyCheckbox
|
||||
? t(
|
||||
'"Reduce only" can be used only with non-persistent orders, such as "Fill or Kill" or "Immediate or Cancel".'
|
||||
)
|
||||
: t(REDUCE_ONLY_TOOLTIP)}
|
||||
</span>
|
||||
<>
|
||||
<span>
|
||||
{disableReduceOnlyCheckbox
|
||||
? t(
|
||||
'"Reduce only" can be used only with non-persistent orders, such as "Fill or Kill" or "Immediate or Cancel".'
|
||||
)
|
||||
: t(REDUCE_ONLY_TOOLTIP)}
|
||||
</span>{' '}
|
||||
<ExternalLink href={DocsLinks?.POST_REDUCE_ONLY}>
|
||||
{t('Find out more')}
|
||||
</ExternalLink>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<div>
|
||||
@@ -618,7 +632,10 @@ export const DealTicket = ({
|
||||
{t(`Trade only a fraction of the order size at once.
|
||||
After the peak size of the order has traded, the size is reset. This is repeated until the order is cancelled, expires, or its full volume trades away.
|
||||
For example, an iceberg order with a size of 1000 and a peak size of 100 will effectively be split into 10 orders with a size of 100 each.
|
||||
Note that the full volume of the order is not hidden and is still reflected in the order book.`)}
|
||||
Note that the full volume of the order is not hidden and is still reflected in the order book.`)}{' '}
|
||||
<ExternalLink href={DocsLinks?.ICEBERG_ORDERS}>
|
||||
{t('Find out more')}
|
||||
</ExternalLink>{' '}
|
||||
</p>
|
||||
}
|
||||
>
|
||||
@@ -668,7 +685,7 @@ export const DealTicket = ({
|
||||
subLabel={`${formatValue(
|
||||
normalizedOrder.size,
|
||||
market.positionDecimalPlaces
|
||||
)} ${assetUnit} @ ${
|
||||
)} ${baseQuote} @ ${
|
||||
type === Schema.OrderType.TYPE_MARKET
|
||||
? 'market'
|
||||
: `${formatValue(
|
||||
|
||||
@@ -82,6 +82,8 @@ export const DocsLinks = VEGA_DOCS_URL
|
||||
VALIDATOR_SCORES_REWARDS: `${VEGA_DOCS_URL}/concepts/vega-chain/validator-scores-and-rewards`,
|
||||
MARKET_LIFECYCLE: `${VEGA_DOCS_URL}/concepts/trading-on-vega/market-lifecycle`,
|
||||
ETH_DATA_SOURCES: `${VEGA_DOCS_URL}/concepts/trading-on-vega/data-sources#ethereum-data-sources`,
|
||||
ICEBERG_ORDERS: `${VEGA_DOCS_URL}/concepts/trading-on-vega/orders#iceberg-order`,
|
||||
POST_REDUCE_ONLY: `${VEGA_DOCS_URL}/concepts/trading-on-vega/orders#conditional-order-parameters`,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
|
||||
@@ -37,6 +37,14 @@ const defaultFill: PartialDeep<Trade> = {
|
||||
};
|
||||
describe('FillsTable', () => {
|
||||
it('correct columns are rendered', async () => {
|
||||
// 7005-FILL-001
|
||||
// 7005-FILL-002
|
||||
// 7005-FILL-003
|
||||
// 7005-FILL-004
|
||||
// 7005-FILL-005
|
||||
// 7005-FILL-006
|
||||
// 7005-FILL-007
|
||||
// 7005-FILL-008
|
||||
await act(async () => {
|
||||
render(<FillsTable partyId="party-id" rowData={[generateFill()]} />);
|
||||
});
|
||||
|
||||
@@ -49,6 +49,11 @@ query EstimatePosition(
|
||||
openVolume: $openVolume
|
||||
orders: $orders
|
||||
collateralAvailable: $collateralAvailable
|
||||
# Everywhere in the codebase we expect price values of the underlying to have the right
|
||||
# number of digits for formatting with market.decimalPlaces. By default the estimatePosition
|
||||
# query will return a full value requiring formatting using asset.decimals. For consistency
|
||||
# we can set this variable to true so that we can format with market.decimalPlaces
|
||||
scaleLiquidationPriceToMarketDecimals: true
|
||||
) {
|
||||
margin {
|
||||
worstCase {
|
||||
|
||||
@@ -130,6 +130,7 @@ export const EstimatePositionDocument = gql`
|
||||
openVolume: $openVolume
|
||||
orders: $orders
|
||||
collateralAvailable: $collateralAvailable
|
||||
scaleLiquidationPriceToMarketDecimals: true
|
||||
) {
|
||||
margin {
|
||||
worstCase {
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import type { MockedResponse } from '@apollo/client/testing';
|
||||
import { render, screen, within } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { EstimatePositionDocument } from './__generated__/Positions';
|
||||
import type { EstimatePositionQuery } from './__generated__/Positions';
|
||||
import { LiquidationPrice } from './liquidation-price';
|
||||
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
|
||||
|
||||
describe('LiquidationPrice', () => {
|
||||
const props = {
|
||||
marketId: 'market-id',
|
||||
openVolume: '100',
|
||||
collateralAvailable: '1000',
|
||||
decimalPlaces: 2,
|
||||
};
|
||||
const worstCaseOpenVolume = '200';
|
||||
const bestCaseOpenVolume = '100';
|
||||
const mock: MockedResponse<EstimatePositionQuery> = {
|
||||
request: {
|
||||
query: EstimatePositionDocument,
|
||||
variables: {
|
||||
marketId: props.marketId,
|
||||
openVolume: props.openVolume,
|
||||
collateralAvailable: props.collateralAvailable,
|
||||
},
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
estimatePosition: {
|
||||
margin: {
|
||||
worstCase: {
|
||||
maintenanceLevel: '100',
|
||||
searchLevel: '100',
|
||||
initialLevel: '100',
|
||||
collateralReleaseLevel: '100',
|
||||
},
|
||||
bestCase: {
|
||||
maintenanceLevel: '100',
|
||||
searchLevel: '100',
|
||||
initialLevel: '100',
|
||||
collateralReleaseLevel: '100',
|
||||
},
|
||||
},
|
||||
liquidation: {
|
||||
worstCase: {
|
||||
open_volume_only: worstCaseOpenVolume,
|
||||
including_buy_orders: '100',
|
||||
including_sell_orders: '100',
|
||||
},
|
||||
bestCase: {
|
||||
open_volume_only: bestCaseOpenVolume,
|
||||
including_buy_orders: '100',
|
||||
including_sell_orders: '100',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
it('correctly formats best and worst case values for the tooltip', async () => {
|
||||
render(
|
||||
<MockedProvider mocks={[mock]}>
|
||||
<LiquidationPrice {...props} />
|
||||
</MockedProvider>
|
||||
);
|
||||
|
||||
expect(screen.getByText('-')).toBeInTheDocument();
|
||||
const el = await screen.findByTestId('liquidation-price');
|
||||
expect(el).toHaveTextContent(
|
||||
addDecimalsFormatNumber(worstCaseOpenVolume, props.decimalPlaces)
|
||||
);
|
||||
await userEvent.hover(el);
|
||||
const tooltip = within(await screen.findByRole('tooltip'));
|
||||
expect(
|
||||
tooltip.getByText('Worst case').nextElementSibling
|
||||
).toHaveTextContent(
|
||||
addDecimalsFormatNumber(worstCaseOpenVolume, props.decimalPlaces)
|
||||
);
|
||||
expect(tooltip.getByText('Best case').nextElementSibling).toHaveTextContent(
|
||||
addDecimalsFormatNumber(bestCaseOpenVolume, props.decimalPlaces)
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -8,20 +8,12 @@ export const LiquidationPrice = ({
|
||||
openVolume,
|
||||
collateralAvailable,
|
||||
decimalPlaces,
|
||||
formatDecimals,
|
||||
}: {
|
||||
marketId: string;
|
||||
openVolume: string;
|
||||
collateralAvailable: string;
|
||||
decimalPlaces: number;
|
||||
formatDecimals: number;
|
||||
}) => {
|
||||
// NOTE!
|
||||
//
|
||||
// The estimate order query API gives us the liquidation price unformatted but expecting to be converted
|
||||
// using asset decimal placse.
|
||||
//
|
||||
// We need to convert it with asset decimals, but display it formatted with market decimals precision until the API changes.
|
||||
const { data: currentData, previousData } = useEstimatePositionQuery({
|
||||
variables: {
|
||||
marketId,
|
||||
@@ -38,21 +30,11 @@ export const LiquidationPrice = ({
|
||||
return <span>-</span>;
|
||||
}
|
||||
|
||||
let bestCase = '-';
|
||||
let worstCase = '-';
|
||||
let bestCase = data.estimatePosition.liquidation.bestCase.open_volume_only;
|
||||
let worstCase = data.estimatePosition.liquidation.worstCase.open_volume_only;
|
||||
|
||||
bestCase =
|
||||
data.estimatePosition?.liquidation?.bestCase.open_volume_only.replace(
|
||||
/\..*/,
|
||||
''
|
||||
);
|
||||
worstCase =
|
||||
data.estimatePosition?.liquidation?.worstCase.open_volume_only.replace(
|
||||
/\..*/,
|
||||
''
|
||||
);
|
||||
worstCase = addDecimalsFormatNumber(worstCase, decimalPlaces, formatDecimals);
|
||||
bestCase = addDecimalsFormatNumber(bestCase, decimalPlaces, formatDecimals);
|
||||
worstCase = addDecimalsFormatNumber(worstCase, decimalPlaces, decimalPlaces);
|
||||
bestCase = addDecimalsFormatNumber(bestCase, decimalPlaces, decimalPlaces);
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
|
||||
@@ -339,16 +339,12 @@ export const PositionsTable = ({
|
||||
if (!data) {
|
||||
return '-';
|
||||
}
|
||||
// The estimate order query API gives us the liquidation price unformatted but expecting
|
||||
// conversion using asset decimals. We need to convert it with asset decimals, but format
|
||||
// it with market decimals precision until the API changes.
|
||||
return (
|
||||
<LiquidationPrice
|
||||
marketId={data.marketId}
|
||||
openVolume={data.openVolume}
|
||||
collateralAvailable={data.totalBalance}
|
||||
decimalPlaces={data.assetDecimals}
|
||||
formatDecimals={data.marketDecimalPlaces}
|
||||
decimalPlaces={data.marketDecimalPlaces}
|
||||
/>
|
||||
);
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { act, fireEvent, render, screen } from '@testing-library/react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { generateAccount, generateAsset } from './test-helpers';
|
||||
import type { WithdrawManagerProps } from './withdraw-manager';
|
||||
@@ -57,10 +57,18 @@ describe('WithdrawManager', () => {
|
||||
);
|
||||
|
||||
it('calls submit if valid form submission', async () => {
|
||||
// 1002-WITH-002
|
||||
// 1002-WITH-003
|
||||
const { container } = render(generateJsx(props));
|
||||
await act(async () => {
|
||||
await submitValid(container);
|
||||
});
|
||||
const select = container.querySelector('select[name="asset"]') as Element;
|
||||
await userEvent.selectOptions(select, props.assets[0].id);
|
||||
await userEvent.clear(screen.getByLabelText('To (Ethereum address)'));
|
||||
await userEvent.type(
|
||||
screen.getByLabelText('To (Ethereum address)'),
|
||||
ethereumAddress
|
||||
);
|
||||
await userEvent.type(screen.getByLabelText('Amount'), '0.01');
|
||||
await userEvent.click(screen.getByTestId('submit-withdrawal'));
|
||||
expect(props.submit).toHaveBeenCalledWith({
|
||||
amount: '1000',
|
||||
asset: props.assets[0].id,
|
||||
@@ -70,58 +78,56 @@ describe('WithdrawManager', () => {
|
||||
});
|
||||
|
||||
it('validates correctly', async () => {
|
||||
render(generateJsx(props));
|
||||
// 1002-WITH-010
|
||||
// 1002-WITH-005
|
||||
// 1002-WITH-008
|
||||
// 1002-WITH-018
|
||||
const { container } = render(generateJsx(props));
|
||||
|
||||
// Set other fields to be valid
|
||||
fireEvent.change(screen.getByLabelText('Asset'), {
|
||||
target: { value: props.assets[0].id },
|
||||
});
|
||||
fireEvent.change(screen.getByLabelText('To (Ethereum address)'), {
|
||||
target: { value: ethereumAddress },
|
||||
});
|
||||
const select = container.querySelector('select[name="asset"]') as Element;
|
||||
await userEvent.selectOptions(select, props.assets[0].id);
|
||||
expect(screen.getByTestId('connect-eth-wallet-btn')).toBeInTheDocument();
|
||||
|
||||
await userEvent.type(
|
||||
screen.getByLabelText('To (Ethereum address)'),
|
||||
ethereumAddress
|
||||
);
|
||||
|
||||
// Min amount
|
||||
fireEvent.change(screen.getByLabelText('Amount'), {
|
||||
target: { value: '0.00000001' },
|
||||
});
|
||||
fireEvent.submit(screen.getByTestId('withdraw-form'));
|
||||
await userEvent.clear(screen.getByLabelText('Amount'));
|
||||
await userEvent.type(screen.getByLabelText('Amount'), '0.00000001');
|
||||
await userEvent.click(screen.getByTestId('submit-withdrawal'));
|
||||
|
||||
expect(
|
||||
await screen.findByText('Value is below minimum')
|
||||
).toBeInTheDocument();
|
||||
expect(props.submit).not.toBeCalled();
|
||||
|
||||
fireEvent.change(screen.getByLabelText('Amount'), {
|
||||
target: { value: '0.00001' },
|
||||
});
|
||||
await userEvent.clear(screen.getByLabelText('Amount'));
|
||||
await userEvent.type(screen.getByLabelText('Amount'), '0.00001');
|
||||
|
||||
// Max amount (balance is 1)
|
||||
fireEvent.change(screen.getByLabelText('Amount'), {
|
||||
target: { value: '2' },
|
||||
});
|
||||
fireEvent.submit(screen.getByTestId('withdraw-form'));
|
||||
await userEvent.clear(screen.getByLabelText('Amount'));
|
||||
await userEvent.type(screen.getByLabelText('Amount'), '2');
|
||||
|
||||
await userEvent.click(screen.getByTestId('submit-withdrawal'));
|
||||
expect(
|
||||
await screen.findByText('Insufficient amount in account')
|
||||
).toBeInTheDocument();
|
||||
expect(props.submit).not.toBeCalled();
|
||||
});
|
||||
it('can set amount using use maximum button', async () => {
|
||||
// 1002-WITH-004
|
||||
render(generateJsx(props));
|
||||
|
||||
const submitValid = async (container: HTMLElement) => {
|
||||
const select = container.querySelector('select[name="asset"]') as Element;
|
||||
await userEvent.selectOptions(select, props.assets[0].id);
|
||||
fireEvent.change(screen.getByLabelText('To (Ethereum address)'), {
|
||||
target: { value: ethereumAddress },
|
||||
});
|
||||
fireEvent.change(screen.getByLabelText('Amount'), {
|
||||
target: { value: '0.01' },
|
||||
});
|
||||
fireEvent.submit(screen.getByTestId('withdraw-form'));
|
||||
};
|
||||
await userEvent.click(screen.getByTestId('use-maximum'));
|
||||
expect(screen.getByTestId('amount-input')).toHaveValue(1);
|
||||
});
|
||||
|
||||
it('shows withdraw delay notification if amount greater than threshold', async () => {
|
||||
render(generateJsx(props));
|
||||
fireEvent.change(screen.getByLabelText('Amount'), {
|
||||
target: { value: '1001' },
|
||||
});
|
||||
await userEvent.type(screen.getByLabelText('Amount'), '1001');
|
||||
expect(
|
||||
await screen.findByTestId('amount-withdrawal-delay-notification')
|
||||
).toBeInTheDocument();
|
||||
@@ -130,9 +136,7 @@ describe('WithdrawManager', () => {
|
||||
it('shows withdraw delay notification if threshold is 0', async () => {
|
||||
withdrawAsset.threshold = new BigNumber(0);
|
||||
render(generateJsx(props));
|
||||
fireEvent.change(screen.getByLabelText('Amount'), {
|
||||
target: { value: '0.01' },
|
||||
});
|
||||
await userEvent.type(screen.getByLabelText('Amount'), '0.01');
|
||||
expect(
|
||||
await screen.findByTestId('withdrawals-delay-notification')
|
||||
).toBeInTheDocument();
|
||||
|
||||
+1
-1
@@ -71,7 +71,7 @@
|
||||
"jsondiffpatch": "^0.4.1",
|
||||
"lodash": "^4.17.21",
|
||||
"next": "13.3.0",
|
||||
"pennant": "1.13.4",
|
||||
"pennant": "1.14.0",
|
||||
"react": "18.2.0",
|
||||
"react-copy-to-clipboard": "^5.0.4",
|
||||
"react-dom": "18.2.0",
|
||||
|
||||
@@ -20500,10 +20500,10 @@ pend@~1.2.0:
|
||||
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
|
||||
integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==
|
||||
|
||||
pennant@1.13.4:
|
||||
version "1.13.4"
|
||||
resolved "https://registry.yarnpkg.com/pennant/-/pennant-1.13.4.tgz#33a5f3413634a2341a7b91c917f023c59ecc44c7"
|
||||
integrity sha512-sqwkUiYHxmS97RY8jToMfgR9ePcEr5PWVQu9BPrhdUIa1Q/NztE36SWM5tVIsPPTx3pPIHYTNEVJm02Ubl8cZQ==
|
||||
pennant@1.14.0:
|
||||
version "1.14.0"
|
||||
resolved "https://registry.yarnpkg.com/pennant/-/pennant-1.14.0.tgz#4100c25a6d836d6f0ff425181fb6f812f9fe5778"
|
||||
integrity sha512-9H0zWzFUSbD1BlDXnHFmKwkAxXGb1xTxjkUD+RwaMygtSwPXzQEyk2ScVyMqxdcz0RuJmI5HCVmZTOjdr1NwuA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@d3fc/d3fc-technical-indicator" "^8.0.1"
|
||||
|
||||
Reference in New Issue
Block a user