Compare commits
23
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cea19a1197 | ||
|
|
c7e4b24c9c | ||
|
|
b5ba4f99d9 | ||
|
|
66f25603d3 | ||
|
|
4afd469404 | ||
|
|
9e5bc9c8d1 | ||
|
|
e41aff88b1 | ||
|
|
9209074332 | ||
|
|
39e5836edb | ||
|
|
85a4981700 | ||
|
|
247927e939 | ||
|
|
6523490d96 | ||
|
|
105a758e8d | ||
|
|
559ef48d6d | ||
|
|
255c3752f2 | ||
|
|
2bbf1e2b81 | ||
|
|
06d3ef8d73 | ||
|
|
de4c7926c2 | ||
|
|
50959b4c50 | ||
|
|
a8c2f4e025 | ||
|
|
2cea73c567 | ||
|
|
b0a54be408 | ||
|
|
d1a036e53c |
@@ -1,6 +1,6 @@
|
||||
# Related issues 🔗
|
||||
|
||||
Closes #[Issue number here]
|
||||
Issue: #[Issue number here]
|
||||
|
||||
# Description ℹ️
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ on:
|
||||
- types
|
||||
- utils
|
||||
- i18n
|
||||
- wallet
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
|
||||
@@ -3,6 +3,3 @@
|
||||
|
||||
# Lint commit messages to ensure they follow conventional commit standards
|
||||
yarn commitlint --edit "${1}"
|
||||
|
||||
# Lint all staged files
|
||||
yarn lint-staged
|
||||
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
# Lint all staged files
|
||||
yarn lint-staged
|
||||
Executable
+8
@@ -0,0 +1,8 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
# Lint all staged files
|
||||
yarn nx format:check
|
||||
|
||||
# Test all projects with changes
|
||||
yarn nx affected -t test --exclude trading
|
||||
@@ -0,0 +1,68 @@
|
||||
context('Oracle page', { tags: '@smoke' }, () => {
|
||||
describe('Verify elements on page', () => {
|
||||
before('create market and navigate to oracle page', () => {
|
||||
cy.createMarket();
|
||||
cy.visit('/oracles');
|
||||
});
|
||||
it('should see oracle data', () => {
|
||||
cy.getByTestId('oracle-details').should('have.length.at.least', 2);
|
||||
cy.getByTestId('oracle-details')
|
||||
.should('exist')
|
||||
.eq(0)
|
||||
.within(() => {
|
||||
cy.get('tr')
|
||||
.eq(0)
|
||||
.within(() => {
|
||||
cy.get('th').should('have.text', 'ID');
|
||||
cy.get('a').invoke('text').should('have.length', 64);
|
||||
cy.get('a')
|
||||
.should('have.attr', 'href')
|
||||
.and('contain', '/oracles/');
|
||||
});
|
||||
cy.get('tr')
|
||||
.eq(1)
|
||||
.within(() => {
|
||||
cy.get('th').should('have.text', 'Type');
|
||||
cy.get('td').should('have.text', 'External data');
|
||||
});
|
||||
cy.get('tr')
|
||||
.eq(2)
|
||||
.within(() => {
|
||||
cy.get('th').should('have.text', 'Signer');
|
||||
cy.getByTestId('keytype').should('have.text', 'Vega');
|
||||
cy.get('a').invoke('text').should('have.length', 64);
|
||||
cy.get('a')
|
||||
.should('have.attr', 'href')
|
||||
.and('contain', '/parties/');
|
||||
});
|
||||
cy.get('tr')
|
||||
.eq(3)
|
||||
.within(() => {
|
||||
cy.get('th').should('have.text', 'Settlement for');
|
||||
cy.get('a').invoke('text').should('have.length', 64);
|
||||
cy.get('a')
|
||||
.should('have.attr', 'href')
|
||||
.and('contain', '/markets/');
|
||||
});
|
||||
cy.get('tr')
|
||||
.eq(4)
|
||||
.within(() => {
|
||||
cy.get('th').should('have.text', 'Matched data');
|
||||
cy.get('td').should('have.text', '❌');
|
||||
});
|
||||
cy.get('details')
|
||||
.eq(0)
|
||||
.within(() => {
|
||||
cy.contains('Filter').click();
|
||||
cy.get('.language-json').should('exist');
|
||||
});
|
||||
cy.get('details')
|
||||
.eq(1)
|
||||
.within(() => {
|
||||
cy.contains('JSON').click();
|
||||
cy.get('.language-json').should('exist');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,10 +1,12 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import type { components } from '../../../../../types/explorer';
|
||||
import { Tooltip } from '@vegaprotocol/ui-toolkit';
|
||||
import SizeInMarket from '../../../size-in-market/size-in-market';
|
||||
|
||||
export interface TxDetailsOrderIcebergDetailsProps {
|
||||
iceberg: components['schemas']['v1IcebergOpts'];
|
||||
size: components['schemas']['v1OrderSubmission']['size'];
|
||||
marketId?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -28,6 +30,7 @@ export interface TxDetailsOrderIcebergDetailsProps {
|
||||
export const TxOrderIcebergDetails = ({
|
||||
iceberg,
|
||||
size,
|
||||
marketId,
|
||||
}: TxDetailsOrderIcebergDetailsProps) => {
|
||||
return (
|
||||
<div
|
||||
@@ -36,15 +39,28 @@ export const TxOrderIcebergDetails = ({
|
||||
>
|
||||
<Tooltip description={t('Iceberg: Minimum visible size')}>
|
||||
<span className="align-bottom text-vega-orange-650">
|
||||
{iceberg.minimumVisibleSize || '-'}
|
||||
{marketId ? (
|
||||
<SizeInMarket
|
||||
size={iceberg.minimumVisibleSize}
|
||||
marketId={marketId}
|
||||
/>
|
||||
) : (
|
||||
iceberg.minimumVisibleSize
|
||||
)}
|
||||
</span>
|
||||
</Tooltip>
|
||||
<Tooltip description={t('Iceberg: Total size')}>
|
||||
<span className="text-sm text-vega-blue-600 mx-3">{size}</span>
|
||||
<span className="text-sm text-vega-blue-600 mx-3">
|
||||
{marketId ? <SizeInMarket size={size} marketId={marketId} /> : size}
|
||||
</span>
|
||||
</Tooltip>
|
||||
<Tooltip description={t('Iceberg: Visible peak')}>
|
||||
<span className="align-top text-vega-yellow-600">
|
||||
{iceberg.peakSize || '-'}
|
||||
{marketId ? (
|
||||
<SizeInMarket size={iceberg.peakSize} marketId={marketId} />
|
||||
) : (
|
||||
iceberg.peakSize
|
||||
)}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
@@ -81,7 +81,11 @@ export const TxDetailsOrder = ({
|
||||
<TableRow modifier="bordered">
|
||||
<TableCell>{t('Iceberg details')}</TableCell>
|
||||
<TableCell>
|
||||
<TxOrderIcebergDetails iceberg={iceberg} size={size} />
|
||||
<TxOrderIcebergDetails
|
||||
iceberg={iceberg}
|
||||
size={size}
|
||||
marketId={marketId}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : null}
|
||||
|
||||
@@ -38,7 +38,12 @@ const Oracles = () => {
|
||||
const dataConnection = o?.node.dataConnection;
|
||||
|
||||
return (
|
||||
<div id={id} key={id} className="mb-10">
|
||||
<div
|
||||
id={id}
|
||||
key={id}
|
||||
className="mb-10"
|
||||
data-testid="oracle-details"
|
||||
>
|
||||
<OracleDetails
|
||||
id={id}
|
||||
dataSource={o?.node}
|
||||
|
||||
@@ -20,6 +20,10 @@ NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
|
||||
NX_VEGA_REST_URL=http://localhost:3008/api/v2/
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
|
||||
NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
|
||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/pl/firefox/addon/vega-wallet
|
||||
|
||||
#Test configuration variables
|
||||
CYPRESS_FAIRGROUND=false
|
||||
CYPRESS_VEGA_URL=http://localhost:3008/graphql
|
||||
|
||||
@@ -22,6 +22,8 @@ NX_VEGA_REST_URL=https://api.n00.stagnet1.vega.xyz/api/v2/
|
||||
NX_TENDERMINT_URL=https://tm.n01.stagnet1.vega.rocks
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.stagnet1.vega.xyz/websocket
|
||||
|
||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-fairground
|
||||
|
||||
#Test configuration variables
|
||||
CYPRESS_FAIRGROUND=false
|
||||
@@ -29,4 +31,4 @@ LC_ALL="en_US.UTF-8"
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
NX_METAMASK_SNAPS=true
|
||||
NX_METAMASK_SNAPS=true
|
||||
|
||||
@@ -20,7 +20,7 @@ NX_TRANCHES_SERVICE_URL=https://tranches-stagnet1-k8s.ops.vega.xyz
|
||||
NX_VEGA_REST_URL=http://localhost:3008/api/v2/
|
||||
|
||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/pl/firefox/addon/vega-wallet
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-fairground
|
||||
|
||||
NX_TENDERMINT_URL=http://localhost:26617
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://localhost:26617/websocket
|
||||
@@ -30,4 +30,4 @@ CYPRESS_FAIRGROUND=false
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=false
|
||||
NX_METAMASK_SNAPS=false
|
||||
NX_METAMASK_SNAPS=false
|
||||
|
||||
@@ -15,11 +15,11 @@ NX_VEGA_REST_URL=https://api.n00.devnet1.vega.xyz/api/v2/
|
||||
NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40@o286262.ingest.sentry.io/5882996
|
||||
|
||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/pl/firefox/addon/vega-wallet
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-fairground
|
||||
|
||||
NX_TENDERMINT_URL=https://tm.be.devnet1.vega.xyz/
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.devnet1.vega.xyz/websocket
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
NX_METAMASK_SNAPS=true
|
||||
NX_METAMASK_SNAPS=true
|
||||
|
||||
@@ -15,7 +15,7 @@ NX_TRANCHES_SERVICE_URL=https://tranches-mainnet-k8s.ops.vega.xyz
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/mainnet/announcements.json
|
||||
NX_VEGA_REST_URL=https://api.vega.community/api/v2/
|
||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/pl/firefox/addon/vega-wallet
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-mainnet
|
||||
|
||||
NX_TENDERMINT_URL=https://be.vega.community
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.vega.community/websocket
|
||||
|
||||
@@ -14,7 +14,7 @@ NX_TRANCHES_SERVICE_URL=https://tranches-mainnet-mirror-k8s.ops.vega.xyz
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/mainnet/announcements.json
|
||||
NX_VEGA_REST_URL=https://api.mainnet-mirror.vega.rocks/api/v2/
|
||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/pl/firefox/addon/vega-wallet
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-mainnet
|
||||
|
||||
NX_TENDERMINT_URL=https://be.mainnet-mirror.vega.rocks
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.mainnet-mirror.vega.rocks/websocket
|
||||
|
||||
@@ -11,7 +11,7 @@ NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/annou
|
||||
NX_VEGA_REST_URL=https://api.n00.stagnet1.vega.xyz/api/v2/
|
||||
|
||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/pl/firefox/addon/vega-wallet
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-fairground
|
||||
|
||||
NX_TENDERMINT_URL=https://tm.n01.stagnet1.vega.rocks
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.stagnet1.vega.xyz/websocket
|
||||
|
||||
@@ -16,7 +16,7 @@ NX_VEGA_REST_URL=https://api.n07.testnet.vega.xyz/api/v2/
|
||||
NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40@o286262.ingest.sentry.io/5882996
|
||||
|
||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/pl/firefox/addon/vega-wallet
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-fairground
|
||||
|
||||
NX_TENDERMINT_URL=https://tm.be.testnet.vega.xyz
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.testnet.vega.xyz/websocket
|
||||
|
||||
@@ -13,7 +13,7 @@ NX_VEGA_REST_URL=https://api-validators-testnet.vega.rocks/api/v2/
|
||||
NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40@o286262.ingest.sentry.io/5882996
|
||||
|
||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/pl/firefox/addon/vega-wallet
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-fairground
|
||||
|
||||
NX_TENDERMINT_URL=https://tm.be.validators-testnet.vega.rocks
|
||||
NX_TENDERMINT_WEBSOCKET_URL=wss://be.validators-testnet.vega.
|
||||
|
||||
@@ -41,6 +41,7 @@ import {
|
||||
AppFailure,
|
||||
NodeSwitcherDialog,
|
||||
useNodeSwitcherStore,
|
||||
DocsLinks,
|
||||
} from '@vegaprotocol/environment';
|
||||
import { ENV } from './config';
|
||||
import type { InMemoryCacheConfig } from '@apollo/client';
|
||||
@@ -109,8 +110,17 @@ const Web3Container = ({
|
||||
store.connectors,
|
||||
store.initialize,
|
||||
]);
|
||||
const { ETHEREUM_PROVIDER_URL, ETH_LOCAL_PROVIDER_URL, ETH_WALLET_MNEMONIC } =
|
||||
useEnvironment();
|
||||
const {
|
||||
ETHEREUM_PROVIDER_URL,
|
||||
ETH_LOCAL_PROVIDER_URL,
|
||||
ETH_WALLET_MNEMONIC,
|
||||
VEGA_ENV,
|
||||
VEGA_URL,
|
||||
VEGA_EXPLORER_URL,
|
||||
CHROME_EXTENSION_URL,
|
||||
MOZILLA_EXTENSION_URL,
|
||||
VEGA_WALLET_URL,
|
||||
} = useEnvironment();
|
||||
useEffect(() => {
|
||||
if (chainId) {
|
||||
return initializeConnectors(
|
||||
@@ -139,10 +149,33 @@ const Web3Container = ({
|
||||
return <SplashLoader />;
|
||||
}
|
||||
|
||||
if (
|
||||
!VEGA_URL ||
|
||||
!VEGA_WALLET_URL ||
|
||||
!VEGA_EXPLORER_URL ||
|
||||
!DocsLinks ||
|
||||
!CHROME_EXTENSION_URL ||
|
||||
!MOZILLA_EXTENSION_URL
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Web3Provider connectors={connectors}>
|
||||
<Web3Connector connectors={connectors} chainId={Number(chainId)}>
|
||||
<VegaWalletProvider>
|
||||
<VegaWalletProvider
|
||||
config={{
|
||||
network: VEGA_ENV,
|
||||
vegaUrl: VEGA_URL,
|
||||
vegaWalletServiceUrl: VEGA_WALLET_URL,
|
||||
links: {
|
||||
explorer: VEGA_EXPLORER_URL,
|
||||
concepts: DocsLinks?.VEGA_WALLET_CONCEPTS_URL,
|
||||
chromeExtensionUrl: CHROME_EXTENSION_URL,
|
||||
mozillaExtensionUrl: MOZILLA_EXTENSION_URL,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ContractsProvider>
|
||||
<AppLoader>
|
||||
<BalanceManager>
|
||||
@@ -275,7 +308,7 @@ const AppContainer = () => {
|
||||
<Router>
|
||||
<ScrollToTop />
|
||||
<AppStateProvider>
|
||||
<div className="grid min-h-full text-white">
|
||||
<div className="min-h-full text-white grid">
|
||||
<NodeGuard
|
||||
skeleton={<div>{t('Loading')}</div>}
|
||||
failure={
|
||||
|
||||
@@ -54,7 +54,7 @@ export const WalletCardRow = ({
|
||||
}) => {
|
||||
const ref = React.useRef<HTMLDivElement | null>(null);
|
||||
useAnimateValue(ref, value);
|
||||
const [integers, decimalsPlaces] = useNumberParts(value, decimals);
|
||||
const [integers, decimalsPlaces, separator] = useNumberParts(value, decimals);
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -75,7 +75,10 @@ export const WalletCardRow = ({
|
||||
className="font-mono flex-1 text-right"
|
||||
data-testid="associated-amount"
|
||||
>
|
||||
<span>{integers}.</span>
|
||||
<span>
|
||||
{integers}
|
||||
{separator}
|
||||
</span>
|
||||
<span>{decimalsPlaces}</span>
|
||||
</span>
|
||||
)}
|
||||
@@ -110,7 +113,10 @@ export const WalletCardAsset = ({
|
||||
border,
|
||||
subheading,
|
||||
}: WalletCardAssetProps) => {
|
||||
const [integers, decimalsPlaces] = useNumberParts(balance, decimals);
|
||||
const [integers, decimalsPlaces, separator] = useNumberParts(
|
||||
balance,
|
||||
decimals
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex flex-nowrap mt-2 mb-4">
|
||||
@@ -132,7 +138,10 @@ export const WalletCardAsset = ({
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-2 basis-full font-mono" data-testid="currency-value">
|
||||
<span>{integers}.</span>
|
||||
<span>
|
||||
{integers}
|
||||
{separator}
|
||||
</span>
|
||||
<span className="text-neutral-400">{decimalsPlaces}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ENV } from '@vegaprotocol/environment';
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
import {
|
||||
JsonRpcConnector,
|
||||
ViewConnector,
|
||||
@@ -13,10 +13,9 @@ export const jsonRpc = new JsonRpcConnector();
|
||||
export const injected = new InjectedConnector();
|
||||
export const view = new ViewConnector(urlParams.get('address'));
|
||||
|
||||
export const snap = new SnapConnector(
|
||||
ENV.VEGA_URL ? new URL(ENV.VEGA_URL).origin : undefined,
|
||||
DEFAULT_SNAP_ID
|
||||
);
|
||||
export const snap = FLAGS.METAMASK_SNAPS
|
||||
? new SnapConnector(DEFAULT_SNAP_ID)
|
||||
: undefined;
|
||||
|
||||
export const Connectors = {
|
||||
injected,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { VegaWalletProvider } from '@vegaprotocol/wallet';
|
||||
import type { VegaWalletConfig } from '@vegaprotocol/wallet';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { generateProposal } from '../../test-helpers/generate-proposals';
|
||||
import { Proposal } from './proposal';
|
||||
@@ -43,11 +44,23 @@ jest.mock('../list-asset', () => ({
|
||||
ListAsset: () => <div data-testid="proposal-list-asset"></div>,
|
||||
}));
|
||||
|
||||
const vegaWalletConfig: VegaWalletConfig = {
|
||||
network: 'TESTNET',
|
||||
vegaUrl: 'https://vega.xyz',
|
||||
vegaWalletServiceUrl: 'https://wallet.vega.xyz',
|
||||
links: {
|
||||
explorer: 'explorer',
|
||||
concepts: 'concepts',
|
||||
chromeExtensionUrl: 'chrome',
|
||||
mozillaExtensionUrl: 'mozilla',
|
||||
},
|
||||
};
|
||||
|
||||
const renderComponent = (proposal: ProposalQuery['proposal']) => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<MockedProvider>
|
||||
<VegaWalletProvider>
|
||||
<VegaWalletProvider config={vegaWalletConfig}>
|
||||
<Proposal
|
||||
restData={{}}
|
||||
proposal={proposal as ProposalQuery['proposal']}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { VoteButtons } from './vote-buttons';
|
||||
import { VoteState } from './use-user-vote';
|
||||
import { ProposalState } from '@vegaprotocol/types';
|
||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
|
||||
import { mockWalletContext } from '../../test-helpers/mocks';
|
||||
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
|
||||
import { MockedProvider } from '@apollo/react-testing';
|
||||
@@ -67,7 +68,7 @@ describe('Vote buttons', () => {
|
||||
disconnect: jest.fn(),
|
||||
selectPubKey: jest.fn(),
|
||||
connector: null,
|
||||
};
|
||||
} as unknown as VegaWalletContextShape;
|
||||
|
||||
render(
|
||||
<AppStateProvider>
|
||||
|
||||
@@ -114,6 +114,7 @@ describe('Raw proposal form', () => {
|
||||
{
|
||||
pubKey,
|
||||
sendTx: mockSendTx,
|
||||
links: { explorer: 'explorer' },
|
||||
} as unknown as VegaWalletContextShape
|
||||
}
|
||||
>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { NetworkParamsDocument } from '@vegaprotocol/network-parameters';
|
||||
import type { MockedResponse } from '@apollo/client/testing';
|
||||
import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters';
|
||||
import type { PubKey } from '@vegaprotocol/wallet';
|
||||
import type { PubKey, VegaWalletContextShape } from '@vegaprotocol/wallet';
|
||||
import type { VoteValue } from '@vegaprotocol/types';
|
||||
import type { UserVoteQuery } from '../components/vote-details/__generated__/Vote';
|
||||
import { UserVoteDocument } from '../components/vote-details/__generated__/Vote';
|
||||
@@ -21,7 +21,7 @@ export const mockWalletContext = {
|
||||
disconnect: jest.fn(),
|
||||
selectPubKey: jest.fn(),
|
||||
connector: null,
|
||||
};
|
||||
} as unknown as VegaWalletContextShape;
|
||||
|
||||
const mockEthereumConfig = {
|
||||
network_id: '3',
|
||||
|
||||
@@ -1,475 +0,0 @@
|
||||
import { aliasGQLQuery } from '@vegaprotocol/cypress';
|
||||
import type { DataSourceDefinition } from '@vegaprotocol/types';
|
||||
import {
|
||||
MarketState,
|
||||
MarketStateMapping,
|
||||
PropertyKeyType,
|
||||
} from '@vegaprotocol/types';
|
||||
import { addDays, subDays } from 'date-fns';
|
||||
import {
|
||||
chainIdQuery,
|
||||
statisticsQuery,
|
||||
createDataConnection,
|
||||
oracleSpecDataConnectionQuery,
|
||||
createMarketFragment,
|
||||
marketsQuery,
|
||||
marketsDataQuery,
|
||||
createMarketsDataFragment,
|
||||
assetQuery,
|
||||
networkParamsQuery,
|
||||
nodeGuardQuery,
|
||||
} from '@vegaprotocol/mock';
|
||||
import {
|
||||
addDecimalsFormatNumber,
|
||||
getDateTimeFormat,
|
||||
} from '@vegaprotocol/utils';
|
||||
|
||||
describe('Closed markets', { tags: '@smoke' }, () => {
|
||||
const settlementDataProperty = 'settlement-data-property';
|
||||
const settlementDataPropertyKey = {
|
||||
__typename: 'PropertyKey' as const,
|
||||
name: settlementDataProperty,
|
||||
type: PropertyKeyType.TYPE_INTEGER,
|
||||
numberDecimalPlaces: 2,
|
||||
};
|
||||
const settlementDataSourceData: DataSourceDefinition = {
|
||||
sourceType: {
|
||||
sourceType: {
|
||||
filters: [
|
||||
{
|
||||
__typename: 'Filter',
|
||||
key: settlementDataPropertyKey,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
const rowSelector =
|
||||
'[data-testid="tab-closed-markets"] .ag-center-cols-container .ag-row';
|
||||
|
||||
const assetsResult = assetQuery();
|
||||
// @ts-ignore asset definitely exists
|
||||
const settlementAsset = assetsResult.assetsConnection.edges[0].node;
|
||||
|
||||
const settledMarket = createMarketFragment({
|
||||
id: '0',
|
||||
state: MarketState.STATE_SETTLED,
|
||||
marketTimestamps: {
|
||||
open: subDays(new Date(), 10).toISOString(),
|
||||
close: subDays(new Date(), 4).toISOString(),
|
||||
},
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
product: {
|
||||
dataSourceSpecBinding: {
|
||||
settlementDataProperty,
|
||||
},
|
||||
dataSourceSpecForTradingTermination: {
|
||||
id: 'market-1-trading-termination-oracle-id',
|
||||
},
|
||||
dataSourceSpecForSettlementData: {
|
||||
id: 'market-1-settlement-data-oracle-id',
|
||||
data: settlementDataSourceData,
|
||||
},
|
||||
settlementAsset,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const terminatedMarket = createMarketFragment({
|
||||
id: '1',
|
||||
state: MarketState.STATE_TRADING_TERMINATED,
|
||||
marketTimestamps: {
|
||||
open: subDays(new Date(), 10).toISOString(),
|
||||
close: null, // market
|
||||
},
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
metadata: {
|
||||
tags: [
|
||||
`settlement-expiry-date:${addDays(new Date(), 4).toISOString()}`,
|
||||
],
|
||||
},
|
||||
product: {
|
||||
dataSourceSpecBinding: {
|
||||
settlementDataProperty,
|
||||
},
|
||||
dataSourceSpecForSettlementData: {
|
||||
id: 'market-1-settlement-data-oracle-id',
|
||||
data: settlementDataSourceData,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const delayedSettledMarket = createMarketFragment({
|
||||
id: '2',
|
||||
state: MarketState.STATE_TRADING_TERMINATED,
|
||||
marketTimestamps: {
|
||||
open: subDays(new Date(), 10).toISOString(),
|
||||
close: null, // market
|
||||
},
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
metadata: {
|
||||
tags: [
|
||||
`settlement-expiry-date:${subDays(new Date(), 2).toISOString()}`,
|
||||
],
|
||||
},
|
||||
product: {
|
||||
dataSourceSpecBinding: {
|
||||
settlementDataProperty,
|
||||
},
|
||||
dataSourceSpecForSettlementData: {
|
||||
id: 'market-1-settlement-data-oracle-id',
|
||||
data: settlementDataSourceData,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const unknownMarket = createMarketFragment({
|
||||
id: '3',
|
||||
state: MarketState.STATE_SETTLED,
|
||||
});
|
||||
|
||||
const closedMarketsResult = [
|
||||
{
|
||||
node: settledMarket,
|
||||
},
|
||||
{
|
||||
node: terminatedMarket,
|
||||
},
|
||||
{
|
||||
node: delayedSettledMarket,
|
||||
},
|
||||
{ node: unknownMarket },
|
||||
{
|
||||
node: createMarketFragment({ id: '4', state: MarketState.STATE_PENDING }),
|
||||
},
|
||||
{
|
||||
node: createMarketFragment({ id: '5', state: MarketState.STATE_ACTIVE }),
|
||||
},
|
||||
];
|
||||
|
||||
const settledMarketData = createMarketsDataFragment({
|
||||
market: {
|
||||
id: settledMarket.id,
|
||||
},
|
||||
bestBidPrice: '1000',
|
||||
bestOfferPrice: '2000',
|
||||
markPrice: '1500',
|
||||
});
|
||||
|
||||
const closedMarketsDataResult = [
|
||||
{
|
||||
node: {
|
||||
data: settledMarketData,
|
||||
},
|
||||
},
|
||||
{
|
||||
node: {
|
||||
data: createMarketsDataFragment({
|
||||
market: {
|
||||
id: terminatedMarket.id,
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
node: {
|
||||
data: createMarketsDataFragment({
|
||||
market: {
|
||||
id: delayedSettledMarket.id,
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
node: {
|
||||
data: createMarketsDataFragment({
|
||||
market: {
|
||||
id: unknownMarket.id,
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const specDataConnection = createDataConnection();
|
||||
|
||||
before(() => {
|
||||
cy.setOnBoardingViewed();
|
||||
cy.mockGQL((req) => {
|
||||
aliasGQLQuery(req, 'ChainId', chainIdQuery());
|
||||
aliasGQLQuery(req, 'Statistics', statisticsQuery());
|
||||
aliasGQLQuery(req, 'NodeGuard', nodeGuardQuery());
|
||||
aliasGQLQuery(req, 'NetworkParams', networkParamsQuery());
|
||||
aliasGQLQuery(
|
||||
req,
|
||||
'Markets',
|
||||
marketsQuery({
|
||||
marketsConnection: {
|
||||
edges: closedMarketsResult,
|
||||
},
|
||||
})
|
||||
);
|
||||
aliasGQLQuery(
|
||||
req,
|
||||
'MarketsData',
|
||||
marketsDataQuery({
|
||||
marketsConnection: {
|
||||
edges: closedMarketsDataResult,
|
||||
},
|
||||
})
|
||||
);
|
||||
aliasGQLQuery(
|
||||
req,
|
||||
'OracleSpecDataConnection',
|
||||
oracleSpecDataConnectionQuery()
|
||||
);
|
||||
});
|
||||
|
||||
cy.mockSubscription();
|
||||
|
||||
cy.visit('/#/markets/all');
|
||||
cy.get('[data-testid="Closed markets"]').click();
|
||||
});
|
||||
|
||||
it('renders a settled market', () => {
|
||||
const expectedMarkets = closedMarketsResult.filter((edge) => {
|
||||
return [
|
||||
MarketState.STATE_SETTLED,
|
||||
MarketState.STATE_TRADING_TERMINATED,
|
||||
].includes(edge.node.state);
|
||||
});
|
||||
const product = settledMarket.tradableInstrument.instrument.product;
|
||||
|
||||
// rows should be filtered to only include settled/terminated markets
|
||||
cy.get(rowSelector).should('have.length', expectedMarkets.length);
|
||||
|
||||
// check each column in the first row renders correctly
|
||||
// 6001-MARK-001
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find('[col-id="code"]')
|
||||
.find('[data-testid="market-code"]')
|
||||
.should('have.text', settledMarket.tradableInstrument.instrument.code);
|
||||
|
||||
// 6001-MARK-071
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find('[title="Future"]')
|
||||
.should('have.text', 'Futr');
|
||||
|
||||
// 6001-MARK-002
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find('[col-id="name"]')
|
||||
.should('have.text', settledMarket.tradableInstrument.instrument.name);
|
||||
|
||||
// 6001-MARK-003
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find('[col-id="state"]')
|
||||
.should('have.text', MarketStateMapping[settledMarket.state]);
|
||||
|
||||
// 6001-MARK-004
|
||||
// 6001-MARK-005
|
||||
// 6001-MARK-009
|
||||
// 6001-MARK-008
|
||||
// 6001-MARK-010
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find('[col-id="settlementDate"]')
|
||||
.find('[data-testid="link"]')
|
||||
.should(($el) => {
|
||||
const href = $el.attr('href');
|
||||
expect(href).to.match(
|
||||
new RegExp(
|
||||
`/oracles/${product.dataSourceSpecForTradingTermination.id}`
|
||||
)
|
||||
);
|
||||
})
|
||||
.should('have.text', '4 days ago')
|
||||
.should(
|
||||
'have.attr',
|
||||
'title',
|
||||
getDateTimeFormat().format(
|
||||
new Date(settledMarket.marketTimestamps.close)
|
||||
)
|
||||
);
|
||||
|
||||
// 6001-MARK-011
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find('[col-id="bestBidPrice"]')
|
||||
.should(
|
||||
'have.text',
|
||||
addDecimalsFormatNumber(
|
||||
settledMarketData.bestBidPrice,
|
||||
settledMarket.decimalPlaces
|
||||
)
|
||||
);
|
||||
|
||||
// 6001-MARK-012
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find('[col-id="bestOfferPrice"]')
|
||||
.should(
|
||||
'have.text',
|
||||
addDecimalsFormatNumber(
|
||||
settledMarketData.bestOfferPrice,
|
||||
settledMarket.decimalPlaces
|
||||
)
|
||||
);
|
||||
|
||||
// 6001-MARK-013
|
||||
cy.get(rowSelector).first().find('[col-id="markPrice"]').should(
|
||||
'have.text',
|
||||
|
||||
addDecimalsFormatNumber(
|
||||
settledMarketData.markPrice,
|
||||
settledMarket.decimalPlaces
|
||||
)
|
||||
);
|
||||
|
||||
// 6001-MARK-014
|
||||
// 6001-MARK-015
|
||||
// 6001-MARK-016
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find('[col-id="settlementDataOracleId"]')
|
||||
.find('[data-testid="link"]')
|
||||
.should(($el) => {
|
||||
const href = $el.attr('href');
|
||||
expect(href).to.match(
|
||||
new RegExp(`/oracles/${product.dataSourceSpecForSettlementData.id}`)
|
||||
);
|
||||
})
|
||||
.should(
|
||||
'have.text',
|
||||
addDecimalsFormatNumber(
|
||||
// @ts-ignore cannot deep un-partial
|
||||
specDataConnection.externalData.data.data[0].value,
|
||||
settlementDataPropertyKey.numberDecimalPlaces
|
||||
)
|
||||
);
|
||||
|
||||
// 6001-MARK-018
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find('[col-id="settlementAsset"]')
|
||||
.should('have.text', product.settlementAsset.symbol);
|
||||
|
||||
// 6001-MARK-020
|
||||
cy.get('.ag-pinned-right-cols-container')
|
||||
.find('[col-id="market-actions"]')
|
||||
.first()
|
||||
.find('button svg')
|
||||
.should('exist');
|
||||
if (Cypress.env('NX_SUCCESSOR_MARKETS')) {
|
||||
cy.get(rowSelector)
|
||||
.find('[col-id="successorMarket"]')
|
||||
.first()
|
||||
.should('have.text', '-');
|
||||
}
|
||||
});
|
||||
|
||||
// test market list for market in terminated state
|
||||
it('renders a terminated market', () => {
|
||||
cy.get(rowSelector)
|
||||
.eq(1)
|
||||
.find('[col-id="state"]')
|
||||
.should('have.text', MarketStateMapping[terminatedMarket.state]);
|
||||
|
||||
// 6001-MARK-006
|
||||
// 6001-MARK-007
|
||||
cy.get(rowSelector)
|
||||
.eq(1)
|
||||
.find('[col-id="settlementDate"]')
|
||||
.find('[data-testid="link"]')
|
||||
.should('have.text', 'Expected in 4 days');
|
||||
});
|
||||
|
||||
it('renders a terminated market which was expected to have settled', () => {
|
||||
cy.get(rowSelector)
|
||||
.eq(2)
|
||||
.find('[col-id="settlementDate"]')
|
||||
.should('have.class', 'text-danger')
|
||||
.find('[data-testid="link"]')
|
||||
.should('have.text', 'Expected 2 days ago');
|
||||
});
|
||||
|
||||
it('renders terminated market which doesnt have settlement date metadata', () => {
|
||||
cy.get(rowSelector)
|
||||
.eq(3)
|
||||
.find('[col-id="settlementDate"]')
|
||||
.find('[data-testid="link"]')
|
||||
.should('have.text', 'Unknown');
|
||||
});
|
||||
|
||||
it('can open asset detail dialog', () => {
|
||||
cy.mockGQL((req) => {
|
||||
aliasGQLQuery(req, 'Asset', assetsResult);
|
||||
});
|
||||
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find('[col-id="settlementAsset"]')
|
||||
.find('button')
|
||||
.click();
|
||||
|
||||
// 6001-MARK-019
|
||||
cy.get('[data-testid="dialog-title"]').should(
|
||||
'have.text',
|
||||
`Asset details - ${settlementAsset.symbol}`
|
||||
);
|
||||
|
||||
cy.get('[data-testid="dialog-close"]').click();
|
||||
});
|
||||
|
||||
it('can open row actions', () => {
|
||||
cy.get('.ag-pinned-right-cols-container')
|
||||
.find('[col-id="market-actions"]')
|
||||
.first()
|
||||
.find('button')
|
||||
.click();
|
||||
|
||||
const dropdownContent = '[data-testid="market-actions-content"]';
|
||||
const dropdownContentItem = '[role="menuitem"]';
|
||||
cy.get(dropdownContent)
|
||||
.find(dropdownContentItem)
|
||||
.eq(0)
|
||||
// Cannot click the copy button as it falls back to window.prompt, blocking the test.
|
||||
.should('have.text', 'Copy Market ID');
|
||||
|
||||
cy.get(dropdownContent)
|
||||
.find(dropdownContentItem)
|
||||
.eq(1)
|
||||
.find('a')
|
||||
.then(($el) => {
|
||||
const href = $el.attr('href');
|
||||
expect(/\/markets\/0/.test(href || '')).to.equal(true);
|
||||
})
|
||||
.should('have.text', 'View on Explorer');
|
||||
});
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
it('can see no markets message', () => {
|
||||
// 6001-MARK-034
|
||||
cy.getByTestId('tab-closed-markets').should('contain.text', 'No markets');
|
||||
});
|
||||
});
|
||||
@@ -133,11 +133,7 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
|
||||
validateMarketDataRow(4, 'Decimals', '5');
|
||||
validateMarketDataRow(5, 'Quantum', '1');
|
||||
validateMarketDataRow(6, 'Status', 'Enabled');
|
||||
validateMarketDataRow(
|
||||
7,
|
||||
'Contract address',
|
||||
'0x0158031158Bb4dF2AD02eAA31e8963E84EA978a4'
|
||||
);
|
||||
validateMarketDataRow(7, 'Contract address', '0x0158…78a4');
|
||||
validateMarketDataRow(8, 'Withdrawal threshold', '0.0005');
|
||||
validateMarketDataRow(9, 'Lifetime limit', '1,230');
|
||||
validateMarketDataRow(10, 'Infrastructure fee account balance', '0.00001');
|
||||
|
||||
@@ -6,7 +6,7 @@ const askVolume = 'ask-vol-9894185';
|
||||
const bidVolume = 'bid-vol-9889001';
|
||||
const askCumulative = 'cumulative-vol-9894185';
|
||||
const bidCumulative = 'cumulative-vol-9889001';
|
||||
const midPrice = 'middle-mark-price-4612690000';
|
||||
const midPrice = 'last-traded-4612690000';
|
||||
const priceResolution = 'resolution';
|
||||
const dealTicketPrice = 'order-price';
|
||||
const dealTicketSize = 'order-size';
|
||||
|
||||
@@ -33,9 +33,7 @@ describe('deal ticker order validation', { tags: '@smoke' }, () => {
|
||||
|
||||
it('must see the price unit', function () {
|
||||
// 7002-SORD-018
|
||||
cy.getByTestId(orderPriceField)
|
||||
.siblings('label')
|
||||
.should('have.text', 'Price (DAI)');
|
||||
cy.getByTestId(orderPriceField).next().should('have.text', 'DAI');
|
||||
});
|
||||
|
||||
it('must see warning when placing an order with expiry date in past', () => {
|
||||
|
||||
@@ -43,11 +43,10 @@ describe('ethereum wallet', { tags: '@smoke', testIsolation: true }, () => {
|
||||
// 0004-EWAL-005
|
||||
// 0004-EWAL-006
|
||||
|
||||
const ethWalletAddress = Cypress.env('ETHEREUM_WALLET_ADDRESS');
|
||||
cy.getByTestId('Deposits').click();
|
||||
cy.getByTestId('deposit-button').click();
|
||||
connectEthereumWallet('MetaMask');
|
||||
cy.getByTestId('ethereum-address').should('have.text', ethWalletAddress);
|
||||
cy.getByTestId('ethereum-address').should('have.text', '0xEe7D…d94F');
|
||||
cy.getByTestId('disconnect-ethereum-wallet')
|
||||
.should('have.text', 'Disconnect')
|
||||
.click();
|
||||
|
||||
@@ -12,6 +12,8 @@ NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/main/announcements.json
|
||||
NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
|
||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-fairground
|
||||
|
||||
|
||||
# Cosmic elevator flags
|
||||
|
||||
@@ -13,7 +13,7 @@ NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/test/announcements.json
|
||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/pl/firefox/addon/vega-wallet
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-fairground
|
||||
|
||||
NX_ETH_LOCAL_PROVIDER_URL=http://localhost:8545/
|
||||
NX_ETH_WALLET_MNEMONIC="ozone access unlock valid olympic save include omit supply green clown session"
|
||||
|
||||
@@ -14,7 +14,7 @@ NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega-dev-releases/releases
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
|
||||
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
|
||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/pl/firefox/addon/vega-wallet
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-fairground
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
|
||||
@@ -14,7 +14,7 @@ NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/annou
|
||||
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
|
||||
NX_VEGA_CONSOLE_URL=https://console.vega.xyz
|
||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-mainnet/codfcglpplgmmlokgilfkpcjnmkbfiel
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/pl/firefox/addon/vega-wallet-mainnet
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-mainnet
|
||||
# TAG name of the current app version - TODO: bump to the latest upon release
|
||||
NX_APP_VERSION=v0.20.21-core-0.71.6
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/annou
|
||||
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
|
||||
NX_VEGA_CONSOLE_URL=https://console.mainnet-mirror.vega.rocks
|
||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-mainnet/codfcglpplgmmlokgilfkpcjnmkbfiel
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/pl/firefox/addon/vega-wallet-mainnet
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-mainnet
|
||||
# TAG name of the current app version - TODO: bump to the latest upon release
|
||||
NX_APP_VERSION=v0.20.19-core-0.71.6
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
|
||||
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
|
||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/pl/firefox/addon/vega-wallet
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-fairground
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
|
||||
@@ -15,7 +15,7 @@ NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/annou
|
||||
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
|
||||
NX_VEGA_CONSOLE_URL=https://console.fairground.wtf
|
||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/pl/firefox/addon/vega-wallet
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-fairground
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=true
|
||||
|
||||
@@ -16,7 +16,7 @@ NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
|
||||
NX_VEGA_CONSOLE_URL=https://trading.validators-testnet.vega.rocks
|
||||
|
||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/pl/firefox/addon/vega-wallet
|
||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-fairground
|
||||
|
||||
# Cosmic elevator flags
|
||||
NX_SUCCESSOR_MARKETS=false
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Intent, TradingButton } from '@vegaprotocol/ui-toolkit';
|
||||
import { TradingButton } from '@vegaprotocol/ui-toolkit';
|
||||
import { ViewType, useSidebar } from '../sidebar';
|
||||
|
||||
export const AccountsMenu = () => {
|
||||
@@ -8,7 +8,6 @@ export const AccountsMenu = () => {
|
||||
return (
|
||||
<>
|
||||
<TradingButton
|
||||
intent={Intent.Primary}
|
||||
size="extra-small"
|
||||
data-testid="open-transfer"
|
||||
onClick={() => setView({ type: ViewType.Transfer })}
|
||||
@@ -16,7 +15,6 @@ export const AccountsMenu = () => {
|
||||
{t('Transfer')}
|
||||
</TradingButton>
|
||||
<TradingButton
|
||||
intent={Intent.Primary}
|
||||
size="extra-small"
|
||||
onClick={() => setView({ type: ViewType.Deposit })}
|
||||
>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { InMemoryCacheConfig } from '@apollo/client';
|
||||
import {
|
||||
AppFailure,
|
||||
DocsLinks,
|
||||
NetworkLoader,
|
||||
NodeGuard,
|
||||
useEnvironment,
|
||||
@@ -17,16 +18,32 @@ export const DynamicLoader = dynamic(() => import('../preloader/preloader'), {
|
||||
});
|
||||
|
||||
export const AppLoader = ({ children }: { children: ReactNode }) => {
|
||||
const { error, VEGA_URL, MAINTENANCE_PAGE } = useEnvironment((store) => ({
|
||||
error: store.error,
|
||||
VEGA_URL: store.VEGA_URL,
|
||||
MAINTENANCE_PAGE: store.MAINTENANCE_PAGE,
|
||||
}));
|
||||
const {
|
||||
error,
|
||||
VEGA_URL,
|
||||
VEGA_ENV,
|
||||
VEGA_WALLET_URL,
|
||||
VEGA_EXPLORER_URL,
|
||||
MAINTENANCE_PAGE,
|
||||
MOZILLA_EXTENSION_URL,
|
||||
CHROME_EXTENSION_URL,
|
||||
} = useEnvironment();
|
||||
|
||||
if (MAINTENANCE_PAGE) {
|
||||
return <MaintenancePage />;
|
||||
}
|
||||
|
||||
if (
|
||||
!VEGA_URL ||
|
||||
!VEGA_WALLET_URL ||
|
||||
!VEGA_EXPLORER_URL ||
|
||||
!CHROME_EXTENSION_URL ||
|
||||
!MOZILLA_EXTENSION_URL ||
|
||||
!DocsLinks
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<NetworkLoader
|
||||
cache={cacheConfig}
|
||||
@@ -40,7 +57,21 @@ export const AppLoader = ({ children }: { children: ReactNode }) => {
|
||||
failure={<AppFailure title={t(`Node: ${VEGA_URL} is unsuitable`)} />}
|
||||
>
|
||||
<Web3Provider>
|
||||
<VegaWalletProvider>{children}</VegaWalletProvider>
|
||||
<VegaWalletProvider
|
||||
config={{
|
||||
network: VEGA_ENV,
|
||||
vegaUrl: VEGA_URL,
|
||||
vegaWalletServiceUrl: VEGA_WALLET_URL,
|
||||
links: {
|
||||
explorer: VEGA_EXPLORER_URL,
|
||||
concepts: DocsLinks.VEGA_WALLET_CONCEPTS_URL,
|
||||
chromeExtensionUrl: CHROME_EXTENSION_URL,
|
||||
mozillaExtensionUrl: MOZILLA_EXTENSION_URL,
|
||||
},
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</VegaWalletProvider>
|
||||
</Web3Provider>
|
||||
</NodeGuard>
|
||||
</NetworkLoader>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Intent, TradingButton } from '@vegaprotocol/ui-toolkit';
|
||||
import { TradingButton } from '@vegaprotocol/ui-toolkit';
|
||||
import { ViewType, useSidebar } from '../sidebar';
|
||||
|
||||
export const DepositsMenu = () => {
|
||||
@@ -7,7 +7,6 @@ export const DepositsMenu = () => {
|
||||
|
||||
return (
|
||||
<TradingButton
|
||||
intent={Intent.Primary}
|
||||
size="extra-small"
|
||||
onClick={() => setView({ type: ViewType.Deposit })}
|
||||
data-testid="deposit-button"
|
||||
|
||||
@@ -69,6 +69,7 @@ describe('MarketSelectorItem', () => {
|
||||
targetStake: '1000000',
|
||||
trigger: AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED,
|
||||
priceMonitoringBounds: null,
|
||||
lastTradedPrice: '100',
|
||||
};
|
||||
|
||||
const candles = [
|
||||
|
||||
@@ -262,9 +262,7 @@ describe('MarketSelector', () => {
|
||||
await userEvent.click(screen.getByTestId('sort-trigger'));
|
||||
const options = screen.getAllByTestId(/sort-item/);
|
||||
expect(options.map((o) => o.textContent?.trim())).toEqual(
|
||||
Object.entries(Sort)
|
||||
.filter(([key]) => key !== Sort.None)
|
||||
.map(([key]) => SortTypeMapping[key as SortType])
|
||||
Object.entries(Sort).map(([key]) => SortTypeMapping[key as SortType])
|
||||
);
|
||||
await userEvent.click(screen.getByTestId('sort-item-Gained'));
|
||||
expect(
|
||||
|
||||
@@ -40,7 +40,7 @@ export const MarketSelector = ({
|
||||
const [filter, setFilter] = useState<Filter>({
|
||||
searchTerm: '',
|
||||
product: Product.All,
|
||||
sort: Sort.None,
|
||||
sort: Sort.TopTraded,
|
||||
assets: [],
|
||||
});
|
||||
const allProducts = filter.product === Product.All;
|
||||
@@ -53,7 +53,7 @@ export const MarketSelector = ({
|
||||
|
||||
return (
|
||||
<div data-testid="market-selector">
|
||||
<div className="pt-2 px-2 mb-2">
|
||||
<div className="px-2 pt-2 mb-2">
|
||||
<ProductSelector
|
||||
product={filter.product}
|
||||
onSelect={(product) => {
|
||||
@@ -106,9 +106,6 @@ export const MarketSelector = ({
|
||||
currentSort={filter.sort}
|
||||
onSelect={(sort) => {
|
||||
setFilter((curr) => {
|
||||
if (curr.sort === sort) {
|
||||
return { ...curr, sort: Sort.None };
|
||||
}
|
||||
return {
|
||||
...curr,
|
||||
sort,
|
||||
@@ -294,9 +291,9 @@ const List = ({
|
||||
|
||||
const Skeleton = () => {
|
||||
return (
|
||||
<div className="mb-2 px-2">
|
||||
<div className="bg-vega-light-100 dark:bg-vega-dark-100 rounded-lg p-4">
|
||||
<div className="w-full h-3 bg-vega-light-200 dark:bg-vega-dark-200 mb-2" />
|
||||
<div className="px-2 mb-2">
|
||||
<div className="p-4 rounded-lg bg-vega-light-100 dark:bg-vega-dark-100">
|
||||
<div className="w-full h-3 mb-2 bg-vega-light-200 dark:bg-vega-dark-200" />
|
||||
<div className="w-2/3 h-3 bg-vega-light-200 dark:bg-vega-dark-200" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
@@ -11,7 +10,6 @@ import {
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
export const Sort = {
|
||||
None: 'None',
|
||||
Gained: 'Gained',
|
||||
Lost: 'Lost',
|
||||
New: 'New',
|
||||
@@ -23,17 +21,15 @@ export type SortType = keyof typeof Sort;
|
||||
export const SortTypeMapping: {
|
||||
[key in SortType]: string;
|
||||
} = {
|
||||
[Sort.None]: 'None',
|
||||
[Sort.TopTraded]: 'Top traded',
|
||||
[Sort.Gained]: 'Top gaining',
|
||||
[Sort.Lost]: 'Top losing',
|
||||
[Sort.New]: 'New markets',
|
||||
[Sort.TopTraded]: 'Top traded',
|
||||
};
|
||||
|
||||
const SortIconMapping: {
|
||||
[key in SortType]: VegaIconNames;
|
||||
} = {
|
||||
[Sort.None]: null as unknown as VegaIconNames, // not shown in list
|
||||
[Sort.Gained]: VegaIconNames.TREND_UP,
|
||||
[Sort.Lost]: VegaIconNames.TREND_DOWN,
|
||||
[Sort.New]: VegaIconNames.STAR,
|
||||
@@ -51,10 +47,8 @@ export const SortDropdown = ({
|
||||
<DropdownMenu
|
||||
trigger={
|
||||
<DropdownMenuTrigger data-testid="sort-trigger">
|
||||
<span className="flex justify-between items-center">
|
||||
{currentSort === SortTypeMapping.None
|
||||
? t('Sort')
|
||||
: SortTypeMapping[currentSort]}{' '}
|
||||
<span className="flex items-center justify-between gap-1">
|
||||
{SortTypeMapping[currentSort]}
|
||||
<VegaIcon name={VegaIconNames.CHEVRON_DOWN} />
|
||||
</span>
|
||||
</DropdownMenuTrigger>
|
||||
@@ -65,24 +59,22 @@ export const SortDropdown = ({
|
||||
value={currentSort}
|
||||
onValueChange={(value) => onSelect(value as SortType)}
|
||||
>
|
||||
{Object.keys(Sort)
|
||||
.filter((s) => s !== Sort.None)
|
||||
.map((key) => {
|
||||
return (
|
||||
<DropdownMenuRadioItem
|
||||
inset
|
||||
key={key}
|
||||
value={key}
|
||||
data-testid={`sort-item-${key}`}
|
||||
>
|
||||
<span className="flex gap-2">
|
||||
<VegaIcon name={SortIconMapping[key as SortType]} />{' '}
|
||||
{SortTypeMapping[key as SortType]}
|
||||
</span>
|
||||
<DropdownMenuItemIndicator />
|
||||
</DropdownMenuRadioItem>
|
||||
);
|
||||
})}
|
||||
{Object.keys(Sort).map((key) => {
|
||||
return (
|
||||
<DropdownMenuRadioItem
|
||||
inset
|
||||
key={key}
|
||||
value={key}
|
||||
data-testid={`sort-item-${key}`}
|
||||
>
|
||||
<span className="flex gap-2">
|
||||
<VegaIcon name={SortIconMapping[key as SortType]} />{' '}
|
||||
{SortTypeMapping[key as SortType]}
|
||||
</span>
|
||||
<DropdownMenuItemIndicator />
|
||||
</DropdownMenuRadioItem>
|
||||
);
|
||||
})}
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
@@ -12,7 +12,10 @@ import { useMarketList } from '@vegaprotocol/markets';
|
||||
import type { Filter } from '../../components/market-selector';
|
||||
import { subDays } from 'date-fns';
|
||||
|
||||
jest.mock('@vegaprotocol/markets');
|
||||
jest.mock('@vegaprotocol/markets', () => ({
|
||||
...jest.requireActual('@vegaprotocol/markets'),
|
||||
useMarketList: jest.fn(),
|
||||
}));
|
||||
const mockUseMarketList = useMarketList as jest.Mock;
|
||||
|
||||
describe('useMarketSelectorList', () => {
|
||||
@@ -20,7 +23,7 @@ describe('useMarketSelectorList', () => {
|
||||
const defaultArgs: Filter = {
|
||||
searchTerm: '',
|
||||
product: Product.Future,
|
||||
sort: Sort.None,
|
||||
sort: Sort.TopTraded,
|
||||
assets: [],
|
||||
};
|
||||
return renderHook((args) => useMarketSelectorList(args), {
|
||||
@@ -109,21 +112,21 @@ describe('useMarketSelectorList', () => {
|
||||
rerender({
|
||||
searchTerm: '',
|
||||
product: Product.Spot as 'Future',
|
||||
sort: Sort.None,
|
||||
sort: Sort.TopTraded,
|
||||
assets: [],
|
||||
});
|
||||
expect(result.current.markets).toEqual([markets[1]]);
|
||||
rerender({
|
||||
searchTerm: '',
|
||||
product: Product.Perpetual as 'Future',
|
||||
sort: Sort.None,
|
||||
sort: Sort.TopTraded,
|
||||
assets: [],
|
||||
});
|
||||
expect(result.current.markets).toEqual([markets[2]]);
|
||||
rerender({
|
||||
searchTerm: '',
|
||||
product: Product.All,
|
||||
sort: Sort.None,
|
||||
sort: Sort.TopTraded,
|
||||
assets: [],
|
||||
});
|
||||
expect(result.current.markets).toEqual(markets);
|
||||
@@ -189,7 +192,7 @@ describe('useMarketSelectorList', () => {
|
||||
const { result, rerender } = setup({
|
||||
searchTerm: '',
|
||||
product: Product.Future,
|
||||
sort: Sort.None,
|
||||
sort: Sort.TopTraded,
|
||||
assets: ['asset-0'],
|
||||
});
|
||||
expect(result.current.markets).toEqual([markets[0], markets[1]]);
|
||||
@@ -197,7 +200,7 @@ describe('useMarketSelectorList', () => {
|
||||
rerender({
|
||||
searchTerm: '',
|
||||
product: Product.Future,
|
||||
sort: Sort.None,
|
||||
sort: Sort.TopTraded,
|
||||
assets: ['asset-0', 'asset-1'],
|
||||
});
|
||||
|
||||
@@ -210,7 +213,7 @@ describe('useMarketSelectorList', () => {
|
||||
rerender({
|
||||
searchTerm: '',
|
||||
product: Product.Future,
|
||||
sort: Sort.None,
|
||||
sort: Sort.TopTraded,
|
||||
assets: ['asset-0', 'asset-1', 'asset-2'],
|
||||
});
|
||||
|
||||
@@ -220,7 +223,7 @@ describe('useMarketSelectorList', () => {
|
||||
rerender({
|
||||
searchTerm: '',
|
||||
product: Product.Future,
|
||||
sort: Sort.None,
|
||||
sort: Sort.TopTraded,
|
||||
assets: ['asset-invalid'],
|
||||
});
|
||||
|
||||
@@ -275,28 +278,28 @@ describe('useMarketSelectorList', () => {
|
||||
const { result, rerender } = setup({
|
||||
searchTerm: 'abc',
|
||||
product: Product.Future,
|
||||
sort: Sort.None,
|
||||
sort: Sort.TopTraded,
|
||||
assets: [],
|
||||
});
|
||||
expect(result.current.markets).toEqual([markets[0]]);
|
||||
rerender({
|
||||
searchTerm: 'def',
|
||||
product: Product.Future,
|
||||
sort: Sort.None,
|
||||
sort: Sort.TopTraded,
|
||||
assets: [],
|
||||
});
|
||||
expect(result.current.markets).toEqual([markets[1], markets[2]]);
|
||||
rerender({
|
||||
searchTerm: 'defg',
|
||||
product: Product.Future,
|
||||
sort: Sort.None,
|
||||
sort: Sort.TopTraded,
|
||||
assets: [],
|
||||
});
|
||||
expect(result.current.markets).toEqual([markets[2]]);
|
||||
rerender({
|
||||
searchTerm: 'zzz',
|
||||
product: Product.Future,
|
||||
sort: Sort.None,
|
||||
sort: Sort.TopTraded,
|
||||
assets: [],
|
||||
});
|
||||
expect(result.current.markets).toEqual([]);
|
||||
@@ -305,14 +308,14 @@ describe('useMarketSelectorList', () => {
|
||||
rerender({
|
||||
searchTerm: 'aaa',
|
||||
product: Product.Future,
|
||||
sort: Sort.None,
|
||||
sort: Sort.TopTraded,
|
||||
assets: [],
|
||||
});
|
||||
expect(result.current.markets).toEqual([markets[0]]);
|
||||
rerender({
|
||||
searchTerm: 'ggg',
|
||||
product: Product.Future,
|
||||
sort: Sort.None,
|
||||
sort: Sort.TopTraded,
|
||||
assets: [],
|
||||
});
|
||||
expect(result.current.markets).toEqual([
|
||||
@@ -322,11 +325,15 @@ describe('useMarketSelectorList', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('sorts by state and volume by default', () => {
|
||||
it('sorts by top traded by default', () => {
|
||||
const markets = [
|
||||
createMarketFragment({
|
||||
id: 'market-0',
|
||||
state: MarketState.STATE_PENDING,
|
||||
state: MarketState.STATE_ACTIVE,
|
||||
// @ts-ignore data not on fragment
|
||||
data: {
|
||||
markPrice: '1',
|
||||
},
|
||||
// @ts-ignore candles not on fragment
|
||||
candles: [
|
||||
{
|
||||
@@ -337,30 +344,42 @@ describe('useMarketSelectorList', () => {
|
||||
createMarketFragment({
|
||||
id: 'market-1',
|
||||
state: MarketState.STATE_ACTIVE,
|
||||
// @ts-ignore data not on fragment
|
||||
data: {
|
||||
markPrice: '1',
|
||||
},
|
||||
// @ts-ignore candles not on fragment
|
||||
candles: [
|
||||
{
|
||||
volume: '200',
|
||||
volume: '100',
|
||||
},
|
||||
],
|
||||
}),
|
||||
createMarketFragment({
|
||||
id: 'market-2',
|
||||
state: MarketState.STATE_ACTIVE,
|
||||
// @ts-ignore data not on fragment
|
||||
data: {
|
||||
markPrice: '1',
|
||||
},
|
||||
// @ts-ignore candles not on fragment
|
||||
candles: [
|
||||
{
|
||||
volume: '100',
|
||||
volume: '300',
|
||||
},
|
||||
],
|
||||
}),
|
||||
createMarketFragment({
|
||||
state: MarketState.STATE_PENDING,
|
||||
id: 'market-3',
|
||||
state: MarketState.STATE_ACTIVE,
|
||||
// @ts-ignore data not on fragment
|
||||
data: {
|
||||
markPrice: '1',
|
||||
},
|
||||
// @ts-ignore candles not on fragment
|
||||
candles: [
|
||||
{
|
||||
volume: '100',
|
||||
volume: '400',
|
||||
},
|
||||
],
|
||||
}),
|
||||
@@ -375,14 +394,15 @@ describe('useMarketSelectorList', () => {
|
||||
const { result } = setup({
|
||||
searchTerm: '',
|
||||
product: Product.Future,
|
||||
sort: Sort.None,
|
||||
sort: Sort.TopTraded,
|
||||
assets: [],
|
||||
});
|
||||
|
||||
expect(result.current.markets).toEqual([
|
||||
markets[1],
|
||||
markets[3],
|
||||
markets[2],
|
||||
markets[0],
|
||||
markets[3],
|
||||
markets[1],
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import { useMemo } from 'react';
|
||||
import orderBy from 'lodash/orderBy';
|
||||
import { MarketState } from '@vegaprotocol/types';
|
||||
import {
|
||||
calcCandleVolume,
|
||||
calcTradedFactor,
|
||||
useMarketList,
|
||||
} from '@vegaprotocol/markets';
|
||||
import { calcTradedFactor, useMarketList } from '@vegaprotocol/markets';
|
||||
import { priceChangePercentage } from '@vegaprotocol/utils';
|
||||
import type { Filter } from '../../components/market-selector/market-selector';
|
||||
import { Sort } from './sort-dropdown';
|
||||
@@ -60,22 +56,6 @@ export const useMarketSelectorList = ({
|
||||
return false;
|
||||
});
|
||||
|
||||
if (sort === Sort.None) {
|
||||
// Sort by market state primarily and AtoZ secondarily
|
||||
return orderBy(
|
||||
markets,
|
||||
[
|
||||
(m) => MARKET_TEMPLATE.indexOf(m.state),
|
||||
(m) => {
|
||||
if (!m.candles?.length) return 0;
|
||||
const vol = calcCandleVolume(m.candles);
|
||||
return Number(vol || 0);
|
||||
},
|
||||
],
|
||||
['asc', 'desc']
|
||||
);
|
||||
}
|
||||
|
||||
if (sort === Sort.Gained || sort === Sort.Lost) {
|
||||
const dir = sort === Sort.Gained ? 'desc' : 'asc';
|
||||
return orderBy(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Intent, TradingButton } from '@vegaprotocol/ui-toolkit';
|
||||
import { TradingButton } from '@vegaprotocol/ui-toolkit';
|
||||
import { usePositionsStore } from '../positions-container';
|
||||
|
||||
export const PositionsMenu = () => {
|
||||
@@ -7,7 +7,6 @@ export const PositionsMenu = () => {
|
||||
const toggle = usePositionsStore((store) => store.toggleClosedMarkets);
|
||||
return (
|
||||
<TradingButton
|
||||
intent={Intent.Primary}
|
||||
size="extra-small"
|
||||
data-testid="open-transfer"
|
||||
onClick={toggle}
|
||||
|
||||
@@ -24,8 +24,8 @@ export const Settings = () => {
|
||||
>
|
||||
<Switch
|
||||
name="settings-telemetry-switch"
|
||||
onCheckedChange={(isOn) => setIsApproved(isOn)}
|
||||
checked={isApproved}
|
||||
onCheckedChange={(isOn) => setIsApproved(isOn ? 'true' : 'false')}
|
||||
checked={isApproved === 'true'}
|
||||
/>
|
||||
</SettingsGroup>
|
||||
<SettingsGroup label={t('Toast location')}>
|
||||
|
||||
@@ -9,7 +9,8 @@ import {
|
||||
} from './sidebar';
|
||||
import { MemoryRouter, Route, Routes } from 'react-router-dom';
|
||||
import { VegaIconNames } from '@vegaprotocol/ui-toolkit';
|
||||
import { VegaWalletProvider } from '@vegaprotocol/wallet';
|
||||
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
|
||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
|
||||
jest.mock('../node-health', () => ({
|
||||
NodeHealthContainer: () => <span data-testid="node-health" />,
|
||||
@@ -31,16 +32,20 @@ jest.mock('../welcome-dialog', () => ({
|
||||
GetStarted: () => <div data-testid="get-started" />,
|
||||
}));
|
||||
|
||||
const walletContext = {
|
||||
pubKeys: [{ publicKey: 'pubkey' }],
|
||||
} as VegaWalletContextShape;
|
||||
|
||||
describe('Sidebar', () => {
|
||||
it.each(['/markets/all', '/portfolio'])(
|
||||
'does not render ticket and info',
|
||||
(path) => {
|
||||
render(
|
||||
<VegaWalletProvider>
|
||||
<VegaWalletContext.Provider value={walletContext}>
|
||||
<MemoryRouter initialEntries={[path]}>
|
||||
<Sidebar />
|
||||
</MemoryRouter>
|
||||
</VegaWalletProvider>
|
||||
</VegaWalletContext.Provider>
|
||||
);
|
||||
|
||||
expect(screen.getByTestId(ViewType.ViewAs)).toBeInTheDocument();
|
||||
@@ -58,11 +63,11 @@ describe('Sidebar', () => {
|
||||
|
||||
it('renders ticket and info on market pages', () => {
|
||||
render(
|
||||
<VegaWalletProvider>
|
||||
<VegaWalletContext.Provider value={walletContext}>
|
||||
<MemoryRouter initialEntries={['/markets/ABC']}>
|
||||
<Sidebar />
|
||||
</MemoryRouter>
|
||||
</VegaWalletProvider>
|
||||
</VegaWalletContext.Provider>
|
||||
);
|
||||
|
||||
expect(screen.getByTestId(ViewType.ViewAs)).toBeInTheDocument();
|
||||
@@ -79,11 +84,11 @@ describe('Sidebar', () => {
|
||||
|
||||
it('renders selected state', async () => {
|
||||
render(
|
||||
<VegaWalletProvider>
|
||||
<VegaWalletContext.Provider value={walletContext}>
|
||||
<MemoryRouter initialEntries={['/markets/ABC']}>
|
||||
<Sidebar />
|
||||
</MemoryRouter>
|
||||
</VegaWalletProvider>
|
||||
</VegaWalletContext.Provider>
|
||||
);
|
||||
|
||||
const settingsButton = screen.getByTestId(ViewType.Settings);
|
||||
@@ -107,13 +112,13 @@ describe('Sidebar', () => {
|
||||
describe('SidebarContent', () => {
|
||||
it('renders the correct content', () => {
|
||||
const { container } = render(
|
||||
<VegaWalletProvider>
|
||||
<VegaWalletContext.Provider value={walletContext}>
|
||||
<MemoryRouter initialEntries={['/markets/ABC']}>
|
||||
<Routes>
|
||||
<Route path="/markets/:marketId" element={<SidebarContent />} />
|
||||
</Routes>
|
||||
</MemoryRouter>
|
||||
</VegaWalletProvider>
|
||||
</VegaWalletContext.Provider>
|
||||
);
|
||||
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
@@ -133,13 +138,13 @@ describe('SidebarContent', () => {
|
||||
|
||||
it('closes sidebar if market id is required but not present', () => {
|
||||
const { container } = render(
|
||||
<VegaWalletProvider>
|
||||
<VegaWalletContext.Provider value={walletContext}>
|
||||
<MemoryRouter initialEntries={['/portfolio']}>
|
||||
<Routes>
|
||||
<Route path="/portfolio" element={<SidebarContent />} />
|
||||
</Routes>
|
||||
</MemoryRouter>
|
||||
</VegaWalletProvider>
|
||||
</VegaWalletContext.Provider>
|
||||
);
|
||||
|
||||
act(() => {
|
||||
|
||||
@@ -23,18 +23,20 @@ import { Links, Routes } from '../../pages/client-router';
|
||||
import { useGlobalStore } from '../../stores';
|
||||
import { useSidebar, ViewType } from '../sidebar';
|
||||
import * as constants from '../constants';
|
||||
import { useOnboardingStore } from './welcome-dialog';
|
||||
|
||||
interface Props {
|
||||
lead?: string;
|
||||
}
|
||||
|
||||
const GetStartedButton = ({ step }: { step: OnboardingStep }) => {
|
||||
const { CHROME_EXTENSION_URL, MOZILLA_EXTENSION_URL } = useEnvironment();
|
||||
const navigate = useNavigate();
|
||||
const [, setOnboardingViewed] = useLocalStorage(
|
||||
constants.ONBOARDING_VIEWED_KEY
|
||||
);
|
||||
|
||||
const update = useGlobalStore((store) => store.update);
|
||||
const dismiss = useOnboardingStore((store) => store.dismiss);
|
||||
const marketId = useGlobalStore((store) => store.marketId);
|
||||
const link = marketId ? Links[Routes.MARKET](marketId) : Links[Routes.HOME]();
|
||||
const openVegaWalletDialog = useVegaWalletDialogStore(
|
||||
@@ -46,7 +48,13 @@ const GetStartedButton = ({ step }: { step: OnboardingStep }) => {
|
||||
openVegaWalletDialog();
|
||||
};
|
||||
if (step === OnboardingStep.ONBOARDING_WALLET_STEP) {
|
||||
return <GetWalletButton className="justify-between" />;
|
||||
return (
|
||||
<GetWalletButton
|
||||
className="justify-between"
|
||||
chromeExtensionUrl={CHROME_EXTENSION_URL}
|
||||
mozillaExtensionUrl={MOZILLA_EXTENSION_URL}
|
||||
/>
|
||||
);
|
||||
} else if (step === OnboardingStep.ONBOARDING_CONNECT_STEP) {
|
||||
buttonText = t('Connect');
|
||||
} else if (step === OnboardingStep.ONBOARDING_DEPOSIT_STEP) {
|
||||
@@ -54,7 +62,7 @@ const GetStartedButton = ({ step }: { step: OnboardingStep }) => {
|
||||
onClickHandle = () => {
|
||||
navigate(link);
|
||||
setView({ type: ViewType.Deposit });
|
||||
update({ onBoardingDismissed: true });
|
||||
dismiss();
|
||||
};
|
||||
} else if (step === OnboardingStep.ONBOARDING_ORDER_STEP) {
|
||||
buttonText = t('Dismiss');
|
||||
|
||||
@@ -2,29 +2,35 @@ import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { TelemetryApproval } from './telemetry-approval';
|
||||
|
||||
jest.mock('@vegaprotocol/logger', () => ({
|
||||
SentryInit: () => undefined,
|
||||
SentryClose: () => undefined,
|
||||
}));
|
||||
|
||||
jest.mock('@vegaprotocol/environment', () => ({
|
||||
useEnvironment: () => ({ VEGA_ENV: 'test', SENTRY_DSN: 'sentry-dsn' }),
|
||||
}));
|
||||
|
||||
describe('TelemetryApproval', () => {
|
||||
it('click on checkbox should be properly handled', async () => {
|
||||
const helpText = 'My help text';
|
||||
render(<TelemetryApproval helpText={helpText} />);
|
||||
expect(screen.getByRole('checkbox')).toHaveAttribute(
|
||||
'data-state',
|
||||
'unchecked'
|
||||
it('click on buttons should be properly handled', async () => {
|
||||
const mockSetTelemetryValue = jest.fn();
|
||||
render(
|
||||
<TelemetryApproval
|
||||
telemetryValue="false"
|
||||
setTelemetryValue={mockSetTelemetryValue}
|
||||
/>
|
||||
);
|
||||
await userEvent.click(screen.getByRole('checkbox'));
|
||||
expect(screen.getByRole('checkbox')).toHaveAttribute(
|
||||
'data-state',
|
||||
'checked'
|
||||
expect(
|
||||
screen.getByRole('button', { name: 'No thanks' })
|
||||
).toBeInTheDocument();
|
||||
await userEvent.click(screen.getByRole('button', { name: 'No thanks' }));
|
||||
expect(mockSetTelemetryValue).toHaveBeenCalledWith('false');
|
||||
expect(screen.getByText('Share data')).toBeInTheDocument();
|
||||
await userEvent.click(screen.getByText('Share data'));
|
||||
expect(mockSetTelemetryValue).toHaveBeenCalledWith('true');
|
||||
});
|
||||
|
||||
it('confirm button should have proper text', async () => {
|
||||
const mockSetTelemetryValue = jest.fn();
|
||||
render(
|
||||
<TelemetryApproval
|
||||
telemetryValue="true"
|
||||
setTelemetryValue={mockSetTelemetryValue}
|
||||
/>
|
||||
);
|
||||
expect(screen.getByText('Share usage data')).toBeInTheDocument();
|
||||
expect(screen.getByText(helpText)).toBeInTheDocument();
|
||||
expect(screen.getByText('Continue sharing data')).toBeInTheDocument();
|
||||
await userEvent.click(screen.getByText('Continue sharing data'));
|
||||
expect(mockSetTelemetryValue).toHaveBeenCalledWith('true');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,21 +1,66 @@
|
||||
import { TradingCheckbox } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
Intent,
|
||||
TradingButton,
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useTelemetryApproval } from '../../lib/hooks/use-telemetry-approval';
|
||||
|
||||
export const TelemetryApproval = ({ helpText }: { helpText: string }) => {
|
||||
const [isApproved, setIsApproved] = useTelemetryApproval();
|
||||
interface Props {
|
||||
telemetryValue: string;
|
||||
setTelemetryValue: (value: string) => void;
|
||||
}
|
||||
|
||||
export const TelemetryApproval = ({
|
||||
telemetryValue,
|
||||
setTelemetryValue,
|
||||
}: Props) => {
|
||||
return (
|
||||
<div className="flex flex-col py-3">
|
||||
<div className="mr-4" role="form">
|
||||
<TradingCheckbox
|
||||
label={<span className="text-lg pl-1">{t('Share usage data')}</span>}
|
||||
checked={isApproved}
|
||||
name="telemetry-approval"
|
||||
onCheckedChange={() => setIsApproved(!isApproved)}
|
||||
/>
|
||||
</div>
|
||||
<div className="text-sm text-vega-light-300 dark:text-vega-dark-300 ml-6">
|
||||
<span>{helpText}</span>
|
||||
<div className="mt-2">
|
||||
{t(
|
||||
'Help us identify bugs and improve Vega Governance by sharing anonymous usage data.'
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center mt-2">
|
||||
<VegaIcon name={VegaIconNames.EYE_OFF} size={24} />
|
||||
<div className="flex flex-col gap-1 ml-6">
|
||||
<div className="font-semibold">{t('Anonymous')}</div>
|
||||
<div>{t('Your identity is always anonymous on Vega')}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center mt-2">
|
||||
<VegaIcon name={VegaIconNames.COG} size={24} />
|
||||
<div className="flex flex-col gap-1 ml-6">
|
||||
<div className="font-semibold">{t('Optional')}</div>
|
||||
<div>{t('You can opt out any time via settings')}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col justify-around items-center mt-10 gap-4 w-full px-4">
|
||||
<TradingButton
|
||||
onClick={() => setTelemetryValue('false')}
|
||||
size="small"
|
||||
intent={Intent.None}
|
||||
data-testid="do-not-share-data-button"
|
||||
fill
|
||||
>
|
||||
{t('No thanks')}
|
||||
</TradingButton>
|
||||
|
||||
<TradingButton
|
||||
onClick={() => setTelemetryValue('true')}
|
||||
intent={Intent.Info}
|
||||
data-testid="share-data-button"
|
||||
size="small"
|
||||
fill
|
||||
>
|
||||
{telemetryValue === 'true'
|
||||
? t('Continue sharing data')
|
||||
: t('Share data')}
|
||||
</TradingButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -5,17 +5,17 @@ import { useNavigate } from 'react-router-dom';
|
||||
import { Links, Routes } from '../../pages/client-router';
|
||||
import { Networks, useEnvironment } from '@vegaprotocol/environment';
|
||||
import type { ReactNode } from 'react';
|
||||
import { useGlobalStore } from '../../stores';
|
||||
import { useOnboardingStore } from './welcome-dialog';
|
||||
|
||||
export const WelcomeDialogContent = () => {
|
||||
const { VEGA_ENV } = useEnvironment();
|
||||
|
||||
const update = useGlobalStore((store) => store.update);
|
||||
const dismiss = useOnboardingStore((store) => store.dismiss);
|
||||
const navigate = useNavigate();
|
||||
const browseMarkets = () => {
|
||||
const link = Links[Routes.MARKETS]();
|
||||
navigate(link);
|
||||
update({ onBoardingDismissed: true });
|
||||
dismiss();
|
||||
};
|
||||
const lead =
|
||||
VEGA_ENV === Networks.MAINNET
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import React from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Dialog, Intent } from '@vegaprotocol/ui-toolkit';
|
||||
import type { Toast } from '@vegaprotocol/ui-toolkit';
|
||||
import { Dialog, Intent, useToasts } from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { useLocalStorage } from '@vegaprotocol/react-helpers';
|
||||
import { WelcomeDialogContent } from './welcome-dialog-content';
|
||||
@@ -12,15 +14,41 @@ import {
|
||||
OnboardingStep,
|
||||
} from './use-get-onboarding-step';
|
||||
import * as constants from '../constants';
|
||||
import { TelemetryApproval } from './telemetry-approval';
|
||||
import { useTelemetryApproval } from '../../lib/hooks/use-telemetry-approval';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
const ONBOARDING_STORAGE_KEY = 'vega_onboarding_dismiss_store';
|
||||
export const useOnboardingStore = create<{
|
||||
dismissed: boolean;
|
||||
dismiss: () => void;
|
||||
}>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
dismissed: false,
|
||||
dismiss: () => set(() => ({ dismissed: true })),
|
||||
}),
|
||||
{
|
||||
name: ONBOARDING_STORAGE_KEY,
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
const TELEMETRY_APPROVAL_TOAST_ID = 'telemetry_tost_id';
|
||||
export const WelcomeDialog = () => {
|
||||
const { VEGA_ENV } = useEnvironment();
|
||||
const [onBoardingViewed] = useLocalStorage(constants.ONBOARDING_VIEWED_KEY);
|
||||
const update = useGlobalStore((store) => store.update);
|
||||
const dismissed = useGlobalStore((store) => store.onBoardingDismissed);
|
||||
const currentStep = useGetOnboardingStep();
|
||||
|
||||
const navigate = useNavigate();
|
||||
const [telemetryValue, setTelemetryValue, isTelemetryNeeded, closeTelemetry] =
|
||||
useTelemetryApproval();
|
||||
const [onBoardingViewed] = useLocalStorage(constants.ONBOARDING_VIEWED_KEY);
|
||||
const dismiss = useOnboardingStore((store) => store.dismiss);
|
||||
const dismissed = useOnboardingStore((store) => store.dismissed);
|
||||
const currentStep = useGetOnboardingStep();
|
||||
const isTelemetryPopupNeeded =
|
||||
isTelemetryNeeded &&
|
||||
(onBoardingViewed === 'true' ||
|
||||
currentStep > OnboardingStep.ONBOARDING_ORDER_STEP);
|
||||
|
||||
const isOnboardingDialogNeeded =
|
||||
onBoardingViewed !== 'true' &&
|
||||
currentStep &&
|
||||
@@ -29,12 +57,58 @@ export const WelcomeDialog = () => {
|
||||
const marketId = useGlobalStore((store) => store.marketId);
|
||||
|
||||
const onClose = () => {
|
||||
const link = marketId
|
||||
? Links[Routes.MARKET](marketId)
|
||||
: Links[Routes.HOME]();
|
||||
navigate(link);
|
||||
update({ onBoardingDismissed: true });
|
||||
if (isTelemetryPopupNeeded) {
|
||||
closeTelemetry();
|
||||
} else {
|
||||
const link = marketId
|
||||
? Links[Routes.MARKET](marketId)
|
||||
: Links[Routes.HOME]();
|
||||
navigate(link);
|
||||
dismiss();
|
||||
}
|
||||
};
|
||||
|
||||
const [setToast, hasToast, removeToast] = useToasts((store) => [
|
||||
store.setToast,
|
||||
store.hasToast,
|
||||
store.remove,
|
||||
]);
|
||||
const onApprovalClose = useCallback(() => {
|
||||
closeTelemetry();
|
||||
removeToast(TELEMETRY_APPROVAL_TOAST_ID);
|
||||
}, [removeToast, closeTelemetry]);
|
||||
|
||||
const setTelemetryApprovalAndClose = useCallback(
|
||||
(value: string) => {
|
||||
setTelemetryValue(value);
|
||||
onApprovalClose();
|
||||
},
|
||||
[setTelemetryValue, onApprovalClose]
|
||||
);
|
||||
|
||||
if (isTelemetryPopupNeeded) {
|
||||
const toast: Toast = {
|
||||
id: TELEMETRY_APPROVAL_TOAST_ID,
|
||||
intent: Intent.Primary,
|
||||
content: (
|
||||
<>
|
||||
<h3 className="text-sm uppercase mb-1">
|
||||
{t('Improve vega console')}
|
||||
</h3>
|
||||
<TelemetryApproval
|
||||
telemetryValue={telemetryValue}
|
||||
setTelemetryValue={setTelemetryApprovalAndClose}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
onClose: onApprovalClose,
|
||||
};
|
||||
if (!hasToast(TELEMETRY_APPROVAL_TOAST_ID)) {
|
||||
setToast(toast);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const title = (
|
||||
<span className="font-alpha calt" data-testid="welcome-title">
|
||||
{t('Console')}{' '}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Intent, TradingButton } from '@vegaprotocol/ui-toolkit';
|
||||
import { TradingButton } from '@vegaprotocol/ui-toolkit';
|
||||
import { ViewType, useSidebar } from '../sidebar';
|
||||
|
||||
export const WithdrawalsMenu = () => {
|
||||
@@ -7,7 +7,6 @@ export const WithdrawalsMenu = () => {
|
||||
|
||||
return (
|
||||
<TradingButton
|
||||
intent={Intent.Primary}
|
||||
size="extra-small"
|
||||
onClick={() => setView({ type: ViewType.Withdraw })}
|
||||
data-testid="withdraw-dialog-button"
|
||||
|
||||
@@ -1,19 +1,40 @@
|
||||
import { renderHook, act, waitFor } from '@testing-library/react';
|
||||
import { useLocalStorage } from '@vegaprotocol/react-helpers';
|
||||
import { SentryInit, SentryClose } from '@vegaprotocol/logger';
|
||||
import { STORAGE_KEY, useTelemetryApproval } from './use-telemetry-approval';
|
||||
import {
|
||||
STORAGE_KEY,
|
||||
STORAGE_SECOND_KEY,
|
||||
useTelemetryApproval,
|
||||
} from './use-telemetry-approval';
|
||||
import { Networks } from '@vegaprotocol/environment';
|
||||
|
||||
const mockSetValue = jest.fn();
|
||||
const mockRemoveValue = jest.fn();
|
||||
let mockStorageHookApprovalResult: [string | null, jest.Mock] = [
|
||||
null,
|
||||
mockSetValue,
|
||||
];
|
||||
const mockSetSecondValue = jest.fn();
|
||||
let mockStorageHookViewedResult: [string | null, jest.Mock] = [
|
||||
null,
|
||||
mockSetSecondValue,
|
||||
];
|
||||
jest.mock('@vegaprotocol/logger');
|
||||
jest.mock('@vegaprotocol/react-helpers', () => ({
|
||||
...jest.requireActual('@vegaprotocol/react-helpers'),
|
||||
useLocalStorage: jest
|
||||
.fn()
|
||||
.mockImplementation(() => [false, mockSetValue, mockRemoveValue]),
|
||||
useLocalStorage: jest.fn((key: string) => {
|
||||
if (key === 'vega_telemetry_approval') {
|
||||
return mockStorageHookApprovalResult;
|
||||
}
|
||||
return mockStorageHookViewedResult;
|
||||
}),
|
||||
}));
|
||||
let mockVegaEnv = 'test';
|
||||
jest.mock('@vegaprotocol/environment', () => ({
|
||||
useEnvironment: () => ({ VEGA_ENV: 'test', SENTRY_DSN: 'sentry-dsn' }),
|
||||
...jest.requireActual('@vegaprotocol/environment'),
|
||||
useEnvironment: jest.fn(() => ({
|
||||
VEGA_ENV: mockVegaEnv,
|
||||
SENTRY_DSN: 'sentry-dsn',
|
||||
})),
|
||||
}));
|
||||
|
||||
describe('useTelemetryApproval', () => {
|
||||
@@ -21,32 +42,71 @@ describe('useTelemetryApproval', () => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('hook should return proper array', () => {
|
||||
it('when empty hook should return proper array', () => {
|
||||
const { result } = renderHook(() => useTelemetryApproval());
|
||||
expect(result.current[0]).toEqual(false);
|
||||
expect(result.current[0]).toEqual('');
|
||||
expect(result.current[1]).toEqual(expect.any(Function));
|
||||
expect(result.current[2]).toEqual(true);
|
||||
expect(result.current[3]).toEqual(expect.any(Function));
|
||||
expect(useLocalStorage).toHaveBeenCalledWith(STORAGE_KEY);
|
||||
expect(useLocalStorage).toHaveBeenCalledWith(STORAGE_SECOND_KEY);
|
||||
expect(mockSetValue).toHaveBeenCalledWith('true');
|
||||
expect(mockSetSecondValue).not.toHaveBeenCalledWith('true');
|
||||
});
|
||||
|
||||
it('when approval not empty but viewed is empty should return proper array', () => {
|
||||
mockStorageHookApprovalResult = ['false', mockSetValue];
|
||||
const { result } = renderHook(() => useTelemetryApproval());
|
||||
expect(result.current[0]).toEqual('false');
|
||||
expect(result.current[1]).toEqual(expect.any(Function));
|
||||
expect(result.current[2]).toEqual(true);
|
||||
expect(result.current[3]).toEqual(expect.any(Function));
|
||||
expect(useLocalStorage).toHaveBeenCalledWith(STORAGE_KEY);
|
||||
expect(useLocalStorage).toHaveBeenCalledWith(STORAGE_SECOND_KEY);
|
||||
expect(mockSetValue).not.toHaveBeenCalled();
|
||||
expect(mockSetSecondValue).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('when NOT empty hook should return proper array', () => {
|
||||
mockStorageHookApprovalResult = ['false', mockSetValue];
|
||||
mockStorageHookViewedResult = ['true', mockSetSecondValue];
|
||||
const { result } = renderHook(() => useTelemetryApproval());
|
||||
expect(result.current[0]).toEqual('false');
|
||||
expect(result.current[1]).toEqual(expect.any(Function));
|
||||
expect(result.current[2]).toEqual(false);
|
||||
expect(result.current[3]).toEqual(expect.any(Function));
|
||||
expect(useLocalStorage).toHaveBeenCalledWith(STORAGE_KEY);
|
||||
expect(mockSetValue).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('on mainnet hook should init properly', () => {
|
||||
mockStorageHookApprovalResult = [null, mockSetValue];
|
||||
mockVegaEnv = Networks.MAINNET;
|
||||
renderHook(() => useTelemetryApproval());
|
||||
expect(mockSetValue).toHaveBeenCalledWith('false');
|
||||
});
|
||||
|
||||
it('hook should init stuff properly', async () => {
|
||||
const { result } = renderHook(() => useTelemetryApproval());
|
||||
await act(() => {
|
||||
result.current[1](true);
|
||||
result.current[1]('true');
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(SentryInit).toHaveBeenCalled();
|
||||
expect(mockSetValue).toHaveBeenCalledWith('1');
|
||||
expect(mockSetValue).toHaveBeenCalledWith('true');
|
||||
expect(mockSetSecondValue).toHaveBeenCalledWith('true');
|
||||
});
|
||||
});
|
||||
|
||||
it('hook should close stuff properly', async () => {
|
||||
const { result } = renderHook(() => useTelemetryApproval());
|
||||
await act(() => {
|
||||
result.current[1](false);
|
||||
result.current[1]('false');
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(SentryClose).toHaveBeenCalled();
|
||||
expect(mockRemoveValue).toHaveBeenCalledWith();
|
||||
expect(mockSetValue).toHaveBeenCalledWith('false');
|
||||
expect(mockSetSecondValue).toHaveBeenCalledWith('true');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,25 +1,51 @@
|
||||
import { useLocalStorage } from '@vegaprotocol/react-helpers';
|
||||
import { useCallback } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { SentryInit, SentryClose } from '@vegaprotocol/logger';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { Networks, useEnvironment } from '@vegaprotocol/environment';
|
||||
|
||||
export const STORAGE_KEY = 'vega_telemetry_approval';
|
||||
export const STORAGE_SECOND_KEY = 'vega_telemetry_viewed';
|
||||
|
||||
export const useTelemetryApproval = (): [
|
||||
value: boolean,
|
||||
setValue: (value: boolean) => void
|
||||
value: string,
|
||||
setValue: (value: string) => void,
|
||||
shouldOpen: boolean,
|
||||
close: () => void
|
||||
] => {
|
||||
const { VEGA_ENV, SENTRY_DSN } = useEnvironment();
|
||||
const [value, setValue, removeValue] = useLocalStorage(STORAGE_KEY);
|
||||
const setApprove = useCallback(
|
||||
(value: boolean) => {
|
||||
if (value && SENTRY_DSN) {
|
||||
const defaultTelemetryValue =
|
||||
VEGA_ENV === Networks.MAINNET ? 'false' : 'true';
|
||||
const [value, setValue] = useLocalStorage(STORAGE_KEY);
|
||||
const [viewedValue, setViewedValue] = useLocalStorage(STORAGE_SECOND_KEY);
|
||||
const [shouldOpen, setShouldOpen] = useState(!value || !viewedValue);
|
||||
const close = useCallback(() => {
|
||||
setShouldOpen(false);
|
||||
setViewedValue('true');
|
||||
}, [setViewedValue]);
|
||||
const manageValue = useCallback(
|
||||
(value: string) => {
|
||||
if (value === 'true' && SENTRY_DSN) {
|
||||
SentryInit(SENTRY_DSN, VEGA_ENV);
|
||||
return setValue('1');
|
||||
return setValue('true');
|
||||
}
|
||||
SentryClose();
|
||||
removeValue();
|
||||
setValue('false');
|
||||
},
|
||||
[setValue, removeValue, SENTRY_DSN, VEGA_ENV]
|
||||
[setValue, SENTRY_DSN, VEGA_ENV]
|
||||
);
|
||||
return [Boolean(value), setApprove];
|
||||
const setTelemetryValue = useCallback(
|
||||
(value: string) => {
|
||||
setShouldOpen(false);
|
||||
setViewedValue('true');
|
||||
manageValue(value);
|
||||
},
|
||||
[manageValue, setViewedValue]
|
||||
);
|
||||
useEffect(() => {
|
||||
if (!value) {
|
||||
manageValue(defaultTelemetryValue);
|
||||
}
|
||||
}, [value, manageValue, defaultTelemetryValue]);
|
||||
|
||||
return [value || '', setTelemetryValue, shouldOpen, close];
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ENV } from '@vegaprotocol/environment';
|
||||
import { FLAGS } from '@vegaprotocol/environment';
|
||||
import {
|
||||
JsonRpcConnector,
|
||||
ViewConnector,
|
||||
@@ -18,10 +18,9 @@ if (typeof window !== 'undefined') {
|
||||
view = new ViewConnector();
|
||||
}
|
||||
|
||||
export const snap = new SnapConnector(
|
||||
ENV.VEGA_URL ? new URL(ENV.VEGA_URL).origin : undefined,
|
||||
DEFAULT_SNAP_ID
|
||||
);
|
||||
export const snap = FLAGS.METAMASK_SNAPS
|
||||
? new SnapConnector(DEFAULT_SNAP_ID)
|
||||
: undefined;
|
||||
|
||||
export const Connectors = {
|
||||
injected,
|
||||
|
||||
@@ -4,7 +4,6 @@ import produce from 'immer';
|
||||
|
||||
interface GlobalStore {
|
||||
marketId: string | null;
|
||||
onBoardingDismissed: boolean;
|
||||
eagerConnecting: boolean;
|
||||
update: (store: Partial<Omit<GlobalStore, 'update'>>) => void;
|
||||
}
|
||||
@@ -16,7 +15,6 @@ interface PageTitleStore {
|
||||
|
||||
export const useGlobalStore = create<GlobalStore>()((set) => ({
|
||||
marketId: LocalStorage.getItem('marketId') || null,
|
||||
onBoardingDismissed: false,
|
||||
eagerConnecting: false,
|
||||
update: (newState) => {
|
||||
set(
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"jsc": {
|
||||
"target": "es2017",
|
||||
"parser": {
|
||||
"syntax": "typescript",
|
||||
"decorators": true,
|
||||
"dynamicImport": true
|
||||
},
|
||||
"transform": {
|
||||
"decoratorMetadata": true,
|
||||
"legacyDecorator": true
|
||||
},
|
||||
"keepClassNames": true,
|
||||
"externalHelpers": true,
|
||||
"loose": true
|
||||
},
|
||||
"module": {
|
||||
"type": "commonjs",
|
||||
"strict": true,
|
||||
"noInterop": true
|
||||
},
|
||||
"sourceMaps": true,
|
||||
"exclude": ["jest.config.ts",".*\\.spec.tsx?$",".*\\.test.tsx?$","./src/jest-setup.ts$","./**/jest-setup.ts$",".*.js$"]
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"name": "@vegaprotocol/accounts",
|
||||
"version": "0.0.1"
|
||||
}
|
||||
@@ -5,26 +5,6 @@
|
||||
"projectType": "library",
|
||||
"tags": [],
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "@nx/rollup:rollup",
|
||||
"outputs": ["{options.outputPath}"],
|
||||
"options": {
|
||||
"outputPath": "dist/libs/accounts",
|
||||
"tsConfig": "libs/accounts/tsconfig.lib.json",
|
||||
"project": "libs/accounts/package.json",
|
||||
"entryFile": "libs/accounts/src/index.ts",
|
||||
"external": ["react", "react-dom", "react/jsx-runtime"],
|
||||
"rollupConfig": "@nx/react/plugins/bundle-rollup",
|
||||
"compiler": "babel",
|
||||
"assets": [
|
||||
{
|
||||
"glob": "libs/accounts/README.md",
|
||||
"input": ".",
|
||||
"output": "."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/linter:eslint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"jsc": {
|
||||
"target": "es2017",
|
||||
"parser": {
|
||||
"syntax": "typescript",
|
||||
"decorators": true,
|
||||
"dynamicImport": true
|
||||
},
|
||||
"transform": {
|
||||
"decoratorMetadata": true,
|
||||
"legacyDecorator": true
|
||||
},
|
||||
"keepClassNames": true,
|
||||
"externalHelpers": true,
|
||||
"loose": true
|
||||
},
|
||||
"module": {
|
||||
"type": "commonjs",
|
||||
"strict": true,
|
||||
"noInterop": true
|
||||
},
|
||||
"sourceMaps": true,
|
||||
"exclude": ["jest.config.ts",".*\\.spec.tsx?$",".*\\.test.tsx?$","./src/jest-setup.ts$","./**/jest-setup.ts$",".*.js$"]
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"name": "@vegaprotocol/announcements",
|
||||
"version": "0.0.2",
|
||||
"peerDependencies": {
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0"
|
||||
}
|
||||
}
|
||||
@@ -5,26 +5,6 @@
|
||||
"projectType": "library",
|
||||
"tags": [],
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "@nx/rollup:rollup",
|
||||
"outputs": ["{options.outputPath}"],
|
||||
"options": {
|
||||
"outputPath": "dist/libs/announcements",
|
||||
"tsConfig": "libs/announcements/tsconfig.lib.json",
|
||||
"project": "libs/announcements/package.json",
|
||||
"entryFile": "libs/announcements/src/index.ts",
|
||||
"external": ["react/jsx-runtime"],
|
||||
"rollupConfig": "@nx/react/plugins/bundle-rollup",
|
||||
"compiler": "babel",
|
||||
"assets": [
|
||||
{
|
||||
"glob": "libs/announcements/README.md",
|
||||
"input": ".",
|
||||
"output": "."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/linter:eslint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"jsc": {
|
||||
"target": "es2017",
|
||||
"parser": {
|
||||
"syntax": "typescript",
|
||||
"decorators": true,
|
||||
"dynamicImport": true
|
||||
},
|
||||
"transform": {
|
||||
"decoratorMetadata": true,
|
||||
"legacyDecorator": true
|
||||
},
|
||||
"keepClassNames": true,
|
||||
"externalHelpers": true,
|
||||
"loose": true
|
||||
},
|
||||
"module": {
|
||||
"type": "commonjs",
|
||||
"strict": true,
|
||||
"noInterop": true
|
||||
},
|
||||
"sourceMaps": true,
|
||||
"exclude": ["jest.config.ts",".*\\.spec.tsx?$",".*\\.test.tsx?$","./src/jest-setup.ts$","./**/jest-setup.ts$",".*.js$"]
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"name": "@vegaprotocol/apollo-client",
|
||||
"version": "0.0.1"
|
||||
}
|
||||
@@ -4,16 +4,6 @@
|
||||
"sourceRoot": "libs/apollo-client/src",
|
||||
"projectType": "library",
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "@nx/js:tsc",
|
||||
"outputs": ["{options.outputPath}"],
|
||||
"options": {
|
||||
"outputPath": "dist/libs/apollo-client",
|
||||
"main": "libs/apollo-client/src/index.ts",
|
||||
"tsConfig": "libs/apollo-client/tsconfig.lib.json",
|
||||
"assets": ["libs/apollo-client/*.md"]
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/linter:eslint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"jsc": {
|
||||
"target": "es2017",
|
||||
"parser": {
|
||||
"syntax": "typescript",
|
||||
"decorators": true,
|
||||
"dynamicImport": true
|
||||
},
|
||||
"transform": {
|
||||
"decoratorMetadata": true,
|
||||
"legacyDecorator": true
|
||||
},
|
||||
"keepClassNames": true,
|
||||
"externalHelpers": true,
|
||||
"loose": true
|
||||
},
|
||||
"module": {
|
||||
"type": "commonjs",
|
||||
"strict": true,
|
||||
"noInterop": true
|
||||
},
|
||||
"sourceMaps": true,
|
||||
"exclude": ["jest.config.ts",".*\\.spec.tsx?$",".*\\.test.tsx?$","./src/jest-setup.ts$","./**/jest-setup.ts$",".*.js$"]
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"name": "@vegaprotocol/assets",
|
||||
"version": "0.0.1"
|
||||
}
|
||||
@@ -5,26 +5,6 @@
|
||||
"projectType": "library",
|
||||
"tags": [],
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "@nx/rollup:rollup",
|
||||
"outputs": ["{options.outputPath}"],
|
||||
"options": {
|
||||
"outputPath": "dist/libs/assets",
|
||||
"tsConfig": "libs/assets/tsconfig.lib.json",
|
||||
"project": "libs/assets/package.json",
|
||||
"entryFile": "libs/assets/src/index.ts",
|
||||
"external": ["react/jsx-runtime"],
|
||||
"rollupConfig": "@nx/react/plugins/bundle-rollup",
|
||||
"compiler": "babel",
|
||||
"assets": [
|
||||
{
|
||||
"glob": "libs/assets/README.md",
|
||||
"input": ".",
|
||||
"output": "."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/linter:eslint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
|
||||
@@ -97,7 +97,7 @@ export const AssetDetailsDialog = ({
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
<p className="text-sm my-4">
|
||||
<p className="text-xs my-4">
|
||||
{t(
|
||||
'There is 1 unit of the settlement asset (%s) to every 1 quote unit.',
|
||||
[assetSymbol]
|
||||
|
||||
@@ -3,7 +3,11 @@ import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import type * as Schema from '@vegaprotocol/types';
|
||||
import type { KeyValueTableRowProps } from '@vegaprotocol/ui-toolkit';
|
||||
import { CopyWithTooltip, Icon } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
CopyWithTooltip,
|
||||
Icon,
|
||||
truncateMiddle,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
KeyValueTable,
|
||||
KeyValueTableRow,
|
||||
@@ -56,7 +60,7 @@ export const rows: Rows = [
|
||||
key: AssetDetail.ID,
|
||||
label: t('ID'),
|
||||
tooltip: '',
|
||||
value: (asset) => asset.id,
|
||||
value: (asset) => truncateMiddle(asset.id),
|
||||
},
|
||||
{
|
||||
key: AssetDetail.TYPE,
|
||||
@@ -109,7 +113,9 @@ export const rows: Rows = [
|
||||
|
||||
return (
|
||||
<>
|
||||
<EtherscanLink address={asset.source.contractAddress} />{' '}
|
||||
<EtherscanLink address={asset.source.contractAddress}>
|
||||
{truncateMiddle(asset.source.contractAddress)}
|
||||
</EtherscanLink>{' '}
|
||||
<CopyWithTooltip text={asset.source.contractAddress}>
|
||||
<button title={t('Copy address to clipboard')}>
|
||||
<Icon size={3} name="duplicate" />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { TradingOption } from '@vegaprotocol/ui-toolkit';
|
||||
import { TradingOption, truncateMiddle } from '@vegaprotocol/ui-toolkit';
|
||||
import type { AssetFieldsFragment } from './__generated__/Asset';
|
||||
import classNames from 'classnames';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
@@ -45,7 +45,7 @@ export const AssetOption = ({ asset, balance }: AssetOptionProps) => {
|
||||
{balance}
|
||||
<div className="text-[12px] font-mono w-full text-left break-all">
|
||||
<span className="text-vega-light-300 dark:text-vega-dark-300">
|
||||
{asset.id}
|
||||
{truncateMiddle(asset.id)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"jsc": {
|
||||
"target": "es2017",
|
||||
"parser": {
|
||||
"syntax": "typescript",
|
||||
"decorators": true,
|
||||
"dynamicImport": true
|
||||
},
|
||||
"transform": {
|
||||
"decoratorMetadata": true,
|
||||
"legacyDecorator": true
|
||||
},
|
||||
"keepClassNames": true,
|
||||
"externalHelpers": true,
|
||||
"loose": true
|
||||
},
|
||||
"module": {
|
||||
"type": "commonjs",
|
||||
"strict": true,
|
||||
"noInterop": true
|
||||
},
|
||||
"sourceMaps": true,
|
||||
"exclude": ["jest.config.ts",".*\\.spec.tsx?$",".*\\.test.tsx?$","./src/jest-setup.ts$","./**/jest-setup.ts$",".*.js$"]
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"name": "@vegaprotocol/candles-chart",
|
||||
"version": "0.0.1"
|
||||
}
|
||||
@@ -5,26 +5,6 @@
|
||||
"projectType": "library",
|
||||
"tags": [],
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "@nx/rollup:rollup",
|
||||
"outputs": ["{options.outputPath}"],
|
||||
"options": {
|
||||
"outputPath": "dist/libs/candles-chart",
|
||||
"tsConfig": "libs/candles-chart/tsconfig.lib.json",
|
||||
"project": "libs/candles-chart/package.json",
|
||||
"entryFile": "libs/candles-chart/src/index.ts",
|
||||
"external": ["react/jsx-runtime"],
|
||||
"rollupConfig": "@nx/react/plugins/bundle-rollup",
|
||||
"compiler": "babel",
|
||||
"assets": [
|
||||
{
|
||||
"glob": "libs/candles-chart/README.md",
|
||||
"input": ".",
|
||||
"output": "."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/linter:eslint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
|
||||
@@ -7,8 +7,8 @@ describe('CandlesMenu', () => {
|
||||
render(<CandlesMenu />);
|
||||
|
||||
await userEvent.click(
|
||||
screen.getByText('Studies', {
|
||||
selector: '[type="button"]',
|
||||
screen.getByRole('button', {
|
||||
name: 'Studies',
|
||||
})
|
||||
);
|
||||
expect(await screen.findByRole('menu')).toBeInTheDocument();
|
||||
|
||||
@@ -10,13 +10,14 @@ import {
|
||||
studyLabels,
|
||||
} from 'pennant';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItemIndicator,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuTrigger,
|
||||
TradingButton,
|
||||
TradingDropdown,
|
||||
TradingDropdownCheckboxItem,
|
||||
TradingDropdownContent,
|
||||
TradingDropdownItemIndicator,
|
||||
TradingDropdownRadioGroup,
|
||||
TradingDropdownRadioItem,
|
||||
TradingDropdownTrigger,
|
||||
Icon,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import type { IconName } from '@blueprintjs/icons';
|
||||
@@ -44,69 +45,76 @@ export const CandlesMenu = () => {
|
||||
} = useCandlesChartSettings();
|
||||
const triggerClasses = 'text-xs';
|
||||
const contentAlign = 'end';
|
||||
const triggerButtonProps = { size: 'extra-small' } as const;
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenu
|
||||
<TradingDropdown
|
||||
trigger={
|
||||
<DropdownMenuTrigger className={triggerClasses}>
|
||||
{t(`Interval: ${intervalLabels[interval]}`)}
|
||||
</DropdownMenuTrigger>
|
||||
<TradingDropdownTrigger className={triggerClasses}>
|
||||
<TradingButton {...triggerButtonProps}>
|
||||
{t(`Interval: ${intervalLabels[interval]}`)}
|
||||
</TradingButton>
|
||||
</TradingDropdownTrigger>
|
||||
}
|
||||
>
|
||||
<DropdownMenuContent align={contentAlign}>
|
||||
<DropdownMenuRadioGroup
|
||||
<TradingDropdownContent align={contentAlign}>
|
||||
<TradingDropdownRadioGroup
|
||||
value={interval}
|
||||
onValueChange={(value) => {
|
||||
setInterval(value as Interval);
|
||||
}}
|
||||
>
|
||||
{Object.values(Interval).map((timeInterval) => (
|
||||
<DropdownMenuRadioItem
|
||||
<TradingDropdownRadioItem
|
||||
key={timeInterval}
|
||||
inset
|
||||
value={timeInterval}
|
||||
>
|
||||
{intervalLabels[timeInterval]}
|
||||
<DropdownMenuItemIndicator />
|
||||
</DropdownMenuRadioItem>
|
||||
<TradingDropdownItemIndicator />
|
||||
</TradingDropdownRadioItem>
|
||||
))}
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<DropdownMenu
|
||||
</TradingDropdownRadioGroup>
|
||||
</TradingDropdownContent>
|
||||
</TradingDropdown>
|
||||
<TradingDropdown
|
||||
trigger={
|
||||
<DropdownMenuTrigger className={triggerClasses}>
|
||||
<Icon name={chartTypeIcon.get(chartType) as IconName} />
|
||||
</DropdownMenuTrigger>
|
||||
<TradingDropdownTrigger className={triggerClasses}>
|
||||
<TradingButton {...triggerButtonProps}>
|
||||
<Icon name={chartTypeIcon.get(chartType) as IconName} />
|
||||
</TradingButton>
|
||||
</TradingDropdownTrigger>
|
||||
}
|
||||
>
|
||||
<DropdownMenuContent align={contentAlign}>
|
||||
<DropdownMenuRadioGroup
|
||||
<TradingDropdownContent align={contentAlign}>
|
||||
<TradingDropdownRadioGroup
|
||||
value={chartType}
|
||||
onValueChange={(value) => {
|
||||
setType(value as ChartType);
|
||||
}}
|
||||
>
|
||||
{Object.values(ChartType).map((type) => (
|
||||
<DropdownMenuRadioItem key={type} inset value={type}>
|
||||
<TradingDropdownRadioItem key={type} inset value={type}>
|
||||
{chartTypeLabels[type]}
|
||||
<DropdownMenuItemIndicator />
|
||||
</DropdownMenuRadioItem>
|
||||
<TradingDropdownItemIndicator />
|
||||
</TradingDropdownRadioItem>
|
||||
))}
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<DropdownMenu
|
||||
</TradingDropdownRadioGroup>
|
||||
</TradingDropdownContent>
|
||||
</TradingDropdown>
|
||||
<TradingDropdown
|
||||
trigger={
|
||||
<DropdownMenuTrigger className={triggerClasses}>
|
||||
{t('Overlays')}
|
||||
</DropdownMenuTrigger>
|
||||
<TradingDropdownTrigger className={triggerClasses}>
|
||||
<TradingButton {...triggerButtonProps}>
|
||||
{t('Overlays')}
|
||||
</TradingButton>
|
||||
</TradingDropdownTrigger>
|
||||
}
|
||||
>
|
||||
<DropdownMenuContent align={contentAlign}>
|
||||
<TradingDropdownContent align={contentAlign}>
|
||||
{Object.values(Overlay).map((overlay) => (
|
||||
<DropdownMenuCheckboxItem
|
||||
<TradingDropdownCheckboxItem
|
||||
key={overlay}
|
||||
checked={overlays.includes(overlay)}
|
||||
onCheckedChange={() => {
|
||||
@@ -121,21 +129,23 @@ export const CandlesMenu = () => {
|
||||
}}
|
||||
>
|
||||
{overlayLabels[overlay]}
|
||||
<DropdownMenuItemIndicator />
|
||||
</DropdownMenuCheckboxItem>
|
||||
<TradingDropdownItemIndicator />
|
||||
</TradingDropdownCheckboxItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<DropdownMenu
|
||||
</TradingDropdownContent>
|
||||
</TradingDropdown>
|
||||
<TradingDropdown
|
||||
trigger={
|
||||
<DropdownMenuTrigger className={triggerClasses}>
|
||||
{t('Studies')}
|
||||
</DropdownMenuTrigger>
|
||||
<TradingDropdownTrigger className={triggerClasses}>
|
||||
<TradingButton {...triggerButtonProps}>
|
||||
{t('Studies')}
|
||||
</TradingButton>
|
||||
</TradingDropdownTrigger>
|
||||
}
|
||||
>
|
||||
<DropdownMenuContent align={contentAlign}>
|
||||
<TradingDropdownContent align={contentAlign}>
|
||||
{Object.values(Study).map((study) => (
|
||||
<DropdownMenuCheckboxItem
|
||||
<TradingDropdownCheckboxItem
|
||||
key={study}
|
||||
checked={studies.includes(study)}
|
||||
onCheckedChange={() => {
|
||||
@@ -150,11 +160,11 @@ export const CandlesMenu = () => {
|
||||
}}
|
||||
>
|
||||
{studyLabels[study]}
|
||||
<DropdownMenuItemIndicator />
|
||||
</DropdownMenuCheckboxItem>
|
||||
<TradingDropdownItemIndicator />
|
||||
</TradingDropdownCheckboxItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</TradingDropdownContent>
|
||||
</TradingDropdown>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -65,6 +65,8 @@ export function addSetVegaWallet() {
|
||||
Cypress.Commands.add('setVegaWallet', () => {
|
||||
cy.window().then((win) => {
|
||||
win.localStorage.setItem('vega_onboarding_viewed', 'true');
|
||||
win.localStorage.setItem('vega_telemetry_approval', 'false');
|
||||
win.localStorage.setItem('vega_telemetry_viewed', 'true');
|
||||
win.localStorage.setItem(
|
||||
'vega_wallet_config',
|
||||
JSON.stringify({
|
||||
@@ -81,6 +83,8 @@ export function addSetOnBoardingViewed() {
|
||||
Cypress.Commands.add('setOnBoardingViewed', () => {
|
||||
cy.window().then((win) => {
|
||||
win.localStorage.setItem('vega_onboarding_viewed', 'true');
|
||||
win.localStorage.setItem('vega_telemetry_approval', 'false');
|
||||
win.localStorage.setItem('vega_telemetry_viewed', 'true');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"jsc": {
|
||||
"target": "es2017",
|
||||
"parser": {
|
||||
"syntax": "typescript",
|
||||
"decorators": true,
|
||||
"dynamicImport": true
|
||||
},
|
||||
"transform": {
|
||||
"decoratorMetadata": true,
|
||||
"legacyDecorator": true
|
||||
},
|
||||
"keepClassNames": true,
|
||||
"externalHelpers": true,
|
||||
"loose": true
|
||||
},
|
||||
"module": {
|
||||
"type": "commonjs",
|
||||
"strict": true,
|
||||
"noInterop": true
|
||||
},
|
||||
"sourceMaps": true,
|
||||
"exclude": ["jest.config.ts",".*\\.spec.tsx?$",".*\\.test.tsx?$","./src/jest-setup.ts$","./**/jest-setup.ts$",".*.js$"]
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"name": "@vegaprotocol/data-provider",
|
||||
"version": "0.0.1"
|
||||
}
|
||||
@@ -5,26 +5,6 @@
|
||||
"projectType": "library",
|
||||
"tags": [],
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "@nx/rollup:rollup",
|
||||
"outputs": ["{options.outputPath}"],
|
||||
"options": {
|
||||
"outputPath": "dist/libs/data-provider",
|
||||
"tsConfig": "libs/data-provider/tsconfig.lib.json",
|
||||
"project": "libs/data-provider/package.json",
|
||||
"entryFile": "libs/data-provider/src/index.ts",
|
||||
"external": ["react/jsx-runtime"],
|
||||
"rollupConfig": "@nx/react/plugins/bundle-rollup",
|
||||
"compiler": "babel",
|
||||
"assets": [
|
||||
{
|
||||
"glob": "libs/data-provider/README.md",
|
||||
"input": ".",
|
||||
"output": "."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/linter:eslint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"jsc": {
|
||||
"target": "es2017",
|
||||
"parser": {
|
||||
"syntax": "typescript",
|
||||
"decorators": true,
|
||||
"dynamicImport": true
|
||||
},
|
||||
"transform": {
|
||||
"decoratorMetadata": true,
|
||||
"legacyDecorator": true
|
||||
},
|
||||
"keepClassNames": true,
|
||||
"externalHelpers": true,
|
||||
"loose": true
|
||||
},
|
||||
"module": {
|
||||
"type": "commonjs",
|
||||
"strict": true,
|
||||
"noInterop": true
|
||||
},
|
||||
"sourceMaps": true,
|
||||
"exclude": ["jest.config.ts",".*\\.spec.tsx?$",".*\\.test.tsx?$","./src/jest-setup.ts$","./**/jest-setup.ts$",".*.js$"]
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"name": "@vegaprotocol/datagrid",
|
||||
"version": "0.0.1"
|
||||
}
|
||||
@@ -5,26 +5,6 @@
|
||||
"projectType": "library",
|
||||
"tags": [],
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "@nx/rollup:rollup",
|
||||
"outputs": ["{options.outputPath}"],
|
||||
"options": {
|
||||
"outputPath": "dist/libs/datagrid",
|
||||
"tsConfig": "libs/datagrid/tsconfig.lib.json",
|
||||
"project": "libs/datagrid/package.json",
|
||||
"entryFile": "libs/datagrid/src/index.ts",
|
||||
"external": ["react/jsx-runtime"],
|
||||
"rollupConfig": "@nx/react/plugins/bundle-rollup",
|
||||
"compiler": "babel",
|
||||
"assets": [
|
||||
{
|
||||
"glob": "libs/datagrid/README.md",
|
||||
"input": ".",
|
||||
"output": "."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/linter:eslint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"jsc": {
|
||||
"target": "es2017",
|
||||
"parser": {
|
||||
"syntax": "typescript",
|
||||
"decorators": true,
|
||||
"dynamicImport": true
|
||||
},
|
||||
"transform": {
|
||||
"decoratorMetadata": true,
|
||||
"legacyDecorator": true
|
||||
},
|
||||
"keepClassNames": true,
|
||||
"externalHelpers": true,
|
||||
"loose": true
|
||||
},
|
||||
"module": {
|
||||
"type": "commonjs",
|
||||
"strict": true,
|
||||
"noInterop": true
|
||||
},
|
||||
"sourceMaps": true,
|
||||
"exclude": ["jest.config.ts",".*\\.spec.tsx?$",".*\\.test.tsx?$","./src/jest-setup.ts$","./**/jest-setup.ts$",".*.js$"]
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"name": "@vegaprotocol/deal-ticket",
|
||||
"version": "0.0.1"
|
||||
}
|
||||
@@ -5,26 +5,6 @@
|
||||
"projectType": "library",
|
||||
"tags": [],
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "@nx/rollup:rollup",
|
||||
"outputs": ["{options.outputPath}"],
|
||||
"options": {
|
||||
"outputPath": "dist/libs/deal-ticket",
|
||||
"tsConfig": "libs/deal-ticket/tsconfig.lib.json",
|
||||
"project": "libs/deal-ticket/package.json",
|
||||
"entryFile": "libs/deal-ticket/src/index.ts",
|
||||
"external": ["react/jsx-runtime"],
|
||||
"rollupConfig": "@nx/react/plugins/bundle-rollup",
|
||||
"compiler": "babel",
|
||||
"assets": [
|
||||
{
|
||||
"glob": "libs/deal-ticket/README.md",
|
||||
"input": ".",
|
||||
"output": "."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/linter:eslint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { Side } from '@vegaprotocol/types';
|
||||
import classNames from 'classnames';
|
||||
|
||||
interface Props {
|
||||
side: Side;
|
||||
label?: string;
|
||||
}
|
||||
|
||||
export const DealTicketButton = ({ side, label }: Props) => {
|
||||
const buttonClasses = classNames(
|
||||
'px-10 py-2 uppercase rounded-md text-white w-full',
|
||||
{
|
||||
'bg-market-red': side === Side.SIDE_SELL,
|
||||
'bg-market-green-550': side === Side.SIDE_BUY,
|
||||
}
|
||||
);
|
||||
return (
|
||||
<div className="mb-2">
|
||||
<button type="submit" data-testid="place-order" className={buttonClasses}>
|
||||
{label || t('Place order')}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,7 +1,4 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { Tooltip } from '@vegaprotocol/ui-toolkit';
|
||||
import classnames from 'classnames';
|
||||
import type { ReactNode } from 'react';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { FeesBreakdown } from '@vegaprotocol/markets';
|
||||
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
|
||||
@@ -16,7 +13,6 @@ import { marketMarginDataProvider } from '@vegaprotocol/accounts';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
|
||||
import {
|
||||
NOTIONAL_SIZE_TOOLTIP_TEXT,
|
||||
MARGIN_DIFF_TOOLTIP_TEXT,
|
||||
DEDUCTION_FROM_COLLATERAL_TOOLTIP_TEXT,
|
||||
TOTAL_MARGIN_AVAILABLE,
|
||||
@@ -25,114 +21,54 @@ import {
|
||||
MARGIN_ACCOUNT_TOOLTIP_TEXT,
|
||||
} from '../../constants';
|
||||
import { useEstimateFees } from '../../hooks';
|
||||
import { KeyValue } from './key-value';
|
||||
|
||||
const emptyValue = '-';
|
||||
|
||||
export interface DealTicketFeeDetailPros {
|
||||
label: string;
|
||||
value?: string | null | undefined;
|
||||
symbol: string;
|
||||
indent?: boolean | undefined;
|
||||
labelDescription?: ReactNode;
|
||||
formattedValue?: string;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
export const DealTicketFeeDetail = ({
|
||||
label,
|
||||
value,
|
||||
labelDescription,
|
||||
symbol,
|
||||
indent,
|
||||
onClick,
|
||||
formattedValue,
|
||||
}: DealTicketFeeDetailPros) => {
|
||||
const displayValue = `${formattedValue ?? '-'} ${symbol || ''}`;
|
||||
const valueElement = onClick ? (
|
||||
<button onClick={onClick} className="text-muted">
|
||||
{displayValue}
|
||||
</button>
|
||||
) : (
|
||||
<div className="text-muted">{displayValue}</div>
|
||||
);
|
||||
return (
|
||||
<div
|
||||
data-testid={
|
||||
'deal-ticket-fee-' + label.toLocaleLowerCase().replace(/\s/g, '-')
|
||||
}
|
||||
key={typeof label === 'string' ? label : 'value-dropdown'}
|
||||
className={classnames(
|
||||
'text-xs mt-2 flex justify-between items-center gap-4 flex-wrap',
|
||||
{ 'ml-2': indent }
|
||||
)}
|
||||
>
|
||||
<Tooltip description={labelDescription}>
|
||||
<div>{label}</div>
|
||||
</Tooltip>
|
||||
<Tooltip description={`${value ?? '-'} ${symbol || ''}`}>
|
||||
{valueElement}
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export interface DealTicketFeeDetailsProps {
|
||||
assetSymbol: string;
|
||||
order: OrderSubmissionBody['orderSubmission'];
|
||||
market: Market;
|
||||
notionalSize: string | null;
|
||||
}
|
||||
|
||||
export const DealTicketFeeDetails = ({
|
||||
assetSymbol,
|
||||
order,
|
||||
market,
|
||||
notionalSize,
|
||||
}: DealTicketFeeDetailsProps) => {
|
||||
const feeEstimate = useEstimateFees(order);
|
||||
const { settlementAsset: asset } =
|
||||
market.tradableInstrument.instrument.product;
|
||||
const { decimals: assetDecimals, quantum } = asset;
|
||||
const marketDecimals = market.decimalPlaces;
|
||||
const quoteName = market.tradableInstrument.instrument.product.quoteName;
|
||||
|
||||
return (
|
||||
<>
|
||||
<DealTicketFeeDetail
|
||||
label={t('Notional')}
|
||||
value={formatValue(notionalSize, marketDecimals)}
|
||||
formattedValue={formatValue(notionalSize, marketDecimals)}
|
||||
symbol={quoteName}
|
||||
labelDescription={NOTIONAL_SIZE_TOOLTIP_TEXT(quoteName)}
|
||||
/>
|
||||
<DealTicketFeeDetail
|
||||
label={t('Fees')}
|
||||
value={
|
||||
feeEstimate?.totalFeeAmount &&
|
||||
`~${formatValue(feeEstimate?.totalFeeAmount, assetDecimals)}`
|
||||
}
|
||||
formattedValue={
|
||||
feeEstimate?.totalFeeAmount &&
|
||||
`~${formatValue(feeEstimate?.totalFeeAmount, assetDecimals, quantum)}`
|
||||
}
|
||||
labelDescription={
|
||||
<>
|
||||
<span>
|
||||
{t(
|
||||
`An estimate of the most you would be expected to pay in fees, in the market's settlement asset ${assetSymbol}.`
|
||||
)}
|
||||
</span>
|
||||
<FeesBreakdown
|
||||
fees={feeEstimate?.fees}
|
||||
feeFactors={market.fees.factors}
|
||||
symbol={assetSymbol}
|
||||
decimals={assetDecimals}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
symbol={assetSymbol}
|
||||
/>
|
||||
</>
|
||||
<KeyValue
|
||||
label={t('Fees')}
|
||||
value={
|
||||
feeEstimate?.totalFeeAmount &&
|
||||
`~${formatValue(feeEstimate?.totalFeeAmount, assetDecimals)}`
|
||||
}
|
||||
formattedValue={
|
||||
feeEstimate?.totalFeeAmount &&
|
||||
`~${formatValue(feeEstimate?.totalFeeAmount, assetDecimals, quantum)}`
|
||||
}
|
||||
labelDescription={
|
||||
<>
|
||||
<span>
|
||||
{t(
|
||||
`An estimate of the most you would be expected to pay in fees, in the market's settlement asset ${assetSymbol}.`
|
||||
)}
|
||||
</span>
|
||||
<FeesBreakdown
|
||||
fees={feeEstimate?.fees}
|
||||
feeFactors={market.fees.factors}
|
||||
symbol={assetSymbol}
|
||||
decimals={assetDecimals}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
symbol={assetSymbol}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -209,7 +145,7 @@ export const DealTicketMarginDetails = ({
|
||||
BigInt(marginAccountBalance);
|
||||
|
||||
deductionFromCollateral = (
|
||||
<DealTicketFeeDetail
|
||||
<KeyValue
|
||||
indent
|
||||
label={t('Deduction from collateral')}
|
||||
value={formatRange(
|
||||
@@ -236,7 +172,7 @@ export const DealTicketMarginDetails = ({
|
||||
/>
|
||||
);
|
||||
projectedMargin = (
|
||||
<DealTicketFeeDetail
|
||||
<KeyValue
|
||||
label={t('Projected margin')}
|
||||
value={formatRange(
|
||||
marginEstimate?.bestCase.initialLevel,
|
||||
@@ -308,7 +244,7 @@ export const DealTicketMarginDetails = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<DealTicketFeeDetail
|
||||
<KeyValue
|
||||
label={t('Margin required')}
|
||||
value={formatRange(
|
||||
marginRequiredBestCase,
|
||||
@@ -324,7 +260,7 @@ export const DealTicketMarginDetails = ({
|
||||
labelDescription={MARGIN_DIFF_TOOLTIP_TEXT(assetSymbol)}
|
||||
symbol={assetSymbol}
|
||||
/>
|
||||
<DealTicketFeeDetail
|
||||
<KeyValue
|
||||
label={t('Total margin available')}
|
||||
indent
|
||||
value={formatValue(totalMarginAvailable, assetDecimals)}
|
||||
@@ -342,7 +278,7 @@ export const DealTicketMarginDetails = ({
|
||||
)}
|
||||
/>
|
||||
{deductionFromCollateral}
|
||||
<DealTicketFeeDetail
|
||||
<KeyValue
|
||||
label={t('Current margin allocation')}
|
||||
indent
|
||||
onClick={
|
||||
@@ -358,7 +294,7 @@ export const DealTicketMarginDetails = ({
|
||||
)}
|
||||
/>
|
||||
{projectedMargin}
|
||||
<DealTicketFeeDetail
|
||||
<KeyValue
|
||||
label={t('Liquidation price estimate')}
|
||||
value={liquidationPriceEstimate}
|
||||
formattedValue={liquidationPriceEstimate}
|
||||
|
||||
@@ -32,7 +32,7 @@ export const DealTicketSizeIceberg = ({
|
||||
const renderPeakSizeError = () => {
|
||||
if (peakSizeError) {
|
||||
return (
|
||||
<TradingInputError testId="deal-ticket-peak-error-message-size-limit">
|
||||
<TradingInputError testId="deal-ticket-peak-error-message">
|
||||
{peakSizeError}
|
||||
</TradingInputError>
|
||||
);
|
||||
@@ -44,7 +44,7 @@ export const DealTicketSizeIceberg = ({
|
||||
const renderMinimumSizeError = () => {
|
||||
if (minimumVisibleSizeError) {
|
||||
return (
|
||||
<TradingInputError testId="deal-ticket-minimum-error-message-size-limit">
|
||||
<TradingInputError testId="deal-ticket-minimum-error-message">
|
||||
{minimumVisibleSizeError}
|
||||
</TradingInputError>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { generateMarket } from '../../test-helpers';
|
||||
import { StopOrder } from './deal-ticket-stop-order';
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
useDealTicketFormValues,
|
||||
} from '../../hooks/use-form-values';
|
||||
import type { FeatureFlags } from '@vegaprotocol/environment';
|
||||
import { formatForInput } from '@vegaprotocol/utils';
|
||||
|
||||
jest.mock('zustand');
|
||||
jest.mock('./deal-ticket-fee-details', () => ({
|
||||
@@ -57,7 +58,7 @@ const orderSideBuy = 'order-side-SIDE_BUY';
|
||||
const orderSideSell = 'order-side-SIDE_SELL';
|
||||
|
||||
const triggerDirectionRisesAbove = 'triggerDirection-risesAbove';
|
||||
// const triggerDirectionFallsBelow = 'triggerDirection-fallsBelow';
|
||||
const triggerDirectionFallsBelow = 'triggerDirection-fallsBelow';
|
||||
|
||||
const expiryStrategySubmit = 'expiryStrategy-submit';
|
||||
const expiryStrategyCancel = 'expiryStrategy-cancel';
|
||||
@@ -65,6 +66,7 @@ const expiryStrategyCancel = 'expiryStrategy-cancel';
|
||||
const triggerTypePrice = 'triggerType-price';
|
||||
const triggerTypeTrailingPercentOffset = 'triggerType-trailingPercentOffset';
|
||||
|
||||
const oco = 'oco';
|
||||
const expire = 'expire';
|
||||
const datePicker = 'date-picker-field';
|
||||
const timeInForce = 'order-tif';
|
||||
@@ -76,6 +78,8 @@ const triggerPriceWarningMessage = 'stop-order-warning-message-trigger-price';
|
||||
const triggerTrailingPercentOffsetErrorMessage =
|
||||
'stop-order-error-message-trigger-trailing-percent-offset';
|
||||
|
||||
const ocoPostfix = (id: string, postfix = true) => (postfix ? `${id}-oco` : id);
|
||||
|
||||
describe('StopOrder', () => {
|
||||
beforeEach(() => {
|
||||
localStorage.clear();
|
||||
@@ -107,6 +111,7 @@ describe('StopOrder', () => {
|
||||
'checked'
|
||||
);
|
||||
expect(screen.getByTestId(expire).dataset.state).toEqual('unchecked');
|
||||
expect(screen.getByTestId(oco).dataset.state).toEqual('unchecked');
|
||||
await userEvent.click(screen.getByTestId(expire));
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId(expiryStrategySubmit).dataset.state).toEqual(
|
||||
@@ -115,6 +120,32 @@ describe('StopOrder', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('calculate notional for market limit', async () => {
|
||||
render(generateJsx());
|
||||
await userEvent.type(screen.getByTestId(sizeInput), '10');
|
||||
await userEvent.type(screen.getByTestId(priceInput), '10');
|
||||
expect(screen.getByTestId('deal-ticket-fee-notional')).toHaveTextContent(
|
||||
'Notional100.00 BTC'
|
||||
);
|
||||
});
|
||||
|
||||
it('calculates notional for limit order', async () => {
|
||||
render(generateJsx());
|
||||
await userEvent.click(screen.getByTestId(orderTypeTrigger));
|
||||
await userEvent.click(screen.getByTestId(orderTypeMarket));
|
||||
await userEvent.type(screen.getByTestId(sizeInput), '10');
|
||||
// price trigger is selected but it's empty, calculate base on size and marketPrice prop
|
||||
expect(screen.getByTestId('deal-ticket-fee-notional')).toHaveTextContent(
|
||||
'Notional20.00 BTC'
|
||||
);
|
||||
|
||||
await userEvent.type(screen.getByTestId(triggerPriceInput), '3');
|
||||
// calculate base on size and price trigger
|
||||
expect(screen.getByTestId('deal-ticket-fee-notional')).toHaveTextContent(
|
||||
'Notional30.00 BTC'
|
||||
);
|
||||
});
|
||||
|
||||
it('should use local storage state for initial values', async () => {
|
||||
const values: Partial<StopOrderFormValues> = {
|
||||
type: Schema.OrderType.TYPE_LIMIT,
|
||||
@@ -125,6 +156,11 @@ describe('StopOrder', () => {
|
||||
expire: true,
|
||||
expiryStrategy: Schema.StopOrderExpiryStrategy.EXPIRY_STRATEGY_CANCELS,
|
||||
expiresAt: '2023-07-27T16:43:27.000',
|
||||
oco: true,
|
||||
ocoType: Schema.OrderType.TYPE_LIMIT,
|
||||
ocoSize: '0.2',
|
||||
ocoPrice: '300.23',
|
||||
ocoTimeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_FOK,
|
||||
};
|
||||
|
||||
useDealTicketFormValues.setState({
|
||||
@@ -143,10 +179,22 @@ describe('StopOrder', () => {
|
||||
expect(screen.getByTestId(sizeInput)).toHaveDisplayValue(
|
||||
values.size as string
|
||||
);
|
||||
expect(screen.getByTestId('order-tif')).toHaveValue(values.timeInForce);
|
||||
expect(screen.getByTestId(timeInForce)).toHaveValue(values.timeInForce);
|
||||
expect(screen.getByTestId(priceInput)).toHaveDisplayValue(
|
||||
values.price as string
|
||||
);
|
||||
|
||||
expect(screen.getByTestId(ocoPostfix(sizeInput))).toHaveDisplayValue(
|
||||
values.ocoSize as string
|
||||
);
|
||||
expect(screen.getByTestId(ocoPostfix(timeInForce))).toHaveValue(
|
||||
values.ocoTimeInForce
|
||||
);
|
||||
expect(screen.getByTestId(ocoPostfix(priceInput))).toHaveDisplayValue(
|
||||
values.ocoPrice as string
|
||||
);
|
||||
expect(screen.getByTestId('ocoTypeLimit').dataset.state).toEqual('checked');
|
||||
|
||||
expect(screen.getByTestId(expire).dataset.state).toEqual('checked');
|
||||
expect(screen.getByTestId(expiryStrategyCancel).dataset.state).toEqual(
|
||||
'checked'
|
||||
@@ -154,6 +202,9 @@ describe('StopOrder', () => {
|
||||
expect(screen.getByTestId(datePicker)).toHaveDisplayValue(
|
||||
values.expiresAt as string
|
||||
);
|
||||
|
||||
await userEvent.click(screen.getByTestId(orderTypeMarket));
|
||||
expect(screen.getByTestId(oco).dataset.state).toEqual('unchecked');
|
||||
});
|
||||
|
||||
it('does not submit if no wallet connected', async () => {
|
||||
@@ -174,145 +225,239 @@ describe('StopOrder', () => {
|
||||
expect(submit).toBeCalled();
|
||||
});
|
||||
|
||||
it('validates size field', async () => {
|
||||
it.each([
|
||||
{ fieldName: 'size', ocoValue: false },
|
||||
{ fieldName: 'ocoSize', ocoValue: true },
|
||||
])('validates $fieldName field', async ({ ocoValue }) => {
|
||||
render(generateJsx());
|
||||
|
||||
if (ocoValue) {
|
||||
await userEvent.click(screen.getByTestId(oco));
|
||||
}
|
||||
await userEvent.click(screen.getByTestId(submitButton));
|
||||
|
||||
const getByTestId = (id: string) =>
|
||||
screen.getByTestId(ocoPostfix(id, ocoValue));
|
||||
const queryByTestId = (id: string) =>
|
||||
screen.queryByTestId(ocoPostfix(id, ocoValue));
|
||||
// default value should be invalid
|
||||
expect(screen.getByTestId(sizeErrorMessage)).toBeInTheDocument();
|
||||
expect(getByTestId(sizeErrorMessage)).toBeInTheDocument();
|
||||
// to small value should be invalid
|
||||
await userEvent.type(screen.getByTestId(sizeInput), '0.01');
|
||||
expect(screen.getByTestId(sizeErrorMessage)).toBeInTheDocument();
|
||||
await userEvent.type(getByTestId(sizeInput), '0.01');
|
||||
expect(getByTestId(sizeErrorMessage)).toBeInTheDocument();
|
||||
|
||||
// clear and fill using valid value
|
||||
await userEvent.clear(screen.getByTestId(sizeInput));
|
||||
await userEvent.type(screen.getByTestId(sizeInput), '0.1');
|
||||
expect(screen.queryByTestId(sizeErrorMessage)).toBeNull();
|
||||
await userEvent.clear(getByTestId(sizeInput));
|
||||
await userEvent.type(getByTestId(sizeInput), '0.1');
|
||||
expect(queryByTestId(sizeErrorMessage)).toBeNull();
|
||||
});
|
||||
|
||||
it('validates price field', async () => {
|
||||
it.each([
|
||||
{ fieldName: 'price', ocoValue: false },
|
||||
{ fieldName: 'ocoPrice', ocoValue: true },
|
||||
])('validates $fieldName field', async ({ ocoValue }) => {
|
||||
render(generateJsx());
|
||||
|
||||
if (ocoValue) {
|
||||
await userEvent.click(screen.getByTestId(oco));
|
||||
}
|
||||
await userEvent.click(screen.getByTestId(submitButton));
|
||||
// price error message should not show if size has error
|
||||
// expect(screen.queryByTestId(priceErrorMessage)).toBeNull();
|
||||
// await userEvent.type(screen.getByTestId(sizeInput), '0.1');
|
||||
expect(screen.getByTestId(priceErrorMessage)).toBeInTheDocument();
|
||||
await userEvent.type(screen.getByTestId(priceInput), '0.001');
|
||||
expect(screen.getByTestId(priceErrorMessage)).toBeInTheDocument();
|
||||
|
||||
const getByTestId = (id: string) =>
|
||||
screen.getByTestId(ocoPostfix(id, ocoValue));
|
||||
const queryByTestId = (id: string) =>
|
||||
screen.queryByTestId(ocoPostfix(id, ocoValue));
|
||||
|
||||
expect(getByTestId(priceErrorMessage)).toBeInTheDocument();
|
||||
await userEvent.type(getByTestId(priceInput), '0.001');
|
||||
expect(getByTestId(priceErrorMessage)).toBeInTheDocument();
|
||||
|
||||
// switch to market order type error should disappear
|
||||
await userEvent.click(screen.getByTestId(orderTypeTrigger));
|
||||
await userEvent.click(screen.getByTestId(orderTypeMarket));
|
||||
await userEvent.click(screen.getByTestId(submitButton));
|
||||
expect(screen.queryByTestId(priceErrorMessage)).toBeNull();
|
||||
expect(queryByTestId(priceErrorMessage)).toBeNull();
|
||||
|
||||
// switch back to limit type
|
||||
await userEvent.click(screen.getByTestId(orderTypeTrigger));
|
||||
await userEvent.click(screen.getByTestId(orderTypeLimit));
|
||||
await userEvent.click(screen.getByTestId(submitButton));
|
||||
expect(screen.getByTestId(priceErrorMessage)).toBeInTheDocument();
|
||||
expect(getByTestId(priceErrorMessage)).toBeInTheDocument();
|
||||
|
||||
// to small value should be invalid
|
||||
await userEvent.type(screen.getByTestId(priceInput), '0.001');
|
||||
expect(screen.getByTestId(priceErrorMessage)).toBeInTheDocument();
|
||||
await userEvent.type(getByTestId(priceInput), '0.001');
|
||||
expect(getByTestId(priceErrorMessage)).toBeInTheDocument();
|
||||
|
||||
// clear and fill using valid value
|
||||
await userEvent.clear(screen.getByTestId(priceInput));
|
||||
await userEvent.type(screen.getByTestId(priceInput), '0.01');
|
||||
expect(screen.queryByTestId(priceErrorMessage)).toBeNull();
|
||||
await userEvent.clear(getByTestId(priceInput));
|
||||
await userEvent.type(getByTestId(priceInput), '0.01');
|
||||
expect(queryByTestId(priceErrorMessage)).toBeNull();
|
||||
});
|
||||
|
||||
it('validates trigger price field', async () => {
|
||||
it.each([
|
||||
{ fieldName: 'triggerPrice', ocoValue: false },
|
||||
{ fieldName: 'ocoTriggerPrice', ocoValue: true },
|
||||
])('validates $fieldName field', async ({ ocoValue }) => {
|
||||
render(generateJsx());
|
||||
|
||||
if (ocoValue) {
|
||||
await userEvent.click(screen.getByTestId(oco));
|
||||
await userEvent.click(screen.getByTestId(triggerDirectionFallsBelow));
|
||||
}
|
||||
await userEvent.click(screen.getByTestId(submitButton));
|
||||
expect(screen.getByTestId(triggerPriceErrorMessage)).toBeInTheDocument();
|
||||
const getByTestId = (id: string) =>
|
||||
screen.getByTestId(ocoPostfix(id, ocoValue));
|
||||
const queryByTestId = (id: string) =>
|
||||
screen.queryByTestId(ocoPostfix(id, ocoValue));
|
||||
expect(getByTestId(triggerPriceErrorMessage)).toBeInTheDocument();
|
||||
|
||||
// switch to trailing percentage offset trigger type
|
||||
await userEvent.click(screen.getByTestId(triggerTypeTrailingPercentOffset));
|
||||
expect(screen.queryByTestId(triggerPriceErrorMessage)).toBeNull();
|
||||
await userEvent.click(getByTestId(triggerTypeTrailingPercentOffset));
|
||||
expect(queryByTestId(triggerPriceErrorMessage)).toBeNull();
|
||||
|
||||
// switch back to price trigger type
|
||||
await userEvent.click(screen.getByTestId(triggerTypePrice));
|
||||
expect(screen.getByTestId(triggerPriceErrorMessage)).toBeInTheDocument();
|
||||
await userEvent.click(getByTestId(triggerTypePrice));
|
||||
expect(getByTestId(triggerPriceErrorMessage)).toBeInTheDocument();
|
||||
|
||||
// to small value should be invalid
|
||||
await userEvent.type(screen.getByTestId(triggerPriceInput), '0.001');
|
||||
expect(screen.getByTestId(triggerPriceErrorMessage)).toBeInTheDocument();
|
||||
await userEvent.type(getByTestId(triggerPriceInput), '0.001');
|
||||
expect(getByTestId(triggerPriceErrorMessage)).toBeInTheDocument();
|
||||
|
||||
// clear and fill using value causing immediate trigger
|
||||
await userEvent.clear(screen.getByTestId(triggerPriceInput));
|
||||
await userEvent.type(screen.getByTestId(triggerPriceInput), '0.01');
|
||||
expect(screen.queryByTestId(triggerPriceErrorMessage)).toBeNull();
|
||||
expect(
|
||||
screen.queryByTestId(triggerPriceWarningMessage)
|
||||
).toBeInTheDocument();
|
||||
await userEvent.clear(getByTestId(triggerPriceInput));
|
||||
await userEvent.type(getByTestId(triggerPriceInput), '0.01');
|
||||
expect(queryByTestId(triggerPriceErrorMessage)).toBeNull();
|
||||
expect(queryByTestId(triggerPriceWarningMessage)).toBeInTheDocument();
|
||||
|
||||
// change to correct value
|
||||
await userEvent.type(screen.getByTestId(triggerPriceInput), '2');
|
||||
expect(screen.queryByTestId(triggerPriceWarningMessage)).toBeNull();
|
||||
await userEvent.type(getByTestId(triggerPriceInput), '2');
|
||||
expect(queryByTestId(triggerPriceWarningMessage)).toBeNull();
|
||||
});
|
||||
|
||||
it('validates trigger trailing percentage offset field', async () => {
|
||||
it.each([
|
||||
{ fieldName: 'trailingPercentageOffset', ocoValue: false },
|
||||
{ fieldName: 'ocoTrailingPercentageOffset', ocoValue: true },
|
||||
])('validates $fieldName field', async ({ ocoValue }) => {
|
||||
render(generateJsx());
|
||||
if (ocoValue) {
|
||||
await userEvent.click(screen.getByTestId(oco));
|
||||
}
|
||||
await userEvent.click(screen.getByTestId(submitButton));
|
||||
const getByTestId = (id: string) =>
|
||||
screen.getByTestId(ocoPostfix(id, ocoValue));
|
||||
const queryByTestId = (id: string) =>
|
||||
screen.queryByTestId(ocoPostfix(id, ocoValue));
|
||||
|
||||
// should not show error with default form values
|
||||
await userEvent.click(screen.getByTestId(submitButton));
|
||||
expect(
|
||||
screen.queryByTestId(triggerTrailingPercentOffsetErrorMessage)
|
||||
).toBeNull();
|
||||
expect(queryByTestId(triggerTrailingPercentOffsetErrorMessage)).toBeNull();
|
||||
|
||||
// switch to trailing percentage offset trigger type
|
||||
await userEvent.click(screen.getByTestId(triggerTypeTrailingPercentOffset));
|
||||
await userEvent.click(getByTestId(triggerTypeTrailingPercentOffset));
|
||||
expect(
|
||||
screen.getByTestId(triggerTrailingPercentOffsetErrorMessage)
|
||||
getByTestId(triggerTrailingPercentOffsetErrorMessage)
|
||||
).toBeInTheDocument();
|
||||
|
||||
// to small value should be invalid
|
||||
await userEvent.type(
|
||||
screen.getByTestId(triggerTrailingPercentOffsetInput),
|
||||
getByTestId(triggerTrailingPercentOffsetInput),
|
||||
'0.09'
|
||||
);
|
||||
expect(
|
||||
screen.getByTestId(triggerTrailingPercentOffsetErrorMessage)
|
||||
getByTestId(triggerTrailingPercentOffsetErrorMessage)
|
||||
).toBeInTheDocument();
|
||||
|
||||
// clear and fill using valid value
|
||||
await userEvent.clear(
|
||||
screen.getByTestId(triggerTrailingPercentOffsetInput)
|
||||
);
|
||||
await userEvent.type(
|
||||
screen.getByTestId(triggerTrailingPercentOffsetInput),
|
||||
'0.1'
|
||||
);
|
||||
expect(
|
||||
screen.queryByTestId(triggerTrailingPercentOffsetErrorMessage)
|
||||
).toBeNull();
|
||||
await userEvent.clear(getByTestId(triggerTrailingPercentOffsetInput));
|
||||
await userEvent.type(getByTestId(triggerTrailingPercentOffsetInput), '0.1');
|
||||
expect(queryByTestId(triggerTrailingPercentOffsetErrorMessage)).toBeNull();
|
||||
|
||||
// to big value should be invalid
|
||||
await userEvent.clear(
|
||||
screen.getByTestId(triggerTrailingPercentOffsetInput)
|
||||
);
|
||||
await userEvent.clear(getByTestId(triggerTrailingPercentOffsetInput));
|
||||
await userEvent.type(
|
||||
screen.getByTestId(triggerTrailingPercentOffsetInput),
|
||||
getByTestId(triggerTrailingPercentOffsetInput),
|
||||
'99.91'
|
||||
);
|
||||
expect(
|
||||
screen.getByTestId(triggerTrailingPercentOffsetErrorMessage)
|
||||
getByTestId(triggerTrailingPercentOffsetErrorMessage)
|
||||
).toBeInTheDocument();
|
||||
|
||||
// clear and fill using valid value
|
||||
await userEvent.clear(
|
||||
screen.getByTestId(triggerTrailingPercentOffsetInput)
|
||||
);
|
||||
await userEvent.clear(getByTestId(triggerTrailingPercentOffsetInput));
|
||||
await userEvent.type(
|
||||
screen.getByTestId(triggerTrailingPercentOffsetInput),
|
||||
getByTestId(triggerTrailingPercentOffsetInput),
|
||||
'99.9'
|
||||
);
|
||||
expect(queryByTestId(triggerTrailingPercentOffsetErrorMessage)).toBeNull();
|
||||
});
|
||||
|
||||
it('sync oco trigger', async () => {
|
||||
render(generateJsx());
|
||||
await userEvent.click(screen.getByTestId(oco));
|
||||
expect(
|
||||
screen.queryByTestId(triggerTrailingPercentOffsetErrorMessage)
|
||||
).toBeNull();
|
||||
screen.getByTestId(triggerDirectionRisesAbove).dataset.state
|
||||
).toEqual('checked');
|
||||
expect(
|
||||
screen.getByTestId(ocoPostfix(triggerDirectionFallsBelow)).dataset.state
|
||||
).toEqual('checked');
|
||||
await userEvent.click(screen.getByTestId(triggerDirectionFallsBelow));
|
||||
expect(
|
||||
screen.getByTestId(triggerDirectionRisesAbove).dataset.state
|
||||
).toEqual('unchecked');
|
||||
expect(
|
||||
screen.getByTestId(ocoPostfix(triggerDirectionFallsBelow)).dataset.state
|
||||
).toEqual('unchecked');
|
||||
await userEvent.click(
|
||||
screen.getByTestId(ocoPostfix(triggerDirectionFallsBelow))
|
||||
);
|
||||
expect(
|
||||
screen.getByTestId(triggerDirectionRisesAbove).dataset.state
|
||||
).toEqual('checked');
|
||||
expect(
|
||||
screen.getByTestId(ocoPostfix(triggerDirectionFallsBelow)).dataset.state
|
||||
).toEqual('checked');
|
||||
});
|
||||
|
||||
it('disables submit expiry strategy when OCO selected', async () => {
|
||||
render(generateJsx());
|
||||
await userEvent.click(screen.getByTestId(expire));
|
||||
await userEvent.click(screen.getByTestId(expiryStrategySubmit));
|
||||
await userEvent.click(screen.getByTestId(oco));
|
||||
expect(screen.getByTestId(expiryStrategySubmit).dataset.state).toEqual(
|
||||
'unchecked'
|
||||
);
|
||||
expect(screen.getByTestId(expiryStrategySubmit)).toBeDisabled();
|
||||
await userEvent.click(screen.getByTestId(oco));
|
||||
await userEvent.click(screen.getByTestId(expiryStrategySubmit));
|
||||
expect(screen.getByTestId(expiryStrategySubmit).dataset.state).toEqual(
|
||||
'checked'
|
||||
);
|
||||
expect(screen.getByTestId(expiryStrategySubmit)).not.toBeDisabled();
|
||||
});
|
||||
|
||||
it('sets expiry time/date to now if expiry is changed to checked', async () => {
|
||||
const now = 24 * 60 * 60 * 1000;
|
||||
render(generateJsx());
|
||||
jest.spyOn(global.Date, 'now').mockImplementation(() => now);
|
||||
await userEvent.click(screen.getByTestId(expire));
|
||||
|
||||
// expiry time/date was empty it should be set to now
|
||||
expect(
|
||||
new Date(screen.getByTestId<HTMLInputElement>(datePicker).value).getTime()
|
||||
).toEqual(now);
|
||||
|
||||
// set to the value in the past (now - 1s)
|
||||
fireEvent.change(screen.getByTestId<HTMLInputElement>(datePicker), {
|
||||
target: { value: formatForInput(new Date(now - 1000)) },
|
||||
});
|
||||
expect(
|
||||
new Date(
|
||||
screen.getByTestId<HTMLInputElement>(datePicker).value
|
||||
).getTime() + 1000
|
||||
).toEqual(now);
|
||||
|
||||
// switch expiry off and on
|
||||
await userEvent.click(screen.getByTestId(expire));
|
||||
await userEvent.click(screen.getByTestId(expire));
|
||||
// expiry time/date was in the past it should be set to now
|
||||
expect(
|
||||
new Date(screen.getByTestId<HTMLInputElement>(datePicker).value).getTime()
|
||||
).toEqual(now);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import { useRef, useCallback, useEffect } from 'react';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import type { StopOrdersSubmission } from '@vegaprotocol/wallet';
|
||||
import type {
|
||||
OrderSubmissionBody,
|
||||
StopOrdersSubmission,
|
||||
} from '@vegaprotocol/wallet';
|
||||
import {
|
||||
formatForInput,
|
||||
formatValue,
|
||||
removeDecimal,
|
||||
toDecimal,
|
||||
validateAmount,
|
||||
@@ -19,6 +23,9 @@ import {
|
||||
TradingInputError as InputError,
|
||||
TradingSelect as Select,
|
||||
Tooltip,
|
||||
TradingButton as Button,
|
||||
Pill,
|
||||
Intent,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { getDerivedPrice } from '@vegaprotocol/markets';
|
||||
import type { Market } from '@vegaprotocol/markets';
|
||||
@@ -31,6 +38,7 @@ import {
|
||||
REDUCE_ONLY_TOOLTIP,
|
||||
stopSubmit,
|
||||
getNotionalSize,
|
||||
getAssetUnit,
|
||||
} from './deal-ticket';
|
||||
import { TypeToggle } from './type-selector';
|
||||
import {
|
||||
@@ -41,9 +49,10 @@ import {
|
||||
} from '../../hooks/use-form-values';
|
||||
import type { StopOrderFormValues } from '../../hooks/use-form-values';
|
||||
import { mapFormValuesToStopOrdersSubmission } from '../../utils/map-form-values-to-submission';
|
||||
import { DealTicketButton } from './deal-ticket-button';
|
||||
import { DealTicketFeeDetails } from './deal-ticket-fee-details';
|
||||
import { validateExpiration } from '../../utils';
|
||||
import { NOTIONAL_SIZE_TOOLTIP_TEXT } from '../../constants';
|
||||
import { KeyValue } from './key-value';
|
||||
|
||||
export interface StopOrderProps {
|
||||
market: Market;
|
||||
@@ -78,7 +87,7 @@ const Trigger = ({
|
||||
control,
|
||||
watch,
|
||||
priceStep,
|
||||
assetSymbol,
|
||||
quoteName,
|
||||
oco,
|
||||
marketPrice,
|
||||
decimalPlaces,
|
||||
@@ -86,7 +95,7 @@ const Trigger = ({
|
||||
control: Control<StopOrderFormValues>;
|
||||
watch: UseFormWatch<StopOrderFormValues>;
|
||||
priceStep: string;
|
||||
assetSymbol: string;
|
||||
quoteName: string;
|
||||
oco?: boolean;
|
||||
marketPrice?: string | null;
|
||||
decimalPlaces: number;
|
||||
@@ -181,7 +190,7 @@ const Trigger = ({
|
||||
data-testid={`triggerPrice${oco ? '-oco' : ''}`}
|
||||
type="number"
|
||||
step={priceStep}
|
||||
appendElement={assetSymbol}
|
||||
appendElement={<Pill size="xs">{quoteName}</Pill>}
|
||||
value={value || ''}
|
||||
hasError={!!fieldState.error}
|
||||
{...props}
|
||||
@@ -249,7 +258,7 @@ const Trigger = ({
|
||||
<Input
|
||||
type="number"
|
||||
step={trailingPercentOffsetStep}
|
||||
appendElement="%"
|
||||
appendElement={<Pill size="xs">%</Pill>}
|
||||
data-testid={`triggerTrailingPercentOffset${
|
||||
oco ? '-oco' : ''
|
||||
}`}
|
||||
@@ -311,10 +320,14 @@ const Size = ({
|
||||
control,
|
||||
sizeStep,
|
||||
oco,
|
||||
isLimitType,
|
||||
assetUnit,
|
||||
}: {
|
||||
control: Control<StopOrderFormValues>;
|
||||
sizeStep: string;
|
||||
oco?: boolean;
|
||||
isLimitType: boolean;
|
||||
assetUnit?: string;
|
||||
}) => {
|
||||
return (
|
||||
<Controller
|
||||
@@ -332,7 +345,7 @@ const Size = ({
|
||||
const { value, ...props } = field;
|
||||
const id = `order-size${oco ? '-oco' : ''}`;
|
||||
return (
|
||||
<div className="mb-4">
|
||||
<div className={isLimitType ? 'mb-4' : 'mb-2'}>
|
||||
<FormGroup labelFor={id} label={t(`Size`)} compact>
|
||||
<Input
|
||||
id={id}
|
||||
@@ -341,6 +354,7 @@ const Size = ({
|
||||
step={sizeStep}
|
||||
min={sizeStep}
|
||||
onWheel={(e) => e.currentTarget.blur()}
|
||||
appendElement={assetUnit && <Pill size="xs">{assetUnit}</Pill>}
|
||||
data-testid={id}
|
||||
value={value || ''}
|
||||
hasError={!!fieldState.error}
|
||||
@@ -394,12 +408,8 @@ const Price = ({
|
||||
const { value, ...props } = field;
|
||||
const id = `order-price${oco ? '-oco' : ''}`;
|
||||
return (
|
||||
<div className="mb-4">
|
||||
<FormGroup
|
||||
labelFor={id}
|
||||
label={t(`Price (${quoteName})`)}
|
||||
compact={true}
|
||||
>
|
||||
<div className="mb-2">
|
||||
<FormGroup labelFor={id} label={t('Price')} compact={true}>
|
||||
<Input
|
||||
id={id}
|
||||
className="w-full"
|
||||
@@ -409,6 +419,7 @@ const Price = ({
|
||||
onWheel={(e) => e.currentTarget.blur()}
|
||||
value={value || ''}
|
||||
hasError={!!fieldState.error}
|
||||
appendElement={<Pill size="xs">{quoteName}</Pill>}
|
||||
{...props}
|
||||
/>
|
||||
</FormGroup>
|
||||
@@ -434,17 +445,17 @@ const TimeInForce = ({
|
||||
oco?: boolean;
|
||||
}) => (
|
||||
<Controller
|
||||
name="timeInForce"
|
||||
name={oco ? 'ocoTimeInForce' : 'timeInForce'}
|
||||
control={control}
|
||||
render={({ field, fieldState }) => {
|
||||
const id = `select-time-in-force${oco ? '-oco' : ''}`;
|
||||
const id = `order-tif${oco ? '-oco' : ''}`;
|
||||
return (
|
||||
<div className="mb-2">
|
||||
<FormGroup label={t('Time in force')} labelFor={id} compact={true}>
|
||||
<Select
|
||||
id={id}
|
||||
className="w-full"
|
||||
data-testid="order-tif"
|
||||
data-testid={id}
|
||||
hasError={!!fieldState.error}
|
||||
{...field}
|
||||
>
|
||||
@@ -486,6 +497,255 @@ const ReduceOnly = () => (
|
||||
/>
|
||||
);
|
||||
|
||||
const NotionalAndFees = ({
|
||||
market,
|
||||
marketPrice,
|
||||
side,
|
||||
size,
|
||||
price,
|
||||
timeInForce,
|
||||
triggerPrice,
|
||||
triggerType,
|
||||
type,
|
||||
}: Pick<
|
||||
OrderSubmissionBody['orderSubmission'],
|
||||
'side' | 'size' | 'timeInForce' | 'type' | 'price'
|
||||
> &
|
||||
Pick<StopOrderProps, 'market' | 'marketPrice'> &
|
||||
Pick<StopOrderFormValues, 'triggerType' | 'triggerPrice'>) => {
|
||||
const { quoteName, settlementAsset: asset } =
|
||||
market.tradableInstrument.instrument.product;
|
||||
const isPriceTrigger = triggerType === 'price';
|
||||
const derivedPrice = getDerivedPrice(
|
||||
{
|
||||
type,
|
||||
price,
|
||||
},
|
||||
type === Schema.OrderType.TYPE_MARKET && isPriceTrigger && triggerPrice
|
||||
? removeDecimal(triggerPrice, market.decimalPlaces)
|
||||
: marketPrice || '0'
|
||||
);
|
||||
|
||||
const notionalSize = getNotionalSize(
|
||||
derivedPrice,
|
||||
size,
|
||||
market.decimalPlaces,
|
||||
market.positionDecimalPlaces
|
||||
);
|
||||
return (
|
||||
<div className="mb-4">
|
||||
<KeyValue
|
||||
label={t('Notional')}
|
||||
value={formatValue(notionalSize, market.decimalPlaces)}
|
||||
formattedValue={formatValue(notionalSize, market.decimalPlaces)}
|
||||
symbol={quoteName}
|
||||
labelDescription={NOTIONAL_SIZE_TOOLTIP_TEXT(quoteName)}
|
||||
/>
|
||||
<DealTicketFeeDetails
|
||||
order={{
|
||||
marketId: market.id,
|
||||
price: derivedPrice,
|
||||
side,
|
||||
size,
|
||||
timeInForce,
|
||||
type,
|
||||
}}
|
||||
assetSymbol={asset.symbol}
|
||||
market={market}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const formatSizeAtPrice = ({
|
||||
assetUnit,
|
||||
decimalPlaces,
|
||||
positionDecimalPlaces,
|
||||
price,
|
||||
quoteName,
|
||||
side,
|
||||
size,
|
||||
type,
|
||||
}: Pick<StopOrderFormValues, 'price' | 'side' | 'size' | 'type'> & {
|
||||
assetUnit?: string;
|
||||
decimalPlaces: number;
|
||||
positionDecimalPlaces: number;
|
||||
quoteName: string;
|
||||
}) =>
|
||||
`${formatValue(
|
||||
removeDecimal(size, positionDecimalPlaces),
|
||||
positionDecimalPlaces
|
||||
)} ${assetUnit} @ ${
|
||||
type === Schema.OrderType.TYPE_MARKET
|
||||
? 'market'
|
||||
: `${formatValue(
|
||||
removeDecimal(price || '0', decimalPlaces),
|
||||
decimalPlaces
|
||||
)} ${quoteName}`
|
||||
}`;
|
||||
const formatTrigger = ({
|
||||
decimalPlaces,
|
||||
triggerDirection,
|
||||
triggerPrice,
|
||||
triggerTrailingPercentOffset,
|
||||
triggerType,
|
||||
quoteName,
|
||||
}: Pick<
|
||||
StopOrderFormValues,
|
||||
| 'triggerDirection'
|
||||
| 'triggerType'
|
||||
| 'triggerPrice'
|
||||
| 'triggerTrailingPercentOffset'
|
||||
> & {
|
||||
decimalPlaces: number;
|
||||
quoteName: string;
|
||||
}) =>
|
||||
`${
|
||||
triggerDirection ===
|
||||
Schema.StopOrderTriggerDirection.TRIGGER_DIRECTION_RISES_ABOVE
|
||||
? t('above')
|
||||
: t('below')
|
||||
} ${
|
||||
triggerType === 'price'
|
||||
? `${formatValue(
|
||||
removeDecimal(triggerPrice || '', decimalPlaces),
|
||||
decimalPlaces
|
||||
)} ${quoteName}`
|
||||
: `${(Number(triggerTrailingPercentOffset) || 0).toFixed(1)}% ${t(
|
||||
'trailing'
|
||||
)}`
|
||||
}`;
|
||||
|
||||
const SubmitButton = ({
|
||||
assetUnit,
|
||||
market,
|
||||
oco,
|
||||
ocoPrice,
|
||||
ocoSize,
|
||||
ocoTriggerPrice,
|
||||
ocoTriggerTrailingPercentOffset,
|
||||
ocoTriggerType,
|
||||
ocoType,
|
||||
price,
|
||||
side,
|
||||
size,
|
||||
triggerDirection,
|
||||
triggerPrice,
|
||||
triggerTrailingPercentOffset,
|
||||
triggerType,
|
||||
type,
|
||||
}: Pick<
|
||||
StopOrderFormValues,
|
||||
| 'oco'
|
||||
| 'ocoPrice'
|
||||
| 'ocoSize'
|
||||
| 'ocoTriggerPrice'
|
||||
| 'ocoTriggerTrailingPercentOffset'
|
||||
| 'ocoTriggerType'
|
||||
| 'ocoType'
|
||||
| 'price'
|
||||
| 'side'
|
||||
| 'size'
|
||||
| 'triggerDirection'
|
||||
| 'triggerPrice'
|
||||
| 'triggerTrailingPercentOffset'
|
||||
| 'triggerType'
|
||||
| 'type'
|
||||
> &
|
||||
Pick<StopOrderProps, 'market'> & { assetUnit?: string }) => {
|
||||
const { quoteName } = market.tradableInstrument.instrument.product;
|
||||
const risesAbove =
|
||||
triggerDirection ===
|
||||
Schema.StopOrderTriggerDirection.TRIGGER_DIRECTION_RISES_ABOVE;
|
||||
const subLabel = oco ? (
|
||||
<>
|
||||
{formatSizeAtPrice({
|
||||
assetUnit,
|
||||
decimalPlaces: market.decimalPlaces,
|
||||
positionDecimalPlaces: market.positionDecimalPlaces,
|
||||
price: risesAbove ? price : ocoPrice,
|
||||
quoteName,
|
||||
side,
|
||||
size: risesAbove ? size : ocoSize,
|
||||
type,
|
||||
})}{' '}
|
||||
{formatTrigger({
|
||||
decimalPlaces: market.decimalPlaces,
|
||||
quoteName,
|
||||
triggerDirection:
|
||||
Schema.StopOrderTriggerDirection.TRIGGER_DIRECTION_RISES_ABOVE,
|
||||
triggerPrice: risesAbove ? triggerPrice : ocoTriggerPrice,
|
||||
triggerTrailingPercentOffset: risesAbove
|
||||
? triggerTrailingPercentOffset
|
||||
: ocoTriggerTrailingPercentOffset,
|
||||
triggerType: risesAbove ? triggerType : ocoTriggerType,
|
||||
})}
|
||||
<br />
|
||||
{formatSizeAtPrice({
|
||||
assetUnit,
|
||||
decimalPlaces: market.decimalPlaces,
|
||||
positionDecimalPlaces: market.positionDecimalPlaces,
|
||||
price: !risesAbove ? price : ocoPrice,
|
||||
quoteName,
|
||||
side,
|
||||
size: !risesAbove ? size : ocoSize,
|
||||
type: ocoType,
|
||||
})}{' '}
|
||||
{formatTrigger({
|
||||
decimalPlaces: market.decimalPlaces,
|
||||
quoteName,
|
||||
triggerDirection:
|
||||
Schema.StopOrderTriggerDirection.TRIGGER_DIRECTION_FALLS_BELOW,
|
||||
triggerPrice: !risesAbove ? triggerPrice : ocoTriggerPrice,
|
||||
triggerTrailingPercentOffset: !risesAbove
|
||||
? triggerTrailingPercentOffset
|
||||
: ocoTriggerTrailingPercentOffset,
|
||||
triggerType: !risesAbove ? triggerType : ocoTriggerType,
|
||||
})}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{formatSizeAtPrice({
|
||||
assetUnit,
|
||||
decimalPlaces: market.decimalPlaces,
|
||||
positionDecimalPlaces: market.positionDecimalPlaces,
|
||||
price,
|
||||
quoteName,
|
||||
side,
|
||||
size,
|
||||
type,
|
||||
})}
|
||||
<br />
|
||||
{t('Trigger')}{' '}
|
||||
{formatTrigger({
|
||||
decimalPlaces: market.decimalPlaces,
|
||||
quoteName,
|
||||
triggerDirection,
|
||||
triggerPrice,
|
||||
triggerTrailingPercentOffset,
|
||||
triggerType,
|
||||
})}
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<Button
|
||||
intent={side === Schema.Side.SIDE_BUY ? Intent.Success : Intent.Danger}
|
||||
data-testid="place-order"
|
||||
type="submit"
|
||||
className="w-full"
|
||||
subLabel={subLabel}
|
||||
>
|
||||
{t(
|
||||
oco
|
||||
? 'Place OCO stop order'
|
||||
: type === Schema.OrderType.TYPE_MARKET
|
||||
? 'Place market stop order'
|
||||
: 'Place limit stop order'
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||
const { pubKey, isReadOnly } = useVegaWallet();
|
||||
const setType = useDealTicketFormValues((state) => state.setType);
|
||||
@@ -521,50 +781,40 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||
},
|
||||
[market.id, market.decimalPlaces, market.positionDecimalPlaces, submit]
|
||||
);
|
||||
const side = watch('side');
|
||||
const expire = watch('expire');
|
||||
const triggerType = watch('triggerType');
|
||||
const triggerPrice = watch('triggerPrice');
|
||||
const timeInForce = watch('timeInForce');
|
||||
const rawPrice = watch('price');
|
||||
const rawSize = watch('size');
|
||||
const oco = watch('oco');
|
||||
const expiresAt = watch('expiresAt');
|
||||
|
||||
useEffect(() => {
|
||||
const size = storedFormValues?.[dealTicketType]?.size;
|
||||
if (size && rawSize !== size) {
|
||||
setValue('size', size);
|
||||
}
|
||||
}, [storedFormValues, dealTicketType, rawSize, setValue]);
|
||||
|
||||
useEffect(() => {
|
||||
const price = storedFormValues?.[dealTicketType]?.price;
|
||||
if (price && rawPrice !== price) {
|
||||
setValue('price', price);
|
||||
}
|
||||
}, [storedFormValues, dealTicketType, rawPrice, setValue]);
|
||||
|
||||
const isPriceTrigger = triggerType === 'price';
|
||||
const size = removeDecimal(rawSize, market.positionDecimalPlaces);
|
||||
const price =
|
||||
marketPrice &&
|
||||
getDerivedPrice(
|
||||
{
|
||||
type,
|
||||
price: rawPrice && removeDecimal(rawPrice, market.decimalPlaces),
|
||||
},
|
||||
type === Schema.OrderType.TYPE_MARKET && isPriceTrigger && triggerPrice
|
||||
? removeDecimal(triggerPrice, market.decimalPlaces)
|
||||
: marketPrice
|
||||
);
|
||||
|
||||
const notionalSize = getNotionalSize(
|
||||
price,
|
||||
size,
|
||||
market.decimalPlaces,
|
||||
market.positionDecimalPlaces
|
||||
const oco = watch('oco');
|
||||
const ocoPrice = watch('ocoPrice');
|
||||
const ocoSize = watch('ocoSize');
|
||||
const ocoTimeInForce = watch('ocoTimeInForce');
|
||||
const ocoTriggerPrice = watch('ocoTriggerPrice');
|
||||
const ocoTriggerTrailingPercentOffset = watch(
|
||||
'ocoTriggerTrailingPercentOffset'
|
||||
);
|
||||
const ocoTriggerType = watch('ocoTriggerType');
|
||||
const ocoType = watch('ocoType');
|
||||
const price = watch('price');
|
||||
const side = watch('side');
|
||||
const size = watch('size');
|
||||
const timeInForce = watch('timeInForce');
|
||||
const triggerDirection = watch('triggerDirection');
|
||||
const triggerPrice = watch('triggerPrice');
|
||||
const triggerTrailingPercentOffset = watch('triggerTrailingPercentOffset');
|
||||
const triggerType = watch('triggerType');
|
||||
|
||||
useEffect(() => {
|
||||
const storedSize = storedFormValues?.[dealTicketType]?.size;
|
||||
if (storedSize && size !== storedSize) {
|
||||
setValue('size', storedSize);
|
||||
}
|
||||
}, [storedFormValues, dealTicketType, size, setValue]);
|
||||
|
||||
useEffect(() => {
|
||||
const storedPrice = storedFormValues?.[dealTicketType]?.price;
|
||||
if (storedPrice && price !== storedPrice) {
|
||||
setValue('price', storedPrice);
|
||||
}
|
||||
}, [storedFormValues, dealTicketType, price, setValue]);
|
||||
|
||||
useEffect(() => {
|
||||
const subscription = watch((value, { name, type }) => {
|
||||
@@ -573,8 +823,10 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||
return () => subscription.unsubscribe();
|
||||
}, [watch, market.id, updateStoredFormValues]);
|
||||
|
||||
const { quoteName, settlementAsset: asset } =
|
||||
market.tradableInstrument.instrument.product;
|
||||
const { quoteName } = market.tradableInstrument.instrument.product;
|
||||
const assetUnit = getAssetUnit(
|
||||
market.tradableInstrument.instrument.metadata.tags
|
||||
);
|
||||
|
||||
const sizeStep = toDecimal(market?.positionDecimalPlaces);
|
||||
const priceStep = toDecimal(market?.decimalPlaces);
|
||||
@@ -584,6 +836,10 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||
control,
|
||||
});
|
||||
|
||||
const normalizedPrice = price && removeDecimal(price, market.decimalPlaces);
|
||||
const normalizedSize =
|
||||
size && removeDecimal(size, market.positionDecimalPlaces);
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={isReadOnly || !pubKey ? stopSubmit : handleSubmit(onSubmit)}
|
||||
@@ -620,18 +876,34 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||
control={control}
|
||||
watch={watch}
|
||||
priceStep={priceStep}
|
||||
assetSymbol={asset.symbol}
|
||||
quoteName={quoteName}
|
||||
marketPrice={marketPrice}
|
||||
decimalPlaces={market.decimalPlaces}
|
||||
/>
|
||||
<hr className="mb-4 border-vega-clight-500 dark:border-vega-cdark-500" />
|
||||
<Size
|
||||
control={control}
|
||||
sizeStep={sizeStep}
|
||||
isLimitType={type === Schema.OrderType.TYPE_LIMIT}
|
||||
assetUnit={assetUnit}
|
||||
/>
|
||||
<Price
|
||||
control={control}
|
||||
watch={watch}
|
||||
priceStep={priceStep}
|
||||
quoteName={quoteName}
|
||||
/>
|
||||
<Size control={control} sizeStep={sizeStep} />
|
||||
<NotionalAndFees
|
||||
market={market}
|
||||
marketPrice={marketPrice}
|
||||
price={normalizedPrice}
|
||||
side={side}
|
||||
size={normalizedSize}
|
||||
timeInForce={timeInForce}
|
||||
triggerPrice={triggerPrice}
|
||||
triggerType={triggerType}
|
||||
type={type}
|
||||
/>
|
||||
<TimeInForce control={control} />
|
||||
<div className="flex justify-end pb-3 gap-2">
|
||||
<ReduceOnly />
|
||||
@@ -682,12 +954,12 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||
>
|
||||
<Radio
|
||||
value={Schema.OrderType.TYPE_MARKET}
|
||||
id={`ocoTypeMarket`}
|
||||
id="ocoTypeMarket"
|
||||
label={'Market'}
|
||||
/>
|
||||
<Radio
|
||||
value={Schema.OrderType.TYPE_LIMIT}
|
||||
id={`ocoTypeLimit`}
|
||||
id="ocoTypeLimit"
|
||||
label={'Limit'}
|
||||
/>
|
||||
</RadioGroup>
|
||||
@@ -699,12 +971,19 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||
control={control}
|
||||
watch={watch}
|
||||
priceStep={priceStep}
|
||||
assetSymbol={asset.symbol}
|
||||
quoteName={quoteName}
|
||||
marketPrice={marketPrice}
|
||||
decimalPlaces={market.decimalPlaces}
|
||||
oco
|
||||
/>
|
||||
<hr className="mb-2 border-vega-clight-500 dark:border-vega-cdark-500" />
|
||||
<Size
|
||||
control={control}
|
||||
sizeStep={sizeStep}
|
||||
assetUnit={assetUnit}
|
||||
oco
|
||||
isLimitType={ocoType === Schema.OrderType.TYPE_LIMIT}
|
||||
/>
|
||||
<Price
|
||||
control={control}
|
||||
watch={watch}
|
||||
@@ -712,7 +991,19 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||
quoteName={quoteName}
|
||||
oco
|
||||
/>
|
||||
<Size control={control} sizeStep={sizeStep} oco />
|
||||
<NotionalAndFees
|
||||
market={market}
|
||||
marketPrice={marketPrice}
|
||||
price={ocoPrice && removeDecimal(ocoPrice, market.decimalPlaces)}
|
||||
side={side}
|
||||
size={
|
||||
ocoSize && removeDecimal(ocoSize, market.positionDecimalPlaces)
|
||||
}
|
||||
timeInForce={ocoTimeInForce}
|
||||
triggerPrice={ocoTriggerPrice}
|
||||
triggerType={ocoTriggerType}
|
||||
type={ocoType}
|
||||
/>
|
||||
<TimeInForce control={control} oco />
|
||||
<div className="flex justify-end mb-2 gap-2">
|
||||
<ReduceOnly />
|
||||
@@ -728,11 +1019,12 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||
return (
|
||||
<Checkbox
|
||||
onCheckedChange={(value) => {
|
||||
const now = Date.now();
|
||||
if (
|
||||
value &&
|
||||
(!expiresAt || new Date(expiresAt).getTime() < Date.now())
|
||||
(!expiresAt || new Date(expiresAt).getTime() < now)
|
||||
) {
|
||||
setValue('expiresAt', formatForInput(new Date()), {
|
||||
setValue('expiresAt', formatForInput(new Date(now)), {
|
||||
shouldValidate: true,
|
||||
});
|
||||
}
|
||||
@@ -803,19 +1095,24 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||
</>
|
||||
)}
|
||||
<NoWalletWarning isReadOnly={isReadOnly} />
|
||||
<DealTicketButton side={side} label={t('Submit Stop Order')} />
|
||||
<DealTicketFeeDetails
|
||||
order={{
|
||||
marketId: market.id,
|
||||
price: price || undefined,
|
||||
side,
|
||||
size,
|
||||
timeInForce,
|
||||
type,
|
||||
}}
|
||||
notionalSize={notionalSize}
|
||||
assetSymbol={asset.symbol}
|
||||
<SubmitButton
|
||||
assetUnit={assetUnit}
|
||||
market={market}
|
||||
oco={oco}
|
||||
ocoPrice={ocoPrice}
|
||||
ocoSize={ocoSize}
|
||||
ocoTriggerPrice={ocoTriggerPrice}
|
||||
ocoTriggerTrailingPercentOffset={ocoTriggerTrailingPercentOffset}
|
||||
ocoTriggerType={ocoTriggerType}
|
||||
ocoType={ocoType}
|
||||
price={price}
|
||||
side={side}
|
||||
size={size}
|
||||
triggerDirection={triggerDirection}
|
||||
triggerPrice={triggerPrice}
|
||||
triggerTrailingPercentOffset={triggerTrailingPercentOffset}
|
||||
triggerType={triggerType}
|
||||
type={type}
|
||||
/>
|
||||
</form>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
import { act, render, screen, waitFor } from '@testing-library/react';
|
||||
import {
|
||||
act,
|
||||
fireEvent,
|
||||
render,
|
||||
screen,
|
||||
waitFor,
|
||||
} from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { generateMarket, generateMarketData } from '../../test-helpers';
|
||||
import { DealTicket } from './deal-ticket';
|
||||
@@ -14,6 +20,9 @@ import {
|
||||
} from '../../hooks/use-form-values';
|
||||
import * as positionsTools from '@vegaprotocol/positions';
|
||||
import { OrdersDocument } from '@vegaprotocol/orders';
|
||||
import { formatForInput } from '@vegaprotocol/utils';
|
||||
import type { PartialDeep } from 'type-fest';
|
||||
import type { Market } from '@vegaprotocol/markets';
|
||||
|
||||
jest.mock('zustand');
|
||||
jest.mock('./deal-ticket-fee-details', () => ({
|
||||
@@ -29,12 +38,19 @@ const market = generateMarket();
|
||||
const marketData = generateMarketData();
|
||||
const submit = jest.fn();
|
||||
|
||||
function generateJsx(mocks: MockedResponse[] = []) {
|
||||
function generateJsx(
|
||||
mocks: MockedResponse[] = [],
|
||||
marketOverrides: PartialDeep<Market> = {}
|
||||
) {
|
||||
const joinedMarket: Market = {
|
||||
...market,
|
||||
...marketOverrides,
|
||||
} as Market;
|
||||
return (
|
||||
<MockedProvider mocks={[...mocks]}>
|
||||
<VegaWalletContext.Provider value={{ pubKey, isReadOnly: false } as any}>
|
||||
<DealTicket
|
||||
market={market}
|
||||
market={joinedMarket}
|
||||
marketData={marketData}
|
||||
marketPrice={marketPrice}
|
||||
submit={submit}
|
||||
@@ -314,7 +330,7 @@ describe('DealTicket', () => {
|
||||
expect(screen.getByTestId('iceberg')).toBeChecked();
|
||||
});
|
||||
|
||||
it('should set values for a non-persistent iceberg order and disable post only checkbox', () => {
|
||||
it('should set values for a non-persistent order and disable post only checkbox', () => {
|
||||
const expectedOrder = {
|
||||
marketId: market.id,
|
||||
type: Schema.OrderType.TYPE_LIMIT,
|
||||
@@ -357,9 +373,9 @@ describe('DealTicket', () => {
|
||||
expect(screen.getByTestId('reduce-only')).not.toBeChecked();
|
||||
expect(screen.getByTestId('post-only')).not.toBeChecked();
|
||||
expect(screen.getByTestId('iceberg')).not.toBeChecked();
|
||||
expect(screen.getByTestId('iceberg')).toBeDisabled();
|
||||
});
|
||||
|
||||
// eslint-disable-next-line jest/no-disabled-tests
|
||||
it('handles TIF select box dependent on order type', async () => {
|
||||
render(generateJsx());
|
||||
|
||||
@@ -473,4 +489,169 @@ describe('DealTicket', () => {
|
||||
Object.keys(Schema.OrderTimeInForce).length
|
||||
);
|
||||
});
|
||||
|
||||
it('validates size field', async () => {
|
||||
render(generateJsx());
|
||||
const sizeErrorMessage = 'deal-ticket-error-message-size';
|
||||
const sizeInput = 'order-size';
|
||||
await userEvent.click(screen.getByTestId('place-order'));
|
||||
// default value should be invalid
|
||||
expect(screen.getByTestId(sizeErrorMessage)).toBeInTheDocument();
|
||||
// to small value should be invalid
|
||||
await userEvent.type(screen.getByTestId(sizeInput), '0.01');
|
||||
expect(screen.getByTestId(sizeErrorMessage)).toBeInTheDocument();
|
||||
|
||||
// clear and fill using valid value
|
||||
await userEvent.clear(screen.getByTestId(sizeInput));
|
||||
await userEvent.type(screen.getByTestId(sizeInput), '0.1');
|
||||
expect(screen.queryByTestId(sizeErrorMessage)).toBeNull();
|
||||
});
|
||||
|
||||
it('validates price field', async () => {
|
||||
const priceErrorMessage = 'deal-ticket-error-message-price';
|
||||
const priceInput = 'order-price';
|
||||
const submitButton = 'place-order';
|
||||
const orderTypeMarket = 'order-type-Market';
|
||||
const orderTypeLimit = 'order-type-Limit';
|
||||
render(generateJsx());
|
||||
|
||||
await userEvent.click(screen.getByTestId(submitButton));
|
||||
|
||||
expect(screen.getByTestId(priceErrorMessage)).toBeInTheDocument();
|
||||
await userEvent.type(screen.getByTestId(priceInput), '0.001');
|
||||
expect(screen.getByTestId(priceErrorMessage)).toBeInTheDocument();
|
||||
|
||||
// switch to market order type error should disappear
|
||||
await userEvent.click(screen.getByTestId(orderTypeMarket));
|
||||
await userEvent.click(screen.getByTestId(submitButton));
|
||||
expect(screen.queryByTestId(priceErrorMessage)).toBeNull();
|
||||
|
||||
// switch back to limit type
|
||||
await userEvent.click(screen.getByTestId(orderTypeLimit));
|
||||
await userEvent.click(screen.getByTestId(submitButton));
|
||||
expect(screen.getByTestId(priceErrorMessage)).toBeInTheDocument();
|
||||
|
||||
// to small value should be invalid
|
||||
await userEvent.type(screen.getByTestId(priceInput), '0.001');
|
||||
expect(screen.getByTestId(priceErrorMessage)).toBeInTheDocument();
|
||||
|
||||
// clear and fill using valid value
|
||||
await userEvent.clear(screen.getByTestId(priceInput));
|
||||
await userEvent.type(screen.getByTestId(priceInput), '0.01');
|
||||
expect(screen.queryByTestId(priceErrorMessage)).toBeNull();
|
||||
});
|
||||
|
||||
it('validates size when positionDecimalPlaces is negative', async () => {
|
||||
render(generateJsx([], { positionDecimalPlaces: -4 }));
|
||||
const sizeErrorMessage = 'deal-ticket-error-message-size';
|
||||
const sizeInput = 'order-size';
|
||||
await userEvent.click(screen.getByTestId('place-order'));
|
||||
// default value should be invalid
|
||||
expect(screen.getByTestId(sizeErrorMessage)).toBeInTheDocument();
|
||||
expect(screen.getByTestId(sizeErrorMessage)).toHaveTextContent(
|
||||
'Size cannot be lower than 10000'
|
||||
);
|
||||
await userEvent.type(screen.getByTestId(sizeInput), '10001');
|
||||
expect(screen.getByTestId(sizeErrorMessage)).toHaveTextContent(
|
||||
'Size must be a multiple of 10000 for this market'
|
||||
);
|
||||
await userEvent.clear(screen.getByTestId(sizeInput));
|
||||
await userEvent.type(screen.getByTestId(sizeInput), '10000');
|
||||
expect(screen.queryByTestId(sizeErrorMessage)).toBeNull();
|
||||
});
|
||||
|
||||
it('validates iceberg field', async () => {
|
||||
const peakSizeErrorMessage = 'deal-ticket-peak-error-message';
|
||||
const minimumSizeErrorMessage = 'deal-ticket-minimum-error-message';
|
||||
const sizeInput = 'order-size';
|
||||
const peakSizeInput = 'order-peak-size';
|
||||
const minimumSizeInput = 'order-minimum-size';
|
||||
const submitButton = 'place-order';
|
||||
|
||||
render(generateJsx());
|
||||
await userEvent.selectOptions(
|
||||
screen.getByTestId('order-tif'),
|
||||
Schema.OrderTimeInForce.TIME_IN_FORCE_GFA
|
||||
);
|
||||
await userEvent.click(screen.getByTestId('iceberg'));
|
||||
await userEvent.click(screen.getByTestId(submitButton));
|
||||
|
||||
// validate empty fields
|
||||
expect(screen.getByTestId(peakSizeErrorMessage)).toBeInTheDocument();
|
||||
expect(screen.getByTestId(minimumSizeErrorMessage)).toBeInTheDocument();
|
||||
|
||||
await userEvent.type(screen.getByTestId(peakSizeInput), '0.01');
|
||||
await userEvent.type(screen.getByTestId(minimumSizeInput), '0.01');
|
||||
|
||||
// validate value smaller than step
|
||||
expect(screen.getByTestId(peakSizeErrorMessage)).toBeInTheDocument();
|
||||
expect(screen.getByTestId(minimumSizeErrorMessage)).toBeInTheDocument();
|
||||
|
||||
await userEvent.clear(screen.getByTestId(peakSizeInput));
|
||||
await userEvent.type(screen.getByTestId(peakSizeInput), '0.5');
|
||||
await userEvent.clear(screen.getByTestId(minimumSizeInput));
|
||||
await userEvent.type(screen.getByTestId(minimumSizeInput), '0.7');
|
||||
|
||||
await userEvent.clear(screen.getByTestId(sizeInput));
|
||||
await userEvent.type(screen.getByTestId(sizeInput), '0.1');
|
||||
|
||||
// validate value higher than size
|
||||
expect(screen.getByTestId(peakSizeErrorMessage)).toBeInTheDocument();
|
||||
expect(screen.getByTestId(minimumSizeErrorMessage)).toBeInTheDocument();
|
||||
|
||||
await userEvent.clear(screen.getByTestId(sizeInput));
|
||||
await userEvent.type(screen.getByTestId(sizeInput), '1');
|
||||
// validate peak higher than minimum
|
||||
expect(screen.queryByTestId(peakSizeErrorMessage)).toBeNull();
|
||||
expect(screen.getByTestId(minimumSizeErrorMessage)).toBeInTheDocument();
|
||||
|
||||
await userEvent.clear(screen.getByTestId(peakSizeInput));
|
||||
await userEvent.type(screen.getByTestId(peakSizeInput), '1');
|
||||
await userEvent.clear(screen.getByTestId(minimumSizeInput));
|
||||
await userEvent.type(screen.getByTestId(minimumSizeInput), '1');
|
||||
|
||||
// validate correct values
|
||||
expect(screen.queryByTestId(peakSizeErrorMessage)).toBeNull();
|
||||
expect(screen.queryByTestId(minimumSizeErrorMessage)).toBeNull();
|
||||
});
|
||||
|
||||
it('sets expiry time/date to now if expiry is changed to checked', async () => {
|
||||
const datePicker = 'date-picker-field';
|
||||
const now = 24 * 60 * 60 * 1000;
|
||||
render(generateJsx());
|
||||
jest.spyOn(global.Date, 'now').mockImplementation(() => now);
|
||||
await userEvent.selectOptions(
|
||||
screen.getByTestId('order-tif'),
|
||||
Schema.OrderTimeInForce.TIME_IN_FORCE_GTT
|
||||
);
|
||||
|
||||
// expiry time/date was empty it should be set to now
|
||||
expect(
|
||||
new Date(screen.getByTestId<HTMLInputElement>(datePicker).value).getTime()
|
||||
).toEqual(now);
|
||||
|
||||
// set to the value in the past (now - 1s)
|
||||
fireEvent.change(screen.getByTestId<HTMLInputElement>(datePicker), {
|
||||
target: { value: formatForInput(new Date(now - 1000)) },
|
||||
});
|
||||
expect(
|
||||
new Date(
|
||||
screen.getByTestId<HTMLInputElement>(datePicker).value
|
||||
).getTime() + 1000
|
||||
).toEqual(now);
|
||||
|
||||
// switch expiry off and on
|
||||
await userEvent.selectOptions(
|
||||
screen.getByTestId('order-tif'),
|
||||
Schema.OrderTimeInForce.TIME_IN_FORCE_GFA
|
||||
);
|
||||
await userEvent.selectOptions(
|
||||
screen.getByTestId('order-tif'),
|
||||
Schema.OrderTimeInForce.TIME_IN_FORCE_GTT
|
||||
);
|
||||
// expiry time/date was in the past it should be set to now
|
||||
expect(
|
||||
new Date(screen.getByTestId<HTMLInputElement>(datePicker).value).getTime()
|
||||
).toEqual(now);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,7 +3,6 @@ import * as Schema from '@vegaprotocol/types';
|
||||
import type { FormEventHandler } from 'react';
|
||||
import { memo, useCallback, useEffect, useRef, useMemo } from 'react';
|
||||
import { Controller, useController, useForm } from 'react-hook-form';
|
||||
import { DealTicketButton } from './deal-ticket-button';
|
||||
import {
|
||||
DealTicketFeeDetails,
|
||||
DealTicketMarginDetails,
|
||||
@@ -23,6 +22,8 @@ import {
|
||||
Intent,
|
||||
Notification,
|
||||
Tooltip,
|
||||
TradingButton as Button,
|
||||
Pill,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
import {
|
||||
@@ -35,6 +36,7 @@ import {
|
||||
validateAmount,
|
||||
toDecimal,
|
||||
formatForInput,
|
||||
formatValue,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { activeOrdersProvider } from '@vegaprotocol/orders';
|
||||
import { getDerivedPrice } from '@vegaprotocol/markets';
|
||||
@@ -46,7 +48,10 @@ import {
|
||||
validateType,
|
||||
} from '../../utils';
|
||||
import { ZeroBalanceError } from '../deal-ticket-validation/zero-balance-error';
|
||||
import { SummaryValidationType } from '../../constants';
|
||||
import {
|
||||
NOTIONAL_SIZE_TOOLTIP_TEXT,
|
||||
SummaryValidationType,
|
||||
} from '../../constants';
|
||||
import type {
|
||||
Market,
|
||||
MarketData,
|
||||
@@ -68,6 +73,7 @@ import { useDealTicketFormValues } from '../../hooks/use-form-values';
|
||||
import { DealTicketSizeIceberg } from './deal-ticket-size-iceberg';
|
||||
import noop from 'lodash/noop';
|
||||
import { isNonPersistentOrder } from '../../utils/time-in-force-persistance';
|
||||
import { KeyValue } from './key-value';
|
||||
|
||||
export const REDUCE_ONLY_TOOLTIP =
|
||||
'"Reduce only" will ensure that this order will not increase the size of an open position. When the order is matched, it will only trade enough volume to bring your open volume towards 0 but never change the direction of your position. If applied to a limit order that is not instantly filled, the order will be stopped.';
|
||||
@@ -118,6 +124,11 @@ const getDefaultValues = (
|
||||
...storedValues,
|
||||
});
|
||||
|
||||
export const getAssetUnit = (tags?: string[] | null) =>
|
||||
tags
|
||||
?.find((tag) => tag.startsWith('base:') || tag.startsWith('ticker:'))
|
||||
?.replace(/^[^:]*:/, '');
|
||||
|
||||
export const DealTicket = ({
|
||||
market,
|
||||
onMarketClick,
|
||||
@@ -257,6 +268,10 @@ export const DealTicket = ({
|
||||
const assetSymbol =
|
||||
market.tradableInstrument.instrument.product.settlementAsset.symbol;
|
||||
|
||||
const assetUnit = getAssetUnit(
|
||||
market.tradableInstrument.instrument.metadata.tags
|
||||
);
|
||||
|
||||
const summaryError = useMemo(() => {
|
||||
if (!pubKey) {
|
||||
return {
|
||||
@@ -338,6 +353,7 @@ export const DealTicket = ({
|
||||
const priceStep = toDecimal(market?.decimalPlaces);
|
||||
const sizeStep = toDecimal(market?.positionDecimalPlaces);
|
||||
const quoteName = market.tradableInstrument.instrument.product.quoteName;
|
||||
const isLimitType = type === Schema.OrderType.TYPE_LIMIT;
|
||||
|
||||
return (
|
||||
<form
|
||||
@@ -384,18 +400,16 @@ export const DealTicket = ({
|
||||
message: t('Size cannot be lower than ' + sizeStep),
|
||||
},
|
||||
validate: validateAmount(sizeStep, 'Size'),
|
||||
deps: ['peakSize', 'minimumVisibleSize'],
|
||||
}}
|
||||
render={({ field, fieldState }) => (
|
||||
<div className="mb-4">
|
||||
<FormGroup
|
||||
label={t('Size')}
|
||||
labelFor="input-order-size-limit"
|
||||
compact
|
||||
>
|
||||
<div className={isLimitType ? 'mb-4' : 'mb-2'}>
|
||||
<FormGroup label={t('Size')} labelFor="order-size" compact>
|
||||
<Input
|
||||
id="input-order-size-limit"
|
||||
id="order-size"
|
||||
className="w-full"
|
||||
type="number"
|
||||
appendElement={assetUnit && <Pill size="xs">{assetUnit}</Pill>}
|
||||
step={sizeStep}
|
||||
min={sizeStep}
|
||||
data-testid="order-size"
|
||||
@@ -411,7 +425,7 @@ export const DealTicket = ({
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
{type === Schema.OrderType.TYPE_LIMIT && (
|
||||
{isLimitType && (
|
||||
<Controller
|
||||
name="price"
|
||||
control={control}
|
||||
@@ -424,14 +438,15 @@ export const DealTicket = ({
|
||||
validate: validateAmount(priceStep, 'Price'),
|
||||
}}
|
||||
render={({ field, fieldState }) => (
|
||||
<div className="mb-4">
|
||||
<div className="mb-2">
|
||||
<FormGroup
|
||||
labelFor="input-price-quote"
|
||||
label={t(`Price (${quoteName})`)}
|
||||
label={t('Price')}
|
||||
compact
|
||||
>
|
||||
<Input
|
||||
id="input-price-quote"
|
||||
appendElement={<Pill size="xs">{quoteName}</Pill>}
|
||||
className="w-full"
|
||||
type="number"
|
||||
step={priceStep}
|
||||
@@ -449,6 +464,22 @@ export const DealTicket = ({
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<div className="mb-4">
|
||||
<KeyValue
|
||||
label={t('Notional')}
|
||||
value={formatValue(notionalSize, market.decimalPlaces)}
|
||||
formattedValue={formatValue(notionalSize, market.decimalPlaces)}
|
||||
symbol={quoteName}
|
||||
labelDescription={NOTIONAL_SIZE_TOOLTIP_TEXT(quoteName)}
|
||||
/>
|
||||
<DealTicketFeeDetails
|
||||
order={
|
||||
normalizedOrder && { ...normalizedOrder, price: price || undefined }
|
||||
}
|
||||
assetSymbol={assetSymbol}
|
||||
market={market}
|
||||
/>
|
||||
</div>
|
||||
<Controller
|
||||
name="timeInForce"
|
||||
control={control}
|
||||
@@ -465,17 +496,18 @@ export const DealTicket = ({
|
||||
onSelect={(value) => {
|
||||
// If GTT is selected and no expiresAt time is set, or its
|
||||
// behind current time then reset the value to current time
|
||||
const now = Date.now();
|
||||
if (
|
||||
value === Schema.OrderTimeInForce.TIME_IN_FORCE_GTT &&
|
||||
(!expiresAt || new Date(expiresAt).getTime() < Date.now())
|
||||
(!expiresAt || new Date(expiresAt).getTime() < now)
|
||||
) {
|
||||
setValue('expiresAt', formatForInput(new Date()), {
|
||||
setValue('expiresAt', formatForInput(new Date(now)), {
|
||||
shouldValidate: true,
|
||||
});
|
||||
}
|
||||
|
||||
// iceberg orders must be persistent orders, so if user
|
||||
// switches to to a non persisten tif value, remove iceberg selection
|
||||
// switches to a non persistent tif value, remove iceberg selection
|
||||
if (iceberg && isNonPersistentOrder(value)) {
|
||||
setValue('iceberg', false);
|
||||
}
|
||||
@@ -487,7 +519,7 @@ export const DealTicket = ({
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{type === Schema.OrderType.TYPE_LIMIT &&
|
||||
{isLimitType &&
|
||||
timeInForce === Schema.OrderTimeInForce.TIME_IN_FORCE_GTT && (
|
||||
<Controller
|
||||
name="expiresAt"
|
||||
@@ -569,7 +601,7 @@ export const DealTicket = ({
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
{type === Schema.OrderType.TYPE_LIMIT && (
|
||||
{isLimitType && (
|
||||
<>
|
||||
<div className="flex justify-between pb-2 gap-2">
|
||||
<Controller
|
||||
@@ -624,15 +656,29 @@ export const DealTicket = ({
|
||||
pubKey={pubKey}
|
||||
onDeposit={onDeposit}
|
||||
/>
|
||||
<DealTicketButton side={side} />
|
||||
<DealTicketFeeDetails
|
||||
order={
|
||||
normalizedOrder && { ...normalizedOrder, price: price || undefined }
|
||||
}
|
||||
notionalSize={notionalSize}
|
||||
assetSymbol={assetSymbol}
|
||||
market={market}
|
||||
/>
|
||||
<Button
|
||||
data-testid="place-order"
|
||||
type="submit"
|
||||
className="w-full"
|
||||
intent={side === Schema.Side.SIDE_BUY ? Intent.Success : Intent.Danger}
|
||||
subLabel={`${formatValue(
|
||||
normalizedOrder.size,
|
||||
market.positionDecimalPlaces
|
||||
)} ${assetUnit} @ ${
|
||||
type === Schema.OrderType.TYPE_MARKET
|
||||
? 'market'
|
||||
: `${formatValue(
|
||||
normalizedOrder.price,
|
||||
market.decimalPlaces
|
||||
)} ${quoteName}`
|
||||
}`}
|
||||
>
|
||||
{t(
|
||||
type === Schema.OrderType.TYPE_MARKET
|
||||
? 'Place market order'
|
||||
: 'Place limit order'
|
||||
)}
|
||||
</Button>
|
||||
<DealTicketMarginDetails
|
||||
onMarketClick={onMarketClick}
|
||||
assetSymbol={assetSymbol}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { Tooltip } from '@vegaprotocol/ui-toolkit';
|
||||
import classnames from 'classnames';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export interface KeyValuePros {
|
||||
label: string;
|
||||
value?: string | null | undefined;
|
||||
symbol: string;
|
||||
indent?: boolean | undefined;
|
||||
labelDescription?: ReactNode;
|
||||
formattedValue?: string;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
export const KeyValue = ({
|
||||
label,
|
||||
value,
|
||||
labelDescription,
|
||||
symbol,
|
||||
indent,
|
||||
onClick,
|
||||
formattedValue,
|
||||
}: KeyValuePros) => {
|
||||
const displayValue = `${formattedValue ?? '-'} ${symbol || ''}`;
|
||||
const valueElement = onClick ? (
|
||||
<button onClick={onClick} className="text-muted">
|
||||
{displayValue}
|
||||
</button>
|
||||
) : (
|
||||
<div className="text-muted">{displayValue}</div>
|
||||
);
|
||||
return (
|
||||
<div
|
||||
data-testid={
|
||||
'deal-ticket-fee-' + label.toLocaleLowerCase().replace(/\s/g, '-')
|
||||
}
|
||||
key={typeof label === 'string' ? label : 'value-dropdown'}
|
||||
className={classnames(
|
||||
'text-xs mt-2 flex justify-between items-center gap-4 flex-wrap',
|
||||
{ 'ml-2': indent }
|
||||
)}
|
||||
>
|
||||
<Tooltip description={labelDescription}>
|
||||
<div>{label}</div>
|
||||
</Tooltip>
|
||||
<Tooltip description={`${value ?? '-'} ${symbol || ''}`}>
|
||||
{valueElement}
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"jsc": {
|
||||
"target": "es2017",
|
||||
"parser": {
|
||||
"syntax": "typescript",
|
||||
"decorators": true,
|
||||
"dynamicImport": true
|
||||
},
|
||||
"transform": {
|
||||
"decoratorMetadata": true,
|
||||
"legacyDecorator": true
|
||||
},
|
||||
"keepClassNames": true,
|
||||
"externalHelpers": true,
|
||||
"loose": true
|
||||
},
|
||||
"module": {
|
||||
"type": "commonjs",
|
||||
"strict": true,
|
||||
"noInterop": true
|
||||
},
|
||||
"sourceMaps": true,
|
||||
"exclude": ["jest.config.ts",".*\\.spec.tsx?$",".*\\.test.tsx?$","./src/jest-setup.ts$","./**/jest-setup.ts$",".*.js$"]
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"name": "@vegaprotocol/deposits",
|
||||
"version": "0.0.1"
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user