Compare commits

..
Author SHA1 Message Date
Madalina Raicu b4e655ffec fix: typename reset 2023-07-17 15:43:38 +02:00
Madalina Raicu f616597fd7 Merge branch 'develop' of github.com:vegaprotocol/frontend-monorepo into feat/submit-iceberg 2023-07-17 15:15:03 +02:00
Madalina Raicu 649857a88a Merge branch 'feat/view-iceberg-orders' of github.com:vegaprotocol/frontend-monorepo into feat/submit-iceberg 2023-07-17 14:36:37 +02:00
Madalina Raicu ad2dbe2684 fix: skip flaky test 2023-07-17 14:35:36 +02:00
Madalina Raicu 1062db9b1a Merge branch 'develop' of github.com:vegaprotocol/frontend-monorepo into feat/submit-iceberg 2023-07-17 14:26:05 +02:00
Madalina Raicu 6b3c807dec Merge branch 'develop' of github.com:vegaprotocol/frontend-monorepo into feat/submit-iceberg 2023-07-14 12:47:46 +01:00
Madalina Raicu 11dff1fcab Merge branch 'develop' of github.com:vegaprotocol/frontend-monorepo into feat/view-iceberg-orders 2023-07-14 12:47:05 +01:00
Madalina Raicu c33b8e12ba Merge branch 'develop' of github.com:vegaprotocol/frontend-monorepo into feat/view-iceberg-orders 2023-07-14 09:30:24 +01:00
Madalina Raicu 2d0e8e0a14 Merge branch 'develop' of github.com:vegaprotocol/frontend-monorepo into feat/submit-iceberg 2023-07-14 09:29:23 +01:00
Madalina Raicu cc7fde6bbe Merge branch 'fix/add-internal-data-source-termination-oracle' of github.com:vegaprotocol/frontend-monorepo into feat/submit-iceberg 2023-07-13 09:19:37 +01:00
Madalina Raicu a96843fef7 Merge branch 'develop' of github.com:vegaprotocol/frontend-monorepo into feat/submit-iceberg 2023-07-12 20:33:54 +01:00
Madalina Raicu c1ed810d0e Merge branch 'develop' of github.com:vegaprotocol/frontend-monorepo into feat/view-iceberg-orders 2023-07-12 20:26:00 +01:00
Madalina Raicu ee38cea4f2 fix: update market info 2023-07-12 20:24:30 +01:00
Madalina Raicu 93869b178c Merge branch 'develop' of github.com:vegaprotocol/frontend-monorepo into fix/add-internal-data-source-termination-oracle 2023-07-12 20:23:58 +01:00
Madalina Raicu 66d3f91069 Merge branch 'develop' of github.com:vegaprotocol/frontend-monorepo into feat/submit-iceberg 2023-07-12 11:57:13 +01:00
Madalina Raicu 2185c52084 chore: update test trading-portfolio 2023-07-12 11:03:34 +01:00
Madalina Raicu 46c18ddfe0 Merge branch 'develop' of github.com:vegaprotocol/frontend-monorepo into feat/submit-iceberg 2023-07-12 09:44:30 +01:00
Madalina Raicu 03aac931fd Merge branch 'develop' of github.com:vegaprotocol/frontend-monorepo into feat/submit-iceberg 2023-07-11 23:56:05 +01:00
Madalina Raicu 694c9765d3 Merge branch 'develop' of github.com:vegaprotocol/frontend-monorepo into fix/add-internal-data-source-termination-oracle 2023-07-11 13:02:42 +01:00
Madalina Raicu ac11eea37b Merge branch 'develop' of github.com:vegaprotocol/frontend-monorepo into fix/add-internal-data-source-termination-oracle 2023-07-11 09:27:23 +01:00
Madalina Raicu 9a8e4b2526 fix: order type cast removed 2023-07-10 14:39:57 +01:00
Madalina Raicu 70ca29c9ee chore: add extra tests for iceberg orders 2023-07-10 08:56:25 +01:00
Madalina Raicu 07403bdc81 Merge branch 'develop' of github.com:vegaprotocol/frontend-monorepo into feat/submit-iceberg 2023-07-09 16:08:08 +03:00
Madalina Raicu e07fa70bb7 Merge branch 'feat/view-iceberg-orders' of github.com:vegaprotocol/frontend-monorepo into feat/submit-iceberg 2023-07-07 22:54:13 +03:00
Madalina Raicu 96db36fa67 fix: add extra field 2023-07-07 22:35:31 +03:00
Madalina Raicu abc278aab5 fix: mappings 2023-07-07 22:15:43 +03:00
Madalina Raicu 331c492408 fix: update iceberg opts 2023-07-07 19:14:47 +03:00
Madalina Raicu 52b5274f47 fix: order conflict 2023-07-07 18:59:52 +03:00
Madalina Raicu 24fc4721e7 Merge branch 'feat/view-iceberg-orders' of github.com:vegaprotocol/frontend-monorepo into feat/submit-iceberg 2023-07-07 18:56:59 +03:00
Madalina Raicu 381c1d34c8 fix: order fields update 2023-07-07 18:49:39 +03:00
Madalina Raicu cb797e5b3f fix: add typename 2023-07-07 18:19:07 +03:00
Madalina Raicu 6366f5d7de chore: remove remaining from tx res 2023-07-07 13:04:26 +03:00
Madalina Raicu 7fe867756c Merge branch 'develop' of github.com:vegaprotocol/frontend-monorepo into feat/view-iceberg-orders 2023-07-06 15:16:27 +03:00
Madalina Raicu dc4a7e63b5 Merge branch 'develop' of github.com:vegaprotocol/frontend-monorepo into feat/view-iceberg-orders 2023-07-06 11:17:50 +03:00
Madalina Raicu 5ef0969dcb fix: update types 2023-07-06 00:32:28 +03:00
Madalina Raicu 4c9f2ae84b fix: update order update on iceberg subscriptions 2023-07-05 18:08:50 +03:00
Madalina Raicu cacc7e1304 fix: update types 2023-07-05 18:04:40 +03:00
Madalina Raicu 77f49e3520 Merge branch 'feat/view-iceberg-orders' of github.com:vegaprotocol/frontend-monorepo into feat/submit-iceberg 2023-07-05 12:06:06 +03:00
Madalina Raicu 13d13bd930 Merge branch 'develop' of github.com:vegaprotocol/frontend-monorepo into feat/view-iceberg-orders 2023-07-05 00:18:48 +03:00
Madalina Raicu c3b95357dc fix: transfer type mappings 2023-07-04 17:59:59 +03:00
Madalina Raicu 1d74fd8311 Merge branch 'feat/view-iceberg-orders' of github.com:vegaprotocol/frontend-monorepo into feat/view-iceberg-orders 2023-07-04 17:58:21 +03:00
Madalina Raicu 981212bd64 fix: proposal type mappings 2023-07-04 17:58:07 +03:00
Madalina Raicu 54e6aadfa3 fix: account type holding 2023-07-04 17:49:20 +03:00
Madalina Raicu be86d91249 fix: remove log 2023-07-04 17:48:17 +03:00
Madalina Raicu 93340db681 fix(trading): update order submission type wrapper 2023-07-03 14:54:59 +03:00
Madalina Raicu 869a739ea4 fix: remove log update copy 2023-07-03 11:51:09 +03:00
Madalina Raicu 6efbd514be Merge branch 'develop' of github.com:vegaprotocol/frontend-monorepo into feat/submit-iceberg 2023-06-29 14:51:43 +03:00
Madalina Raicu 39b24be40f feat(trading): submit iceberg order using cli wallet 2023-06-29 00:47:02 +03:00
Madalina Raicu 3d26147be3 Merge branch 'feat/view-iceberg-orders' of github.com:vegaprotocol/frontend-monorepo into feat/submit-iceberg 2023-06-28 16:47:06 +03:00
Madalina Raicu 800b884ad9 fix: revert orders data provider changes 2023-06-28 16:41:12 +03:00
Madalina Raicu 44c00f808b fix: add internal data source termination oracle 2023-06-28 16:34:01 +03:00
Madalina Raicu 32c8a926a5 feat(trading): view iceberg orders 2023-06-28 15:47:51 +03:00
Madalina Raicu a1fec2c63f feat(trading): submit iceberg orders deal ticket 2023-06-28 13:24:27 +03:00
281 changed files with 3572 additions and 6099 deletions
+1 -1
View File
@@ -6,7 +6,7 @@ name: 'Add Issues To Project Board'
types:
- opened
env:
GH_TOKEN: ${{ secrets.PROJECT_MANAGE_ACTION }}
GH_TOKEN: ${{ secrets.GH_NEW_CARD_TO_PROJECT }}
PROJECT_ID: ${{ secrets.FRONT_END_PROJECT_ID }}
ISSUE_ID: ${{ github.event.issue.node_id }}
USER: ${{ github.actor }}
+6 -6
View File
@@ -28,7 +28,7 @@ jobs:
echo IS_MAINNET_RELEASE=false >> $GITHUB_ENV
echo IS_TESTNET_RELEASE=false >> $GITHUB_ENV
echo IS_IPFS_RELEASE=false >> $GITHUB_ENV
echo IS_S3_RELEASE=false >> $GITHUB_ENV
echo IS_S3_RELASE=false >> $GITHUB_ENV
- name: Is PR
if: ${{ github.event_name == 'pull_request' }}
@@ -51,9 +51,9 @@ jobs:
echo IS_IPFS_RELEASE=true >> $GITHUB_ENV
- name: Is S3 Release
if: ${{ env.IS_IPFS_RELEASE == 'false' && github.event_name == 'push' }}
if: ${{ env.IS_IPFS_RELASE == 'false' && github.event_name == 'push' }}
run: |
echo IS_S3_RELEASE=true >> $GITHUB_ENV
echo IS_S3_RELASE=true >> $GITHUB_ENV
- name: Set up QEMU
id: quemu
@@ -251,7 +251,7 @@ jobs:
- name: Publish dist to s3
uses: jakejarvis/s3-sync-action@master
# s3 releases are not happening for trading on mainnet - it's IPFS
if: ${{ env.IS_S3_RELEASE == 'true' }}
if: ${{ env.IS_S3_RELASE == 'true' }}
with:
args: --acl private --follow-symlinks --delete
env:
@@ -262,11 +262,11 @@ jobs:
SOURCE_DIR: 'dist-result'
- name: Install aws CLI
if: ${{ env.IS_S3_RELEASE == 'true' }}
if: ${{ env.IS_S3_RELASE == 'true' }}
uses: unfor19/install-aws-cli-action@master
- name: Perform cache invalidation
if: ${{ env.IS_S3_RELEASE == 'true' }}
if: ${{ env.IS_S3_RELASE == 'true' }}
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
+1 -6
View File
@@ -16,10 +16,5 @@ NX_EXPLORER_NETWORK_PARAMETERS=1
NX_EXPLORER_PARTIES=1
NX_EXPLORER_VALIDATORS=1
CYPRESS_VEGA_URL=http://localhost:3008/graphql
CYPRESS_ETH_WALLET_MNEMONIC=ozone access unlock valid olympic save include omit supply green clown session
CYPRESS_ETHEREUM_PROVIDER_URL=http://localhost:8545
CYPRESS_FAUCET_URL=http://localhost:1790/api/v1/mint
CYPRESS_VEGA_PUBLIC_KEY=02eceaba4df2bef76ea10caf728d8a099a2aa846cced25737cccaa9812342f65
CYPRESS_VEGA_WALLET_URL=http://localhost:1789
CYPRESS_VEGA_WALLET_API_TOKEN=
CYPRESS_VEGA_URL=http://localhost:3008/graphql
+10 -121
View File
@@ -1,130 +1,19 @@
context('Market page', { tags: '@regression' }, function () {
//Tests set to skip until market bug for capsule checkpoint is fixed
context.skip('Market page', { tags: '@regression' }, function () {
describe('Verify elements on page', function () {
const marketHeaders = 'markets-heading';
const createdMarketId =
'2eab0e66545a789047561bc5a2e5cbc3b19eb708da41104e3cac2474ee36c4d4';
const marketHeaders = '[data-testid="markets-header"]';
const marketNavigation = 'a[href="/markets"]';
before('Create market', function () {
it('Markets page is displayed', function () {
cy.visit('/');
cy.createMarket();
cy.get(marketNavigation).click();
cy.common_validate_blocks_data_displayed(marketHeaders);
});
beforeEach('Get market id', function () {
cy.navigate_to('markets');
cy.get('[col-id="id"]').eq(1).invoke('text').as('createdMarketId');
});
it('Market displayed on market page', function () {
cy.navigate_to('markets');
cy.getByTestId(marketHeaders).should('be.visible');
cy.get(`[row-id="${this.createdMarketId}"]`)
.should('be.visible')
.within(() => {
cy.get_element_by_col_id('code').should('have.text', 'TEST.24h');
cy.get_element_by_col_id('name').should('have.text', 'Test market 1');
cy.get_element_by_col_id('state').should('have.text', 'Pending');
cy.get_element_by_col_id('asset').should('have.text', 'fUSDC');
cy.get_element_by_col_id('id').should(
'have.text',
this.createdMarketId
);
cy.get_element_by_col_id('actions')
.find('a')
.should('have.attr', 'href', `/markets/${this.createdMarketId}`);
});
});
it('Able to go to market details page', function () {
cy.navigate_to('markets');
cy.get_element_by_col_id('actions').eq(1).click();
cy.getByTestId(marketHeaders).should('have.text', 'Test market 1');
cy.validate_element_from_table('Name', 'Test market 1');
cy.validate_element_from_table('Market ID', this.createdMarketId);
cy.validate_element_from_table('Trading Mode', 'Opening auction');
cy.validate_element_from_table('Market Decimal Places', '5');
cy.validate_element_from_table('Position Decimal Places', '5');
cy.validate_element_from_table('Settlement Asset Decimal Places', '5');
// Instrument
cy.validate_element_from_table('Market Name', 'Test market 1');
cy.validate_element_from_table('Code', 'TEST.24h');
cy.validate_element_from_table('Product Type', 'Future');
cy.validate_element_from_table('Quote Name', 'fUSDC');
// Settlement Asset
cy.validate_element_from_table('Quote Name', 'fUSDC');
cy.validate_element_from_table('Symbol', 'fUSDC');
cy.validate_element_from_table('Decimals', '5');
cy.validate_element_from_table('Quantum', '0.00');
cy.validate_element_from_table('Status', 'Enabled');
cy.validate_element_from_table('Max faucet amount', '10,000,000.00');
cy.validate_element_from_table(
'Infrastructure fee account balance',
'0.00'
);
cy.validate_element_from_table(
'Global reward pool account balance',
'0.00'
);
// Metadata
cy.validate_element_from_table('Sector', 'tech');
cy.validate_element_from_table('Source', 'docs.vega.xyz');
// Risk model
cy.validate_element_from_table('Tau', '0.0001140771161');
cy.validate_element_from_table('Risk Aversion Parameter', '0.01');
// Risk parameters
// cy.validate_element_from_table('R', '0.016')
cy.validate_element_from_table('Sigma', '0.5');
// Risk factors
cy.validate_element_from_table('Short', '0.0143218738374871');
cy.validate_element_from_table('Long', '0.0141450471498822');
// Price monitoring settings 1
cy.validate_element_from_table('Horizon Secs', '43,200');
cy.validate_element_from_table('Probability', '1');
cy.validate_element_from_table('Auction Extension Secs', '600');
// Liquidity Monitoring
cy.validate_element_from_table('Triggering Ratio', '0.7');
cy.validate_element_from_table('Time Window', '3,600');
cy.validate_element_from_table('Scaling Factor', '10');
// Liquidity
cy.validate_element_from_table('Target Stake', '0.00 fUSDC');
cy.validate_element_from_table('Supplied Stake', '0.00 fUSDC');
cy.validate_element_from_table('Market Value Proxy', '0.00 fUSDC');
// Liquidity price range
cy.validate_element_from_table(
'Liquidity Price Range',
'1,000.00% of mid price'
);
cy.validate_element_from_table('Lowest Price', '0.00 fUSDC');
cy.validate_element_from_table('Highest Price', '0.00 fUSDC');
cy.getByTestId('oracle-spec-links')
.should('have.attr', 'href')
.and(
'contain',
'/oracles/bf242aa5c9f64fcbb77808aa8582e73711519f4b35264eb797a80f1803590a24'
);
// Able to view Json
cy.contains('View JSON').click();
cy.get('.language-json').should('exist');
});
// Skipping due to resize observer loop limit error
it.skip('Markets page displayed on mobile', function () {
it('Markets page displayed on mobile', function () {
cy.common_switch_to_mobile_and_click_toggle();
cy.navigate_to('markets', true);
cy.getByTestId(marketHeaders).should('be.visible');
cy.get(`[row-id="${createdMarketId}"]`)
.should('be.visible')
.within(() => {
cy.get_element_by_col_id('code').should('have.text', 'TEST.24h');
cy.get_element_by_col_id('name').should('have.text', 'Test market 1');
cy.get_element_by_col_id('state').should('have.text', 'Pending');
cy.get_element_by_col_id('asset').should('have.text', 'fUSDC');
cy.get_element_by_col_id('id').should('have.text', createdMarketId);
cy.get_element_by_col_id('actions')
.find('a')
.should('have.attr', 'href', `/markets/${createdMarketId}`);
});
cy.get(marketNavigation).click();
cy.common_validate_blocks_data_displayed(marketHeaders);
});
});
});
@@ -40,7 +40,7 @@ context.skip('Transactions page', function () {
.first()
.click({ force: true });
} else {
cy.log('Unable to find any transactions on page');
cy.slack('Unable to find any transactions on page');
cy.screenshot();
}
});
@@ -78,7 +78,7 @@ context.skip('Transactions page', function () {
}
});
} else {
cy.log('Unable to find any transactions on page');
cy.slack('Unable to find any transactions on page');
cy.screenshot();
}
});
@@ -28,7 +28,7 @@ Cypress.Commands.add('switchToMobile', () => {
Cypress.Commands.add('common_switch_to_mobile_and_click_toggle', function () {
cy.viewport('iphone-x');
cy.visit('/');
cy.get('[data-testid="button-menu-drawer"]').click();
cy.get('[data-testid="open-menu"]').click();
});
Cypress.Commands.add('monitor_clipboard', () => {
@@ -88,42 +88,3 @@ Cypress.Commands.add('convert_number_to_max_four_decimal', (number) => {
maximumFractionDigits: 4,
});
});
Cypress.Commands.add('navigate_to', (page, mobileView = false) => {
const navigation = {
transactions: '/txs',
blocks: '/blocks',
oracles: '/oracles',
validators: '/validators',
parties: '/parties',
assets: '/assets',
markets: '/markets',
governanceProposals: '/governance',
networkParameters: '/network-parameters',
genesisParameters: '/genesis',
};
const topLevelRoutes = ['transactions', 'blocks', 'oracles', 'validators'];
cy.getByTestId('navigation').within(() => {
if (!topLevelRoutes.includes(page) && mobileView === false) {
cy.getByTestId('other').first().click();
}
cy.get(`[href="${navigation[page]}"]`).first().click();
});
cy.url().should('include', navigation[page]);
});
Cypress.Commands.add('get_element_by_col_id', (elementName) => {
cy.get(`[col-id="${elementName}"]`);
});
Cypress.Commands.add(
'validate_element_from_table',
(tableRowName, tableRowValue) => {
cy.contains(tableRowName)
.parentsUntil("[data-testid='key-value-table-row']")
.siblings()
.should('have.text', tableRowValue);
}
);
+1 -1
View File
@@ -4,7 +4,7 @@ NX_TENDERMINT_WEBSOCKET_URL=wss://be.mainnet-mirror.vega.rocks/websocket
NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40@o286262.ingest.sentry.io/5882996
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/mainnet-mirror/vegawallet-mainnet-mirror.toml
NX_VEGA_URL=https://api.mainnet-mirror.vega.rocks/graphql
NX_VEGA_ENV=MAINNET_MIRROR
NX_VEGA_ENV=MAINNET-MIRROR
NX_BLOCK_EXPLORER=https://be.mainnet-mirror.vega.rocks/rest
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_GOVERNANCE_URL=https://governance.mainnet-mirror.vega.rocks
@@ -95,10 +95,7 @@ export const Header = () => {
{mainItems.map(routeToNavigationItem)}
{groupedItems && groupedItems.length > 0 && (
<NavigationItem>
<NavigationTrigger
isActive={Boolean(isOnOther)}
data-testid="other"
>
<NavigationTrigger isActive={Boolean(isOnOther)}>
{t('Other')}
</NavigationTrigger>
<NavigationContent>
@@ -137,7 +137,7 @@ export const ProposalsTable = ({ data }: ProposalsTableProps) => {
hide: window.innerWidth <= BREAKPOINT_MD,
headerName: t('Enactment date'),
field: 'terms.enactmentDatetime',
valueFormatter: ({
valueFormatte: ({
value,
}: VegaValueFormatterParams<
ProposalListFieldsFragment,
@@ -1,52 +0,0 @@
import { t } from '@vegaprotocol/i18n';
import type { components } from '../../../../../types/explorer';
import { Tooltip } from '@vegaprotocol/ui-toolkit';
export interface TxDetailsOrderIcebergDetailsProps {
iceberg: components['schemas']['v1IcebergOpts'];
size: components['schemas']['v1OrderSubmission']['size'];
}
/**
.iceberg .sub {
vertical-align: bottom;
color: #954F01;
}
.iceberg .sup {
vertical-align: top;
color: #515E1E;
}
*/
/**
* Visualises the iceberg details of an order, showing the minimum visible size,
* the full size and the peak size.
*/
export const TxOrderIcebergDetails = ({
iceberg,
size,
}: TxDetailsOrderIcebergDetailsProps) => {
return (
<div
className="inline-block iceberg bg-vega-blue-300 border-vega-blue-350 border-[1px] text-[70%] font-mono py-[2px] pl-2 pr-3 height-[1em] leading-[1em] cursor-pointer"
style={{ clipPath: 'polygon(10% 0%, 100% 0%, 90% 100%, 0% 100%)' }}
>
<Tooltip description={t('Iceberg: Minimum visible size')}>
<span className="align-bottom text-vega-orange-650">
{iceberg.minimumVisibleSize || '-'}
</span>
</Tooltip>
<Tooltip description={t('Iceberg: Total size')}>
<span className="text-sm text-vega-blue-600 mx-3">{size}</span>
</Tooltip>
<Tooltip description={t('Iceberg: Visible peak')}>
<span className="align-top text-vega-yellow-600">
{iceberg.peakSize || '-'}
</span>
</Tooltip>
</div>
);
};
@@ -8,7 +8,6 @@ import { txSignatureToDeterministicId } from '../lib/deterministic-ids';
import DeterministicOrderDetails from '../../order-details/deterministic-order-details';
import Hash from '../../links/hash';
import { TxOrderPeggedReferenceRow } from './order/tx-order-peg';
import { TxOrderIcebergDetails } from './order/tx-order-iceberg';
interface TxDetailsOrderProps {
txData: BlockExplorerTransactionResult | undefined;
@@ -33,8 +32,6 @@ export const TxDetailsOrder = ({
const marketId = txData.command.orderSubmission.marketId || '-';
const reference = txData.command.orderSubmission.peggedOrder;
const side = txData.command.orderSubmission.side;
const iceberg = txData.command.orderSubmission.icebergOpts;
const size = txData.command.orderSubmission.size;
let deterministicId = '';
@@ -77,14 +74,6 @@ export const TxDetailsOrder = ({
marketId={marketId}
/>
) : null}
{iceberg ? (
<TableRow modifier="bordered">
<TableCell>{t('Iceberg details')}</TableCell>
<TableCell>
<TxOrderIcebergDetails iceberg={iceberg} size={size} />
</TableCell>
</TableRow>
) : null}
</TableWithTbody>
{deterministicId.length > 0 ? (
@@ -166,7 +166,6 @@ export const TxOrderType = ({ orderType, command }: TxOrderTypeProps) => {
'text-white dark:text-white bg-vega-dark-150 dark:bg-vega-dark-250';
// This will get unwieldy and should probably produce a different colour of tag
// Note that colours are currently arbitrary
if (type === 'Chain Event' && !!command?.chainEvent) {
type = getLabelForChainEvent(command.chainEvent);
colours = 'text-white dark-text-white bg-vega-pink dark:bg-vega-pink';
@@ -178,9 +177,6 @@ export const TxOrderType = ({ orderType, command }: TxOrderTypeProps) => {
type = getLabelForProposal(command.proposalSubmission);
}
colours = 'text-black bg-vega-yellow';
} else if (type === 'Order' && command) {
type = getLabelForOrderType(orderType, command);
colours = 'text-white dark-text-white bg-vega-blue dark:bg-vega-blue';
}
if (type === 'Vote on Proposal') {
+15
View File
@@ -5,6 +5,8 @@ const windowOrDefault = (key: string) => {
return (process.env[key] as string) || '';
};
const truthy = ['1', 'true'];
export const ENV = {
// Data sources
// Environment
@@ -18,6 +20,19 @@ export const ENV = {
governanceUrl: windowOrDefault('NX_VEGA_GOVERNANCE_URL'),
vegaRepoUrl: windowOrDefault('NX_VEGA_REPO_URL'),
},
flags: {
assets: truthy.includes(windowOrDefault('NX_EXPLORER_ASSETS')),
genesis: truthy.includes(windowOrDefault('NX_EXPLORER_GENESIS')),
governance: truthy.includes(windowOrDefault('NX_EXPLORER_GOVERNANCE')),
markets: truthy.includes(windowOrDefault('NX_EXPLORER_MARKETS')),
oracles: truthy.includes(windowOrDefault('NX_EXPLORER_ORACLES')),
txsList: truthy.includes(windowOrDefault('NX_EXPLORER_TXS_LIST')),
networkParameters: truthy.includes(
windowOrDefault('NX_EXPLORER_NETWORK_PARAMETERS')
),
parties: truthy.includes(windowOrDefault('NX_EXPLORER_PARTIES')),
validators: truthy.includes(windowOrDefault('NX_EXPLORER_VALIDATORS')),
},
addresses: {
feedback: windowOrDefault('NX_GITHUB_FEEDBACK_URL'),
},
+5
View File
@@ -0,0 +1,5 @@
import { ENV } from './env';
export default {
...ENV.flags,
};
@@ -1,5 +1,7 @@
import { Link, useParams } from 'react-router-dom';
import { DATA_SOURCES } from '../../../config';
import { getDateTimeFormat } from '@vegaprotocol/utils';
import type { TendermintBlocksResponse } from '../tendermint-blocks-response';
import { RouteTitle } from '../../../components/route-title';
import { TimeAgo } from '../../../components/time-ago';
import {
@@ -12,7 +14,7 @@ import { TxsPerBlock } from '../../../components/txs/txs-per-block';
import { AsyncRenderer, Button } from '@vegaprotocol/ui-toolkit';
import { Routes } from '../../route-names';
import { t } from '@vegaprotocol/i18n';
import { useBlockInfo } from '@vegaprotocol/tendermint';
import { useFetch } from '@vegaprotocol/react-helpers';
import { NodeLink } from '../../../components/links';
import { useDocumentTitle } from '../../../hooks/use-document-title';
import EmptyList from '../../../components/empty-list/empty-list';
@@ -22,7 +24,10 @@ const Block = () => {
useDocumentTitle(['Blocks', `Block #${block}`]);
const {
state: { data: blockData, loading, error },
} = useBlockInfo(Number(block));
} = useFetch<TendermintBlocksResponse>(
`${DATA_SOURCES.tendermintUrl}/block?height=${block}`,
{ cache: 'force-cache' }
);
return (
<section>
@@ -54,7 +54,7 @@ export const PartyBlockStake = ({
{p?.stakingSummary.currentStakeAvailable ? (
<KeyValueTable>
<KeyValueTableRow noBorder={true}>
<div>{t('Associated to key')}</div>
<div>{t('Available stake')}</div>
<div>
<GovernanceAssetBalance
price={p.stakingSummary.currentStakeAvailable}
@@ -62,7 +62,7 @@ export const PartyBlockStake = ({
</div>
</KeyValueTableRow>
<KeyValueTableRow noBorder={true}>
<div>{t('Staked to validator')}</div>
<div>{t('Active stake')}</div>
<div>
<GovernanceAssetBalance price={linkedStake || '0'} />
</div>
@@ -67,7 +67,7 @@ const Party = () => {
text: t('Go back'),
action: () => navigate(-1),
className: 'py-1',
size: 'small',
size: 'sm',
}}
/>
</div>
@@ -14,6 +14,7 @@ import { Block } from './blocks/id';
import { Blocks } from './blocks/home';
import { Tx } from './txs/id';
import { TxsList } from './txs/home';
import flags from '../config/flags';
import { t } from '@vegaprotocol/i18n';
import { Routes } from './route-names';
import { NetworkParameters } from './network-parameters';
@@ -29,7 +30,6 @@ import { truncateMiddle } from '@vegaprotocol/ui-toolkit';
import { remove0x } from '@vegaprotocol/utils';
import { PartyAccountsByAsset } from './parties/id/accounts';
import { Disclaimer } from './pages/disclaimer';
import { FLAGS } from '@vegaprotocol/environment';
export type Navigable = {
path: string;
@@ -59,7 +59,7 @@ type Route = RouteItem & {
children?: RouteItem[];
};
const partiesRoutes: Route[] = FLAGS.EXPLORER_PARTIES
const partiesRoutes: Route[] = flags.parties
? [
{
path: Routes.PARTIES,
@@ -119,7 +119,7 @@ const partiesRoutes: Route[] = FLAGS.EXPLORER_PARTIES
]
: [];
const assetsRoutes: Route[] = FLAGS.EXPLORER_ASSETS
const assetsRoutes: Route[] = flags.assets
? [
{
path: Routes.ASSETS,
@@ -147,7 +147,7 @@ const assetsRoutes: Route[] = FLAGS.EXPLORER_ASSETS
]
: [];
const genesisRoutes: Route[] = FLAGS.EXPLORER_GENESIS
const genesisRoutes: Route[] = flags.genesis
? [
{
path: Routes.GENESIS,
@@ -163,7 +163,7 @@ const genesisRoutes: Route[] = FLAGS.EXPLORER_GENESIS
]
: [];
const governanceRoutes: Route[] = FLAGS.EXPLORER_GOVERNANCE
const governanceRoutes: Route[] = flags.governance
? [
{
path: Routes.GOVERNANCE,
@@ -179,7 +179,7 @@ const governanceRoutes: Route[] = FLAGS.EXPLORER_GOVERNANCE
]
: [];
const marketsRoutes: Route[] = FLAGS.EXPLORER_MARKETS
const marketsRoutes: Route[] = flags.markets
? [
{
path: Routes.MARKETS,
@@ -207,7 +207,7 @@ const marketsRoutes: Route[] = FLAGS.EXPLORER_MARKETS
]
: [];
const networkParametersRoutes: Route[] = FLAGS.EXPLORER_NETWORK_PARAMETERS
const networkParametersRoutes: Route[] = flags.networkParameters
? [
{
path: Routes.NETWORK_PARAMETERS,
@@ -225,7 +225,7 @@ const networkParametersRoutes: Route[] = FLAGS.EXPLORER_NETWORK_PARAMETERS
]
: [];
const validators: Route[] = FLAGS.EXPLORER_VALIDATORS
const validators: Route[] = flags.validators
? [
{
path: Routes.VALIDATORS,
@@ -144,7 +144,7 @@ context(
closeStakingDialog();
navigateTo(navigation.validators);
cy.getByTestId(viewStakedByMeToggle).click();
cy.getByTestId(userStakeBtn, epochTimeout).should('have.length', 2);
cy.getByTestId(userStakeBtn).should('have.length', 2);
});
it('Able to stake against a validator - using vega from vesting contract', function () {
@@ -11,7 +11,7 @@ context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
cy.getByTestId('home-proposals').within(() => {
cy.get('[href="/proposals"]')
.should('exist')
.and('have.text', 'See all proposals');
.and('have.text', 'Browse, vote, and propose');
});
});
@@ -161,7 +161,7 @@ context(
);
cy.getByTestId('protocol-upgrade-proposal-status').should(
'have.text',
'Approved by validators'
'Approved by validators '
);
});
});
@@ -227,42 +227,5 @@ context(
cy.getByTestId(networkUpgradeProposalListItem).should('not.exist');
cy.getByTestId(closedProposalToggle).should('not.exist');
});
it('should display network upgrade banner with estimate', function () {
mockNetworkUpgradeProposal();
cy.visit('/');
cy.getByTestId('banners').within(() => {
cy.get('div')
.should('contain.text', 'The network will upgrade to v1 in ')
.and(
'contain.text',
'Trading activity will be interrupted, manage your risk appropriately.'
);
cy.getByTestId('external-link')
.should('have.attr', 'href')
.and('contain', '/proposals/protocol-upgrade/v1');
});
// estimate does not display possibly due to mocks or Cypress unless the proposal is clicked on several times
// By default the application waits for 10 blocks until showing estimate - roughly 10 seconds
for (let i = 0; i < 3; i++) {
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(3000);
navigateTo(navigation.proposals);
cy.getByTestId(networkUpgradeProposalListItem)
.first()
.find('[data-testid="view-proposal-btn"]')
.click();
}
cy.getByTestId('upgrade-proposal-estimate')
.invoke('text')
.as('displayedEstimate');
cy.get('@displayedEstimate').then((estimateText) => {
// Estimated time should automatically update every second
cy.getByTestId('protocol-upgrade-time')
.invoke('text')
.should('not.eq', estimateText);
});
});
}
);
@@ -174,8 +174,7 @@ export function clickOnValidatorFromList(
cy.get(`[row-id="${validatorNumber}"]`)
.should('be.visible')
.find(stakeValidatorListName)
.as('validatorOnList');
cy.get('@validatorOnList').click();
.click();
}
}
-4
View File
@@ -18,10 +18,6 @@ NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/annou
NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
NX_VEGA_REST_URL=https://api.n00.stagnet1.vega.xyz/api/v2/
NX_TENDERMINT_URL=https://tm.n01.stagnet1.vega.rocks
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.stagnet1.vega.xyz/websocket
#Test configuration variables
CYPRESS_FAIRGROUND=false
LC_ALL="en_US.UTF-8"
-3
View File
@@ -19,9 +19,6 @@ NX_DELEGATIONS_PAGINATION=50
NX_TRANCHES_SERVICE_URL=https://tranches-stagnet1-k8s.ops.vega.xyz
NX_VEGA_REST_URL=http://localhost:3008/api/v2/
NX_TENDERMINT_URL=http://localhost:26617
NX_TENDERMINT_WEBSOCKET_URL=wss://localhost:26617/websocket
#Test configuration variables
CYPRESS_FAIRGROUND=false
-3
View File
@@ -13,6 +13,3 @@ NX_TRANCHES_SERVICE_URL=https://tranches-devnet1-k8s.ops.vega.xyz
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
NX_VEGA_REST_URL=https://api.n00.devnet1.vega.xyz/api/v2/
NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40@o286262.ingest.sentry.io/5882996
NX_TENDERMINT_URL=https://tm.be.devnet1.vega.xyz/
NX_TENDERMINT_WEBSOCKET_URL=wss://be.devnet1.vega.xyz/websocket
-3
View File
@@ -14,6 +14,3 @@ NX_DELEGATIONS_PAGINATION=50
NX_TRANCHES_SERVICE_URL=https://tranches-mainnet-k8s.ops.vega.xyz
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/mainnet/announcements.json
NX_VEGA_REST_URL=https://api.vega.community/api/v2/
NX_TENDERMINT_URL=https://be.vega.community
NX_TENDERMINT_WEBSOCKET_URL=wss://be.vega.community/websocket
+2 -5
View File
@@ -1,8 +1,8 @@
# App configuration variables
NX_VEGA_ENV=MAINNET_MIRROR
NX_VEGA_ENV=MAINNET-MIRROR
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/mainnet-mirror/vegawallet-mainnet-mirror.toml
NX_VEGA_URL=https://api.mainnet-mirror.vega.rocks/graphql
NX_VEGA_NETWORKS='{"DEVNET":"https://dev.governance.vega.xyz","TESTNET":"https://governance.fairground.wtf","MAINNET":"https://governance.vega.xyz","MAINNET_MIRROR":"https://governance.mainnet-mirror.vega.rocks","STAGNET1":"https://trading.stagnet1.vega.rocks"}'
NX_VEGA_NETWORKS='{"DEVNET":"https://dev.governance.vega.xyz","TESTNET":"https://governance.fairground.wtf","MAINNET":"https://governance.vega.xyz","MAINNET-MIRROR":"https://governance.mainnet-mirror.vega.rocks","STAGNET1":"https://trading.stagnet1.vega.rocks"}'
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
@@ -13,6 +13,3 @@ NX_DELEGATIONS_PAGINATION=50
NX_TRANCHES_SERVICE_URL=https://tranches-mainnet-mirror-k8s.ops.vega.xyz
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/mainnet/announcements.json
NX_VEGA_REST_URL=https://api.mainnet-mirror.vega.rocks/api/v2/
NX_TENDERMINT_URL=https://be.mainnet-mirror.vega.rocks
NX_TENDERMINT_WEBSOCKET_URL=wss://be.mainnet-mirror.vega.rocks/websocket
-3
View File
@@ -9,6 +9,3 @@ NX_DELEGATIONS_PAGINATION=50
NX_TRANCHES_SERVICE_URL=https://tranches-stagnet1-k8s.ops.vega.xyz
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
NX_VEGA_REST_URL=https://api.n00.stagnet1.vega.xyz/api/v2/
NX_TENDERMINT_URL=https://tm.n01.stagnet1.vega.rocks
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.stagnet1.vega.xyz/websocket
-3
View File
@@ -14,6 +14,3 @@ NX_TRANCHES_SERVICE_URL=https://tranches-testnet-k8s.ops.vega.xyz
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
NX_VEGA_REST_URL=https://api.n07.testnet.vega.xyz/api/v2/
NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40@o286262.ingest.sentry.io/5882996
NX_TENDERMINT_URL=https://tm.be.testnet.vega.xyz
NX_TENDERMINT_WEBSOCKET_URL=wss://be.testnet.vega.xyz/websocket
-3
View File
@@ -11,6 +11,3 @@ NX_VEGA_EXPLORER_URL=https://explorer.validators-testnet.vega.rocks/
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
NX_VEGA_REST_URL=https://api-validators-testnet.vega.rocks/api/v2/
NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40@o286262.ingest.sentry.io/5882996
NX_TENDERMINT_URL=https://tm.be.validators-testnet.vega.rocks
NX_TENDERMINT_WEBSOCKET_URL=wss://be.validators-testnet.vega.
+5 -4
View File
@@ -2,13 +2,14 @@ import * as Sentry from '@sentry/react';
import { toBigNum } from '@vegaprotocol/utils';
import { Splash } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet, useEagerConnect } from '@vegaprotocol/wallet';
import { FLAGS, useEnvironment } from '@vegaprotocol/environment';
import { useEnvironment } from '@vegaprotocol/environment';
import { useWeb3React } from '@web3-react/core';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { SplashError } from './components/splash-error';
import { SplashLoader } from './components/splash-loader';
import { Flags } from './config';
import {
AppStateActionType,
useAppState,
@@ -79,7 +80,7 @@ export const AppLoader = ({ children }: { children: React.ReactElement }) => {
}
};
if (!FLAGS.GOVERNANCE_NETWORK_DOWN) {
if (!Flags.NETWORK_DOWN) {
run();
}
}, [token, appDispatch, staking, vesting]);
@@ -147,7 +148,7 @@ export const AppLoader = ({ children }: { children: React.ReactElement }) => {
};
// Only begin polling if network limits flag is set, as this is a new API not yet on mainnet 7/3/22
if (FLAGS.GOVERNANCE_NETWORK_LIMITS) {
if (Flags.NETWORK_LIMITS) {
getNetworkLimits();
}
@@ -156,7 +157,7 @@ export const AppLoader = ({ children }: { children: React.ReactElement }) => {
};
}, [appDispatch, VEGA_URL, t]);
if (FLAGS.GOVERNANCE_NETWORK_DOWN) {
if (Flags.NETWORK_DOWN) {
return (
<Splash>
<SplashError />
+1 -12
View File
@@ -18,10 +18,6 @@ import { VegaWallet } from '../vega-wallet';
import { useLocation, useMatch } from 'react-router-dom';
import { useEffect } from 'react';
import { useTelemetryDialog } from '../telemetry-dialog/telemetry-dialog';
import {
ProtocolUpgradeCountdown,
ProtocolUpgradeCountdownMode,
} from '@vegaprotocol/proposals';
export const SettingsLink = () => {
const { open, isOpen, close } = useTelemetryDialog();
@@ -65,14 +61,7 @@ export const Nav = ({ theme }: Pick<NavigationProps, 'theme'>) => {
appName="Governance"
theme={theme}
breakpoints={[458, 959]}
actions={
<>
<SettingsLink />
<ProtocolUpgradeCountdown
mode={ProtocolUpgradeCountdownMode.IN_ESTIMATED_TIME_REMAINING}
/>
</>
}
actions={<SettingsLink />}
>
<NavigationList
className="[.drawer-content_&]:border-b [.drawer-content_&]:border-b-vega-light-200 dark:[.drawer-content_&]:border-b-vega-dark-200 [.drawer-content_&]:pb-8 [.drawer-content_&]:mb-2"
@@ -4,11 +4,7 @@ import type { ReactNode } from 'react';
import { AnnouncementBanner } from '@vegaprotocol/announcements';
import { Nav } from '../nav';
import { Networks, useEnvironment } from '@vegaprotocol/environment';
import {
ProtocolUpgradeCountdownMode,
ProtocolUpgradeProposalNotification,
} from '@vegaprotocol/proposals';
import { ViewingAsBanner } from '@vegaprotocol/ui-toolkit';
import React from 'react';
interface AppLayoutProps {
children: ReactNode;
@@ -35,23 +31,8 @@ export const AppLayout = ({ children }: AppLayoutProps) => {
/>
)}
<Nav theme={VEGA_ENV === Networks.TESTNET ? 'yellow' : 'dark'} />
<NotificationsContainer />
</div>
<div className={AppLayoutClasses}>{children}</div>
</div>
);
};
const NotificationsContainer = () => {
const { isReadOnly, pubKey, disconnect } = useVegaWallet();
return (
<div data-testid="banners">
<ProtocolUpgradeProposalNotification
mode={ProtocolUpgradeCountdownMode.IN_ESTIMATED_TIME_REMAINING}
/>
{isReadOnly ? (
<ViewingAsBanner pubKey={pubKey} disconnect={disconnect} />
) : null}
</div>
);
};
@@ -1,3 +1,5 @@
import { ViewingAsBanner } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet';
import React from 'react';
export interface TemplateSidebarProps {
@@ -6,18 +8,24 @@ export interface TemplateSidebarProps {
}
export function TemplateSidebar({ children, sidebar }: TemplateSidebarProps) {
const { isReadOnly, pubKey, disconnect } = useVegaWallet();
return (
<div className="w-full border-b border-neutral-700 lg:grid lg:grid-rows-[1fr] lg:grid-cols-[1fr_450px]">
<main className="max-w-[100vw] col-start-1 p-4 overflow-auto">
{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) => (
<section className="mb-4 last:mb-0" key={i}>
{Component}
</section>
))}
</aside>
</div>
<>
{isReadOnly ? (
<ViewingAsBanner pubKey={pubKey} disconnect={disconnect} />
) : null}
<div className="w-full border-b border-neutral-700 lg:grid lg:grid-rows-[1fr] lg:grid-cols-[1fr_450px]">
<main className="max-w-[100vw] col-start-1 p-4 overflow-auto">
{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) => (
<section className="mb-4 last:mb-0" key={i}>
{Component}
</section>
))}
</aside>
</div>
</>
);
}
+6 -4
View File
@@ -25,10 +25,6 @@ export const ContractAddresses: {
claimAddress: customClaimAddress ?? '0x0',
lockedAddress: customLockedAddress ?? '0x0',
},
MAINNET_MIRROR: {
claimAddress: '0x8Cef746ab7C83B61F6461cC92882bD61AB65a994', // TODO not deployed to this env, but random address so app doesn't error
lockedAddress: '0x0', // TODO not deployed to this env
},
DEVNET: {
claimAddress: '0x8Cef746ab7C83B61F6461cC92882bD61AB65a994', // TODO not deployed to this env, but random address so app doesn't error
lockedAddress: '0x0', // TODO not deployed to this env
@@ -67,6 +63,12 @@ export const ENV = {
localProviderUrl: windowOrDefault('NX_LOCAL_PROVIDER_URL'),
delegationsPagination: windowOrDefault('NX_DELEGATIONS_PAGINATION'),
rest: windowOrDefault('NX_VEGA_REST_URL'),
flags: {
NETWORK_DOWN: TRUTHY.includes(windowOrDefault('NX_NETWORK_DOWN')),
MOCK: TRUTHY.includes(windowOrDefault('NX_MOCKED')),
FAIRGROUND: TRUTHY.includes(windowOrDefault('NX_FAIRGROUND')),
NETWORK_LIMITS: TRUTHY.includes(windowOrDefault('NX_NETWORK_LIMITS')),
},
addresses:
ContractAddresses[(envName === 'local' ? 'CUSTOM' : envName) as Networks],
};
+5
View File
@@ -0,0 +1,5 @@
import { ENV } from './env';
export const Flags = {
...ENV.flags,
};
+1
View File
@@ -1 +1,2 @@
export * from './flags';
export * from './env';
+3 -19
View File
@@ -166,7 +166,6 @@
"proposedEnactment": "Proposed enactment",
"Enacted": "Enacted",
"enactedOn": "Enacted on",
"enactedOn{{date}}": "Enacted on {{enactmentDate}}",
"status": "Status",
"state": "State",
"shouldPass": "Should pass",
@@ -186,7 +185,6 @@
"proposedOn": "Proposed on",
"proposedBy": "Proposed by",
"toEnactOn": "Enacts on",
"enactsOn{{date}}": "Enacts on {{enactmentDate}}",
"closesOn": "Closes on",
"closedOn": "Closed on",
"errorDetails": "Error details",
@@ -219,7 +217,6 @@
"votingThresholdInfo": "If the token vote passes the participation threshold it will be the deciding vote. If not, the outcome will be determined by liquidity providers on this market.",
"noGovernanceTokens": "You need some VEGA tokens to participate in governance",
"youVoted": "You voted",
"voted": "Voted",
"changeVote": "Change vote",
"txRequested": "Confirm transaction in wallet",
"votePending": "Casting vote",
@@ -631,6 +628,7 @@
"pendingDescriptionLinkText": "set up and run a node on Vega",
"pendingDescription2": ". A node can move from being a candidate into standby based on how much nomination it attracts, assuming it has proven reliability by sending heartbeats to the network.",
"n/a": "N/A",
"Set to": "Set to",
"pass": "pass",
"fail": "fail",
"New asset": "New asset",
@@ -790,7 +788,7 @@
"overstakedPenalty": "Overstaked penalty",
"multisigPenalty": "Multisig penalty",
"homeProposalsIntro": "Decisions on the Vega network are on-chain, with tokenholders creating proposals that other tokenholders vote to approve or reject. Network upgrades are proposed and approved by validators.",
"homeProposalsButtonText": "See all proposals",
"homeProposalsButtonText": "Browse, vote, and propose",
"homeValidatorsIntro": "Vega runs on a delegated proof of stake blockchain, where validators earn fees for validating block transactions. Tokenholders can nominate validators by staking tokens to them.",
"homeValidatorsButtonText": "Browse, and stake",
"homeRewardsIntro": "Track rewards you've earned for trading, liquidity provision, market creation, and staking.",
@@ -839,19 +837,5 @@
"RejectedProposals": "Rejected proposals",
"networkGovernance": "Network governance",
"networkUpgrades": "Network upgrades",
"assetSpecification": "Asset specification",
"viewDetails": "View details",
"vegaGovernance": "Vega Governance",
"latestProposals": "Latest proposals",
"vegaToken": "VEGA Token",
"majorityVotedForProposal": "majority voted for this proposal",
"majorityNotVotedForProposal": "majority not voted for this proposal",
"requiredMajorityVotedForProposal": "Required majority voted for this proposal",
"requiredMajorityNotVotedForProposal": "Required majority not voted for this proposal",
"minParticipationReached": "Min. participation reached",
"minParticipationNotReached": "Min. participation not reached",
"consensusNodes": "consensus nodes",
"activeNodes": "active nodes",
"Estimated time to upgrade": "Estimated time to upgrade",
"Upgraded at": "Upgraded at"
"assetSpecification": "Asset specification"
}
+28 -59
View File
@@ -20,7 +20,7 @@ import {
getNotRejectedProposals,
getNotRejectedProtocolUpgradeProposals,
} from '../proposals/proposals/proposals-container';
import { Heading, SubHeading } from '../../components/heading';
import { Heading } from '../../components/heading';
import * as Schema from '@vegaprotocol/types';
import type { RouteChildProps } from '..';
import type { ProposalFieldsFragment } from '../proposals/proposals/__generated__/Proposals';
@@ -31,11 +31,6 @@ import {
orderByDate,
orderByUpgradeBlockHeight,
} from '../proposals/components/proposals-list/proposals-list';
import {
NetworkParams,
useNetworkParams,
} from '@vegaprotocol/network-parameters';
import { BigNumber } from '../../lib/bignumber';
const nodesToShow = 6;
@@ -47,57 +42,33 @@ const HomeProposals = ({
protocolUpgradeProposals: ProtocolUpgradeProposalFieldsFragment[];
}) => {
const { t } = useTranslation();
const {
params: networkParams,
loading: networkParamsLoading,
error: networkParamsError,
} = useNetworkParams([
NetworkParams.governance_proposal_market_requiredMajority,
NetworkParams.governance_proposal_updateMarket_requiredMajority,
NetworkParams.governance_proposal_updateMarket_requiredMajorityLP,
NetworkParams.governance_proposal_asset_requiredMajority,
NetworkParams.governance_proposal_updateAsset_requiredMajority,
NetworkParams.governance_proposal_updateNetParam_requiredMajority,
NetworkParams.governance_proposal_freeform_requiredMajority,
]);
return (
<AsyncRenderer
loading={networkParamsLoading}
error={networkParamsError}
data={networkParams}
>
<section className="mb-16" data-testid="home-proposals">
<Heading title={t('vegaGovernance')} />
<h3 className="mb-6">{t('homeProposalsIntro')}</h3>
<div className="mb-8">
<ExternalLink href={ExternalLinks.GOVERNANCE_PAGE}>
{t(`readMoreGovernance`)}
</ExternalLink>
</div>
<section className="mb-16" data-testid="home-proposals">
<Heading title={t('Proposals')} />
<h3 className="mb-6">{t('homeProposalsIntro')}</h3>
<div className="flex items-center mb-8 gap-8">
<Link to={`${Routes.PROPOSALS}`}>
<Button size="md">{t('homeProposalsButtonText')}</Button>
</Link>
<SubHeading title={t('latestProposals')} />
<ul data-testid="home-proposal-list" className="grid gap-6">
{protocolUpgradeProposals.map((proposal, index) => (
<ProtocolUpgradeProposalsListItem key={index} proposal={proposal} />
))}
<ExternalLink href={ExternalLinks.GOVERNANCE_PAGE}>
{t(`readMoreGovernance`)}
</ExternalLink>
</div>
<ul
data-testid="home-proposal-list"
className="grid md:grid-cols-2 lg:grid-cols-1 xl:grid-cols-2 gap-6"
>
{protocolUpgradeProposals.map((proposal, index) => (
<ProtocolUpgradeProposalsListItem key={index} proposal={proposal} />
))}
{proposals.map((proposal) => (
<ProposalsListItem
key={proposal.id}
proposal={proposal}
networkParams={networkParams}
/>
))}
</ul>
<div className="mt-6">
<Link to={`${Routes.PROPOSALS}`}>
<Button size="md">{t('homeProposalsButtonText')}</Button>
</Link>
</div>
</section>
</AsyncRenderer>
{proposals.map((proposal) => (
<ProposalsListItem key={proposal.id} proposal={proposal} />
))}
</ul>
</section>
);
};
@@ -115,8 +86,8 @@ const HomeNodes = ({
const { t } = useTranslation();
const highlightedNodeData = [
{ title: t('activeNodes'), length: activeNodes.length },
{ title: t('consensusNodes'), length: consensusNodes.length },
{ title: t('active nodes'), length: activeNodes.length },
{ title: t('consensus nodes'), length: consensusNodes.length },
];
return (
@@ -262,7 +233,7 @@ const GovernanceHome = ({ name }: RouteChildProps) => {
[protocolUpgradeProposals]
);
const totalProposalsDesired = 3;
const totalProposalsDesired = 4;
const protocolUpgradeProposalsToShow = sortedProtocolUpgradeProposals.slice(
0,
totalProposalsDesired
@@ -277,8 +248,6 @@ const GovernanceHome = ({ name }: RouteChildProps) => {
const activeNodes = removePaginationWrapper(
validatorsData?.nodesConnection.edges
).filter((node) =>
new BigNumber(node.rankingScore.performanceScore).isGreaterThan(0)
);
const trimmedActiveNodes = activeNodes?.slice(0, nodesToShow);
@@ -318,7 +287,7 @@ const GovernanceHome = ({ name }: RouteChildProps) => {
</div>
<div data-testid="home-vega-token">
<Heading title={t('vegaToken')} marginTop={false} />
<Heading title={t('VEGA Token')} marginTop={false} />
<h3 className="mb-6">{t('homeVegaTokenIntro')}</h3>
<div className="flex items-center mb-8 gap-4">
<Link to={Routes.WITHDRAWALS}>
@@ -1,4 +1,5 @@
import { useTranslation } from 'react-i18next';
import { Icon } from '@vegaprotocol/ui-toolkit';
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import { ProposalState } from '@vegaprotocol/types';
@@ -17,32 +18,53 @@ export const CurrentProposalState = ({
switch (proposal?.state) {
case ProposalState.STATE_ENACTED: {
proposalStatus = t('voteState_Enacted');
proposalStatus = (
<>
<span className="mr-2">{t('voteState_Enacted')}</span>
<Icon name={'tick'} />
</>
);
break;
}
case ProposalState.STATE_PASSED: {
proposalStatus = t('voteState_Passed');
proposalStatus = (
<>
<span className="mr-2">{t('voteState_Passed')}</span>
<Icon name={'tick'} />
</>
);
break;
}
case ProposalState.STATE_WAITING_FOR_NODE_VOTE: {
proposalStatus = t('voteState_WaitingForNodeVote');
proposalStatus = (
<>
<span className="mr-2">{t('voteState_WaitingForNodeVote')}</span>
<Icon name={'time'} />
</>
);
break;
}
case ProposalState.STATE_OPEN: {
variant = 'primary' as ProposalInfoLabelVariant;
proposalStatus = t('voteState_Open');
proposalStatus = <>{t('voteState_Open')}</>;
break;
}
case ProposalState.STATE_DECLINED: {
proposalStatus = t('voteState_Declined');
proposalStatus = (
<>
<span className="mr-2">{t('voteState_Declined')}</span>
<Icon name={'cross'} />
</>
);
break;
}
case ProposalState.STATE_REJECTED: {
proposalStatus = t('voteState_Rejected');
break;
}
case ProposalState.STATE_FAILED: {
proposalStatus = t('voteState_Failed');
proposalStatus = (
<>
<span className="mr-2">{t('voteState_Rejected')}</span>
<Icon name={'warning-sign'} />
</>
);
break;
}
}
@@ -1,46 +1,18 @@
import { render, screen } from '@testing-library/react';
import { MockedProvider } from '@apollo/client/testing';
import {
ProposalRejectionReason,
ProposalState,
VoteValue,
} from '@vegaprotocol/types';
import { VegaWalletContext } from '@vegaprotocol/wallet';
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
import {
generateNoVotes,
generateProposal,
generateYesVotes,
} from '../../test-helpers/generate-proposals';
import { ProposalHeader } from './proposal-header';
import {
lastWeek,
nextWeek,
mockNetworkParams,
mockWalletContext,
createUserVoteQueryMock,
} from '../../test-helpers/mocks';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import type { MockedResponse } from '@apollo/client/testing';
import { ProposalRejectionReason, ProposalState } from '@vegaprotocol/types';
import { lastWeek, nextWeek } from '../../test-helpers/mocks';
const renderComponent = (
proposal: ProposalQuery['proposal'],
isListItem = true,
mocks: MockedResponse[] = []
) =>
render(
<AppStateProvider>
<MockedProvider mocks={mocks}>
<VegaWalletContext.Provider value={mockWalletContext}>
<ProposalHeader
proposal={proposal}
isListItem={isListItem}
networkParams={mockNetworkParams}
/>
</VegaWalletContext.Provider>
</MockedProvider>
</AppStateProvider>
);
isListItem = true
) => render(<ProposalHeader proposal={proposal} isListItem={isListItem} />);
describe('Proposal header', () => {
it('Renders New market proposal', () => {
@@ -345,6 +317,22 @@ describe('Proposal header', () => {
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Open');
});
it('Renders proposal state: Declined - majority not reached', () => {
renderComponent(
generateProposal({
state: ProposalState.STATE_DECLINED,
terms: {
enactmentDatetime: lastWeek.toString(),
},
votes: {
no: generateNoVotes(1, 1000000000000000000),
yes: generateYesVotes(1, 1000000000000000000),
},
})
);
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Declined');
});
it('Renders proposal state: Rejected', () => {
renderComponent(
generateProposal({
@@ -358,32 +346,4 @@ describe('Proposal header', () => {
);
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Rejected');
});
it('Renders proposal state: Open - user voted against', async () => {
const proposal = generateProposal({
state: ProposalState.STATE_OPEN,
terms: {
closingDatetime: nextWeek.toString(),
},
});
renderComponent(proposal, true, [
// @ts-ignore generateProposal always creates an id
createUserVoteQueryMock(proposal.id, VoteValue.VALUE_NO),
]);
expect(await screen.findByTestId('user-voted-no')).toBeInTheDocument();
});
it('Renders proposal state: Open - user voted for', async () => {
const proposal = generateProposal({
state: ProposalState.STATE_OPEN,
terms: {
closingDatetime: nextWeek.toString(),
},
});
renderComponent(proposal, true, [
// @ts-ignore generateProposal always creates an id
createUserVoteQueryMock(proposal.id, VoteValue.VALUE_YES),
]);
expect(await screen.findByTestId('user-voted-yes')).toBeInTheDocument();
});
});
@@ -1,5 +1,5 @@
import { useTranslation } from 'react-i18next';
import { Lozenge, VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
import { Lozenge } from '@vegaprotocol/ui-toolkit';
import { shorten } from '@vegaprotocol/utils';
import { Heading, SubHeading } from '../../../../components/heading';
import type { ReactNode } from 'react';
@@ -8,21 +8,15 @@ import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import { truncateMiddle } from '../../../../lib/truncate-middle';
import { CurrentProposalState } from '../current-proposal-state';
import { ProposalInfoLabel } from '../proposal-info-label';
import { useUserVote } from '../vote-details/use-user-vote';
import { ProposalVotingStatus } from '../proposal-voting-status';
import type { NetworkParamsResult } from '@vegaprotocol/network-parameters';
export const ProposalHeader = ({
proposal,
networkParams,
isListItem = true,
}: {
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
networkParams: Partial<NetworkParamsResult>;
isListItem?: boolean;
}) => {
const { t } = useTranslation();
const { voteState } = useUserVote(proposal?.id);
const change = proposal?.terms.change;
let details: ReactNode;
@@ -125,35 +119,6 @@ export const ProposalHeader = ({
return (
<>
<div className="flex items-center justify-between gap-4 mb-6 text-sm">
<div data-testid="proposal-type">
<ProposalInfoLabel variant="secondary">
{t(`${proposalType}`)}
</ProposalInfoLabel>
</div>
<div className="flex items-center gap-6">
{(voteState === 'Yes' || voteState === 'No') && (
<div
className="flex items-center gap-2"
data-testid={`user-voted-${voteState.toLowerCase()}`}
>
<div className="text-vega-green">
<VegaIcon name={VegaIconNames.VOTE} size={24} />
</div>
<div>
{t('voted')}{' '}
<span className="uppercase">{t(`voteState_${voteState}`)}</span>
</div>
</div>
)}
<div data-testid="proposal-status">
<CurrentProposalState proposal={proposal} />
</div>
</div>
</div>
<div data-testid="proposal-title">
{isListItem ? (
<header>
@@ -168,16 +133,23 @@ export const ProposalHeader = ({
)}
</div>
<div className="flex items-center gap-2 mb-4">
<div data-testid="proposal-type">
<ProposalInfoLabel variant="secondary">
{t(`${proposalType}`)}
</ProposalInfoLabel>
</div>
<div data-testid="proposal-status">
<CurrentProposalState proposal={proposal} />
</div>
</div>
{details && (
<div
data-testid="proposal-details"
className="break-words mb-6 text-vega-light-200"
>
<div data-testid="proposal-details" className="break-words my-10">
{details}
</div>
)}
<ProposalVotingStatus proposal={proposal} networkParams={networkParams} />
</>
);
};
@@ -7,10 +7,10 @@ export type ProposalInfoLabelVariant =
| 'tertiary'
| 'highlight';
const base = 'rounded-full px-3 py-1 font-alpha';
const primary = 'bg-vega-green text-black';
const secondary = 'bg-vega-dark-200 text-vega-light-200';
const tertiary = 'bg-vega-dark-150 text-vega-light-200';
const base = 'rounded-md px-2 py-1 font-alpha';
const primary = 'bg-vega-light-150 text-black';
const secondary = 'bg-vega-dark-200 text-white';
const tertiary = 'bg-vega-dark-150 text-white';
const highlight = 'bg-vega-yellow text-black';
const getClassname = (variant: ProposalInfoLabelVariant) => {
@@ -1 +0,0 @@
export * from './proposal-voting-status';
@@ -1,153 +0,0 @@
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,
mockNetworkParams,
} from '../../test-helpers/mocks';
import { ProposalVotingStatus } from './proposal-voting-status';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import type { MockedResponse } from '@apollo/client/testing';
import {
generateNoVotes,
generateProposal,
generateYesVotes,
} from '../../test-helpers/generate-proposals';
import { ProposalState } from '@vegaprotocol/types';
import { BigNumber } from '../../../../lib/bignumber';
import type { AppState } from '../../../../contexts/app-state/app-state-context';
const mockTotalSupply = new BigNumber(100);
// Note - giving a fixedTokenValue of 1 means a ratio of 1:1 votes to tokens, making sums easier :)
const fixedTokenValue = 1000000000000000000;
const mockAppState: AppState = {
totalAssociated: new BigNumber('50063005'),
decimals: 18,
totalSupply: mockTotalSupply,
vegaWalletManageOverlay: false,
transactionOverlay: false,
bannerMessage: '',
disconnectNotice: false,
};
jest.mock('../../../../contexts/app-state/app-state-context', () => ({
useAppState: () => ({
appState: mockAppState,
}),
}));
const renderComponent = (
proposal: ProposalQuery['proposal'],
// eslint-disable-next-line @typescript-eslint/no-explicit-any
mocks: MockedResponse<any>[] = [networkParamsQueryMock]
) =>
render(
<Router>
<MockedProvider mocks={mocks}>
<VegaWalletContext.Provider value={mockWalletContext}>
<ProposalVotingStatus
proposal={proposal}
networkParams={mockNetworkParams}
/>
</VegaWalletContext.Provider>
</MockedProvider>
</Router>
);
describe('ProposalVotingStatus', () => {
beforeAll(() => {
jest.useFakeTimers();
jest.setSystemTime(0);
});
afterAll(() => {
jest.useRealTimers();
});
it('Renders majority reached', () => {
const yesVotes = 100;
const noVotes = 0;
renderComponent(
generateProposal({
state: ProposalState.STATE_PASSED,
terms: {
closingDatetime: lastWeek.toString(),
enactmentDatetime: nextWeek.toString(),
},
votes: {
__typename: 'ProposalVotes',
yes: generateYesVotes(yesVotes, fixedTokenValue),
no: generateNoVotes(noVotes, fixedTokenValue),
},
})
);
expect(screen.getByTestId('majority-reached')).toBeInTheDocument();
});
it('Renders majority not reached', () => {
const yesVotes = 20;
const noVotes = 80;
renderComponent(
generateProposal({
state: ProposalState.STATE_PASSED,
terms: {
closingDatetime: lastWeek.toString(),
enactmentDatetime: nextWeek.toString(),
},
votes: {
__typename: 'ProposalVotes',
yes: generateYesVotes(yesVotes, fixedTokenValue),
no: generateNoVotes(noVotes, fixedTokenValue),
},
})
);
expect(screen.getByTestId('majority-not-reached')).toBeInTheDocument();
});
it('Renders participation reached', () => {
const yesVotes = 1000;
const noVotes = 0;
renderComponent(
generateProposal({
state: ProposalState.STATE_PASSED,
terms: {
closingDatetime: lastWeek.toString(),
enactmentDatetime: nextWeek.toString(),
},
votes: {
__typename: 'ProposalVotes',
yes: generateYesVotes(yesVotes, fixedTokenValue),
no: generateNoVotes(noVotes, fixedTokenValue),
},
})
);
expect(screen.getByTestId('participation-reached')).toBeInTheDocument();
});
it('Renders participation not reached', () => {
const yesVotes = 0;
const noVotes = 0;
renderComponent(
generateProposal({
terms: {
closingDatetime: lastWeek.toString(),
enactmentDatetime: nextWeek.toString(),
},
votes: {
__typename: 'ProposalVotes',
yes: generateYesVotes(yesVotes, fixedTokenValue),
no: generateNoVotes(noVotes, fixedTokenValue),
},
})
);
expect(screen.getByTestId('participation-not-reached')).toBeInTheDocument();
});
});
@@ -1,174 +0,0 @@
import classNames from 'classnames';
import { useTranslation } from 'react-i18next';
import { Icon } from '@vegaprotocol/ui-toolkit';
import { useVoteInformation } from '../../hooks';
import { BigNumber } from '../../../../lib/bignumber';
import type { NetworkParamsResult } from '@vegaprotocol/network-parameters';
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
const statusClasses = (reached: boolean) =>
classNames('flex items-center gap-2 px-4 py-2 rounded-md', {
'bg-vega-green-700': reached,
'bg-vega-red-700': !reached,
});
const MajorityStatus = ({
reached,
requiredMajority,
}: {
reached: boolean;
requiredMajority: string | null | undefined;
}) => {
const { t } = useTranslation();
return (
<div
className={statusClasses(reached)}
data-testid="proposal-majority-status"
>
{reached ? <Icon name="tick" /> : <Icon name="cross" />}
{reached ? (
<div data-testid="majority-reached">
{requiredMajority ? (
<>
{new BigNumber(requiredMajority).times(100).toString()}%{' '}
{t('majorityVotedForProposal')}
</>
) : (
t('requiredMajorityVotedForProposal')
)}
</div>
) : (
<div data-testid="majority-not-reached">
{requiredMajority ? (
<>
{new BigNumber(requiredMajority).times(100).toString()}%{' '}
{t('majorityNotVotedForProposal')}
</>
) : (
t('requiredMajorityNotVotedForProposal')
)}
</div>
)}
</div>
);
};
const ParticipationStatus = ({ reached }: { reached: boolean }) => {
const { t } = useTranslation();
return (
<div className={statusClasses(reached)}>
{reached ? (
<>
<Icon name="tick" />
<div data-testid="participation-reached">
{t('minParticipationReached')}
</div>
</>
) : (
<>
<Icon name="cross" />
<div data-testid="participation-not-reached">
{t('minParticipationNotReached')}
</div>
</>
)}
</div>
);
};
export const ProposalVotingStatus = ({
proposal,
networkParams,
}: {
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
networkParams: Partial<NetworkParamsResult>;
}) => {
const { t } = useTranslation();
const { majorityMet, majorityLPMet, participationMet, participationLPMet } =
useVoteInformation({
proposal,
});
if (!proposal) {
return null;
}
const isUpdateMarket = proposal?.terms.change.__typename === 'UpdateMarket';
let requiredVotingMajority = null;
let requiredVotingMajorityLP = null;
if (networkParams) {
switch (proposal.terms.change.__typename) {
case 'NewMarket':
requiredVotingMajority =
networkParams.governance_proposal_market_requiredMajority;
break;
case 'UpdateMarket':
requiredVotingMajority =
networkParams.governance_proposal_updateMarket_requiredMajority;
requiredVotingMajorityLP =
networkParams.governance_proposal_updateMarket_requiredMajorityLP;
break;
case 'NewAsset':
requiredVotingMajority =
networkParams.governance_proposal_asset_requiredMajority;
break;
case 'UpdateAsset':
requiredVotingMajority =
networkParams.governance_proposal_updateAsset_requiredMajority;
break;
case 'UpdateNetworkParameter':
requiredVotingMajority =
networkParams.governance_proposal_updateNetParam_requiredMajority;
break;
case 'NewFreeform':
requiredVotingMajority =
networkParams.governance_proposal_freeform_requiredMajority;
break;
}
}
if (isUpdateMarket) {
return (
<div className="mb-6">
<p>{t('Token vote')}</p>
<div
className="grid grid-cols-2 gap-4 items-center"
data-testid="token-vote-statuses"
>
<MajorityStatus
reached={majorityMet}
requiredMajority={requiredVotingMajority}
/>{' '}
<ParticipationStatus reached={participationMet} />
</div>
<p className="mt-4">{t('Liquidity provider vote')}</p>
<div
className="grid grid-cols-2 gap-4 items-center"
data-testid="lp-vote-statuses"
>
<MajorityStatus
reached={majorityLPMet}
requiredMajority={requiredVotingMajorityLP}
/>{' '}
<ParticipationStatus reached={participationLPMet} />
</div>
</div>
);
}
return (
<div className="grid grid-cols-2 gap-4 items-center mb-6">
<MajorityStatus
reached={majorityMet}
requiredMajority={requiredVotingMajority}
/>{' '}
<ParticipationStatus reached={participationMet} />
</div>
);
};
@@ -4,7 +4,6 @@ import { generateProposal } from '../../test-helpers/generate-proposals';
import { Proposal } from './proposal';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import { ProposalState } from '@vegaprotocol/types';
import { mockNetworkParams } from '../../test-helpers/mocks';
jest.mock('@vegaprotocol/network-parameters', () => ({
...jest.requireActual('@vegaprotocol/network-parameters'),
@@ -47,7 +46,6 @@ const renderComponent = (proposal: ProposalQuery['proposal']) => {
<Proposal
restData={{}}
proposal={proposal as ProposalQuery['proposal']}
networkParams={mockNetworkParams}
/>
</MemoryRouter>
);
@@ -1,6 +1,10 @@
import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
import { Icon, RoundedWrapper } from '@vegaprotocol/ui-toolkit';
import {
NetworkParams,
useNetworkParams,
} from '@vegaprotocol/network-parameters';
import { AsyncRenderer, Icon, RoundedWrapper } from '@vegaprotocol/ui-toolkit';
import { ProposalHeader } from '../proposal-detail-header/proposal-header';
import { ProposalDescription } from '../proposal-description';
import { ProposalChangeTable } from '../proposal-change-table';
@@ -17,7 +21,6 @@ import type { MarketInfoWithData } from '@vegaprotocol/markets';
import type { AssetQuery } from '@vegaprotocol/assets';
import { removePaginationWrapper } from '@vegaprotocol/utils';
import { ProposalState } from '@vegaprotocol/types';
import type { NetworkParamsResult } from '@vegaprotocol/network-parameters';
export enum ProposalType {
PROPOSAL_NEW_MARKET = 'PROPOSAL_NEW_MARKET',
@@ -29,7 +32,6 @@ export enum ProposalType {
}
export interface ProposalProps {
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
networkParams: Partial<NetworkParamsResult>;
newMarketData?: MarketInfoWithData | null;
assetData?: AssetQuery | null;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -38,12 +40,20 @@ export interface ProposalProps {
export const Proposal = ({
proposal,
networkParams,
restData,
newMarketData,
assetData,
}: ProposalProps) => {
const { t } = useTranslation();
const { params, loading, error } = useNetworkParams([
NetworkParams.governance_proposal_market_minVoterBalance,
NetworkParams.governance_proposal_updateMarket_minVoterBalance,
NetworkParams.governance_proposal_asset_minVoterBalance,
NetworkParams.governance_proposal_updateAsset_minVoterBalance,
NetworkParams.governance_proposal_updateNetParam_minVoterBalance,
NetworkParams.governance_proposal_freeform_minVoterBalance,
NetworkParams.spam_protection_voting_min_tokens,
]);
if (!proposal) {
return null;
@@ -69,122 +79,122 @@ export const Proposal = ({
let minVoterBalance = null;
let proposalType = null;
if (networkParams) {
if (params) {
switch (proposal.terms.change.__typename) {
case 'NewMarket':
minVoterBalance =
networkParams.governance_proposal_market_minVoterBalance;
minVoterBalance = params.governance_proposal_market_minVoterBalance;
proposalType = ProposalType.PROPOSAL_NEW_MARKET;
break;
case 'UpdateMarket':
minVoterBalance =
networkParams.governance_proposal_updateMarket_minVoterBalance;
params.governance_proposal_updateMarket_minVoterBalance;
proposalType = ProposalType.PROPOSAL_UPDATE_MARKET;
break;
case 'NewAsset':
minVoterBalance =
networkParams.governance_proposal_asset_minVoterBalance;
minVoterBalance = params.governance_proposal_asset_minVoterBalance;
proposalType = ProposalType.PROPOSAL_NEW_ASSET;
break;
case 'UpdateAsset':
minVoterBalance =
networkParams.governance_proposal_updateAsset_minVoterBalance;
params.governance_proposal_updateAsset_minVoterBalance;
proposalType = ProposalType.PROPOSAL_UPDATE_ASSET;
break;
case 'UpdateNetworkParameter':
minVoterBalance =
networkParams.governance_proposal_updateNetParam_minVoterBalance;
params.governance_proposal_updateNetParam_minVoterBalance;
proposalType = ProposalType.PROPOSAL_NETWORK_PARAMETER;
break;
case 'NewFreeform':
minVoterBalance =
networkParams.governance_proposal_freeform_minVoterBalance;
minVoterBalance = params.governance_proposal_freeform_minVoterBalance;
proposalType = ProposalType.PROPOSAL_FREEFORM;
break;
}
}
return (
<section data-testid="proposal">
<div className="flex items-center gap-1 mb-6">
<Icon name={'chevron-left'} />
<AsyncRenderer data={params} loading={loading} error={error}>
<section data-testid="proposal">
<div className="flex items-center gap-1">
<Icon name={'chevron-left'} />
{proposal.state === ProposalState.STATE_REJECTED ? (
<div data-testid="rejected-proposals-link">
<Link className="underline" to={Routes.PROPOSALS_REJECTED}>
{t('RejectedProposals')}
</Link>
</div>
) : (
<div data-testid="all-proposals-link">
<Link className="underline" to={Routes.PROPOSALS}>
{t('AllProposals')}
</Link>
</div>
)}
</div>
<ProposalHeader
proposal={proposal}
isListItem={false}
networkParams={networkParams}
/>
<div id="details">
<div className="my-10">
<ProposalChangeTable proposal={proposal} />
{proposal.state === ProposalState.STATE_REJECTED ? (
<div data-testid="rejected-proposals-link">
<Link className="underline" to={Routes.PROPOSALS_REJECTED}>
{t('RejectedProposals')}
</Link>
</div>
) : (
<div data-testid="all-proposals-link">
<Link className="underline" to={Routes.PROPOSALS}>
{t('AllProposals')}
</Link>
</div>
)}
</div>
<ProposalHeader proposal={proposal} isListItem={false} />
{proposal.terms.change.__typename === 'NewAsset' &&
proposal.terms.change.source.__typename === 'ERC20' &&
proposal.id ? (
<ListAsset
assetId={proposal.id}
withdrawalThreshold={proposal.terms.change.source.withdrawThreshold}
lifetimeLimit={proposal.terms.change.source.lifetimeLimit}
/>
) : null}
<div id="details">
<div className="my-10">
<ProposalChangeTable proposal={proposal} />
</div>
<div className="mb-4">
<ProposalDescription description={proposal.rationale.description} />
</div>
{proposal.terms.change.__typename === 'NewAsset' &&
proposal.terms.change.source.__typename === 'ERC20' &&
proposal.id ? (
<ListAsset
assetId={proposal.id}
withdrawalThreshold={
proposal.terms.change.source.withdrawThreshold
}
lifetimeLimit={proposal.terms.change.source.lifetimeLimit}
/>
) : null}
{newMarketData && (
<div className="mb-4">
<ProposalMarketData marketData={newMarketData} />
<ProposalDescription description={proposal.rationale.description} />
</div>
)}
{(proposal.terms.change.__typename === 'NewAsset' ||
proposal.terms.change.__typename === 'UpdateAsset') &&
asset && (
{newMarketData && (
<div className="mb-4">
<ProposalAssetDetails asset={asset} />
<ProposalMarketData marketData={newMarketData} />
</div>
)}
<div className="mb-6">
<ProposalJson proposal={restData?.data?.proposal} />
</div>
</div>
{(proposal.terms.change.__typename === 'NewAsset' ||
proposal.terms.change.__typename === 'UpdateAsset') &&
asset && (
<div className="mb-4">
<ProposalAssetDetails asset={asset} />
</div>
)}
<div id="voting">
<div className="mb-10">
<RoundedWrapper paddingBottom={true}>
<VoteDetails
<div className="mb-6">
<ProposalJson proposal={restData?.data?.proposal} />
</div>
</div>
<div id="voting">
<div className="mb-10">
<RoundedWrapper paddingBottom={true}>
<VoteDetails
proposal={proposal}
proposalType={proposalType}
minVoterBalance={minVoterBalance}
spamProtectionMinTokens={
params?.spam_protection_voting_min_tokens
}
/>
</RoundedWrapper>
</div>
<div className="mb-4">
<ProposalVotesTable
proposal={proposal}
proposalType={proposalType}
minVoterBalance={minVoterBalance}
spamProtectionMinTokens={
networkParams?.spam_protection_voting_min_tokens
}
/>
</RoundedWrapper>
</div>
</div>
<div className="mb-4">
<ProposalVotesTable proposal={proposal} proposalType={proposalType} />
</div>
</div>
</section>
</section>
</AsyncRenderer>
);
};
@@ -5,7 +5,11 @@ import type { MockedResponse } from '@apollo/client/testing';
import { MockedProvider } from '@apollo/client/testing';
import { render, screen } from '@testing-library/react';
import { format } from 'date-fns';
import { ProposalRejectionReason, ProposalState } from '@vegaprotocol/types';
import {
ProposalRejectionReason,
ProposalState,
VoteValue,
} from '@vegaprotocol/types';
import {
generateNoVotes,
generateProposal,
@@ -14,6 +18,7 @@ import {
import { ProposalsListItemDetails } from './proposals-list-item-details';
import { DATE_FORMAT_DETAILED } from '../../../../lib/date-formats';
import {
mockPubkey,
mockWalletContext,
networkParamsQueryMock,
fiveMinutes,
@@ -23,6 +28,39 @@ import {
nextWeek,
} from '../../test-helpers/mocks';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import { UserVoteDocument } from '../vote-details/__generated__/Vote';
import faker from 'faker';
const createUserVoteQueryMock = (
proposalId: string | undefined | null,
value: VoteValue
) => ({
request: {
query: UserVoteDocument,
variables: {
partyId: mockPubkey.publicKey,
},
},
result: {
data: {
party: {
votesConnection: {
edges: [
{
node: {
proposalId,
vote: {
value,
datetime: faker.date.past().toISOString(),
},
},
},
],
},
},
},
},
});
const renderComponent = (
proposal: ProposalQuery['proposal'],
@@ -93,7 +131,7 @@ describe('Proposals list item details', () => {
);
});
it('Renders proposal state: Update market proposal - Currently expected to pass by LP vote', () => {
it('Renders proposal state: Update market proposal - set to pass by LP vote', () => {
renderComponent(
generateProposal({
state: ProposalState.STATE_OPEN,
@@ -115,11 +153,11 @@ describe('Proposals list item details', () => {
})
);
expect(screen.getByTestId('vote-status')).toHaveTextContent(
'Currently expected to pass by LP vote'
'Set to pass by LP vote'
);
});
it('Renders proposal state: Update market proposal - Currently expected to pass by token vote', () => {
it('Renders proposal state: Update market proposal - set to pass by token vote', () => {
renderComponent(
generateProposal({
state: ProposalState.STATE_OPEN,
@@ -141,11 +179,11 @@ describe('Proposals list item details', () => {
})
);
expect(screen.getByTestId('vote-status')).toHaveTextContent(
'Currently expected to pass by token vote'
'Set to pass by token vote'
);
});
it('Renders proposal state: Update market proposal - Currently expected to fail', () => {
it('Renders proposal state: Update market proposal - set to fail', () => {
renderComponent(
generateProposal({
state: ProposalState.STATE_OPEN,
@@ -166,9 +204,7 @@ describe('Proposals list item details', () => {
},
})
);
expect(screen.getByTestId('vote-status')).toHaveTextContent(
'Currently expected to fail'
);
expect(screen.getByTestId('vote-status')).toHaveTextContent('Set to fail');
});
it('Renders proposal state: Open - 5 minutes left to vote', () => {
@@ -213,6 +249,52 @@ describe('Proposals list item details', () => {
);
});
it('Renders proposal state: Open - user voted for', async () => {
const proposal = generateProposal({
state: ProposalState.STATE_OPEN,
terms: {
closingDatetime: nextWeek.toString(),
},
});
renderComponent(proposal, [
networkParamsQueryMock,
createUserVoteQueryMock(proposal?.id, VoteValue.VALUE_YES),
]);
expect(await screen.findByText('You voted For')).toBeInTheDocument();
});
it('Renders proposal state: Open - user voted against', async () => {
const proposal = generateProposal({
state: ProposalState.STATE_OPEN,
terms: {
closingDatetime: nextWeek.toString(),
},
});
renderComponent(proposal, [
networkParamsQueryMock,
createUserVoteQueryMock(proposal?.id, VoteValue.VALUE_NO),
]);
expect(await screen.findByText('You voted Against')).toBeInTheDocument();
});
it('Renders proposal state: Open - participation not reached', () => {
renderComponent(
generateProposal({
state: ProposalState.STATE_OPEN,
terms: {
enactmentDatetime: nextWeek.toString(),
},
votes: {
no: generateNoVotes(0),
yes: generateYesVotes(0),
},
})
);
expect(screen.getByTestId('vote-status')).toHaveTextContent(
'Participation not reached'
);
});
it('Renders proposal state: Open - majority not reached', () => {
renderComponent(
generateProposal({
@@ -227,7 +309,7 @@ describe('Proposals list item details', () => {
})
);
expect(screen.getByTestId('vote-status')).toHaveTextContent(
'Currently expected to fail'
'Majority not reached'
);
});
@@ -245,8 +327,42 @@ describe('Proposals list item details', () => {
},
})
);
expect(screen.getByTestId('vote-status')).toHaveTextContent('Set to pass');
});
it('Renders proposal state: Declined - participation not reached', () => {
renderComponent(
generateProposal({
state: ProposalState.STATE_DECLINED,
terms: {
enactmentDatetime: lastWeek.toString(),
},
votes: {
no: generateNoVotes(0),
yes: generateYesVotes(0),
},
})
);
expect(screen.getByTestId('vote-status')).toHaveTextContent(
'Currently expected to pass'
'Participation not reached'
);
});
it('Renders proposal state: Declined - majority not reached', () => {
renderComponent(
generateProposal({
state: ProposalState.STATE_DECLINED,
terms: {
enactmentDatetime: lastWeek.toString(),
},
votes: {
no: generateNoVotes(1, 1000000000000000000),
yes: generateYesVotes(1, 1000000000000000000),
},
})
);
expect(screen.getByTestId('vote-status')).toHaveTextContent(
'Majority not reached'
);
});
@@ -1,6 +1,9 @@
import { Link } from 'react-router-dom';
import { Button, VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
import { differenceInHours, format, formatDistanceToNowStrict } from 'date-fns';
import { Button } from '@vegaprotocol/ui-toolkit';
import { useVoteInformation } from '../../hooks';
import { useUserVote } from '../vote-details/use-user-vote';
import { StatusPass } from '../current-proposal-status/current-proposal-status';
import { format, formatDistanceToNowStrict } from 'date-fns';
import { useTranslation } from 'react-i18next';
import { DATE_FORMAT_DETAILED } from '../../../../lib/date-formats';
import type { ReactNode } from 'react';
@@ -11,100 +14,138 @@ import {
import Routes from '../../../routes';
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import { useVoteInformation } from '../../hooks';
const MajorityNotReached = () => {
const { t } = useTranslation();
return (
<>
{t('Majority')} {t('not reached')}
</>
);
};
const ParticipationNotReached = () => {
const { t } = useTranslation();
return (
<>
{t('Participation')} {t('not reached')}
</>
);
};
export const ProposalsListItemDetails = ({
proposal,
}: {
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
}) => {
const { t } = useTranslation();
const state = proposal?.state;
const { willPassByTokenVote, willPassByLPVote } = useVoteInformation({
const {
willPassByTokenVote,
willPassByLPVote,
majorityMet,
participationMet,
} = useVoteInformation({
proposal,
});
const { t } = useTranslation();
const { voteState } = useUserVote(proposal?.id);
const isUpdateMarket = proposal?.terms.change.__typename === 'UpdateMarket';
const updateMarketWillPass = willPassByTokenVote || willPassByLPVote;
const updateMarketVotePassMethod = willPassByTokenVote
? t('byTokenVote')
: t('byLPVote');
const nowToEnactmentInHours = differenceInHours(
new Date(proposal?.terms.closingDatetime),
new Date()
);
const isUpdateMarket = proposal?.terms.change.__typename === 'UpdateMarket';
let voteDetails: ReactNode;
let voteStatus: ReactNode;
switch (state) {
case ProposalState.STATE_ENACTED: {
voteDetails =
proposal?.terms.enactmentDatetime &&
t('enactedOn{{date}}', {
enactmentDate:
proposal?.terms.enactmentDatetime &&
format(
new Date(proposal?.terms.enactmentDatetime),
DATE_FORMAT_DETAILED
),
});
voteDetails = proposal?.terms.enactmentDatetime && (
<>
{format(
new Date(proposal?.terms.enactmentDatetime),
DATE_FORMAT_DETAILED
)}
</>
);
break;
}
case ProposalState.STATE_PASSED: {
voteDetails =
proposal?.terms.change.__typename !== 'NewFreeform' &&
t('enactsOn{{date}}', {
enactmentDate:
proposal?.terms.enactmentDatetime &&
voteDetails = proposal?.terms.change.__typename !== 'NewFreeform' && (
<>
{t('toEnactOn')}{' '}
{proposal?.terms.enactmentDatetime &&
format(
new Date(proposal.terms.enactmentDatetime),
DATE_FORMAT_DETAILED
),
});
)}
</>
);
break;
}
case ProposalState.STATE_WAITING_FOR_NODE_VOTE: {
voteDetails =
proposal?.terms.change.__typename !== 'NewFreeform' &&
t('enactsOn{{date}}', {
enactmentDate:
proposal?.terms.enactmentDatetime &&
voteDetails = proposal?.terms.change.__typename !== 'NewFreeform' && (
<>
{t('toEnactOn')}{' '}
{proposal?.terms.enactmentDatetime &&
format(
new Date(proposal.terms.enactmentDatetime),
DATE_FORMAT_DETAILED
),
});
)}
</>
);
break;
}
case ProposalState.STATE_OPEN: {
voteDetails = (
<span className={nowToEnactmentInHours < 6 ? 'text-vega-pink' : ''}>
{formatDistanceToNowStrict(new Date(proposal?.terms.closingDatetime))}{' '}
{t('left to vote')}
</span>
);
voteDetails = (voteState === 'Yes' && (
<>
{t('youVoted')} {t('voteState_Yes')}
</>
)) ||
(voteState === 'No' && (
<>
{t('youVoted')} {t('voteState_No')}
</>
)) || (
<>
{formatDistanceToNowStrict(
new Date(proposal?.terms.closingDatetime)
)}{' '}
{t('left to vote')}
</>
);
voteStatus =
(isUpdateMarket &&
(updateMarketWillPass ? (
<>
{t('currentlySetTo')} {t('pass')} {updateMarketVotePassMethod}
{t('Set to')}{' '}
<StatusPass>
{t('pass')} {updateMarketVotePassMethod}
</StatusPass>
</>
) : (
<>
{t('currentlySetTo')} {t('fail')}
{t('Set to')} {t('fail')}
</>
))) ||
(!participationMet && <ParticipationNotReached />) ||
(!majorityMet && <MajorityNotReached />) ||
(willPassByTokenVote ? (
<>
{t('currentlySetTo')} {t('pass')}
{t('Set to')} {t('pass')}
</>
) : (
<>
{t('currentlySetTo')} {t('fail')}
{t('Set to')} {t('fail')}
</>
));
break;
}
case ProposalState.STATE_DECLINED: {
voteStatus =
(!participationMet && <ParticipationNotReached />) ||
(!majorityMet && <MajorityNotReached />);
break;
}
case ProposalState.STATE_REJECTED: {
voteStatus = proposal?.rejectionReason && (
<>{t(ProposalRejectionReasonMapping[proposal.rejectionReason])}</>
@@ -114,21 +155,29 @@ export const ProposalsListItemDetails = ({
}
return (
<div className="mt-4 items-start text-sm">
<div className="text-vega-green">
<VegaIcon size={16} name={VegaIconNames.VOTE} />
<VegaIcon size={16} name={VegaIconNames.TICKET} />
</div>
<div className="flex items-center gap-2 text-vega-light-300 mb-2">
{voteDetails && <span data-testid="vote-details">{voteDetails}</span>}
{voteDetails && voteStatus && <span>&middot;</span>}
{voteStatus && <span data-testid="vote-status">{voteStatus}</span>}
</div>
<div className="grid grid-cols-[1fr_auto] mt-4 items-start gap-2 text-sm">
{voteDetails && (
<div
className="col-start-1 row-start-2 text-vega-light-300"
data-testid="vote-details"
>
{voteDetails}
</div>
)}
{voteStatus && (
<div
className="col-start-2 row-start-1 justify-self-end"
data-testid="vote-status"
>
{voteStatus}
</div>
)}
{proposal?.id && (
<Link to={`${Routes.PROPOSALS}/${proposal.id}`}>
<Button data-testid="view-proposal-btn">{t('viewDetails')}</Button>
</Link>
<div className="col-start-2 row-start-2 justify-self-end">
<Link to={`${Routes.PROPOSALS}/${proposal.id}`}>
<Button data-testid="view-proposal-btn">{t('View')}</Button>
</Link>
</div>
)}
</div>
);
@@ -3,23 +3,18 @@ import { ProposalHeader } from '../proposal-detail-header/proposal-header';
import { ProposalsListItemDetails } from './proposals-list-item-details';
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import type { NetworkParamsResult } from '@vegaprotocol/network-parameters';
interface ProposalsListItemProps {
proposal?: ProposalFieldsFragment | ProposalQuery['proposal'] | null;
networkParams: Partial<NetworkParamsResult> | null;
}
export const ProposalsListItem = ({
proposal,
networkParams,
}: ProposalsListItemProps) => {
if (!proposal || !proposal.id || !networkParams) return null;
export const ProposalsListItem = ({ proposal }: ProposalsListItemProps) => {
if (!proposal || !proposal.id) return null;
return (
<li id={proposal.id} data-testid="proposals-list-item">
<RoundedWrapper paddingBottom={true} heightFull={true}>
<ProposalHeader proposal={proposal} networkParams={networkParams} />
<ProposalHeader proposal={proposal} />
<ProposalsListItemDetails proposal={proposal} />
</RoundedWrapper>
</li>
@@ -88,39 +88,31 @@ afterAll(() => {
jest.useRealTimers();
});
jest.mock('../vote-details/use-user-vote', () => ({
useUserVote: jest.fn().mockImplementation(() => ({ voteState: 'NotCast' })),
}));
describe('Proposals list', () => {
it('Render a page title and link to the make proposal form', async () => {
it('Render a page title and link to the make proposal form', () => {
render(renderComponent([]));
await screen.findByTestId('proposals-list');
expect(screen.getByText('Proposals')).toBeInTheDocument();
expect(screen.getByTestId('new-proposal-link')).toBeInTheDocument();
});
it('Will hide filter if no proposals', async () => {
it('Will hide filter if no proposals', () => {
render(renderComponent([]));
await screen.findByTestId('proposals-list');
expect(
screen.queryByTestId('proposals-list-filter')
).not.toBeInTheDocument();
});
it('Will show filter if there are proposals', async () => {
it('Will show filter if there are proposals', () => {
render(renderComponent([enactedProposalClosedLastWeek]));
await screen.findByTestId('proposals-list');
expect(screen.queryByTestId('proposals-list-filter')).toBeInTheDocument();
});
it('Will render a link to rejected proposals', async () => {
it('Will render a link to rejected proposals', () => {
render(renderComponent([]));
await screen.findByTestId('proposals-list');
expect(screen.getByText('See rejected proposals')).toBeInTheDocument();
});
it('Places proposals correctly in open or closed lists', async () => {
it('Places proposals correctly in open or closed lists', () => {
render(
renderComponent([
openProposalClosesNextWeek,
@@ -129,7 +121,6 @@ describe('Proposals list', () => {
failedProposalClosedLastMonth,
])
);
await screen.findByTestId('proposals-list');
const openProposals = within(screen.getByTestId('open-proposals'));
const closedProposals = within(screen.getByTestId('closed-proposals'));
expect(openProposals.getAllByTestId('proposals-list-item').length).toBe(2);
@@ -138,47 +129,42 @@ describe('Proposals list', () => {
);
});
it('Displays info on no proposals', async () => {
it('Displays info on no proposals', () => {
render(renderComponent([]));
await screen.findByTestId('proposals-list');
expect(screen.queryByTestId('open-proposals')).not.toBeInTheDocument();
expect(screen.getByTestId('no-open-proposals')).toBeInTheDocument();
expect(screen.queryByTestId('closed-proposals')).not.toBeInTheDocument();
expect(screen.getByTestId('no-closed-proposals')).toBeInTheDocument();
});
it('Displays info on no open proposals if only closed are present', async () => {
it('Displays info on no open proposals if only closed are present', () => {
render(renderComponent([enactedProposalClosedLastWeek]));
await screen.findByTestId('proposals-list');
expect(screen.queryByTestId('open-proposals')).not.toBeInTheDocument();
expect(screen.getByTestId('no-open-proposals')).toBeInTheDocument();
expect(screen.getByTestId('closed-proposals')).toBeInTheDocument();
expect(screen.queryByTestId('no-closed-proposals')).not.toBeInTheDocument();
});
it('Displays info on no closed proposals if only open are present', async () => {
it('Displays info on no closed proposals if only open are present', () => {
render(renderComponent([openProposalClosesNextWeek]));
await screen.findByTestId('proposals-list');
expect(screen.getByTestId('open-proposals')).toBeInTheDocument();
expect(screen.queryByTestId('no-open-proposals')).not.toBeInTheDocument();
expect(screen.queryByTestId('closed-proposals')).not.toBeInTheDocument();
expect(screen.getByTestId('no-closed-proposals')).toBeInTheDocument();
});
it('Opens filter form when button is clicked', async () => {
it('Opens filter form when button is clicked', () => {
render(
renderComponent([openProposalClosesNextMonth, openProposalClosesNextWeek])
);
await screen.findByTestId('proposals-list');
fireEvent.click(screen.getByTestId('proposal-filter-toggle'));
expect(screen.getByTestId('proposals-list-filter')).toBeInTheDocument();
});
it('Filters list by text - party id', async () => {
it('Filters list by text - party id', () => {
render(
renderComponent([openProposalClosesNextMonth, openProposalClosesNextWeek])
);
await screen.findByTestId('proposals-list');
fireEvent.click(screen.getByTestId('proposal-filter-toggle'));
fireEvent.change(screen.getByTestId('filter-input'), {
target: { value: 'bvcx' },
@@ -188,11 +174,10 @@ describe('Proposals list', () => {
expect(container.querySelector('#proposal1')).not.toBeInTheDocument();
});
it('Filters list by text - proposal id', async () => {
it('Filters list by text - proposal id', () => {
render(
renderComponent([openProposalClosesNextMonth, openProposalClosesNextWeek])
);
await screen.findByTestId('proposals-list');
fireEvent.click(screen.getByTestId('proposal-filter-toggle'));
fireEvent.change(screen.getByTestId('filter-input'), {
target: { value: 'proposal1' },
@@ -202,11 +187,10 @@ describe('Proposals list', () => {
expect(container.querySelector('#proposal2')).not.toBeInTheDocument();
});
it('Filters list by text - check for substring matching', async () => {
it('Filters list by text - check for substring matching', () => {
render(
renderComponent([openProposalClosesNextMonth, openProposalClosesNextWeek])
);
await screen.findByTestId('proposals-list');
fireEvent.click(screen.getByTestId('proposal-filter-toggle'));
fireEvent.change(screen.getByTestId('filter-input'), {
target: { value: 'osal1' },
@@ -216,11 +200,10 @@ describe('Proposals list', () => {
expect(container.querySelector('#proposal2')).not.toBeInTheDocument();
});
it('When filter is used, clear button is visible', async () => {
it('When filter is used, clear button is visible', () => {
render(
renderComponent([openProposalClosesNextMonth, openProposalClosesNextWeek])
);
await screen.findByTestId('proposals-list');
fireEvent.click(screen.getByTestId('proposal-filter-toggle'));
fireEvent.change(screen.getByTestId('filter-input'), {
target: { value: 'test' },
@@ -228,11 +211,10 @@ describe('Proposals list', () => {
expect(screen.getByTestId('clear-filter')).toBeInTheDocument();
});
it('When clear filter button is used, input is cleared', async () => {
it('When clear filter button is used, input is cleared', () => {
render(
renderComponent([openProposalClosesNextMonth, openProposalClosesNextWeek])
);
await screen.findByTestId('proposals-list');
fireEvent.click(screen.getByTestId('proposal-filter-toggle'));
fireEvent.change(screen.getByTestId('filter-input'), {
target: { value: 'test' },
@@ -243,41 +225,37 @@ describe('Proposals list', () => {
);
});
it('Displays a toggle for closed proposals if there are both closed governance proposals and closed upgrade proposals', async () => {
it('Displays a toggle for closed proposals if there are both closed governance proposals and closed upgrade proposals', () => {
render(
renderComponent(
[enactedProposalClosedLastWeek],
[closedProtocolUpgradeProposal]
)
);
await screen.findByTestId('proposals-list');
expect(screen.getByTestId('toggle-closed-proposals')).toBeInTheDocument();
});
it('Does not display a toggle for closed proposals if there are only closed upgrade proposals', async () => {
it('Does not display a toggle for closed proposals if there are only closed upgrade proposals', () => {
render(renderComponent([], [closedProtocolUpgradeProposal]));
await screen.findByTestId('proposals-list');
expect(
screen.queryByTestId('toggle-closed-proposals')
).not.toBeInTheDocument();
});
it('Does not display a toggle for closed proposals if there are only closed governance proposals', async () => {
it('Does not display a toggle for closed proposals if there are only closed governance proposals', () => {
render(renderComponent([enactedProposalClosedLastWeek]));
await screen.findByTestId('proposals-list');
expect(
screen.queryByTestId('toggle-closed-proposals')
).not.toBeInTheDocument();
});
it('Does not display a toggle for closed proposals if the proposal filter is engaged', async () => {
it('Does not display a toggle for closed proposals if the proposal filter is engaged', () => {
render(
renderComponent(
[enactedProposalClosedLastWeek],
[closedProtocolUpgradeProposal]
)
);
await screen.findByTestId('proposal-filter-toggle');
fireEvent.click(screen.getByTestId('proposal-filter-toggle'));
fireEvent.change(screen.getByTestId('filter-input'), {
target: { value: 'test' },
@@ -287,7 +265,7 @@ describe('Proposals list', () => {
).not.toBeInTheDocument();
});
it('Displays closed governance proposals by default due to default for the toggle', async () => {
it('Displays closed governance proposals by default due to default for the toggle', () => {
render(
renderComponent(
[enactedProposalClosedLastWeek],
@@ -295,19 +273,17 @@ describe('Proposals list', () => {
)
);
expect(
await screen.findByTestId('closed-governance-proposals')
screen.getByTestId('closed-governance-proposals')
).toBeInTheDocument();
});
it('Displays closed upgrade proposals when the toggle is clicked', async () => {
it('Displays closed upgrade proposals when the toggle is clicked', () => {
render(
renderComponent(
[enactedProposalClosedLastWeek],
[closedProtocolUpgradeProposal]
)
);
await screen.findByTestId('toggle-closed-proposals');
fireEvent.click(screen.getByText('Network upgrades'));
expect(screen.getByTestId('closed-upgrade-proposals')).toBeInTheDocument();
});
@@ -8,7 +8,6 @@ import { ProtocolUpgradeProposalsListItem } from '../protocol-upgrade-proposals-
import { ProposalsListFilter } from '../proposals-list-filter';
import Routes from '../../../routes';
import {
AsyncRenderer,
Button,
Toggle,
VegaIcon,
@@ -20,10 +19,6 @@ import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
import type { ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
import { DocsLinks, ExternalLinks } from '@vegaprotocol/environment';
import {
NetworkParams,
useNetworkParams,
} from '@vegaprotocol/network-parameters';
interface ProposalsListProps {
proposals: Array<ProposalFieldsFragment | ProposalQuery['proposal']>;
@@ -75,20 +70,6 @@ export const ProposalsList = ({
lastBlockHeight,
}: ProposalsListProps) => {
const { t } = useTranslation();
const {
params: networkParams,
loading: networkParamsLoading,
error: networkParamsError,
} = useNetworkParams([
NetworkParams.governance_proposal_market_requiredMajority,
NetworkParams.governance_proposal_updateMarket_requiredMajority,
NetworkParams.governance_proposal_updateMarket_requiredMajorityLP,
NetworkParams.governance_proposal_asset_requiredMajority,
NetworkParams.governance_proposal_updateAsset_requiredMajority,
NetworkParams.governance_proposal_updateNetParam_requiredMajority,
NetworkParams.governance_proposal_freeform_requiredMajority,
]);
const [filterString, setFilterString] = useState('');
const [closedProposalsView, setClosedProposalsView] =
useState<ClosedProposalsViewOptions>(
@@ -153,181 +134,166 @@ export const ProposalsList = ({
p?.party?.id?.toString().includes(filterString);
return (
<AsyncRenderer
loading={networkParamsLoading}
error={networkParamsError}
data={networkParams}
>
<div data-testid="proposals-list">
<div className="grid xs:grid-cols-2 items-center">
<Heading
centerContent={false}
marginBottom={false}
title={t('pageTitleProposals')}
/>
<>
<div className="grid xs:grid-cols-2 items-center">
<Heading
centerContent={false}
marginBottom={false}
title={t('pageTitleProposals')}
/>
{DocsLinks && (
<div
className="xs:justify-self-end"
data-testid="new-proposal-link"
>
<ExternalLink href={DocsLinks.PROPOSALS_GUIDE}>
<Button variant="primary" size="sm">
<div className="flex items-center gap-1">
{t('NewProposal')}
<VegaIcon name={VegaIconNames.OPEN_EXTERNAL} size={13} />
</div>
</Button>
</ExternalLink>
</div>
)}
</div>
<p className="mb-8">
{t(
`The Vega network is governed by the community. View active proposals, vote on them or propose changes to the network. Network upgrades are proposed and approved by validators.`
)}{' '}
<ExternalLink
data-testid="proposal-documentation-link"
href={ExternalLinks.GOVERNANCE_PAGE}
className="text-white"
>
{t(`Find out more about Vega governance`)}
</ExternalLink>
</p>
{proposals.length > 0 && (
<ProposalsListFilter
filterString={filterString}
setFilterString={(value) => {
setFilterString(value);
if (value.length > 0) {
// If the filter is engaged, ensure the user is viewing governance proposals,
// as network upgrades do not have IDs to filter by and will be excluded.
setClosedProposalsView(
ClosedProposalsViewOptions.NetworkGovernance
);
}
}}
/>
{DocsLinks && (
<div className="xs:justify-self-end" data-testid="new-proposal-link">
<ExternalLink href={DocsLinks.PROPOSALS_GUIDE}>
<Button variant="primary" size="sm">
<div className="flex items-center gap-1">
{t('NewProposal')}
<VegaIcon name={VegaIconNames.OPEN_EXTERNAL} size={13} />
</div>
</Button>
</ExternalLink>
</div>
)}
</div>
<section className="-mx-4 p-4 mb-8 bg-vega-dark-100">
<SubHeading title={t('openProposals')} />
<p className="mb-8">
{t(
`The Vega network is governed by the community. View active proposals, vote on them or propose changes to the network. Network upgrades are proposed and approved by validators.`
)}{' '}
<ExternalLink
data-testid="proposal-documentation-link"
href={ExternalLinks.GOVERNANCE_PAGE}
className="text-white"
>
{t(`Find out more about Vega governance`)}
</ExternalLink>
</p>
{sortedProposals.open.length > 0 ||
sortedProtocolUpgradeProposals.open.length > 0 ? (
<ul data-testid="open-proposals">
{filterString.length < 1 &&
sortedProtocolUpgradeProposals.open.map((proposal) => (
<ProtocolUpgradeProposalsListItem
key={proposal.upgradeBlockHeight}
proposal={proposal}
/>
))}
{proposals.length > 0 && (
<ProposalsListFilter
filterString={filterString}
setFilterString={(value) => {
setFilterString(value);
if (value.length > 0) {
// If the filter is engaged, ensure the user is viewing governance proposals,
// as network upgrades do not have IDs to filter by and will be excluded.
setClosedProposalsView(
ClosedProposalsViewOptions.NetworkGovernance
);
}
}}
/>
)}
{sortedProposals.open.filter(filterPredicate).map((proposal) => (
<ProposalsListItem
key={proposal?.id}
<section className="-mx-4 p-4 mb-8 bg-vega-dark-100">
<SubHeading title={t('openProposals')} />
{sortedProposals.open.length > 0 ||
sortedProtocolUpgradeProposals.open.length > 0 ? (
<ul data-testid="open-proposals">
{filterString.length < 1 &&
sortedProtocolUpgradeProposals.open.map((proposal) => (
<ProtocolUpgradeProposalsListItem
key={proposal.upgradeBlockHeight}
proposal={proposal}
networkParams={networkParams}
/>
))}
</ul>
) : (
<p className="mb-0" data-testid="no-open-proposals">
{t('noOpenProposals')}
</p>
)}
</section>
<section className="relative">
<SubHeading title={t('closedProposals')} />
{sortedProposals.closed.length > 0 ||
sortedProtocolUpgradeProposals.closed.length > 0 ? (
<>
{
// We need both the closed proposals and closed protocol upgrade
// proposals to be present for there to be a toggle. It also gets
// hidden if the user has filtered the list, as the upgrade proposals
// do not have the necessary fields for filtering.
sortedProposals.closed.length > 0 &&
sortedProtocolUpgradeProposals.closed.length > 0 &&
filterString.length < 1 && (
<div
className="grid w-full justify-end xl:-mt-12 pb-6"
data-testid="toggle-closed-proposals"
>
<div className="w-[440px]">
<Toggle
name="closed-proposals-toggle"
toggles={[
{
label: t(
ClosedProposalsViewOptions.NetworkGovernance
),
value:
ClosedProposalsViewOptions.NetworkGovernance,
},
{
label: t(
ClosedProposalsViewOptions.NetworkUpgrades
),
value: ClosedProposalsViewOptions.NetworkUpgrades,
},
]}
checkedValue={closedProposalsView}
onChange={(e) =>
setClosedProposalsView(
e.target.value as ClosedProposalsViewOptions
)
}
/>
</div>
{sortedProposals.open.filter(filterPredicate).map((proposal) => (
<ProposalsListItem key={proposal?.id} proposal={proposal} />
))}
</ul>
) : (
<p className="mb-0" data-testid="no-open-proposals">
{t('noOpenProposals')}
</p>
)}
</section>
<section className="relative">
<SubHeading title={t('closedProposals')} />
{sortedProposals.closed.length > 0 ||
sortedProtocolUpgradeProposals.closed.length > 0 ? (
<>
{
// We need both the closed proposals and closed protocol upgrade
// proposals to be present for there to be a toggle. It also gets
// hidden if the user has filtered the list, as the upgrade proposals
// do not have the necessary fields for filtering.
sortedProposals.closed.length > 0 &&
sortedProtocolUpgradeProposals.closed.length > 0 &&
filterString.length < 1 && (
<div
className="grid w-full justify-end xl:-mt-12 pb-6"
data-testid="toggle-closed-proposals"
>
<div className="w-[440px]">
<Toggle
name="closed-proposals-toggle"
toggles={[
{
label: t(
ClosedProposalsViewOptions.NetworkGovernance
),
value: ClosedProposalsViewOptions.NetworkGovernance,
},
{
label: t(
ClosedProposalsViewOptions.NetworkUpgrades
),
value: ClosedProposalsViewOptions.NetworkUpgrades,
},
]}
checkedValue={closedProposalsView}
onChange={(e) =>
setClosedProposalsView(
e.target.value as ClosedProposalsViewOptions
)
}
/>
</div>
)
}
</div>
)
}
<ul data-testid="closed-proposals">
{closedProposalsView ===
ClosedProposalsViewOptions.NetworkUpgrades && (
<div data-testid="closed-upgrade-proposals">
{sortedProtocolUpgradeProposals.closed.map((proposal) => (
<ProtocolUpgradeProposalsListItem
key={proposal.upgradeBlockHeight}
<ul data-testid="closed-proposals">
{closedProposalsView ===
ClosedProposalsViewOptions.NetworkUpgrades && (
<div data-testid="closed-upgrade-proposals">
{sortedProtocolUpgradeProposals.closed.map((proposal) => (
<ProtocolUpgradeProposalsListItem
key={proposal.upgradeBlockHeight}
proposal={proposal}
/>
))}
</div>
)}
{closedProposalsView ===
ClosedProposalsViewOptions.NetworkGovernance && (
<div data-testid="closed-governance-proposals">
{sortedProposals.closed
.filter(filterPredicate)
.map((proposal) => (
<ProposalsListItem
key={proposal?.id}
proposal={proposal}
/>
))}
</div>
)}
</div>
)}
</ul>
</>
) : (
<p className="mb-0" data-testid="no-closed-proposals">
{t('noClosedProposals')}
</p>
)}
</section>
{closedProposalsView ===
ClosedProposalsViewOptions.NetworkGovernance && (
<div data-testid="closed-governance-proposals">
{sortedProposals.closed
.filter(filterPredicate)
.map((proposal) => (
<ProposalsListItem
key={proposal?.id}
proposal={proposal}
networkParams={networkParams}
/>
))}
</div>
)}
</ul>
</>
) : (
<p className="mb-0" data-testid="no-closed-proposals">
{t('noClosedProposals')}
</p>
)}
</section>
<Link className="underline" to={Routes.PROPOSALS_REJECTED}>
{t('seeRejectedProposals')}
</Link>
</div>
</AsyncRenderer>
<Link className="underline" to={Routes.PROPOSALS_REJECTED}>
{t('seeRejectedProposals')}
</Link>
</>
);
};
@@ -5,7 +5,7 @@ import { BrowserRouter as Router } from 'react-router-dom';
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
import { RejectedProposalsList } from './rejected-proposals-list';
import { ProposalState } from '@vegaprotocol/types';
import { render, screen, waitFor, within } from '@testing-library/react';
import { render, screen, within } from '@testing-library/react';
import {
mockWalletContext,
networkParamsQueryMock,
@@ -55,38 +55,24 @@ afterAll(() => {
jest.useRealTimers();
});
jest.mock('../vote-details/use-user-vote', () => ({
useUserVote: jest.fn().mockImplementation(() => ({ voteState: 'NotCast' })),
}));
describe('Rejected proposals list', () => {
it('Renders a list of proposals', async () => {
it('Renders a list of proposals', () => {
render(
renderComponent([
rejectedProposalClosedLastMonth,
rejectedProposalClosesNextWeek,
])
);
await waitFor(() => {
const rejectedProposals = within(
screen.getByTestId('rejected-proposals')
);
const rejectedProposalsItems = rejectedProposals.getAllByTestId(
'proposals-list-item'
);
expect(rejectedProposalsItems).toHaveLength(2);
});
const rejectedProposals = within(screen.getByTestId('rejected-proposals'));
const rejectedProposalsItems = rejectedProposals.getAllByTestId(
'proposals-list-item'
);
expect(rejectedProposalsItems).toHaveLength(2);
});
it('Displays text when there are no proposals', async () => {
it('Displays text when there are no proposals', () => {
render(renderComponent([]));
await waitFor(() => {
expect(screen.getByTestId('no-rejected-proposals')).toBeInTheDocument();
expect(
screen.queryByTestId('rejected-proposals')
).not.toBeInTheDocument();
});
expect(screen.queryByTestId('rejected-proposals')).not.toBeInTheDocument();
expect(screen.getByTestId('no-rejected-proposals')).toBeInTheDocument();
});
});
@@ -1,15 +1,10 @@
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import { Heading } from '../../../../components/heading';
import { ProposalsListItem } from '../proposals-list-item';
import { ProposalsListFilter } from '../proposals-list-filter';
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import {
NetworkParams,
useNetworkParams,
} from '@vegaprotocol/network-parameters';
interface ProposalsListProps {
proposals: Array<ProposalQuery['proposal'] | ProposalFieldsFragment>;
@@ -17,19 +12,6 @@ interface ProposalsListProps {
export const RejectedProposalsList = ({ proposals }: ProposalsListProps) => {
const { t } = useTranslation();
const {
params: networkParams,
loading: networkParamsLoading,
error: networkParamsError,
} = useNetworkParams([
NetworkParams.governance_proposal_market_requiredMajority,
NetworkParams.governance_proposal_updateMarket_requiredMajority,
NetworkParams.governance_proposal_updateMarket_requiredMajorityLP,
NetworkParams.governance_proposal_asset_requiredMajority,
NetworkParams.governance_proposal_updateAsset_requiredMajority,
NetworkParams.governance_proposal_updateNetParam_requiredMajority,
NetworkParams.governance_proposal_freeform_requiredMajority,
]);
const [filterString, setFilterString] = useState('');
const filterPredicate = (
@@ -39,11 +21,7 @@ export const RejectedProposalsList = ({ proposals }: ProposalsListProps) => {
p?.party?.id?.toString().includes(filterString);
return (
<AsyncRenderer
loading={networkParamsLoading}
error={networkParamsError}
data={networkParams}
>
<>
<Heading title={t('pageTitleRejectedProposals')} />
<ProposalsListFilter
filterString={filterString}
@@ -53,11 +31,7 @@ export const RejectedProposalsList = ({ proposals }: ProposalsListProps) => {
{proposals.length > 0 ? (
<ul data-testid="rejected-proposals">
{proposals.filter(filterPredicate).map((proposal) => (
<ProposalsListItem
key={proposal?.id}
proposal={proposal}
networkParams={networkParams}
/>
<ProposalsListItem key={proposal?.id} proposal={proposal} />
))}
</ul>
) : (
@@ -66,6 +40,6 @@ export const RejectedProposalsList = ({ proposals }: ProposalsListProps) => {
</p>
)}
</section>
</AsyncRenderer>
</>
);
};
@@ -4,20 +4,16 @@ import { render } from '@testing-library/react';
import { ProtocolUpgradeProposalStatus } from '@vegaprotocol/types';
import { ProtocolUpgradeProposalDetailInfo } from './protocol-upgrade-proposal-detail-info';
const PROPOSAL = {
vegaReleaseTag: 'v0.1.234',
status:
ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_PENDING,
upgradeBlockHeight: '12345',
approvers: [],
};
const renderComponent = (lastBlockHeight?: string, time?: string) =>
const renderComponent = () =>
render(
<ProtocolUpgradeProposalDetailInfo
proposal={PROPOSAL}
lastBlockHeight={lastBlockHeight}
time={time}
proposal={{
vegaReleaseTag: 'v0.1.234',
status:
ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_PENDING,
upgradeBlockHeight: '12345',
approvers: [],
}}
/>
);
@@ -47,28 +43,4 @@ describe('ProtocolUpgradeProposalDetailInfo', () => {
'v0.1.234'
);
});
it('should display estimated time if proposal is still pending', () => {
const { getByTestId } = renderComponent('12', 'time');
expect(getByTestId('protocol-upgrade-time-label')).toHaveTextContent(
'Estimated time to upgrade'
);
expect(getByTestId('protocol-upgrade-time')).toHaveTextContent('time');
});
it('should display upgraded at if proposal is done', () => {
const { getByTestId } = renderComponent('123456', 'time');
expect(getByTestId('protocol-upgrade-time-label')).toHaveTextContent(
'Upgraded at'
);
expect(getByTestId('protocol-upgrade-time')).toHaveTextContent('time');
});
it('should not display time if none provided', () => {
const { queryByTestId } = renderComponent('123456', undefined);
expect(
queryByTestId('protocol-upgrade-time-label')
).not.toBeInTheDocument();
expect(queryByTestId('protocol-upgrade-time')).not.toBeInTheDocument();
});
});
@@ -5,33 +5,19 @@ import {
RoundedWrapper,
} from '@vegaprotocol/ui-toolkit';
import { SubHeading } from '../../../../components/heading';
import { type ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
import * as Types from '@vegaprotocol/types';
import type { ReactNode } from 'react';
import type { ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
export interface ProtocolUpgradeProposalDetailInfoProps {
proposal: ProtocolUpgradeProposalFieldsFragment;
lastBlockHeight?: string;
time?: string | ReactNode;
}
export const ProtocolUpgradeProposalDetailInfo = ({
proposal,
lastBlockHeight,
time,
}: ProtocolUpgradeProposalDetailInfoProps) => {
const { t } = useTranslation();
const shouldShowUpgradeTime =
![
Types.ProtocolUpgradeProposalStatus
.PROTOCOL_UPGRADE_PROPOSAL_STATUS_REJECTED,
Types.ProtocolUpgradeProposalStatus
.PROTOCOL_UPGRADE_PROPOSAL_STATUS_UNSPECIFIED,
].includes(proposal.status) && time;
const pending = Number(proposal.upgradeBlockHeight) > Number(lastBlockHeight);
return (
<div className="mb-10">
<SubHeading title={t('proposal')} />
@@ -43,26 +29,13 @@ export const ProtocolUpgradeProposalDetailInfo = ({
<span data-testid="protocol-upgrade-block-height">
{proposal.upgradeBlockHeight}{' '}
{lastBlockHeight && (
<span className="text-vega-light-300 dark:text-vega-dark-300">
<>
({t('currently')} {lastBlockHeight})
</span>
</>
)}
</span>
</KeyValueTableRow>
{shouldShowUpgradeTime && (
<KeyValueTableRow>
<span
data-testid="protocol-upgrade-time-label"
className="uppercase"
>
{pending ? t('Estimated time to upgrade') : t('Upgraded at')}
</span>
<span data-testid="protocol-upgrade-time">{time}</span>
</KeyValueTableRow>
)}
<KeyValueTableRow>
<span className="uppercase">{t('state')}</span>
@@ -20,6 +20,43 @@ const renderComponent = (proposal: ProtocolUpgradeProposalFieldsFragment) =>
);
describe('ProtocolUpgradeProposalsListItem', () => {
it('renders the correct status icon for each proposal status', () => {
const statuses = [
{
status:
ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_REJECTED,
icon: 'protocol-upgrade-proposal-status-icon-rejected',
text: 'Declined by validators',
},
{
status:
ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_PENDING,
icon: 'protocol-upgrade-proposal-status-icon-pending',
text: 'Waiting for validator votes',
},
{
status:
ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_APPROVED,
icon: 'protocol-upgrade-proposal-status-icon-approved',
text: 'Approved by validators',
},
{
status:
ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_UNSPECIFIED,
icon: 'protocol-upgrade-proposal-status-icon-unspecified',
text: 'Unspecified',
},
];
statuses.forEach(({ status, icon, text }) => {
renderComponent({ ...proposal, status });
const statusIcon = screen.getByTestId(icon);
const textContent = screen.getByText(text);
expect(statusIcon).toBeInTheDocument();
expect(textContent).toBeInTheDocument();
});
});
it('renders the correct Vega release tag', () => {
renderComponent(proposal);
const releaseTag = screen.getByTestId(
@@ -1,10 +1,17 @@
import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
import { Button, RoundedWrapper } from '@vegaprotocol/ui-toolkit';
import {
Button,
Icon,
Lozenge,
RoundedWrapper,
} from '@vegaprotocol/ui-toolkit';
import { stripFullStops } from '@vegaprotocol/utils';
import { ProtocolUpgradeProposalStatus } from '@vegaprotocol/types';
import { SubHeading } from '../../../../components/heading';
import { ProposalInfoLabel } from '../proposal-info-label';
import Routes from '../../../routes';
import type { ReactNode } from 'react';
import type { ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
interface ProtocolProposalsListItemProps {
@@ -17,21 +24,45 @@ export const ProtocolUpgradeProposalsListItem = ({
const { t } = useTranslation();
if (!proposal || !proposal.upgradeBlockHeight) return null;
let proposalStatusIcon: ReactNode;
switch (proposal.status) {
case ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_REJECTED:
proposalStatusIcon = (
<span data-testid="protocol-upgrade-proposal-status-icon-rejected">
<Icon name={'cross'} />
</span>
);
break;
case ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_PENDING:
proposalStatusIcon = (
<span data-testid="protocol-upgrade-proposal-status-icon-pending">
<Icon name={'time'} />
</span>
);
break;
case ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_APPROVED:
proposalStatusIcon = (
<span data-testid="protocol-upgrade-proposal-status-icon-approved">
<Icon name={'tick'} />
</span>
);
break;
case ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_UNSPECIFIED:
proposalStatusIcon = (
<span data-testid="protocol-upgrade-proposal-status-icon-unspecified">
<Icon name={'disable'} />
</span>
);
break;
}
return (
<li
id={proposal.upgradeBlockHeight}
data-testid="protocol-upgrade-proposals-list-item"
>
<RoundedWrapper paddingBottom={true} heightFull={true}>
<div
data-testid="protocol-upgrade-proposal-type"
className="flex items-center gap-2 mb-4 text-sm"
>
<ProposalInfoLabel variant="highlight">
{t('networkUpgrade')}
</ProposalInfoLabel>
</div>
<div
data-testid="protocol-upgrade-proposal-title"
className="text-sm mb-2"
@@ -39,13 +70,30 @@ export const ProtocolUpgradeProposalsListItem = ({
<SubHeading title={`Vega release ${proposal.vegaReleaseTag}`} />
</div>
<div className="flex items-center gap-4 text-vega-light-200">
<div className="text-sm">
<div className="flex gap-2">
<div
data-testid="protocol-upgrade-proposal-type"
className="flex items-center gap-2 mb-4"
>
<ProposalInfoLabel variant="highlight">
{t('networkUpgrade')}
</ProposalInfoLabel>
</div>
<div data-testid="protocol-upgrade-proposal-status">
<ProposalInfoLabel>
{t(`${proposal.status}`)} {proposalStatusIcon}
</ProposalInfoLabel>
</div>
</div>
<div
data-testid="protocol-upgrade-proposal-release-tag"
className="mb-2"
>
<span>{t('vegaReleaseTag')}:</span>{' '}
<span>{proposal.vegaReleaseTag}</span>
<Lozenge>{proposal.vegaReleaseTag}</Lozenge>
</div>
<div
@@ -53,24 +101,21 @@ export const ProtocolUpgradeProposalsListItem = ({
className="mb-2"
>
<span>{t('upgradeBlockHeight')}:</span>{' '}
<span>{proposal.upgradeBlockHeight}</span>
<Lozenge>{proposal.upgradeBlockHeight}</Lozenge>
</div>
<div className="grid grid-cols-1 mt-3">
<div className="justify-self-end">
<Link
to={`${Routes.PROTOCOL_UPGRADES}/${stripFullStops(
proposal.vegaReleaseTag
)}`}
>
<Button data-testid="view-proposal-btn">{t('View')}</Button>
</Link>
</div>
</div>
</div>
<div
className="text-sm text-vega-light-300 mb-4"
data-testid="protocol-upgrade-proposal-status"
>
{t(`${proposal.status}`)}
</div>
<Link
to={`${Routes.PROTOCOL_UPGRADES}/${stripFullStops(
proposal.vegaReleaseTag
)}`}
>
<Button data-testid="view-proposal-btn">{t('viewDetails')}</Button>
</Link>
</RoundedWrapper>
</li>
);
@@ -24,8 +24,8 @@ interface VoteButtonsContainerProps {
voteDatetime: Date | null;
proposalId: string | null;
proposalState: ProposalState;
minVoterBalance: string | null | undefined;
spamProtectionMinTokens: string | null | undefined;
minVoterBalance: string | null;
spamProtectionMinTokens: string | null;
submit: (voteValue: VoteValue, proposalId: string | null) => Promise<void>;
dialog: (props: DialogProps) => JSX.Element;
className?: string;
@@ -17,8 +17,8 @@ import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
interface VoteDetailsProps {
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
minVoterBalance: string | null | undefined;
spamProtectionMinTokens: string | null | undefined;
minVoterBalance: string | null;
spamProtectionMinTokens: string | null;
proposalType: ProposalType | null;
}
@@ -10,33 +10,9 @@ import { ENV } from '../../../config';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { marketInfoWithDataProvider } from '@vegaprotocol/markets';
import { useAssetQuery } from '@vegaprotocol/assets';
import {
NetworkParams,
useNetworkParams,
} from '@vegaprotocol/network-parameters';
export const ProposalContainer = () => {
const params = useParams<{ proposalId: string }>();
const {
params: networkParams,
loading: networkParamsLoading,
error: networkParamsError,
} = useNetworkParams([
NetworkParams.governance_proposal_market_minVoterBalance,
NetworkParams.governance_proposal_updateMarket_minVoterBalance,
NetworkParams.governance_proposal_asset_minVoterBalance,
NetworkParams.governance_proposal_updateAsset_minVoterBalance,
NetworkParams.governance_proposal_updateNetParam_minVoterBalance,
NetworkParams.governance_proposal_freeform_minVoterBalance,
NetworkParams.spam_protection_voting_min_tokens,
NetworkParams.governance_proposal_market_requiredMajority,
NetworkParams.governance_proposal_updateMarket_requiredMajority,
NetworkParams.governance_proposal_updateMarket_requiredMajorityLP,
NetworkParams.governance_proposal_asset_requiredMajority,
NetworkParams.governance_proposal_updateAsset_requiredMajority,
NetworkParams.governance_proposal_updateNetParam_requiredMajority,
NetworkParams.governance_proposal_freeform_requiredMajority,
]);
const {
state: { data: restData },
} = useFetch(`${ENV.rest}governance?proposalId=${params.proposalId}`);
@@ -86,13 +62,10 @@ export const ProposalContainer = () => {
return (
<AsyncRenderer
loading={
loading || newMarketLoading || assetLoading || networkParamsLoading
}
error={error || newMarketError || assetError || networkParamsError}
loading={loading || newMarketLoading || assetLoading}
error={error || newMarketError || assetError}
data={{
...data,
...networkParams,
...(newMarketData ? { newMarketData } : {}),
...(assetData ? { assetData } : {}),
}}
@@ -100,7 +73,6 @@ export const ProposalContainer = () => {
{data?.proposal ? (
<Proposal
proposal={data.proposal}
networkParams={networkParams}
restData={restData}
newMarketData={newMarketData}
assetData={assetData}
@@ -1,23 +1,14 @@
import { useEffect, useMemo } from 'react';
import { useParams } from 'react-router-dom';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import {
convertToCountdownString,
formatDateWithLocalTimezone,
removePaginationWrapper,
stripFullStops,
} from '@vegaprotocol/utils';
import { removePaginationWrapper, stripFullStops } from '@vegaprotocol/utils';
import * as Schema from '@vegaprotocol/types';
import { ProtocolUpgradeProposal } from './protocol-upgrade-proposal';
import { ProposalNotFound } from '../components/proposal-not-found';
import { useNodesQuery } from '../../staking/home/__generated__/Nodes';
import { useRefreshAfterEpoch } from '../../../hooks/use-refresh-after-epoch';
import {
useProtocolUpgradeProposalsQuery,
useTimeToUpgrade,
} from '@vegaprotocol/proposals';
import { useBlockInfo } from '@vegaprotocol/tendermint';
import { useProtocolUpgradeProposalsQuery } from '@vegaprotocol/proposals';
export const ProtocolUpgradeProposalContainer = () => {
const params = useParams<{ proposalReleaseTag: string }>();
@@ -67,20 +58,6 @@ export const ProtocolUpgradeProposalContainer = () => {
);
}, [nodesData]);
const pending =
Number(protocolUpgradeProposal?.upgradeBlockHeight) >
Number(data?.lastBlockHeight);
const {
state: { data: blockInfo },
} = useBlockInfo(
Number(protocolUpgradeProposal?.upgradeBlockHeight),
!pending
);
const time = useTimeToUpgrade(
Number(protocolUpgradeProposal?.upgradeBlockHeight)
);
return (
<AsyncRenderer
loading={loading || nodesLoading}
@@ -92,19 +69,6 @@ export const ProtocolUpgradeProposalContainer = () => {
proposal={protocolUpgradeProposal}
lastBlockHeight={data?.lastBlockHeight}
consensusValidators={consensusValidators}
time={
pending && time ? (
convertToCountdownString(time, '0:00:00:00')
) : blockInfo?.result ? (
<span title={blockInfo.result.block.header.time}>
{formatDateWithLocalTimezone(
new Date(blockInfo.result.block.header.time)
)}
</span>
) : (
'-'
)
}
/>
) : (
<ProposalNotFound />
@@ -60,11 +60,6 @@ const mockConsensusValidators: NodesFragmentFragment[] = [
},
];
jest.mock('@vegaprotocol/environment', () => ({
useVegaRelease: jest.fn(),
useVegaReleases: jest.fn(),
}));
const renderComponent = () =>
render(
<ProtocolUpgradeProposal proposal={mockProposal} consensusValidators={[]} />
@@ -1,11 +1,10 @@
import { useMemo } from 'react';
import type { ReactNode } from 'react';
import { ProtocolUpgradeProposalDetailHeader } from '../components/protocol-upgrade-proposal-detail-header';
import { ProtocolUpdateProposalDetailApprovals } from '../components/protocol-upgrade-proposal-detail-approvals';
import { ProtocolUpgradeProposalDetailInfo } from '../components/protocol-upgrade-proposal-detail-info';
import { getNormalisedVotingPower } from '../../staking/shared';
import type { NodesFragmentFragment } from '../../staking/home/__generated__/Nodes';
import { type ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
import type { ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
import { useVegaRelease } from '@vegaprotocol/environment';
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
import { useTranslation } from 'react-i18next';
@@ -14,7 +13,6 @@ export interface ProtocolUpgradeProposalProps {
proposal: ProtocolUpgradeProposalFieldsFragment;
consensusValidators: NodesFragmentFragment[] | null;
lastBlockHeight?: string;
time?: string | ReactNode;
}
export const getConsensusApprovals = (
@@ -46,7 +44,6 @@ export const ProtocolUpgradeProposal = ({
proposal,
lastBlockHeight,
consensusValidators,
time,
}: ProtocolUpgradeProposalProps) => {
const { t } = useTranslation();
const releaseInfo = useVegaRelease(proposal.vegaReleaseTag);
@@ -76,7 +73,6 @@ export const ProtocolUpgradeProposal = ({
<ProtocolUpgradeProposalDetailInfo
proposal={proposal}
lastBlockHeight={lastBlockHeight}
time={time}
/>
{consensusValidators && consensusApprovals && (
@@ -2,10 +2,6 @@ import { NetworkParamsDocument } from '@vegaprotocol/network-parameters';
import type { MockedResponse } from '@apollo/client/testing';
import type { NetworkParamsQuery } from '@vegaprotocol/network-parameters';
import type { PubKey } from '@vegaprotocol/wallet';
import type { 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';
export const mockPubkey: PubKey = {
publicKey: '0x123',
@@ -53,16 +49,6 @@ export const networkParamsQueryMock: MockedResponse<NetworkParamsQuery> = {
},
};
export const mockNetworkParams = {
governance_proposal_asset_requiredMajority: '0.66',
governance_proposal_freeform_requiredMajority: '0.66',
governance_proposal_market_requiredMajority: '0.66',
governance_proposal_updateAsset_requiredMajority: '0.66',
governance_proposal_updateMarket_requiredMajority: '0.66',
governance_proposal_updateMarket_requiredMajorityLP: '0.66',
governance_proposal_updateNetParam_requiredMajority: '0.5',
};
const oneMinute = 1000 * 60;
const oneHour = oneMinute * 60;
const oneDay = oneHour * 24;
@@ -76,34 +62,3 @@ export const lastWeek = new Date(-oneWeek);
export const nextWeek = new Date(oneWeek);
export const lastMonth = new Date(-oneMonth);
export const nextMonth = new Date(oneMonth);
export const createUserVoteQueryMock = (
proposalId: string,
value: VoteValue
): MockedResponse<UserVoteQuery> => ({
request: {
query: UserVoteDocument,
variables: {
partyId: mockPubkey.publicKey,
},
},
result: {
data: {
party: {
votesConnection: {
edges: [
{
node: {
proposalId,
vote: {
value,
datetime: faker.date.past().toISOString(),
},
},
},
],
},
},
},
},
});
@@ -216,7 +216,7 @@ export const StakingForm = ({
text: t('associateVegaNow'),
action: () => navigate(Routes.ASSOCIATE),
className: 'py-1',
size: 'small',
size: 'sm',
}}
/>
)}
-4
View File
@@ -33,7 +33,3 @@ 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
+9 -11
View File
@@ -54,7 +54,7 @@ describe('capsule - without MultiSign', { tags: '@slow' }, () => {
it('can deposit', function () {
cy.visit('/#/portfolio');
cy.get('[data-testid="pathname-/portfolio"]').should('exist');
cy.get('main[data-testid="/portfolio"]').should('exist');
// 1001-DEPO-001
// 1001-DEPO-002
@@ -117,10 +117,10 @@ describe('capsule - without MultiSign', { tags: '@slow' }, () => {
it('can key to key transfers', function () {
// 1003-TRAN-023
// 1003-TRAN-006
cy.get('[data-testid="pathname-/portfolio"]').should('exist');
cy.get('main[data-testid="/portfolio"]').should('exist');
cy.getByTestId(collateralTab).click();
cy.getByTestId('open-transfer').click();
cy.getByTestId('open-transfer-dialog').click();
cy.getByTestId('transfer-form').should('be.visible');
cy.getByTestId('transfer-form').find('[name="toAddress"]').select(1);
cy.get('select option')
@@ -187,21 +187,19 @@ describe('capsule', { tags: '@slow', testIsolation: true }, () => {
// 0006-NETW-010
const market = this.market;
cy.visit(`/#/markets/${market.id}`);
cy.getByTestId('node-health-trigger').realHover();
cy.getByTestId('node-health')
.children()
.first()
.should('contain.text', 'Operational')
.next()
.should('contain.text', new URL(Cypress.env('VEGA_URL')).hostname)
.next()
.then(($el) => {
const blockHeight = parseInt($el.text());
// block height will increase over the course of the test run so best
// we can do here is check that its showing something sensible
expect(blockHeight).to.be.greaterThan(0);
});
cy.getByTestId('node-health')
.children()
.eq(1)
.should('contain.text', new URL(Cypress.env('VEGA_URL')).hostname);
});
it('can place and receive an order', function () {
@@ -344,7 +342,7 @@ describe('capsule', { tags: '@slow', testIsolation: true }, () => {
const ethWalletAddress = Cypress.env('ETHEREUM_WALLET_ADDRESS');
cy.visit('/#/portfolio');
cy.get('[data-testid="pathname-/portfolio"]').should('exist');
cy.get('main[data-testid="/portfolio"]').should('exist');
cy.getByTestId(toastCloseBtn, txTimeout).click();
cy.getByTestId('Withdrawals').click();
cy.getByTestId('withdraw-dialog-button').click();
@@ -431,7 +429,7 @@ describe('capsule', { tags: '@slow', testIsolation: true }, () => {
// 1001-DEPO-006
// 1001-DEPO-007
cy.visit('/#/portfolio');
cy.get('[data-testid="pathname-/portfolio"]').should('exist');
cy.get('main[data-testid="/portfolio"]').should('exist');
cy.getByTestId(toastCloseBtn, txTimeout).click();
cy.getByTestId(depositsTab).click();
cy.getByTestId('deposit-button').click();
@@ -453,7 +451,7 @@ describe('capsule', { tags: '@slow', testIsolation: true }, () => {
// 1002-WITH-007
cy.visit('/#/portfolio');
cy.get('[data-testid="pathname-/portfolio"]', txTimeout).should('exist');
cy.get('main[data-testid="/portfolio"]', txTimeout).should('exist');
cy.getByTestId(toastCloseBtn, txTimeout).click();
cy.getByTestId(depositsTab).click();
cy.getByTestId('deposit-button').click();
@@ -351,6 +351,12 @@ describe('Closed markets', { tags: '@smoke' }, () => {
)
);
// 6001-MARK-017
cy.get(rowSelector)
.first()
.find('[col-id="realisedPNL"]')
.should('have.text', '-');
// 6001-MARK-018
cy.get(rowSelector)
.first()
@@ -363,13 +369,6 @@ describe('Closed markets', { tags: '@smoke' }, () => {
.first()
.find('button svg')
.should('exist');
if (Cypress.env('NX_SUCCESSOR_MARKETS')) {
cy.get(rowSelector)
.find('[col-id="successorMarketID"]')
.first()
.should('have.text', ' - ');
}
});
// test market list for market in terminated state
@@ -17,11 +17,11 @@ describe('deposit form validation', { tags: '@smoke' }, () => {
cy.mockTradingPage();
cy.setVegaWallet();
cy.visit('/#/portfolio');
cy.get('[data-testid="pathname-/portfolio"]').should('exist');
cy.get('main[data-testid="/portfolio"]').should('exist');
cy.getByTestId('Deposits').click();
cy.getByTestId('deposit-button').click();
connectEthereumWallet('MetaMask');
cy.wait('@Assets');
connectEthereumWallet('MetaMask');
}
before(() => {
@@ -102,6 +102,8 @@ describe('deposit actions', { tags: '@smoke' }, () => {
cy.mockSubscription();
cy.setVegaWallet();
cy.visit('/#/markets/market-1');
cy.wait('@MarketsCandles');
cy.getByTestId('dialog-close').click();
});
it('Deposit to trade is visble', () => {
@@ -1,6 +1,5 @@
const dialogContent = 'dialog-content';
const nodeHealth = 'node-health';
const nodeHealthTrigger = 'node-health-trigger';
describe('home', { tags: '@regression' }, () => {
before(() => {
@@ -9,23 +8,22 @@ describe('home', { tags: '@regression' }, () => {
cy.visit('/');
});
describe('node health', () => {
describe('footer', () => {
it('shows current block height', () => {
// 0006-NETW-004
// 0006-NETW-008
// 0006-NETW-009
cy.getByTestId(nodeHealthTrigger).realHover();
cy.getByTestId(nodeHealth)
.children()
.first()
.should('contain.text', 'Operational', {
timeout: 10000,
})
.next()
.should('contain.text', new URL(Cypress.env('VEGA_URL')).hostname)
.next()
.should('contain.text', '100'); // all mocked queries have x-block-height header set to 100
cy.getByTestId(nodeHealth)
.children()
.eq(1)
.should('contain.text', new URL(Cypress.env('VEGA_URL')).hostname);
});
it('shows node switcher details', () => {
@@ -34,7 +32,7 @@ describe('home', { tags: '@regression' }, () => {
// 0006-NETW-014
// 0006-NETW-015
// 0006-NETW-016
cy.getByTestId(nodeHealthTrigger).click();
cy.getByTestId(nodeHealth).click();
cy.getByTestId(dialogContent).should('contain.text', 'Connected node');
cy.getByTestId(dialogContent).should(
'contain.text',
@@ -58,7 +56,7 @@ describe('home', { tags: '@regression' }, () => {
// 0006-NETW-018
// 0006-NETW-019
// 0006-NETW-020
cy.getByTestId(nodeHealthTrigger).click();
cy.getByTestId(nodeHealth).click();
cy.getByTestId('connect').should('be.disabled');
cy.getByTestId('node-url-custom').click();
cy.getByTestId('connect').should('be.disabled');
+1 -1
View File
@@ -106,7 +106,7 @@ describe('home', { tags: '@regression' }, () => {
cy.visit('/');
cy.wait('@Markets');
cy.get('[data-testid^="pathname-/markets/"]');
cy.get('main[data-testid^="/markets/"]');
// the choose market overlay is no longer showing
cy.contains('Loading...').should('not.exist');
@@ -68,10 +68,7 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
validateMarketDataRow(0, 'Name', 'BTCUSD Monthly (30 Jun 2022)');
validateMarketDataRow(1, 'Market ID', 'market-0');
if (Cypress.env('NX_SUCCESSOR_MARKETS')) {
validateMarketDataRow(2, 'Parent Market ID', 'PARENT-A');
}
validateMarketDataRow(2, 'Parent Market ID', 'market-1');
validateMarketDataRow(
3,
'Trading Mode',
@@ -132,10 +132,9 @@ describe('liquidity table view', { tags: '@smoke' }, () => {
it('can see header title', () => {
// 5002-LIQP-004
// 5002-LIQP-005
cy.getByTestId('header-title').should(
'contain.text',
'BTCUSD.MF21 liquidity provision'
);
cy.getByTestId('header-title')
.should('contain.text', 'BTCUSD.MF21 liquidity provision')
.and('contain.text', 'Go to trading');
});
it('can see target stake', () => {
@@ -172,7 +171,7 @@ describe('liquidity table view', { tags: '@smoke' }, () => {
cy.getByTestId('liquidity-supplied').within(() => {
cy.getByTestId(itemHeader).should('have.text', 'Liquidity supplied');
cy.getByTestId('indicator').should('be.visible');
cy.getByTestId(itemValue).should('have.text', ' 0.10%').realHover();
cy.getByTestId(itemValue).should('have.text', '0.10%').realHover();
});
});
});
@@ -22,12 +22,15 @@ describe('markets selector', { tags: '@smoke' }, () => {
cy.wait('@Markets');
cy.wait('@MarketsData');
cy.wait('@MarketsCandles');
});
// 6001-MARK-066
it('can open popover to view markets', () => {
it('can toggle the sidebar', () => {
cy.getByTestId('market-selector').should('be.visible');
cy.getByTestId('sidebar-toggle').click();
cy.getByTestId('market-selector').should('not.exist');
cy.getByTestId('header-title').should('be.visible').click();
cy.getByTestId('sidebar-toggle').click();
cy.getByTestId('market-selector').should('be.visible');
});
@@ -37,26 +40,29 @@ describe('markets selector', { tags: '@smoke' }, () => {
const data = [
{
code: 'SOLUSD',
markPrice: '84.41',
vol: '0.00',
markPrice: '84.41XYZalpha',
change: '',
vol: '0.0024h vol',
},
{
code: 'ETHBTC.QM21',
markPrice: '46,126.90058',
vol: '0.00',
markPrice: '46,126.90058tBTC',
change: '',
vol: '0.0024h vol',
},
{
code: 'BTCUSD.MF21',
markPrice: '46,126.90058',
vol: '0.00',
markPrice: '46,126.90058tDAI',
change: '',
vol: '0.0024h vol',
},
{
code: 'AAPL.MF21',
markPrice: '46,126.90058',
vol: '0.00',
markPrice: '46,126.90058tUSDC',
change: '',
vol: '0.0024h vol',
},
];
cy.getByTestId('header-title').should('be.visible').click();
cy.getByTestId(list)
.find('a')
.each((item, i) => {
@@ -65,20 +71,33 @@ describe('markets selector', { tags: '@smoke' }, () => {
// 6001-MARK-022
expect(item.find('h3').text()).equals(market.code);
expect(
item.find('[data-testid="market-selector-volume"]').text()
item.find('[data-testid="market-selector-data-row"]').eq(0).text()
).contains(market.vol);
// 6001-MARK-024
expect(
item.find('[data-testid="market-selector-price"]').text()
item.find('[data-testid="market-selector-data-row"]').eq(1).text()
).contains(market.markPrice);
// 6001-MARK-023
expect(item.find('[data-testid="market-item-change"]').text()).equals(
market.change
);
// 6001-MARK-025
expect(item.find('[data-testid="sparkline-svg"]')).to.not.exist;
});
});
it('can use the filter options', () => {
cy.getByTestId('header-title').should('be.visible').click();
it('can see all markets link', () => {
// 6001-MARK-026
cy.getByTestId('market-selector').within(() => {
cy.getByTestId('all-markets-link')
.should('be.visible')
.and('have.text', 'All markets')
.and('have.attr', 'href')
.and('contain', '#/markets/all');
});
});
it('can use the filter options', () => {
// 6001-MARK-027
// product type
cy.getByTestId('product-Spot').click();
@@ -99,8 +118,6 @@ describe('markets selector', { tags: '@smoke' }, () => {
});
it('can sort by by top gaining and top losing market', () => {
cy.getByTestId('header-title').should('be.visible').click();
// 6001-MARK-030
// 6001-MARK-031
// 6001-MARK-032
@@ -118,8 +135,6 @@ describe('markets selector', { tags: '@smoke' }, () => {
});
it('can filter by settlement asset', () => {
cy.getByTestId('header-title').should('be.visible').click();
// 6001-MARK-028
cy.getByTestId('asset-trigger').click();
cy.getByTestId('asset-id-asset-3').contains('tBTC').click();
@@ -0,0 +1,71 @@
describe('market bottom panel', { tags: '@smoke' }, () => {
before(() => {
cy.clearAllLocalStorage();
cy.mockTradingPage();
cy.mockSubscription();
cy.visit('/#/markets/market-0');
cy.wait('@MarketData');
});
it('on xxl screen should be splitted out into two tables', () => {
cy.getByTestId('tab-positions').should('have.attr', 'data-state', 'active');
cy.getByTestId('tab-open-orders').should(
'have.attr',
'data-state',
'inactive'
);
cy.getByTestId('tab-closed-orders').should(
'have.attr',
'data-state',
'inactive'
);
cy.getByTestId('tab-rejected-orders').should(
'have.attr',
'data-state',
'inactive'
);
cy.getByTestId('tab-orders').should('have.attr', 'data-state', 'inactive');
cy.getByTestId('tab-fills').should('have.attr', 'data-state', 'inactive');
cy.getByTestId('tab-accounts').should(
'have.attr',
'data-state',
'inactive'
);
cy.viewport(1801, 1000);
cy.getByTestId('tab-positions').should('have.attr', 'data-state', 'active');
cy.getByTestId('tab-open-orders').should(
'have.attr',
'data-state',
'inactive'
);
cy.getByTestId('tab-closed-orders').should(
'have.attr',
'data-state',
'inactive'
);
cy.getByTestId('tab-rejected-orders').should(
'have.attr',
'data-state',
'inactive'
);
cy.getByTestId('tab-orders').should('have.attr', 'data-state', 'inactive');
cy.getByTestId('tab-fills').should('have.attr', 'data-state', 'inactive');
cy.getByTestId('tab-accounts').should(
'have.attr',
'data-state',
'inactive'
);
cy.getByTestId('Fills').click();
cy.getByTestId('Collateral').click();
cy.getByTestId('tab-positions').should(
'have.attr',
'data-state',
'inactive'
);
cy.getByTestId('tab-orders').should('have.attr', 'data-state', 'inactive');
cy.getByTestId('tab-fills').should('have.attr', 'data-state', 'active');
cy.getByTestId('tab-accounts').should('have.attr', 'data-state', 'active');
});
});
@@ -99,6 +99,9 @@ describe('Navbar', { tags: '@smoke' }, () => {
cy.getByTestId('menu-drawer').should('not.be.visible');
cy.getByTestId('button-menu-drawer').click();
cy.getByTestId('menu-drawer').should('be.visible');
cy.getByTestId('menu-drawer')
.find('[data-testid="Settings"]')
.should('be.visible');
cy.getByTestId('button-menu-drawer').click();
cy.getByTestId('menu-drawer').should('not.be.visible');
});
@@ -1,15 +1,14 @@
const orderbookTab = 'Orderbook';
const orderbookTable = 'tab-orderbook';
const askPrice = 'price-9894185';
const askPrice = 'price-9894585';
const bidPrice = 'price-9889001';
const askVolume = 'ask-vol-9894185';
const askVolume = 'ask-vol-9894585';
const bidVolume = 'bid-vol-9889001';
const askCumulative = 'cumulative-vol-9894185';
const askCumulative = 'cumulative-vol-9894585';
const bidCumulative = 'cumulative-vol-9889001';
const midPrice = 'middle-mark-price-4612690000';
const priceResolution = 'resolution';
const dealTicketPrice = 'order-price';
const dealTicketSize = 'order-size';
const resPrice = 'price-990';
describe('order book', { tags: '@smoke' }, () => {
@@ -34,7 +33,7 @@ describe('order book', { tags: '@smoke' }, () => {
it('show orders prices', () => {
// 6003-ORDB-003
cy.getByTestId(askPrice).should('have.text', '98.94185');
cy.getByTestId(askPrice).should('have.text', '98.94585');
cy.getByTestId(bidPrice).should('have.text', '98.89001');
});
@@ -46,7 +45,7 @@ describe('order book', { tags: '@smoke' }, () => {
it('show prices cumulative volumes', () => {
// 6003-ORDB-005
cy.getByTestId(askCumulative).should('have.text', '38');
cy.getByTestId(askCumulative).should('have.text', '39');
cy.getByTestId(bidCumulative).should('have.text', '7');
});
@@ -72,19 +71,7 @@ describe('order book', { tags: '@smoke' }, () => {
it('copy price to deal ticket form', () => {
// 6003-ORDB-009
cy.getByTestId(askPrice).click();
cy.getByTestId(dealTicketPrice).should('have.value', '98.94185');
});
it('copy size to deal ticket form', () => {
// 6003-ORDB-009
cy.getByTestId(bidCumulative).click();
cy.getByTestId(dealTicketSize).should('have.value', '7');
});
it('copy size to deal ticket form', () => {
// 6003-ORDB-009
cy.getByTestId(bidVolume).click();
cy.getByTestId(dealTicketSize).should('have.value', '1');
cy.getByTestId(dealTicketPrice).should('have.value', '98.94585');
});
it('change price resolution', () => {
@@ -101,14 +88,13 @@ describe('order book', { tags: '@smoke' }, () => {
'1,000',
'10,000',
];
cy.getByTestId(priceResolution).click();
cy.get('[role="menu"]')
.find('[role="menuitem"]')
cy.getByTestId(priceResolution)
.find('option')
.each(($el, index) => {
expect($el.text()).to.equal(resolutions[index]);
});
cy.get('[role="menuitem"]').eq(4).click();
cy.getByTestId(priceResolution).select('0.0');
cy.getByTestId(resPrice).should('have.text', '99.0');
cy.getByTestId(askPrice).should('not.exist');
cy.getByTestId(bidPrice).should('not.exist');
+33 -20
View File
@@ -1,29 +1,42 @@
describe('Settings page', { tags: '@smoke' }, () => {
beforeEach(() => {
cy.clearLocalStorage();
cy.mockTradingPage();
cy.mockSubscription();
cy.visit('/');
// Only click if not already active otherwise sidebar will close
cy.get('[data-testid="sidebar-content"]').then(($sidebarContent) => {
if ($sidebarContent.find('h2').text() !== 'Settings') {
cy.get('[data-testid="sidebar"] [data-testid="Settings"]').click();
}
cy.clearLocalStorage().then(() => {
cy.mockTradingPage();
cy.mockSubscription();
cy.visit('/');
cy.get('[aria-label="cog icon"]').click();
});
});
it('telemetry checkbox should work well', () => {
const telemetrySwitch = '#switch-settings-telemetry-switch';
cy.get(telemetrySwitch).should('have.attr', 'data-state', 'unchecked');
cy.get(telemetrySwitch).click();
cy.get(telemetrySwitch).should('have.attr', 'data-state', 'checked');
cy.location('hash').should('equal', '#/settings');
cy.getByTestId('telemetry-approval').should(
'have.attr',
'data-state',
'unchecked'
);
cy.get('[for="telemetry-approval"]').click();
cy.getByTestId('telemetry-approval').should(
'have.attr',
'data-state',
'checked'
);
cy.reload();
cy.get(telemetrySwitch).should('have.attr', 'data-state', 'checked');
cy.get(telemetrySwitch).click();
cy.get(telemetrySwitch).should('have.attr', 'data-state', 'unchecked');
cy.getByTestId('telemetry-approval').should(
'have.attr',
'data-state',
'checked'
);
cy.get('[for="telemetry-approval"]').click();
cy.getByTestId('telemetry-approval').should(
'have.attr',
'data-state',
'unchecked'
);
cy.reload();
cy.get(telemetrySwitch).should('have.attr', 'data-state', 'unchecked');
cy.getByTestId('telemetry-approval').should(
'have.attr',
'data-state',
'unchecked'
);
});
});
@@ -14,12 +14,6 @@ describe('deal ticker order validation', { tags: '@smoke' }, () => {
cy.mockSubscription();
cy.visit('/#/markets/market-0');
cy.wait('@Markets');
cy.get('[data-testid="deal-ticket-form"]').then(($form) => {
if (!$form.length) {
cy.getByTestId('Order').click();
}
});
});
beforeEach(() => {
@@ -12,7 +12,7 @@ describe(
'account validation',
{ tags: '@regression', testIsolation: true },
() => {
describe.skip('zero balance error', () => {
describe('zero balance error', () => {
beforeEach(() => {
cy.setVegaWallet();
cy.mockTradingPage();
@@ -59,12 +59,6 @@ describe(
cy.mockSubscription();
cy.visit('/#/markets/market-0');
cy.wait('@Markets');
cy.get('[data-testid="deal-ticket-form"]').then(($form) => {
if (!$form.length) {
cy.getByTestId('Order').click();
}
});
});
it('should display info and button for deposit', () => {
@@ -80,8 +74,8 @@ describe(
'You may not have enough margin available to open this position. 5.00 tDAI is currently required. You have only 0.01001 tDAI available.'
);
cy.getByTestId('deal-ticket-deposit-dialog-button').click();
cy.getByTestId('sidebar-content')
.find('h2')
cy.getByTestId('dialog-content')
.find('h1')
.eq(0)
.should('have.text', 'Deposit');
});
@@ -37,7 +37,7 @@ describe('fills', { tags: '@regression' }, () => {
it('renders fills on portfolio page', () => {
cy.visit('/#/portfolio');
cy.get('[data-testid="pathname-/portfolio"]').should('exist');
cy.get('main[data-testid="/portfolio"]').should('exist');
cy.getByTestId('Fills').click();
validateFillsDisplayed();
});
@@ -431,8 +431,6 @@ describe('amend and cancel order', { tags: '@smoke' }, () => {
});
const orderId = '1234567890';
// this test is flakey
it('must be able to amend the price of an order', () => {
// 7003-MORD-007
// 7003-MORD-012
@@ -6,17 +6,15 @@ const tradesTab = 'Trades';
const tradesTable = 'tab-trades';
describe('trades', { tags: '@smoke' }, () => {
beforeEach(() => {
cy.mockTradingPage();
cy.mockSubscription();
});
before(() => {
cy.mockTradingPage();
cy.mockSubscription();
cy.intercept('POST', '/graphql', (req) => {
if (req.body.operationName === 'Trades') {
req.alias = '@Trades';
}
});
cy.visit('/#/markets/market-0');
cy.getByTestId(tradesTab).click();
cy.wait('@Trades');
});
it('show trades', () => {
@@ -29,49 +27,37 @@ describe('trades', { tags: '@smoke' }, () => {
it('show trades prices', () => {
// 6005-THIS-003
cy.getByTestId(tradesTable)
.get(`${colIdPrice} ${colHeader}`)
.first()
.should('have.text', 'Price');
cy.getByTestId(tradesTable)
.get(colIdPrice)
.each(($tradePrice) => {
cy.wrap($tradePrice).invoke('text').should('not.be.empty');
});
cy.get(`${colIdPrice} ${colHeader}`).first().should('have.text', 'Price');
cy.get(colIdPrice).each(($tradePrice) => {
cy.wrap($tradePrice).invoke('text').should('not.be.empty');
});
});
it('show trades sizes', () => {
// 6005-THIS-004
cy.getByTestId(tradesTable)
.get(`${colIdSize} ${colHeader}`)
.first()
.should('have.text', 'Size');
cy.getByTestId(tradesTable)
.get(colIdSize)
.each(($tradeSize) => {
cy.wrap($tradeSize).invoke('text').should('not.be.empty');
});
cy.get(`${colIdSize} ${colHeader}`).first().should('have.text', 'Size');
cy.get(colIdSize).each(($tradeSize) => {
cy.wrap($tradeSize).invoke('text').should('not.be.empty');
});
});
it('show trades date and time', () => {
// 6005-THIS-005
cy.getByTestId(tradesTable) // order table shares identical col id
.find(`${colIdCreatedAt} ${colHeader}`)
.should('have.text', 'Created at');
cy.get(`${colIdCreatedAt} ${colHeader}`).should('have.text', 'Created at');
const dateTimeRegex =
/(\d{1,2})\/(\d{1,2})\/(\d{4}), (\d{1,2}):(\d{1,2}):(\d{1,2})/gm;
cy.getByTestId(tradesTable)
.get(`.ag-center-cols-container ${colIdCreatedAt}`)
.each(($tradeDateTime) => {
cy.get(colIdCreatedAt).each(($tradeDateTime, index) => {
if (index != 0) {
//ignore header
cy.wrap($tradeDateTime).invoke('text').should('match', dateTimeRegex);
});
}
});
});
it('trades are sorted descending by datetime', () => {
// 6005-THIS-006
const dateTimes: Date[] = [];
cy.getByTestId(tradesTable)
.find(colIdCreatedAt)
cy.get(colIdCreatedAt)
.each(($tradeDateTime, index) => {
if (index != 0) {
//ignore header
@@ -85,7 +71,7 @@ describe('trades', { tags: '@smoke' }, () => {
});
});
it('copy price to deal ticket form', () => {
it.skip('copy price to deal ticket form', () => {
// 6005-THIS-007
cy.get(colIdPrice).last().should('be.visible').click();
cy.getByTestId('order-price').should('have.value', '171.16898');
@@ -10,7 +10,7 @@ describe('ethereum wallet', { tags: '@smoke', testIsolation: true }, () => {
cy.mockSubscription();
cy.setVegaWallet();
cy.visit('/#/portfolio');
cy.get('[data-testid="pathname-/portfolio"]').should('exist');
cy.get('main[data-testid="/portfolio"]').should('exist');
cy.getByTestId('Withdrawals').click();
});
@@ -17,7 +17,7 @@ describe(
cy.visit('/#/portfolio');
cy.mockTradingPage();
cy.mockSubscription();
cy.get('[data-testid="pathname-/portfolio"]').should('exist');
cy.get('main[data-testid="/portfolio"]').should('exist');
});
it('can connect', () => {
@@ -122,7 +122,7 @@ describe('connect vega wallet', { tags: '@smoke', testIsolation: true }, () => {
cy.visit('/#/portfolio');
cy.mockTradingPage();
cy.mockSubscription();
cy.get('[data-testid="pathname-/portfolio"]').should('exist');
cy.get('main[data-testid="/portfolio"]').should('exist');
});
it('can connect', () => {
@@ -1,20 +1,24 @@
import { selectAsset } from '../support/helpers';
// #region consts
const amountField = 'input[name="amount"]';
const amountShortName = 'input[name="amount"] + div + span.text-xs';
const assetSelection = 'select-asset';
const assetBalance = 'asset-balance';
const assetOption = 'rich-select-option';
const transferText = 'transfer-intro-text';
const closeDialog = 'dialog-close';
const dialogTitle = 'dialog-title';
const dialogTransferText = 'dialog-transfer-text';
const dropdownMenu = 'dropdown-menu';
const errorText = 'input-error-text';
const formFieldError = 'input-error-text';
const includeTransferFeeRadioBtn = 'include-transfer-fee';
const keyID = `[data-testid="${transferText}"] > .rounded-md`;
const keyID = '[data-testid="dialog-transfer-text"] > .rounded-md';
const manageVegaWallet = 'manage-vega-wallet';
const openTransferButton = 'open-transfer';
const openTransferDialog = 'open-transfer-dialog';
const submitTransferBtn = '[type="submit"]';
const toAddressField = '[name="toAddress"]';
const totalTransferfee = 'total-transfer-fee';
const transfer = 'transfer';
const transferAmount = 'transfer-amount';
const transferForm = 'transfer-form';
const transferFee = 'transfer-fee';
@@ -26,6 +30,7 @@ const ASSET_SEPOLIA_TBTC = 2;
const collateralTab = 'Collateral';
const toastCloseBtn = 'toast-close';
const toastContent = 'toast-content';
// #endregion
describe('transfer fees', { tags: '@regression', testIsolation: true }, () => {
beforeEach(() => {
@@ -34,19 +39,15 @@ describe('transfer fees', { tags: '@regression', testIsolation: true }, () => {
cy.mockSubscription();
cy.setVegaWallet();
cy.visit('/');
cy.visit('/#/portfolio');
cy.getByTestId('Trading').first().click();
cy.getByTestId(collateralTab).click();
cy.getByTestId(dropdownMenu).first().click();
cy.getByTestId(transfer).click();
cy.wait('@Assets');
cy.wait('@Accounts');
cy.wait('@Assets');
cy.mockVegaWalletTransaction();
// Only click if not already active otherwise sidebar will close
cy.get('[data-testid="sidebar-content"]').then(($sidebarContent) => {
if ($sidebarContent.find('h2').text() !== 'Transfer') {
cy.get('[data-testid="sidebar"] [data-testid="Transfer"]').click();
}
});
});
it('transfer fees tooltips', () => {
@@ -71,18 +72,21 @@ describe('transfer fees', { tags: '@regression', testIsolation: true }, () => {
cy.get('[data-side="bottom"] div')
.should('be.visible')
.should('not.be.empty');
cy.getByTestId(dialogTitle).click();
//Check Transfer Fee tooltip
cy.contains('div', 'Transfer fee').realHover();
cy.get('[data-side="bottom"] div')
.should('be.visible')
.should('not.be.empty');
cy.getByTestId(dialogTitle).click();
//Check Amount to be transferred tooltip
cy.contains('div', 'Amount to be transferred').realHover();
cy.get('[data-side="bottom"] div')
.should('be.visible')
.should('not.be.empty');
cy.getByTestId(dialogTitle).click();
//Check Total amount (with fee) tooltip
cy.contains('div', 'Total amount (with fee)').realHover();
@@ -130,7 +134,6 @@ describe('transfer fees', { tags: '@regression', testIsolation: true }, () => {
.should('contain.text', '1.00');
});
});
describe(
'transfer form validation',
{ tags: '@regression', testIsolation: true },
@@ -151,7 +154,7 @@ describe(
it('transfer Text', () => {
// 1003-TRAN-003
cy.getByTestId(transferText)
cy.getByTestId(dialogTransferText)
.should('exist')
.get(keyID)
.invoke('text')
@@ -201,6 +204,7 @@ describe(
'contain.text',
'You cannot transfer more than your available collateral'
);
cy.getByTestId(closeDialog).click();
});
}
);
@@ -213,7 +217,7 @@ describe('withdraw actions', { tags: '@smoke', testIsolation: true }, () => {
cy.visit('/#/portfolio');
cy.getByTestId(collateralTab).click();
cy.getByTestId(openTransferButton).click();
cy.getByTestId(openTransferDialog).click();
cy.wait('@Accounts');
cy.wait('@Assets');
@@ -21,7 +21,7 @@ describe('withdraw form validation', { tags: '@smoke' }, () => {
cy.visit('/#/portfolio');
cy.getByTestId('Withdrawals').click();
cy.getByTestId('Withdraw').click(); // sidebar item
cy.getByTestId('withdraw-dialog-button').click();
// It also requires connection Ethereum wallet
connectEthereumWallet('MetaMask');
@@ -87,17 +87,15 @@ describe(
cy.setVegaWallet();
cy.visit('/#/portfolio');
cy.wait('@Accounts');
cy.wait('@Assets');
cy.getByTestId('Withdrawals').click();
cy.getByTestId('Withdraw').click();
cy.getByTestId('withdraw-dialog-button').click();
// It also requires connection Ethereum wallet
connectEthereumWallet('MetaMask');
cy.wait('@Accounts');
cy.wait('@Assets');
cy.mockVegaWalletTransaction();
});
-6
View File
@@ -33,9 +33,6 @@ import {
networkParamQuery,
liquidityProvisionsQuery,
liquidityProviderFeeShareQuery,
successorMarketQuery,
parentMarketIdQuery,
successorMarketIdsQuery,
} from '@vegaprotocol/mock';
import type { PartialDeep } from 'type-fest';
import type { MarketDataQuery, MarketsQuery } from '@vegaprotocol/markets';
@@ -183,9 +180,6 @@ const mockTradingPage = (
protocolUpgradeProposalsQuery()
);
aliasGQLQuery(req, 'BlockStatistics', blockStatisticsQuery());
aliasGQLQuery(req, 'SuccessorMarket', successorMarketQuery());
aliasGQLQuery(req, 'ParentMarketId', parentMarketIdQuery());
aliasGQLQuery(req, 'SuccessorMarketIds', successorMarketIdsQuery());
};
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
-6
View File
@@ -13,9 +13,3 @@ NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/main/announcements.json
NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=true
# NX_STOP_ORDERS
# NX_ICEBERG_ORDERS
# NX_PRODUCT_PERPETUALS
-6
View File
@@ -15,9 +15,3 @@ NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/annou
NX_ETH_LOCAL_PROVIDER_URL=http://localhost:8545/
NX_ETH_WALLET_MNEMONIC="ozone access unlock valid olympic save include omit supply green clown session"
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=false
# NX_STOP_ORDERS
# NX_ICEBERG_ORDERS
# NX_PRODUCT_PERPETUALS
-6
View File
@@ -13,9 +13,3 @@ NX_VEGA_DOCS_URL=#
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega-dev-releases/releases
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=true
# NX_STOP_ORDERS
# NX_ICEBERG_ORDERS
# NX_PRODUCT_PERPETUALS
-6
View File
@@ -15,9 +15,3 @@ NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
NX_VEGA_CONSOLE_URL=https://console.vega.xyz
# TAG name of the current app version - TODO: bump to the latest upon release
NX_APP_VERSION=v0.20.21-core-0.71.6
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=false
# NX_STOP_ORDERS
# NX_ICEBERG_ORDERS
# NX_PRODUCT_PERPETUALS
+2 -8
View File
@@ -3,9 +3,9 @@ NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_SENTRY_DSN=https://2ffce43721964aafa78277c50654ece4@o286262.ingest.sentry.io/6300613
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/mainnet-mirror/vegawallet-mainnet-mirror.toml
NX_VEGA_ENV=MAINNET_MIRROR
NX_VEGA_ENV=MAINNET-MIRROR
NX_VEGA_EXPLORER_URL=https://explorer.mainnet-mirror.vega.rocks
NX_VEGA_NETWORKS={\"MAINNET\":\"https://console.vega.xyz\",\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"https://trading.stagnet1.vega.rocks\",\"MAINNET_MIRROR\":\"https://trading.mainnet-mirror.vega.rocks\"}
NX_VEGA_NETWORKS={\"MAINNET\":\"https://console.vega.xyz\",\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"https://trading.stagnet1.vega.rocks\",\"MAINNET-MIRROR\":\"https://trading.mainnet-mirror.vega.rocks\"}
NX_VEGA_TOKEN_URL=https://governance.mainnet-mirror.vega.rocks
NX_VEGA_WALLET_URL=http://localhost:1789
NX_VEGA_DOCS_URL=https://docs.vega.xyz/mainnet
@@ -15,9 +15,3 @@ NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
NX_VEGA_CONSOLE_URL=https://console.mainnet-mirror.vega.rocks
# TAG name of the current app version - TODO: bump to the latest upon release
NX_APP_VERSION=v0.20.19-core-0.71.6
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=false
# NX_STOP_ORDERS
# NX_ICEBERG_ORDERS
# NX_PRODUCT_PERPETUALS
-6
View File
@@ -13,9 +13,3 @@ NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=true
# NX_STOP_ORDERS
# NX_ICEBERG_ORDERS
# NX_PRODUCT_PERPETUALS
-6
View File
@@ -14,9 +14,3 @@ NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
NX_VEGA_CONSOLE_URL=https://console.fairground.wtf
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=true
# NX_STOP_ORDERS
# NX_ICEBERG_ORDERS
# NX_PRODUCT_PERPETUALS
-6
View File
@@ -14,9 +14,3 @@ NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
NX_VEGA_CONSOLE_URL=https://trading.validators-testnet.vega.rocks
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=false
# NX_STOP_ORDERS
# NX_ICEBERG_ORDERS
# NX_PRODUCT_PERPETUALS
+142 -28
View File
@@ -1,10 +1,36 @@
import { matchFilter, lpAggregatedDataProvider } from '@vegaprotocol/liquidity';
import {
matchFilter,
lpAggregatedDataProvider,
useCheckLiquidityStatus,
} from '@vegaprotocol/liquidity';
import { tooltipMapping } from '@vegaprotocol/markets';
import {
addDecimalsFormatNumber,
formatNumberPercentage,
} from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import {
NetworkParams,
useNetworkParams,
} from '@vegaprotocol/network-parameters';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { Tab, Tabs } from '@vegaprotocol/ui-toolkit';
import {
Tab,
Tabs,
Link as UiToolkitLink,
Indicator,
ExternalLink,
} from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import { memo, useEffect, useState } from 'react';
import { Header, HeaderStat, HeaderTitle } from '../../components/header';
import { Link, useParams } from 'react-router-dom';
import { Links, Routes } from '../../pages/client-router';
import { useMarket, useStaticMarketData } from '@vegaprotocol/markets';
import { DocsLinks } from '@vegaprotocol/environment';
import { LiquidityContainer } from '../../components/liquidity-container';
const enum LiquidityTabs {
@@ -19,6 +45,98 @@ export const Liquidity = () => {
return <LiquidityViewContainer marketId={marketId} />;
};
const LiquidityViewHeader = memo(({ marketId }: { marketId?: string }) => {
const { data: market } = useMarket(marketId);
const { data: marketData } = useStaticMarketData(marketId);
const targetStake = marketData?.targetStake;
const suppliedStake = marketData?.suppliedStake;
const assetDecimalPlaces =
market?.tradableInstrument.instrument.product.settlementAsset.decimals || 0;
const symbol =
market?.tradableInstrument.instrument.product.settlementAsset.symbol;
const { params } = useNetworkParams([
NetworkParams.market_liquidity_stakeToCcyVolume,
NetworkParams.market_liquidity_targetstake_triggering_ratio,
]);
const triggeringRatio =
params.market_liquidity_targetstake_triggering_ratio || '1';
const { percentage, status } = useCheckLiquidityStatus({
suppliedStake: suppliedStake || 0,
targetStake: targetStake || 0,
triggeringRatio,
});
return (
<Header
title={
market?.tradableInstrument.instrument.name &&
market?.tradableInstrument.instrument.code &&
marketId && (
<HeaderTitle
primaryContent={`${market.tradableInstrument.instrument.code} ${t(
'liquidity provision'
)}`}
secondaryContent={
<Link to={Links[Routes.MARKET](marketId)}>
<UiToolkitLink>{t('Go to trading')}</UiToolkitLink>
</Link>
}
/>
)
}
>
<HeaderStat
heading={t('Target stake')}
description={tooltipMapping['targetStake']}
testId="target-stake"
>
<div>
{targetStake
? `${addDecimalsFormatNumber(
targetStake,
assetDecimalPlaces ?? 0
)} ${symbol}`
: '-'}
</div>
</HeaderStat>
<HeaderStat
heading={t('Supplied stake')}
description={tooltipMapping['suppliedStake']}
testId="supplied-stake"
>
<div>
{suppliedStake
? `${addDecimalsFormatNumber(
suppliedStake,
assetDecimalPlaces ?? 0
)} ${symbol}`
: '-'}
</div>
</HeaderStat>
<HeaderStat heading={t('Liquidity supplied')} testId="liquidity-supplied">
<Indicator variant={status} />
{formatNumberPercentage(percentage, 2)}
</HeaderStat>
<HeaderStat heading={t('Market ID')} testId="liquidity-market-id">
<div className="break-word">{marketId}</div>
</HeaderStat>
<HeaderStat heading={t('Learn more')} testId="liquidity-learn-more">
{DocsLinks ? (
<ExternalLink href={DocsLinks.LIQUIDITY}>
{t('Providing liquidity')}
</ExternalLink>
) : (
(null as React.ReactNode)
)}
</HeaderStat>
</Header>
);
});
LiquidityViewHeader.displayName = 'LiquidityViewHeader';
export const LiquidityViewContainer = ({
marketId,
}: {
@@ -49,30 +167,26 @@ export const LiquidityViewContainer = ({
}, [data, pubKey]);
return (
<div className="h-full p-1.5">
<div className="h-full border border-default">
<Tabs value={tab || LiquidityTabs.Active} onValueChange={setTab}>
<Tab
id={LiquidityTabs.MyLiquidityProvision}
name={t('My liquidity provision')}
hidden={!pubKey}
>
<LiquidityContainer
marketId={marketId}
filter={{ partyId: pubKey || undefined }}
/>
</Tab>
<Tab id={LiquidityTabs.Active} name={t('Active')}>
<LiquidityContainer marketId={marketId} filter={{ active: true }} />
</Tab>
<Tab id={LiquidityTabs.Inactive} name={t('Inactive')}>
<LiquidityContainer
marketId={marketId}
filter={{ active: false }}
/>
</Tab>
</Tabs>
</div>
<div className="h-full grid grid-rows-[min-content_1fr]">
<LiquidityViewHeader marketId={marketId} />
<Tabs value={tab || LiquidityTabs.Active} onValueChange={setTab}>
<Tab
id={LiquidityTabs.MyLiquidityProvision}
name={t('My liquidity provision')}
hidden={!pubKey}
>
<LiquidityContainer
marketId={marketId}
filter={{ partyId: pubKey || undefined }}
/>
</Tab>
<Tab id={LiquidityTabs.Active} name={t('Active')}>
<LiquidityContainer marketId={marketId} filter={{ active: true }} />
</Tab>
<Tab id={LiquidityTabs.Inactive} name={t('Inactive')}>
<LiquidityContainer marketId={marketId} filter={{ active: false }} />
</Tab>
</Tabs>
</div>
);
};
@@ -19,6 +19,7 @@ describe('AssetDropdown', () => {
checkedAssets={[]}
assets={assets}
onSelect={mockOnSelect}
onReset={jest.fn()}
/>
);
await userEvent.click(screen.getByRole('button'));
@@ -38,6 +39,7 @@ describe('AssetDropdown', () => {
checkedAssets={[assets[0].id]}
assets={assets}
onSelect={mockOnSelect}
onReset={jest.fn()}
/>
);
await userEvent.click(screen.getByRole('button'));
@@ -46,9 +48,35 @@ describe('AssetDropdown', () => {
expect(mockOnSelect).toHaveBeenCalledWith(assets[0].id, false);
});
it('can be reset clearing all assets', async () => {
const mockOnSelect = jest.fn();
const mockOnReset = jest.fn();
render(
<AssetDropdown
checkedAssets={assets.map((a) => a.id)}
assets={assets}
onSelect={mockOnSelect}
onReset={mockOnReset}
/>
);
await userEvent.click(screen.getByRole('button'));
const items = screen.getAllByRole('menuitemcheckbox');
// all should be checked
items.forEach((item) => {
expect(item).toBeChecked();
});
await userEvent.click(screen.getByText('Reset'));
expect(mockOnReset).toHaveBeenCalled();
});
it('doesnt render if no assets provided', async () => {
const { container } = render(
<AssetDropdown checkedAssets={[]} assets={[]} onSelect={jest.fn()} />
<AssetDropdown
checkedAssets={[]}
assets={[]}
onSelect={jest.fn()}
onReset={jest.fn()}
/>
);
expect(container).toBeEmptyDOMElement();
});

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