Compare commits
28
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a12fb5862 | ||
|
|
4392678921 | ||
|
|
ab1a2cb222 | ||
|
|
cdd91c24f2 | ||
|
|
e3eb13ca72 | ||
|
|
3f0ebbf33d | ||
|
|
026aa3964b | ||
|
|
e1a1ae0fa1 | ||
|
|
aa4c5a4a57 | ||
|
|
0874314d2a | ||
|
|
a70a7bcc8e | ||
|
|
4461a6a497 | ||
|
|
1041864ad8 | ||
|
|
015e0188ec | ||
|
|
228236c152 | ||
|
|
cd6e906dc6 | ||
|
|
8a47a92dbd | ||
|
|
af14c27df9 | ||
|
|
38edcd88c0 | ||
|
|
a74cf02030 | ||
|
|
6686755763 | ||
|
|
79cf8163be | ||
|
|
1688e26f4b | ||
|
|
5e101a8eaf | ||
|
|
65071f57d0 | ||
|
|
b87c981a90 | ||
|
|
b509e5b347 | ||
|
|
bb9fce4831 |
@@ -1,7 +1,6 @@
|
||||
---
|
||||
name: Feature Epic
|
||||
about:
|
||||
A template to capture and scope user requirements, high level process, and basic mockups for an upcoming feature as part of the initial core spec review process.
|
||||
about: A template to capture and scope user requirements, high level process, and basic mockups for an upcoming feature as part of the initial core spec review process.
|
||||
title: 'Epic: '
|
||||
labels: feature-epic
|
||||
---
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
context('Proposal page', { tags: '@smoke' }, function () {
|
||||
describe('Verify elements on page', function () {
|
||||
const proposalHeading = 'proposals-heading';
|
||||
const dateTimeRegex =
|
||||
/(\d{1,2})\/(\d{1,2})\/(\d{4}), (\d{1,2}):(\d{1,2}):(\d{1,2})/gm;
|
||||
const proposalTitle = 'Add Lorem Ipsum market';
|
||||
|
||||
before('Create market proposal', function () {
|
||||
cy.visit('/');
|
||||
cy.createMarket();
|
||||
});
|
||||
|
||||
it('Able to view proposal', function () {
|
||||
cy.navigate_to('governanceProposals');
|
||||
cy.getByTestId(proposalHeading).should('be.visible');
|
||||
// get first proposal in list
|
||||
cy.get('[row-index="0"]').within(() => {
|
||||
cy.get_element_by_col_id('title').should('have.text', proposalTitle);
|
||||
cy.get_element_by_col_id('type').should('have.text', 'NewMarket');
|
||||
cy.get_element_by_col_id('state').should('have.text', 'Enacted');
|
||||
cy.getByTestId('vote-progress').should('be.visible');
|
||||
cy.get('[col-id="cDate"]')
|
||||
.invoke('text')
|
||||
.should('match', dateTimeRegex);
|
||||
cy.get('[col-id="eDate"]')
|
||||
.invoke('text')
|
||||
.should('match', dateTimeRegex);
|
||||
cy.getByTestId('external-link')
|
||||
.should('have.attr', 'href')
|
||||
.and('contains', 'https://governance.fairground.wtf/proposals/');
|
||||
cy.contains('View terms').should('exist').click();
|
||||
});
|
||||
cy.getByTestId('dialog-title').should('have.text', proposalTitle);
|
||||
cy.get('.language-json').should('exist');
|
||||
});
|
||||
|
||||
it('Proposal page displayed on mobile', function () {
|
||||
cy.common_switch_to_mobile_and_click_toggle();
|
||||
cy.navigate_to('governanceProposals', true);
|
||||
cy.getByTestId(proposalHeading).should('be.visible');
|
||||
cy.get('[row-index="0"]').within(() => {
|
||||
cy.get_element_by_col_id('title').should('have.text', proposalTitle);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -23,7 +23,7 @@ export const Footer = () => {
|
||||
);
|
||||
|
||||
return (
|
||||
<footer className="grid grid-rows-2 lg:grid-cols-[1fr_auto] text-xs md:text-md lg:flex md:col-span-2 px-4 py-2 gap-4 border-t border-vega-light-200 dark:border-vega-dark-200">
|
||||
<footer className="grid grid-cols-[1fr_auto] items-center text-xs md:text-md lg:flex md:col-span-2 px-4 pt-2 pb-3 gap-4 border-t border-vega-light-200 dark:border-vega-dark-200">
|
||||
<div className="flex justify-between gap-2 align-middle">
|
||||
{GIT_COMMIT_HASH && (
|
||||
<div className="content-center flex border-r border-neutral-700 dark:border-neutral-300 pr-4">
|
||||
@@ -43,9 +43,12 @@ export const Footer = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="content-center flex pl-2 md:border-r border-neutral-700 dark:border-neutral-300 pr-4">
|
||||
{VEGA_URL && <NodeUrl url={VEGA_URL} />}
|
||||
<Link className="ml-2" onClick={() => setNodeSwitcherOpen(true)}>
|
||||
<div className="content-center flex pr-4 md:border-r border-neutral-700 dark:border-neutral-300">
|
||||
<span className="pr-2">{VEGA_URL && <NodeUrl url={VEGA_URL} />}</span>
|
||||
<Link
|
||||
className="ml-2 underline-offset-4"
|
||||
onClick={() => setNodeSwitcherOpen(true)}
|
||||
>
|
||||
{t('Change')}
|
||||
</Link>
|
||||
</div>
|
||||
@@ -59,7 +62,10 @@ export const Footer = () => {
|
||||
) : null}
|
||||
</div>
|
||||
<div className="pl-2 align-center lg:align-right lg:flex lg:justify-end gap-2 align-middle lg:max-w-xs lg:ml-auto">
|
||||
<RouteLink to={`/${Routes.DISCLAIMER}`} className="underline">
|
||||
<RouteLink
|
||||
to={`/${Routes.DISCLAIMER}`}
|
||||
className="underline underline-offset-4"
|
||||
>
|
||||
Disclaimer
|
||||
</RouteLink>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
{
|
||||
"changes": {
|
||||
"decimalPlaces": "5",
|
||||
"positionDecimalPlaces": "5",
|
||||
"linearSlippageFactor": "0.001",
|
||||
"quadraticSlippageFactor": "0",
|
||||
"lpPriceRange": "10",
|
||||
"instrument": {
|
||||
"name": "Token test market",
|
||||
"code": "Token.24h",
|
||||
"future": {
|
||||
"settlementAsset": "816af99af60d684502a40824758f6b5377e6af48e50a9ee8ef478ecb879ea8bc",
|
||||
"quoteName": "fUSDC",
|
||||
"dataSourceSpecForSettlementData": {
|
||||
"external": {
|
||||
"oracle": {
|
||||
"signers": [
|
||||
{
|
||||
"pubKey": {
|
||||
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
|
||||
}
|
||||
}
|
||||
],
|
||||
"filters": [
|
||||
{
|
||||
"key": {
|
||||
"name": "prices.BTC.value",
|
||||
"type": "TYPE_INTEGER",
|
||||
"numberDecimalPlaces": "0"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"operator": "OPERATOR_GREATER_THAN",
|
||||
"value": "0"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"dataSourceSpecForTradingTermination": {
|
||||
"external": {
|
||||
"oracle": {
|
||||
"signers": [
|
||||
{
|
||||
"pubKey": {
|
||||
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
|
||||
}
|
||||
}
|
||||
],
|
||||
"filters": [
|
||||
{
|
||||
"key": {
|
||||
"name": "trading.terminated.ETH5",
|
||||
"type": "TYPE_BOOLEAN"
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"operator": "OPERATOR_EQUALS",
|
||||
"value": "true"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"dataSourceSpecBinding": {
|
||||
"settlementDataProperty": "prices.BTC.value",
|
||||
"tradingTerminationProperty": "trading.terminated.ETH5"
|
||||
}
|
||||
}
|
||||
},
|
||||
"metadata": ["sector:food", "sector:materials", "source:docs.vega.xyz"],
|
||||
"priceMonitoringParameters": {
|
||||
"triggers": [
|
||||
{
|
||||
"horizon": "43200",
|
||||
"probability": "0.9999999",
|
||||
"auctionExtension": "600"
|
||||
}
|
||||
]
|
||||
},
|
||||
"liquidityMonitoringParameters": {
|
||||
"targetStakeParameters": {
|
||||
"timeWindow": "3600",
|
||||
"scalingFactor": 10
|
||||
},
|
||||
"triggeringRatio": "0.7",
|
||||
"auctionExtension": "1"
|
||||
},
|
||||
"logNormal": {
|
||||
"tau": 0.0001140771161,
|
||||
"riskAversionParameter": 0.001,
|
||||
"params": {
|
||||
"mu": 0,
|
||||
"r": 0.016,
|
||||
"sigma": 0.8
|
||||
}
|
||||
},
|
||||
"successor": {
|
||||
"parentMarketId": "",
|
||||
"insurancePoolFraction": "0.75"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -220,7 +220,7 @@ context(
|
||||
function () {
|
||||
const proposalTitle = 'Test new market proposal';
|
||||
goToMakeNewProposal(governanceProposalType.NEW_MARKET);
|
||||
cy.getByTestId(newProposalTitle).type('Test new market proposal');
|
||||
cy.getByTestId(newProposalTitle).type(proposalTitle);
|
||||
cy.getByTestId(newProposalDescription).type('E2E test for proposals');
|
||||
cy.fixture('/proposals/new-market').then((newMarketProposal) => {
|
||||
const newMarketPayload = JSON.stringify(newMarketProposal);
|
||||
@@ -606,6 +606,94 @@ context(
|
||||
});
|
||||
});
|
||||
|
||||
it('able to submit successor market proposal', function () {
|
||||
const proposalTitle = 'Test successor market proposal';
|
||||
|
||||
cy.createMarket();
|
||||
cy.reload();
|
||||
waitForSpinner();
|
||||
cy.getByTestId('closed-proposals').within(() => {
|
||||
cy.contains('Add Lorem Ipsum market')
|
||||
.parentsUntil(proposalListItem)
|
||||
.last()
|
||||
.within(() => {
|
||||
cy.getByTestId(viewProposalBtn).click();
|
||||
});
|
||||
});
|
||||
getProposalInformationFromTable('ID').invoke('text').as('parentMarketId');
|
||||
goToMakeNewProposal(governanceProposalType.NEW_MARKET);
|
||||
cy.getByTestId(newProposalTitle).type(proposalTitle);
|
||||
cy.getByTestId(newProposalDescription).type(
|
||||
'E2E test for successor market'
|
||||
);
|
||||
cy.fixture('/proposals/successor-market').then((newMarketProposal) => {
|
||||
newMarketProposal.changes.successor.parentMarketId =
|
||||
this.parentMarketId;
|
||||
const newMarketPayload = JSON.stringify(newMarketProposal);
|
||||
cy.getByTestId(newProposalTerms).type(newMarketPayload, {
|
||||
parseSpecialCharSequences: false,
|
||||
delay: 2,
|
||||
});
|
||||
});
|
||||
cy.getByTestId(proposalDownloadBtn)
|
||||
.should('be.visible')
|
||||
.click()
|
||||
.then(() => {
|
||||
cy.wrap(
|
||||
getDownloadedProposalJsonPath('vega-new-market-proposal-')
|
||||
).then((filePath) => {
|
||||
goToMakeNewProposal(governanceProposalType.RAW);
|
||||
submitUniqueRawProposal({ proposalBody: filePath });
|
||||
});
|
||||
});
|
||||
navigateTo(navigation.proposals);
|
||||
getProposalFromTitle(proposalTitle).within(() => {
|
||||
// 3003-PMAN-008
|
||||
cy.getByTestId('proposal-successor-info')
|
||||
.should('have.text', 'Successor market to: TEST.24h')
|
||||
.find('a')
|
||||
.should('have.attr', 'href')
|
||||
.and('contain', this.parentMarketId);
|
||||
cy.getByTestId('view-proposal-btn').click();
|
||||
});
|
||||
// #3003-PMAN-010
|
||||
cy.getByTestId(proposalJsonToggle).click();
|
||||
cy.get('.language-json').within(() => {
|
||||
cy.get('.hljs-attr').should('contain.text', 'parentMarketId');
|
||||
cy.get('.hljs-string').should('contain.text', this.parentMarketId);
|
||||
cy.get('.hljs-attr').should('contain.text', 'insurancePoolFraction');
|
||||
cy.get('.hljs-string').should('contain.text', '0.75');
|
||||
});
|
||||
cy.getByTestId('proposal-market-data').within(() => {
|
||||
cy.getByTestId('proposal-market-data-toggle').click();
|
||||
cy.contains('Key details').click();
|
||||
// 3003-PMAN-009
|
||||
getMarketProposalDetailsFromTable('Parent Market ID').should(
|
||||
'have.text',
|
||||
this.parentMarketId
|
||||
);
|
||||
getMarketProposalDetailsFromTable('Insurance Pool Fraction').should(
|
||||
'have.text',
|
||||
'0.75'
|
||||
);
|
||||
getMarketProposalDetailsFromTable('Trading Mode').should(
|
||||
'have.text',
|
||||
'No trading'
|
||||
);
|
||||
});
|
||||
// 3003-PMAN-011
|
||||
cy.contains('Parent Market ID').realHover();
|
||||
cy.getByTestId('tooltip-content').should(
|
||||
'contain.text',
|
||||
'The ID of the market this market succeeds.'
|
||||
);
|
||||
cy.contains('Insurance Pool Fraction').realHover();
|
||||
cy.getByTestId('tooltip-content').should(
|
||||
'contain.text',
|
||||
'The fraction of the insurance pool balance that is carried over from the parent market to the successor.'
|
||||
);
|
||||
});
|
||||
|
||||
after('Disassociate from second wallet key if present', function () {
|
||||
cy.reload();
|
||||
waitForSpinner();
|
||||
|
||||
@@ -96,12 +96,14 @@ context('rewards - flow', { tags: '@slow' }, function () {
|
||||
.within(() => {
|
||||
cy.get('h2').first().should('contain.text', 'EPOCH');
|
||||
cy.getByTestId('individual-rewards-asset').should('have.text', 'Vega');
|
||||
cy.getByTestId('ACCOUNT_TYPE_GLOBAL_REWARD', rewardsTimeOut)
|
||||
.should('contain.text', '0.4415')
|
||||
.and('contain.text', '(44.15%)');
|
||||
cy.getByTestId('ACCOUNT_TYPE_FEES_INFRASTRUCTURE')
|
||||
.should('contain.text', '0.0004')
|
||||
.and('contain.text', '(44.15%)');
|
||||
cy.getByTestId('ACCOUNT_TYPE_GLOBAL_REWARD', rewardsTimeOut).should(
|
||||
'contain.text',
|
||||
'0.4415'
|
||||
);
|
||||
cy.getByTestId('ACCOUNT_TYPE_FEES_INFRASTRUCTURE').should(
|
||||
'contain.text',
|
||||
'0.0004'
|
||||
);
|
||||
cy.getByTestId('total').should('have.text', '0.4419');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -339,7 +339,7 @@ context(
|
||||
cy.getByTestId(vegaWalletCurrencyTitle)
|
||||
.contains(name)
|
||||
.parent()
|
||||
.siblings()
|
||||
.siblings(txTimeout)
|
||||
.should((elementAmount) => {
|
||||
const displayedAmount = parseFloat(elementAmount.text());
|
||||
expect(displayedAmount).be.gte(expectedAmount);
|
||||
|
||||
@@ -107,7 +107,7 @@ export function dissociateFromSecondWalletKey() {
|
||||
cy.getByTestId('vega-in-wallet')
|
||||
.first()
|
||||
.within(() => {
|
||||
cy.getByTestId('eth-wallet-associated-balances')
|
||||
cy.getByTestId('eth-wallet-associated-balances', txTimeout)
|
||||
.last()
|
||||
.within(() => {
|
||||
cy.getByTestId('associated-key')
|
||||
|
||||
@@ -78,13 +78,21 @@ export function stakingPageAssociateTokens(
|
||||
}
|
||||
cy.get(tokenAmountInputBox, epochTimeout).type(amount);
|
||||
if (approve) {
|
||||
cy.get(tokenInputApprove, txTimeout).should('be.enabled').click();
|
||||
cy.contains('Approve $VEGA Tokens for staking on Vega').should(
|
||||
'be.visible'
|
||||
);
|
||||
cy.contains('Approve $VEGA Tokens for staking on Vega', txTimeout).should(
|
||||
'not.exist'
|
||||
);
|
||||
cy.getByTestId('wallet-associate').then((walletAssociateField) => {
|
||||
if (
|
||||
walletAssociateField.find('[data-testid="token-input-approve-button"]')
|
||||
.length
|
||||
) {
|
||||
cy.get(tokenInputApprove, txTimeout).should('be.enabled').click();
|
||||
cy.contains('Approve $VEGA Tokens for staking on Vega').should(
|
||||
'be.visible'
|
||||
);
|
||||
cy.contains(
|
||||
'Approve $VEGA Tokens for staking on Vega',
|
||||
txTimeout
|
||||
).should('not.exist');
|
||||
}
|
||||
});
|
||||
}
|
||||
cy.get(tokenSubmitButton, txTimeout).should('be.enabled').click();
|
||||
|
||||
|
||||
+1
-1
@@ -200,7 +200,7 @@ export const ProposalMarketData = ({
|
||||
title={marketData.tradableInstrument.instrument.code}
|
||||
open={isOpen}
|
||||
onChange={(isOpen) => (isOpen ? open() : close())}
|
||||
size="medium"
|
||||
size="large"
|
||||
dataTestId="market-json-dialog"
|
||||
>
|
||||
<CopyWithTooltip text={JSON.stringify(marketData)}>
|
||||
|
||||
+1
-1
@@ -57,7 +57,7 @@ const ROWS = [
|
||||
|
||||
export const HealthDialog = ({ onChange, isOpen }: HealthDialogProps) => {
|
||||
return (
|
||||
<Dialog size="medium" open={isOpen} onChange={onChange}>
|
||||
<Dialog size="large" open={isOpen} onChange={onChange}>
|
||||
<h1 className="text-2xl mb-5 pr-2 font-medium font-alpha uppercase">
|
||||
{t('Health')}
|
||||
</h1>
|
||||
|
||||
@@ -26,6 +26,7 @@ module.exports = defineConfig({
|
||||
requestTimeout: 20000,
|
||||
retries: 1,
|
||||
testIsolation: false,
|
||||
experimentalMemoryManagement: true,
|
||||
},
|
||||
env: {
|
||||
ETHERSCAN_URL: 'https://sepolia.etherscan.io',
|
||||
|
||||
@@ -2,14 +2,14 @@ describe('charts', { tags: '@smoke' }, () => {
|
||||
before(() => {
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.setOnBoardingViewed();
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.wait('@Markets');
|
||||
cy.getByTestId('Depth').click();
|
||||
});
|
||||
|
||||
it('can see market depth chart', () => {
|
||||
// 6006-DEPC-001
|
||||
cy.getByTestId('Depth').click();
|
||||
cy.getByTestId('tab-depth').should('be.visible');
|
||||
cy.get('.depth-chart-module_canvas__260De').should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -202,6 +202,7 @@ describe('Closed markets', { tags: '@smoke' }, () => {
|
||||
const specDataConnection = createDataConnection();
|
||||
|
||||
before(() => {
|
||||
cy.setOnBoardingViewed();
|
||||
cy.mockGQL((req) => {
|
||||
aliasGQLQuery(req, 'ChainId', chainIdQuery());
|
||||
aliasGQLQuery(req, 'Statistics', statisticsQuery());
|
||||
@@ -455,6 +456,7 @@ describe('no closed markets', { tags: '@smoke', testIsolation: true }, () => {
|
||||
before(() => {
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.setOnBoardingViewed();
|
||||
cy.visit('/#/markets/all');
|
||||
cy.get('[data-testid="Closed markets"]').click();
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ const nodeHealthTrigger = 'node-health-trigger';
|
||||
|
||||
describe('home', { tags: '@regression' }, () => {
|
||||
before(() => {
|
||||
cy.setOnBoardingViewed();
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.visit('/');
|
||||
|
||||
@@ -2,7 +2,7 @@ import { aliasGQLQuery } from '@vegaprotocol/cypress';
|
||||
import type { ProposalListFieldsFragment } from '@vegaprotocol/proposals';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
|
||||
const dialogContent = 'dialog-content';
|
||||
const dialogContent = 'welcome-dialog';
|
||||
|
||||
const generateProposal = (code: string): ProposalListFieldsFragment => ({
|
||||
__typename: 'Proposal',
|
||||
@@ -140,43 +140,43 @@ describe('home', { tags: '@regression' }, () => {
|
||||
cy.wait('@MarketsData');
|
||||
});
|
||||
|
||||
it('redirects to market/all and displays welcome notice', () => {
|
||||
it('close welcome dialog should redirect to market/all', () => {
|
||||
cy.url().should('eq', Cypress.config().baseUrl + `/#/markets/all`);
|
||||
cy.getByTestId('welcome-notice-title').should(
|
||||
'contain.text',
|
||||
'Welcome to Console'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('no proposal nor markets found', () => {
|
||||
it('there are welcome text and a link to propose market', () => {
|
||||
cy.mockGQL((req) => {
|
||||
const data = {
|
||||
marketsConnection: {
|
||||
__typename: 'MarketConnection',
|
||||
edges: [],
|
||||
},
|
||||
};
|
||||
aliasGQLQuery(req, 'Markets', data);
|
||||
aliasGQLQuery(req, 'MarketsData', data);
|
||||
aliasGQLQuery(req, 'ProposalsList', {
|
||||
proposalsConnection: {
|
||||
__typename: 'ProposalsConnection',
|
||||
edges: null,
|
||||
},
|
||||
});
|
||||
cy.getByTestId('welcome-dialog').should('be.visible');
|
||||
cy.getByTestId('welcome-title').should('contain.text', 'Console CUSTOM');
|
||||
cy.getByTestId('browse-markets-button').should('not.be.disabled');
|
||||
cy.getByTestId('get-started-banner').should('be.visible');
|
||||
cy.getByTestId('get-started-button').should('not.be.disabled');
|
||||
cy.getByTestId('dialog-close').click();
|
||||
cy.url().should('eq', Cypress.config().baseUrl + `/#/markets/all`);
|
||||
cy.window().then((window) => {
|
||||
expect(window.localStorage.getItem('vega_onboarding_viewed')).to.equal(
|
||||
'true'
|
||||
);
|
||||
});
|
||||
cy.visit('/');
|
||||
cy.wait('@Markets');
|
||||
cy.wait('@MarketsData');
|
||||
cy.getByTestId('welcome-notice-title').should(
|
||||
'contain.text',
|
||||
'Welcome to Console'
|
||||
);
|
||||
cy.getByTestId('external-link')
|
||||
.contains('Propose a market')
|
||||
.should('exist');
|
||||
});
|
||||
|
||||
it('click browse markets button should redirect to market/all', () => {
|
||||
cy.getByTestId('welcome-dialog').should('be.visible');
|
||||
cy.getByTestId('browse-markets-button').click();
|
||||
cy.url().should('eq', Cypress.config().baseUrl + `/#/markets/all`);
|
||||
cy.window().then((window) => {
|
||||
expect(window.localStorage.getItem('vega_onboarding_viewed')).to.equal(
|
||||
'true'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('click get started button should open connect dialog', () => {
|
||||
cy.getByTestId('welcome-dialog').should('be.visible');
|
||||
cy.getByTestId('get-started-button').click();
|
||||
cy.url().should('eq', Cypress.config().baseUrl + `/#/markets/all`);
|
||||
cy.window().then((window) => {
|
||||
expect(window.localStorage.getItem('vega_onboarding_viewed')).to.equal(
|
||||
'true'
|
||||
);
|
||||
});
|
||||
cy.getByTestId('wallet-dialog-title').should('contain.text', 'Connect');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -185,7 +185,7 @@ describe('home', { tags: '@regression' }, () => {
|
||||
cy.window().then((window) => {
|
||||
window.localStorage.setItem('marketId', 'market-1');
|
||||
cy.visit('/');
|
||||
cy.wait('@Markets');
|
||||
cy.getByTestId('dialog-close').click();
|
||||
cy.location('hash').should('equal', '#/markets/market-1');
|
||||
cy.getByTestId(dialogContent).should('not.exist');
|
||||
});
|
||||
@@ -199,6 +199,7 @@ describe('home', { tags: '@regression' }, () => {
|
||||
});
|
||||
cy.visit('/');
|
||||
cy.wait('@Markets');
|
||||
cy.getByTestId('dialog-close').click();
|
||||
cy.location('hash').should('equal', '#/markets/market-not-existing');
|
||||
cy.getByTestId(dialogContent).should('not.exist');
|
||||
});
|
||||
|
||||
@@ -9,6 +9,7 @@ const colInstrumentCode = '[col-id="tradableInstrument.instrument.code"]';
|
||||
describe('markets all table', { tags: '@smoke' }, () => {
|
||||
beforeEach(() => {
|
||||
cy.clearLocalStorage().then(() => {
|
||||
cy.setOnBoardingViewed();
|
||||
cy.mockTradingPage(
|
||||
Schema.MarketState.STATE_ACTIVE,
|
||||
Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION,
|
||||
@@ -199,6 +200,7 @@ describe('no open markets', { tags: '@smoke', testIsolation: true }, () => {
|
||||
aliasGQLQuery(req, 'Markets', markets);
|
||||
});
|
||||
cy.mockSubscription();
|
||||
cy.setOnBoardingViewed();
|
||||
cy.visit('/#/markets/all');
|
||||
});
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
|
||||
});
|
||||
|
||||
before(() => {
|
||||
cy.setOnBoardingViewed();
|
||||
cy.mockTradingPage(MarketState.STATE_ACTIVE);
|
||||
cy.mockSubscription();
|
||||
cy.visit('/#/markets/market-0');
|
||||
|
||||
@@ -36,6 +36,7 @@ const headers = [
|
||||
|
||||
describe('liquidity table - trading', { tags: '@smoke' }, () => {
|
||||
before(() => {
|
||||
cy.setOnBoardingViewed();
|
||||
cy.mockSubscription();
|
||||
cy.mockTradingPage(
|
||||
Schema.MarketState.STATE_ACTIVE,
|
||||
@@ -119,6 +120,7 @@ describe('liquidity table - trading', { tags: '@smoke' }, () => {
|
||||
|
||||
describe('liquidity table view', { tags: '@smoke' }, () => {
|
||||
before(() => {
|
||||
cy.setOnBoardingViewed();
|
||||
cy.mockSubscription();
|
||||
cy.mockTradingPage(
|
||||
Schema.MarketState.STATE_ACTIVE,
|
||||
|
||||
@@ -12,6 +12,7 @@ describe('markets selector', { tags: '@smoke' }, () => {
|
||||
cy.window().then((window) => {
|
||||
window.localStorage.setItem('marketId', 'market-1');
|
||||
});
|
||||
cy.setOnBoardingViewed();
|
||||
cy.mockTradingPage(
|
||||
MarketState.STATE_ACTIVE,
|
||||
MarketTradingMode.TRADING_MODE_MONITORING_AUCTION,
|
||||
|
||||
@@ -30,6 +30,7 @@ describe('Market trading page', () => {
|
||||
Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY_TARGET_NOT_MET
|
||||
);
|
||||
cy.mockSubscription();
|
||||
cy.setOnBoardingViewed();
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.wait('@MarketData');
|
||||
cy.getByTestId(marketSummaryBlock).should('be.visible');
|
||||
|
||||
@@ -9,6 +9,7 @@ describe('markets proposed table', { tags: '@smoke' }, () => {
|
||||
before(() => {
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.setOnBoardingViewed();
|
||||
cy.visit('/#/markets/all');
|
||||
cy.get('[data-testid="Proposed markets"]').click();
|
||||
});
|
||||
@@ -213,6 +214,7 @@ describe('no markets proposed', { tags: '@smoke', testIsolation: true }, () => {
|
||||
aliasGQLQuery(req, 'ProposalsList', proposal);
|
||||
});
|
||||
cy.mockSubscription();
|
||||
cy.setOnBoardingViewed();
|
||||
});
|
||||
|
||||
it('can see no markets message', () => {
|
||||
|
||||
@@ -12,6 +12,7 @@ describe('markets table', { tags: '@smoke' }, () => {
|
||||
Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY_TARGET_NOT_MET
|
||||
);
|
||||
cy.mockSubscription();
|
||||
cy.setOnBoardingViewed();
|
||||
cy.visit('/#/markets/all');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,6 +6,7 @@ const oracleFullProfile = 'oracle-full-profile';
|
||||
|
||||
describe('oracle information', { tags: '@smoke' }, () => {
|
||||
before(() => {
|
||||
cy.setOnBoardingViewed();
|
||||
cy.mockTradingPage(
|
||||
MarketState.STATE_ACTIVE,
|
||||
undefined,
|
||||
|
||||
@@ -14,6 +14,7 @@ const resPrice = 'price-990';
|
||||
|
||||
describe('order book', { tags: '@smoke' }, () => {
|
||||
before(() => {
|
||||
cy.setOnBoardingViewed();
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.visit('/#/markets/market-0');
|
||||
|
||||
@@ -4,6 +4,7 @@ describe('Settings page', { tags: '@smoke' }, () => {
|
||||
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.setOnBoardingViewed();
|
||||
cy.visit('/');
|
||||
|
||||
// Only click if not already active otherwise sidebar will close
|
||||
|
||||
@@ -77,6 +77,7 @@ function getButtonSelectorByText(text: string): string {
|
||||
beforeEach(() => {
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.setOnBoardingViewed();
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.wait('@Markets');
|
||||
});
|
||||
@@ -154,7 +155,10 @@ describe(
|
||||
{ name: 'Volume', infoText: 'Volume: 55,000' },
|
||||
];
|
||||
cy.get(indicatorInfo).eq(1).realHover();
|
||||
cy.get('.close-button-module_closeButton__2ifkl').click({ force: true });
|
||||
cy.get('.chart__wrapper [data-testid="split-view-view"]')
|
||||
.last()
|
||||
.find('[role="button"][title="Close"]')
|
||||
.click({ force: true });
|
||||
cy.get(indicatorInfo).should('have.length', 1);
|
||||
|
||||
checkMenuItemCheckbox('Studies', studyInfo);
|
||||
|
||||
@@ -14,6 +14,7 @@ describe('deal ticket basics', { tags: '@smoke' }, () => {
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.clearAllLocalStorage();
|
||||
cy.setOnBoardingViewed();
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.wait('@Markets');
|
||||
});
|
||||
@@ -21,9 +22,8 @@ describe('deal ticket basics', { tags: '@smoke' }, () => {
|
||||
it('must show place order button and connect wallet if wallet is not connected', () => {
|
||||
// 0003-WTXN-001
|
||||
cy.getByTestId('connect-vega-wallet'); // Not connected
|
||||
cy.getByTestId('order-connect-wallet').should('exist');
|
||||
cy.getByTestId(placeOrderBtn).should('exist');
|
||||
cy.getByTestId('deal-ticket-connect-wallet').should('exist');
|
||||
cy.getByTestId('get-started-button').should('exist');
|
||||
});
|
||||
|
||||
it('must be able to select order direction - long/short', function () {
|
||||
@@ -44,7 +44,7 @@ describe('deal ticket basics', { tags: '@smoke' }, () => {
|
||||
mockConnectWallet();
|
||||
cy.getByTestId(toggleLimit).click();
|
||||
cy.getByTestId(orderPriceField).clear().type('101');
|
||||
cy.getByTestId('order-connect-wallet').click();
|
||||
cy.getByTestId('get-started-button').click();
|
||||
cy.getByTestId('dialog-content').should('be.visible');
|
||||
cy.getByTestId('connectors-list')
|
||||
.find('[data-testid="connector-jsonRpc"]')
|
||||
|
||||
@@ -15,6 +15,7 @@ describe('time in force validation', { tags: '@smoke' }, () => {
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.clearAllLocalStorage();
|
||||
cy.setOnBoardingViewed();
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.wait('@Markets');
|
||||
});
|
||||
|
||||
@@ -251,11 +251,13 @@ describe('positions', { tags: '@regression', testIsolation: true }, () => {
|
||||
it('I can see warnings', () => {
|
||||
visitAndClickPositions();
|
||||
|
||||
cy.get('[col-id="openVolume"]').within(() => {
|
||||
cy.get('[aria-label="warning-sign icon"]')
|
||||
.should('be.visible')
|
||||
.realHover();
|
||||
});
|
||||
cy.get('[col-id="openVolume"]')
|
||||
.eq(3)
|
||||
.within(() => {
|
||||
cy.get('[aria-label="warning-sign icon"]')
|
||||
.should('be.visible')
|
||||
.realHover();
|
||||
});
|
||||
// 7004-POSI-011
|
||||
cy.getByTestId(tooltipContent).should('be.visible');
|
||||
});
|
||||
|
||||
@@ -9,6 +9,7 @@ describe('trades', { tags: '@smoke' }, () => {
|
||||
before(() => {
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.setOnBoardingViewed();
|
||||
cy.intercept('POST', '/graphql', (req) => {
|
||||
if (req.body.operationName === 'Trades') {
|
||||
req.alias = '@Trades';
|
||||
|
||||
@@ -17,6 +17,7 @@ describe(
|
||||
cy.visit('/#/portfolio');
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.setOnBoardingViewed();
|
||||
cy.get('[data-testid="pathname-/portfolio"]').should('exist');
|
||||
});
|
||||
|
||||
@@ -122,6 +123,7 @@ describe('connect vega wallet', { tags: '@smoke', testIsolation: true }, () => {
|
||||
cy.visit('/#/portfolio');
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.setOnBoardingViewed();
|
||||
cy.get('[data-testid="pathname-/portfolio"]').should('exist');
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
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('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');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,91 @@
|
||||
import { selectAsset } from '../support/helpers';
|
||||
|
||||
const amountField = 'input[name="amount"]';
|
||||
const transferText = 'transfer-intro-text';
|
||||
const errorText = 'input-error-text';
|
||||
const formFieldError = 'input-error-text';
|
||||
const keyID = `[data-testid="${transferText}"] > .rounded-md`;
|
||||
const manageVegaWallet = 'manage-vega-wallet';
|
||||
const submitTransferBtn = '[type="submit"]';
|
||||
const toAddressField = '[name="toAddress"]';
|
||||
const transferForm = 'transfer-form';
|
||||
const walletTransfer = 'wallet-transfer';
|
||||
|
||||
const ASSET_EURO = 1;
|
||||
const ASSET_SEPOLIA_TBTC = 2;
|
||||
|
||||
describe(
|
||||
'transfer form validation',
|
||||
{ tags: '@regression', testIsolation: true },
|
||||
() => {
|
||||
beforeEach(() => {
|
||||
cy.mockWeb3Provider();
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.setVegaWallet();
|
||||
|
||||
cy.visit('/#/portfolio');
|
||||
cy.getByTestId(manageVegaWallet).click();
|
||||
cy.getByTestId(walletTransfer).click();
|
||||
|
||||
cy.wait('@Accounts');
|
||||
cy.wait('@Assets');
|
||||
|
||||
cy.mockVegaWalletTransaction();
|
||||
});
|
||||
|
||||
it('transfer Text', () => {
|
||||
// 1003-TRAN-003
|
||||
cy.getByTestId(transferText)
|
||||
.should('exist')
|
||||
.get(keyID)
|
||||
.invoke('text')
|
||||
.should('match', /[\w.]{6}…[\w.]{6}/);
|
||||
});
|
||||
|
||||
it('invalid vega key validation', () => {
|
||||
//1003-TRAN-013
|
||||
//1003-TRAN-004
|
||||
cy.getByTestId(transferForm).should('be.visible');
|
||||
cy.contains('Enter manually').click();
|
||||
cy.getByTestId(transferForm).find(toAddressField).type('asd');
|
||||
cy.getByTestId(transferForm).find(submitTransferBtn).click();
|
||||
cy.getByTestId(formFieldError).should('contain.text', 'Invalid Vega key');
|
||||
cy.contains('label', 'Vega key').should('be.visible');
|
||||
cy.contains('label', 'Asset').should('be.visible');
|
||||
cy.contains('label', 'Amount').should('be.visible');
|
||||
});
|
||||
|
||||
it('empty fields', () => {
|
||||
// 1003-TRAN-012
|
||||
cy.getByTestId(transferForm).find(submitTransferBtn).click();
|
||||
cy.getByTestId(formFieldError).should('contain.text', 'Required');
|
||||
cy.getByTestId(formFieldError).should('have.length', 3);
|
||||
});
|
||||
|
||||
it('min amount', () => {
|
||||
// 1002-WITH-010
|
||||
// 1003-TRAN-014
|
||||
selectAsset(ASSET_SEPOLIA_TBTC);
|
||||
cy.get(amountField).clear().type('0');
|
||||
cy.getByTestId(transferForm).find(submitTransferBtn).click();
|
||||
cy.getByTestId(errorText).should(
|
||||
'contain.text',
|
||||
'Value is below minimum'
|
||||
);
|
||||
});
|
||||
|
||||
it('max amount', () => {
|
||||
// 1003-TRAN-002
|
||||
// 1003-TRAN-011
|
||||
// 1003-TRAN-002
|
||||
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(transferForm).find(submitTransferBtn).click();
|
||||
cy.getByTestId(errorText).should(
|
||||
'contain.text',
|
||||
'You cannot transfer more than your available collateral'
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -5,206 +5,15 @@ const amountShortName = 'input[name="amount"] + div + span.text-xs';
|
||||
const assetSelection = 'select-asset';
|
||||
const assetBalance = 'asset-balance';
|
||||
const assetOption = 'rich-select-option';
|
||||
const transferText = 'transfer-intro-text';
|
||||
const errorText = 'input-error-text';
|
||||
const formFieldError = 'input-error-text';
|
||||
const includeTransferFeeRadioBtn = 'include-transfer-fee';
|
||||
const keyID = `[data-testid="${transferText}"] > .rounded-md`;
|
||||
const manageVegaWallet = 'manage-vega-wallet';
|
||||
const openTransferButton = 'open-transfer';
|
||||
const submitTransferBtn = '[type="submit"]';
|
||||
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_EURO = 1;
|
||||
const ASSET_SEPOLIA_TBTC = 2;
|
||||
|
||||
const collateralTab = 'Collateral';
|
||||
const toastCloseBtn = 'toast-close';
|
||||
const toastContent = 'toast-content';
|
||||
|
||||
describe('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();
|
||||
|
||||
// Only click if not already active otherwise sidebar will close
|
||||
cy.get('[data-testid="sidebar-content"]').then(($sidebarContent) => {
|
||||
if ($sidebarContent.find('h2').text() !== 'Transfer') {
|
||||
cy.get('[data-testid="sidebar"] [data-testid="Transfer"]').click();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
describe(
|
||||
'transfer form validation',
|
||||
{ tags: '@regression', testIsolation: true },
|
||||
() => {
|
||||
beforeEach(() => {
|
||||
cy.mockWeb3Provider();
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.setVegaWallet();
|
||||
|
||||
cy.visit('/#/portfolio');
|
||||
cy.getByTestId(manageVegaWallet).click();
|
||||
cy.getByTestId(walletTransfer).click();
|
||||
|
||||
cy.wait('@Accounts');
|
||||
cy.wait('@Assets');
|
||||
});
|
||||
|
||||
it('transfer Text', () => {
|
||||
// 1003-TRAN-003
|
||||
cy.getByTestId(transferText)
|
||||
.should('exist')
|
||||
.get(keyID)
|
||||
.invoke('text')
|
||||
.should('match', /[\w.]{6}…[\w.]{6}/);
|
||||
});
|
||||
|
||||
it('invalid vega key validation', () => {
|
||||
//1003-TRAN-013
|
||||
//1003-TRAN-004
|
||||
cy.getByTestId(transferForm).should('be.visible');
|
||||
cy.contains('Enter manually').click();
|
||||
cy.getByTestId(transferForm).find(toAddressField).type('asd');
|
||||
cy.getByTestId(transferForm).find(submitTransferBtn).click();
|
||||
cy.getByTestId(formFieldError).should('contain.text', 'Invalid Vega key');
|
||||
cy.contains('label', 'Vega key').should('be.visible');
|
||||
cy.contains('label', 'Asset').should('be.visible');
|
||||
cy.contains('label', 'Amount').should('be.visible');
|
||||
});
|
||||
|
||||
it('empty fields', () => {
|
||||
// 1003-TRAN-012
|
||||
cy.getByTestId(transferForm).find(submitTransferBtn).click();
|
||||
cy.getByTestId(formFieldError).should('contain.text', 'Required');
|
||||
cy.getByTestId(formFieldError).should('have.length', 3);
|
||||
});
|
||||
|
||||
it('min amount', () => {
|
||||
// 1002-WITH-010
|
||||
// 1003-TRAN-014
|
||||
selectAsset(ASSET_SEPOLIA_TBTC);
|
||||
cy.get(amountField).clear().type('0');
|
||||
cy.getByTestId(transferForm).find(submitTransferBtn).click();
|
||||
cy.getByTestId(errorText).should(
|
||||
'contain.text',
|
||||
'Value is below minimum'
|
||||
);
|
||||
});
|
||||
|
||||
it('max amount', () => {
|
||||
// 1003-TRAN-002
|
||||
// 1003-TRAN-011
|
||||
// 1003-TRAN-002
|
||||
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(transferForm).find(submitTransferBtn).click();
|
||||
cy.getByTestId(errorText).should(
|
||||
'contain.text',
|
||||
'You cannot transfer more than your available collateral'
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
describe('withdraw actions', { tags: '@smoke', testIsolation: true }, () => {
|
||||
beforeEach(() => {
|
||||
cy.mockWeb3Provider();
|
||||
|
||||
@@ -20,4 +20,4 @@ NX_APP_VERSION=v0.20.21-core-0.71.6
|
||||
NX_SUCCESSOR_MARKETS=false
|
||||
NX_STOP_ORDERS=false
|
||||
# NX_ICEBERG_ORDERS
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
|
||||
@@ -18,5 +18,5 @@ NX_VEGA_CONSOLE_URL=https://console.fairground.wtf
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
NX_STOP_ORDERS=true
|
||||
# NX_ICEBERG_ORDERS
|
||||
NX_ICEBERG_ORDERS=true
|
||||
# NX_PRODUCT_PERPETUALS
|
||||
@@ -0,0 +1,19 @@
|
||||
import { DepositContainer } from '@vegaprotocol/deposits';
|
||||
import { GetStarted } from '../../components/welcome-dialog';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
|
||||
export const Deposit = () => {
|
||||
return (
|
||||
<div className="py-16 px-8 flex w-full justify-center">
|
||||
<div className="lg:min-w-[700px] min-w-[300px] max-w-[700px]">
|
||||
<h1 className="text-4xl xl:text-5xl uppercase font-alpha calt">
|
||||
{t('Deposit')}
|
||||
</h1>
|
||||
<div className="mt-10">
|
||||
<DepositContainer />
|
||||
<GetStarted />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
import { Deposit } from './deposit';
|
||||
|
||||
export default Deposit;
|
||||
@@ -23,7 +23,6 @@ export const Home = () => {
|
||||
replace: true,
|
||||
});
|
||||
} else if (data) {
|
||||
update({ shouldDisplayWelcomeDialog: true });
|
||||
const marketDataId = data[0]?.id;
|
||||
if (marketDataId) {
|
||||
navigate(Links[Routes.MARKET](marketDataId), {
|
||||
|
||||
@@ -10,7 +10,6 @@ import type { Market } from '@vegaprotocol/markets';
|
||||
import { Filter } from '@vegaprotocol/orders';
|
||||
import { Tab, LocalStoragePersistTabs as Tabs } from '@vegaprotocol/ui-toolkit';
|
||||
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
|
||||
import { VegaWalletContainer } from '../../components/vega-wallet-container';
|
||||
import {
|
||||
ResizableGrid,
|
||||
ResizableGridPanel,
|
||||
@@ -99,64 +98,48 @@ const MainGrid = memo(
|
||||
<TradeGridChild>
|
||||
<Tabs storageKey="console-trade-grid-bottom">
|
||||
<Tab id="positions" name={t('Positions')}>
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.positions.component
|
||||
onMarketClick={onMarketClick}
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
<TradingViews.positions.component
|
||||
onMarketClick={onMarketClick}
|
||||
/>
|
||||
</Tab>
|
||||
<Tab id="open-orders" name={t('Open')}>
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.orders.component
|
||||
marketId={marketId}
|
||||
filter={Filter.Open}
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
<TradingViews.orders.component
|
||||
marketId={marketId}
|
||||
filter={Filter.Open}
|
||||
/>
|
||||
</Tab>
|
||||
<Tab id="closed-orders" name={t('Closed')}>
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.orders.component
|
||||
marketId={marketId}
|
||||
filter={Filter.Closed}
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
<TradingViews.orders.component
|
||||
marketId={marketId}
|
||||
filter={Filter.Closed}
|
||||
/>
|
||||
</Tab>
|
||||
<Tab id="rejected-orders" name={t('Rejected')}>
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.orders.component
|
||||
marketId={marketId}
|
||||
filter={Filter.Rejected}
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
<TradingViews.orders.component
|
||||
marketId={marketId}
|
||||
filter={Filter.Rejected}
|
||||
/>
|
||||
</Tab>
|
||||
<Tab id="orders" name={t('All')}>
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.orders.component marketId={marketId} />
|
||||
</VegaWalletContainer>
|
||||
<TradingViews.orders.component marketId={marketId} />
|
||||
</Tab>
|
||||
{FLAGS.STOP_ORDERS ? (
|
||||
<Tab id="stop-orders" name={t('Stop orders')}>
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.stopOrders.component />
|
||||
</VegaWalletContainer>
|
||||
<TradingViews.stopOrders.component />
|
||||
</Tab>
|
||||
) : null}
|
||||
<Tab id="fills" name={t('Fills')}>
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.fills.component
|
||||
marketId={marketId}
|
||||
onMarketClick={onMarketClick}
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
<TradingViews.fills.component
|
||||
marketId={marketId}
|
||||
onMarketClick={onMarketClick}
|
||||
/>
|
||||
</Tab>
|
||||
<Tab id="accounts" name={t('Collateral')}>
|
||||
<VegaWalletContainer>
|
||||
<TradingViews.collateral.component
|
||||
pinnedAsset={pinnedAsset}
|
||||
onMarketClick={onMarketClick}
|
||||
hideButtons
|
||||
/>
|
||||
</VegaWalletContainer>
|
||||
<TradingViews.collateral.component
|
||||
pinnedAsset={pinnedAsset}
|
||||
onMarketClick={onMarketClick}
|
||||
hideButtons
|
||||
/>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</TradeGridChild>
|
||||
|
||||
@@ -71,7 +71,7 @@ export const AccountHistoryContainer = () => {
|
||||
const { data: assets } = useAssetsDataProvider();
|
||||
|
||||
if (!pubKey) {
|
||||
return <Splash>{t('Connect wallet')}</Splash>;
|
||||
return <Splash>{t('Please connect Vega wallet')}</Splash>;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
import { Button } from '@vegaprotocol/ui-toolkit';
|
||||
import { Button, Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { DepositsTable } from '@vegaprotocol/deposits';
|
||||
import { depositsProvider } from '@vegaprotocol/deposits';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { useRef } from 'react';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import { useSidebar, ViewType } from '../../components/sidebar';
|
||||
|
||||
export const DepositsContainer = () => {
|
||||
const gridRef = useRef<AgGridReact | null>(null);
|
||||
const { pubKey, isReadOnly } = useVegaWallet();
|
||||
const { data, error } = useDataProvider({
|
||||
dataProvider: depositsProvider,
|
||||
@@ -17,11 +14,13 @@ export const DepositsContainer = () => {
|
||||
skip: !pubKey,
|
||||
});
|
||||
const setView = useSidebar((store) => store.setView);
|
||||
if (!pubKey) {
|
||||
return <Splash>{t('Please connect Vega wallet')}</Splash>;
|
||||
}
|
||||
return (
|
||||
<div className="h-full">
|
||||
<DepositsTable
|
||||
rowData={data}
|
||||
ref={gridRef}
|
||||
overlayNoRowsTemplate={error ? error.message : t('No deposits')}
|
||||
/>
|
||||
{!isReadOnly && (
|
||||
|
||||
@@ -13,7 +13,6 @@ import { FillsContainer } from '../../components/fills-container';
|
||||
import { PositionsContainer } from '../../components/positions-container';
|
||||
import { WithdrawalsContainer } from './withdrawals-container';
|
||||
import { OrdersContainer } from '../../components/orders-container';
|
||||
import { VegaWalletContainer } from '../../components/vega-wallet-container';
|
||||
import { LedgerContainer } from '../../components/ledger-container';
|
||||
import { AccountHistoryContainer } from './account-history-container';
|
||||
import {
|
||||
@@ -62,29 +61,19 @@ export const Portfolio = () => {
|
||||
<PortfolioGridChild>
|
||||
<Tabs storageKey="console-portfolio-top">
|
||||
<Tab id="account-history" name={t('Account history')}>
|
||||
<VegaWalletContainer>
|
||||
<AccountHistoryContainer />
|
||||
</VegaWalletContainer>
|
||||
<AccountHistoryContainer />
|
||||
</Tab>
|
||||
<Tab id="positions" name={t('Positions')}>
|
||||
<VegaWalletContainer>
|
||||
<PositionsContainer onMarketClick={onMarketClick} allKeys />
|
||||
</VegaWalletContainer>
|
||||
<PositionsContainer onMarketClick={onMarketClick} allKeys />
|
||||
</Tab>
|
||||
<Tab id="orders" name={t('Orders')}>
|
||||
<VegaWalletContainer>
|
||||
<OrdersContainer />
|
||||
</VegaWalletContainer>
|
||||
<OrdersContainer />
|
||||
</Tab>
|
||||
<Tab id="fills" name={t('Fills')}>
|
||||
<VegaWalletContainer>
|
||||
<FillsContainer onMarketClick={onMarketClick} />
|
||||
</VegaWalletContainer>
|
||||
<FillsContainer onMarketClick={onMarketClick} />
|
||||
</Tab>
|
||||
<Tab id="ledger-entries" name={t('Ledger entries')}>
|
||||
<VegaWalletContainer>
|
||||
<LedgerContainer />
|
||||
</VegaWalletContainer>
|
||||
<LedgerContainer />
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</PortfolioGridChild>
|
||||
@@ -97,14 +86,10 @@ export const Portfolio = () => {
|
||||
<PortfolioGridChild>
|
||||
<Tabs storageKey="console-portfolio-bottom">
|
||||
<Tab id="collateral" name={t('Collateral')}>
|
||||
<VegaWalletContainer>
|
||||
<AccountsContainer />
|
||||
</VegaWalletContainer>
|
||||
<AccountsContainer />
|
||||
</Tab>
|
||||
<Tab id="deposits" name={t('Deposits')}>
|
||||
<VegaWalletContainer>
|
||||
<DepositsContainer />
|
||||
</VegaWalletContainer>
|
||||
<DepositsContainer />
|
||||
</Tab>
|
||||
<Tab
|
||||
id="withdrawals"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Button } from '@vegaprotocol/ui-toolkit';
|
||||
import { Button, Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
withdrawalProvider,
|
||||
WithdrawalsTable,
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { VegaWalletContainer } from '../../components/vega-wallet-container';
|
||||
import { ViewType, useSidebar } from '../../components/sidebar';
|
||||
|
||||
export const WithdrawalsContainer = () => {
|
||||
@@ -19,9 +18,11 @@ export const WithdrawalsContainer = () => {
|
||||
});
|
||||
const setView = useSidebar((store) => store.setView);
|
||||
const { ready, delayed } = useIncompleteWithdrawals();
|
||||
|
||||
if (!pubKey) {
|
||||
return <Splash>{t('Please connect Vega wallet')}</Splash>;
|
||||
}
|
||||
return (
|
||||
<VegaWalletContainer>
|
||||
<>
|
||||
<div className="h-full relative">
|
||||
<WithdrawalsTable
|
||||
data-testid="withdrawals-history"
|
||||
@@ -43,6 +44,6 @@ export const WithdrawalsContainer = () => {
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</VegaWalletContainer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useCallback } from 'react';
|
||||
import { Button } from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
|
||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import type { PinnedAsset } from '@vegaprotocol/accounts';
|
||||
import { AccountManager } from '@vegaprotocol/accounts';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
|
||||
export const THROTTLE_UPDATE_TIME = 500;
|
||||
export const RISK_ACCEPTED_KEY = 'vega_risk_accepted';
|
||||
export const ONBOARDING_VIEWED_KEY = 'vega_onboarding_viewed';
|
||||
export const MAINNET_WELCOME_HEADER = t(
|
||||
'Trade cash settled futures on the fully decentralised Vega network.'
|
||||
);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { FillsManager } from '@vegaprotocol/fills';
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import { useDataGridEvents } from '@vegaprotocol/datagrid';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import type { DataGridSlice } from '../../stores/datagrid-store-slice';
|
||||
import { createDataGridSlice } from '../../stores/datagrid-store-slice';
|
||||
|
||||
|
||||
@@ -52,6 +52,9 @@ export const Navbar = ({
|
||||
'bg-vega-yellow text-vega-clight-50': theme === 'yellow',
|
||||
'text-default': theme === 'system',
|
||||
})}
|
||||
style={{
|
||||
background: theme === 'yellow' ? 'url(/testnet-logo-bg.png' : 'none',
|
||||
}}
|
||||
>
|
||||
<VLogo className="w-4" />
|
||||
</NavLink>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useDataGridEvents } from '@vegaprotocol/datagrid';
|
||||
import { Filter, OrderListManager } from '@vegaprotocol/orders';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Filter } from '@vegaprotocol/orders';
|
||||
import { OrderListManager } from '@vegaprotocol/orders';
|
||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import {
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
import { act, render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { Sidebar, SidebarContent, ViewType, useSidebar } from './sidebar';
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarButton,
|
||||
SidebarContent,
|
||||
ViewType,
|
||||
useSidebar,
|
||||
} from './sidebar';
|
||||
import { MemoryRouter, Route, Routes } from 'react-router-dom';
|
||||
import { VegaIconNames } from '@vegaprotocol/ui-toolkit';
|
||||
import { VegaWalletProvider } from '@vegaprotocol/wallet';
|
||||
|
||||
jest.mock('../node-health', () => ({
|
||||
NodeHealthContainer: () => <span data-testid="node-health" />,
|
||||
@@ -19,16 +27,23 @@ jest.mock('../settings', () => ({
|
||||
Settings: () => <div data-testid="settings" />,
|
||||
}));
|
||||
|
||||
jest.mock('../welcome-dialog', () => ({
|
||||
GetStarted: () => <div data-testid="get-started" />,
|
||||
}));
|
||||
|
||||
describe('Sidebar', () => {
|
||||
it.each(['/markets/all', '/portfolio'])(
|
||||
'does not render ticket and info',
|
||||
(path) => {
|
||||
render(
|
||||
<MemoryRouter initialEntries={[path]}>
|
||||
<Sidebar />
|
||||
</MemoryRouter>
|
||||
<VegaWalletProvider>
|
||||
<MemoryRouter initialEntries={[path]}>
|
||||
<Sidebar />
|
||||
</MemoryRouter>
|
||||
</VegaWalletProvider>
|
||||
);
|
||||
|
||||
expect(screen.getByTestId(ViewType.ViewAs)).toBeInTheDocument();
|
||||
expect(screen.getByTestId(ViewType.Settings)).toBeInTheDocument();
|
||||
expect(screen.getByTestId(ViewType.Transfer)).toBeInTheDocument();
|
||||
expect(screen.getByTestId(ViewType.Deposit)).toBeInTheDocument();
|
||||
@@ -43,11 +58,14 @@ describe('Sidebar', () => {
|
||||
|
||||
it('renders ticket and info on market pages', () => {
|
||||
render(
|
||||
<MemoryRouter initialEntries={['/markets/ABC']}>
|
||||
<Sidebar />
|
||||
</MemoryRouter>
|
||||
<VegaWalletProvider>
|
||||
<MemoryRouter initialEntries={['/markets/ABC']}>
|
||||
<Sidebar />
|
||||
</MemoryRouter>
|
||||
</VegaWalletProvider>
|
||||
);
|
||||
|
||||
expect(screen.getByTestId(ViewType.ViewAs)).toBeInTheDocument();
|
||||
expect(screen.getByTestId(ViewType.Settings)).toBeInTheDocument();
|
||||
expect(screen.getByTestId(ViewType.Transfer)).toBeInTheDocument();
|
||||
expect(screen.getByTestId(ViewType.Deposit)).toBeInTheDocument();
|
||||
@@ -61,9 +79,11 @@ describe('Sidebar', () => {
|
||||
|
||||
it('renders selected state', async () => {
|
||||
render(
|
||||
<MemoryRouter initialEntries={['/markets/ABC']}>
|
||||
<Sidebar />
|
||||
</MemoryRouter>
|
||||
<VegaWalletProvider>
|
||||
<MemoryRouter initialEntries={['/markets/ABC']}>
|
||||
<Sidebar />
|
||||
</MemoryRouter>
|
||||
</VegaWalletProvider>
|
||||
);
|
||||
|
||||
const settingsButton = screen.getByTestId(ViewType.Settings);
|
||||
@@ -87,11 +107,13 @@ describe('Sidebar', () => {
|
||||
describe('SidebarContent', () => {
|
||||
it('renders the correct content', () => {
|
||||
const { container } = render(
|
||||
<MemoryRouter initialEntries={['/markets/ABC']}>
|
||||
<Routes>
|
||||
<Route path="/markets/:marketId" element={<SidebarContent />} />
|
||||
</Routes>
|
||||
</MemoryRouter>
|
||||
<VegaWalletProvider>
|
||||
<MemoryRouter initialEntries={['/markets/ABC']}>
|
||||
<Routes>
|
||||
<Route path="/markets/:marketId" element={<SidebarContent />} />
|
||||
</Routes>
|
||||
</MemoryRouter>
|
||||
</VegaWalletProvider>
|
||||
);
|
||||
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
@@ -111,11 +133,13 @@ describe('SidebarContent', () => {
|
||||
|
||||
it('closes sidebar if market id is required but not present', () => {
|
||||
const { container } = render(
|
||||
<MemoryRouter initialEntries={['/portfolio']}>
|
||||
<Routes>
|
||||
<Route path="/portfolio" element={<SidebarContent />} />
|
||||
</Routes>
|
||||
</MemoryRouter>
|
||||
<VegaWalletProvider>
|
||||
<MemoryRouter initialEntries={['/portfolio']}>
|
||||
<Routes>
|
||||
<Route path="/portfolio" element={<SidebarContent />} />
|
||||
</Routes>
|
||||
</MemoryRouter>
|
||||
</VegaWalletProvider>
|
||||
);
|
||||
|
||||
act(() => {
|
||||
@@ -137,3 +161,25 @@ describe('SidebarContent', () => {
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
});
|
||||
|
||||
describe('SidebarButton', () => {
|
||||
it.each([ViewType.Info, ViewType.Deposit, ViewType.ViewAs])(
|
||||
'runs given callback regardless of requested view (%s)',
|
||||
async (view) => {
|
||||
const onClick = jest.fn();
|
||||
render(
|
||||
<SidebarButton
|
||||
icon={VegaIconNames.INFO}
|
||||
tooltip="INFO"
|
||||
onClick={onClick}
|
||||
view={view}
|
||||
/>
|
||||
);
|
||||
|
||||
const btn = screen.getByTestId(view);
|
||||
await userEvent.click(btn);
|
||||
|
||||
expect(onClick).toBeCalled();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
@@ -15,6 +15,8 @@ import { Tooltip } from '../../components/tooltip';
|
||||
import { WithdrawContainer } from '../withdraw-container';
|
||||
import { Routes as AppRoutes } from '../../pages/client-router';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import { GetStarted } from '../welcome-dialog';
|
||||
import { useVegaWallet, useViewAsDialog } from '@vegaprotocol/wallet';
|
||||
|
||||
const STORAGE_KEY = 'vega_sidebar_store';
|
||||
|
||||
@@ -25,6 +27,7 @@ export enum ViewType {
|
||||
Withdraw = 'Withdraw',
|
||||
Transfer = 'Transfer',
|
||||
Settings = 'Settings',
|
||||
ViewAs = 'ViewAs',
|
||||
}
|
||||
|
||||
type SidebarView =
|
||||
@@ -52,6 +55,8 @@ type SidebarView =
|
||||
|
||||
export const Sidebar = () => {
|
||||
const navClasses = 'flex lg:flex-col items-center gap-2 lg:gap-4 p-1';
|
||||
const setViewAsDialogOpen = useViewAsDialog((state) => state.setOpen);
|
||||
const { pubKeys } = useVegaWallet();
|
||||
return (
|
||||
<div className="flex lg:flex-col gap-2 h-full p-1" data-testid="sidebar">
|
||||
<nav className={navClasses}>
|
||||
@@ -104,6 +109,16 @@ export const Sidebar = () => {
|
||||
</Routes>
|
||||
</nav>
|
||||
<nav className={classNames(navClasses, 'ml-auto lg:mt-auto lg:ml-0')}>
|
||||
<SidebarButton
|
||||
view={ViewType.ViewAs}
|
||||
onClick={() => {
|
||||
setViewAsDialogOpen(true);
|
||||
}}
|
||||
icon={VegaIconNames.EYE}
|
||||
tooltip={t('View as party')}
|
||||
disabled={Boolean(pubKeys)}
|
||||
/>
|
||||
|
||||
<SidebarButton
|
||||
view={ViewType.Settings}
|
||||
icon={VegaIconNames.COG}
|
||||
@@ -115,25 +130,43 @@ export const Sidebar = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const SidebarButton = ({
|
||||
export const SidebarButton = ({
|
||||
view,
|
||||
icon,
|
||||
tooltip,
|
||||
disabled = false,
|
||||
onClick,
|
||||
}: {
|
||||
view: ViewType;
|
||||
view?: ViewType;
|
||||
icon: VegaIconNames;
|
||||
tooltip: string;
|
||||
disabled?: boolean;
|
||||
onClick?: () => void;
|
||||
}) => {
|
||||
const { currView, setView } = useSidebar((store) => ({
|
||||
currView: store.view,
|
||||
setView: store.setView,
|
||||
}));
|
||||
const buttonClasses = classNames('flex items-center p-1 rounded', {
|
||||
'text-vega-clight-200 dark:text-vega-cdark-200 hover:bg-vega-clight-500 dark:hover:bg-vega-cdark-500':
|
||||
view !== currView?.type,
|
||||
'bg-vega-yellow hover:bg-vega-yellow-550 text-black':
|
||||
view === currView?.type,
|
||||
});
|
||||
|
||||
const onSelect = (view: SidebarView['type']) => {
|
||||
if (view === currView?.type) {
|
||||
setView(null);
|
||||
} else {
|
||||
setView({ type: view });
|
||||
}
|
||||
};
|
||||
|
||||
const buttonClasses = classNames(
|
||||
'flex items-center p-1 rounded',
|
||||
'disabled:cursor-not-allowed disabled:text-vega-clight-500 dark:disabled:text-vega-cdark-500',
|
||||
{
|
||||
'text-vega-clight-200 dark:text-vega-cdark-200 enabled:hover:bg-vega-clight-500 dark:enabled:hover:bg-vega-cdark-500':
|
||||
!view || view !== currView?.type,
|
||||
'bg-vega-yellow enabled:hover:bg-vega-yellow-550 text-black':
|
||||
view && view === currView?.type,
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
description={tooltip}
|
||||
@@ -145,13 +178,8 @@ const SidebarButton = ({
|
||||
<button
|
||||
className={buttonClasses}
|
||||
data-testid={view}
|
||||
onClick={() => {
|
||||
if (view === currView?.type) {
|
||||
setView(null);
|
||||
} else {
|
||||
setView({ type: view });
|
||||
}
|
||||
}}
|
||||
onClick={onClick || (() => onSelect(view as SidebarView['type']))}
|
||||
disabled={disabled}
|
||||
>
|
||||
<VegaIcon name={icon} size={20} />
|
||||
</button>
|
||||
@@ -184,6 +212,7 @@ export const SidebarContent = () => {
|
||||
setView({ type: ViewType.Deposit, assetId })
|
||||
}
|
||||
/>
|
||||
<GetStarted />
|
||||
</ContentWrapper>
|
||||
);
|
||||
} else {
|
||||
@@ -207,6 +236,7 @@ export const SidebarContent = () => {
|
||||
return (
|
||||
<ContentWrapper title={t('Deposit')}>
|
||||
<DepositContainer assetId={view.assetId} />
|
||||
<GetStarted />
|
||||
</ContentWrapper>
|
||||
);
|
||||
}
|
||||
@@ -215,6 +245,7 @@ export const SidebarContent = () => {
|
||||
return (
|
||||
<ContentWrapper title={t('Withdraw')}>
|
||||
<WithdrawContainer assetId={view.assetId} />
|
||||
<GetStarted />
|
||||
</ContentWrapper>
|
||||
);
|
||||
}
|
||||
@@ -223,6 +254,7 @@ export const SidebarContent = () => {
|
||||
return (
|
||||
<ContentWrapper title={t('Transfer')}>
|
||||
<TransferContainer assetId={view.assetId} />
|
||||
<GetStarted />
|
||||
</ContentWrapper>
|
||||
);
|
||||
}
|
||||
@@ -277,7 +309,7 @@ export const useSidebar = create<{
|
||||
view: null,
|
||||
setView: (x) =>
|
||||
set(() => {
|
||||
if (x === null) {
|
||||
if (x == null) {
|
||||
return { view: null, init: false };
|
||||
}
|
||||
|
||||
|
||||
+10
@@ -27,6 +27,16 @@ describe('VegaWalletConnectButton', () => {
|
||||
it('should fire dialog when not connected', () => {
|
||||
render(generateJsx({ pubKey: null } as VegaWalletContextShape));
|
||||
|
||||
const button = screen.getByTestId('connect-vega-wallet');
|
||||
expect(button).toHaveTextContent('Get started');
|
||||
fireEvent.click(button);
|
||||
expect(mockUpdateDialogOpen).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should render "Connect" when browser wallet is detected', () => {
|
||||
window.vega = window.vega || ({} as Vega);
|
||||
render(generateJsx({ pubKey: null } as VegaWalletContextShape));
|
||||
|
||||
const button = screen.getByTestId('connect-vega-wallet');
|
||||
expect(button).toHaveTextContent('Connect');
|
||||
fireEvent.click(button);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import CopyToClipboard from 'react-copy-to-clipboard';
|
||||
import { isBrowserWalletInstalled } from '@vegaprotocol/wallet';
|
||||
import { truncateByChars } from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import {
|
||||
@@ -37,7 +38,7 @@ export const VegaWalletConnectButton = () => {
|
||||
fetchPubKeys,
|
||||
} = useVegaWallet();
|
||||
const isConnected = pubKey !== null;
|
||||
|
||||
const walletInstalled = isBrowserWalletInstalled();
|
||||
const activeKey = useMemo(() => {
|
||||
return pubKeys?.find((pk) => pk.publicKey === pubKey);
|
||||
}, [pubKey, pubKeys]);
|
||||
@@ -118,7 +119,9 @@ export const VegaWalletConnectButton = () => {
|
||||
intent={Intent.None}
|
||||
icon={<VegaIcon name={VegaIconNames.ARROW_RIGHT} size={14} />}
|
||||
>
|
||||
<span className="whitespace-nowrap uppercase">{t('Connect')}</span>
|
||||
<span className="whitespace-nowrap uppercase">
|
||||
{walletInstalled ? t('Connect') : t('Get started')}
|
||||
</span>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
|
||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
import { GetStarted } from './get-started';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
|
||||
describe('GetStarted', () => {
|
||||
const renderComponent = (context: Partial<VegaWalletContextShape> = {}) => {
|
||||
return render(
|
||||
<VegaWalletContext.Provider value={context as VegaWalletContextShape}>
|
||||
<GetStarted />
|
||||
</VegaWalletContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
it('renders full get started content if not connected and no browser wallet detected', () => {
|
||||
renderComponent();
|
||||
expect(screen.getByTestId('get-started-banner')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders connect prompt if no pubKey but wallet installed', () => {
|
||||
globalThis.window.vega = {} as Vega;
|
||||
renderComponent();
|
||||
expect(screen.getByTestId('order-connect-wallet')).toBeInTheDocument();
|
||||
globalThis.window.vega = undefined as unknown as Vega;
|
||||
});
|
||||
|
||||
it('renders nothing if connected', () => {
|
||||
const { container } = renderComponent({ pubKey: 'my-pubkey' });
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,107 @@
|
||||
import classNames from 'classnames';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { ExternalLink, Intent, TradingButton } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
useVegaWallet,
|
||||
useVegaWalletDialogStore,
|
||||
isBrowserWalletInstalled,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import { Networks, useEnvironment } from '@vegaprotocol/environment';
|
||||
import { useLocalStorage } from '@vegaprotocol/react-helpers';
|
||||
import * as constants from '../constants';
|
||||
|
||||
interface Props {
|
||||
lead?: string;
|
||||
}
|
||||
|
||||
export const GetStarted = ({ lead }: Props) => {
|
||||
const { pubKey } = useVegaWallet();
|
||||
const { VEGA_ENV, VEGA_NETWORKS } = useEnvironment();
|
||||
const CANONICAL_URL = VEGA_NETWORKS[VEGA_ENV] || 'https://console.vega.xyz';
|
||||
|
||||
const [, setOnboardingViewed] = useLocalStorage(
|
||||
constants.ONBOARDING_VIEWED_KEY
|
||||
);
|
||||
|
||||
const openVegaWalletDialog = useVegaWalletDialogStore(
|
||||
(store) => store.openVegaWalletDialog
|
||||
);
|
||||
|
||||
const onButtonClick = () => {
|
||||
openVegaWalletDialog();
|
||||
setOnboardingViewed('true');
|
||||
};
|
||||
|
||||
const wrapperClasses = classNames(
|
||||
'flex flex-col py-4 px-6 gap-4 rounded',
|
||||
'bg-vega-blue-300 dark:bg-vega-blue-700',
|
||||
'border border-vega-blue-350 dark:border-vega-blue-650',
|
||||
{ 'mt-8': !lead }
|
||||
);
|
||||
|
||||
if (!pubKey && !isBrowserWalletInstalled()) {
|
||||
return (
|
||||
<div className={wrapperClasses} data-testid="get-started-banner">
|
||||
{lead && <h2>{lead}</h2>}
|
||||
<h3 className="text-lg">{t('Get started')}</h3>
|
||||
<div>
|
||||
<ul className="list-decimal list-inside">
|
||||
<li>{t('Get a Vega wallet')}</li>
|
||||
<li>{t('Connect')}</li>
|
||||
<li>{t('Deposit funds')}</li>
|
||||
<li>{t('Open a position')}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<TradingButton
|
||||
intent={Intent.Info}
|
||||
onClick={onButtonClick}
|
||||
data-testid="get-started-button"
|
||||
>
|
||||
{t('Get started')}
|
||||
</TradingButton>
|
||||
</div>
|
||||
{VEGA_ENV === Networks.MAINNET && (
|
||||
<p className="text-sm">
|
||||
{t('Experiment for free with virtual assets on')}{' '}
|
||||
<ExternalLink href={CANONICAL_URL}>
|
||||
{t('Fairground Testnet')}
|
||||
</ExternalLink>
|
||||
</p>
|
||||
)}
|
||||
{VEGA_ENV === Networks.TESTNET && (
|
||||
<p className="text-sm">
|
||||
{t('Ready to trade with real funds?')}{' '}
|
||||
<ExternalLink href={CANONICAL_URL}>
|
||||
{t('Switch to Mainnet')}
|
||||
</ExternalLink>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!pubKey) {
|
||||
return (
|
||||
<div className={wrapperClasses}>
|
||||
<p className="text-sm mb-1">
|
||||
You need a{' '}
|
||||
<ExternalLink href="https://vega.xyz/wallet">
|
||||
Vega wallet
|
||||
</ExternalLink>{' '}
|
||||
to start trading in this market.
|
||||
</p>
|
||||
<TradingButton
|
||||
onClick={openVegaWalletDialog}
|
||||
size="small"
|
||||
data-testid="order-connect-wallet"
|
||||
intent={Intent.Info}
|
||||
>
|
||||
{t('Connect wallet')}
|
||||
</TradingButton>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './welcome-dialog';
|
||||
export * from './risk-message';
|
||||
export * from './get-started';
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import { Networks, useEnvironment } from '@vegaprotocol/environment';
|
||||
import { RiskNoticeDialog } from './risk-notice-dialog';
|
||||
|
||||
jest.mock('@vegaprotocol/environment');
|
||||
|
||||
const mockEnvDefinitions = {
|
||||
VEGA_CONFIG_URL: 'https://config.url',
|
||||
VEGA_URL: 'https://test.url',
|
||||
VEGA_NETWORKS: JSON.stringify({}),
|
||||
};
|
||||
|
||||
describe('Risk notice dialog', () => {
|
||||
const mockOnClose = jest.fn();
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it.each`
|
||||
assertion | network
|
||||
${'displays'} | ${Networks.CUSTOM}
|
||||
${'displays'} | ${Networks.DEVNET}
|
||||
${'displays'} | ${Networks.TESTNET}
|
||||
`(
|
||||
'$assertion a generic message on $network',
|
||||
async ({ assertion, network }) => {
|
||||
// @ts-ignore ignore mock implementation
|
||||
useEnvironment.mockImplementation(() => ({
|
||||
...mockEnvDefinitions,
|
||||
VEGA_ENV: network,
|
||||
}));
|
||||
|
||||
render(<RiskNoticeDialog onClose={mockOnClose} network={network} />);
|
||||
|
||||
expect(
|
||||
screen.getByText(
|
||||
new RegExp(
|
||||
`This application for trading on Vega is connected to ${network}`
|
||||
)
|
||||
)
|
||||
).toBeInTheDocument();
|
||||
|
||||
const button = screen.getByRole('button', {
|
||||
name: 'Continue',
|
||||
});
|
||||
fireEvent.click(button);
|
||||
expect(mockOnClose).toHaveBeenCalled();
|
||||
}
|
||||
);
|
||||
});
|
||||
@@ -1,89 +0,0 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import {
|
||||
Button,
|
||||
Link,
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { RISK_ACCEPTED_KEY } from '../constants';
|
||||
import { TelemetryApproval } from './telemetry-approval';
|
||||
import type { Networks } from '@vegaprotocol/environment';
|
||||
import {
|
||||
useEnvironment,
|
||||
DocsLinks,
|
||||
ExternalLinks,
|
||||
} from '@vegaprotocol/environment';
|
||||
import { useLocalStorage } from '@vegaprotocol/react-helpers';
|
||||
|
||||
interface Props {
|
||||
onClose: () => void;
|
||||
network: Networks;
|
||||
}
|
||||
export const RiskNoticeDialog = ({ onClose, network }: Props) => {
|
||||
const [, setValue] = useLocalStorage(RISK_ACCEPTED_KEY);
|
||||
|
||||
const handleAcceptRisk = () => {
|
||||
onClose();
|
||||
setValue('true');
|
||||
};
|
||||
|
||||
return (
|
||||
<TestnetContent network={network} handleAcceptRisk={handleAcceptRisk} />
|
||||
);
|
||||
};
|
||||
|
||||
const TestnetContent = ({
|
||||
network,
|
||||
handleAcceptRisk,
|
||||
}: {
|
||||
network: Networks;
|
||||
handleAcceptRisk: () => void;
|
||||
}) => {
|
||||
const { GITHUB_FEEDBACK_URL } = useEnvironment();
|
||||
return (
|
||||
<>
|
||||
<p className="mb-4">
|
||||
{t(
|
||||
'This application for trading on Vega is connected to %s, meaning you are free to try out trading with virtual assets and no risk.',
|
||||
[network]
|
||||
)}
|
||||
</p>
|
||||
<p className="mb-4">
|
||||
{t(
|
||||
'Your Vega wallet must also be connected to %s, and your Ethereum wallet must be connected to Sepolia.',
|
||||
[network]
|
||||
)}
|
||||
</p>
|
||||
{GITHUB_FEEDBACK_URL && DocsLinks && (
|
||||
<ul className="list-disc pl-4">
|
||||
<li className="mb-1">
|
||||
<Link href={ExternalLinks.VEGA_WALLET_URL} target="_blank">
|
||||
<span className="underline">{t('Get a Vega Wallet')}</span>{' '}
|
||||
<VegaIcon name={VegaIconNames.OPEN_EXTERNAL} />
|
||||
</Link>
|
||||
</li>
|
||||
<li className="mb-1">
|
||||
<Link href={DocsLinks.VEGA_WALLET_TOOLS_URL} target="_blank">
|
||||
<span className="underline">{t('Learn about Vega Wallet')}</span>{' '}
|
||||
<VegaIcon name={VegaIconNames.OPEN_EXTERNAL} />
|
||||
</Link>
|
||||
</li>
|
||||
<li className="mb-1">
|
||||
<Link href={GITHUB_FEEDBACK_URL} target="_blank">
|
||||
<span className="underline">{t('Provide feedback')}</span>{' '}
|
||||
<VegaIcon name={VegaIconNames.OPEN_EXTERNAL} />
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
)}
|
||||
<div className="my-4">
|
||||
<TelemetryApproval
|
||||
helpText={t(
|
||||
'Help identify bugs and improve the service by sharing anonymous usage data. You can change this in your settings at any time.'
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<Button onClick={handleAcceptRisk}>{t('Continue')}</Button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,159 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { GetStarted } from './get-started';
|
||||
import { TradingButton } from '@vegaprotocol/ui-toolkit';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Links, Routes } from '../../pages/client-router';
|
||||
import { useLocalStorage } from '@vegaprotocol/react-helpers';
|
||||
import * as constants from '../constants';
|
||||
import { Networks, useEnvironment } from '@vegaprotocol/environment';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export const WelcomeDialogContent = () => {
|
||||
const { VEGA_ENV } = useEnvironment();
|
||||
const [, setOnboardingViewed] = useLocalStorage(
|
||||
constants.ONBOARDING_VIEWED_KEY
|
||||
);
|
||||
const navigate = useNavigate();
|
||||
const browseMarkets = () => {
|
||||
const link = Links[Routes.MARKETS]();
|
||||
navigate(link);
|
||||
setOnboardingViewed('true');
|
||||
};
|
||||
const lead =
|
||||
VEGA_ENV === Networks.MAINNET
|
||||
? t('Start trading on the worlds most advanced decentralised exchange.')
|
||||
: t(
|
||||
'Free from the risks of real trading, Fairground is a safe and fun place to try out Vega yourself with virtual assets.'
|
||||
);
|
||||
return (
|
||||
<div className="flex flex-col sm:flex-row gap-8">
|
||||
<div className="sm:w-1/2 flex flex-col justify-between pt-3">
|
||||
<ul className="ml-0">
|
||||
<ListItemContent
|
||||
icon={<NonCustodialIcon />}
|
||||
title={t('Non-custodial and pseudonymous')}
|
||||
text={t('No third party has access to your funds.')}
|
||||
/>
|
||||
<ListItemContent
|
||||
icon={<PurposeBuiltIcon />}
|
||||
title={t('Purpose built proof of stake blockchain')}
|
||||
text={t(
|
||||
'Fully decentralised high performance peer-to-network trading.'
|
||||
)}
|
||||
/>
|
||||
<ListItemContent
|
||||
icon={<RewardIcon />}
|
||||
title={t('Low fees and no cost to place orders')}
|
||||
text={t(
|
||||
'Fees work like a CEX with no per-transaction gas for orders'
|
||||
)}
|
||||
/>
|
||||
</ul>
|
||||
<TradingButton
|
||||
onClick={browseMarkets}
|
||||
className="block w-full"
|
||||
data-testid="browse-markets-button"
|
||||
>
|
||||
{t('Browse the markets')}
|
||||
</TradingButton>
|
||||
</div>
|
||||
<div className="sm:w-1/2">
|
||||
<GetStarted lead={lead} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ListItemContent = ({
|
||||
icon,
|
||||
title,
|
||||
text,
|
||||
}: {
|
||||
icon: ReactNode;
|
||||
title: string;
|
||||
text: string;
|
||||
}) => {
|
||||
return (
|
||||
<li className="my-4 flex gap-3">
|
||||
<div className="shrink-0 pt-1">{icon}</div>
|
||||
<div>
|
||||
<h3 className="text-lg leading-snug mb-2">{title}</h3>
|
||||
<p className="text-sm text-secondary">{text}</p>
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
};
|
||||
|
||||
const PurposeBuiltIcon = () => {
|
||||
return (
|
||||
<svg width="40" height="40" viewBox="0 0 30 30" fill="currentColor">
|
||||
<rect x="14" y="20" width="2" height="2" />
|
||||
<rect x="12" y="18" width="2" height="2" />
|
||||
<rect x="10" y="8" width="2" height="10" />
|
||||
<rect x="16" y="18" width="2" height="2" />
|
||||
<rect x="20" y="16" width="2" height="2" />
|
||||
<rect x="18" y="8" width="2" height="8" />
|
||||
<rect x="28" y="2" width="2" height="26" />
|
||||
<rect y="2" width="2" height="26" />
|
||||
<rect x="28" width="2" height="26" transform="rotate(90 28 0)" />
|
||||
<rect x="28" y="28" width="2" height="26" transform="rotate(90 28 28)" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
const NonCustodialIcon = () => {
|
||||
return (
|
||||
<svg width="40" height="40" viewBox="0 0 30 30" className="fill-current">
|
||||
<rect x="8" y="22" width="14" height="2" />
|
||||
<rect x="8" y="12" width="14" height="2" />
|
||||
<rect
|
||||
x="22"
|
||||
y="24"
|
||||
width="2"
|
||||
height="12"
|
||||
transform="rotate(-180 22 24)"
|
||||
/>
|
||||
<rect
|
||||
x="10"
|
||||
y="24"
|
||||
width="2"
|
||||
height="12"
|
||||
transform="rotate(-180 10 24)"
|
||||
/>
|
||||
<rect x="12" y="12" width="2" height="2" transform="rotate(-180 12 12)" />
|
||||
<rect x="20" y="12" width="2" height="2" transform="rotate(-180 20 12)" />
|
||||
<rect x="12" y="10" width="2" height="2" transform="rotate(-180 12 10)" />
|
||||
<rect x="20" y="10" width="2" height="2" transform="rotate(-180 20 10)" />
|
||||
<rect x="18" y="8" width="2" height="2" transform="rotate(-180 18 8)" />
|
||||
<rect x="14" y="8" width="2" height="2" transform="rotate(-180 14 8)" />
|
||||
<rect x="16" y="8" width="2" height="2" transform="rotate(-180 16 8)" />
|
||||
<rect x="28" y="2" width="2" height="26" />
|
||||
<rect y="2" width="2" height="26" />
|
||||
<rect x="28" width="2" height="26" transform="rotate(90 28 0)" />
|
||||
<rect x="28" y="28" width="2" height="26" transform="rotate(90 28 28)" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
const RewardIcon = () => {
|
||||
return (
|
||||
<svg width="40" height="40" viewBox="0 0 30 30" fill="currentColor">
|
||||
<rect x="6" y="16" width="4" height="2" />
|
||||
<rect x="10" y="14" width="2" height="2" />
|
||||
<rect x="12" y="24" width="4" height="2" transform="rotate(-90 12 24)" />
|
||||
<rect x="10" y="20" width="2" height="2" transform="rotate(-90 10 20)" />
|
||||
<rect x="20" y="18" width="4" height="2" transform="rotate(-180 20 18)" />
|
||||
<rect x="16" y="20" width="2" height="2" transform="rotate(-180 16 20)" />
|
||||
<rect x="16" y="14" width="2" height="2" transform="rotate(90 16 14)" />
|
||||
<rect x="22" y="10" width="2" height="2" transform="rotate(90 22 10)" />
|
||||
<rect x="20" y="8" width="2" height="2" transform="rotate(90 20 8)" />
|
||||
<rect x="24" y="8" width="2" height="2" transform="rotate(90 24 8)" />
|
||||
<rect x="22" y="6" width="2" height="2" transform="rotate(90 22 6)" />
|
||||
<rect x="12" y="10" width="2" height="4" />
|
||||
<rect x="28" y="2" width="2" height="26" />
|
||||
<rect y="2" width="2" height="26" />
|
||||
<rect x="28" width="2" height="26" transform="rotate(90 28 0)" />
|
||||
<rect x="28" y="28" width="2" height="26" transform="rotate(90 28 28)" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -1,68 +1,52 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { Dialog } from '@vegaprotocol/ui-toolkit';
|
||||
import React from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Dialog, Intent } from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useLocalStorage } from '@vegaprotocol/react-helpers';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { activeMarketsProvider } from '@vegaprotocol/markets';
|
||||
import * as constants from '../constants';
|
||||
import { RiskNoticeDialog } from './risk-notice-dialog';
|
||||
import { WelcomeNoticeDialog } from './welcome-notice-dialog';
|
||||
import { useGlobalStore } from '../../stores';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { Networks } from '@vegaprotocol/environment';
|
||||
import { isTestEnv } from '@vegaprotocol/utils';
|
||||
import { isBrowserWalletInstalled } from '@vegaprotocol/wallet';
|
||||
import * as constants from '../constants';
|
||||
import { WelcomeDialogContent } from './welcome-dialog-content';
|
||||
import { getConfig } from '@vegaprotocol/wallet';
|
||||
import { Links, Routes } from '../../pages/client-router';
|
||||
import { useGlobalStore } from '../../stores';
|
||||
|
||||
export const WelcomeDialog = () => {
|
||||
const { VEGA_ENV } = useEnvironment();
|
||||
const { pathname } = useLocation();
|
||||
let dialogContent: React.ReactNode;
|
||||
let title = '';
|
||||
let size: 'small' | 'medium' = 'small';
|
||||
let onClose: ((open: boolean) => void) | undefined = undefined;
|
||||
const [riskAccepted] = useLocalStorage(constants.RISK_ACCEPTED_KEY);
|
||||
const { data } = useDataProvider({
|
||||
dataProvider: activeMarketsProvider,
|
||||
variables: undefined,
|
||||
});
|
||||
const [onBoardingViewed, setOnboardingViewed] = useLocalStorage(
|
||||
constants.ONBOARDING_VIEWED_KEY
|
||||
);
|
||||
const navigate = useNavigate();
|
||||
const isOnboardingDialogNeeded =
|
||||
onBoardingViewed !== 'true' && !isBrowserWalletInstalled() && !getConfig();
|
||||
const marketId = useGlobalStore((store) => store.marketId);
|
||||
|
||||
const update = useGlobalStore((store) => store.update);
|
||||
const shouldDisplayWelcomeDialog = useGlobalStore(
|
||||
(store) => store.shouldDisplayWelcomeDialog
|
||||
const onClose = () => {
|
||||
setOnboardingViewed('true');
|
||||
const link = marketId
|
||||
? Links[Routes.MARKET](marketId)
|
||||
: Links[Routes.HOME]();
|
||||
navigate(link);
|
||||
};
|
||||
const title = (
|
||||
<span className="font-alpha calt" data-testid="welcome-title">
|
||||
{t('Console')}{' '}
|
||||
<span className="text-vega-clight-100 dark:text-vega-cdark-100">
|
||||
{VEGA_ENV}
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
|
||||
const isRiskDialogNeeded =
|
||||
riskAccepted !== 'true' && VEGA_ENV !== Networks.MAINNET && !isTestEnv();
|
||||
|
||||
const isWelcomeDialogNeeded = pathname === '/' || shouldDisplayWelcomeDialog;
|
||||
|
||||
const onCloseDialog = useCallback(() => {
|
||||
update({
|
||||
shouldDisplayWelcomeDialog: isRiskDialogNeeded,
|
||||
});
|
||||
}, [update, isRiskDialogNeeded]);
|
||||
|
||||
if (isRiskDialogNeeded) {
|
||||
dialogContent = (
|
||||
<RiskNoticeDialog onClose={onCloseDialog} network={VEGA_ENV} />
|
||||
);
|
||||
title = t('Vega Console');
|
||||
size = 'medium';
|
||||
} else if (isWelcomeDialogNeeded && data?.length === 0) {
|
||||
dialogContent = <WelcomeNoticeDialog />;
|
||||
onClose = onCloseDialog;
|
||||
} else {
|
||||
dialogContent = null as React.ReactNode;
|
||||
}
|
||||
|
||||
return (
|
||||
return isOnboardingDialogNeeded ? (
|
||||
<Dialog
|
||||
open={Boolean(dialogContent)}
|
||||
open
|
||||
title={title}
|
||||
size={size}
|
||||
size="medium"
|
||||
onChange={onClose}
|
||||
intent={Intent.None}
|
||||
dataTestId="welcome-dialog"
|
||||
>
|
||||
{dialogContent}
|
||||
<WelcomeDialogContent />
|
||||
</Dialog>
|
||||
);
|
||||
) : null;
|
||||
};
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import {
|
||||
DApp,
|
||||
Networks,
|
||||
TOKEN_NEW_MARKET_PROPOSAL,
|
||||
TOKEN_PROPOSALS,
|
||||
useEnvironment,
|
||||
useLinks,
|
||||
ExternalLinks,
|
||||
} from '@vegaprotocol/environment';
|
||||
import { ProposedMarkets } from './proposed-markets';
|
||||
import { TelemetryApproval } from './telemetry-approval';
|
||||
|
||||
export const WelcomeNoticeDialog = () => {
|
||||
const { VEGA_ENV } = useEnvironment();
|
||||
const tokenLink = useLinks(DApp.Token);
|
||||
const consoleFairgroundLink = useLinks(DApp.Console, Networks.TESTNET);
|
||||
const isMainnet = VEGA_ENV === Networks.MAINNET;
|
||||
return (
|
||||
<>
|
||||
<h1
|
||||
data-testid="welcome-notice-title"
|
||||
className="text-2xl uppercase mb-6 text-center font-alpha calt"
|
||||
>
|
||||
{t('Welcome to Console')}
|
||||
</h1>
|
||||
<p className="leading-6 mb-4">
|
||||
{t(
|
||||
'There are no markets to trade on right now. Trading on Vega is now live, but markets need to pass a governance vote before they can be traded on. In the meantime:'
|
||||
)}
|
||||
</p>
|
||||
<ul className="list-[square] pl-4 mb-4">
|
||||
{isMainnet && (
|
||||
<li className="mb-1">
|
||||
<ExternalLink target="_blank" href={consoleFairgroundLink()}>
|
||||
{t('Try out Console')}
|
||||
</ExternalLink>
|
||||
{t(' on Fairground, our Testnet')}
|
||||
</li>
|
||||
)}
|
||||
<li className="mb-1">
|
||||
<ExternalLink target="_blank" href={tokenLink(TOKEN_PROPOSALS)}>
|
||||
{t('View and vote for proposed markets')}
|
||||
</ExternalLink>
|
||||
</li>
|
||||
<li className="mb-1">
|
||||
<ExternalLink
|
||||
target="_blank"
|
||||
href={tokenLink(TOKEN_NEW_MARKET_PROPOSAL)}
|
||||
>
|
||||
{t('Propose a market')}
|
||||
</ExternalLink>
|
||||
</li>
|
||||
<li className="mb-1">
|
||||
<ExternalLink target="_blank" href={ExternalLinks.BLOG}>
|
||||
{t('Read about the mainnet launch')}
|
||||
</ExternalLink>
|
||||
</li>
|
||||
</ul>
|
||||
{isMainnet && (
|
||||
<TelemetryApproval
|
||||
helpText={t(
|
||||
'Help identify bugs and improve the service by sharing anonymous usage data. You can change this in your settings at any time.'
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<ProposedMarkets />
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -31,6 +31,10 @@ const LazyDisclaimer = dynamic(() => import('../client-pages/disclaimer'), {
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
const LazyDeposit = dynamic(() => import('../client-pages/deposit'), {
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
export enum Routes {
|
||||
HOME = '/',
|
||||
MARKET = '/markets/:marketId',
|
||||
@@ -38,6 +42,7 @@ export enum Routes {
|
||||
PORTFOLIO = '/portfolio',
|
||||
LIQUIDITY = '/liquidity/:marketId',
|
||||
DISCLAIMER = '/disclaimer',
|
||||
DEPOSIT = '/deposit',
|
||||
}
|
||||
|
||||
type ConsoleLinks = { [r in Routes]: (...args: string[]) => string };
|
||||
@@ -51,6 +56,7 @@ export const Links: ConsoleLinks = {
|
||||
[Routes.LIQUIDITY]: (marketId: string) =>
|
||||
trimEnd(Routes.LIQUIDITY.replace(':marketId', marketId)),
|
||||
[Routes.DISCLAIMER]: () => Routes.DISCLAIMER,
|
||||
[Routes.DEPOSIT]: () => Routes.DEPOSIT,
|
||||
};
|
||||
|
||||
const routerConfig: RouteObject[] = [
|
||||
@@ -97,6 +103,7 @@ const routerConfig: RouteObject[] = [
|
||||
path: Routes.DISCLAIMER,
|
||||
element: <LazyDisclaimer />,
|
||||
},
|
||||
{ path: Routes.DEPOSIT, element: <LazyDeposit /> },
|
||||
{
|
||||
path: '*',
|
||||
element: (
|
||||
|
||||
@@ -2,7 +2,7 @@ import {
|
||||
AssetDetailsDialog,
|
||||
useAssetDetailsDialogStore,
|
||||
} from '@vegaprotocol/assets';
|
||||
import { VegaConnectDialog } from '@vegaprotocol/wallet';
|
||||
import { VegaConnectDialog, ViewAsDialog } from '@vegaprotocol/wallet';
|
||||
import { Connectors } from '../lib/vega-connectors';
|
||||
import {
|
||||
Web3ConnectUncontrolledDialog,
|
||||
@@ -19,6 +19,7 @@ const DialogsContainer = () => {
|
||||
connectors={Connectors}
|
||||
riskMessage={<RiskMessage />}
|
||||
/>
|
||||
<ViewAsDialog connector={Connectors.view} />
|
||||
<AssetDetailsDialog
|
||||
assetId={id}
|
||||
trigger={trigger || null}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
@@ -5,7 +5,6 @@ import produce from 'immer';
|
||||
interface GlobalStore {
|
||||
marketId: string | null;
|
||||
update: (store: Partial<Omit<GlobalStore, 'update'>>) => void;
|
||||
shouldDisplayWelcomeDialog: boolean;
|
||||
}
|
||||
|
||||
interface PageTitleStore {
|
||||
@@ -15,7 +14,6 @@ interface PageTitleStore {
|
||||
|
||||
export const useGlobalStore = create<GlobalStore>()((set) => ({
|
||||
marketId: LocalStorage.getItem('marketId') || null,
|
||||
shouldDisplayWelcomeDialog: false,
|
||||
update: (newState) => {
|
||||
set(
|
||||
produce((state: GlobalStore) => {
|
||||
|
||||
@@ -75,7 +75,7 @@ export const AccountBreakdownDialog = memo(
|
||||
}) => {
|
||||
return (
|
||||
<Dialog
|
||||
size="medium"
|
||||
size="large"
|
||||
open={Boolean(assetId)}
|
||||
onChange={(isOpen) => {
|
||||
if (!isOpen) {
|
||||
@@ -118,7 +118,6 @@ export const AccountManager = ({
|
||||
onMarketClick,
|
||||
gridProps,
|
||||
}: AccountManagerProps) => {
|
||||
const gridRef = useRef<AgGridReact | null>(null);
|
||||
const [breakdownAssetId, setBreakdownAssetId] = useState<string>();
|
||||
const { data, error } = useDataProvider({
|
||||
dataProvider: aggregatedAccountsDataProvider,
|
||||
@@ -138,7 +137,6 @@ export const AccountManager = ({
|
||||
return (
|
||||
<div className="relative h-full">
|
||||
<AccountTable
|
||||
ref={gridRef}
|
||||
rowData={data}
|
||||
onClickAsset={onClickAsset}
|
||||
onClickDeposit={onClickDeposit}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { forwardRef, useMemo, useCallback } from 'react';
|
||||
import { useMemo, useCallback } from 'react';
|
||||
import {
|
||||
addDecimalsFormatNumber,
|
||||
addDecimalsFormatNumberQuantum,
|
||||
@@ -21,7 +21,7 @@ import type {
|
||||
RowHeightParams,
|
||||
ColDef,
|
||||
} from 'ag-grid-community';
|
||||
import type { AgGridReact, AgGridReactProps } from 'ag-grid-react';
|
||||
import type { AgGridReactProps } from 'ag-grid-react';
|
||||
import type { AccountFields } from './accounts-data-provider';
|
||||
import type { Asset } from '@vegaprotocol/types';
|
||||
import { CenteredGridCellWrapper } from '@vegaprotocol/datagrid';
|
||||
@@ -75,256 +75,250 @@ export interface AccountTableProps extends AgGridReactProps {
|
||||
pinnedAsset?: PinnedAsset;
|
||||
}
|
||||
|
||||
export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
|
||||
(
|
||||
{
|
||||
onClickAsset,
|
||||
onClickWithdraw,
|
||||
onClickDeposit,
|
||||
onClickBreakdown,
|
||||
onClickTransfer,
|
||||
rowData,
|
||||
isReadOnly,
|
||||
pinnedAsset,
|
||||
...props
|
||||
export const AccountTable = ({
|
||||
onClickAsset,
|
||||
onClickWithdraw,
|
||||
onClickDeposit,
|
||||
onClickBreakdown,
|
||||
onClickTransfer,
|
||||
rowData,
|
||||
isReadOnly,
|
||||
pinnedAsset,
|
||||
...props
|
||||
}: AccountTableProps) => {
|
||||
const pinnedRow = useMemo(() => {
|
||||
if (!pinnedAsset) {
|
||||
return;
|
||||
}
|
||||
const currentPinnedAssetRow = rowData?.find(
|
||||
(row) => row.asset.id === pinnedAsset?.id
|
||||
);
|
||||
if (!currentPinnedAssetRow) {
|
||||
return {
|
||||
asset: pinnedAsset,
|
||||
available: '0',
|
||||
used: '0',
|
||||
total: '0',
|
||||
balance: '0',
|
||||
};
|
||||
}
|
||||
return currentPinnedAssetRow;
|
||||
}, [pinnedAsset, rowData]);
|
||||
|
||||
const { getRowHeight } = props;
|
||||
|
||||
const getPinnedAssetRowHeight = useCallback(
|
||||
(params: RowHeightParams) => {
|
||||
if (
|
||||
params.node.rowPinned &&
|
||||
params.data.asset.id === pinnedAsset?.id &&
|
||||
new BigNumber(params.data.total).isLessThanOrEqualTo(0)
|
||||
) {
|
||||
return 32;
|
||||
}
|
||||
return getRowHeight ? getRowHeight(params) : undefined;
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const pinnedRow = useMemo(() => {
|
||||
if (!pinnedAsset) {
|
||||
return;
|
||||
}
|
||||
const currentPinnedAssetRow = rowData?.find(
|
||||
(row) => row.asset.id === pinnedAsset?.id
|
||||
);
|
||||
if (!currentPinnedAssetRow) {
|
||||
return {
|
||||
asset: pinnedAsset,
|
||||
available: '0',
|
||||
used: '0',
|
||||
total: '0',
|
||||
balance: '0',
|
||||
};
|
||||
}
|
||||
return currentPinnedAssetRow;
|
||||
}, [pinnedAsset, rowData]);
|
||||
[pinnedAsset?.id, getRowHeight]
|
||||
);
|
||||
|
||||
const { getRowHeight } = props;
|
||||
const showDepositButton = pinnedRow?.balance === '0';
|
||||
|
||||
const getPinnedAssetRowHeight = useCallback(
|
||||
(params: RowHeightParams) => {
|
||||
if (
|
||||
params.node.rowPinned &&
|
||||
params.data.asset.id === pinnedAsset?.id &&
|
||||
new BigNumber(params.data.total).isLessThanOrEqualTo(0)
|
||||
) {
|
||||
return 32;
|
||||
}
|
||||
return getRowHeight ? getRowHeight(params) : undefined;
|
||||
const colDefs = useMemo(() => {
|
||||
const defs: ColDef[] = [
|
||||
{
|
||||
headerName: t('Asset'),
|
||||
field: 'asset.symbol',
|
||||
headerTooltip: t(
|
||||
'Asset is the collateral that is deposited into the Vega protocol.'
|
||||
),
|
||||
cellClass: 'underline',
|
||||
onCellClicked: ({ data }) => {
|
||||
if (data) {
|
||||
onClickAsset(data.asset.id);
|
||||
}
|
||||
},
|
||||
},
|
||||
[pinnedAsset?.id, getRowHeight]
|
||||
);
|
||||
|
||||
const showDepositButton = pinnedRow?.balance === '0';
|
||||
|
||||
const colDefs = useMemo(() => {
|
||||
const defs: ColDef[] = [
|
||||
{
|
||||
headerName: t('Asset'),
|
||||
field: 'asset.symbol',
|
||||
headerTooltip: t(
|
||||
'Asset is the collateral that is deposited into the Vega protocol.'
|
||||
),
|
||||
cellClass: 'underline',
|
||||
onCellClicked: ({ data }) => {
|
||||
if (data) {
|
||||
onClickAsset(data.asset.id);
|
||||
}
|
||||
},
|
||||
{
|
||||
headerName: t('Used'),
|
||||
type: 'rightAligned',
|
||||
field: 'used',
|
||||
headerTooltip: t(
|
||||
'Currently allocated to a market as margin or bond. Check the breakdown for details.'
|
||||
),
|
||||
tooltipValueGetter: ({ value, data }) => {
|
||||
if (!value || !data) return null;
|
||||
return addDecimalsFormatNumber(value, data.asset.decimals);
|
||||
},
|
||||
{
|
||||
headerName: t('Used'),
|
||||
type: 'rightAligned',
|
||||
field: 'used',
|
||||
headerTooltip: t(
|
||||
'Currently allocated to a market as margin or bond. Check the breakdown for details.'
|
||||
),
|
||||
tooltipValueGetter: ({ value, data }) => {
|
||||
if (!value || !data) return null;
|
||||
return addDecimalsFormatNumber(value, data.asset.decimals);
|
||||
},
|
||||
onCellClicked: ({ data }) => {
|
||||
if (!data || !onClickBreakdown) return;
|
||||
onClickBreakdown(data.asset.id);
|
||||
},
|
||||
cellRenderer: ({
|
||||
data,
|
||||
onCellClicked: ({ data }) => {
|
||||
if (!data || !onClickBreakdown) return;
|
||||
onClickBreakdown(data.asset.id);
|
||||
},
|
||||
cellRenderer: ({
|
||||
data,
|
||||
value,
|
||||
}: VegaICellRendererParams<AccountFields, 'used'>) => {
|
||||
if (!value || !data) return '-';
|
||||
const percentageUsed = percentageValue(value, data.total);
|
||||
const valueFormatted = addDecimalsFormatNumberQuantum(
|
||||
value,
|
||||
}: VegaICellRendererParams<AccountFields, 'used'>) => {
|
||||
if (!value || !data) return '-';
|
||||
const percentageUsed = percentageValue(value, data.total);
|
||||
const valueFormatted = addDecimalsFormatNumberQuantum(
|
||||
value,
|
||||
data.asset.decimals,
|
||||
data.asset.quantum
|
||||
);
|
||||
data.asset.decimals,
|
||||
data.asset.quantum
|
||||
);
|
||||
|
||||
return data.breakdown ? (
|
||||
<>
|
||||
<span className="underline">{valueFormatted}</span>
|
||||
<span
|
||||
className={classNames(
|
||||
colorClass(percentageUsed),
|
||||
'ml-1 inline-block w-14'
|
||||
)}
|
||||
return data.breakdown ? (
|
||||
<>
|
||||
<span className="underline">{valueFormatted}</span>
|
||||
<span
|
||||
className={classNames(
|
||||
colorClass(percentageUsed),
|
||||
'ml-1 inline-block w-14'
|
||||
)}
|
||||
>
|
||||
{percentageUsed.toFixed(2)}%
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span className="underline">{valueFormatted}</span>
|
||||
<span className="ml-2 inline-block w-14 text-muted">
|
||||
{t('0.00%')}
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
headerName: t('Available'),
|
||||
field: 'available',
|
||||
type: 'rightAligned',
|
||||
headerTooltip: t(
|
||||
'Deposited on the network, but not allocated to a market. Free to use for placing orders or providing liquidity.'
|
||||
),
|
||||
tooltipValueGetter: ({ value, data }) => {
|
||||
if (!value || !data) return null;
|
||||
return addDecimalsFormatNumber(value, data.asset.decimals);
|
||||
},
|
||||
cellClass: ({ data }) => {
|
||||
const percentageUsed = percentageValue(data?.used, data?.total);
|
||||
return colorClass(percentageUsed, true);
|
||||
},
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<AccountFields, 'available'>) => {
|
||||
if (!value || !data) return '-';
|
||||
return addDecimalsFormatNumberQuantum(
|
||||
value,
|
||||
data.asset.decimals,
|
||||
data.asset.quantum
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
headerName: t('Total'),
|
||||
type: 'rightAligned',
|
||||
field: 'total',
|
||||
headerTooltip: t(
|
||||
'The total amount of each asset on this key. Includes used and available collateral.'
|
||||
),
|
||||
tooltipValueGetter: ({ value, data }) => {
|
||||
if (!value || !data) return null;
|
||||
return addDecimalsFormatNumber(value, data.asset.decimals);
|
||||
},
|
||||
valueFormatter: ({
|
||||
data,
|
||||
value,
|
||||
}: VegaValueFormatterParams<AccountFields, 'total'>) => {
|
||||
if (!data || !value) return '-';
|
||||
return addDecimalsFormatNumberQuantum(
|
||||
value,
|
||||
data.asset.decimals,
|
||||
data.asset.quantum
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
colId: 'accounts-actions',
|
||||
field: 'asset.id',
|
||||
...COL_DEFS.actions,
|
||||
minWidth: showDepositButton ? 130 : COL_DEFS.actions.minWidth,
|
||||
maxWidth: showDepositButton ? 130 : COL_DEFS.actions.maxWidth,
|
||||
cellRenderer: ({
|
||||
value: assetId,
|
||||
node,
|
||||
}: VegaICellRendererParams<AccountFields, 'asset.id'>) => {
|
||||
if (!assetId) return null;
|
||||
if (node.rowPinned && node.data?.total === '0') {
|
||||
return (
|
||||
<CenteredGridCellWrapper className="h-[30px] justify-end py-1">
|
||||
<Button
|
||||
size="xs"
|
||||
variant="primary"
|
||||
data-testid="deposit"
|
||||
onClick={() => {
|
||||
onClickDeposit && onClickDeposit(assetId);
|
||||
}}
|
||||
>
|
||||
{percentageUsed.toFixed(2)}%
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span className="underline">{valueFormatted}</span>
|
||||
<span className="ml-2 inline-block w-14 text-muted">
|
||||
{t('0.00%')}
|
||||
</span>
|
||||
</>
|
||||
<VegaIcon name={VegaIconNames.DEPOSIT} /> {t('Deposit')}
|
||||
</Button>
|
||||
</CenteredGridCellWrapper>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
headerName: t('Available'),
|
||||
field: 'available',
|
||||
type: 'rightAligned',
|
||||
headerTooltip: t(
|
||||
'Deposited on the network, but not allocated to a market. Free to use for placing orders or providing liquidity.'
|
||||
),
|
||||
tooltipValueGetter: ({ value, data }) => {
|
||||
if (!value || !data) return null;
|
||||
return addDecimalsFormatNumber(value, data.asset.decimals);
|
||||
},
|
||||
cellClass: ({ data }) => {
|
||||
const percentageUsed = percentageValue(data?.used, data?.total);
|
||||
return colorClass(percentageUsed, true);
|
||||
},
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<AccountFields, 'available'>) => {
|
||||
if (!value || !data) return '-';
|
||||
return addDecimalsFormatNumberQuantum(
|
||||
value,
|
||||
data.asset.decimals,
|
||||
data.asset.quantum
|
||||
);
|
||||
},
|
||||
}
|
||||
return isReadOnly ? null : (
|
||||
<AccountsActionsDropdown
|
||||
assetId={assetId}
|
||||
assetContractAddress={
|
||||
node.data?.asset.source?.__typename === 'ERC20'
|
||||
? node.data.asset.source.contractAddress
|
||||
: undefined
|
||||
}
|
||||
onClickDeposit={() => {
|
||||
onClickDeposit && onClickDeposit(assetId);
|
||||
}}
|
||||
onClickWithdraw={() => {
|
||||
onClickWithdraw && onClickWithdraw(assetId);
|
||||
}}
|
||||
onClickBreakdown={() => {
|
||||
onClickBreakdown && onClickBreakdown(assetId);
|
||||
}}
|
||||
onClickTransfer={() => {
|
||||
onClickTransfer && onClickTransfer(assetId);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
return defs;
|
||||
}, [
|
||||
onClickAsset,
|
||||
onClickBreakdown,
|
||||
onClickDeposit,
|
||||
onClickWithdraw,
|
||||
onClickTransfer,
|
||||
isReadOnly,
|
||||
showDepositButton,
|
||||
]);
|
||||
|
||||
{
|
||||
headerName: t('Total'),
|
||||
type: 'rightAligned',
|
||||
field: 'total',
|
||||
headerTooltip: t(
|
||||
'The total amount of each asset on this key. Includes used and available collateral.'
|
||||
),
|
||||
tooltipValueGetter: ({ value, data }) => {
|
||||
if (!value || !data) return null;
|
||||
return addDecimalsFormatNumber(value, data.asset.decimals);
|
||||
},
|
||||
valueFormatter: ({
|
||||
data,
|
||||
value,
|
||||
}: VegaValueFormatterParams<AccountFields, 'total'>) => {
|
||||
if (!data || !value) return '-';
|
||||
return addDecimalsFormatNumberQuantum(
|
||||
value,
|
||||
data.asset.decimals,
|
||||
data.asset.quantum
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
colId: 'accounts-actions',
|
||||
field: 'asset.id',
|
||||
...COL_DEFS.actions,
|
||||
minWidth: showDepositButton ? 130 : COL_DEFS.actions.minWidth,
|
||||
maxWidth: showDepositButton ? 130 : COL_DEFS.actions.maxWidth,
|
||||
cellRenderer: ({
|
||||
value: assetId,
|
||||
node,
|
||||
}: VegaICellRendererParams<AccountFields, 'asset.id'>) => {
|
||||
if (!assetId) return null;
|
||||
if (node.rowPinned && node.data?.total === '0') {
|
||||
return (
|
||||
<CenteredGridCellWrapper className="h-[30px] justify-end py-1">
|
||||
<Button
|
||||
size="xs"
|
||||
variant="primary"
|
||||
data-testid="deposit"
|
||||
onClick={() => {
|
||||
onClickDeposit && onClickDeposit(assetId);
|
||||
}}
|
||||
>
|
||||
<VegaIcon name={VegaIconNames.DEPOSIT} /> {t('Deposit')}
|
||||
</Button>
|
||||
</CenteredGridCellWrapper>
|
||||
);
|
||||
}
|
||||
return isReadOnly ? null : (
|
||||
<AccountsActionsDropdown
|
||||
assetId={assetId}
|
||||
assetContractAddress={
|
||||
node.data?.asset.source?.__typename === 'ERC20'
|
||||
? node.data.asset.source.contractAddress
|
||||
: undefined
|
||||
}
|
||||
onClickDeposit={() => {
|
||||
onClickDeposit && onClickDeposit(assetId);
|
||||
}}
|
||||
onClickWithdraw={() => {
|
||||
onClickWithdraw && onClickWithdraw(assetId);
|
||||
}}
|
||||
onClickBreakdown={() => {
|
||||
onClickBreakdown && onClickBreakdown(assetId);
|
||||
}}
|
||||
onClickTransfer={() => {
|
||||
onClickTransfer && onClickTransfer(assetId);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
return defs;
|
||||
}, [
|
||||
onClickAsset,
|
||||
onClickBreakdown,
|
||||
onClickDeposit,
|
||||
onClickWithdraw,
|
||||
onClickTransfer,
|
||||
isReadOnly,
|
||||
showDepositButton,
|
||||
]);
|
||||
const data = rowData?.filter((data) => data.asset.id !== pinnedAsset?.id);
|
||||
|
||||
const data = rowData?.filter((data) => data.asset.id !== pinnedAsset?.id);
|
||||
|
||||
return (
|
||||
<AgGrid
|
||||
{...props}
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
getRowId={({ data }: { data: AccountFields }) => data.asset.id}
|
||||
ref={ref}
|
||||
tooltipShowDelay={500}
|
||||
rowData={data}
|
||||
defaultColDef={{
|
||||
resizable: true,
|
||||
tooltipComponent: TooltipCellComponent,
|
||||
sortable: true,
|
||||
comparator: accountValuesComparator,
|
||||
}}
|
||||
columnDefs={colDefs}
|
||||
getRowHeight={getPinnedAssetRowHeight}
|
||||
pinnedTopRowData={pinnedRow ? [pinnedRow] : undefined}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
return (
|
||||
<AgGrid
|
||||
{...props}
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
getRowId={({ data }: { data: AccountFields }) => data.asset.id}
|
||||
tooltipShowDelay={500}
|
||||
rowData={data}
|
||||
defaultColDef={{
|
||||
resizable: true,
|
||||
tooltipComponent: TooltipCellComponent,
|
||||
sortable: true,
|
||||
comparator: accountValuesComparator,
|
||||
}}
|
||||
columnDefs={colDefs}
|
||||
getRowHeight={getPinnedAssetRowHeight}
|
||||
pinnedTopRowData={pinnedRow ? [pinnedRow] : undefined}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4,9 +4,25 @@ export default {
|
||||
preset: '../../jest.preset.js',
|
||||
transform: {
|
||||
'^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nx/react/plugins/jest',
|
||||
'^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nx/react/babel'] }],
|
||||
'^.+\\.[tj]sx?$': [
|
||||
'babel-jest',
|
||||
{
|
||||
presets: ['@nx/react/babel'],
|
||||
// required for pennant to work in jest, due to having untranspiled exports
|
||||
plugins: [
|
||||
[
|
||||
'@babel/plugin-proposal-private-methods',
|
||||
{
|
||||
loose: true,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
|
||||
coverageDirectory: '../../coverage/libs/candles-chart',
|
||||
setupFilesAfterEnv: ['./src/setup-tests.ts'],
|
||||
// dont ignore pennant from transpilation
|
||||
transformIgnorePatterns: ['<rootDir>/node_modules/pennant'],
|
||||
};
|
||||
|
||||
@@ -12,6 +12,7 @@ import { addUpdateCapsuleMultiSig } from './lib/commands/add-validators-to-multi
|
||||
import {
|
||||
addVegaWalletConnect,
|
||||
addSetVegaWallet,
|
||||
addSetOnBoardingViewed,
|
||||
} from './lib/commands/vega-wallet-connect';
|
||||
import { addMockTransactionResponse } from './lib/commands/mock-transaction-response';
|
||||
import { addCreateMarket } from './lib/commands/create-market';
|
||||
@@ -38,6 +39,7 @@ addGetNetworkParameters();
|
||||
addUpdateCapsuleMultiSig();
|
||||
addVegaWalletConnect();
|
||||
addSetVegaWallet();
|
||||
addSetOnBoardingViewed();
|
||||
addMockTransactionResponse();
|
||||
addCreateMarket();
|
||||
addConnectPublicKey();
|
||||
|
||||
@@ -14,6 +14,10 @@ declare global {
|
||||
interface Chainable<Subject> {
|
||||
setVegaWallet(): void;
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
interface Chainable<Subject> {
|
||||
setOnBoardingViewed(): void;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +64,7 @@ export function addVegaWalletConnect() {
|
||||
export function addSetVegaWallet() {
|
||||
Cypress.Commands.add('setVegaWallet', () => {
|
||||
cy.window().then((win) => {
|
||||
win.localStorage.setItem('vega_risk_accepted', 'true');
|
||||
win.localStorage.setItem('vega_onboarding_viewed', 'true');
|
||||
win.localStorage.setItem(
|
||||
'vega_wallet_config',
|
||||
JSON.stringify({
|
||||
@@ -72,3 +76,11 @@ export function addSetVegaWallet() {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function addSetOnBoardingViewed() {
|
||||
Cypress.Commands.add('setOnBoardingViewed', () => {
|
||||
cy.window().then((win) => {
|
||||
win.localStorage.setItem('vega_onboarding_viewed', 'true');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -21,5 +21,4 @@ export * from './lib/type-helpers';
|
||||
|
||||
export * from './lib/cells/grid-progress-bar';
|
||||
|
||||
export * from './lib/ag-grid-update';
|
||||
export * from './lib/use-datagrid-events';
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
import type { MutableRefObject, RefObject } from 'react';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
|
||||
type AnyArray = Array<any> | null; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
|
||||
export const isXOrWasEmpty = (prev?: AnyArray, curr?: AnyArray) =>
|
||||
Boolean(Number(!!prev?.length) ^ Number(!!curr?.length));
|
||||
|
||||
export const updateGridData = (
|
||||
dataRef: MutableRefObject<AnyArray>,
|
||||
data: AnyArray,
|
||||
gridRef: RefObject<AgGridReact>
|
||||
) => {
|
||||
const rerender = isXOrWasEmpty(dataRef.current, data);
|
||||
dataRef.current = data;
|
||||
if (gridRef.current?.api?.getModel().getType() === 'infinite') {
|
||||
gridRef.current?.api?.refreshInfiniteCache();
|
||||
}
|
||||
return !rerender;
|
||||
};
|
||||
@@ -158,16 +158,13 @@ describe('StopOrder', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('shows no wallet warning and do not submit if no wallet connected', async () => {
|
||||
it('does not submit if no wallet connected', async () => {
|
||||
render(generateJsx(null));
|
||||
await userEvent.type(screen.getByTestId(sizeInput), '1');
|
||||
await userEvent.type(screen.getByTestId(priceInput), '1');
|
||||
await userEvent.type(screen.getByTestId(triggerPriceInput), '1');
|
||||
await userEvent.click(screen.getByTestId(submitButton));
|
||||
expect(submit).not.toBeCalled();
|
||||
expect(
|
||||
screen.getByTestId('deal-ticket-connect-wallet')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calls submit if form is valid', async () => {
|
||||
|
||||
@@ -488,19 +488,7 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||
</InputError>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex gap-2 pb-2 justify-end">
|
||||
<Checkbox
|
||||
name="reduce-only"
|
||||
checked={true}
|
||||
disabled={true}
|
||||
label={
|
||||
<Tooltip description={<span>{t(REDUCE_ONLY_TOOLTIP)}</span>}>
|
||||
<span className="text-xs">{t('Reduce only')}</span>
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-2">
|
||||
<div className="flex gap-2 pb-2 justify-between">
|
||||
<Controller
|
||||
name="expire"
|
||||
control={control}
|
||||
@@ -511,11 +499,21 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||
onCheckedChange={onCheckedChange}
|
||||
checked={value}
|
||||
name="expire"
|
||||
label={'Expire'}
|
||||
label={<span className="text-xs">{t('Expire')}</span>}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Checkbox
|
||||
name="reduce-only"
|
||||
checked={true}
|
||||
disabled={true}
|
||||
label={
|
||||
<Tooltip description={<span>{t(REDUCE_ONLY_TOOLTIP)}</span>}>
|
||||
<span className="text-xs">{t('Reduce only')}</span>
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
{expire && (
|
||||
<>
|
||||
@@ -570,7 +568,7 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<NoWalletWarning pubKey={pubKey} isReadOnly={isReadOnly} asset={asset} />
|
||||
<NoWalletWarning isReadOnly={isReadOnly} />
|
||||
<DealTicketButton side={side} label={t('Submit Stop Order')} />
|
||||
<DealTicketFeeDetails
|
||||
order={{
|
||||
|
||||
@@ -13,14 +13,9 @@ import { SideSelector } from './side-selector';
|
||||
import { TimeInForceSelector } from './time-in-force-selector';
|
||||
import { TypeSelector } from './type-selector';
|
||||
import type { OrderSubmission } from '@vegaprotocol/wallet';
|
||||
import {
|
||||
normalizeOrderSubmission,
|
||||
useVegaWallet,
|
||||
useVegaWalletDialogStore,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import { normalizeOrderSubmission, useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import {
|
||||
Checkbox,
|
||||
ExternalLink,
|
||||
InputError,
|
||||
Intent,
|
||||
Notification,
|
||||
@@ -637,13 +632,7 @@ interface SummaryMessageProps {
|
||||
|
||||
export const NoWalletWarning = ({
|
||||
isReadOnly,
|
||||
pubKey,
|
||||
asset,
|
||||
}: Pick<SummaryMessageProps, 'isReadOnly' | 'pubKey' | 'asset'>) => {
|
||||
const assetSymbol = asset.symbol;
|
||||
const openVegaWalletDialog = useVegaWalletDialogStore(
|
||||
(store) => store.openVegaWalletDialog
|
||||
);
|
||||
}: Pick<SummaryMessageProps, 'isReadOnly'>) => {
|
||||
if (isReadOnly) {
|
||||
return (
|
||||
<div className="mb-2">
|
||||
@@ -655,31 +644,6 @@ export const NoWalletWarning = ({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (!pubKey) {
|
||||
return (
|
||||
<div className="mb-2">
|
||||
<Notification
|
||||
testId={'deal-ticket-connect-wallet'}
|
||||
intent={Intent.Warning}
|
||||
message={
|
||||
<p className="text-sm pb-2">
|
||||
You need a{' '}
|
||||
<ExternalLink href="https://vega.xyz/wallet">
|
||||
Vega wallet
|
||||
</ExternalLink>{' '}
|
||||
with {assetSymbol} to start trading in this market.
|
||||
</p>
|
||||
}
|
||||
buttonProps={{
|
||||
text: t('Connect wallet'),
|
||||
action: openVegaWalletDialog,
|
||||
dataTestId: 'order-connect-wallet',
|
||||
size: 'small',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -698,13 +662,7 @@ const SummaryMessage = memo(
|
||||
// Specific error UI for if balance is so we can
|
||||
// render a deposit dialog
|
||||
if (isReadOnly || !pubKey) {
|
||||
return (
|
||||
<NoWalletWarning
|
||||
isReadOnly={isReadOnly}
|
||||
asset={asset}
|
||||
pubKey={pubKey}
|
||||
/>
|
||||
);
|
||||
return <NoWalletWarning isReadOnly={isReadOnly} />;
|
||||
}
|
||||
|
||||
if (errorMessage === SummaryValidationType.NoCollateral) {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import {
|
||||
Icon,
|
||||
InputError,
|
||||
SimpleGrid,
|
||||
Tooltip,
|
||||
@@ -10,6 +9,8 @@ import {
|
||||
TradingDropdownRadioGroup,
|
||||
TradingDropdownRadioItem,
|
||||
TradingDropdownTrigger,
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import type { Market, StaticMarketData } from '@vegaprotocol/markets';
|
||||
@@ -81,11 +82,11 @@ export const TypeToggle = ({
|
||||
}
|
||||
)}
|
||||
>
|
||||
<button>
|
||||
<button className="flex gap-1">
|
||||
<span className="text-ellipsis whitespace-nowrap shrink overflow-hidden">
|
||||
{t(selectedOption ? selectedOption.label : 'Stop')}
|
||||
</span>
|
||||
<Icon name="chevron-down" className="ml-1" />
|
||||
<VegaIcon name={VegaIconNames.CHEVRON_DOWN} size={14} />
|
||||
</button>
|
||||
</TradingDropdownTrigger>
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { forwardRef, useMemo } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import {
|
||||
addDecimalsFormatNumber,
|
||||
getDateTimeFormat,
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
isNumeric,
|
||||
} from '@vegaprotocol/utils';
|
||||
import type { ColDef } from 'ag-grid-community';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
|
||||
import type {
|
||||
VegaICellRendererParams,
|
||||
@@ -17,10 +16,9 @@ import type { DepositFieldsFragment } from './__generated__/Deposit';
|
||||
import { EtherscanLink } from '@vegaprotocol/environment';
|
||||
import { DepositStatusMapping } from '@vegaprotocol/types';
|
||||
|
||||
export const DepositsTable = forwardRef<
|
||||
AgGridReact,
|
||||
TypedDataAgGrid<DepositFieldsFragment>
|
||||
>((props, ref) => {
|
||||
export const DepositsTable = (
|
||||
props: TypedDataAgGrid<DepositFieldsFragment>
|
||||
) => {
|
||||
const columnDefs = useMemo<ColDef[]>(
|
||||
() => [
|
||||
{ headerName: 'Asset', field: 'asset.symbol' },
|
||||
@@ -79,11 +77,10 @@ export const DepositsTable = forwardRef<
|
||||
);
|
||||
return (
|
||||
<AgGrid
|
||||
ref={ref}
|
||||
defaultColDef={{ flex: 1 }}
|
||||
columnDefs={columnDefs}
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@ export const NodeSwitcherDialog = ({
|
||||
setOpen: (x: boolean) => void;
|
||||
}) => {
|
||||
return (
|
||||
<Dialog open={open} onChange={setOpen} size="medium">
|
||||
<Dialog open={open} onChange={setOpen} size="large">
|
||||
<NodeSwitcher closeDialog={() => setOpen(false)} />
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import type * as Schema from '@vegaprotocol/types';
|
||||
import type { FilterChangedEvent } from 'ag-grid-community';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import { useCallback, useRef, useState, useMemo } from 'react';
|
||||
import { useCallback, useState, useMemo } from 'react';
|
||||
import { subDays, formatRFC3339 } from 'date-fns';
|
||||
import { ledgerEntriesProvider } from './ledger-entries-data-provider';
|
||||
import type { LedgerEntriesQueryVariables } from './__generated__/LedgerEntries';
|
||||
@@ -32,7 +31,6 @@ export const LedgerManager = ({
|
||||
partyId: string;
|
||||
gridProps: ReturnType<typeof useDataGridEvents>;
|
||||
}) => {
|
||||
const gridRef = useRef<AgGridReact | null>(null);
|
||||
const [filter, setFilter] = useState<Filter>(defaultFilter);
|
||||
|
||||
const variables = useMemo<LedgerEntriesQueryVariables>(
|
||||
@@ -64,7 +62,6 @@ export const LedgerManager = ({
|
||||
return (
|
||||
<div className="h-full relative">
|
||||
<LedgerTable
|
||||
ref={gridRef}
|
||||
rowData={data}
|
||||
overlayNoRowsTemplate={error ? error.message : t('No entries')}
|
||||
{...gridProps}
|
||||
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
DateRangeFilter,
|
||||
SetFilter,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import type * as Types from '@vegaprotocol/types';
|
||||
import type { ColDef } from 'ag-grid-community';
|
||||
import {
|
||||
@@ -23,7 +22,7 @@ import {
|
||||
TransferTypeMapping,
|
||||
} from '@vegaprotocol/types';
|
||||
import type { LedgerEntry } from './ledger-entries-data-provider';
|
||||
import { forwardRef, useMemo } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { formatRFC3339, subDays } from 'date-fns';
|
||||
|
||||
export const TransferTooltipCellComponent = ({
|
||||
@@ -45,162 +44,159 @@ const dateRangeFilterParams = {
|
||||
};
|
||||
type LedgerEntryProps = TypedDataAgGrid<LedgerEntry>;
|
||||
|
||||
export const LedgerTable = forwardRef<AgGridReact, LedgerEntryProps>(
|
||||
(props, ref) => {
|
||||
const columnDefs = useMemo<ColDef[]>(
|
||||
() => [
|
||||
{
|
||||
headerName: t('Sender'),
|
||||
field: 'fromAccountPartyId',
|
||||
cellRenderer: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'fromAccountPartyId'>) =>
|
||||
truncateByChars(value || ''),
|
||||
export const LedgerTable = (props: LedgerEntryProps) => {
|
||||
const columnDefs = useMemo<ColDef[]>(
|
||||
() => [
|
||||
{
|
||||
headerName: t('Sender'),
|
||||
field: 'fromAccountPartyId',
|
||||
cellRenderer: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'fromAccountPartyId'>) =>
|
||||
truncateByChars(value || ''),
|
||||
},
|
||||
{
|
||||
headerName: t('Account type'),
|
||||
filter: SetFilter,
|
||||
filterParams: {
|
||||
set: AccountTypeMapping,
|
||||
},
|
||||
{
|
||||
headerName: t('Account type'),
|
||||
filter: SetFilter,
|
||||
filterParams: {
|
||||
set: AccountTypeMapping,
|
||||
},
|
||||
field: 'fromAccountType',
|
||||
cellRenderer: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'fromAccountType'>) =>
|
||||
value ? AccountTypeMapping[value] : '-',
|
||||
field: 'fromAccountType',
|
||||
cellRenderer: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'fromAccountType'>) =>
|
||||
value ? AccountTypeMapping[value] : '-',
|
||||
},
|
||||
{
|
||||
headerName: t('Market'),
|
||||
field: 'marketSender.tradableInstrument.instrument.code',
|
||||
cellRenderer: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<
|
||||
LedgerEntry,
|
||||
'marketSender.tradableInstrument.instrument.code'
|
||||
>) => value || '-',
|
||||
},
|
||||
{
|
||||
headerName: t('Receiver'),
|
||||
field: 'toAccountPartyId',
|
||||
cellRenderer: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'toAccountPartyId'>) =>
|
||||
truncateByChars(value || ''),
|
||||
},
|
||||
{
|
||||
headerName: t('Account type'),
|
||||
filter: SetFilter,
|
||||
filterParams: {
|
||||
set: AccountTypeMapping,
|
||||
},
|
||||
{
|
||||
headerName: t('Market'),
|
||||
field: 'marketSender.tradableInstrument.instrument.code',
|
||||
cellRenderer: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<
|
||||
LedgerEntry,
|
||||
'marketSender.tradableInstrument.instrument.code'
|
||||
>) => value || '-',
|
||||
field: 'toAccountType',
|
||||
cellRenderer: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'toAccountType'>) =>
|
||||
value ? AccountTypeMapping[value] : '-',
|
||||
},
|
||||
{
|
||||
headerName: t('Market'),
|
||||
field: 'marketReceiver.tradableInstrument.instrument.code',
|
||||
cellRenderer: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<
|
||||
LedgerEntry,
|
||||
'marketReceiver.tradableInstrument.instrument.code'
|
||||
>) => value || '-',
|
||||
},
|
||||
{
|
||||
headerName: t('Transfer type'),
|
||||
field: 'transferType',
|
||||
tooltipField: 'transferType',
|
||||
filter: SetFilter,
|
||||
filterParams: {
|
||||
set: TransferTypeMapping,
|
||||
},
|
||||
{
|
||||
headerName: t('Receiver'),
|
||||
field: 'toAccountPartyId',
|
||||
cellRenderer: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'toAccountPartyId'>) =>
|
||||
truncateByChars(value || ''),
|
||||
valueFormatter: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'transferType'>) =>
|
||||
value ? TransferTypeMapping[value] : '',
|
||||
},
|
||||
{
|
||||
headerName: t('Quantity'),
|
||||
field: 'quantity',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'quantity'>) => {
|
||||
const assetDecimalPlaces = data?.asset?.decimals || 0;
|
||||
return value
|
||||
? addDecimalsFormatNumber(value, assetDecimalPlaces)
|
||||
: '';
|
||||
},
|
||||
{
|
||||
headerName: t('Account type'),
|
||||
filter: SetFilter,
|
||||
filterParams: {
|
||||
set: AccountTypeMapping,
|
||||
},
|
||||
field: 'toAccountType',
|
||||
cellRenderer: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'toAccountType'>) =>
|
||||
value ? AccountTypeMapping[value] : '-',
|
||||
},
|
||||
{
|
||||
headerName: t('Asset'),
|
||||
field: 'assetId',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'asset'>) =>
|
||||
data?.asset?.symbol || '',
|
||||
},
|
||||
{
|
||||
headerName: t('Sender account balance'),
|
||||
field: 'fromAccountBalance',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'fromAccountBalance'>) => {
|
||||
const assetDecimalPlaces = data?.asset?.decimals || 0;
|
||||
return value
|
||||
? addDecimalsFormatNumber(value, assetDecimalPlaces)
|
||||
: '';
|
||||
},
|
||||
{
|
||||
headerName: t('Market'),
|
||||
field: 'marketReceiver.tradableInstrument.instrument.code',
|
||||
cellRenderer: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<
|
||||
LedgerEntry,
|
||||
'marketReceiver.tradableInstrument.instrument.code'
|
||||
>) => value || '-',
|
||||
},
|
||||
{
|
||||
headerName: t('Receiver account balance'),
|
||||
field: 'toAccountBalance',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'toAccountBalance'>) => {
|
||||
const assetDecimalPlaces = data?.asset?.decimals || 0;
|
||||
return value
|
||||
? addDecimalsFormatNumber(value, assetDecimalPlaces)
|
||||
: '';
|
||||
},
|
||||
{
|
||||
headerName: t('Transfer type'),
|
||||
field: 'transferType',
|
||||
tooltipField: 'transferType',
|
||||
filter: SetFilter,
|
||||
filterParams: {
|
||||
set: TransferTypeMapping,
|
||||
},
|
||||
valueFormatter: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'transferType'>) =>
|
||||
value ? TransferTypeMapping[value] : '',
|
||||
},
|
||||
{
|
||||
headerName: t('Vega time'),
|
||||
field: 'vegaTime',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'vegaTime'>) =>
|
||||
value ? getDateTimeFormat().format(fromNanoSeconds(value)) : '-',
|
||||
filterParams: dateRangeFilterParams,
|
||||
filter: DateRangeFilter,
|
||||
flex: 1,
|
||||
},
|
||||
],
|
||||
[]
|
||||
);
|
||||
return (
|
||||
<AgGrid
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
tooltipShowDelay={500}
|
||||
defaultColDef={{
|
||||
resizable: true,
|
||||
sortable: true,
|
||||
tooltipComponent: TransferTooltipCellComponent,
|
||||
filterParams: {
|
||||
...dateRangeFilterParams,
|
||||
buttons: ['reset'],
|
||||
},
|
||||
{
|
||||
headerName: t('Quantity'),
|
||||
field: 'quantity',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'quantity'>) => {
|
||||
const assetDecimalPlaces = data?.asset?.decimals || 0;
|
||||
return value
|
||||
? addDecimalsFormatNumber(value, assetDecimalPlaces)
|
||||
: '';
|
||||
},
|
||||
},
|
||||
{
|
||||
headerName: t('Asset'),
|
||||
field: 'assetId',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'asset'>) =>
|
||||
data?.asset?.symbol || '',
|
||||
},
|
||||
{
|
||||
headerName: t('Sender account balance'),
|
||||
field: 'fromAccountBalance',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'fromAccountBalance'>) => {
|
||||
const assetDecimalPlaces = data?.asset?.decimals || 0;
|
||||
return value
|
||||
? addDecimalsFormatNumber(value, assetDecimalPlaces)
|
||||
: '';
|
||||
},
|
||||
},
|
||||
{
|
||||
headerName: t('Receiver account balance'),
|
||||
field: 'toAccountBalance',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'toAccountBalance'>) => {
|
||||
const assetDecimalPlaces = data?.asset?.decimals || 0;
|
||||
return value
|
||||
? addDecimalsFormatNumber(value, assetDecimalPlaces)
|
||||
: '';
|
||||
},
|
||||
},
|
||||
{
|
||||
headerName: t('Vega time'),
|
||||
field: 'vegaTime',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<LedgerEntry, 'vegaTime'>) =>
|
||||
value ? getDateTimeFormat().format(fromNanoSeconds(value)) : '-',
|
||||
filterParams: dateRangeFilterParams,
|
||||
filter: DateRangeFilter,
|
||||
flex: 1,
|
||||
},
|
||||
],
|
||||
[]
|
||||
);
|
||||
return (
|
||||
<AgGrid
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
ref={ref}
|
||||
tooltipShowDelay={500}
|
||||
defaultColDef={{
|
||||
resizable: true,
|
||||
sortable: true,
|
||||
tooltipComponent: TransferTooltipCellComponent,
|
||||
filterParams: {
|
||||
...dateRangeFilterParams,
|
||||
buttons: ['reset'],
|
||||
},
|
||||
}}
|
||||
columnDefs={columnDefs}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
}}
|
||||
columnDefs={columnDefs}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4,9 +4,25 @@ export default {
|
||||
preset: '../../jest.preset.js',
|
||||
transform: {
|
||||
'^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nx/react/plugins/jest',
|
||||
'^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nx/react/babel'] }],
|
||||
'^.+\\.[tj]sx?$': [
|
||||
'babel-jest',
|
||||
{
|
||||
presets: ['@nx/react/babel'],
|
||||
// required for pennant to work in jest, due to having untranspiled exports
|
||||
plugins: [
|
||||
[
|
||||
'@babel/plugin-proposal-private-methods',
|
||||
{
|
||||
loose: true,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
|
||||
coverageDirectory: '../../coverage/libs/market-depth',
|
||||
setupFilesAfterEnv: ['./src/setup-tests.ts'],
|
||||
// dont ignore pennant from transpilation
|
||||
transformIgnorePatterns: ['<rootDir>/node_modules/pennant'],
|
||||
};
|
||||
|
||||
@@ -34,5 +34,8 @@ query SuccessorMarket($marketId: ID!) {
|
||||
code
|
||||
}
|
||||
}
|
||||
proposal {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
-1
@@ -27,7 +27,7 @@ export type SuccessorMarketQueryVariables = Types.Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type SuccessorMarketQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, state: Types.MarketState, tradingMode: Types.MarketTradingMode, positionDecimalPlaces: number, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, code: string } } } | null };
|
||||
export type SuccessorMarketQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, state: Types.MarketState, tradingMode: Types.MarketTradingMode, positionDecimalPlaces: number, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, code: string } }, proposal?: { __typename?: 'Proposal', id?: string | null } | null } | null };
|
||||
|
||||
|
||||
export const SuccessorMarketIdDocument = gql`
|
||||
@@ -153,6 +153,9 @@ export const SuccessorMarketDocument = gql`
|
||||
code
|
||||
}
|
||||
}
|
||||
proposal {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { TokenStaticLinks, useEnvironment } from '@vegaprotocol/environment';
|
||||
import {
|
||||
FLAGS,
|
||||
TokenStaticLinks,
|
||||
useEnvironment,
|
||||
} from '@vegaprotocol/environment';
|
||||
import { removePaginationWrapper } from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
@@ -34,6 +38,7 @@ import {
|
||||
RiskModelInfoPanel,
|
||||
RiskParametersInfoPanel,
|
||||
SettlementAssetInfoPanel,
|
||||
SuccessionLineInfoPanel,
|
||||
} from './market-info-panels';
|
||||
import type { DataSourceDefinition } from '@vegaprotocol/types';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
@@ -291,6 +296,13 @@ export const MarketInfoAccordion = ({
|
||||
</>
|
||||
}
|
||||
/>
|
||||
{FLAGS.SUCCESSOR_MARKETS && (
|
||||
<AccordionItem
|
||||
itemId="succession-line"
|
||||
title={t('Succession line')}
|
||||
content={<SuccessionLineInfoPanel market={market} />}
|
||||
/>
|
||||
)}
|
||||
</Accordion>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import {
|
||||
ConditionOperator,
|
||||
ConditionOperatorMapping,
|
||||
} from '@vegaprotocol/types';
|
||||
import { DataSourceProof } from './market-info-panels';
|
||||
import { DataSourceProof, SuccessionLineInfoPanel } from './market-info-panels';
|
||||
import { MockedProvider } from '@apollo/react-testing';
|
||||
import { SuccessorMarketIdsDocument } from '../../__generated__';
|
||||
|
||||
jest.mock('../../hooks/use-oracle-markets', () => ({
|
||||
useOracleMarkets: () => [],
|
||||
@@ -137,3 +139,93 @@ describe('DataSourceProof', () => {
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('SuccessionLineInfoPanel', () => {
|
||||
const mocks = [
|
||||
{
|
||||
request: {
|
||||
query: SuccessorMarketIdsDocument,
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
__typename: 'Query',
|
||||
marketsConnection: {
|
||||
__typename: 'MarketConnection',
|
||||
edges: [
|
||||
{
|
||||
__typename: 'MarketEdge',
|
||||
node: {
|
||||
__typename: 'Market',
|
||||
id: 'abc',
|
||||
successorMarketID: 'def',
|
||||
parentMarketID: null,
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'MarketEdge',
|
||||
node: {
|
||||
__typename: 'Market',
|
||||
id: 'def',
|
||||
successorMarketID: 'ghi',
|
||||
parentMarketID: 'abc',
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'MarketEdge',
|
||||
node: {
|
||||
__typename: 'Market',
|
||||
id: 'ghi',
|
||||
successorMarketID: null,
|
||||
parentMarketID: 'def',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
it.each([
|
||||
['abc', 1],
|
||||
['def', 2],
|
||||
['ghi', 3],
|
||||
])(
|
||||
'renders succession line for %s (current position %d)',
|
||||
async (id, number) => {
|
||||
render(
|
||||
<MockedProvider mocks={mocks}>
|
||||
<SuccessionLineInfoPanel
|
||||
market={{
|
||||
id,
|
||||
}}
|
||||
/>
|
||||
</MockedProvider>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
const items = screen.getAllByTestId('succession-line-item');
|
||||
expect(items.length).toBe(3);
|
||||
expect(
|
||||
items[0].querySelector(
|
||||
'[data-testid="succession-line-item-market-id"]'
|
||||
)?.textContent
|
||||
).toBe('abc');
|
||||
expect(
|
||||
items[1].querySelector(
|
||||
'[data-testid="succession-line-item-market-id"]'
|
||||
)?.textContent
|
||||
).toBe('def');
|
||||
expect(
|
||||
items[2].querySelector(
|
||||
'[data-testid="succession-line-item-market-id"]'
|
||||
)?.textContent
|
||||
).toBe('ghi');
|
||||
|
||||
expect(
|
||||
items[number - 1].querySelector('[data-testid="icon-bullet"]')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { Fragment, useState } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { AssetDetailsTable, useAssetDataProvider } from '@vegaprotocol/assets';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { marketDataProvider } from '../../market-data-provider';
|
||||
import { totalFeesPercentage } from '../../market-utils';
|
||||
import { ExternalLink, Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
ExternalLink,
|
||||
Splash,
|
||||
Tooltip,
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
addDecimalsFormatNumber,
|
||||
formatNumber,
|
||||
@@ -23,14 +29,26 @@ import BigNumber from 'bignumber.js';
|
||||
import type { DataSourceDefinition, SignerKind } from '@vegaprotocol/types';
|
||||
import { ConditionOperatorMapping } from '@vegaprotocol/types';
|
||||
import { MarketTradingModeMapping } from '@vegaprotocol/types';
|
||||
import { FLAGS, useEnvironment } from '@vegaprotocol/environment';
|
||||
import {
|
||||
DApp,
|
||||
FLAGS,
|
||||
TOKEN_PROPOSAL,
|
||||
useEnvironment,
|
||||
useLinks,
|
||||
} from '@vegaprotocol/environment';
|
||||
import type { Provider } from '../../oracle-schema';
|
||||
import { OracleBasicProfile } from '../../components/oracle-basic-profile';
|
||||
import { useOracleProofs } from '../../hooks';
|
||||
import { OracleDialog } from '../oracle-dialog/oracle-dialog';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { useParentMarketIdQuery } from '../../__generated__';
|
||||
import {
|
||||
useParentMarketIdQuery,
|
||||
useSuccessorMarketIdsQuery,
|
||||
useSuccessorMarketQuery,
|
||||
} from '../../__generated__';
|
||||
import { useSuccessorMarketProposalDetailsQuery } from '@vegaprotocol/proposals';
|
||||
import classNames from 'classnames';
|
||||
import compact from 'lodash/compact';
|
||||
|
||||
type MarketInfoProps = {
|
||||
market: MarketInfo;
|
||||
@@ -191,6 +209,126 @@ export const KeyDetailsInfoPanel = ({ market }: MarketInfoProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
const SuccessionLineItem = ({
|
||||
marketId,
|
||||
isCurrent,
|
||||
}: {
|
||||
marketId: string;
|
||||
isCurrent?: boolean;
|
||||
}) => {
|
||||
const { data } = useSuccessorMarketQuery({
|
||||
variables: {
|
||||
marketId,
|
||||
},
|
||||
});
|
||||
|
||||
const marketData = data?.market;
|
||||
const governanceLink = useLinks(DApp.Token);
|
||||
const proposalLink = marketData?.proposal?.id
|
||||
? governanceLink(TOKEN_PROPOSAL.replace(':id', marketData?.proposal?.id))
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<div
|
||||
data-testid="succession-line-item"
|
||||
className={classNames(
|
||||
'rounded p-2 bg-vega-clight-700 dark:bg-vega-cdark-700',
|
||||
'font-alpha',
|
||||
'flex flex-col '
|
||||
)}
|
||||
>
|
||||
<div className="flex justify-between">
|
||||
<div>
|
||||
{marketData ? (
|
||||
proposalLink ? (
|
||||
<ExternalLink href={proposalLink}>
|
||||
{marketData.tradableInstrument.instrument.code}
|
||||
</ExternalLink>
|
||||
) : (
|
||||
marketData.tradableInstrument.instrument.code
|
||||
)
|
||||
) : (
|
||||
<span className="block w-20 h-4 mb-1 bg-vega-clight-500 dark:bg-vega-cdark-500 animate-pulse"></span>
|
||||
)}
|
||||
</div>
|
||||
{isCurrent && (
|
||||
<Tooltip description={t('This market')}>
|
||||
<div className="text-vega-clight-200 dark:text-vega-cdark-200 cursor-help">
|
||||
<VegaIcon name={VegaIconNames.BULLET} size={16} />
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-xs">
|
||||
{marketData ? (
|
||||
marketData.tradableInstrument.instrument.name
|
||||
) : (
|
||||
<span className="block w-28 h-4 bg-vega-clight-500 dark:bg-vega-cdark-500 animate-pulse"></span>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
data-testid="succession-line-item-market-id"
|
||||
className="text-xs truncate mt-1"
|
||||
>
|
||||
{marketId}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const SuccessionLink = () => (
|
||||
<div className="text-center leading-none" aria-hidden>
|
||||
<VegaIcon name={VegaIconNames.ARROW_DOWN} size={12} />
|
||||
</div>
|
||||
);
|
||||
|
||||
const buildSuccessionLine = (
|
||||
all: {
|
||||
id: string;
|
||||
successorMarketID?: string | null | undefined;
|
||||
parentMarketID?: string | null | undefined;
|
||||
}[],
|
||||
id: string
|
||||
) => {
|
||||
let line = [id];
|
||||
const find = (id: string, dir?: 'up' | 'down') => {
|
||||
const item = all.find((a) => a.id === id);
|
||||
const anc = dir === 'up' && item?.parentMarketID;
|
||||
const des = dir === 'down' && item?.successorMarketID;
|
||||
if (anc) {
|
||||
line = [anc, ...line];
|
||||
find(anc, 'up');
|
||||
}
|
||||
if (des) {
|
||||
line = [...line, des];
|
||||
find(des, 'down');
|
||||
}
|
||||
};
|
||||
find(id, 'up');
|
||||
find(id, 'down');
|
||||
return line;
|
||||
};
|
||||
export const SuccessionLineInfoPanel = ({
|
||||
market,
|
||||
}: {
|
||||
market: Pick<MarketInfo, 'id'>;
|
||||
}) => {
|
||||
const { data } = useSuccessorMarketIdsQuery();
|
||||
const ids = compact(data?.marketsConnection?.edges.map((e) => e.node));
|
||||
const line = buildSuccessionLine(ids, market.id);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
{line.map((id, i) => (
|
||||
<Fragment key={i}>
|
||||
{i > 0 && <SuccessionLink />}
|
||||
<SuccessionLineItem marketId={id} isCurrent={id === market.id} />
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const InstrumentInfoPanel = ({ market }: MarketInfoProps) => (
|
||||
<MarketInfoTable
|
||||
data={{
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import React, { forwardRef } from 'react';
|
||||
import type { TypedDataAgGrid } from '@vegaprotocol/datagrid';
|
||||
import {
|
||||
AgGridLazy as AgGrid,
|
||||
PriceFlashCell,
|
||||
MarketNameCell,
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import type { MarketMaybeWithData } from '../../markets-provider';
|
||||
import { OracleStatus } from './oracle-status';
|
||||
import { useColumnDefs } from './use-column-defs';
|
||||
@@ -43,14 +41,15 @@ const defaultColDef = {
|
||||
filterParams: { buttons: ['reset'] },
|
||||
minWidth: 100,
|
||||
};
|
||||
|
||||
export const MarketListTable = forwardRef<
|
||||
AgGridReact,
|
||||
TypedDataAgGrid<MarketMaybeWithData> & {
|
||||
onMarketClick: (marketId: string, metaKey?: boolean) => void;
|
||||
SuccessorMarketRenderer?: React.FC<{ value: string }>;
|
||||
}
|
||||
>(({ onMarketClick, SuccessorMarketRenderer, ...props }, ref) => {
|
||||
type Props = TypedDataAgGrid<MarketMaybeWithData> & {
|
||||
onMarketClick: (marketId: string, metaKey?: boolean) => void;
|
||||
SuccessorMarketRenderer?: React.FC<{ value: string }>;
|
||||
};
|
||||
export const MarketListTable = ({
|
||||
onMarketClick,
|
||||
SuccessorMarketRenderer,
|
||||
...props
|
||||
}: Props) => {
|
||||
const columnDefs = useColumnDefs({ onMarketClick });
|
||||
const components = {
|
||||
PriceFlashCell,
|
||||
@@ -61,7 +60,6 @@ export const MarketListTable = forwardRef<
|
||||
<AgGrid
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
getRowId={getRowId}
|
||||
ref={ref}
|
||||
defaultColDef={defaultColDef}
|
||||
columnDefs={columnDefs}
|
||||
suppressCellFocus
|
||||
@@ -69,6 +67,6 @@ export const MarketListTable = forwardRef<
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
export default MarketListTable;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { MouseEvent } from 'react';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import React, { useEffect } from 'react';
|
||||
import type { CellClickedEvent } from 'ag-grid-community';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { MarketListTable } from './market-list-table';
|
||||
@@ -18,8 +17,6 @@ export const MarketsContainer = ({
|
||||
onSelect,
|
||||
SuccessorMarketRenderer,
|
||||
}: MarketsContainerProps) => {
|
||||
const gridRef = useRef<AgGridReact | null>(null);
|
||||
|
||||
const { data, error, reload } = useDataProvider({
|
||||
dataProvider,
|
||||
variables: undefined,
|
||||
@@ -37,7 +34,6 @@ export const MarketsContainer = ({
|
||||
return (
|
||||
<div className="h-full relative">
|
||||
<MarketListTable
|
||||
ref={gridRef}
|
||||
rowData={data}
|
||||
onCellClicked={(cellEvent: CellClickedEvent) => {
|
||||
const { data, column, event } = cellEvent;
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export * from './order-actions-dropdown';
|
||||
@@ -1,13 +0,0 @@
|
||||
import {
|
||||
ActionsDropdown,
|
||||
TradingDropdownCopyItem,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
|
||||
export const OrderActionsDropdown = ({ id }: { id: string }) => {
|
||||
return (
|
||||
<ActionsDropdown data-testid="order-actions-content">
|
||||
<TradingDropdownCopyItem value={id} text={t('Copy order ID')} />
|
||||
</ActionsDropdown>
|
||||
);
|
||||
};
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
import type { OrderTxUpdateFieldsFragment } from '@vegaprotocol/wallet';
|
||||
import { OrderEditDialog } from '../order-list/order-edit-dialog';
|
||||
import type { Order } from '../order-data-provider';
|
||||
import { OrderViewDialog } from '../order-list/order-view-dialog';
|
||||
|
||||
export enum Filter {
|
||||
'Open' = 'Open',
|
||||
@@ -42,6 +43,7 @@ export const OrderListManager = ({
|
||||
}: OrderListManagerProps) => {
|
||||
const gridRef = useRef<AgGridReact | null>(null);
|
||||
const [editOrder, setEditOrder] = useState<Order | null>(null);
|
||||
const [viewOrder, setViewOrder] = useState<Order | null>(null);
|
||||
const create = useVegaTransactionStore((state) => state.create);
|
||||
const hasAmendableOrder = useHasAmendableOrder(marketId);
|
||||
const variables =
|
||||
@@ -89,6 +91,7 @@ export const OrderListManager = ({
|
||||
filter={filter}
|
||||
onCancel={cancel}
|
||||
onEdit={setEditOrder}
|
||||
onView={setViewOrder}
|
||||
onMarketClick={onMarketClick}
|
||||
onOrderTypeClick={onOrderTypeClick}
|
||||
isReadOnly={isReadOnly}
|
||||
@@ -134,6 +137,14 @@ export const OrderListManager = ({
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{viewOrder && (
|
||||
<OrderViewDialog
|
||||
isOpen={Boolean(viewOrder)}
|
||||
order={viewOrder}
|
||||
onChange={() => setViewOrder(null)}
|
||||
onMarketClick={onMarketClick}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -27,6 +27,7 @@ const defaultProps: OrderListTableProps = {
|
||||
rowData: [],
|
||||
onEdit: jest.fn(),
|
||||
onCancel: jest.fn(),
|
||||
onView: jest.fn(),
|
||||
isReadOnly: false,
|
||||
};
|
||||
|
||||
|
||||
@@ -22,6 +22,9 @@ const Template: Story = (args) => {
|
||||
onEdit={() => {
|
||||
return;
|
||||
}}
|
||||
onView={() => {
|
||||
return;
|
||||
}}
|
||||
isReadOnly={false}
|
||||
/>
|
||||
</div>
|
||||
@@ -49,6 +52,9 @@ const Template2: Story = (args) => {
|
||||
rowData={args.data}
|
||||
onCancel={cancel}
|
||||
onEdit={setEditOrder}
|
||||
onView={() => {
|
||||
return;
|
||||
}}
|
||||
isReadOnly={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,14 @@ import {
|
||||
} from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
ActionsDropdown,
|
||||
ButtonLink,
|
||||
TradingDropdownCopyItem,
|
||||
DropdownMenuItem,
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import type { ForwardedRef } from 'react';
|
||||
import { memo, forwardRef, useMemo } from 'react';
|
||||
import {
|
||||
@@ -27,7 +34,6 @@ import type {
|
||||
} from '@vegaprotocol/datagrid';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import type { Order } from '../order-data-provider';
|
||||
import { OrderActionsDropdown } from '../order-actions-dropdown';
|
||||
import { Filter } from '../order-list-manager';
|
||||
import type { ColDef } from 'ag-grid-community';
|
||||
|
||||
@@ -35,6 +41,7 @@ export type OrderListTableProps = TypedDataAgGrid<Order> & {
|
||||
marketId?: string;
|
||||
onCancel: (order: Order) => void;
|
||||
onEdit: (order: Order) => void;
|
||||
onView: (order: Order) => void;
|
||||
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
|
||||
onOrderTypeClick?: (marketId: string, metaKey?: boolean) => void;
|
||||
filter?: Filter;
|
||||
@@ -46,7 +53,15 @@ export const OrderListTable = memo<
|
||||
>(
|
||||
forwardRef<AgGridReact, OrderListTableProps>(
|
||||
(
|
||||
{ onCancel, onEdit, onMarketClick, onOrderTypeClick, filter, ...props },
|
||||
{
|
||||
onCancel,
|
||||
onEdit,
|
||||
onView,
|
||||
onMarketClick,
|
||||
onOrderTypeClick,
|
||||
filter,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const showAllActions = props.isReadOnly
|
||||
@@ -295,7 +310,20 @@ export const OrderListTable = memo<
|
||||
</ButtonLink>
|
||||
</>
|
||||
)}
|
||||
<OrderActionsDropdown id={data?.id} />
|
||||
<ActionsDropdown data-testid="market-actions-content">
|
||||
<TradingDropdownCopyItem
|
||||
value={data.id}
|
||||
text={t('Copy order ID')}
|
||||
/>
|
||||
<DropdownMenuItem
|
||||
key={'view-order'}
|
||||
data-testid="view-order"
|
||||
onClick={() => onView(data)}
|
||||
>
|
||||
<VegaIcon name={VegaIconNames.INFO} size={16} />
|
||||
{t('View order details')}
|
||||
</DropdownMenuItem>
|
||||
</ActionsDropdown>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
@@ -305,6 +333,7 @@ export const OrderListTable = memo<
|
||||
filter,
|
||||
onCancel,
|
||||
onEdit,
|
||||
onView,
|
||||
onMarketClick,
|
||||
onOrderTypeClick,
|
||||
props.isReadOnly,
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { OrderViewDialog } from './order-view-dialog';
|
||||
import type { Order } from '../order-data-provider';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import {
|
||||
MarketState,
|
||||
MarketTradingMode,
|
||||
OrderStatus,
|
||||
OrderTimeInForce,
|
||||
OrderType,
|
||||
Side,
|
||||
} from '@vegaprotocol/types';
|
||||
|
||||
describe('OrderViewDialog', () => {
|
||||
it('should render the order view dialog if the order is provided', async () => {
|
||||
const order: Order = {
|
||||
id: '7f2a78f370062e41683d26a0fcc4521b2bd4b747530b78bc6bc86195db0e5fb3',
|
||||
market: {
|
||||
__typename: 'Market',
|
||||
id: 'b66cd4be223dfd900a4750bb5175e17d8f678996877d262be4c749a99e22a970',
|
||||
decimalPlaces: 5,
|
||||
positionDecimalPlaces: 3,
|
||||
state: MarketState.STATE_ACTIVE,
|
||||
tradingMode: MarketTradingMode.TRADING_MODE_CONTINUOUS,
|
||||
fees: {
|
||||
__typename: 'Fees',
|
||||
factors: {
|
||||
__typename: 'FeeFactors',
|
||||
makerFee: '0.0002',
|
||||
infrastructureFee: '0.0005',
|
||||
liquidityFee: '0.001',
|
||||
},
|
||||
},
|
||||
tradableInstrument: {
|
||||
__typename: 'TradableInstrument',
|
||||
instrument: {
|
||||
__typename: 'Instrument',
|
||||
id: '',
|
||||
name: 'Tesla Quarterly (Sep 2023)',
|
||||
code: 'TSLA.QM21',
|
||||
metadata: {
|
||||
__typename: 'InstrumentMetadata',
|
||||
tags: [
|
||||
'formerly:5A86B190C384997F',
|
||||
'quote:EURO',
|
||||
'ticker:TSLA',
|
||||
'class:equities/single-stock-futures',
|
||||
'sector:tech',
|
||||
'listing_venue:NASDAQ',
|
||||
'country:US',
|
||||
'auto:tsla',
|
||||
],
|
||||
},
|
||||
product: {
|
||||
__typename: 'Future',
|
||||
settlementAsset: {
|
||||
__typename: 'Asset',
|
||||
id: '177e8f6c25a955bd18475084b99b2b1d37f28f3dec393fab7755a7e69c3d8c3b',
|
||||
symbol: 'tEURO',
|
||||
name: 'tEURO Fairground',
|
||||
decimals: 5,
|
||||
quantum: '1',
|
||||
},
|
||||
quoteName: 'EURO',
|
||||
dataSourceSpecForTradingTermination: {
|
||||
__typename: 'DataSourceSpec',
|
||||
id: '3f5941ba047dabb3bec23f112db0a8cc8aecbbbc6b34d1366d9383ed1b0f39df',
|
||||
data: {
|
||||
__typename: 'DataSourceDefinition',
|
||||
sourceType: {
|
||||
__typename: 'DataSourceDefinitionExternal',
|
||||
sourceType: {
|
||||
__typename: 'DataSourceSpecConfiguration',
|
||||
signers: [
|
||||
{
|
||||
__typename: 'Signer',
|
||||
signer: {
|
||||
__typename: 'PubKey',
|
||||
key: '69464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f',
|
||||
},
|
||||
},
|
||||
],
|
||||
filters: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
dataSourceSpecForSettlementData: {
|
||||
__typename: 'DataSourceSpec',
|
||||
id: '05ca5485678aa7c705aa6a683fd062714cbcfdac2d1a27641cd27b66168d9c1d',
|
||||
data: {
|
||||
__typename: 'DataSourceDefinition',
|
||||
sourceType: {
|
||||
__typename: 'DataSourceDefinitionExternal',
|
||||
sourceType: {
|
||||
__typename: 'DataSourceSpecConfiguration',
|
||||
signers: [
|
||||
{
|
||||
__typename: 'Signer',
|
||||
signer: {
|
||||
__typename: 'PubKey',
|
||||
key: '69464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f',
|
||||
},
|
||||
},
|
||||
],
|
||||
filters: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
dataSourceSpecBinding: {
|
||||
__typename: 'DataSourceSpecToFutureBinding',
|
||||
settlementDataProperty: 'prices.TSLA.value',
|
||||
tradingTerminationProperty: 'termination.TSLA.value',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
marketTimestamps: {
|
||||
__typename: 'MarketTimestamps',
|
||||
open: '2023-07-19T12:05:25.822854221Z',
|
||||
close: null,
|
||||
},
|
||||
},
|
||||
type: OrderType.TYPE_LIMIT,
|
||||
side: Side.SIDE_BUY,
|
||||
size: '10000',
|
||||
status: OrderStatus.STATUS_ACTIVE,
|
||||
rejectionReason: null,
|
||||
price: '15000000',
|
||||
timeInForce: OrderTimeInForce.TIME_IN_FORCE_GTT,
|
||||
remaining: '5000',
|
||||
expiresAt: null,
|
||||
createdAt: '2023-08-01T14:47:34.977742Z',
|
||||
updatedAt: null,
|
||||
postOnly: false,
|
||||
reduceOnly: false,
|
||||
liquidityProvision: null,
|
||||
peggedOrder: null,
|
||||
icebergOrder: {
|
||||
__typename: 'IcebergOrder',
|
||||
peakSize: '5000',
|
||||
minimumVisibleSize: '2000',
|
||||
reservedRemaining: '5000',
|
||||
},
|
||||
__typename: 'Order',
|
||||
};
|
||||
|
||||
render(
|
||||
<BrowserRouter>
|
||||
<OrderViewDialog order={order} onChange={jest.fn()} isOpen={true} />
|
||||
</BrowserRouter>
|
||||
);
|
||||
|
||||
expect(screen.getByTestId('order-market-label')).toHaveTextContent(
|
||||
'Market'
|
||||
);
|
||||
expect(screen.getByTestId('order-market-value')).toHaveTextContent(
|
||||
'Tesla Quarterly (Sep 2023)'
|
||||
);
|
||||
expect(screen.getByTestId('order-type-label')).toHaveTextContent('Type');
|
||||
expect(screen.getByTestId('order-type-value')).toHaveTextContent('Limit');
|
||||
expect(screen.getByTestId('order-price-label')).toHaveTextContent('Price');
|
||||
expect(screen.getByTestId('order-price-value')).toHaveTextContent('150.00');
|
||||
expect(screen.getByTestId('order-size-label')).toHaveTextContent('Size');
|
||||
expect(screen.getByTestId('order-size-value')).toHaveTextContent('+10.00');
|
||||
expect(screen.getByTestId('order-remaining-label')).toHaveTextContent(
|
||||
'Remaining'
|
||||
);
|
||||
expect(screen.getByTestId('order-remaining-value')).toHaveTextContent(
|
||||
'+5.00'
|
||||
);
|
||||
expect(
|
||||
screen.getByTestId('order-iceberg-order-reserved-remaining-value')
|
||||
).toHaveTextContent('5.00');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,280 @@
|
||||
import {
|
||||
addDecimalsFormatNumber,
|
||||
getDateTimeFormat,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Size } from '@vegaprotocol/datagrid';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import {
|
||||
ButtonLink,
|
||||
Dialog,
|
||||
KeyValueTable,
|
||||
KeyValueTableRow,
|
||||
Tooltip,
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
truncateMiddle,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import type { Order } from '../order-data-provider';
|
||||
import CopyToClipboard from 'react-copy-to-clipboard';
|
||||
import { useCopyTimeout } from '@vegaprotocol/react-helpers';
|
||||
|
||||
interface OrderViewDialogProps {
|
||||
isOpen: boolean;
|
||||
order: Order;
|
||||
onChange: (open: boolean) => void;
|
||||
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
|
||||
}
|
||||
|
||||
export const OrderViewDialog = ({
|
||||
isOpen,
|
||||
order,
|
||||
onChange,
|
||||
onMarketClick,
|
||||
}: OrderViewDialogProps) => {
|
||||
const [, setCopied] = useCopyTimeout();
|
||||
return (
|
||||
<Dialog open={isOpen} title={t('Order details')} onChange={onChange}>
|
||||
<KeyValueTable>
|
||||
<KeyValueTableRow key={'order-market'}>
|
||||
<div data-testid={'order-market-label'}>{t('Market')}</div>
|
||||
<div data-testid={`order-market-value`}>
|
||||
{onMarketClick ? (
|
||||
<ButtonLink
|
||||
onClick={() => order.market && onMarketClick(order.market?.id)}
|
||||
>
|
||||
{order.market?.tradableInstrument.instrument.name}
|
||||
</ButtonLink>
|
||||
) : (
|
||||
order.market?.tradableInstrument.instrument.name
|
||||
)}
|
||||
</div>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow key={'order-type'}>
|
||||
<div data-testid={'order-type-label'}>{t('Type')}</div>
|
||||
<div data-testid={`order-type-value`}>
|
||||
{Schema.OrderTypeMapping[order.type as Schema.OrderType]}
|
||||
</div>
|
||||
</KeyValueTableRow>
|
||||
{order.market && (
|
||||
<KeyValueTableRow key={'order-price'}>
|
||||
<div data-testid={'order-price-label'}>{t('Price')}</div>
|
||||
<div data-testid={`order-price-value`}>
|
||||
{addDecimalsFormatNumber(
|
||||
order.price,
|
||||
order.market.decimalPlaces as number
|
||||
)}
|
||||
</div>
|
||||
</KeyValueTableRow>
|
||||
)}
|
||||
{order.market && (
|
||||
<KeyValueTableRow key={'order-size'}>
|
||||
<div data-testid={'order-size-label'}>{t('Size')}</div>
|
||||
<div data-testid={`order-size-value`}>
|
||||
<Size
|
||||
value={order.size}
|
||||
side={order.side}
|
||||
positionDecimalPlaces={
|
||||
order.market.positionDecimalPlaces as number
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</KeyValueTableRow>
|
||||
)}
|
||||
{order.market && (
|
||||
<KeyValueTableRow key={'order-remaining'} className="mb-4">
|
||||
<div data-testid={'order-remaining-label'}>{t('Remaining')}</div>
|
||||
<div data-testid={`order-remaining-value`}>
|
||||
<Size
|
||||
value={order.remaining}
|
||||
side={order.side}
|
||||
positionDecimalPlaces={
|
||||
order.market.positionDecimalPlaces as number
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</KeyValueTableRow>
|
||||
)}
|
||||
<KeyValueTableRow key={'order-status'}>
|
||||
<div data-testid={'order-status-label'}>{t('Status')}</div>
|
||||
<div data-testid={`order-status-value`}>
|
||||
{Schema.OrderStatusMapping[order.status as Schema.OrderStatus]}
|
||||
</div>
|
||||
</KeyValueTableRow>
|
||||
{order.rejectionReason && (
|
||||
<KeyValueTableRow key={'order-rejection-reason'}>
|
||||
<div data-testid={'order-rejection-reason-label'}>
|
||||
{t('rejection reason')}
|
||||
</div>
|
||||
<div data-testid={`order-rejection-reason-value`}>
|
||||
{
|
||||
Schema.OrderRejectionReasonMapping[
|
||||
order.rejectionReason as Schema.OrderRejectionReason
|
||||
]
|
||||
}
|
||||
</div>
|
||||
</KeyValueTableRow>
|
||||
)}
|
||||
<KeyValueTableRow key={'order-id'}>
|
||||
<div data-testid={'order-id-label'}>{t('Order ID')}</div>
|
||||
<div data-testid={`order-id-value`}>
|
||||
{truncateMiddle(order.id, 10)}
|
||||
<CopyToClipboard text={order.id} onCopy={() => setCopied(true)}>
|
||||
<button
|
||||
type="button"
|
||||
data-testid="copy-order-id"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<span className="sr-only">{t('Copy')}</span>
|
||||
<VegaIcon name={VegaIconNames.COPY} />
|
||||
</button>
|
||||
</CopyToClipboard>
|
||||
</div>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow key={'order-created'}>
|
||||
<div data-testid={'order-created-label'}>{t('Created')}</div>
|
||||
<div data-testid={`order-created-value`}>
|
||||
{getDateTimeFormat().format(new Date(order.createdAt))}
|
||||
</div>
|
||||
</KeyValueTableRow>
|
||||
{order.updatedAt && (
|
||||
<KeyValueTableRow key={'order-updated'}>
|
||||
<div data-testid={'order-updated-label'}>{t('Updated')}</div>
|
||||
<div data-testid={`order-updated-value`}>
|
||||
{getDateTimeFormat().format(new Date(order.updatedAt))}
|
||||
</div>
|
||||
</KeyValueTableRow>
|
||||
)}
|
||||
{order.expiresAt && (
|
||||
<KeyValueTableRow key={'order-expires'}>
|
||||
<div data-testid={'order-expires-label'}>{t('Expires')}</div>
|
||||
<div data-testid={`order-expires-value`}>
|
||||
{getDateTimeFormat().format(new Date(order.expiresAt))}
|
||||
</div>
|
||||
</KeyValueTableRow>
|
||||
)}
|
||||
|
||||
<KeyValueTableRow key={'order-time-in-force'} className="mt-4">
|
||||
<div data-testid={'order-time-in-force-label'}>
|
||||
{t('Time in force')}
|
||||
</div>
|
||||
<div data-testid={`order-time-in-force-value`}>
|
||||
{
|
||||
Schema.OrderTimeInForceMapping[
|
||||
order.timeInForce as Schema.OrderTimeInForce
|
||||
]
|
||||
}
|
||||
</div>
|
||||
</KeyValueTableRow>
|
||||
|
||||
<KeyValueTableRow key={'order-post-only'}>
|
||||
<div data-testid={'order-post-only-label'}>{t('Post only')}</div>
|
||||
<div data-testid={`order-post-only-value`}>
|
||||
{order.postOnly ? t('Yes') : t('-')}
|
||||
</div>
|
||||
</KeyValueTableRow>
|
||||
|
||||
<KeyValueTableRow key={'order-reduce-only'}>
|
||||
<div data-testid={'order-reduce-only-label'}>{t('Reduce only')}</div>
|
||||
<div data-testid={`order-reduce-only-value`}>
|
||||
{order.reduceOnly ? t('Yes') : t('-')}
|
||||
</div>
|
||||
</KeyValueTableRow>
|
||||
|
||||
<KeyValueTableRow key={'order-pegged'}>
|
||||
<div data-testid={'order-pegged-label'}>{t('Pegged')}</div>
|
||||
<div data-testid={`order-pegged-value`}>
|
||||
{order.peggedOrder ? t('Yes') : t('-')}
|
||||
</div>
|
||||
</KeyValueTableRow>
|
||||
|
||||
<KeyValueTableRow key={'order-liquidity-provision'}>
|
||||
<div data-testid={'order-liquidity-provision-label'}>
|
||||
{t('Liquidity provision')}
|
||||
</div>
|
||||
<div data-testid={`order-liquidity-provision-value`}>
|
||||
{order.liquidityProvision ? t('Yes') : t('-')}
|
||||
</div>
|
||||
</KeyValueTableRow>
|
||||
</KeyValueTable>
|
||||
|
||||
<KeyValueTableRow key={'order-iceberg-order'}>
|
||||
<div data-testid={'order-iceberg-order-label'}>
|
||||
{t('Iceberg order')}
|
||||
</div>
|
||||
<div data-testid={`order-iceberg-order-value`}>
|
||||
{order.icebergOrder ? t('Yes') : t('-')}
|
||||
</div>
|
||||
</KeyValueTableRow>
|
||||
{order.icebergOrder && (
|
||||
<KeyValueTableRow
|
||||
key={'order-iceberg-order-peak-size'}
|
||||
className="ml-4"
|
||||
>
|
||||
<div data-testid={'order-iceberg-order-peak-size-label'}>
|
||||
<Tooltip
|
||||
description={t(
|
||||
'The maximum volume that can be traded at once. Must be less than the total size of the order.'
|
||||
)}
|
||||
>
|
||||
<span>{t('Peak size')}</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div data-testid={`order-iceberg-order-peak-size-value`}>
|
||||
<Size
|
||||
value={order.icebergOrder.peakSize}
|
||||
side={order.side}
|
||||
positionDecimalPlaces={
|
||||
order.market?.positionDecimalPlaces as number
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</KeyValueTableRow>
|
||||
)}
|
||||
{order.icebergOrder && (
|
||||
<KeyValueTableRow
|
||||
key={'order-iceberg-order-minimum-visible-size'}
|
||||
className="ml-4"
|
||||
>
|
||||
<div data-testid={'order-iceberg-order-minimum-visible-size-label'}>
|
||||
<Tooltip
|
||||
description={t(
|
||||
'When the order trades and its size falls below this threshold, it will be reset to the peak size and moved to the back of the priority order. Must be less than or equal to peak size, and greater than 0.'
|
||||
)}
|
||||
>
|
||||
<span>{t('Minimum size')}</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div data-testid={`order-iceberg-order-minimum-visible-size-value`}>
|
||||
<Size
|
||||
value={order.icebergOrder.minimumVisibleSize}
|
||||
side={order.side}
|
||||
positionDecimalPlaces={
|
||||
order.market?.positionDecimalPlaces as number
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</KeyValueTableRow>
|
||||
)}
|
||||
{order.icebergOrder && (
|
||||
<KeyValueTableRow
|
||||
key={'order-iceberg-order-reserved-remaining'}
|
||||
className="ml-4"
|
||||
>
|
||||
<div data-testid={'order-iceberg-order-reserved-remaining-label'}>
|
||||
{t('Reserved remaining')}
|
||||
</div>
|
||||
<div data-testid={`order-iceberg-order-reserved-remaining-value`}>
|
||||
<Size
|
||||
value={order.icebergOrder.reservedRemaining}
|
||||
side={order.side}
|
||||
positionDecimalPlaces={
|
||||
order.market?.positionDecimalPlaces as number
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</KeyValueTableRow>
|
||||
)}
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -2,17 +2,13 @@ import { render, screen, act } from '@testing-library/react';
|
||||
import { StopOrdersManager } from './stop-orders-manager';
|
||||
import * as useDataProviderHook from '@vegaprotocol/data-provider';
|
||||
import type { StopOrder } from '../order-data-provider/stop-orders-data-provider';
|
||||
import * as stopOrdersTableMock from '../stop-orders-table/stop-orders-table';
|
||||
import { forwardRef } from 'react';
|
||||
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
|
||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
|
||||
// @ts-ignore StopOrdersTable is read only but we need to override with the forwardRef to
|
||||
// avoid warnings about padding refs
|
||||
stopOrdersTableMock.StopOrdersTable = forwardRef(() => (
|
||||
<div>StopOrdersTable</div>
|
||||
));
|
||||
jest.mock('../stop-orders-table/stop-orders-table', () => ({
|
||||
StopOrdersTable: () => <div>StopOrdersTable</div>,
|
||||
}));
|
||||
|
||||
const generateJsx = () => {
|
||||
const pubKey = '0x123';
|
||||
|
||||
@@ -8,7 +8,7 @@ import { t } from '@vegaprotocol/i18n';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
|
||||
import type { ForwardedRef } from 'react';
|
||||
import { memo, forwardRef, useMemo } from 'react';
|
||||
import { memo, useMemo } from 'react';
|
||||
import {
|
||||
AgGridLazy as AgGrid,
|
||||
SetFilter,
|
||||
@@ -36,265 +36,252 @@ export type StopOrdersTableProps = TypedDataAgGrid<StopOrder> & {
|
||||
|
||||
export const StopOrdersTable = memo<
|
||||
StopOrdersTableProps & { ref?: ForwardedRef<AgGridReact> }
|
||||
>(
|
||||
forwardRef<AgGridReact, StopOrdersTableProps>(
|
||||
({ onCancel, onMarketClick, ...props }, ref) => {
|
||||
const showAllActions = !props.isReadOnly;
|
||||
const columnDefs: ColDef[] = useMemo(
|
||||
() => [
|
||||
{
|
||||
headerName: t('Market'),
|
||||
field: 'market.tradableInstrument.instrument.code',
|
||||
cellRenderer: 'MarketNameCell',
|
||||
cellRendererParams: { idPath: 'market.id', onMarketClick },
|
||||
minWidth: 150,
|
||||
},
|
||||
{
|
||||
headerName: t('Trigger'),
|
||||
field: 'trigger',
|
||||
cellClass: 'font-mono text-right',
|
||||
type: 'rightAligned',
|
||||
sortable: false,
|
||||
valueFormatter: ({
|
||||
data,
|
||||
value,
|
||||
}: VegaValueFormatterParams<StopOrder, 'trigger'>): string => {
|
||||
if (data && value?.__typename === 'StopOrderPrice') {
|
||||
return `${t('Mark')} ${
|
||||
data?.triggerDirection ===
|
||||
Schema.StopOrderTriggerDirection.TRIGGER_DIRECTION_FALLS_BELOW
|
||||
? '<'
|
||||
: '>'
|
||||
} ${addDecimalsFormatNumber(
|
||||
value.price,
|
||||
data.market.decimalPlaces
|
||||
)}`;
|
||||
}
|
||||
if (
|
||||
data &&
|
||||
value?.__typename === 'StopOrderTrailingPercentOffset'
|
||||
) {
|
||||
return `${t('Mark')} ${
|
||||
data?.triggerDirection ===
|
||||
Schema.StopOrderTriggerDirection.TRIGGER_DIRECTION_FALLS_BELOW
|
||||
? '+'
|
||||
: '-'
|
||||
}${(Number(value.trailingPercentOffset) * 100).toFixed(1)}%`;
|
||||
}
|
||||
return '-';
|
||||
},
|
||||
minWidth: 100,
|
||||
},
|
||||
{
|
||||
field: 'expiresAt',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<StopOrder, 'expiresAt'>) => {
|
||||
if (
|
||||
data &&
|
||||
value &&
|
||||
data?.expiryStrategy !==
|
||||
Schema.StopOrderExpiryStrategy.EXPIRY_STRATEGY_UNSPECIFIED
|
||||
) {
|
||||
const expiresAt = getDateTimeFormat().format(new Date(value));
|
||||
const expiryStrategy =
|
||||
data.expiryStrategy ===
|
||||
Schema.StopOrderExpiryStrategy.EXPIRY_STRATEGY_SUBMIT
|
||||
? t('Submit')
|
||||
: t('Cancels');
|
||||
return `${expiryStrategy} ${expiresAt}`;
|
||||
}
|
||||
return '';
|
||||
},
|
||||
minWidth: 150,
|
||||
},
|
||||
{
|
||||
headerName: t('Size'),
|
||||
field: 'submission.size',
|
||||
cellClass: 'font-mono text-right',
|
||||
type: 'rightAligned',
|
||||
cellClassRules: {
|
||||
[positiveClassNames]: ({ data }: { data: StopOrder }) =>
|
||||
data?.submission.size === Schema.Side.SIDE_BUY,
|
||||
[negativeClassNames]: ({ data }: { data: StopOrder }) =>
|
||||
data?.submission.size === Schema.Side.SIDE_SELL,
|
||||
},
|
||||
valueGetter: ({ data }: VegaValueGetterParams<StopOrder>) => {
|
||||
return data?.submission.size && data.market
|
||||
? toBigNum(
|
||||
data.submission.size,
|
||||
data.market.positionDecimalPlaces ?? 0
|
||||
)
|
||||
.multipliedBy(
|
||||
data.submission.side === Schema.Side.SIDE_SELL ? -1 : 1
|
||||
)
|
||||
.toNumber()
|
||||
: undefined;
|
||||
},
|
||||
valueFormatter: ({
|
||||
data,
|
||||
}: VegaValueFormatterParams<StopOrder, 'size'>) => {
|
||||
if (!data) {
|
||||
return '';
|
||||
}
|
||||
if (!data?.market || !isNumeric(data.submission.size)) {
|
||||
return '-';
|
||||
}
|
||||
const prefix = data
|
||||
? data.submission.side === Schema.Side.SIDE_BUY
|
||||
? '+'
|
||||
: '-'
|
||||
: '';
|
||||
return (
|
||||
prefix +
|
||||
addDecimalsFormatNumber(
|
||||
data.submission.size,
|
||||
data.market.positionDecimalPlaces
|
||||
>(({ onCancel, onMarketClick, ...props }: StopOrdersTableProps) => {
|
||||
const showAllActions = !props.isReadOnly;
|
||||
const columnDefs: ColDef[] = useMemo(
|
||||
() => [
|
||||
{
|
||||
headerName: t('Market'),
|
||||
field: 'market.tradableInstrument.instrument.code',
|
||||
cellRenderer: 'MarketNameCell',
|
||||
cellRendererParams: { idPath: 'market.id', onMarketClick },
|
||||
minWidth: 150,
|
||||
},
|
||||
{
|
||||
headerName: t('Trigger'),
|
||||
field: 'trigger',
|
||||
cellClass: 'font-mono text-right',
|
||||
type: 'rightAligned',
|
||||
sortable: false,
|
||||
valueFormatter: ({
|
||||
data,
|
||||
value,
|
||||
}: VegaValueFormatterParams<StopOrder, 'trigger'>): string => {
|
||||
if (data && value?.__typename === 'StopOrderPrice') {
|
||||
return `${t('Mark')} ${
|
||||
data?.triggerDirection ===
|
||||
Schema.StopOrderTriggerDirection.TRIGGER_DIRECTION_FALLS_BELOW
|
||||
? '<'
|
||||
: '>'
|
||||
} ${addDecimalsFormatNumber(
|
||||
value.price,
|
||||
data.market.decimalPlaces
|
||||
)}`;
|
||||
}
|
||||
if (data && value?.__typename === 'StopOrderTrailingPercentOffset') {
|
||||
return `${t('Mark')} ${
|
||||
data?.triggerDirection ===
|
||||
Schema.StopOrderTriggerDirection.TRIGGER_DIRECTION_FALLS_BELOW
|
||||
? '+'
|
||||
: '-'
|
||||
}${(Number(value.trailingPercentOffset) * 100).toFixed(1)}%`;
|
||||
}
|
||||
return '-';
|
||||
},
|
||||
minWidth: 100,
|
||||
},
|
||||
{
|
||||
field: 'expiresAt',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<StopOrder, 'expiresAt'>) => {
|
||||
if (
|
||||
data &&
|
||||
value &&
|
||||
data?.expiryStrategy !==
|
||||
Schema.StopOrderExpiryStrategy.EXPIRY_STRATEGY_UNSPECIFIED
|
||||
) {
|
||||
const expiresAt = getDateTimeFormat().format(new Date(value));
|
||||
const expiryStrategy =
|
||||
data.expiryStrategy ===
|
||||
Schema.StopOrderExpiryStrategy.EXPIRY_STRATEGY_SUBMIT
|
||||
? t('Submit')
|
||||
: t('Cancels');
|
||||
return `${expiryStrategy} ${expiresAt}`;
|
||||
}
|
||||
return '';
|
||||
},
|
||||
minWidth: 150,
|
||||
},
|
||||
{
|
||||
headerName: t('Size'),
|
||||
field: 'submission.size',
|
||||
cellClass: 'font-mono text-right',
|
||||
type: 'rightAligned',
|
||||
cellClassRules: {
|
||||
[positiveClassNames]: ({ data }: { data: StopOrder }) =>
|
||||
data?.submission.size === Schema.Side.SIDE_BUY,
|
||||
[negativeClassNames]: ({ data }: { data: StopOrder }) =>
|
||||
data?.submission.size === Schema.Side.SIDE_SELL,
|
||||
},
|
||||
valueGetter: ({ data }: VegaValueGetterParams<StopOrder>) => {
|
||||
return data?.submission.size && data.market
|
||||
? toBigNum(
|
||||
data.submission.size,
|
||||
data.market.positionDecimalPlaces ?? 0
|
||||
)
|
||||
.multipliedBy(
|
||||
data.submission.side === Schema.Side.SIDE_SELL ? -1 : 1
|
||||
)
|
||||
);
|
||||
},
|
||||
minWidth: 80,
|
||||
},
|
||||
{
|
||||
field: 'submission.type',
|
||||
filter: SetFilter,
|
||||
filterParams: {
|
||||
set: Schema.OrderTypeMapping,
|
||||
},
|
||||
cellRenderer: ({
|
||||
value,
|
||||
}: VegaICellRendererParams<StopOrder, 'submission.type'>) =>
|
||||
value ? Schema.OrderTypeMapping[value] : '',
|
||||
minWidth: 80,
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
filter: SetFilter,
|
||||
filterParams: {
|
||||
set: Schema.StopOrderStatusMapping,
|
||||
},
|
||||
valueFormatter: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<StopOrder, 'status'>) => {
|
||||
return value ? Schema.StopOrderStatusMapping[value] : '';
|
||||
},
|
||||
cellRenderer: ({
|
||||
valueFormatted,
|
||||
data,
|
||||
}: {
|
||||
valueFormatted: string;
|
||||
data: StopOrder;
|
||||
}) => (
|
||||
<span data-testid={`order-status-${data?.id}`}>
|
||||
{valueFormatted}
|
||||
</span>
|
||||
),
|
||||
minWidth: 100,
|
||||
},
|
||||
{
|
||||
field: 'submission.price',
|
||||
type: 'rightAligned',
|
||||
cellClass: 'font-mono text-right',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<StopOrder, 'submission.price'>) => {
|
||||
if (!data) {
|
||||
return '';
|
||||
}
|
||||
if (
|
||||
!data?.market ||
|
||||
data.submission.type === Schema.OrderType.TYPE_MARKET ||
|
||||
!isNumeric(value)
|
||||
) {
|
||||
return '-';
|
||||
}
|
||||
return addDecimalsFormatNumber(value, data.market.decimalPlaces);
|
||||
},
|
||||
minWidth: 100,
|
||||
},
|
||||
{
|
||||
field: 'submission.timeInForce',
|
||||
filter: SetFilter,
|
||||
filterParams: {
|
||||
set: Schema.OrderTimeInForceMapping,
|
||||
},
|
||||
valueFormatter: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<
|
||||
StopOrder,
|
||||
'submission.timeInForce'
|
||||
>) => {
|
||||
return value ? Schema.OrderTimeInForceCode[value] : '';
|
||||
},
|
||||
minWidth: 150,
|
||||
},
|
||||
{
|
||||
field: 'updatedAt',
|
||||
filter: DateRangeFilter,
|
||||
valueGetter: ({ data }: VegaValueGetterParams<StopOrder>) =>
|
||||
data?.updatedAt || data?.createdAt,
|
||||
cellRenderer: ({
|
||||
data,
|
||||
}: VegaICellRendererParams<StopOrder, 'createdAt'>) => {
|
||||
if (!data) {
|
||||
return undefined;
|
||||
}
|
||||
const value = data.updatedAt || data.createdAt;
|
||||
return (
|
||||
<span data-value={value}>
|
||||
{value ? getDateTimeFormat().format(new Date(value)) : '-'}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
minWidth: 150,
|
||||
},
|
||||
{
|
||||
colId: 'actions',
|
||||
...COL_DEFS.actions,
|
||||
minWidth: showAllActions ? 120 : COL_DEFS.actions.minWidth,
|
||||
maxWidth: showAllActions ? 120 : COL_DEFS.actions.minWidth,
|
||||
cellRenderer: ({ data }: { data?: StopOrder }) => {
|
||||
if (!data) return null;
|
||||
.toNumber()
|
||||
: undefined;
|
||||
},
|
||||
valueFormatter: ({
|
||||
data,
|
||||
}: VegaValueFormatterParams<StopOrder, 'size'>) => {
|
||||
if (!data) {
|
||||
return '';
|
||||
}
|
||||
if (!data?.market || !isNumeric(data.submission.size)) {
|
||||
return '-';
|
||||
}
|
||||
const prefix = data
|
||||
? data.submission.side === Schema.Side.SIDE_BUY
|
||||
? '+'
|
||||
: '-'
|
||||
: '';
|
||||
return (
|
||||
prefix +
|
||||
addDecimalsFormatNumber(
|
||||
data.submission.size,
|
||||
data.market.positionDecimalPlaces
|
||||
)
|
||||
);
|
||||
},
|
||||
minWidth: 80,
|
||||
},
|
||||
{
|
||||
field: 'submission.type',
|
||||
filter: SetFilter,
|
||||
filterParams: {
|
||||
set: Schema.OrderTypeMapping,
|
||||
},
|
||||
cellRenderer: ({
|
||||
value,
|
||||
}: VegaICellRendererParams<StopOrder, 'submission.type'>) =>
|
||||
value ? Schema.OrderTypeMapping[value] : '',
|
||||
minWidth: 80,
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
filter: SetFilter,
|
||||
filterParams: {
|
||||
set: Schema.StopOrderStatusMapping,
|
||||
},
|
||||
valueFormatter: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<StopOrder, 'status'>) => {
|
||||
return value ? Schema.StopOrderStatusMapping[value] : '';
|
||||
},
|
||||
cellRenderer: ({
|
||||
valueFormatted,
|
||||
data,
|
||||
}: {
|
||||
valueFormatted: string;
|
||||
data: StopOrder;
|
||||
}) => (
|
||||
<span data-testid={`order-status-${data?.id}`}>{valueFormatted}</span>
|
||||
),
|
||||
minWidth: 100,
|
||||
},
|
||||
{
|
||||
field: 'submission.price',
|
||||
type: 'rightAligned',
|
||||
cellClass: 'font-mono text-right',
|
||||
valueFormatter: ({
|
||||
value,
|
||||
data,
|
||||
}: VegaValueFormatterParams<StopOrder, 'submission.price'>) => {
|
||||
if (!data) {
|
||||
return '';
|
||||
}
|
||||
if (
|
||||
!data?.market ||
|
||||
data.submission.type === Schema.OrderType.TYPE_MARKET ||
|
||||
!isNumeric(value)
|
||||
) {
|
||||
return '-';
|
||||
}
|
||||
return addDecimalsFormatNumber(value, data.market.decimalPlaces);
|
||||
},
|
||||
minWidth: 100,
|
||||
},
|
||||
{
|
||||
field: 'submission.timeInForce',
|
||||
filter: SetFilter,
|
||||
filterParams: {
|
||||
set: Schema.OrderTimeInForceMapping,
|
||||
},
|
||||
valueFormatter: ({
|
||||
value,
|
||||
}: VegaValueFormatterParams<StopOrder, 'submission.timeInForce'>) => {
|
||||
return value ? Schema.OrderTimeInForceCode[value] : '';
|
||||
},
|
||||
minWidth: 150,
|
||||
},
|
||||
{
|
||||
field: 'updatedAt',
|
||||
filter: DateRangeFilter,
|
||||
valueGetter: ({ data }: VegaValueGetterParams<StopOrder>) =>
|
||||
data?.updatedAt || data?.createdAt,
|
||||
cellRenderer: ({
|
||||
data,
|
||||
}: VegaICellRendererParams<StopOrder, 'createdAt'>) => {
|
||||
if (!data) {
|
||||
return undefined;
|
||||
}
|
||||
const value = data.updatedAt || data.createdAt;
|
||||
return (
|
||||
<span data-value={value}>
|
||||
{value ? getDateTimeFormat().format(new Date(value)) : '-'}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
minWidth: 150,
|
||||
},
|
||||
{
|
||||
colId: 'actions',
|
||||
...COL_DEFS.actions,
|
||||
minWidth: showAllActions ? 120 : COL_DEFS.actions.minWidth,
|
||||
maxWidth: showAllActions ? 120 : COL_DEFS.actions.minWidth,
|
||||
cellRenderer: ({ data }: { data?: StopOrder }) => {
|
||||
if (!data) return null;
|
||||
|
||||
return (
|
||||
<div className="flex gap-2 items-center justify-end">
|
||||
{data.status === Schema.StopOrderStatus.STATUS_PENDING &&
|
||||
!props.isReadOnly && (
|
||||
<ButtonLink
|
||||
data-testid="cancel"
|
||||
onClick={() => onCancel(data)}
|
||||
>
|
||||
{t('Cancel')}
|
||||
</ButtonLink>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
[onCancel, onMarketClick, props.isReadOnly, showAllActions]
|
||||
);
|
||||
return (
|
||||
<div className="flex gap-2 items-center justify-end">
|
||||
{data.status === Schema.StopOrderStatus.STATUS_PENDING &&
|
||||
!props.isReadOnly && (
|
||||
<ButtonLink
|
||||
data-testid="cancel"
|
||||
onClick={() => onCancel(data)}
|
||||
>
|
||||
{t('Cancel')}
|
||||
</ButtonLink>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
[onCancel, onMarketClick, props.isReadOnly, showAllActions]
|
||||
);
|
||||
|
||||
return (
|
||||
<AgGrid
|
||||
ref={ref}
|
||||
defaultColDef={{
|
||||
resizable: true,
|
||||
sortable: true,
|
||||
filterParams: { buttons: ['reset'] },
|
||||
}}
|
||||
columnDefs={columnDefs}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
}}
|
||||
getRowId={({ data }) => data.id}
|
||||
components={{ MarketNameCell }}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
)
|
||||
);
|
||||
return (
|
||||
<AgGrid
|
||||
defaultColDef={{
|
||||
resizable: true,
|
||||
sortable: true,
|
||||
filterParams: { buttons: ['reset'] },
|
||||
}}
|
||||
columnDefs={columnDefs}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
}}
|
||||
getRowId={({ data }) => data.id}
|
||||
components={{ MarketNameCell }}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -41,285 +41,329 @@ const singleRow: Position = {
|
||||
|
||||
const singleRowData = [singleRow];
|
||||
|
||||
it('should render successfully', async () => {
|
||||
await act(async () => {
|
||||
const { baseElement } = render(
|
||||
<PositionsTable rowData={[]} isReadOnly={false} />
|
||||
);
|
||||
expect(baseElement).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it('render correct columns', async () => {
|
||||
await act(async () => {
|
||||
render(<PositionsTable rowData={singleRowData} isReadOnly={true} />);
|
||||
describe('Positions', () => {
|
||||
it('should render successfully', async () => {
|
||||
await act(async () => {
|
||||
const { baseElement } = render(
|
||||
<PositionsTable rowData={[]} isReadOnly={false} />
|
||||
);
|
||||
expect(baseElement).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
const headers = screen.getAllByRole('columnheader');
|
||||
expect(headers).toHaveLength(12);
|
||||
expect(
|
||||
headers.map((h) => h.querySelector('[ref="eText"]')?.textContent?.trim())
|
||||
).toEqual([
|
||||
'Market',
|
||||
'Notional',
|
||||
'Open volume',
|
||||
'Mark price',
|
||||
'Liquidation price',
|
||||
'Settlement asset',
|
||||
'Entry price',
|
||||
'Leverage',
|
||||
'Margin allocated',
|
||||
'Realised PNL',
|
||||
'Unrealised PNL',
|
||||
'Updated',
|
||||
]);
|
||||
});
|
||||
it('render correct columns', async () => {
|
||||
await act(async () => {
|
||||
render(<PositionsTable rowData={singleRowData} isReadOnly={true} />);
|
||||
});
|
||||
|
||||
it('renders market name', async () => {
|
||||
await act(async () => {
|
||||
render(<PositionsTable rowData={singleRowData} isReadOnly={false} />);
|
||||
});
|
||||
expect(screen.getByText('ETH/BTC (31 july 2022)')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('Does not fail if the market name does not match the split pattern', async () => {
|
||||
const breakingMarketName = 'OP/USD AUG-SEP22 - Incentive';
|
||||
const row = [
|
||||
Object.assign({}, singleRow, { marketName: breakingMarketName }),
|
||||
];
|
||||
await act(async () => {
|
||||
render(<PositionsTable rowData={row} isReadOnly={false} />);
|
||||
});
|
||||
|
||||
expect(screen.getByText(breakingMarketName)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('add color and sign to amount, displays positive notional value', async () => {
|
||||
let result: RenderResult;
|
||||
await act(async () => {
|
||||
result = render(
|
||||
<PositionsTable rowData={singleRowData} isReadOnly={false} />
|
||||
);
|
||||
});
|
||||
let cells = screen.getAllByRole('gridcell');
|
||||
|
||||
expect(cells[2].classList.contains('text-market-green-600')).toBeTruthy();
|
||||
expect(cells[2].classList.contains('text-market-red')).toBeFalsy();
|
||||
expect(cells[2].textContent).toEqual('+100');
|
||||
expect(cells[1].textContent).toEqual('1,230.0');
|
||||
await act(async () => {
|
||||
result.rerender(
|
||||
<PositionsTable
|
||||
rowData={[{ ...singleRow, openVolume: '-100' }]}
|
||||
isReadOnly={false}
|
||||
/>
|
||||
);
|
||||
});
|
||||
cells = screen.getAllByRole('gridcell');
|
||||
expect(cells[2].classList.contains('text-market-green-600')).toBeFalsy();
|
||||
expect(cells[2].classList.contains('text-market-red')).toBeTruthy();
|
||||
expect(cells[2].textContent?.startsWith('-100')).toBeTruthy();
|
||||
expect(cells[1].textContent).toEqual('1,230.0');
|
||||
});
|
||||
|
||||
it('displays mark price', async () => {
|
||||
let result: RenderResult;
|
||||
await act(async () => {
|
||||
result = render(
|
||||
<PositionsTable rowData={singleRowData} isReadOnly={false} />
|
||||
);
|
||||
});
|
||||
|
||||
let cells = screen.getAllByRole('gridcell');
|
||||
expect(cells[3].textContent).toEqual('12.3');
|
||||
|
||||
await act(async () => {
|
||||
result.rerender(
|
||||
<PositionsTable
|
||||
rowData={[
|
||||
{
|
||||
...singleRow,
|
||||
marketTradingMode:
|
||||
Schema.MarketTradingMode.TRADING_MODE_OPENING_AUCTION,
|
||||
},
|
||||
]}
|
||||
isReadOnly={false}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
cells = screen.getAllByRole('gridcell');
|
||||
expect(cells[3].textContent).toEqual('-');
|
||||
});
|
||||
|
||||
it('displays liquidation price', async () => {
|
||||
await act(async () => {
|
||||
render(<PositionsTable rowData={singleRowData} isReadOnly={false} />);
|
||||
});
|
||||
const cells = screen.getAllByRole('gridcell');
|
||||
expect(cells[4].textContent).toEqual('liquidation price');
|
||||
});
|
||||
|
||||
it('displays leverage', async () => {
|
||||
await act(async () => {
|
||||
render(<PositionsTable rowData={singleRowData} isReadOnly={false} />);
|
||||
});
|
||||
const cells = screen.getAllByRole('gridcell');
|
||||
expect(cells[7].textContent).toEqual('1.1');
|
||||
});
|
||||
|
||||
it('displays allocated margin', async () => {
|
||||
await act(async () => {
|
||||
render(<PositionsTable rowData={singleRowData} isReadOnly={false} />);
|
||||
});
|
||||
const cells = screen.getAllByRole('gridcell');
|
||||
const cell = cells[8];
|
||||
expect(cell.textContent).toEqual('123,456.00');
|
||||
});
|
||||
|
||||
it('displays realised and unrealised PNL', async () => {
|
||||
// pnl cells should be rendered with asset dps
|
||||
const expectedRealised = addDecimalsFormatNumber(
|
||||
singleRow.realisedPNL,
|
||||
singleRow.decimals
|
||||
);
|
||||
const expectedUnrealised = addDecimalsFormatNumber(
|
||||
singleRow.unrealisedPNL,
|
||||
singleRow.decimals
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
render(<PositionsTable rowData={singleRowData} isReadOnly={false} />);
|
||||
});
|
||||
|
||||
const cells = screen.getAllByRole('gridcell');
|
||||
expect(cells[9].textContent).toEqual(expectedRealised);
|
||||
expect(cells[10].textContent).toEqual(expectedUnrealised);
|
||||
});
|
||||
|
||||
it('displays close button', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<PositionsTable
|
||||
rowData={singleRowData}
|
||||
pubKey={singleRowData[0].partyId}
|
||||
onClose={() => {
|
||||
return;
|
||||
}}
|
||||
isReadOnly={false}
|
||||
/>
|
||||
);
|
||||
});
|
||||
const cells = screen.getAllByRole('gridcell');
|
||||
expect(cells[12].textContent).toEqual('Close');
|
||||
});
|
||||
|
||||
it('do not display close button if openVolume is zero', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<PositionsTable
|
||||
rowData={[{ ...singleRow, openVolume: '0' }]}
|
||||
onClose={() => {
|
||||
return;
|
||||
}}
|
||||
isReadOnly={false}
|
||||
/>
|
||||
);
|
||||
});
|
||||
const cells = screen.getAllByRole('gridcell');
|
||||
expect(cells[12].textContent).toEqual('');
|
||||
});
|
||||
|
||||
describe('PNLCell', () => {
|
||||
const props = {
|
||||
data: undefined,
|
||||
valueFormatted: '100',
|
||||
};
|
||||
it('renders a dash if no data', () => {
|
||||
render(<PNLCell {...(props as ICellRendererParams)} />);
|
||||
expect(screen.getByText('-')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders value if no loss socialisation has occurred', () => {
|
||||
const props = {
|
||||
data: {
|
||||
...singleRow,
|
||||
lossSocialisationAmount: '0',
|
||||
},
|
||||
valueFormatted: '100',
|
||||
};
|
||||
render(<PNLCell {...(props as ICellRendererParams)} />);
|
||||
expect(screen.getByText(props.valueFormatted)).toBeInTheDocument();
|
||||
expect(screen.queryByRole('img')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders value with warning tooltip if loss socialisation occurred', async () => {
|
||||
const props = {
|
||||
data: {
|
||||
...singleRow,
|
||||
lossSocializationAmount: '500',
|
||||
decimals: 2,
|
||||
},
|
||||
valueFormatted: '100',
|
||||
};
|
||||
render(<PNLCell {...(props as ICellRendererParams)} />);
|
||||
const content = screen.getByText(props.valueFormatted);
|
||||
expect(content).toBeInTheDocument();
|
||||
expect(screen.getByRole('img')).toBeInTheDocument();
|
||||
|
||||
await userEvent.hover(content);
|
||||
const tooltip = await screen.findByRole('tooltip');
|
||||
expect(tooltip).toBeInTheDocument();
|
||||
const headers = screen.getAllByRole('columnheader');
|
||||
expect(headers).toHaveLength(12);
|
||||
expect(
|
||||
// using within as radix renders tooltip content twice
|
||||
within(tooltip).getByText('Lifetime loss socialisation deductions: 5.00')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('OpenVolumeCell', () => {
|
||||
const props = {
|
||||
data: undefined,
|
||||
valueFormatted: '100',
|
||||
};
|
||||
it('renders a dash if no data', () => {
|
||||
render(<OpenVolumeCell {...(props as ICellRendererParams)} />);
|
||||
expect(screen.getByText('-')).toBeInTheDocument();
|
||||
headers.map((h) => h.querySelector('[ref="eText"]')?.textContent?.trim())
|
||||
).toEqual([
|
||||
'Market',
|
||||
'Notional',
|
||||
'Open volume',
|
||||
'Mark price',
|
||||
'Liquidation price',
|
||||
'Settlement asset',
|
||||
'Entry price',
|
||||
'Leverage',
|
||||
'Margin allocated',
|
||||
'Realised PNL',
|
||||
'Unrealised PNL',
|
||||
'Updated',
|
||||
]);
|
||||
});
|
||||
|
||||
it('renders value if no status is normal', () => {
|
||||
it('renders market name', async () => {
|
||||
await act(async () => {
|
||||
render(<PositionsTable rowData={singleRowData} isReadOnly={false} />);
|
||||
});
|
||||
expect(screen.getByText('ETH/BTC (31 july 2022)')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('Does not fail if the market name does not match the split pattern', async () => {
|
||||
const breakingMarketName = 'OP/USD AUG-SEP22 - Incentive';
|
||||
const row = [
|
||||
Object.assign({}, singleRow, { marketName: breakingMarketName }),
|
||||
];
|
||||
await act(async () => {
|
||||
render(<PositionsTable rowData={row} isReadOnly={false} />);
|
||||
});
|
||||
|
||||
expect(screen.getByText(breakingMarketName)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('add color and sign to amount, displays positive notional value', async () => {
|
||||
let result: RenderResult;
|
||||
await act(async () => {
|
||||
result = render(
|
||||
<PositionsTable rowData={singleRowData} isReadOnly={false} />
|
||||
);
|
||||
});
|
||||
let cells = screen.getAllByRole('gridcell');
|
||||
|
||||
expect(cells[2].classList.contains('text-market-green-600')).toBeTruthy();
|
||||
expect(cells[2].classList.contains('text-market-red')).toBeFalsy();
|
||||
expect(cells[2].textContent).toEqual('+100');
|
||||
expect(cells[1].textContent).toEqual('1,230.0');
|
||||
await act(async () => {
|
||||
result.rerender(
|
||||
<PositionsTable
|
||||
rowData={[{ ...singleRow, openVolume: '-100' }]}
|
||||
isReadOnly={false}
|
||||
/>
|
||||
);
|
||||
});
|
||||
cells = screen.getAllByRole('gridcell');
|
||||
expect(cells[2].classList.contains('text-market-green-600')).toBeFalsy();
|
||||
expect(cells[2].classList.contains('text-market-red')).toBeTruthy();
|
||||
expect(cells[2].textContent?.startsWith('-100')).toBeTruthy();
|
||||
expect(cells[1].textContent).toEqual('1,230.0');
|
||||
});
|
||||
|
||||
it('displays mark price', async () => {
|
||||
let result: RenderResult;
|
||||
await act(async () => {
|
||||
result = render(
|
||||
<PositionsTable rowData={singleRowData} isReadOnly={false} />
|
||||
);
|
||||
});
|
||||
|
||||
let cells = screen.getAllByRole('gridcell');
|
||||
expect(cells[3].textContent).toEqual('12.3');
|
||||
|
||||
await act(async () => {
|
||||
result.rerender(
|
||||
<PositionsTable
|
||||
rowData={[
|
||||
{
|
||||
...singleRow,
|
||||
marketTradingMode:
|
||||
Schema.MarketTradingMode.TRADING_MODE_OPENING_AUCTION,
|
||||
},
|
||||
]}
|
||||
isReadOnly={false}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
cells = screen.getAllByRole('gridcell');
|
||||
expect(cells[3].textContent).toEqual('-');
|
||||
});
|
||||
|
||||
it('displays liquidation price', async () => {
|
||||
await act(async () => {
|
||||
render(<PositionsTable rowData={singleRowData} isReadOnly={false} />);
|
||||
});
|
||||
const cells = screen.getAllByRole('gridcell');
|
||||
expect(cells[4].textContent).toEqual('liquidation price');
|
||||
});
|
||||
|
||||
it('displays leverage', async () => {
|
||||
await act(async () => {
|
||||
render(<PositionsTable rowData={singleRowData} isReadOnly={false} />);
|
||||
});
|
||||
const cells = screen.getAllByRole('gridcell');
|
||||
expect(cells[7].textContent).toEqual('1.1');
|
||||
});
|
||||
|
||||
it('displays allocated margin', async () => {
|
||||
await act(async () => {
|
||||
render(<PositionsTable rowData={singleRowData} isReadOnly={false} />);
|
||||
});
|
||||
const cells = screen.getAllByRole('gridcell');
|
||||
const cell = cells[8];
|
||||
expect(cell.textContent).toEqual('123,456.00');
|
||||
});
|
||||
|
||||
it('displays realised and unrealised PNL', async () => {
|
||||
// pnl cells should be rendered with asset dps
|
||||
const expectedRealised = addDecimalsFormatNumber(
|
||||
singleRow.realisedPNL,
|
||||
singleRow.decimals
|
||||
);
|
||||
const expectedUnrealised = addDecimalsFormatNumber(
|
||||
singleRow.unrealisedPNL,
|
||||
singleRow.decimals
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
render(<PositionsTable rowData={singleRowData} isReadOnly={false} />);
|
||||
});
|
||||
|
||||
const cells = screen.getAllByRole('gridcell');
|
||||
expect(cells[9].textContent).toEqual(expectedRealised);
|
||||
expect(cells[10].textContent).toEqual(expectedUnrealised);
|
||||
});
|
||||
|
||||
it('displays close button', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<PositionsTable
|
||||
rowData={singleRowData}
|
||||
pubKey={singleRowData[0].partyId}
|
||||
onClose={() => {
|
||||
return;
|
||||
}}
|
||||
isReadOnly={false}
|
||||
/>
|
||||
);
|
||||
});
|
||||
const cells = screen.getAllByRole('gridcell');
|
||||
expect(cells[12].textContent).toEqual('Close');
|
||||
});
|
||||
|
||||
it('do not display close button if openVolume is zero', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<PositionsTable
|
||||
rowData={[{ ...singleRow, openVolume: '0' }]}
|
||||
onClose={() => {
|
||||
return;
|
||||
}}
|
||||
isReadOnly={false}
|
||||
/>
|
||||
);
|
||||
});
|
||||
const cells = screen.getAllByRole('gridcell');
|
||||
expect(cells[12].textContent).toEqual('');
|
||||
});
|
||||
|
||||
describe('PNLCell', () => {
|
||||
const props = {
|
||||
data: {
|
||||
...singleRow,
|
||||
status: PositionStatus.POSITION_STATUS_UNSPECIFIED,
|
||||
},
|
||||
data: undefined,
|
||||
valueFormatted: '100',
|
||||
};
|
||||
render(<OpenVolumeCell {...(props as ICellRendererParams)} />);
|
||||
expect(screen.getByText(props.valueFormatted)).toBeInTheDocument();
|
||||
expect(screen.queryByRole('img')).not.toBeInTheDocument();
|
||||
it('renders a dash if no data', () => {
|
||||
render(<PNLCell {...(props as ICellRendererParams)} />);
|
||||
expect(screen.getByText('-')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders value if no loss socialisation has occurred', () => {
|
||||
const props = {
|
||||
data: {
|
||||
...singleRow,
|
||||
lossSocialisationAmount: '0',
|
||||
},
|
||||
valueFormatted: '100',
|
||||
};
|
||||
render(<PNLCell {...(props as ICellRendererParams)} />);
|
||||
expect(screen.getByText(props.valueFormatted)).toBeInTheDocument();
|
||||
expect(screen.queryByRole('img')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders value with warning tooltip if loss socialisation occurred', async () => {
|
||||
const props = {
|
||||
data: {
|
||||
...singleRow,
|
||||
lossSocializationAmount: '500',
|
||||
decimals: 2,
|
||||
},
|
||||
valueFormatted: '100',
|
||||
};
|
||||
render(<PNLCell {...(props as ICellRendererParams)} />);
|
||||
const content = screen.getByText(props.valueFormatted);
|
||||
expect(content).toBeInTheDocument();
|
||||
expect(screen.getByRole('img')).toBeInTheDocument();
|
||||
|
||||
await userEvent.hover(content);
|
||||
const tooltip = await screen.findByRole('tooltip');
|
||||
expect(tooltip).toBeInTheDocument();
|
||||
expect(
|
||||
// using within as radix renders tooltip content twice
|
||||
within(tooltip).getByText(
|
||||
'Lifetime loss socialisation deductions: 5.00'
|
||||
)
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
within(tooltip).getByText(
|
||||
`You received less BTC in gains that you should have when the market moved in your favour. This occurred because one or more other trader(s) were closed out and did not have enough funds to cover their losses, and the market's insurance pool was empty.`
|
||||
)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders status with warning tooltip if not normal', async () => {
|
||||
describe('OpenVolumeCell', () => {
|
||||
const props = {
|
||||
data: {
|
||||
...singleRow,
|
||||
status: PositionStatus.POSITION_STATUS_ORDERS_CLOSED,
|
||||
},
|
||||
data: undefined,
|
||||
valueFormatted: '100',
|
||||
};
|
||||
render(<OpenVolumeCell {...(props as ICellRendererParams)} />);
|
||||
const content = screen.getByText(props.valueFormatted);
|
||||
expect(content).toBeInTheDocument();
|
||||
expect(screen.getByRole('img')).toBeInTheDocument();
|
||||
await userEvent.hover(content);
|
||||
const tooltip = await screen.findByRole('tooltip');
|
||||
expect(tooltip).toBeInTheDocument();
|
||||
expect(
|
||||
// using within as radix renders tooltip content twice
|
||||
within(tooltip).getByText(
|
||||
`Status: ${PositionStatusMapping[props.data.status]}`
|
||||
)
|
||||
).toBeInTheDocument();
|
||||
it('renders a dash if no data', () => {
|
||||
render(<OpenVolumeCell {...(props as ICellRendererParams)} />);
|
||||
expect(screen.getByText('-')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders value if no status is normal', () => {
|
||||
const props = {
|
||||
data: {
|
||||
...singleRow,
|
||||
status: PositionStatus.POSITION_STATUS_UNSPECIFIED,
|
||||
},
|
||||
valueFormatted: '100',
|
||||
};
|
||||
render(<OpenVolumeCell {...(props as ICellRendererParams)} />);
|
||||
expect(screen.getByText(props.valueFormatted)).toBeInTheDocument();
|
||||
expect(screen.queryByRole('img')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders status with warning tooltip if orders were closed', async () => {
|
||||
const props = {
|
||||
data: {
|
||||
...singleRow,
|
||||
status: PositionStatus.POSITION_STATUS_ORDERS_CLOSED,
|
||||
},
|
||||
valueFormatted: '100',
|
||||
};
|
||||
render(<OpenVolumeCell {...(props as ICellRendererParams)} />);
|
||||
const content = screen.getByText(props.valueFormatted);
|
||||
expect(content).toBeInTheDocument();
|
||||
expect(screen.getByRole('img')).toBeInTheDocument();
|
||||
await userEvent.hover(content);
|
||||
const tooltip = await screen.findByRole('tooltip');
|
||||
expect(tooltip).toBeInTheDocument();
|
||||
expect(
|
||||
// using within as radix renders tooltip content twice
|
||||
within(tooltip).getByText(
|
||||
`Status: ${PositionStatusMapping[props.data.status]}`
|
||||
)
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
// using within as radix renders tooltip content twice
|
||||
within(tooltip).getByText(
|
||||
'The position was distressed, but removing open orders from the book brought the margin level back to a point where the open position could be maintained.'
|
||||
)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders status with warning tooltip if position was closed out', async () => {
|
||||
const props = {
|
||||
data: {
|
||||
...singleRow,
|
||||
status: PositionStatus.POSITION_STATUS_CLOSED_OUT,
|
||||
},
|
||||
valueFormatted: '100',
|
||||
};
|
||||
render(<OpenVolumeCell {...(props as ICellRendererParams)} />);
|
||||
const content = screen.getByText(props.valueFormatted);
|
||||
expect(content).toBeInTheDocument();
|
||||
expect(screen.getByRole('img')).toBeInTheDocument();
|
||||
await userEvent.hover(content);
|
||||
const tooltip = await screen.findByRole('tooltip');
|
||||
expect(tooltip).toBeInTheDocument();
|
||||
expect(
|
||||
// using within as radix renders tooltip content twice
|
||||
within(tooltip).getByText(
|
||||
`Status: ${PositionStatusMapping[props.data.status]}`
|
||||
)
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
// using within as radix renders tooltip content twice
|
||||
within(tooltip).getByText(
|
||||
'You did not have enough BTC collateral to meet the maintenance margin requirements for your position, so it was closed by the network.'
|
||||
)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user