Compare commits

...
327 changed files with 4865 additions and 7121 deletions
-36
View File
@@ -1,36 +0,0 @@
name: Cypress Console tests -- live environment
# This workflow runs using provided url
on:
workflow_dispatch:
inputs:
url:
description: 'Url'
required: true
type: string
jobs:
cypress-run:
name: Run Cypress Trading tests -- live environment
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Use Node.js 20
id: Node
uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
- name: Run Cypress tests
uses: cypress-io/github-action@v4
with:
browser: chrome
record: true
project: ./apps/trading-e2e
config: baseUrl=${{ github.event.inputs.url }}
env: grepTags=@live
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -12,7 +12,6 @@ on:
options:
- explorer-e2e
- governance-e2e
- trading-e2e
tags:
description: 'Test tags to run'
required: true
+1 -1
View File
@@ -10,5 +10,5 @@ jobs:
uses: ./.github/workflows/cypress-run.yml
secrets: inherit
with:
projects: '["explorer-e2e","governance-e2e","trading-e2e"]'
projects: '["explorer-e2e","governance-e2e"]'
tags: '@smoke @regression @slow'
@@ -8,12 +8,14 @@ import EpochMissingOverview from './epoch-missing';
import { Icon, Tooltip } from '@vegaprotocol/ui-toolkit';
import type { IconProps } from '@vegaprotocol/ui-toolkit';
import isPast from 'date-fns/isPast';
import { EpochSymbol } from '../links/block-link/block-link';
const borderClass =
'border-solid border-2 border-vega-dark-200 border-collapse';
export type EpochOverviewProps = {
id?: string;
icon?: boolean;
};
/**
@@ -24,7 +26,7 @@ export type EpochOverviewProps = {
*
* The details are hidden in a tooltip, behind the epoch number
*/
const EpochOverview = ({ id }: EpochOverviewProps) => {
const EpochOverview = ({ id, icon = true }: EpochOverviewProps) => {
const { data, error, loading } = useExplorerEpochQuery({
variables: { id: id || '' },
});
@@ -38,7 +40,12 @@ const EpochOverview = ({ id }: EpochOverviewProps) => {
}
if (!ti || loading || error) {
return <span>{id}</span>;
return (
<span>
<EpochSymbol />
{id}
</span>
);
}
const description = (
@@ -90,7 +97,11 @@ const EpochOverview = ({ id }: EpochOverviewProps) => {
return (
<Tooltip description={description}>
<p>
<IconForEpoch start={ti.start} end={ti.end} />
{icon ? (
<IconForEpoch start={ti.start} end={ti.end} />
) : (
<EpochSymbol />
)}
{id}
</p>
</Tooltip>
@@ -0,0 +1,10 @@
query ExplorerEpochForBlock($block: String!) {
epoch(block: $block) {
id
timestamps {
start
end
lastBlock
}
}
}
@@ -0,0 +1,53 @@
import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type ExplorerEpochForBlockQueryVariables = Types.Exact<{
block: Types.Scalars['String'];
}>;
export type ExplorerEpochForBlockQuery = { __typename?: 'Query', epoch: { __typename?: 'Epoch', id: string, timestamps: { __typename?: 'EpochTimestamps', start?: any | null, end?: any | null, lastBlock?: string | null } } };
export const ExplorerEpochForBlockDocument = gql`
query ExplorerEpochForBlock($block: String!) {
epoch(block: $block) {
id
timestamps {
start
end
lastBlock
}
}
}
`;
/**
* __useExplorerEpochForBlockQuery__
*
* To run a query within a React component, call `useExplorerEpochForBlockQuery` and pass it any options that fit your needs.
* When your component renders, `useExplorerEpochForBlockQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useExplorerEpochForBlockQuery({
* variables: {
* block: // value for 'block'
* },
* });
*/
export function useExplorerEpochForBlockQuery(baseOptions: Apollo.QueryHookOptions<ExplorerEpochForBlockQuery, ExplorerEpochForBlockQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<ExplorerEpochForBlockQuery, ExplorerEpochForBlockQueryVariables>(ExplorerEpochForBlockDocument, options);
}
export function useExplorerEpochForBlockLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerEpochForBlockQuery, ExplorerEpochForBlockQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<ExplorerEpochForBlockQuery, ExplorerEpochForBlockQueryVariables>(ExplorerEpochForBlockDocument, options);
}
export type ExplorerEpochForBlockQueryHookResult = ReturnType<typeof useExplorerEpochForBlockQuery>;
export type ExplorerEpochForBlockLazyQueryHookResult = ReturnType<typeof useExplorerEpochForBlockLazyQuery>;
export type ExplorerEpochForBlockQueryResult = Apollo.QueryResult<ExplorerEpochForBlockQuery, ExplorerEpochForBlockQueryVariables>;
@@ -4,17 +4,56 @@ import { Link } from 'react-router-dom';
import type { ComponentProps } from 'react';
import Hash from '../hash';
import { useExplorerEpochForBlockQuery } from './__generated__/EpochByBlock';
import { t } from '@vegaprotocol/i18n';
export type BlockLinkProps = Partial<ComponentProps<typeof Link>> & {
height: string;
showEpoch?: boolean;
};
const BlockLink = ({ height, ...props }: BlockLinkProps) => {
const BlockLink = ({ height, showEpoch = false, ...props }: BlockLinkProps) => {
return (
<Link className="underline" {...props} to={`/${Routes.BLOCKS}/${height}`}>
<Hash text={height} />
</Link>
<>
<Link className="underline" {...props} to={`/${Routes.BLOCKS}/${height}`}>
<Hash text={height} />
</Link>
{showEpoch && <EpochForBlock block={height} />}
</>
);
};
export function EpochForBlock(props: { block: string }) {
const { error, data, loading } = useExplorerEpochForBlockQuery({
errorPolicy: 'ignore',
variables: { block: props.block },
});
// NOTE: 0.73.x & <0.74.2 can error showing epoch, so for now we hide loading
// or error states and only display if we get usable data
if (error || loading || !data) {
return null;
}
return (
<span className="ml-2" title={t('Epoch')}>
<EpochSymbol />
{data.epoch.id}
</span>
);
}
export const EPOCH_SYMBOL = 'ⓔ';
export function EpochSymbol() {
return (
<em
title={t('Epoch')}
className="mr-1 cursor-default text-xl leading-none align-text-bottom not-italic"
>
{EPOCH_SYMBOL}
</em>
);
}
export default BlockLink;
@@ -0,0 +1,16 @@
import { render, screen } from '@testing-library/react';
import GovernanceLink from './governance-link';
describe('GovernanceLink', () => {
it('renders the link with the correct text', () => {
render(<GovernanceLink text="Governance internet website" />);
const linkElement = screen.getByText('Governance internet website');
expect(linkElement).toBeInTheDocument();
});
it('renders the link with the correct href and sensible default text', () => {
render(<GovernanceLink />);
const linkElement = screen.getByText('Governance');
expect(linkElement).toBeInTheDocument();
});
});
@@ -0,0 +1,18 @@
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
import { ENV } from '../../../config/env';
import { t } from '@vegaprotocol/i18n';
export type GovernanceLinkProps = {
text?: string;
};
/**
* Just a link to the governance page, with optional text
*/
const GovernanceLink = ({ text = t('Governance') }: GovernanceLinkProps) => {
const base = ENV.dataSources.governanceUrl;
return <ExternalLink href={base}>{text}</ExternalLink>;
};
export default GovernanceLink;
@@ -10,6 +10,8 @@ import { ChainResponseCode } from '../chain-response-code/chain-reponse.code';
import { TxDataView } from '../../tx-data-view';
import Hash from '../../../links/hash';
import { Signature } from '../../../signature/signature';
import { useExplorerEpochForBlockQuery } from '../../../links/block-link/__generated__/EpochByBlock';
import EpochOverview from '../../../epoch-overview/epoch';
interface TxDetailsSharedProps {
txData: BlockExplorerTransactionResult | undefined;
@@ -44,6 +46,11 @@ export const TxDetailsShared = ({
blockData,
hideTypeRow = false,
}: TxDetailsSharedProps) => {
const { data } = useExplorerEpochForBlockQuery({
errorPolicy: 'ignore',
variables: { block: txData?.block.toString() || '' },
});
if (!txData) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
@@ -74,7 +81,7 @@ export const TxDetailsShared = ({
<TableRow modifier="bordered">
<TableCell {...sharedHeaderProps}>{t('Block')}</TableCell>
<TableCell>
<BlockLink height={height} />
<BlockLink height={height} showEpoch={false} />
</TableCell>
</TableRow>
<TableRow modifier="bordered">
@@ -83,6 +90,7 @@ export const TxDetailsShared = ({
<Signature signature={txData.signature} />
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell {...sharedHeaderProps}>{t('Time')}</TableCell>
<TableCell>
@@ -100,6 +108,14 @@ export const TxDetailsShared = ({
)}
</TableCell>
</TableRow>
{data && data.epoch && (
<TableRow modifier="bordered">
<TableCell scope="row">{t('Epoch')}</TableCell>
<TableCell modifier="bordered">
<EpochOverview id={data.epoch.id} icon={false} />
</TableCell>
</TableRow>
)}
<TableRow modifier="bordered">
<TableCell {...sharedHeaderProps}>{t('Response code')}</TableCell>
<TableCell>
@@ -113,13 +113,16 @@ export const TxDetailsTransfer = ({
/**
* Gets a string description of this transfer
* @param txData A full transfer
* @param tx A full transfer
* @returns string Transfer label
*/
export function getTypeLabelForTransfer(tx: Transfer) {
if (tx.to === SPECIAL_CASE_NETWORK || tx.to === SPECIAL_CASE_NETWORK_ID) {
if (tx.toAccountType === 'ACCOUNT_TYPE_NETWORK_TREASURY') {
return 'Treasury transfer';
}
if (tx.recurring && tx.recurring.dispatchStrategy) {
return 'Reward top up transfer';
return 'Reward transfer';
}
// Else: we don't know that it's a reward transfer, so let's not guess
} else if (tx.recurring) {
@@ -2,6 +2,7 @@ import { t } from '@vegaprotocol/i18n';
import type { components } from '../../../types/explorer';
import { VoteIcon } from '../vote-icon/vote-icon';
import { ExternalChainIcon } from '../links/external-explorer-link/external-chain-icon';
import { getTypeLabelForTransfer } from './details/tx-transfer';
interface TxOrderTypeProps {
orderType: string;
@@ -95,7 +96,7 @@ export function getLabelForOrderType(
/**
* Given a proposal, will return a specific label
* @param chainEvent
* @param proposal
* @returns
*/
export function getLabelForProposal(
@@ -142,6 +143,36 @@ export function getLabelForProposal(
}
}
type label = {
type: string;
colours: string;
};
export function getLabelForTransfer(
transfer: components['schemas']['commandsv1Transfer']
): label {
const type = getTypeLabelForTransfer(transfer);
if (transfer.toAccountType === 'ACCOUNT_TYPE_NETWORK_TREASURY') {
return {
type,
colours:
'text-vega-green dark:text-green bg-vega-dark-150 dark:bg-vega-dark-250',
};
} else if (transfer.recurring) {
return {
type,
colours:
'text-vega-yellow dark:text-yellow bg-vega-dark-150 dark:bg-vega-dark-250',
};
}
return {
type,
colours:
'text-white dark:text-white bg-vega-dark-150 dark:bg-vega-dark-250',
};
}
/**
* Given a chain event, will try to provide a more useful label
* @param chainEvent
@@ -225,9 +256,10 @@ export const TxOrderType = ({ orderType, command }: TxOrderTypeProps) => {
if (type === 'Chain Event' && !!command?.chainEvent) {
type = getLabelForChainEvent(command.chainEvent);
colours = 'text-white dark-text-white bg-vega-pink dark:bg-vega-pink';
} else if (type === 'Validator Heartbeat') {
colours =
'text-white dark-text-white bg-vega-light-200 dark:bg-vega-dark-100';
} else if (type === 'Transfer Funds' && command?.transfer) {
const res = getLabelForTransfer(command.transfer);
type = res.type;
colours = res.colours;
} else if (type === 'Proposal' || type === 'Governance Proposal') {
if (command && !!command.proposalSubmission) {
type = getLabelForProposal(command.proposalSubmission);
@@ -16,6 +16,8 @@ import { useBlockInfo } from '@vegaprotocol/tendermint';
import { NodeLink } from '../../../components/links';
import { useDocumentTitle } from '../../../hooks/use-document-title';
import EmptyList from '../../../components/empty-list/empty-list';
import { useExplorerEpochForBlockQuery } from '../../../components/links/block-link/__generated__/EpochByBlock';
import EpochOverview from '../../../components/epoch-overview/epoch';
type Params = { block: string };
@@ -26,6 +28,11 @@ const Block = () => {
state: { data: blockData, loading, error },
} = useBlockInfo(Number(block));
const { data } = useExplorerEpochForBlockQuery({
errorPolicy: 'ignore',
variables: { block: block?.toString() || '' },
});
return (
<section>
<RouteTitle data-testid="block-header">{t(`BLOCK ${block}`)}</RouteTitle>
@@ -75,6 +82,7 @@ const Block = () => {
<code>{blockData.result.block.header.consensus_hash}</code>
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableHeader scope="row">Mined by</TableHeader>
<TableCell modifier="bordered">
@@ -97,6 +105,14 @@ const Block = () => {
)}
</TableCell>
</TableRow>
{data && data.epoch && (
<TableRow modifier="bordered">
<TableCell scope="row">{t('Epoch')}</TableCell>
<TableCell modifier="bordered">
<EpochOverview id={data.epoch.id} icon={false} />
</TableCell>
</TableRow>
)}
<TableRow modifier="bordered">
<TableHeader scope="row">Transactions</TableHeader>
<TableCell modifier="bordered">
@@ -33,7 +33,10 @@ export const NetworkAccountsTable = () => {
return (
<section className="md:flex md:flex-row flex-wrap">
{c.map((a) => (
<div className="basis-1/2 md:basis-1/4">
<div
className="basis-1/2 md:basis-1/4"
key={`${a.assetId}-${a.balance}`}
>
<div className="bg-white rounded overflow-hidden shadow-lg dark:bg-black dark:border-slate-500 dark:border">
<div className="text-center p-6 bg-gray-100 dark:bg-slate-900 border-b dark:border-slate-500">
<p className="flex justify-center">
@@ -16,19 +16,21 @@ import type { DeepPartial } from '@apollo/client/utilities';
describe('typeLabel', () => {
it('should return "Transfer" for "OneOffTransfer" kind', () => {
expect(typeLabel('OneOffTransfer')).toBe('Transfer');
expect(typeLabel('OneOffTransfer')).toBe('Transfer - one time');
});
it('should return "Transfer" for "RecurringTransfer" kind', () => {
expect(typeLabel('RecurringTransfer')).toBe('Transfer');
expect(typeLabel('RecurringTransfer')).toBe('Transfer - repeating');
});
it('should return "Governance" for "OneOffGovernanceTransfer" kind', () => {
expect(typeLabel('OneOffGovernanceTransfer')).toBe('Governance');
expect(typeLabel('OneOffGovernanceTransfer')).toBe('Governance - one time');
});
it('should return "Governance" for "RecurringGovernanceTransfer" kind', () => {
expect(typeLabel('RecurringGovernanceTransfer')).toBe('Governance');
expect(typeLabel('RecurringGovernanceTransfer')).toBe(
'Governance - repeating'
);
});
it('should return "Unknown" for unknown kind', () => {
@@ -256,7 +258,7 @@ describe('NetworkTransfersTable', () => {
expect(screen.getByTestId('from-account').textContent).toEqual('Treasury');
expect(screen.getByTestId('to-account').textContent).toEqual('7100…97a0');
expect(screen.getByTestId('transfer-kind').textContent).toEqual(
'Governance'
'Governance - one time'
);
});
});
@@ -12,6 +12,7 @@ import { t } from '@vegaprotocol/i18n';
import { IconNames } from '@blueprintjs/icons';
import { useMemo } from 'react';
import { useScreenDimensions } from '@vegaprotocol/react-helpers';
import ProposalLink from '../../../components/links/proposal-link/proposal-link';
export const colours = {
INCOMING: '!fill-vega-green-600 text-vega-green-600 mr-2',
@@ -50,14 +51,24 @@ export function getToAccountTypeLabel(type?: AccountType): string {
}
}
export function isGovernanceTransfer(kind?: string): boolean {
if (kind && kind.includes('Governance')) {
return true;
}
return false;
}
export function typeLabel(kind?: string): string {
switch (kind) {
case 'OneOffTransfer':
return t('Transfer - one time');
case 'RecurringTransfer':
return t('Transfer');
return t('Transfer - repeating');
case 'OneOffGovernanceTransfer':
return t('Governance - one time');
case 'RecurringGovernanceTransfer':
return t('Governance');
return t('Governance - repeating');
default:
return t('Unknown');
}
@@ -239,6 +250,11 @@ export const NetworkTransfersTable = () => {
>
{a && typeLabel(a.kind.__typename)}
</span>
{isGovernanceTransfer(a?.kind.__typename) && a?.id && (
<span className="ml-4">
<ProposalLink id={a?.id} text="View" />
</span>
)}
</td>
</tr>
);
@@ -4,6 +4,7 @@ import { t } from '@vegaprotocol/i18n';
import { RouteTitle } from '../../components/route-title';
import { NetworkAccountsTable } from './components/network-accounts-table';
import { NetworkTransfersTable } from './components/network-transfers-table';
import GovernanceLink from '../../components/links/governance-link/governance-link';
export type NonZeroAccount = {
assetId: string;
@@ -16,7 +17,33 @@ export const NetworkTreasury = () => {
return (
<section>
<RouteTitle data-testid="block-header">{t(`Treasury`)}</RouteTitle>
<div>
<details className="w-full md:w-3/5 cursor-pointer shadow-lg p-5 dark:border-l-2 dark:border-vega-green">
<summary>{t('About the Network Treasury')}</summary>
<section className="mt-4 b-1 border-grey">
<p className="mb-2">
The network treasury can hold funds from any active settlement asset
on the network. It is funded periodically by transfers from Gobalsky
as part of the Community Adoption Fund (CAF), but in future may
receive funds from any sources.
</p>
<p className="mb-2">
Funds in the network treasury can be used by creating governance
initiated transfers via{' '}
<GovernanceLink text={t('community governance')} />. These transfers
can be initiated by anyone and be used to fund reward pools, or can
be used to fund other activities the{' '}
<abbr className="decoration-dotted" title="Community Adoption Fund">
CAF
</abbr>{' '}
is exploring.
</p>
<p>
This page shows details of the balances in the treasury, pending
transfers, and historic transfer movements to and from the treasury.
</p>
</section>
</details>
<div className="mt-6">
<NetworkAccountsTable />
</div>
<div className="mt-5">
@@ -34,12 +34,6 @@ context('View functionality with public key', { tags: '@smoke' }, function () {
cy.connectPublicKey(vegaWalletPubKey);
});
it('Able to connect public key using url', function () {
cy.getByTestId('exit-view').click();
cy.visit(`/?address=${vegaWalletPubKey}`);
verifyConnectedToPubKey();
});
it.skip('Able to connect public key via wallet and view assets in wallet', function () {
verifyConnectedToPubKey();
cy.getByTestId('currency-title', { timeout: 10000 })
+2 -16
View File
@@ -1,7 +1,7 @@
import * as Sentry from '@sentry/react';
import { toBigNum } from '@vegaprotocol/utils';
import { Splash } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet, useEagerConnect } from '@vegaprotocol/wallet';
import { useVegaWallet, useEagerConnect } from '@vegaprotocol/wallet-react';
import { useFeatureFlags, useEnvironment } from '@vegaprotocol/environment';
import { useWeb3React } from '@web3-react/core';
import React, { Suspense } from 'react';
@@ -15,20 +15,6 @@ import {
} from './contexts/app-state/app-state-context';
import { useContracts } from './contexts/contracts/contracts-context';
import { useRefreshAssociatedBalances } from './hooks/use-refresh-associated-balances';
import { useConnectors } from './lib/vega-connectors';
import { useSearchParams } from 'react-router-dom';
const useVegaWalletEagerConnect = () => {
const connectors = useConnectors();
const vegaConnecting = useEagerConnect(connectors);
const { pubKey, connect } = useVegaWallet();
const [searchParams] = useSearchParams();
const [query] = React.useState(searchParams.get('address'));
if (query && !pubKey) {
connect(connectors.view);
}
return vegaConnecting;
};
export const AppLoader = ({ children }: { children: React.ReactElement }) => {
const featureFlags = useFeatureFlags((state) => state.flags);
@@ -40,7 +26,7 @@ export const AppLoader = ({ children }: { children: React.ReactElement }) => {
const { token, staking, vesting } = useContracts();
const setAssociatedBalances = useRefreshAssociatedBalances();
const [balancesLoaded, setBalancesLoaded] = React.useState(false);
const vegaConnecting = useVegaWalletEagerConnect();
const vegaConnecting = useEagerConnect();
const loaded = balancesLoaded && !vegaConnecting;
+31 -141
View File
@@ -1,7 +1,6 @@
import './i18n';
import React, { useEffect } from 'react';
import * as Sentry from '@sentry/react';
import { BrowserRouter as Router, useLocation } from 'react-router-dom';
import { AppLoader } from './app-loader';
import { NetworkInfo } from '@vegaprotocol/network-info';
@@ -26,7 +25,7 @@ import {
} from '@vegaprotocol/web3';
import { Web3Provider } from '@vegaprotocol/web3';
import { VegaWalletDialogs } from './components/vega-wallet-dialogs';
import { VegaWalletProvider, useChainId } from '@vegaprotocol/wallet';
import { WalletProvider } from '@vegaprotocol/wallet-react';
import {
useVegaTransactionManager,
useVegaTransactionUpdater,
@@ -36,26 +35,21 @@ import { useEthereumConfig } from '@vegaprotocol/web3';
import {
useEnvironment,
NetworkLoader,
useInitializeEnv,
NodeGuard,
NodeSwitcherDialog,
useNodeSwitcherStore,
DocsLinks,
NodeFailure,
AppLoader as Loader,
useInitializeEnv,
} from '@vegaprotocol/environment';
import { ENV } from './config';
import type { InMemoryCacheConfig } from '@apollo/client';
import { CreateWithdrawalDialog } from '@vegaprotocol/withdraws';
import { SplashLoader } from './components/splash-loader';
import { ToastsManager } from './toasts-manager';
import {
TelemetryDialog,
TELEMETRY_ON,
} from './components/telemetry-dialog/telemetry-dialog';
import { useLocalStorage } from '@vegaprotocol/react-helpers';
import { TelemetryDialog } from './components/telemetry-dialog/telemetry-dialog';
import { useTranslation } from 'react-i18next';
import { isPartyNotFoundError } from './lib/party';
import { useSentryInit } from './hooks/use-sentry-init';
import { useVegaWalletConfig } from './hooks/use-vega-wallet-config';
const cache: InMemoryCacheConfig = {
typePolicies: {
@@ -104,32 +98,12 @@ const Web3Container = ({
/** Ethereum provider url */
providerUrl: string;
}) => {
const InitializeHandlers = () => {
useVegaTransactionManager();
useVegaTransactionUpdater();
useEthTransactionManager();
useEthTransactionUpdater();
useEthWithdrawApprovalsManager();
return null;
};
const [connectors, initializeConnectors] = useWeb3ConnectStore((store) => [
store.connectors,
store.initialize,
]);
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();
const vegaChainId = useChainId(VEGA_URL);
const { ETHEREUM_PROVIDER_URL, ETH_LOCAL_PROVIDER_URL, ETH_WALLET_MNEMONIC } =
useEnvironment();
useEffect(() => {
if (chainId) {
@@ -150,50 +124,31 @@ const Web3Container = ({
ETH_LOCAL_PROVIDER_URL,
ETH_WALLET_MNEMONIC,
]);
const sideBar = React.useMemo(() => {
return [<EthWallet />, <VegaWallet />];
}, []);
if (connectors.length === 0) {
const vegaWalletConfig = useVegaWalletConfig();
if (!vegaWalletConfig || connectors.length === 0) {
// Prevent loading when the connectors are not initialized
return <SplashLoader />;
}
if (
!VEGA_URL ||
!VEGA_WALLET_URL ||
!VEGA_EXPLORER_URL ||
!DocsLinks ||
!CHROME_EXTENSION_URL ||
!MOZILLA_EXTENSION_URL ||
!vegaChainId
) {
return null;
}
return (
<Web3Provider connectors={connectors}>
<Web3Connector connectors={connectors} chainId={Number(chainId)}>
<VegaWalletProvider
config={{
network: VEGA_ENV,
vegaUrl: VEGA_URL,
chainId: vegaChainId,
vegaWalletServiceUrl: VEGA_WALLET_URL,
links: {
explorer: VEGA_EXPLORER_URL,
concepts: DocsLinks?.VEGA_WALLET_CONCEPTS_URL,
chromeExtensionUrl: CHROME_EXTENSION_URL,
mozillaExtensionUrl: MOZILLA_EXTENSION_URL,
},
}}
>
<WalletProvider config={vegaWalletConfig}>
<ContractsProvider>
<AppLoader>
<BalanceManager>
<>
<AppLayout>
<TemplateSidebar sidebar={sideBar}>
<TemplateSidebar
sidebar={
<>
<EthWallet />
<VegaWallet />
</>
}
>
<AppRouter />
</TemplateSidebar>
<footer className="p-4 break-all border-t border-neutral-700">
@@ -211,7 +166,7 @@ const Web3Container = ({
</BalanceManager>
</AppLoader>
</ContractsProvider>
</VegaWalletProvider>
</WalletProvider>
</Web3Connector>
</Web3Provider>
);
@@ -231,20 +186,9 @@ const ScrollToTop = () => {
return null;
};
const removeQueryParams = (url: string) => {
return url.split('?')[0];
};
const AppContainer = () => {
const { config, loading, error } = useEthereumConfig();
const {
VEGA_ENV,
VEGA_URL,
GIT_COMMIT_HASH,
GIT_BRANCH,
ETHEREUM_PROVIDER_URL,
} = useEnvironment();
const [telemetryOn] = useLocalStorage(TELEMETRY_ON);
const { VEGA_URL, ETHEREUM_PROVIDER_URL } = useEnvironment();
const { t } = useTranslation();
const [nodeSwitcherOpen, setNodeSwitcher] = useNodeSwitcherStore((store) => [
store.dialogOpen,
@@ -254,70 +198,7 @@ const AppContainer = () => {
// Hacky skip all the loading & web3 init for geo restricted users
const isRestricted = document?.location?.pathname?.includes('/restricted');
useEffect(() => {
if (ENV.dsn && telemetryOn === 'true') {
Sentry.init({
dsn: ENV.dsn,
tracesSampleRate: 0.1,
enabled: true,
environment: VEGA_ENV,
release: GIT_COMMIT_HASH,
beforeSend(event, hint) {
const error = hint?.originalException;
const errorIsString = typeof error === 'string';
const errorIsObject = error instanceof Error;
const requestUrl = event.request?.url;
const transaction = event.transaction;
if (
(errorIsString && isPartyNotFoundError({ message: error })) ||
(errorIsObject && isPartyNotFoundError(error))
) {
// This error is caused by a pubkey making an API request before
// it has interacted with the chain. This isn't needed in Sentry.
return null;
}
const updatedRequest =
requestUrl && requestUrl.includes('/claim?')
? { ...event.request, url: removeQueryParams(requestUrl) }
: event.request;
const updatedTransaction =
transaction && transaction.includes('/claim?')
? removeQueryParams(transaction)
: transaction;
const updatedBreadcrumbs = event.breadcrumbs?.map((breadcrumb) => {
if (
breadcrumb.type === 'navigation' &&
breadcrumb.data?.to?.includes('/claim?')
) {
return {
...breadcrumb,
data: {
...breadcrumb.data,
to: removeQueryParams(breadcrumb.data.to),
},
};
}
return breadcrumb;
});
return {
...event,
request: updatedRequest,
transaction: updatedTransaction,
breadcrumbs: updatedBreadcrumbs ?? event.breadcrumbs,
};
},
});
Sentry.setTag('branch', GIT_BRANCH);
Sentry.setTag('commit', GIT_COMMIT_HASH);
} else {
Sentry.close();
}
}, [GIT_COMMIT_HASH, GIT_BRANCH, VEGA_ENV, telemetryOn]);
useSentryInit();
if (isRestricted) {
return (
@@ -359,6 +240,15 @@ const AppContainer = () => {
);
};
const InitializeHandlers = () => {
useVegaTransactionManager();
useVegaTransactionUpdater();
useEthTransactionManager();
useEthTransactionUpdater();
useEthWithdrawApprovalsManager();
return null;
};
function App() {
useInitializeEnv();
@@ -7,7 +7,7 @@ import { useGetAssociationBreakdown } from '../../hooks/use-get-association-brea
import { useGetUserBalances } from '../../hooks/use-get-user-balances';
import { useBalances } from '../../lib/balances/balances-store';
import type { ReactElement } from 'react';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useVegaWallet } from '@vegaprotocol/wallet-react';
import { useListenForStakingEvents as useListenForAssociationEvents } from '../../hooks/use-listen-for-staking-events';
import { useTranches } from '../../lib/tranches/tranches-store';
import { useUserTrancheBalances } from '../../routes/redemption/hooks';
@@ -1,18 +1,13 @@
import { Button } from '@vegaprotocol/ui-toolkit';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { useVegaWalletDialogStore } from '@vegaprotocol/wallet';
import { useDialogStore } from '@vegaprotocol/wallet-react';
export const ConnectToVega = () => {
const { t } = useTranslation();
const { openVegaWalletDialog } = useVegaWalletDialogStore((store) => ({
openVegaWalletDialog: store.openVegaWalletDialog,
}));
const openVegaWalletDialog = useDialogStore((store) => store.open);
return (
<Button
onClick={() => {
openVegaWalletDialog();
}}
onClick={openVegaWalletDialog}
data-testid="connect-to-vega-wallet-btn"
variant="primary"
>
@@ -6,8 +6,8 @@ import {
Intent,
Icon,
} from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '../use-vega-wallet';
import { useT } from '../use-t';
import { useVegaWallet } from '@vegaprotocol/wallet-react';
import { useTranslation } from 'react-i18next';
export interface VegaManageDialogProps {
dialogOpen: boolean;
@@ -18,7 +18,7 @@ export const VegaManageDialog = ({
dialogOpen,
setDialogOpen,
}: VegaManageDialogProps) => {
const t = useT();
const { t } = useTranslation();
const { pubKey, pubKeys, selectPubKey, disconnect } = useVegaWallet();
return (
<Dialog
@@ -1,5 +1,5 @@
import classNames from 'classnames';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useVegaWallet } from '@vegaprotocol/wallet-react';
import type { ReactNode } from 'react';
import { AnnouncementBanner } from '@vegaprotocol/announcements';
import { Nav } from '../nav';
@@ -1,8 +1,8 @@
import React from 'react';
import { Children, type ReactNode } from 'react';
export interface TemplateSidebarProps {
children: React.ReactNode;
sidebar: React.ReactNode[];
children: ReactNode;
sidebar: ReactNode;
}
export function TemplateSidebar({ children, sidebar }: TemplateSidebarProps) {
@@ -12,9 +12,9 @@ export function TemplateSidebar({ children, sidebar }: TemplateSidebarProps) {
{children}
</main>
<aside className="col-start-2 row-start-1 row-span-2 hidden lg:block p-4 bg-banner bg-contain border-l border-neutral-700">
{sidebar.map((Component, i) => (
{Children.map(sidebar, (child, i) => (
<section className="mb-4 last:mb-0" key={i}>
{Component}
{child}
</section>
))}
</aside>
@@ -1,5 +1,5 @@
import { Button } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
import { useVegaWallet, useDialogStore } from '@vegaprotocol/wallet-react';
import React from 'react';
import { useTranslation } from 'react-i18next';
@@ -10,9 +10,7 @@ interface VegaWalletContainerProps {
export const VegaWalletContainer = ({ children }: VegaWalletContainerProps) => {
const { t } = useTranslation();
const { pubKey } = useVegaWallet();
const { openVegaWalletDialog } = useVegaWalletDialogStore((store) => ({
openVegaWalletDialog: store.openVegaWalletDialog,
}));
const openVegaWalletDialog = useDialogStore((store) => store.open);
if (!pubKey) {
return (
@@ -9,7 +9,7 @@ import Routes from '../../routes/routes';
export const RiskMessage = () => {
return (
<>
<div className="bg-vega-light-100 dark:bg-vega-dark-100 p-6 mb-6">
<div className="bg-vega-light-100 dark:bg-vega-dark-100 p-6">
<ul className="list-[square] ml-4">
<li>
{t(
@@ -23,7 +23,7 @@ export const RiskMessage = () => {
</li>
</ul>
</div>
<p className="mb-8">
<p>
{t(
'By using the Vega Governance App, you acknowledge that you have read and understood the'
)}{' '}
@@ -1,25 +1,39 @@
import {
VegaConnectDialog,
VegaManageDialog,
ViewAsDialog,
} from '@vegaprotocol/wallet';
ConnectDialogWithRiskAck,
useDialogStore,
} from '@vegaprotocol/wallet-react';
import {
AppStateActionType,
useAppState,
} from '../../contexts/app-state/app-state-context';
import { useConnectors } from '../../lib/vega-connectors';
import { RiskMessage } from './risk-message';
import { VegaManageDialog } from '../manage-dialog';
import { useLocalStorage } from '@vegaprotocol/react-helpers';
import { Networks, useEnvironment } from '@vegaprotocol/environment';
export const VegaWalletDialogs = () => {
const { VEGA_ENV } = useEnvironment();
const { appState, appDispatch } = useAppState();
const connectors = useConnectors();
const [riskAccepted, setRiskAccepted] = useLocalStorage(
'vega_wallet_risk_accepted'
);
const vegaWalletDialogOpen = useDialogStore((store) => store.isOpen);
const setVegaWalletDialog = useDialogStore((store) => store.set);
return (
<>
<VegaConnectDialog
connectors={connectors}
riskMessage={<RiskMessage />}
<ConnectDialogWithRiskAck
open={vegaWalletDialogOpen}
onChange={setVegaWalletDialog}
riskAccepted={
VEGA_ENV === Networks.TESTNET ? riskAccepted === 'true' : true
}
riskAckContent={<RiskMessage />}
onRiskAccepted={() => setRiskAccepted('true')}
onRiskRejected={() => {
setRiskAccepted('false');
setVegaWalletDialog(false);
}}
/>
<VegaManageDialog
dialogOpen={appState.vegaWalletManageOverlay}
setDialogOpen={(open) =>
@@ -29,8 +43,6 @@ export const VegaWalletDialogs = () => {
})
}
/>
<ViewAsDialog connector={connectors.view} />
</>
);
};
@@ -10,7 +10,7 @@ import vegaBlack from '../../images/vega_black.png';
import vegaVesting from '../../images/vega_vesting.png';
import { BigNumber } from '../../lib/bignumber';
import { type WalletCardAssetProps } from '../wallet-card';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useVegaWallet } from '@vegaprotocol/wallet-react';
import { useContracts } from '../../contexts/contracts/contracts-context';
import * as Schema from '@vegaprotocol/types';
import {
@@ -1,11 +1,11 @@
import { ButtonLink, Link } from '@vegaprotocol/ui-toolkit';
import { useTranslation } from 'react-i18next';
import { ExternalLinks } from '@vegaprotocol/environment';
import { useViewAsDialog } from '@vegaprotocol/wallet';
import { useConnect } from '@vegaprotocol/wallet-react';
export const VegaWalletPrompt = () => {
const { t } = useTranslation();
const setViewAsDialog = useViewAsDialog((state) => state.setOpen);
const { connect } = useConnect();
return (
<>
<h3 className="mt-4 mb-2">{t('getWallet')}</h3>
@@ -16,7 +16,7 @@ export const VegaWalletPrompt = () => {
<ButtonLink
className="text-neutral-500"
data-testid="view-as-user"
onClick={() => setViewAsDialog(true)}
onClick={() => connect('viewParty')}
>
{t('viewAsParty')}
</ButtonLink>
@@ -25,7 +25,7 @@ import {
} from '../wallet-card';
import { VegaWalletPrompt } from './vega-wallet-prompt';
import { usePollForDelegations } from './hooks';
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
import { useVegaWallet, useDialogStore } from '@vegaprotocol/wallet-react';
import { Button, ButtonLink } from '@vegaprotocol/ui-toolkit';
import { toBigNum } from '@vegaprotocol/utils';
import { usePendingBalancesStore } from '../../hooks/use-pending-balances-manager';
@@ -34,16 +34,17 @@ import omit from 'lodash/omit';
export const VegaWallet = () => {
const { t } = useTranslation();
const { pubKey, pubKeys } = useVegaWallet();
const { status, pubKey, pubKeys } = useVegaWallet();
const pubKeyObj = useMemo(() => {
return pubKeys?.find((pk) => pk.publicKey === pubKey);
}, [pubKey, pubKeys]);
const child = !pubKeys ? (
<VegaWalletNotConnected />
) : (
<VegaWalletConnected vegaKeys={pubKeys.map((pk) => pk.publicKey)} />
);
const child =
status === 'connected' ? (
<VegaWalletConnected vegaKeys={pubKeys.map((pk) => pk.publicKey)} />
) : (
<VegaWalletNotConnected />
);
return (
<section className="vega-wallet" data-testid="vega-wallet">
@@ -75,9 +76,7 @@ export const VegaWallet = () => {
const VegaWalletNotConnected = () => {
const { t } = useTranslation();
const { openVegaWalletDialog } = useVegaWalletDialogStore((store) => ({
openVegaWalletDialog: store.openVegaWalletDialog,
}));
const openVegaWalletDialog = useDialogStore((store) => store.open);
return (
<>
<Button
@@ -7,7 +7,7 @@ import { useWeb3React } from '@web3-react/core';
export const useListenForStakingEvents = (
contract: Contract | undefined,
vegaPublicKey: string | null,
vegaPublicKey: string | undefined,
numberOfConfirmations: number
) => {
const { account } = useWeb3React();
@@ -1,6 +1,6 @@
import * as Sentry from '@sentry/react';
import { toBigNum } from '@vegaprotocol/utils';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useVegaWallet } from '@vegaprotocol/wallet-react';
import { useEthereumConfig } from '@vegaprotocol/web3';
import React from 'react';
@@ -0,0 +1,81 @@
import { useEffect } from 'react';
import * as Sentry from '@sentry/react';
import { useLocalStorage } from '@vegaprotocol/react-helpers';
import { TELEMETRY_ON } from '../components/telemetry-dialog/telemetry-dialog';
import { useEnvironment } from '@vegaprotocol/environment';
import { ENV } from '../config';
import { isPartyNotFoundError } from '../lib/party';
export const useSentryInit = () => {
const { VEGA_ENV, GIT_COMMIT_HASH, GIT_BRANCH } = useEnvironment();
const [telemetryOn] = useLocalStorage(TELEMETRY_ON);
useEffect(() => {
if (ENV.dsn && telemetryOn === 'true') {
Sentry.init({
dsn: ENV.dsn,
tracesSampleRate: 0.1,
enabled: true,
environment: VEGA_ENV,
release: GIT_COMMIT_HASH,
beforeSend(event, hint) {
const error = hint?.originalException;
const errorIsString = typeof error === 'string';
const errorIsObject = error instanceof Error;
const requestUrl = event.request?.url;
const transaction = event.transaction;
if (
(errorIsString && isPartyNotFoundError({ message: error })) ||
(errorIsObject && isPartyNotFoundError(error))
) {
// This error is caused by a pubkey making an API request before
// it has interacted with the chain. This isn't needed in Sentry.
return null;
}
const updatedRequest =
requestUrl && requestUrl.includes('/claim?')
? { ...event.request, url: removeQueryParams(requestUrl) }
: event.request;
const updatedTransaction =
transaction && transaction.includes('/claim?')
? removeQueryParams(transaction)
: transaction;
const updatedBreadcrumbs = event.breadcrumbs?.map((breadcrumb) => {
if (
breadcrumb.type === 'navigation' &&
breadcrumb.data?.to?.includes('/claim?')
) {
return {
...breadcrumb,
data: {
...breadcrumb.data,
to: removeQueryParams(breadcrumb.data.to),
},
};
}
return breadcrumb;
});
return {
...event,
request: updatedRequest,
transaction: updatedTransaction,
breadcrumbs: updatedBreadcrumbs ?? event.breadcrumbs,
};
},
});
Sentry.setTag('branch', GIT_BRANCH);
Sentry.setTag('commit', GIT_COMMIT_HASH);
} else {
Sentry.close();
}
}, [GIT_COMMIT_HASH, GIT_BRANCH, VEGA_ENV, telemetryOn]);
};
const removeQueryParams = (url: string) => {
return url.split('?')[0];
};
@@ -0,0 +1,41 @@
import { useMemo } from 'react';
import {
InjectedConnector,
JsonRpcConnector,
SnapConnector,
ViewPartyConnector,
createConfig,
fairground,
stagnet,
mainnet,
} from '@vegaprotocol/wallet';
import { useEnvironment } from '@vegaprotocol/environment';
export const useVegaWalletConfig = () => {
const { VEGA_ENV, VEGA_URL, VEGA_WALLET_URL } = useEnvironment();
return useMemo(() => {
if (!VEGA_ENV || !VEGA_URL || !VEGA_WALLET_URL) return;
const injected = new InjectedConnector();
const jsonRpc = new JsonRpcConnector({
url: VEGA_WALLET_URL,
});
const snap = new SnapConnector({
node: new URL(VEGA_URL).origin,
snapId: 'npm:@vegaprotocol/snap',
version: '1.0.1',
});
const viewParty = new ViewPartyConnector();
const config = createConfig({
chains: [mainnet, fairground, stagnet],
defaultChainId: fairground.id,
connectors: [injected, snap, jsonRpc, viewParty],
});
return config;
}, [VEGA_ENV, VEGA_URL, VEGA_WALLET_URL]);
};
+1 -1
View File
@@ -31,7 +31,7 @@ i18n
load: 'languageOnly',
debug: isInDev,
// have a common namespace used around the full app
ns: ['governance'],
ns: ['governance', 'wallet', 'wallet-react'],
defaultNS: 'governance',
keySeparator: false, // we use content as keys
nsSeparator: false,
@@ -1,30 +0,0 @@
import { useFeatureFlags } from '@vegaprotocol/environment';
import { useMemo } from 'react';
import {
JsonRpcConnector,
ViewConnector,
InjectedConnector,
SnapConnector,
DEFAULT_SNAP_ID,
} from '@vegaprotocol/wallet';
const urlParams = new URLSearchParams(window.location.search);
export const jsonRpc = new JsonRpcConnector();
export const injected = new InjectedConnector();
export const view = new ViewConnector(urlParams.get('address'));
export const snap = new SnapConnector(DEFAULT_SNAP_ID);
export const useConnectors = () => {
const featureFlags = useFeatureFlags((state) => state.flags);
return useMemo(
() => ({
injected,
jsonRpc,
view,
snap: featureFlags.METAMASK_SNAPS ? snap : undefined,
}),
[featureFlags.METAMASK_SNAPS]
);
};
@@ -5,7 +5,6 @@ import {
ProposalState,
VoteValue,
} from '@vegaprotocol/types';
import { VegaWalletContext } from '@vegaprotocol/wallet';
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
import {
generateNoVotes,
@@ -16,9 +15,7 @@ import { ProposalHeader, NewTransferSummary } from './proposal-header';
import {
lastWeek,
nextWeek,
mockWalletContext,
createUserVoteQueryMock,
networkParamsQueryMock,
} from '../../test-helpers/mocks';
import { BrowserRouter } from 'react-router-dom';
import { VoteState } from '../vote-details/use-user-vote';
@@ -45,14 +42,12 @@ const renderComponent = (
render(
<AppStateProvider>
<BrowserRouter>
<MockedProvider mocks={[networkParamsQueryMock, ...mocks]}>
<VegaWalletContext.Provider value={mockWalletContext}>
<ProposalHeader
proposal={proposal}
isListItem={isListItem}
voteState={voteState}
/>
</VegaWalletContext.Provider>
<MockedProvider mocks={mocks}>
<ProposalHeader
proposal={proposal}
isListItem={isListItem}
voteState={voteState}
/>
</MockedProvider>
</BrowserRouter>
</AppStateProvider>
@@ -160,7 +155,7 @@ describe('Proposal header', () => {
screen.queryByTestId('proposal-description')
).not.toBeInTheDocument();
expect(screen.getByTestId('proposal-details')).toHaveTextContent(
'Update to market: MarketId'
/Update to market: MarketId/
);
});
@@ -1,12 +1,12 @@
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';
import { ProposalState } from '@vegaprotocol/types';
import { type Proposal as IProposal } from '../../types';
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
import { MockedWalletProvider } from '@vegaprotocol/wallet-react/testing';
jest.mock('@vegaprotocol/network-parameters', () => ({
...jest.requireActual('@vegaprotocol/network-parameters'),
@@ -24,45 +24,44 @@ jest.mock('@vegaprotocol/network-parameters', () => ({
error: null,
})),
}));
jest.mock('../proposal-detail-header/proposal-header', () => ({
ProposalHeader: () => <div data-testid="proposal-header"></div>,
}));
jest.mock('../proposal-change-table', () => ({
ProposalChangeTable: () => <div data-testid="proposal-change-table"></div>,
}));
jest.mock('../proposal-json', () => ({
ProposalJson: () => <div data-testid="proposal-json"></div>,
}));
jest.mock('../list-asset', () => ({
ListAsset: () => <div data-testid="proposal-list-asset"></div>,
}));
jest.mock('./proposal-change-details', () => ({
ProposalChangeDetails: () => (
<div data-testid="proposal-change-details"></div>
),
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',
},
chainId: 'VEGA_CHAIN_ID',
};
jest.mock('../vote-details', () => ({
UserVote: () => <div data-testid="user-vote"></div>,
}));
jest.mock('./proposal-change-details', () => ({
ProposalChangeDetails: () => <div data-testid="proposal-change-details" />,
}));
const renderComponent = (proposal: IProposal) => {
render(
return render(
<MemoryRouter>
<MockedProvider>
<VegaWalletProvider config={vegaWalletConfig}>
<Proposal restData={null} proposal={proposal} />
</VegaWalletProvider>
<MockedWalletProvider>
<AppStateProvider>
<Proposal restData={null} proposal={proposal} />
</AppStateProvider>
</MockedWalletProvider>
</MockedProvider>
</MemoryRouter>
);
@@ -23,7 +23,7 @@ export interface ProposalProps {
export const Proposal = ({ proposal, restData }: ProposalProps) => {
const { t } = useTranslation();
const { submit, Dialog, finalizedVote, transaction } = useVoteSubmit();
const { submit, finalizedVote, transaction } = useVoteSubmit();
const { voteState, voteDatetime } = useUserVote(proposal?.id, finalizedVote);
return (
@@ -88,7 +88,6 @@ export const Proposal = ({ proposal, restData }: ProposalProps) => {
<UserVote
proposal={proposal}
submit={submit}
dialog={Dialog}
transaction={transaction}
voteState={voteState}
voteDatetime={voteDatetime}
@@ -1,15 +1,14 @@
import { BrowserRouter as Router } from 'react-router-dom';
import { VegaWalletContext } from '@vegaprotocol/wallet';
import { render, screen } from '@testing-library/react';
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
import { ProposalsListItemDetails } from './proposals-list-item-details';
import { mockWalletContext } from '../../test-helpers/mocks';
const renderComponent = (id: string) =>
render(
<Router>
<VegaWalletContext.Provider value={mockWalletContext}>
<AppStateProvider>
<ProposalsListItemDetails id={id} />
</VegaWalletContext.Provider>
</AppStateProvider>
</Router>
);
@@ -1,25 +1,32 @@
import { render, screen } from '@testing-library/react';
import { VegaWalletContext } from '@vegaprotocol/wallet';
import { act, render, screen } from '@testing-library/react';
import {
MockedWalletProvider,
mockConfig,
} from '@vegaprotocol/wallet-react/testing';
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
import { ProposalFormSubmit } from './proposal-form-submit';
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
const renderComponent = (
context: VegaWalletContextShape,
isSubmitting: boolean
) => {
const renderComponent = (isSubmitting: boolean) => {
render(
<AppStateProvider>
<VegaWalletContext.Provider value={context}>
<MockedWalletProvider>
<AppStateProvider>
<ProposalFormSubmit isSubmitting={isSubmitting} />
</VegaWalletContext.Provider>
</AppStateProvider>
</AppStateProvider>
</MockedWalletProvider>
);
};
describe('Proposal Form Submit', () => {
const pubKey = { publicKey: '123456__123456', name: 'test' };
afterEach(() => {
act(() => {
mockConfig.reset();
});
});
it('should display connection message and button if wallet not connected', () => {
renderComponent({ pubKey: null } as VegaWalletContextShape, false);
renderComponent(false);
expect(
screen.getByText('Connect your wallet to submit a proposal')
@@ -30,28 +37,22 @@ describe('Proposal Form Submit', () => {
});
it('should display submit button if wallet is connected', () => {
const pubKey = { publicKey: '123456__123456', name: 'test' };
renderComponent(
{
pubKey: pubKey.publicKey,
pubKeys: [pubKey],
} as VegaWalletContextShape,
false
);
mockConfig.store.setState({
pubKey: pubKey.publicKey,
keys: [pubKey],
});
renderComponent(false);
expect(screen.getByTestId('proposal-submit')).toHaveTextContent(
'Submit proposal'
);
});
it('should display submitting button text if wallet is connected and submitting', () => {
const pubKey = { publicKey: '123456__123456', name: 'test' };
renderComponent(
{
pubKey: pubKey.publicKey,
pubKeys: [pubKey],
} as VegaWalletContextShape,
true
);
mockConfig.store.setState({
pubKey: pubKey.publicKey,
keys: [pubKey],
});
renderComponent(true);
expect(screen.getByTestId('proposal-submit')).toHaveTextContent(
'Submitting proposal'
);
@@ -1,6 +1,6 @@
import { useTranslation } from 'react-i18next';
import { Button } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useVegaWallet } from '@vegaprotocol/wallet-react';
import { VegaWalletContainer } from '../../../../components/vega-wallet-container';
interface ProposalFormSubmitProps {
@@ -1,19 +1,24 @@
import {
VegaTransactionDialog,
getProposalDialogIcon,
getProposalDialogIntent,
useGetProposalDialogTitle,
} from '@vegaprotocol/proposals';
import type { ProposalEventFieldsFragment } from '@vegaprotocol/proposals';
import type { DialogProps } from '@vegaprotocol/proposals';
import type {
ProposalEventFieldsFragment,
VegaTxState,
} from '@vegaprotocol/proposals';
interface ProposalFormTransactionDialogProps {
finalizedProposal: ProposalEventFieldsFragment | null;
TransactionDialog: (props: DialogProps) => JSX.Element;
transaction: VegaTxState;
onChange: (open: boolean) => void;
}
export const ProposalFormTransactionDialog = ({
finalizedProposal,
TransactionDialog,
transaction,
onChange,
}: ProposalFormTransactionDialogProps) => {
const title = useGetProposalDialogTitle(finalizedProposal?.state);
// Render a custom complete UI if the proposal was rejected otherwise
@@ -24,13 +29,16 @@ export const ProposalFormTransactionDialog = ({
return (
<div data-testid="proposal-transaction-dialog">
<TransactionDialog
<VegaTransactionDialog
title={title}
intent={getProposalDialogIntent(finalizedProposal?.state)}
icon={getProposalDialogIcon(finalizedProposal?.state)}
content={{
Complete: completeContent,
}}
transaction={transaction}
isOpen={transaction.dialogOpen}
onChange={onChange}
/>
</div>
);
@@ -1,10 +1,8 @@
import { render, screen } from '@testing-library/react';
import { BrowserRouter as Router } from 'react-router-dom';
import { MockedProvider } from '@apollo/client/testing';
import { VegaWalletContext } from '@vegaprotocol/wallet';
import {
lastWeek,
mockWalletContext,
networkParamsQueryMock,
nextWeek,
} from '../../test-helpers/mocks';
@@ -48,9 +46,7 @@ const renderComponent = (
render(
<Router>
<MockedProvider mocks={mocks}>
<VegaWalletContext.Provider value={mockWalletContext}>
<VoteBreakdown proposal={proposal} />
</VegaWalletContext.Provider>
<VoteBreakdown proposal={proposal} />
</MockedProvider>
</Router>
);
@@ -60,6 +56,7 @@ describe('VoteBreakdown', () => {
jest.useFakeTimers();
jest.setSystemTime(0);
});
afterAll(() => {
jest.useRealTimers();
});
@@ -1,5 +1,5 @@
import { captureMessage } from '@sentry/minimal';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useVegaWallet } from '@vegaprotocol/wallet-react';
import { VoteValue } from '@vegaprotocol/types';
import { useEffect, useState } from 'react';
import { useUserVoteQuery } from './__generated__/Vote';
@@ -1,20 +1,19 @@
import { useTranslation } from 'react-i18next';
import { Icon, ExternalLink } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useVegaWallet } from '@vegaprotocol/wallet-react';
import { ProposalState } from '@vegaprotocol/types';
import { ConnectToVega } from '../../../../components/connect-to-vega';
import { VoteButtonsContainer } from './vote-buttons';
import { SubHeading } from '../../../../components/heading';
import { type VoteValue } from '@vegaprotocol/types';
import { type DialogProps, type VegaTxState } from '@vegaprotocol/proposals';
import { type VegaTxState } from '@vegaprotocol/proposals';
import { type VoteState } from './use-user-vote';
import { type Proposal, type BatchProposal } from '../../types';
interface UserVoteProps {
proposal: Proposal | BatchProposal;
transaction: VegaTxState | null;
transaction: VegaTxState;
submit: (voteValue: VoteValue, proposalId: string | null) => Promise<void>;
dialog: (props: DialogProps) => JSX.Element;
voteState: VoteState | null;
voteDatetime: Date | null;
}
@@ -23,7 +22,6 @@ export const UserVote = ({
proposal,
submit,
transaction,
dialog,
voteState,
voteDatetime,
}: UserVoteProps) => {
@@ -56,7 +54,6 @@ export const UserVote = ({
className="flex"
submit={submit}
transaction={transaction}
dialog={dialog}
/>
)
) : (
@@ -2,33 +2,20 @@ import { render, screen } from '@testing-library/react';
import { VoteTransactionDialog } from './vote-transaction-dialog';
import { VoteState } from './use-user-vote';
import { VegaTxStatus } from '@vegaprotocol/proposals';
import { ConnectorErrors, unknownError } from '@vegaprotocol/wallet';
describe('VoteTransactionDialog', () => {
const mockTransactionDialog = jest.fn(({ title, content }) => (
<div>
<div>{title}</div>
<div>{content?.Complete}</div>
</div>
));
it('renders without crashing', () => {
render(
<VoteTransactionDialog
voteState={VoteState.Yes}
transaction={null}
TransactionDialog={mockTransactionDialog}
/>
);
expect(screen.getByTestId('vote-transaction-dialog')).toBeInTheDocument();
});
it('renders with txRequested title when voteState is Requested', () => {
render(
<VoteTransactionDialog
voteState={VoteState.Requested}
transaction={null}
TransactionDialog={mockTransactionDialog}
transaction={{
error: null,
txHash: null,
signature: null,
status: VegaTxStatus.Requested,
dialogOpen: true,
}}
/>
);
@@ -39,8 +26,13 @@ describe('VoteTransactionDialog', () => {
render(
<VoteTransactionDialog
voteState={VoteState.Pending}
transaction={null}
TransactionDialog={mockTransactionDialog}
transaction={{
error: null,
txHash: null,
signature: null,
status: VegaTxStatus.Pending,
dialogOpen: true,
}}
/>
);
@@ -51,8 +43,13 @@ describe('VoteTransactionDialog', () => {
render(
<VoteTransactionDialog
voteState={VoteState.Yes} // or any other state other than Requested or Pending
transaction={null}
TransactionDialog={mockTransactionDialog}
transaction={{
error: null,
txHash: null,
signature: null,
status: VegaTxStatus.Complete,
dialogOpen: true,
}}
/>
);
@@ -65,17 +62,18 @@ describe('VoteTransactionDialog', () => {
<VoteTransactionDialog
voteState={VoteState.Failed}
transaction={{
error: { message: 'Custom error test message', name: 'blah' },
error: unknownError(),
txHash: null,
signature: null,
status: VegaTxStatus.Error,
dialogOpen: false,
dialogOpen: true,
}}
TransactionDialog={mockTransactionDialog}
/>
);
expect(screen.getByText('Custom error test message')).toBeInTheDocument();
expect(
screen.getByText(ConnectorErrors.unknown.message)
).toBeInTheDocument();
});
it('renders default error message when voteState is failed and no error message exists on the tx', () => {
@@ -86,10 +84,9 @@ describe('VoteTransactionDialog', () => {
error: null,
txHash: null,
signature: null,
status: VegaTxStatus.Error,
dialogOpen: false,
status: VegaTxStatus.Complete,
dialogOpen: true,
}}
TransactionDialog={mockTransactionDialog}
/>
);
@@ -100,8 +97,13 @@ describe('VoteTransactionDialog', () => {
render(
<VoteTransactionDialog
voteState={VoteState.Yes}
transaction={null}
TransactionDialog={mockTransactionDialog}
transaction={{
error: null,
txHash: null,
signature: null,
status: VegaTxStatus.Default,
dialogOpen: true,
}}
/>
);
@@ -1,120 +1,77 @@
import { fireEvent, render, screen } from '@testing-library/react';
import { act, fireEvent, render, screen } from '@testing-library/react';
import BigNumber from 'bignumber.js';
import { VoteButtons } from './vote-buttons';
import { VoteButtons, type VoteButtonsProps } 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';
import { VegaTxStatus } from '@vegaprotocol/proposals';
import {
MockedWalletProvider,
mockConfig,
} from '@vegaprotocol/wallet-react/testing';
describe('Vote buttons', () => {
it('should render successfully', () => {
const { baseElement } = render(
const key = { publicKey: '0x123', name: 'key 1' };
const transaction = {
status: VegaTxStatus.Default,
error: null,
txHash: null,
signature: null,
dialogOpen: false,
};
const props = {
voteState: VoteState.NotCast,
voteDatetime: null,
proposalState: ProposalState.STATE_OPEN,
proposalId: null,
minVoterBalance: null,
spamProtectionMinTokens: null,
currentStakeAvailable: new BigNumber(1),
submit: () => Promise.resolve(),
transaction,
};
const renderComponent = (testProps?: Partial<VoteButtonsProps>) => {
return render(
<AppStateProvider>
<MockedProvider>
<VegaWalletContext.Provider value={mockWalletContext}>
<VoteButtons
voteState={VoteState.NotCast}
voteDatetime={null}
proposalState={ProposalState.STATE_OPEN}
proposalId={null}
minVoterBalance={null}
spamProtectionMinTokens={null}
currentStakeAvailable={new BigNumber(1)}
dialog={() => <div>Blah</div>}
submit={() => Promise.resolve()}
transaction={null}
/>
</VegaWalletContext.Provider>
<MockedWalletProvider>
<VoteButtons {...props} {...testProps} />
</MockedWalletProvider>
</MockedProvider>
</AppStateProvider>
);
};
beforeEach(() => {
mockConfig.store.setState({ pubKey: key.publicKey, keys: [key] });
});
afterEach(() => {
act(() => {
mockConfig.reset();
});
});
it('should render successfully', () => {
const { baseElement } = renderComponent();
expect(baseElement).toBeTruthy();
});
it('should explain that voting is closed if the proposal is not open', () => {
render(
<AppStateProvider>
<MockedProvider>
<VegaWalletContext.Provider value={mockWalletContext}>
<VoteButtons
voteState={VoteState.NotCast}
voteDatetime={null}
proposalState={ProposalState.STATE_PASSED}
proposalId={null}
minVoterBalance={null}
spamProtectionMinTokens={null}
currentStakeAvailable={new BigNumber(1)}
dialog={() => <div>Blah</div>}
submit={() => Promise.resolve()}
transaction={null}
/>
</VegaWalletContext.Provider>
</MockedProvider>
</AppStateProvider>
);
renderComponent({ proposalState: ProposalState.STATE_PASSED });
expect(screen.getByText('Voting has ended.')).toBeTruthy();
});
it('should provide a connect wallet prompt if no pubkey', () => {
const mockWalletNoPubKeyContext = {
pubKey: null,
pubKeys: [],
isReadOnly: false,
sendTx: jest.fn().mockReturnValue(Promise.resolve(null)),
connect: jest.fn(),
disconnect: jest.fn(),
selectPubKey: jest.fn(),
connector: null,
} as unknown as VegaWalletContextShape;
render(
<AppStateProvider>
<MockedProvider>
<VegaWalletContext.Provider value={mockWalletNoPubKeyContext}>
<VoteButtons
voteState={VoteState.NotCast}
voteDatetime={null}
proposalState={ProposalState.STATE_OPEN}
proposalId={null}
minVoterBalance={null}
spamProtectionMinTokens={null}
currentStakeAvailable={new BigNumber(1)}
dialog={() => <div>Blah</div>}
submit={() => Promise.resolve()}
transaction={null}
/>
</VegaWalletContext.Provider>
</MockedProvider>
</AppStateProvider>
);
mockConfig.reset();
renderComponent();
expect(screen.getByTestId('connect-wallet')).toBeTruthy();
});
it('should tell the user they need tokens if their current stake is 0', () => {
render(
<AppStateProvider>
<MockedProvider>
<VegaWalletContext.Provider value={mockWalletContext}>
<VoteButtons
voteState={VoteState.NotCast}
voteDatetime={null}
proposalState={ProposalState.STATE_OPEN}
proposalId={null}
minVoterBalance={null}
spamProtectionMinTokens={null}
currentStakeAvailable={new BigNumber(0)}
dialog={() => <div>Blah</div>}
submit={() => Promise.resolve()}
transaction={null}
/>
</VegaWalletContext.Provider>
</MockedProvider>
</AppStateProvider>
);
renderComponent({ currentStakeAvailable: new BigNumber(0) });
expect(
screen.getByText(
'You need some VEGA tokens to participate in governance.'
@@ -123,26 +80,10 @@ describe('Vote buttons', () => {
});
it('should tell the user of the minimum requirements if they have some, but not enough tokens', () => {
render(
<AppStateProvider>
<MockedProvider>
<VegaWalletContext.Provider value={mockWalletContext}>
<VoteButtons
voteState={VoteState.NotCast}
voteDatetime={null}
proposalState={ProposalState.STATE_OPEN}
proposalId={null}
minVoterBalance="2000000000000000000"
spamProtectionMinTokens="1000000000000000000"
currentStakeAvailable={new BigNumber(1)}
dialog={() => <div>Blah</div>}
submit={() => Promise.resolve()}
transaction={null}
/>
</VegaWalletContext.Provider>
</MockedProvider>
</AppStateProvider>
);
renderComponent({
minVoterBalance: '2000000000000000000',
spamProtectionMinTokens: '1000000000000000000',
});
expect(
screen.getByText(
'You must have at least 2 VEGA associated to vote on this proposal'
@@ -151,51 +92,23 @@ describe('Vote buttons', () => {
});
it('should show you voted if vote state is correct, and if the proposal is still open, it will display a change vote button', () => {
render(
<AppStateProvider>
<MockedProvider>
<VegaWalletContext.Provider value={mockWalletContext}>
<VoteButtons
voteState={VoteState.Yes}
voteDatetime={null}
proposalState={ProposalState.STATE_OPEN}
proposalId={null}
minVoterBalance="2000000000000000000"
spamProtectionMinTokens="1000000000000000000"
currentStakeAvailable={new BigNumber(10)}
dialog={() => <div>Blah</div>}
submit={() => Promise.resolve()}
transaction={null}
/>
</VegaWalletContext.Provider>
</MockedProvider>
</AppStateProvider>
);
renderComponent({
voteState: VoteState.Yes,
minVoterBalance: '2000000000000000000',
spamProtectionMinTokens: '1000000000000000000',
currentStakeAvailable: new BigNumber(10),
});
expect(screen.getByTestId('you-voted')).toBeInTheDocument();
expect(screen.getByTestId('change-vote-button')).toBeInTheDocument();
});
it('should allow you to change your vote', () => {
render(
<AppStateProvider>
<MockedProvider>
<VegaWalletContext.Provider value={mockWalletContext}>
<VoteButtons
voteState={VoteState.No}
voteDatetime={null}
proposalState={ProposalState.STATE_OPEN}
proposalId={null}
minVoterBalance="2000000000000000000"
spamProtectionMinTokens="1000000000000000000"
currentStakeAvailable={new BigNumber(10)}
dialog={() => <div>Blah</div>}
submit={() => Promise.resolve()}
transaction={null}
/>
</VegaWalletContext.Provider>
</MockedProvider>
</AppStateProvider>
);
renderComponent({
voteState: VoteState.No,
minVoterBalance: '2000000000000000000',
spamProtectionMinTokens: '1000000000000000000',
currentStakeAvailable: new BigNumber(10),
});
fireEvent.click(screen.getByTestId('change-vote-button'));
expect(screen.getByTestId('vote-buttons')).toBeInTheDocument();
});
@@ -1,7 +1,7 @@
import { format } from 'date-fns';
import React, { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
import { useVegaWallet, useDialogStore } from '@vegaprotocol/wallet-react';
import {
AsyncRenderer,
Button,
@@ -17,7 +17,7 @@ import { VoteState } from './use-user-vote';
import { ProposalMinRequirements, ProposalUserAction } from '../shared';
import { VoteTransactionDialog } from './vote-transaction-dialog';
import { useVoteButtonsQuery } from './__generated__/Stake';
import type { DialogProps, VegaTxState } from '@vegaprotocol/proposals';
import type { VegaTxState } from '@vegaprotocol/proposals';
import { filterAcceptableGraphqlErrors } from '../../../../lib/party';
import {
NetworkParams,
@@ -32,8 +32,7 @@ interface VoteButtonsContainerProps {
proposalId: string | null;
proposalState: ProposalState;
submit: (voteValue: VoteValue, proposalId: string | null) => Promise<void>;
transaction: VegaTxState | null;
dialog: (props: DialogProps) => JSX.Element;
transaction: VegaTxState;
className?: string;
}
@@ -136,17 +135,16 @@ export const VoteButtonsContainer = (props: VoteButtonsContainerProps) => {
);
};
interface VoteButtonsProps {
export interface VoteButtonsProps {
voteState: VoteState | null;
voteDatetime: Date | null;
proposalId: string | null;
proposalState: ProposalState;
submit: (voteValue: VoteValue, proposalId: string | null) => Promise<void>;
transaction: VegaTxState | null;
dialog: (props: DialogProps) => JSX.Element;
proposalId: string | null;
currentStakeAvailable: BigNumber;
minVoterBalance: string | null;
spamProtectionMinTokens: string | null;
submit: (voteValue: VoteValue, proposalId: string | null) => Promise<void>;
transaction: VegaTxState;
}
export const VoteButtons = ({
@@ -159,13 +157,10 @@ export const VoteButtons = ({
spamProtectionMinTokens,
submit,
transaction,
dialog: Dialog,
}: VoteButtonsProps) => {
const { t } = useTranslation();
const { pubKey } = useVegaWallet();
const { openVegaWalletDialog } = useVegaWalletDialogStore((store) => ({
openVegaWalletDialog: store.openVegaWalletDialog,
}));
const openVegaWalletDialog = useDialogStore((store) => store.open);
const [changeVote, setChangeVote] = React.useState(false);
const proposalVotable = useMemo(
() =>
@@ -184,11 +179,7 @@ export const VoteButtons = ({
if (!pubKey) {
return (
<div data-testid="connect-wallet">
<ButtonLink
onClick={() => {
openVegaWalletDialog();
}}
>
<ButtonLink onClick={openVegaWalletDialog}>
{t('connectVegaWallet')}
</ButtonLink>{' '}
{t('toVote')}
@@ -301,11 +292,7 @@ export const VoteButtons = ({
</p>
)
)}
<VoteTransactionDialog
voteState={voteState}
transaction={transaction}
TransactionDialog={Dialog}
/>
<VoteTransactionDialog voteState={voteState} transaction={transaction} />
</>
);
};
@@ -1,11 +1,13 @@
import { t } from '@vegaprotocol/i18n';
import { VoteState } from './use-user-vote';
import type { DialogProps, VegaTxState } from '@vegaprotocol/proposals';
import {
VegaTransactionDialog,
type VegaTxState,
} from '@vegaprotocol/proposals';
interface VoteTransactionDialogProps {
voteState: VoteState;
transaction: VegaTxState | null;
TransactionDialog: (props: DialogProps) => JSX.Element;
transaction: VegaTxState;
}
const dialogTitle = (voteState: VoteState): string | undefined => {
@@ -22,22 +24,23 @@ const dialogTitle = (voteState: VoteState): string | undefined => {
export const VoteTransactionDialog = ({
voteState,
transaction,
TransactionDialog,
}: VoteTransactionDialogProps) => {
// Render a custom message if the voting fails otherwise
// pass undefined so that the default vega transaction dialog UI gets used
const customMessage =
voteState === VoteState.Failed ? (
<p>{transaction?.error?.message || t('voteError')}</p>
<p>{transaction.error?.message || t('voteError')}</p>
) : undefined;
return (
<div data-testid="vote-transaction-dialog">
<TransactionDialog
<VegaTransactionDialog
title={dialogTitle(voteState)}
transaction={transaction}
content={{
Complete: customMessage,
}}
isOpen={transaction.dialogOpen}
/>
</div>
);
@@ -1,13 +1,12 @@
import { render, screen } from '@testing-library/react';
import { ProposeFreeform } from './propose-freeform';
import { MockedProvider } from '@apollo/client/testing';
import { mockWalletContext } from '../../test-helpers/mocks';
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
import { VegaWalletContext } from '@vegaprotocol/wallet';
import { MemoryRouter as Router } from 'react-router-dom';
import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters';
import type { MockedResponse } from '@apollo/client/testing';
import { NetworkParamsDocument } from '@vegaprotocol/network-parameters';
import { MockedWalletProvider } from '@vegaprotocol/wallet-react/testing';
jest.mock('@vegaprotocol/environment', () => ({
...jest.requireActual('@vegaprotocol/environment'),
@@ -72,18 +71,19 @@ const updateMarketNetworkParamsQueryMock: MockedResponse<NetworkParamsQuery> = {
},
};
const renderComponent = () =>
render(
const renderComponent = () => {
return render(
<Router>
<MockedProvider mocks={[updateMarketNetworkParamsQueryMock]}>
<AppStateProvider>
<VegaWalletContext.Provider value={mockWalletContext}>
<MockedWalletProvider>
<AppStateProvider>
<ProposeFreeform />
</VegaWalletContext.Provider>
</AppStateProvider>
</AppStateProvider>
</MockedWalletProvider>
</MockedProvider>
</Router>
);
};
// Note: form submission is tested in propose-raw.spec.tsx. Reusable form
// components are tested in their own directory.
@@ -51,7 +51,8 @@ export const ProposeFreeform = () => {
watch,
trigger,
} = useForm<FreeformProposalFormFields>();
const { finalizedProposal, submit, Dialog } = useProposalSubmit();
const { finalizedProposal, transaction, submit, setTransaction } =
useProposalSubmit();
const assembleProposal = (fields: FreeformProposalFormFields) => {
const isVoteDeadlineAtMinimum =
@@ -169,7 +170,8 @@ export const ProposeFreeform = () => {
<ProposalFormDownloadJson downloadJson={viewJson} />
<ProposalFormTransactionDialog
finalizedProposal={finalizedProposal}
TransactionDialog={Dialog}
transaction={transaction}
onChange={(open) => setTransaction({ dialogOpen: open })}
/>
</form>
</div>
@@ -1,13 +1,12 @@
import { fireEvent, render, screen } from '@testing-library/react';
import { ProposeNetworkParameter } from './propose-network-parameter';
import { MockedProvider } from '@apollo/client/testing';
import { mockWalletContext } from '../../test-helpers/mocks';
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
import { VegaWalletContext } from '@vegaprotocol/wallet';
import { MemoryRouter as Router } from 'react-router-dom';
import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters';
import { NetworkParamsDocument } from '@vegaprotocol/network-parameters';
import type { MockedResponse } from '@apollo/client/testing';
import { MockedWalletProvider } from '@vegaprotocol/wallet-react/testing';
jest.mock('@vegaprotocol/environment', () => ({
...jest.requireActual('@vegaprotocol/environment'),
@@ -72,26 +71,27 @@ const updateMarketNetworkParamsQueryMock: MockedResponse<NetworkParamsQuery> = {
},
};
const renderComponent = () =>
render(
const renderComponent = () => {
return render(
<Router>
<MockedProvider mocks={[updateMarketNetworkParamsQueryMock]}>
<AppStateProvider>
<VegaWalletContext.Provider value={mockWalletContext}>
<MockedWalletProvider>
<AppStateProvider>
<ProposeNetworkParameter />
</VegaWalletContext.Provider>
</AppStateProvider>
</AppStateProvider>
</MockedWalletProvider>
</MockedProvider>
</Router>
);
};
// Note: form submission is tested in propose-raw.spec.tsx. Reusable form
// components are tested in their own directory.
describe('Propose Network Parameter', () => {
it('should render successfully', async () => {
it('should render successfully', () => {
const { baseElement } = renderComponent();
await expect(baseElement).toBeTruthy();
expect(baseElement).toBeTruthy();
});
it('should render the correct title', async () => {
@@ -91,7 +91,8 @@ export const ProposeNetworkParameter = () => {
watch,
trigger,
} = useForm<NetworkParameterProposalFormFields>();
const { finalizedProposal, submit, Dialog } = useProposalSubmit();
const { finalizedProposal, transaction, submit, setTransaction } =
useProposalSubmit();
const selectedParamEntry = params
? Object.entries(params).find(([key]) => key === selectedNetworkParam)
@@ -312,7 +313,8 @@ export const ProposeNetworkParameter = () => {
<ProposalFormDownloadJson downloadJson={viewJson} />
<ProposalFormTransactionDialog
finalizedProposal={finalizedProposal}
TransactionDialog={Dialog}
transaction={transaction}
onChange={(open) => setTransaction({ dialogOpen: open })}
/>
</form>
</div>
@@ -1,13 +1,12 @@
import { MockedProvider } from '@apollo/client/testing';
import { MemoryRouter as Router } from 'react-router-dom';
import { render, screen } from '@testing-library/react';
import { VegaWalletContext } from '@vegaprotocol/wallet';
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
import { mockWalletContext } from '../../test-helpers/mocks';
import { ProposeNewAsset } from './propose-new-asset';
import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters';
import type { MockedResponse } from '@apollo/client/testing';
import { NetworkParamsDocument } from '@vegaprotocol/network-parameters';
import { MockedWalletProvider } from '@vegaprotocol/wallet-react/testing';
jest.mock('@vegaprotocol/environment', () => ({
...jest.requireActual('@vegaprotocol/environment'),
@@ -72,26 +71,27 @@ const newAssetNetworkParamsQueryMock: MockedResponse<NetworkParamsQuery> = {
},
};
const renderComponent = () =>
render(
const renderComponent = () => {
return render(
<Router>
<MockedProvider mocks={[newAssetNetworkParamsQueryMock]}>
<AppStateProvider>
<VegaWalletContext.Provider value={mockWalletContext}>
<MockedWalletProvider>
<AppStateProvider>
<ProposeNewAsset />
</VegaWalletContext.Provider>
</AppStateProvider>
</AppStateProvider>
</MockedWalletProvider>
</MockedProvider>
</Router>
);
};
// Note: form submission is tested in propose-raw.spec.tsx. Reusable form
// components are tested in their own directory.
describe('Propose New Asset', () => {
it('should render successfully', async () => {
it('should render successfully', () => {
const { baseElement } = renderComponent();
await expect(baseElement).toBeTruthy();
expect(baseElement).toBeTruthy();
});
it('should render the title', async () => {
@@ -64,7 +64,8 @@ export const ProposeNewAsset = () => {
watch,
trigger,
} = useForm<NewAssetProposalFormFields>();
const { finalizedProposal, submit, Dialog } = useProposalSubmit();
const { finalizedProposal, transaction, submit, setTransaction } =
useProposalSubmit();
const assembleProposal = (fields: NewAssetProposalFormFields) => {
const isVoteDeadlineAtMinimum = doesValueEquateToParam(
@@ -232,7 +233,8 @@ export const ProposeNewAsset = () => {
<ProposalFormDownloadJson downloadJson={viewJson} />
<ProposalFormTransactionDialog
finalizedProposal={finalizedProposal}
TransactionDialog={Dialog}
transaction={transaction}
onChange={(open) => setTransaction({ dialogOpen: open })}
/>
</form>
</div>
@@ -1,13 +1,12 @@
import { render, screen } from '@testing-library/react';
import { ProposeNewMarket } from './propose-new-market';
import { MockedProvider } from '@apollo/client/testing';
import { mockWalletContext } from '../../test-helpers/mocks';
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
import { VegaWalletContext } from '@vegaprotocol/wallet';
import { BrowserRouter as Router } from 'react-router-dom';
import type { MockedResponse } from '@apollo/client/testing';
import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters';
import { NetworkParamsDocument } from '@vegaprotocol/network-parameters';
import { MockedWalletProvider } from '@vegaprotocol/wallet-react/testing';
jest.mock('@vegaprotocol/environment', () => ({
...jest.requireActual('@vegaprotocol/environment'),
@@ -72,26 +71,27 @@ const newMarketNetworkParamsQueryMock: MockedResponse<NetworkParamsQuery> = {
},
};
const renderComponent = () =>
render(
const renderComponent = () => {
return render(
<Router>
<MockedProvider mocks={[newMarketNetworkParamsQueryMock]}>
<AppStateProvider>
<VegaWalletContext.Provider value={mockWalletContext}>
<MockedWalletProvider>
<AppStateProvider>
<ProposeNewMarket />
</VegaWalletContext.Provider>
</AppStateProvider>
</AppStateProvider>
</MockedWalletProvider>
</MockedProvider>
</Router>
);
};
// Note: form submission is tested in propose-raw.spec.tsx. Reusable form
// components are tested in their own directory.
describe('Propose New Market', () => {
it('should render successfully', async () => {
it('should render successfully', () => {
const { baseElement } = renderComponent();
await expect(baseElement).toBeTruthy();
expect(baseElement).toBeTruthy();
});
it('should render the form components', async () => {
@@ -62,7 +62,8 @@ export const ProposeNewMarket = () => {
watch,
trigger,
} = useForm<NewMarketProposalFormFields>();
const { finalizedProposal, submit, Dialog } = useProposalSubmit();
const { finalizedProposal, transaction, submit, setTransaction } =
useProposalSubmit();
const assembleProposal = (fields: NewMarketProposalFormFields) => {
const isVoteDeadlineAtMinimum = doesValueEquateToParam(
@@ -214,7 +215,8 @@ export const ProposeNewMarket = () => {
<ProposalFormDownloadJson downloadJson={viewJson} />
<ProposalFormTransactionDialog
finalizedProposal={finalizedProposal}
TransactionDialog={Dialog}
transaction={transaction}
onChange={(open) => setTransaction({ dialogOpen: open })}
/>
</form>
</div>
@@ -3,8 +3,6 @@ import type { MockedResponse } from '@apollo/client/testing';
import { addHours, getTime } from 'date-fns';
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
import { MockedProvider } from '@apollo/client/testing';
import type { VegaWalletContextShape } from '@vegaprotocol/wallet';
import { VegaWalletContext } from '@vegaprotocol/wallet';
import * as Schema from '@vegaprotocol/types';
import { ProposeRaw } from './propose-raw';
import { ProposalEventDocument } from '@vegaprotocol/proposals';
@@ -12,6 +10,11 @@ import type { ProposalEventSubscription } from '@vegaprotocol/proposals';
import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters';
import { NetworkParamsDocument } from '@vegaprotocol/network-parameters';
import {
MockedWalletProvider,
mockConfig,
} from '@vegaprotocol/wallet-react/testing';
import { userRejectedError } from '@vegaprotocol/wallet';
const paramsDelay = 20;
@@ -103,23 +106,15 @@ describe('Raw proposal form', () => {
},
delay: 300,
};
const setup = (mockSendTx = jest.fn()) => {
const setup = () => {
return render(
<AppStateProvider>
<MockedProvider
mocks={[rawProposalNetworkParamsQueryMock, mockProposalEvent]}
>
<VegaWalletContext.Provider
value={
{
pubKey,
sendTx: mockSendTx,
links: { explorer: 'explorer' },
} as unknown as VegaWalletContextShape
}
>
<MockedWalletProvider>
<ProposeRaw />
</VegaWalletContext.Provider>
</MockedWalletProvider>
</MockedProvider>
</AppStateProvider>
);
@@ -127,15 +122,22 @@ describe('Raw proposal form', () => {
beforeAll(() => {
jest.useFakeTimers();
mockConfig.store.setState({ status: 'connected', pubKey: '0x123' });
});
afterAll(() => {
jest.useRealTimers();
mockConfig.reset();
});
afterEach(() => {
jest.clearAllMocks();
});
it('handles validation', async () => {
const mockSendTx = jest.fn().mockReturnValue(Promise.resolve());
setup(mockSendTx);
const mockSendTx = jest.spyOn(mockConfig, 'sendTransaction');
setup();
expect(await screen.findByTestId('proposal-submit')).toBeTruthy();
await act(async () => {
@@ -162,20 +164,25 @@ describe('Raw proposal form', () => {
});
it('sends the transaction', async () => {
const mockSendTx = jest.fn().mockReturnValue(
new Promise((resolve) => {
setTimeout(
() =>
resolve({
transactionHash: 'tx-hash',
signature:
'cfe592d169f87d0671dd447751036d0dddc165b9c4b65e5a5060e2bbadd1aa726d4cbe9d3c3b327bcb0bff4f83999592619a2493f9bbd251fae99ce7ce766909',
}),
100
);
})
);
setup(mockSendTx);
const mockSendTx = jest
.spyOn(mockConfig, 'sendTransaction')
.mockReturnValue(
new Promise((resolve) => {
setTimeout(
() =>
resolve({
transactionHash: 'tx-hash',
signature:
'cfe592d169f87d0671dd447751036d0dddc165b9c4b65e5a5060e2bbadd1aa726d4cbe9d3c3b327bcb0bff4f83999592619a2493f9bbd251fae99ce7ce766909',
sentAt: new Date().toISOString(),
receivedAt: new Date().toISOString(),
}),
100
);
})
);
setup();
await act(async () => {
jest.advanceTimersByTime(paramsDelay);
@@ -206,8 +213,12 @@ describe('Raw proposal form', () => {
fireEvent.click(screen.getByTestId('proposal-submit'));
});
expect(mockSendTx).toHaveBeenCalledWith(pubKey, {
proposalSubmission: JSON.parse(inputJSON),
expect(mockSendTx).toHaveBeenCalledWith({
publicKey: pubKey,
sendingMode: 'TYPE_SYNC',
transaction: {
proposalSubmission: JSON.parse(inputJSON),
},
});
expect(screen.getByTestId('dialog-title')).toHaveTextContent(
@@ -232,12 +243,12 @@ describe('Raw proposal form', () => {
});
it('can be rejected by the user', async () => {
const mockSendTx = jest.fn().mockReturnValue(
new Promise((resolve) => {
setTimeout(() => resolve(null), 100);
jest.spyOn(mockConfig, 'sendTransaction').mockReturnValue(
new Promise((_, reject) => {
setTimeout(() => reject(userRejectedError()), 100);
})
);
setup(mockSendTx);
setup();
await act(async () => {
jest.advanceTimersByTime(paramsDelay);
@@ -52,7 +52,8 @@ export const ProposeRaw = () => {
handleSubmit,
formState: { isSubmitting, errors },
} = useForm<RawProposalFormFields>();
const { finalizedProposal, submit, Dialog } = useProposalSubmit();
const { finalizedProposal, transaction, submit, setTransaction } =
useProposalSubmit();
const hasError = Boolean(errors.rawProposalData?.message);
@@ -152,7 +153,8 @@ export const ProposeRaw = () => {
<ProposalFormSubmit isSubmitting={isSubmitting} />
<ProposalFormTransactionDialog
finalizedProposal={finalizedProposal}
TransactionDialog={Dialog}
transaction={transaction}
onChange={(open) => setTransaction({ dialogOpen: open })}
/>
</form>
</div>
@@ -1,13 +1,12 @@
import { MockedProvider } from '@apollo/client/testing';
import { MemoryRouter as Router } from 'react-router-dom';
import { render, screen } from '@testing-library/react';
import { VegaWalletContext } from '@vegaprotocol/wallet';
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
import { mockWalletContext } from '../../test-helpers/mocks';
import { ProposeUpdateAsset } from './propose-update-asset';
import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters';
import type { MockedResponse } from '@apollo/client/testing';
import { NetworkParamsDocument } from '@vegaprotocol/network-parameters';
import { MockedWalletProvider } from '@vegaprotocol/wallet-react/testing';
jest.mock('@vegaprotocol/environment', () => ({
...jest.requireActual('@vegaprotocol/environment'),
@@ -72,26 +71,27 @@ const updateAssetNetworkParamsQueryMock: MockedResponse<NetworkParamsQuery> = {
},
};
const renderComponent = () =>
render(
const renderComponent = () => {
return render(
<Router>
<MockedProvider mocks={[updateAssetNetworkParamsQueryMock]}>
<AppStateProvider>
<VegaWalletContext.Provider value={mockWalletContext}>
<MockedWalletProvider>
<AppStateProvider>
<ProposeUpdateAsset />
</VegaWalletContext.Provider>
</AppStateProvider>
</AppStateProvider>
</MockedWalletProvider>
</MockedProvider>
</Router>
);
};
// Note: form submission is tested in propose-raw.spec.tsx. Reusable form
// components are tested in their own directory.
describe('Propose Update Asset', () => {
it('should render successfully', async () => {
it('should render successfully', () => {
const { baseElement } = renderComponent();
await expect(baseElement).toBeTruthy();
expect(baseElement).toBeTruthy();
});
it('should render the title', async () => {
@@ -62,7 +62,8 @@ export const ProposeUpdateAsset = () => {
watch,
trigger,
} = useForm<UpdateAssetProposalFormFields>();
const { finalizedProposal, submit, Dialog } = useProposalSubmit();
const { finalizedProposal, transaction, submit, setTransaction } =
useProposalSubmit();
const assembleProposal = (fields: UpdateAssetProposalFormFields) => {
const isVoteDeadlineAtMinimum = doesValueEquateToParam(
@@ -218,7 +219,8 @@ export const ProposeUpdateAsset = () => {
<ProposalFormDownloadJson downloadJson={viewJson} />
<ProposalFormTransactionDialog
finalizedProposal={finalizedProposal}
TransactionDialog={Dialog}
transaction={transaction}
onChange={(open) => setTransaction({ dialogOpen: open })}
/>
</form>
</div>
@@ -2,15 +2,14 @@ import type { MockedResponse } from '@apollo/client/testing';
import { MockedProvider } from '@apollo/client/testing';
import { MemoryRouter as Router } from 'react-router-dom';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { VegaWalletContext } from '@vegaprotocol/wallet';
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
import { mockWalletContext } from '../../test-helpers/mocks';
import { ProposeUpdateMarket } from './propose-update-market';
import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters';
import { NetworkParamsDocument } from '@vegaprotocol/network-parameters';
import type { ProposalMarketsQueryQuery } from './__generated__/UpdateMarket';
import { ProposalMarketsQueryDocument } from './__generated__/UpdateMarket';
import { ProposalState } from '@vegaprotocol/types';
import { MockedWalletProvider } from '@vegaprotocol/wallet-react/testing';
const updateMarketNetworkParamsQueryMock: MockedResponse<NetworkParamsQuery> = {
request: {
@@ -217,29 +216,30 @@ const marketQueryMock: MockedResponse<ProposalMarketsQueryQuery> = {
},
};
const renderComponent = () =>
render(
const renderComponent = () => {
return render(
<MockedProvider
mocks={[updateMarketNetworkParamsQueryMock, marketQueryMock]}
addTypename={false}
>
<Router>
<AppStateProvider>
<VegaWalletContext.Provider value={mockWalletContext}>
<MockedWalletProvider>
<AppStateProvider>
<ProposeUpdateMarket />
</VegaWalletContext.Provider>
</AppStateProvider>
</AppStateProvider>
</MockedWalletProvider>
</Router>
</MockedProvider>
);
};
// Note: form submission is tested in propose-raw.spec.tsx. Reusable form
// components are tested in their own directory.
describe('Propose Update Market', () => {
it('should render successfully', async () => {
it('should render successfully', () => {
const { baseElement } = renderComponent();
await expect(baseElement).toBeTruthy();
expect(baseElement).toBeTruthy();
});
it('should render the title', async () => {
@@ -109,7 +109,8 @@ export const ProposeUpdateMarket = () => {
watch,
trigger,
} = useForm<UpdateMarketProposalFormFields>();
const { finalizedProposal, submit, Dialog } = useProposalSubmit();
const { finalizedProposal, transaction, submit, setTransaction } =
useProposalSubmit();
const assembleProposal = (fields: UpdateMarketProposalFormFields) => {
const isVoteDeadlineAtMinimum = doesValueEquateToParam(
@@ -323,7 +324,8 @@ export const ProposeUpdateMarket = () => {
<ProposalFormDownloadJson downloadJson={viewJson} />
<ProposalFormTransactionDialog
finalizedProposal={finalizedProposal}
TransactionDialog={Dialog}
transaction={transaction}
onChange={(open) => setTransaction({ dialogOpen: open })}
/>
</form>
</div>
@@ -1,28 +1,17 @@
import { NetworkParamsDocument } from '@vegaprotocol/network-parameters';
import type { MockedResponse } from '@apollo/client/testing';
import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters';
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';
import faker from 'faker';
import { type Key } from '@vegaprotocol/wallet';
export const mockPubkey: PubKey = {
export const mockPubkey: Key = {
publicKey: '0x123',
name: 'test key 1',
};
export const mockWalletContext = {
pubKey: mockPubkey.publicKey,
pubKeys: [mockPubkey],
isReadOnly: false,
sendTx: jest.fn().mockReturnValue(Promise.resolve(null)),
connect: jest.fn(),
disconnect: jest.fn(),
selectPubKey: jest.fn(),
connector: null,
} as unknown as VegaWalletContextShape;
const mockEthereumConfig = {
network_id: '3',
chain_id: '3',
@@ -1,13 +1,11 @@
import classNames from 'classnames';
import { useTranslation } from 'react-i18next';
import { useVegaWalletDialogStore } from '@vegaprotocol/wallet';
import { useDialogStore } from '@vegaprotocol/wallet-react';
import { Button } from '@vegaprotocol/ui-toolkit';
import { SubHeading } from '../../components/heading';
export const ConnectToSeeRewards = () => {
const { openVegaWalletDialog } = useVegaWalletDialogStore((store) => ({
openVegaWalletDialog: store.openVegaWalletDialog,
}));
const openVegaWalletDialog = useDialogStore((store) => store.open);
const { t } = useTranslation();
const classes = classNames(
@@ -4,7 +4,7 @@ import { AsyncRenderer, Pagination } from '@vegaprotocol/ui-toolkit';
import { removePaginationWrapper } from '@vegaprotocol/utils';
import type { EpochFieldsFragment } from '../home/__generated__/Rewards';
import { useRewardsQuery } from '../home/__generated__/Rewards';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useVegaWallet } from '@vegaprotocol/wallet-react';
import { EpochIndividualRewardsTable } from './epoch-individual-rewards-table';
import { generateEpochIndividualRewardsList } from './generate-epoch-individual-rewards-list';
import { calculateEpochOffset } from '../../../lib/epoch-pagination';
@@ -10,7 +10,7 @@ import {
Toggle,
ExternalLink,
} from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useVegaWallet } from '@vegaprotocol/wallet-react';
import {
useNetworkParams,
NetworkParams,
@@ -33,8 +33,8 @@ let mockVegaWalletHookValue: {
pubKey: null,
};
jest.mock('@vegaprotocol/wallet', () => ({
...jest.requireActual('@vegaprotocol/wallet'),
jest.mock('@vegaprotocol/wallet-react', () => ({
...jest.requireActual('@vegaprotocol/wallet-react'),
useVegaWallet: jest.fn(() => mockVegaWalletHookValue),
}));
@@ -2,7 +2,7 @@ import { useWeb3React } from '@web3-react/core';
import { useTranslation } from 'react-i18next';
import { EthConnectPrompt } from '../../../../../components/eth-connect-prompt';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useVegaWallet } from '@vegaprotocol/wallet-react';
import { ConnectToVega } from '../../../../../components/connect-to-vega';
export const StakingWalletsContainer = ({
@@ -33,8 +33,8 @@ let mockVegaWalletHookValue: {
pubKey: null,
};
jest.mock('@vegaprotocol/wallet', () => ({
...jest.requireActual('@vegaprotocol/wallet'),
jest.mock('@vegaprotocol/wallet-react', () => ({
...jest.requireActual('@vegaprotocol/wallet-react'),
useVegaWallet: jest.fn(() => mockVegaWalletHookValue),
}));
@@ -1,4 +1,4 @@
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useVegaWallet } from '@vegaprotocol/wallet-react';
import { useWeb3React } from '@web3-react/core';
import { EthConnectPrompt } from '../../../components/eth-connect-prompt';
import { DisassociatePage } from './components/disassociate-page';
@@ -5,7 +5,7 @@ import { useStakingQuery } from '../__generated__/Staking';
import { usePreviousEpochQuery } from '../__generated__/PreviousEpoch';
import { ValidatorTables } from './validator-tables';
import { useRefreshAfterEpoch } from '../../../hooks/use-refresh-after-epoch';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useVegaWallet } from '@vegaprotocol/wallet-react';
import { getMultisigStatusInfo } from '../../../lib/get-multisig-status-info';
import { MultisigIncorrectNotice } from '../../../components/multisig-incorrect-notice';
@@ -2,7 +2,7 @@ import React from 'react';
import { useTranslation } from 'react-i18next';
import { useParams } from 'react-router-dom';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useVegaWallet } from '@vegaprotocol/wallet-react';
import {
addDecimal,
removePaginationWrapper,
@@ -1,5 +1,5 @@
import { Callout, Intent, Splash } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useVegaWallet } from '@vegaprotocol/wallet-react';
import { useTranslation } from 'react-i18next';
import { useRefreshAfterEpoch } from '../../../hooks/use-refresh-after-epoch';
import { SplashLoader } from '../../../components/splash-loader';
@@ -1,78 +0,0 @@
import React from 'react';
import * as Sentry from '@sentry/react';
import { Button, Callout, Intent, Loader } from '@vegaprotocol/ui-toolkit';
import { useTranslation } from 'react-i18next';
import { useAppState } from '../../../contexts/app-state/app-state-context';
import type { BigNumber } from '../../../lib/bignumber';
import type { UndelegateSubmissionBody } from '@vegaprotocol/wallet';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { removeDecimal } from '@vegaprotocol/utils';
interface PendingStakeProps {
pendingAmount: BigNumber;
nodeId: string;
pubKey: string;
}
enum FormState {
Default,
Pending,
Success,
Failure,
}
export const PendingStake = ({
pendingAmount,
nodeId,
pubKey,
}: PendingStakeProps) => {
const { t } = useTranslation();
const { sendTx } = useVegaWallet();
const { appState } = useAppState();
const [formState, setFormState] = React.useState(FormState.Default);
const removeStakeNow = async () => {
setFormState(FormState.Pending);
try {
const command: UndelegateSubmissionBody = {
undelegateSubmission: {
nodeId,
amount: removeDecimal(pendingAmount.toString(), appState.decimals),
method: 'METHOD_NOW',
},
};
await sendTx(pubKey, command);
} catch (err) {
setFormState(FormState.Failure);
Sentry.captureException(err);
}
};
if (formState === FormState.Failure) {
return (
<Callout
intent={Intent.Danger}
title={t('failedToRemovePendingStake', { pendingAmount })}
>
<p>{t('pleaseTryAgain')}</p>
</Callout>
);
} else if (formState === FormState.Pending) {
return (
<Callout
icon={<Loader size="small" />}
title={t('removingPendingStake', { pendingAmount })}
/>
);
}
return (
<div className="py-4">
<h2>{t('pendingNomination')}</h2>
<p>{t('pendingNominationNextEpoch', { pendingAmount })}</p>
<Button onClick={() => removeStakeNow()}>
{t('cancelPendingEpochNomination')}
</Button>
</div>
);
};
@@ -27,11 +27,11 @@ import {
NetworkParams,
} from '@vegaprotocol/network-parameters';
import { useBalances } from '../../../lib/balances/balances-store';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useVegaWallet } from '@vegaprotocol/wallet-react';
import { SubHeading } from '../../../components/heading';
import type {
DelegateSubmissionBody,
UndelegateSubmissionBody,
import {
type DelegateSubmissionBody,
type UndelegateSubmissionBody,
} from '@vegaprotocol/wallet';
import Routes from '../../routes';
@@ -9,7 +9,7 @@ import {
useWithdrawalDialog,
WithdrawalsTable,
} from '@vegaprotocol/withdraws';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useVegaWallet } from '@vegaprotocol/wallet-react';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { useDocumentTitle } from '../../hooks/use-document-title';
import type { RouteChildProps } from '../index';
+2 -4
View File
@@ -6,7 +6,7 @@ import {
VegaIconNames,
useToasts,
} from '@vegaprotocol/ui-toolkit';
import { useVegaWalletDialogStore } from '@vegaprotocol/wallet';
import { useDialogStore } from '@vegaprotocol/wallet-react';
import {
useEthereumTransactionToasts,
useEthereumWithdrawApprovalsToasts,
@@ -19,9 +19,7 @@ import { useTranslation } from 'react-i18next';
const WalletDisconnectAdditionalContent = () => {
const { t } = useTranslation();
const { hideToast } = useWalletDisconnectToastActions();
const openVegaWalletDialog = useVegaWalletDialogStore(
(store) => store.openVegaWalletDialog
);
const openVegaWalletDialog = useDialogStore((store) => store.open);
return (
<p className="mt-2">
<TradingButton
-41
View File
@@ -1,41 +0,0 @@
NX_ETHEREUM_PROVIDER_URL=http://localhost:8545
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_VEGA_CONFIG_URL=''
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_VEGA_ENV=CUSTOM
NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
NX_VEGA_CONSOLE_URL=https://console.fairground.wtf
NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"https://trading.stagnet1.vega.rocks\"}
NX_VEGA_TOKEN_URL=https://governance.fairground.wtf
NX_VEGA_URL=http://localhost:3008/graphql
NX_VEGA_WALLET_URL=http://localhost:1789
NX_ETH_LOCAL_PROVIDER_URL=http://localhost:8545/
NX_ETH_WALLET_MNEMONIC="ozone access unlock valid olympic save include omit supply green clown session"
NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
NX_SENTRY_DSN=https://dummy@o999999.ingest.sentry.io/9999999
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
# Expose some env vars to cypress environment for market setup
CYPRESS_ETH_WALLET_MNEMONIC=ozone access unlock valid olympic save include omit supply green clown session
CYPRESS_ETHEREUM_WALLET_ADDRESS=0xEe7D375bcB50C26d52E1A4a472D8822A2A22d94F
CYPRESS_ETHEREUM_PROVIDER_URL=http://localhost:8545
CYPRESS_EXPLORER_URL=https://explorer.fairground.wtf
CYPRESS_CONSOLE_URL=https://console.fairground.wtf
CYPRESS_FAUCET_URL=http://localhost:1790/api/v1/mint
CYPRESS_ORACLE_PUBKEY=6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61
CYPRESS_TRUNCATED_VEGA_PUBLIC_KEY=02ecea…342f65
CYPRESS_TRUNCATED_VEGA_PUBLIC_KEY2=7f9cf0…c25535
CYPRESS_VEGA_ENV=CUSTOM
CYPRESS_VEGA_PUBLIC_KEY=02eceaba4df2bef76ea10caf728d8a099a2aa846cced25737cccaa9812342f65
CYPRESS_VEGA_PUBLIC_KEY2=7f9cf07d3a9905b1a61a1069f7a758855da428bc0f4a97de87f48644bfc25535
CYPRESS_VEGA_TOKEN_URL=https://governance.fairground.wtf
CYPRESS_VEGA_URL=http://localhost:3008/graphql
CYPRESS_VEGA_WALLET_URL=http://localhost:1789
CYPRESS_VEGA_WALLET_API_TOKEN=
# Cosmic elevator flags (MUST be doubled with CYPRESS_ prefix)
NX_SUCCESSOR_MARKETS=true
CYPRESS_NX_SUCCESSOR_MARKETS=true
-33
View File
@@ -1,33 +0,0 @@
NX_ETHEREUM_PROVIDER_URL=http://localhost:8545
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_VEGA_CONFIG_URL=''
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_VEGA_ENV=CUSTOM
NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
NX_VEGA_CONSOLE_URL=https://console.fairground.wtf
NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"https://trading.stagnet1.vega.rocks\"}
NX_VEGA_TOKEN_URL=https://governance.fairground.wtf
NX_VEGA_URL=http://localhost:3008/graphql
NX_VEGA_WALLET_URL=http://localhost:1789
NX_ETH_LOCAL_PROVIDER_URL=http://localhost:8545/
NX_ETH_WALLET_MNEMONIC="ozone access unlock valid olympic save include omit supply green clown session"
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
# Expose some env vars to cypress environment for market setup
CYPRESS_ETH_WALLET_MNEMONIC=ozone access unlock valid olympic save include omit supply green clown session
CYPRESS_ETHEREUM_WALLET_ADDRESS=0xEe7D375bcB50C26d52E1A4a472D8822A2A22d94F
CYPRESS_ETHEREUM_PROVIDER_URL=http://localhost:8545
CYPRESS_EXPLORER_URL=https://explorer.fairground.wtf
CYPRESS_CONSOLE_URL=https://console.fairground.wtf
CYPRESS_FAUCET_URL=http://localhost:1790/api/v1/mint
CYPRESS_TRUNCATED_VEGA_PUBLIC_KEY=02ecea…342f65
CYPRESS_TRUNCATED_VEGA_PUBLIC_KEY2=7f9cf0…c25535
CYPRESS_VEGA_ENV=CUSTOM
CYPRESS_VEGA_PUBLIC_KEY=02eceaba4df2bef76ea10caf728d8a099a2aa846cced25737cccaa9812342f65
CYPRESS_VEGA_PUBLIC_KEY2=7f9cf07d3a9905b1a61a1069f7a758855da428bc0f4a97de87f48644bfc25535
CYPRESS_VEGA_TOKEN_URL=https://governance.fairground.wtf
CYPRESS_VEGA_URL=http://localhost:3008/graphql
CYPRESS_VEGA_WALLET_URL=http://localhost:1789
CYPRESS_VEGA_WALLET_API_TOKEN=
-19
View File
@@ -1,19 +0,0 @@
{
"extends": ["plugin:cypress/recommended", "../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {
"cypress/unsafe-to-chain-command": 0
}
},
{
"files": ["src/plugins/index.js"],
"rules": {
"@typescript-eslint/no-var-requires": "off",
"no-undef": "off"
}
}
]
}
-41
View File
@@ -1,41 +0,0 @@
const { defineConfig } = require('cypress');
module.exports = defineConfig({
reporter: '../../node_modules/cypress-mochawesome-reporter',
e2e: {
setupNodeEvents(on, config) {
require('cypress-mochawesome-reporter/plugin')(on);
require('@cypress/grep/src/plugin')(config);
return config;
},
baseUrl: 'http://localhost:4200',
fileServerFolder: '.',
fixturesFolder: false,
specPattern: '**/*.cy.{js,jsx,ts,tsx}',
supportFile: './src/support/index.js',
video: false,
videosFolder: '../../dist/cypress/apps/trading-e2e/videos',
videoUploadOnPasses: false,
screenshotsFolder: '../../dist/cypress/apps/trading-e2e/screenshots',
chromeWebSecurity: false,
projectId: 'et4snf',
defaultCommandTimeout: 10000,
viewportWidth: 1800,
viewportHeight: 900,
responseTimeout: 50000,
requestTimeout: 20000,
retries: 1,
testIsolation: false,
experimentalMemoryManagement: true,
},
env: {
ETHERSCAN_URL: 'https://sepolia.etherscan.io',
ETHEREUM_CHAIN_ID: 11155111,
TRADING_MODE_LINK:
'https://docs.vega.xyz/testnet/concepts/trading-on-vega/trading-modes#auction-type-liquidity-monitoring',
grepTags: '@regression @smoke @slow',
grepFilterSpecs: true,
grepOmitFiltered: true,
txTimeout: { timeout: 70000 },
},
});
-1
View File
@@ -1 +0,0 @@
declare module '*.scss';
-39
View File
@@ -1,39 +0,0 @@
{
"name": "trading-e2e",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/trading-e2e/src",
"projectType": "application",
"targets": {
"e2e": {
"executor": "@nx/cypress:cypress",
"options": {
"cypressConfig": "apps/trading-e2e/cypress.config.js",
"devServerTarget": "trading:serve"
},
"configurations": {
"production": {
"devServerTarget": "trading:serve:production"
},
"live": {
"devServerTarget": ""
}
}
},
"lint": {
"executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["apps/trading-e2e/**/*.{js,ts}"]
}
},
"build": {
"executor": "nx:run-commands",
"outputs": [],
"options": {
"command": "yarn tsc --project ./apps/trading-e2e/"
}
}
},
"tags": [],
"implicitDependencies": ["trading"]
}
-20
View File
@@ -1,20 +0,0 @@
/// <reference types="cypress" />
declare namespace Cypress {
// specify additional properties in the TestConfig object
// in our case we will add "tags" property
interface SuiteConfigOverrides {
/**
* List of tags for this test
* @example a single tag
* it('logs in', { tags: '@smoke' }, () => { ... })
* @example multiple tags
* it('works', { tags: ['@smoke', '@slow'] }, () => { ... })
*/
tags?: string | string[];
}
interface Cypress {
grep?: (grep?: string, tags?: string, burn?: string) => void;
}
}
@@ -1,337 +0,0 @@
import { connectEthereumWallet } from '../support/ethereum-wallet';
import { selectAsset } from '../support/helpers';
const amountField = 'input[name="amount"]';
const txTimeout = Cypress.env('txTimeout');
const sepoliaUrl = Cypress.env('ETHERSCAN_URL');
const btcName = 0;
const vegaName = 4;
const btcSymbol = 'tBTC';
const vegaSymbol = 'VEGA';
const toastContent = 'toast-content';
const depositsTab = 'Deposits';
const toastCloseBtn = 'toast-close';
const completeWithdrawalBtn = 'complete-withdrawal';
const depositSubmit = 'deposit-submit';
const approveSubmit = 'approve-submit';
const dialogContent = 'dialog-content';
// Because the tests are run on a live network to optimize time, the tests are interdependent and must be run in the given order.
describe('capsule - without MultiSign', { tags: '@slow' }, () => {
before(() => {
cy.createMarket();
cy.get('@markets').then((markets) => {
cy.wrap(markets[0]).as('market');
});
cy.setOnBoardingViewed();
cy.visit('/#/portfolio');
});
it('can deposit', function () {
cy.visit('/#/portfolio');
cy.get('[data-testid="pathname-/portfolio"]').should('exist');
// 1001-DEPO-001
// 1001-DEPO-002
// 1001-DEPO-003
// 1001-DEPO-005
// 1001-DEPO-006
// 1001-DEPO-007
// 1001-DEPO-008
// 1001-DEPO-009
// 1001-DEPO-010
cy.getByTestId(depositsTab).click();
cy.getByTestId('deposit-button').click();
connectEthereumWallet('Unknown');
selectAsset(btcName);
cy.get('[data-testid="rich-select-option"]').eq(btcName).click();
cy.getByTestId('approve-default').should(
'contain.text',
`Before you can make a deposit of your chosen asset, ${btcSymbol}, you need to approve its use in your Ethereum wallet`
);
cy.getByTestId(approveSubmit).click();
cy.getByTestId('approve-pending').should('exist');
cy.getByTestId('approve-confirmed').should('exist');
cy.get(amountField).focus();
cy.get(amountField).clear().type('10');
cy.getByTestId(depositSubmit).click();
cy.getByTestId(toastContent, txTimeout).should(
'contain.text',
`Transaction confirmedYour transaction has been confirmed.View on EtherscanDeposit 10.00 ${btcSymbol}`,
{ matchCase: false }
);
cy.getByTestId(toastCloseBtn).click();
cy.getByTestId('Collateral').click();
cy.highlight('deposit verification');
cy.get('[col-id="asset.symbol"]', txTimeout).should(
'contain.text',
btcSymbol
);
cy.getByTestId(depositsTab).click();
cy.get('.ag-cell-value', txTimeout).should('contain.text', btcSymbol);
cy.get('[col-id="status"]').should('not.have.text', 'Open', txTimeout);
cy.get('[col-id="txHash"]')
.should('have.length.above', 2)
.eq(1)
.parent()
.within(() => {
cy.get('[col-id="asset.symbol"]').should('have.text', btcSymbol);
cy.get('[col-id="amount"]').should('have.text', '10.00');
cy.get('[col-id="createdTimestamp"]').should('not.be.empty');
cy.get('[col-id="status"]').should('have.text', 'Finalized');
cy.get('[col-id="txHash"]')
.find('a')
.should('have.attr', 'href')
.and('contain', `${sepoliaUrl}/tx/0x`);
});
});
it('can not withdrawal because of no MultiSign', function () {
// 1002-WITH-022
// 1002-WITH-023
// 0003-WTXN-011
cy.getByTestId('Withdrawals').click();
cy.getByTestId('withdraw-dialog-button').click();
selectAsset(btcName);
cy.get('[data-testid="rich-select-option"]').eq(btcName).click();
cy.get(amountField).focus();
cy.get(amountField).clear().type('1');
cy.getByTestId('submit-withdrawal').click();
cy.getByTestId(toastContent, txTimeout).should(
'contain.text',
'Funds unlocked'
);
// cy.getByTestId(toastCloseBtn).click();
cy.highlight('withdrawals verification');
cy.getByTestId('toast-complete-withdrawal').last().click();
cy.getByTestId(toastContent, txTimeout).should(
'contain.text',
'Error occurredcannot estimate gas'
);
cy.getByTestId(completeWithdrawalBtn).should(
'contain.text',
'Complete withdrawal'
);
});
});
describe('capsule', { tags: '@slow', testIsolation: true }, () => {
before(() => {
cy.updateCapsuleMultiSig();
});
beforeEach(() => {
cy.createMarket();
cy.get('@markets').then((markets) => {
cy.wrap(markets[0]).as('market');
});
cy.setOnBoardingViewed();
cy.setVegaWallet();
});
it('can withdrawal', function () {
// 1002-WITH-0014
// 1002-WITH-006
// 1002-WITH-009
// 1002-WITH-011
// 1002-WITH-024
// 1002-WITH-012
// 1002-WITH-013
// 1002-WITH-014
// 1002-WITH-015
// 1002-WITH-016
// 1002-WITH-017
// 1002-WITH-019
// 1002-WITH-020
// 1002-WITH-021
const ethWalletAddress = Cypress.env('ETHEREUM_WALLET_ADDRESS');
cy.visit('/#/portfolio');
cy.get('[data-testid="pathname-/portfolio"]').should('exist');
cy.getByTestId(toastCloseBtn, txTimeout).click();
cy.getByTestId('Withdrawals').click();
cy.getByTestId('withdraw-dialog-button').click();
connectEthereumWallet('Unknown');
selectAsset(btcName);
cy.get('[data-testid="rich-select-option"]').eq(btcName).click();
cy.get(amountField).clear().type('1');
cy.getByTestId('submit-withdrawal').click();
cy.getByTestId(toastContent, txTimeout).should(
'contain.text',
'Funds unlocked'
);
cy.highlight('withdrawals verification');
cy.getByTestId('toast-complete-withdrawal').click();
cy.getByTestId(toastContent, txTimeout).should(
'contain.text',
'Transaction confirmed'
);
cy.getByTestId(toastContent, txTimeout)
.should('contain.text', 'Funds unlocked')
.and('contain.text', 'Your funds have been unlocked for withdrawal.')
.and(
'contain.text',
'View in block explorerYou can save your withdrawal details for extra security.'
)
.and('contain.text', 'Withdraw 1.00 tBTCComplete withdrawal');
cy.getByTestId('toast-withdrawal-details').click();
cy.getByTestId(dialogContent)
.last()
.within(() => {
cy.getByTestId('dialog-title').should(
'contain.text',
'Save withdrawal details'
);
cy.getByTestId('copy-button').should('be.visible');
cy.getByTestId('assetSource_value').should(
'have.text',
'0xb63D135B0a6854EEb765d69ca36210cC70BECAE0'
);
cy.getByTestId('amount_value').should('have.text', '100000');
cy.getByTestId('nonce_value').invoke('text').should('not.be.empty');
cy.getByTestId('signatures_value')
.invoke('text')
.should('not.be.empty');
cy.getByTestId('targetAddress_value').should(
'have.text',
ethWalletAddress
);
cy.getByTestId('creation_value').invoke('text').should('not.be.empty');
});
cy.getByTestId('close-withdrawal-approval-dialog').click();
cy.get('.ag-center-cols-container')
.find('[col-id="status"]')
.eq(0, txTimeout)
.should('contain.text', 'Completed');
cy.get('[col-id="txHash"]', txTimeout)
.should('have.length.above', 1)
.eq(1)
.parent()
.within(() => {
cy.get('[col-id="asset.symbol"]').should('have.text', btcSymbol);
cy.get('[col-id="amount"]').should('have.text', '1.00');
cy.get('[col-id="details.receiverAddress"]')
.find('a')
.should('have.attr', 'href')
.and('contain', `${sepoliaUrl}/address/`);
cy.get('[col-id="createdTimestamp"]').should('not.be.empty');
cy.get('[col-id="withdrawnTimestamp"]').should('not.be.empty');
cy.get('[col-id="status"]').should('have.text', 'Completed');
cy.get('[col-id="txHash"]')
.find('a')
.should('have.attr', 'href')
.and('contain', `${sepoliaUrl}/tx/0x`);
});
cy.getByTestId('withdraw-dialog-button').click({ force: true });
// cy.getByTestId('BALANCE_AVAILABLE_value').should('have.text', '6.999');
});
it('approved amount is less than deposit', function () {
// 1001-DEPO-006
// 1001-DEPO-007
cy.visit('/#/portfolio');
cy.get('[data-testid="pathname-/portfolio"]').should('exist');
cy.getByTestId(toastCloseBtn, txTimeout).click();
cy.getByTestId(depositsTab).click();
cy.getByTestId('deposit-button').click();
connectEthereumWallet('Unknown');
selectAsset(btcName);
cy.get('[data-testid="rich-select-option"]').eq(btcName).click();
cy.contains('Deposits of tBTC not approved').should('not.exist');
cy.contains('Use maximum').should('be.visible');
cy.get(amountField).clear().type('20000000');
cy.getByTestId(depositSubmit).should('be.visible');
cy.getByTestId(depositSubmit).click();
cy.getByTestId('input-error-text').should(
'contain.text',
`You can't deposit more than you have in your Ethereum wallet`
);
});
it('withdraw - delay verification', function () {
// 1001-DEPO-024
// 1002-WITH-007
cy.visit('/#/portfolio');
cy.get('[data-testid="pathname-/portfolio"]', txTimeout).should('exist');
cy.getByTestId(toastCloseBtn, txTimeout).click();
cy.getByTestId(depositsTab).click();
cy.getByTestId('deposit-button').click();
connectEthereumWallet('Unknown');
selectAsset(vegaName);
cy.getByTestId('approve-submit').click();
cy.getByTestId('approve-confirmed').should(
'contain.text',
'You approved deposits of up to VEGA'
);
cy.get(amountField).clear().type('10000');
cy.getByTestId('deposit-submit').click();
cy.getByTestId(toastContent, txTimeout).should(
'contain.text',
`Your transaction has been confirmed.`,
{ matchCase: false }
);
cy.getByTestId(toastCloseBtn).click({ multiple: true });
cy.getByTestId('Collateral').click();
cy.highlight('deposit verification');
cy.get('[col-id="asset.symbol"]', txTimeout).should(
'contain.text',
vegaSymbol
);
cy.getByTestId(depositsTab).click();
cy.get('.ag-cell-value', txTimeout).should('contain.text', vegaSymbol);
cy.get('[col-id="status"]').should('not.have.text', 'Open', txTimeout);
cy.get('[col-id="txHash"]')
.should('have.length.above', 2)
.eq(1)
.parent()
.within(() => {
cy.get('[col-id="asset.symbol"]').should('have.text', vegaSymbol);
cy.get('[col-id="amount"]').should('have.text', '10,000.00');
cy.get('[col-id="createdTimestamp"]').should('not.be.empty');
cy.get('[col-id="status"]').should('have.text', 'Finalized');
cy.get('[col-id="txHash"]')
.find('a')
.should('have.attr', 'href')
.and('contain', `${sepoliaUrl}/tx/0x`);
});
cy.getByTestId('Withdrawals').click(txTimeout);
cy.getByTestId('withdraw-dialog-button').click();
selectAsset(1);
cy.get(amountField).clear().type('10000');
cy.getByTestId('DELAY_TIME_value').should('have.text', '5 days');
cy.getByTestId('submit-withdrawal').click();
cy.getByTestId(toastContent, txTimeout).should(
'contain.text',
'Your funds have been unlocked'
);
cy.getByTestId(toastCloseBtn).click();
cy.getByTestId(completeWithdrawalBtn).first().should('be.visible').click();
cy.getByTestId(toastContent, txTimeout).should('contain.text', 'Delayed');
cy.getByTestId('tab-withdrawals').within(() => {
cy.get('.ag-center-cols-container')
.children()
.first()
.within(() => {
cy.get('[col-id="status"]').contains(
/Delayed \(ready in (\d{1,2}:\d{2}:\d{2}:\d{2})\)/
);
});
});
});
});
@@ -1,43 +0,0 @@
import { connectEthereumWallet } from '../support/ethereum-wallet';
const connectEthWalletBtn = 'connect-eth-wallet-btn';
describe('ethereum wallet', { tags: '@smoke', testIsolation: true }, () => {
beforeEach(() => {
cy.mockWeb3Provider();
// Using portfolio withdrawals tab is it requires Ethereum wallet connection
cy.mockTradingPage();
cy.mockSubscription();
cy.setVegaWallet();
cy.visit('/#/portfolio');
cy.get('[data-testid="pathname-/portfolio"]').should('exist');
cy.getByTestId('Withdrawals').click();
});
it('can connect', () => {
// 0004-EWAL-001
cy.getByTestId('Deposits').click();
cy.getByTestId('deposit-button').click();
cy.getByTestId('connect-eth-wallet-btn').click();
cy.getByTestId('web3-connector-list').should('exist');
cy.getByTestId('web3-connector-MetaMask').click();
cy.getByTestId('web3-connector-list').should('not.exist');
cy.getByTestId('tab-deposits').should('not.be.empty');
});
it('able to disconnect eth wallet', () => {
// 0004-EWAL-004
// 0004-EWAL-005
// 0004-EWAL-006
cy.getByTestId('Deposits').click();
cy.getByTestId('deposit-button').click();
connectEthereumWallet('MetaMask');
cy.getByTestId('ethereum-address').should('have.text', '0xEe7D…d94F');
cy.getByTestId('disconnect-ethereum-wallet')
.should('have.text', 'Disconnect')
.click();
cy.getByTestId(connectEthWalletBtn).should('exist');
});
});
@@ -1,29 +0,0 @@
import { OrderType } from '@vegaprotocol/types';
import type { OrderSubmission } from '@vegaprotocol/wallet';
const orderSizeField = 'order-size';
const orderPriceField = 'order-price';
const orderTIFDropDown = 'order-tif';
const placeOrderBtn = 'place-order';
export const createOrder = (order: OrderSubmission): void => {
cy.log('Placing order', order);
const { type, side, size, price, timeInForce, expiresAt } = order;
cy.getByTestId(
`order-type-${type === OrderType.TYPE_LIMIT ? 'Limit' : 'Market'}`
).click();
cy.getByTestId(`order-side-${side}`).click();
cy.getByTestId(orderSizeField).clear().type(size);
if (price) {
cy.getByTestId(orderPriceField).clear().type(price);
}
cy.getByTestId(orderTIFDropDown).select(timeInForce);
if (timeInForce === 'TIME_IN_FORCE_GTT') {
if (!expiresAt) {
throw new Error('Specify expiresAt if using GTT');
}
cy.getByTestId('date-picker-field').type(expiresAt);
}
cy.getByTestId(placeOrderBtn).click();
};
@@ -1,18 +0,0 @@
import * as Schema from '@vegaprotocol/types';
export const orderSizeField = 'order-size';
export const orderPriceField = 'order-price';
export const orderTIFDropDown = 'order-tif';
export const placeOrderBtn = 'place-order';
export const toggleShort = 'order-side-SIDE_SELL';
export const toggleLong = 'order-side-SIDE_BUY';
export const toggleLimit = 'order-type-Limit';
export const toggleMarket = 'order-type-Market';
export const TIFlist = Object.values(Schema.OrderTimeInForce).map((value) => {
return {
code: Schema.OrderTimeInForceCode[value],
value,
text: Schema.OrderTimeInForceMapping[value],
};
});
@@ -1,5 +0,0 @@
export const connectEthereumWallet = (connectorName: string) => {
cy.getByTestId('connect-eth-wallet-btn').should('be.enabled').click();
cy.getByTestId('web3-connector-list').should('be.visible');
cy.getByTestId(`web3-connector-${connectorName}`).click();
};
-11
View File
@@ -1,11 +0,0 @@
export const selectAsset = (assetIndex: number) => {
cy.log(`selecting asset: ${assetIndex}`);
cy.getByTestId('select-asset').click();
cy.get('[data-testid="rich-select-option"]').eq(assetIndex).click();
// The asset only gets set once the queries (getWithdrawThreshold, getDelay)
// against the Ethereum change resolve, we should fix this but for now just force
// some wait time
// eslint-disable-next-line
cy.wait(100);
};
-8
View File
@@ -1,8 +0,0 @@
import '@vegaprotocol/cypress';
import 'cypress-real-events/support';
import registerCypressGrep from '@cypress/grep';
import { addMockTradingPage } from './trading';
import 'cypress-mochawesome-reporter/register';
registerCypressGrep();
addMockTradingPage();
@@ -1,36 +0,0 @@
import type {
OrdersUpdateSubscription,
OrdersUpdateSubscriptionVariables,
OrderUpdateFieldsFragment,
} from '@vegaprotocol/orders';
import type { onMessage } from '@vegaprotocol/cypress';
import type { PartialDeep } from 'type-fest';
import { orderUpdateSubscription } from '@vegaprotocol/mock';
const sendOrderUpdate: ((data: OrdersUpdateSubscription) => void)[] = [];
const getOnOrderUpdate = () => {
const onOrderUpdate: onMessage<
OrdersUpdateSubscription,
OrdersUpdateSubscriptionVariables
> = (send) => {
sendOrderUpdate.push(send);
};
return onOrderUpdate;
};
export const getSubscriptionMocks = () => ({
OrdersUpdate: getOnOrderUpdate(),
});
export function updateOrder(
override?: PartialDeep<OrderUpdateFieldsFragment>
): void {
const update: OrdersUpdateSubscription = orderUpdateSubscription({
// @ts-ignore partial deep check failing
orders: [override],
});
if (!sendOrderUpdate) {
throw new Error('OrderSub not called');
}
sendOrderUpdate.forEach((send) => send(update));
}
@@ -1,81 +0,0 @@
import type {
OrderAmendment,
OrderAmendmentBody,
OrderCancellation,
OrderCancellationBody,
OrderSubmission,
OrderSubmissionBody,
Transaction,
} from '@vegaprotocol/wallet';
export const testOrderSubmission = (
order: OrderSubmission,
expected?: Partial<OrderSubmission>
) => {
const expectedOrder = {
...order,
...expected,
};
const transaction: OrderSubmissionBody = {
orderSubmission: expectedOrder,
};
vegaWalletTransaction(transaction);
verifyToast();
};
export const testOrderAmendment = (
order: OrderAmendment,
expected?: Partial<OrderAmendment>
) => {
const expectedOrder = {
...order,
...expected,
};
const transaction: OrderAmendmentBody = {
orderAmendment: expectedOrder,
};
vegaWalletTransaction(transaction);
verifyToast();
};
export const testOrderCancellation = (
order: OrderCancellation,
expected?: Partial<OrderCancellation>
) => {
const expectedOrder = {
...order,
...expected,
};
const transaction: OrderCancellationBody = {
orderCancellation: expectedOrder,
};
vegaWalletTransaction(transaction);
verifyToast();
};
const vegaWalletTransaction = (transaction: Transaction) => {
cy.wait('@VegaWalletTransaction')
.its('request')
.then((req) => {
expect(req.body.params).to.deep.equal({
publicKey: Cypress.env('VEGA_PUBLIC_KEY'),
sendingMode: 'TYPE_SYNC',
transaction,
});
expect(req.headers.authorization).to.equal(
`VWT ${Cypress.env('VEGA_WALLET_API_TOKEN')}`
);
});
};
const verifyToast = () => {
cy.getByTestId('toast').should('contain.text', 'Awaiting confirmation');
cy.getByTestId('toast')
.find('a')
.invoke('attr', 'href')
.should('include', `${Cypress.env('EXPLORER_URL')}/txs/test-tx-hash`);
cy.getByTestId('toast-close').click();
};
-252
View File
@@ -1,252 +0,0 @@
import { aliasGQLQuery } from '@vegaprotocol/cypress';
import * as Schema from '@vegaprotocol/types';
import type { CyHttpMessages } from 'cypress/types/net-stubbing';
import type { Provider, Status } from '@vegaprotocol/markets';
import {
accountsQuery,
assetQuery,
assetsQuery,
candlesQuery,
chartQuery,
depositsQuery,
estimateFeesQuery,
marginsQuery,
marketCandlesQuery,
marketDataQuery,
marketDepthQuery,
marketInfoQuery,
marketsCandlesQuery,
marketsDataQuery,
marketsQuery,
networkParamsQuery,
nodeGuardQuery,
ordersQuery,
estimatePositionQuery,
positionsQuery,
proposalListQuery,
tradesQuery,
withdrawalsQuery,
protocolUpgradeProposalsQuery,
blockStatisticsQuery,
networkParamQuery,
liquidityProvisionsQuery,
successorMarketQuery,
parentMarketIdQuery,
successorMarketIdsQuery,
successorMarketProposalDetailsQuery,
liquidityProvidersQuery,
} from '@vegaprotocol/mock';
import type { PartialDeep } from 'type-fest';
import type { MarketDataQuery, MarketsQuery } from '@vegaprotocol/markets';
type MarketPageMockData = {
state: Schema.MarketState;
tradingMode?: Schema.MarketTradingMode;
trigger?: Schema.AuctionTrigger;
};
const ORACLE_PUBKEY = Cypress.env('ORACLE_PUBKEY');
const marketDataOverride = (
data: MarketPageMockData
): PartialDeep<MarketDataQuery> => ({
marketsConnection: {
edges: [
{
node: {
data: {
// @ts-ignore conflict between incoming and outgoing types
trigger: data.trigger,
// @ts-ignore same as above
marketTradingMode: data.tradingMode,
marketState: data.state,
},
},
},
],
},
});
const marketsDataOverride = (
data: MarketPageMockData
): PartialDeep<MarketsQuery> => ({
marketsConnection: {
edges: [
{
node: {
// @ts-ignore conflict between incoming and outgoing types
tradingMode: data.tradingMode,
state: data.state,
},
},
],
},
});
const mockTradingPage = (
req: CyHttpMessages.IncomingHttpRequest,
state: Schema.MarketState = Schema.MarketState.STATE_ACTIVE,
tradingMode?: Schema.MarketTradingMode,
trigger?: Schema.AuctionTrigger
) => {
aliasGQLQuery(req, 'NodeGuard', nodeGuardQuery());
aliasGQLQuery(
req,
'Markets',
marketsQuery(marketsDataOverride({ state, tradingMode, trigger }))
);
aliasGQLQuery(
req,
'MarketData',
marketDataQuery(marketDataOverride({ state, tradingMode, trigger }))
);
aliasGQLQuery(req, 'MarketsData', marketsDataQuery());
aliasGQLQuery(req, 'MarketsCandles', marketsCandlesQuery());
aliasGQLQuery(req, 'MarketCandles', marketCandlesQuery());
aliasGQLQuery(req, 'MarketDepth', marketDepthQuery());
aliasGQLQuery(req, 'Orders', ordersQuery());
aliasGQLQuery(req, 'Accounts', accountsQuery());
aliasGQLQuery(req, 'Positions', positionsQuery());
aliasGQLQuery(req, 'Margins', marginsQuery());
aliasGQLQuery(req, 'Assets', assetsQuery());
aliasGQLQuery(req, 'Asset', assetQuery());
aliasGQLQuery(
req,
'MarketInfo',
marketInfoQuery({
market: {
tradableInstrument: {
instrument: {
product: {
__typename: 'Future',
dataSourceSpecForSettlementData: {
data: {
sourceType: {
sourceType: {
signers: [
{
__typename: 'Signer',
signer: {
__typename: 'PubKey',
key: ORACLE_PUBKEY,
},
},
],
},
},
},
},
dataSourceSpecForTradingTermination: {
data: {
sourceType: {
sourceType: {
signers: [
{
__typename: 'Signer',
signer: {
__typename: 'PubKey',
key: ORACLE_PUBKEY,
},
},
],
},
},
},
},
},
},
},
},
})
);
aliasGQLQuery(req, 'Trades', tradesQuery());
aliasGQLQuery(req, 'Chart', chartQuery());
aliasGQLQuery(req, 'LiquidityProvisions', liquidityProvisionsQuery());
aliasGQLQuery(req, 'LiquidityProviders', liquidityProvidersQuery());
aliasGQLQuery(req, 'Candles', candlesQuery());
aliasGQLQuery(req, 'Withdrawals', withdrawalsQuery());
aliasGQLQuery(req, 'NetworkParams', networkParamsQuery());
aliasGQLQuery(req, 'NetworkParam', networkParamQuery);
aliasGQLQuery(req, 'EstimateFees', estimateFeesQuery());
aliasGQLQuery(req, 'EstimatePosition', estimatePositionQuery());
aliasGQLQuery(req, 'ProposalsList', proposalListQuery());
aliasGQLQuery(req, 'Deposits', depositsQuery());
aliasGQLQuery(
req,
'ProtocolUpgradeProposals',
protocolUpgradeProposalsQuery()
);
aliasGQLQuery(req, 'BlockStatistics', blockStatisticsQuery());
aliasGQLQuery(req, 'SuccessorMarket', successorMarketQuery());
aliasGQLQuery(req, 'ParentMarketId', parentMarketIdQuery());
aliasGQLQuery(req, 'SuccessorMarketIds', successorMarketIdsQuery());
aliasGQLQuery(
req,
'SuccessorMarketProposalDetails',
successorMarketProposalDetailsQuery()
);
};
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace Cypress {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface Chainable<Subject> {
mockTradingPage(
state?: Schema.MarketState,
tradingMode?: Schema.MarketTradingMode,
trigger?: Schema.AuctionTrigger,
oracleStatus?: Status
): void;
}
}
}
export const addMockTradingPage = () => {
Cypress.Commands.add(
'mockTradingPage',
(
state = Schema.MarketState.STATE_ACTIVE,
tradingMode,
trigger,
oracleStatus
) => {
cy.mockChainId();
cy.mockGQL((req) => {
mockTradingPage(req, state, tradingMode, trigger);
});
const oracle: Provider = {
name: 'Another oracle',
url: 'https://zombo.com',
description_markdown:
'Some markdown describing the oracle provider.\n\nTwitter: @FacesPics2\n',
oracle: {
status: oracleStatus || 'GOOD',
status_reason: '',
first_verified: '2022-01-01T00:00:00.000Z',
last_verified: '2022-12-31T00:00:00.000Z',
type: 'public_key',
public_key: ORACLE_PUBKEY,
},
proofs: [
{
format: 'signed_message',
available: true,
type: 'public_key',
public_key: ORACLE_PUBKEY,
message: 'SOMEHEX',
},
],
github_link: `https://github.com/vegaprotocol/well-known/blob/main/oracle-providers/public_key-${ORACLE_PUBKEY}.toml`,
};
// Prevent request to github, return some dummy content
cy.intercept(
'GET',
/^https:\/\/raw.githubusercontent.com\/vegaprotocol\/well-known/,
{
body: [oracle],
}
);
}
);
};
-19
View File
@@ -1,19 +0,0 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"jsx": "react-jsx",
"sourceMap": false,
"outDir": "../../dist/out-tsc",
"allowJs": true,
"types": ["cypress", "node", "cypress-real-events", "@cypress/grep"],
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": false,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src/**/*.ts", "src/**/*.js", "./declaration.d.ts"]
}
+1 -7
View File
@@ -49,10 +49,4 @@ To run the minimal set of unit tests, run the following:
yarn nx test trading
```
To run the UI automation tests with a mocked API, run:
```bash
yarn nx run trading-e2e:e2e
```
To run tests with market sim please read [the readme](e2e/README.md).
To run the UI automation tests please read [e2e/README.md](e2e/README.md)

Some files were not shown because too many files have changed in this diff Show More