Compare commits

..
137 changed files with 1710 additions and 3317 deletions
+23 -45
View File
@@ -6,9 +6,7 @@ on:
- release/*
- develop
- main
# uncomment pull_request and comment pull_request_target to test CI changes against feature branch not target branch (develop)
# pull_request:
pull_request_target:
pull_request:
types:
- opened
- ready_for_review
@@ -22,8 +20,6 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Cache node modules
id: cache
@@ -49,7 +45,7 @@ jobs:
lint-pr-title:
needs: node-modules
if: ${{ github.event_name == 'pull_request' || github.event_name == 'pull_request_target' }}
if: ${{ github.event_name == 'pull_request' }}
name: Verify PR title
uses: ./.github/workflows/lint-pr.yml
secrets: inherit
@@ -64,7 +60,6 @@ jobs:
uses: actions/checkout@v3
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Setup node
uses: actions/setup-node@v3
@@ -112,82 +107,65 @@ jobs:
echo "Branch slug: ${branch_slug}"
echo ">>>> eof debug"
projects_array=()
projects_e2e=""
preview_governance="not deployed"
preview_trading="not deployed"
preview_explorer="not deployed"
preview_tools="not deployed"
# parse if affected is any of three main applications, if none - use all of them
if echo "$affected" | grep -q governance; then
echo "Governance is affected"
projects_array+=("governance")
projects_e2e+='"governance-e2e" '
preview_governance=$(printf "https://%s.%s.vega.rocks" "governance" "$branch_slug")
fi
if echo "$affected" | grep -q trading; then
echo "Trading is affected"
projects_array+=("trading")
projects_e2e+='"trading-e2e" '
preview_trading=$(printf "https://%s.%s.vega.rocks" "trading" "$branch_slug")
fi
if echo "$affected" | grep -q explorer; then
echo "Explorer is affected"
projects_array+=("explorer")
projects_e2e+='"explorer-e2e" '
preview_explorer=$(printf "https://%s.%s.vega.rocks" "explorer" "$branch_slug")
fi
if [[ ${#projects_array[@]} -eq 0 ]]; then
projects_array=("governance" "trading" "explorer")
if [[ -z "$projects_e2e" ]]; then
projects_e2e+='"governance-e2e" "trading-e2e" "explorer-e2e" '
preview_governance=$(printf "https://%s.%s.vega.rocks" "governance" "$branch_slug")
preview_trading=$(printf "https://%s.%s.vega.rocks" "trading" "$branch_slug")
preview_explorer=$(printf "https://%s.%s.vega.rocks" "explorer" "$branch_slug")
fi
# applications parsed before this loop are applicable for running e2e-tests
projects_e2e_array=()
for project in "${projects_array[@]}"; do
projects_e2e_array+=("${project}-e2e")
done
# all applications below this loop are not applicable for running e2e-test
# check if pull request event to deploy tools
projects="$(echo $projects_e2e | sed 's|-e2e||g')"
if [[ "${{ github.event_name }}" = "pull_request" ]]; then
if echo "$affected" | grep -q multisig-signer; then
echo "Tools are affected"
# tools are only applicable to check previews or deploy from develop to mainnet
echo "Deploying tools on preview"
preview_tools=$(printf "https://%s.%s.vega.rocks" "tools" "$branch_slug")
projects_array+=("multisig-signer")
projects+=' "multisig-signer" '
fi
# those apps deploy only from develop to mainnet
elif [[ "${{ github.ref }}" =~ .*develop$ ]]; then
if echo "$affected" | grep -q multisig-signer; then
echo "Tools are affected"
# tools are only applicable to check previews or deploy from develop to mainnet
echo "Deploying tools on s3"
projects_array+=("multisig-signer")
projects+=' "multisig-signer" '
fi
if echo "$affected" | grep -q static; then
echo "static is affected"
echo "Deploying static on s3"
projects_array+=("static")
projects+=' "static" '
fi
if echo "$affected" | grep -q ui-toolkit; then
echo "ui-toolkit is affected"
echo "Deploying ui-toolkit on s3"
projects_array+=("ui-toolkit")
projects+=' "ui-toolkit" '
fi
fi
echo "Projects: ${projects_array[@]}"
echo "Projects E2E: ${projects_e2e_array[@]}"
projects_json=$(jq -M --compact-output --null-input '$ARGS.positional' --args -- "${projects_array[@]}")
projects_e2e_json=$(jq -M --compact-output --null-input '$ARGS.positional' --args -- "${projects_e2e_array[@]}")
echo PROJECTS_E2E=$projects_e2e_json >> $GITHUB_ENV
echo PROJECTS=$projects_json >> $GITHUB_ENV
projects_e2e=${projects_e2e%?}
projects_e2e=[${projects_e2e// /,}]
projects=[${projects// /,}]
echo PROJECTS_E2E=$projects_e2e >> $GITHUB_ENV
echo PROJECTS=$projects >> $GITHUB_ENV
echo PREVIEW_GOVERNANCE=$preview_governance >> $GITHUB_ENV
echo PREVIEW_TRADING=$preview_trading >> $GITHUB_ENV
echo PREVIEW_EXPLORER=$preview_explorer >> $GITHUB_ENV
@@ -204,7 +182,7 @@ jobs:
cypress:
needs: lint-test-build
name: '(CI) cypress'
# if: ${{ needs.lint-test-build.outputs.projects-e2e != '[]' }}
if: ${{ needs.lint-test-build.outputs.projects-e2e != '[]' }}
uses: ./.github/workflows/cypress-run.yml
secrets: inherit
with:
@@ -214,7 +192,7 @@ jobs:
publish-dist:
needs: lint-test-build
name: '(CD) publish dist'
# if: ${{ needs.lint-test-build.outputs.projects != '[]' }}
if: ${{ needs.lint-test-build.outputs.projects != '[]' }}
uses: ./.github/workflows/publish-dist.yml
secrets: inherit
with:
@@ -225,7 +203,7 @@ jobs:
needs:
- publish-dist
- lint-test-build
if: ${{ github.event_name == 'pull_request' || github.event_name == 'pull_request_target' }}
if: ${{ github.event_name == 'pull_request' }}
timeout-minutes: 60
name: '(CD) comment preview links'
steps:
-1
View File
@@ -33,7 +33,6 @@ jobs:
with:
fetch-depth: 0
path: './frontend-monorepo'
ref: ${{ github.event.pull_request.head.sha || github.sha }}
# Restore node_modules from cache if possible
- name: Restore node_modules from cache
-2
View File
@@ -11,8 +11,6 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Setup node
uses: actions/setup-node@v3
+4 -6
View File
@@ -19,8 +19,6 @@ jobs:
steps:
- name: Check out code
uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Set up QEMU
id: quemu
@@ -33,7 +31,7 @@ jobs:
uses: docker/setup-buildx-action@v2
- name: Log in to the Container registry (ghcr)
if: ${{ github.event_name == 'pull_request' || github.event_name == 'pull_request_target' }}
if: ${{ github.event_name == 'pull_request' }}
uses: docker/login-action@v2
with:
registry: ghcr.io
@@ -145,7 +143,7 @@ jobs:
ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local
- name: Image digest
if: ${{ github.event_name == 'pull_request' || github.event_name == 'pull_request_target' }}
if: ${{ github.event_name == 'pull_request' }}
run: echo ${{ steps.docker_build.outputs.digest }}
- name: Sanity check docker image
@@ -160,7 +158,7 @@ jobs:
uses: docker/build-push-action@v3
continue-on-error: true
id: ghcr-push
if: ${{ github.event_name == 'pull_request' || github.event_name == 'pull_request_target' }}
if: ${{ github.event_name == 'pull_request' }}
with:
context: .
file: docker/node-outside-docker.Dockerfile
@@ -230,7 +228,7 @@ jobs:
- name: Add preview label
uses: actions-ecosystem/action-add-labels@v1
if: ${{ github.event_name == 'pull_request' || github.event_name == 'pull_request_target' }}
if: ${{ github.event_name == 'pull_request' }}
with:
labels: ${{ matrix.app }}-preview
number: ${{ github.event.number }}
@@ -1,86 +0,0 @@
import { render } from '@testing-library/react';
import { TxDetailsLiquidityAmendment } from './tx-liquidity-amend';
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
import { MockedProvider } from '@apollo/client/testing';
import { MemoryRouter } from 'react-router-dom';
describe('TxDetailsLiquidityAmendment', () => {
const mockTxData = {
hash: 'test',
command: {
liquidityProvisionAmendment: {
marketId: 'BTC-USD',
commitmentAmount: 100,
fee: '0.01',
},
},
};
const mockPubKey = '123';
const mockBlockData = {
result: {
block: {
header: {
height: '123',
},
},
},
};
it('should render the component with correct data', () => {
const { getByText } = render(
<MockedProvider>
<MemoryRouter>
<TxDetailsLiquidityAmendment
txData={mockTxData as BlockExplorerTransactionResult}
pubKey={mockPubKey}
blockData={mockBlockData as TendermintBlocksResponse}
/>
</MemoryRouter>
</MockedProvider>
);
expect(getByText('Market')).toBeInTheDocument();
expect(getByText('BTC-USD')).toBeInTheDocument();
expect(getByText('Commitment amount')).toBeInTheDocument();
expect(getByText('100')).toBeInTheDocument();
expect(getByText('Fee')).toBeInTheDocument();
expect(getByText('1%')).toBeInTheDocument();
});
it('should display awaiting message when tx data is undefined', () => {
const { getByText } = render(
<MockedProvider>
<MemoryRouter>
<TxDetailsLiquidityAmendment
txData={undefined}
pubKey={mockPubKey}
blockData={mockBlockData as TendermintBlocksResponse}
/>
</MemoryRouter>
</MockedProvider>
);
expect(
getByText('Awaiting Block Explorer transaction details')
).toBeInTheDocument();
});
it('should display awaiting message when liquidityProvisionAmendment is undefined', () => {
const { getByText } = render(
<MockedProvider>
<MemoryRouter>
<TxDetailsLiquidityAmendment
txData={{ command: {} } as BlockExplorerTransactionResult}
pubKey={mockPubKey}
blockData={mockBlockData as TendermintBlocksResponse}
/>
</MemoryRouter>
</MockedProvider>
);
expect(
getByText('Awaiting Block Explorer transaction details')
).toBeInTheDocument();
});
});
@@ -7,7 +7,6 @@ import { TableCell, TableRow, TableWithTbody } from '../../table';
import type { components } from '../../../../types/explorer';
import { LiquidityProvisionDetails } from './liquidity-provision/liquidity-provision-details';
import PriceInMarket from '../../price-in-market/price-in-market';
import BigNumber from 'bignumber.js';
export type LiquidityAmendment =
components['schemas']['v1LiquidityProvisionAmendment'];
@@ -35,10 +34,6 @@ export const TxDetailsLiquidityAmendment = ({
txData.command.liquidityProvisionAmendment;
const marketId: string = amendment.marketId || '-';
const fee = amendment.fee
? new BigNumber(amendment.fee).times(100).toString()
: '-';
return (
<>
<TableWithTbody className="mb-8" allowWrap={true}>
@@ -68,7 +63,7 @@ export const TxDetailsLiquidityAmendment = ({
{amendment.fee ? (
<TableRow modifier="bordered">
<TableCell>{t('Fee')}</TableCell>
<TableCell>{fee}%</TableCell>
<TableCell>{amendment.fee}%</TableCell>
</TableRow>
) : null}
</TableWithTbody>
@@ -1,86 +0,0 @@
import { render } from '@testing-library/react';
import { TxDetailsLiquiditySubmission } from './tx-liquidity-submission';
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
import { MockedProvider } from '@apollo/client/testing';
import { MemoryRouter } from 'react-router-dom';
describe('TxDetailsLiquiditySubmission', () => {
const mockTxData = {
hash: 'test',
command: {
liquidityProvisionSubmission: {
marketId: 'BTC-USD',
commitmentAmount: 100,
fee: '0.01',
},
},
};
const mockPubKey = '123';
const mockBlockData = {
result: {
block: {
header: {
height: '123',
},
},
},
};
it('should render the component with correct data', () => {
const { getByText } = render(
<MockedProvider>
<MemoryRouter>
<TxDetailsLiquiditySubmission
txData={mockTxData as BlockExplorerTransactionResult}
pubKey={mockPubKey}
blockData={mockBlockData as TendermintBlocksResponse}
/>
</MemoryRouter>
</MockedProvider>
);
expect(getByText('Market')).toBeInTheDocument();
expect(getByText('BTC-USD')).toBeInTheDocument();
expect(getByText('Commitment amount')).toBeInTheDocument();
expect(getByText('100')).toBeInTheDocument();
expect(getByText('Fee')).toBeInTheDocument();
expect(getByText('1%')).toBeInTheDocument();
});
it('should display awaiting message when tx data is undefined', () => {
const { getByText } = render(
<MockedProvider>
<MemoryRouter>
<TxDetailsLiquiditySubmission
txData={undefined}
pubKey={mockPubKey}
blockData={mockBlockData as TendermintBlocksResponse}
/>
</MemoryRouter>
</MockedProvider>
);
expect(
getByText('Awaiting Block Explorer transaction details')
).toBeInTheDocument();
});
it('should display awaiting message when liquidityProvisionSubmission is undefined', () => {
const { getByText } = render(
<MockedProvider>
<MemoryRouter>
<TxDetailsLiquiditySubmission
txData={{ command: {} } as BlockExplorerTransactionResult}
pubKey={mockPubKey}
blockData={mockBlockData as TendermintBlocksResponse}
/>
</MemoryRouter>
</MockedProvider>
);
expect(
getByText('Awaiting Block Explorer transaction details')
).toBeInTheDocument();
});
});
@@ -7,7 +7,6 @@ import { TableCell, TableRow, TableWithTbody } from '../../table';
import type { components } from '../../../../types/explorer';
import { LiquidityProvisionDetails } from './liquidity-provision/liquidity-provision-details';
import PriceInMarket from '../../price-in-market/price-in-market';
import BigNumber from 'bignumber.js';
export type LiquiditySubmission =
components['schemas']['v1LiquidityProvisionSubmission'];
@@ -34,10 +33,6 @@ export const TxDetailsLiquiditySubmission = ({
txData.command.liquidityProvisionSubmission;
const marketId: string = submission.marketId || '-';
const fee = submission.fee
? new BigNumber(submission.fee).times(100).toString()
: '-';
return (
<>
<TableWithTbody className="mb-8" allowWrap={true}>
@@ -67,7 +62,7 @@ export const TxDetailsLiquiditySubmission = ({
{submission.fee ? (
<TableRow modifier="bordered">
<TableCell>{t('Fee')}</TableCell>
<TableCell>{fee}%</TableCell>
<TableCell>{submission.fee}%</TableCell>
</TableRow>
) : null}
</TableWithTbody>
+1 -1
View File
@@ -49,7 +49,7 @@ module.exports = defineConfig({
vegaTokenContractAddress: '0xF41bD86d462D36b997C0bbb4D97a0a3382f205B7',
vegaTokenAddress: '0x67175Da1D5e966e40D11c4B2519392B2058373de',
txTimeout: { timeout: 70000 },
epochTimeout: { timeout: 12000 },
epochTimeout: { timeout: 10000 },
blockConfirmations: 3,
grepTags: '@regression @smoke @slow',
grepFilterSpecs: true,
@@ -1,63 +0,0 @@
export const previousEpochData = {
epoch: {
id: '7611',
validatorsConnection: {
edges: [
{
node: {
id: 'cd96782bc0ad5679869cf69fe7838a92212da7f53b4a214bed68067117494122',
stakedTotal: '3154229668720612941799',
rewardScore: {
rawValidatorScore: '0.2',
performanceScore: '1',
multisigScore: '0',
validatorScore: '0.2',
normalisedScore: '0.2007216887087119',
validatorStatus: 'VALIDATOR_NODE_STATUS_TENDERMINT',
__typename: 'RewardScore',
},
rankingScore: {
status: 'VALIDATOR_NODE_STATUS_TENDERMINT',
previousStatus: 'VALIDATOR_NODE_STATUS_TENDERMINT',
rankingScore: '0.211713765544955625',
stakeScore: '0.2016321576618625',
performanceScore: '1',
votingPower: '2007',
__typename: 'RankingScore',
},
__typename: 'Node',
},
__typename: 'NodeEdge',
},
{
node: {
id: '887d936f797a47032eceb572a13b69b581d3b1fa595a7d021a3e3cf2a5d2acfd',
stakedTotal: '3151161904761904764551',
rewardScore: {
rawValidatorScore: '0.2',
performanceScore: '1',
multisigScore: '1',
validatorScore: '0.2',
normalisedScore: '0.2007216887087119',
validatorStatus: 'VALIDATOR_NODE_STATUS_TENDERMINT',
__typename: 'RewardScore',
},
rankingScore: {
status: 'VALIDATOR_NODE_STATUS_TENDERMINT',
previousStatus: 'VALIDATOR_NODE_STATUS_TENDERMINT',
rankingScore: '0.211507855409133255',
stakeScore: '0.2014360527706031',
performanceScore: '1',
votingPower: '2007',
__typename: 'RankingScore',
},
__typename: 'Node',
},
__typename: 'NodeEdge',
},
],
__typename: 'NodesConnection',
},
__typename: 'Epoch',
},
};
@@ -28,17 +28,18 @@ import type { testFreeformProposal } from '../../support/common-interfaces';
import { formatDateWithLocalTimezone } from '@vegaprotocol/utils';
const proposalVoteProgressForPercentage =
'vote-progress-indicator-percentage-for';
'[data-testid="vote-progress-indicator-percentage-for"]';
const proposalVoteProgressAgainstPercentage =
'vote-progress-indicator-percentage-against';
const proposalVoteProgressForTokens = 'vote-progress-indicator-tokens-for';
'[data-testid="vote-progress-indicator-percentage-against"]';
const proposalVoteProgressForTokens =
'[data-testid="vote-progress-indicator-tokens-for"]';
const proposalVoteProgressAgainstTokens =
'vote-progress-indicator-tokens-against';
const changeVoteButton = 'change-vote-button';
const proposalDetailsTitle = 'proposal-title';
const proposalDetailsDescription = 'proposal-description';
const openProposals = 'open-proposals';
const viewProposalButton = 'view-proposal-btn';
'[data-testid="vote-progress-indicator-tokens-against"]';
const changeVoteButton = '[data-testid="change-vote-button"]';
const proposalDetailsTitle = '[data-testid="proposal-title"]';
const proposalDetailsDescription = '[data-testid="proposal-description"]';
const openProposals = '[data-testid="open-proposals"]';
const viewProposalButton = '[data-testid="view-proposal-btn"]';
const proposalDescriptionToggle = 'proposal-description-toggle';
const voteBreakdownToggle = 'vote-breakdown-toggle';
const proposalTermsToggle = 'proposal-json-toggle';
@@ -71,18 +72,18 @@ describe(
createRawProposal();
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
cy.getByTestId(openProposals).within(() => {
cy.get(openProposals).within(() => {
getProposalFromTitle(rawProposal.rationale.title).within(() => {
cy.getByTestId(viewProposalButton).should('be.visible').click();
cy.get(viewProposalButton).should('be.visible').click();
});
});
cy.getByTestId(proposalDetailsTitle).should(
cy.get(proposalDetailsTitle).should(
'contain.text',
rawProposal.rationale.title
);
cy.getByTestId(proposalDescriptionToggle).click();
cy.getByTestId('proposal-description-toggle');
cy.getByTestId(proposalDetailsDescription)
cy.get(proposalDetailsDescription)
.find('p')
.should('have.text', proposalDescription);
});
@@ -116,7 +117,7 @@ describe(
});
navigateTo(navigation.proposals);
getProposalFromTitle(proposalTitle).within(() =>
cy.getByTestId(viewProposalButton).click()
cy.get(viewProposalButton).click()
);
cy.wrap(
formatDateWithLocalTimezone(new Date(proposalTimeStamp * 1000))
@@ -138,7 +139,7 @@ describe(
createRawProposal();
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
getProposalFromTitle(rawProposal.rationale.title).within(() =>
cy.getByTestId(viewProposalButton).click()
cy.get(viewProposalButton).click()
);
});
cy.contains('Participation: Not Met 0.00 0.00%(0.00% Required)').should(
@@ -165,7 +166,7 @@ describe(
createRawProposal();
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
getProposalFromTitle(rawProposal.rationale.title).within(() =>
cy.getByTestId(viewProposalButton).click()
cy.get(viewProposalButton).click()
);
});
// 3001-VOTE-080
@@ -182,16 +183,14 @@ describe(
.contains(votedDate)
.should('be.visible');
});
cy.getByTestId(proposalVoteProgressForPercentage) // 3001-VOTE-072
cy.get(proposalVoteProgressForPercentage) // 3001-VOTE-072
.contains('100.00%')
.and('be.visible');
cy.getByTestId(proposalVoteProgressAgainstPercentage)
cy.get(proposalVoteProgressAgainstPercentage)
.contains('0.00%')
.and('be.visible');
cy.getByTestId(proposalVoteProgressForTokens)
.contains('1.00')
.and('be.visible');
cy.getByTestId(proposalVoteProgressAgainstTokens)
cy.get(proposalVoteProgressForTokens).contains('1.00').and('be.visible');
cy.get(proposalVoteProgressAgainstTokens)
.contains('0.00')
.and('be.visible');
cy.getByTestId(voteBreakdownToggle).click();
@@ -212,15 +211,15 @@ describe(
getProposalInformationFromTable('Number of voting parties')
.should('have.text', '1')
.and('be.visible');
cy.getByTestId(changeVoteButton).should('be.visible').click();
cy.get(changeVoteButton).should('be.visible').click();
voteForProposal('for');
// 3001-VOTE-064
getProposalInformationFromTable('Tokens for proposal')
.should('have.text', (1).toFixed(2))
.and('be.visible');
cy.getByTestId(changeVoteButton).should('be.visible').click();
cy.get(changeVoteButton).should('be.visible').click();
voteForProposal('against');
cy.getByTestId(proposalVoteProgressAgainstPercentage)
cy.get(proposalVoteProgressAgainstPercentage)
.contains('100.00%')
.and('be.visible');
getProposalInformationFromTable('Tokens against proposal')
@@ -237,15 +236,13 @@ describe(
createRawProposal();
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
getProposalFromTitle(rawProposal.rationale.title).within(() =>
cy.getByTestId(viewProposalButton).click()
cy.get(viewProposalButton).click()
);
});
voteForProposal('for');
// 3001-VOTE-079
cy.contains('You voted: For').should('be.visible');
cy.getByTestId(proposalVoteProgressForTokens)
.contains('1')
.and('be.visible');
cy.get(proposalVoteProgressForTokens).contains('1').and('be.visible');
cy.getByTestId(voteBreakdownToggle).click();
getProposalInformationFromTable('Total Supply')
.invoke('text')
@@ -261,22 +258,22 @@ describe(
navigateTo(navigation.proposals);
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
getProposalFromTitle(rawProposal.rationale.title).within(() =>
cy.getByTestId(viewProposalButton).click()
cy.get(viewProposalButton).click()
);
});
cy.getByTestId(proposalVoteProgressForPercentage)
cy.get(proposalVoteProgressForPercentage)
.contains('100.00%')
.and('be.visible');
cy.getByTestId(proposalVoteProgressAgainstPercentage)
cy.get(proposalVoteProgressAgainstPercentage)
.contains('0.00%')
.and('be.visible');
// 3001-VOTE-065
cy.getByTestId(changeVoteButton).should('be.visible').click();
cy.get(changeVoteButton).should('be.visible').click();
voteForProposal('for');
cy.getByTestId(proposalVoteProgressForTokens)
cy.get(proposalVoteProgressForTokens)
.contains(tokensRequiredToAchieveResult)
.and('be.visible');
cy.getByTestId(proposalVoteProgressAgainstTokens)
cy.get(proposalVoteProgressAgainstTokens)
.contains('0.00')
.and('be.visible');
cy.getByTestId(voteBreakdownToggle).click();
@@ -313,7 +310,7 @@ describe(
createRawProposal();
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
getProposalFromTitle(rawProposal.rationale.title).within(() =>
cy.getByTestId(viewProposalButton).click()
cy.get(viewProposalButton).click()
);
voteForProposal('for');
cy.contains('You voted: For').should('be.visible');
@@ -322,16 +319,13 @@ describe(
stakingPageAssociateTokens('2');
navigateTo(navigation.proposals);
getProposalFromTitle(rawProposal.rationale.title).within(() =>
cy.getByTestId(viewProposalButton).click()
cy.get(viewProposalButton).click()
);
cy.getByTestId('you-voted').should('not.exist');
voteForProposal('against');
cy.contains('You voted: Against').should('be.visible');
switchVegaWalletPubKey();
cy.getByTestId(proposalVoteProgressForTokens).should(
'contain.text',
'1.00'
);
cy.get(proposalVoteProgressForTokens).should('contain.text', '1.00');
// Checking vote status for different public keys is displayed correctly
cy.contains('You voted: For').should('be.visible');
});
@@ -19,13 +19,13 @@ import { ethereumWalletConnect } from '../../support/wallet-eth.functions';
import { vegaWalletSetSpecifiedApprovalAmount } from '../../support/wallet-functions';
const proposalListItem = '[data-testid="proposals-list-item"]';
const closedProposals = 'closed-proposals';
const proposalStatus = 'proposal-status';
const viewProposalButton = 'view-proposal-btn';
const votesTable = 'votes-table';
const openProposals = 'open-proposals';
const closedProposals = '[data-testid="closed-proposals"]';
const proposalStatus = '[data-testid="proposal-status"]';
const viewProposalButton = '[data-testid="view-proposal-btn"]';
const votesTable = '[data-testid="votes-table"]';
const openProposals = '[data-testid="open-proposals"]';
const proposalVoteProgressForPercentage =
'vote-progress-indicator-percentage-for';
'[data-testid="vote-progress-indicator-percentage-for"]';
const proposalTimeout = { timeout: 8000 };
context(
@@ -55,18 +55,18 @@ context(
cy.createMarket();
cy.reload();
waitForSpinner();
cy.getByTestId(closedProposals).within(() => {
cy.get(closedProposals).within(() => {
cy.contains(proposalTitle)
.parentsUntil(proposalListItem)
.last()
.within(() => {
cy.getByTestId(proposalStatus).should('have.text', 'Enacted');
cy.getByTestId(viewProposalButton).click();
cy.get(proposalStatus).should('have.text', 'Enacted');
cy.get(viewProposalButton).click();
});
});
cy.getByTestId('proposal-type').should('have.text', 'New market');
cy.getByTestId(proposalStatus).should('have.text', 'Enacted');
cy.getByTestId(votesTable).within(() => {
cy.get(proposalStatus).should('have.text', 'Enacted');
cy.get(votesTable).within(() => {
cy.contains('Vote passed.').should('be.visible');
cy.contains('Voting has ended.').should('be.visible');
});
@@ -81,27 +81,27 @@ context(
navigateTo(navigation.proposals);
cy.reload();
waitForSpinner();
cy.getByTestId(openProposals).within(() => {
cy.get(openProposals).within(() => {
cy.contains(proposalTitle)
.parentsUntil(proposalListItem)
.last()
.within(() => cy.getByTestId(viewProposalButton).click());
.within(() => cy.get(viewProposalButton).click());
});
cy.getByTestId(proposalStatus).should('have.text', 'Open');
cy.get(proposalStatus).should('have.text', 'Open');
voteForProposal('for');
cy.getByTestId(proposalStatus, proposalTimeout)
cy.get(proposalStatus, proposalTimeout)
.should('have.text', 'Passed')
.then(() => {
cy.getByTestId(proposalStatus, proposalTimeout).should(
cy.get(proposalStatus, proposalTimeout).should(
'have.text',
'Enacted'
);
});
cy.getByTestId(votesTable).within(() => {
cy.get(votesTable).within(() => {
cy.contains('Vote passed.').should('be.visible');
cy.contains('Voting has ended.').should('be.visible');
});
cy.getByTestId(proposalVoteProgressForPercentage)
cy.get(proposalVoteProgressForPercentage)
.contains('100.00%')
.and('be.visible');
});
@@ -115,18 +115,15 @@ context(
navigateTo(navigation.proposals);
cy.reload();
waitForSpinner();
cy.getByTestId(openProposals, { timeout: 6000 }).within(() => {
cy.get(openProposals, { timeout: 6000 }).within(() => {
cy.contains(proposalTitle)
.parentsUntil(proposalListItem)
.last()
.within(() => cy.getByTestId(viewProposalButton).click());
.within(() => cy.get(viewProposalButton).click());
});
cy.getByTestId(proposalStatus).should('have.text', 'Open');
cy.get(proposalStatus).should('have.text', 'Open');
voteForProposal('for');
cy.getByTestId(proposalStatus, proposalTimeout).should(
'have.text',
'Enacted'
);
cy.get(proposalStatus, proposalTimeout).should('have.text', 'Enacted');
});
// 3001-VOTE-048 3001-VOTE-049 3001-VOTE-050
@@ -137,17 +134,14 @@ context(
navigateTo(navigation.proposals);
cy.reload();
waitForSpinner();
cy.getByTestId(openProposals).within(() => {
cy.get(openProposals).within(() => {
cy.contains(proposalTitle)
.parentsUntil(proposalListItem)
.last()
.within(() => cy.getByTestId(viewProposalButton).click());
.within(() => cy.get(viewProposalButton).click());
});
cy.getByTestId(proposalStatus).should('have.text', 'Open');
cy.getByTestId(proposalStatus, proposalTimeout).should(
'have.text',
'Declined'
);
cy.get(proposalStatus).should('have.text', 'Open');
cy.get(proposalStatus, proposalTimeout).should('have.text', 'Declined');
getProposalInformationFromTable('Rejection reason')
.contains('PROPOSAL_ERROR_PARTICIPATION_THRESHOLD_NOT_REACHED')
.and('be.visible');
@@ -36,19 +36,20 @@ import {
import { ethereumWalletConnect } from '../../support/wallet-eth.functions';
import type { testFreeformProposal } from '../../support/common-interfaces';
const vegaWalletStakedBalances = 'vega-wallet-balance-staked-validators';
const vegaWalletAssociatedBalance = 'associated-amount';
const vegaWalletNameElement = 'wallet-name';
const vegaWallet = 'vega-wallet';
const connectToVegaWalletButton = 'connect-to-vega-wallet-btn';
const newProposalSubmitButton = 'proposal-submit';
const viewProposalButton = 'view-proposal-btn';
const rawProposalData = 'proposal-data';
const voteButtons = 'vote-buttons';
const vegaWalletStakedBalances =
'[data-testid="vega-wallet-balance-staked-validators"]';
const vegaWalletAssociatedBalance = '[data-testid="associated-amount"]';
const vegaWalletNameElement = '[data-testid="wallet-name"]';
const vegaWallet = '[data-testid="vega-wallet"]';
const connectToVegaWalletButton = '[data-testid="connect-to-vega-wallet-btn"]';
const newProposalSubmitButton = '[data-testid="proposal-submit"]';
const viewProposalButton = '[data-testid="view-proposal-btn"]';
const rawProposalData = '[data-testid="proposal-data"]';
const voteButtons = '[data-testid="vote-buttons"]';
const rejectProposalsLink = '[href="/proposals/rejected"]';
const feedbackError = 'Error';
const noOpenProposals = 'no-open-proposals';
const noClosedProposals = 'no-closed-proposals';
const feedbackError = '[data-testid="Error"]';
const noOpenProposals = '[data-testid="no-open-proposals"]';
const noClosedProposals = '[data-testid="no-closed-proposals"]';
const txTimeout = Cypress.env('txTimeout');
const epochTimeout = Cypress.env('epochTimeout');
const proposalTimeout = { timeout: 14000 };
@@ -92,10 +93,10 @@ context(
// Test can only pass if run before other proposal tests.
it.skip('Should be able to see that no proposals exist', function () {
// 3001-VOTE-003
cy.getByTestId(noOpenProposals)
cy.get(noOpenProposals)
.should('be.visible')
.and('have.text', 'There are no open or yet to enact proposals');
cy.getByTestId(noClosedProposals)
cy.get(noClosedProposals)
.should('be.visible')
.and('have.text', 'There are no enacted or rejected proposals');
});
@@ -125,10 +126,7 @@ context(
stakingValidatorPageAddStake('2');
closeStakingDialog();
cy.getByTestId(vegaWalletStakedBalances, txTimeout).should(
'contain',
'2'
);
cy.get(vegaWalletStakedBalances, txTimeout).should('contain', '2');
createRawProposal();
});
@@ -170,7 +168,7 @@ context(
getProposalFromTitle(rawProposal.rationale.title).within(() => {
cy.contains('Rejected').should('be.visible');
cy.contains('Close time too late').should('be.visible');
cy.getByTestId(viewProposalButton).click();
cy.get(viewProposalButton).click();
});
});
cy.getByTestId('proposal-status').should('have.text', 'Rejected');
@@ -187,14 +185,14 @@ context(
const errorMsg =
'Network error: the network blocked the transaction through the spam protection: party has insufficient associated governance tokens in their staking account to submit proposal request (ABCI code 89)';
vegaWalletTeardown();
cy.getByTestId(vegaWalletAssociatedBalance, txTimeout).contains(
cy.get(vegaWalletAssociatedBalance, txTimeout).contains(
'0.00',
txTimeout
);
goToMakeNewProposal(governanceProposalType.RAW);
enterRawProposalBody(createTenDigitUnixTimeStampForSpecifiedDays(8));
cy.contains('Transaction failed', proposalTimeout).should('be.visible');
cy.getByTestId(feedbackError).should('have.text', errorMsg);
cy.get(feedbackError).should('have.text', errorMsg);
closeDialog();
});
@@ -207,7 +205,7 @@ context(
goToMakeNewProposal(governanceProposalType.RAW);
enterRawProposalBody(createTenDigitUnixTimeStampForSpecifiedDays(8));
cy.contains('Transaction failed', proposalTimeout).should('be.visible');
cy.getByTestId(feedbackError).should('have.text', errorMsg);
cy.get(feedbackError).should('have.text', errorMsg);
closeDialog();
});
@@ -223,17 +221,17 @@ context(
createTenDigitUnixTimeStampForSpecifiedDays(8);
freeformProposal.unexpected = `i shouldn't be here`;
const proposalPayload = JSON.stringify(freeformProposal);
cy.getByTestId(rawProposalData).type(proposalPayload, {
cy.get(rawProposalData).type(proposalPayload, {
parseSpecialCharSequences: false,
delay: 2,
});
});
cy.getByTestId(newProposalSubmitButton).should('be.visible').click();
cy.get(newProposalSubmitButton).should('be.visible').click();
cy.contains('Transaction failed', proposalTimeout).should('be.visible');
cy.getByTestId(feedbackError).should('have.text', errorMsg);
cy.get(feedbackError).should('have.text', errorMsg);
closeDialog();
cy.getByTestId(rawProposalData)
cy.get(rawProposalData)
.invoke('val')
.should('contain', "i shouldn't be here");
});
@@ -251,15 +249,15 @@ context(
rawProposal.terms.unexpectedField = `i shouldn't be here`;
const proposalPayload = JSON.stringify(rawProposal);
cy.getByTestId(rawProposalData).type(proposalPayload, {
cy.get(rawProposalData).type(proposalPayload, {
parseSpecialCharSequences: false,
delay: 2,
});
});
cy.getByTestId(newProposalSubmitButton).should('be.visible').click();
cy.get(newProposalSubmitButton).should('be.visible').click();
cy.contains('Transaction failed', proposalTimeout).should('be.visible');
cy.getByTestId(feedbackError).should('have.text', errorMsg);
cy.get(feedbackError).should('have.text', errorMsg);
closeDialog();
});
@@ -267,10 +265,10 @@ context(
// 3006-PASC-006 3006-PASC-007 3008-PFRO-018 3008-PFRO-019 3003-PMAN-006 3003-PMAN-007
it('Unable to submit proposal without valid json', function () {
goToMakeNewProposal(governanceProposalType.RAW);
cy.getByTestId(newProposalSubmitButton).click();
cy.get(newProposalSubmitButton).click();
cy.getByTestId('input-error-text').should('have.text', 'Required');
cy.getByTestId(rawProposalData).type('Not a valid json string');
cy.getByTestId(newProposalSubmitButton).click();
cy.get(rawProposalData).type('Not a valid json string');
cy.get(newProposalSubmitButton).click();
cy.getByTestId('input-error-text').should(
'have.text',
'Must be valid JSON'
@@ -285,22 +283,22 @@ context(
submitUniqueRawProposal({ proposalTitle: proposalTitle });
ethereumWalletConnect();
stakingPageDisassociateTokens('0.0001');
cy.getByTestId(vegaWallet)
cy.get(vegaWallet)
.first()
.within(() => {
cy.getByTestId(vegaWalletAssociatedBalance, txTimeout).should(
cy.get(vegaWalletAssociatedBalance, txTimeout).should(
'contain',
'0.9999'
);
});
navigateTo(navigation.proposals);
getProposalFromTitle(proposalTitle).within(() =>
cy.getByTestId(viewProposalButton).click()
cy.get(viewProposalButton).click()
);
cy.contains('Vote breakdown').should('be.visible', {
timeout: 10000,
});
cy.getByTestId(voteButtons).should('not.exist');
cy.get(voteButtons).should('not.exist');
cy.getByTestId('min-proposal-requirements').should(
'have.text',
`You must have at least ${this.minVoterBalance} VEGA associated to vote on this proposal`
@@ -313,20 +311,20 @@ context(
cy.get('[data-testid="disconnect"]').click();
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
getProposalFromTitle(rawProposal.rationale.title).within(() =>
cy.getByTestId(viewProposalButton).click()
cy.get(viewProposalButton).click()
);
});
// 3001-VOTE-075
// 3001-VOTE-076
cy.getByTestId(connectToVegaWalletButton)
cy.get(connectToVegaWalletButton)
.should('be.visible')
.and('have.text', 'Connect Vega wallet')
.click();
cy.getByTestId('connector-jsonRpc').click();
cy.getByTestId(vegaWalletNameElement).should('be.visible');
cy.getByTestId(connectToVegaWalletButton).should('not.exist');
cy.get(vegaWalletNameElement).should('be.visible');
cy.get(connectToVegaWalletButton).should('not.exist');
// 3001-VOTE-100
cy.getByTestId(vegaWalletAssociatedBalance, txTimeout).contains(
cy.get(vegaWalletAssociatedBalance, txTimeout).contains(
'1.00',
txTimeout
);
@@ -31,25 +31,27 @@ import {
} from '../../support/wallet-functions';
const proposalListItem = '[data-testid="proposals-list-item"]';
const openProposals = 'open-proposals';
const proposalType = 'proposal-type';
const proposalDetails = 'proposal-details';
const newProposalSubmitButton = 'proposal-submit';
const proposalVoteDeadline = 'proposal-vote-deadline';
const proposalParameterSelect = 'proposal-parameter-select';
const proposalMarketSelect = 'proposal-market-select';
const newProposalTitle = 'proposal-title';
const newProposalDescription = 'proposal-description';
const newProposalTerms = 'proposal-terms';
const newProposedParameterValue = 'selected-proposal-param-new-value';
const minVoteDeadline = 'min-vote';
const maxVoteDeadline = 'max-vote';
const minValidationDeadline = 'min-validation';
const minEnactDeadline = 'min-enactment';
const maxEnactDeadline = 'max-enactment';
const inputError = 'input-error-text';
const enactmentDeadlineError = 'enactment-before-voting-deadline';
const proposalDownloadBtn = 'proposal-download-json';
const openProposals = '[data-testid="open-proposals"]';
const proposalType = '[data-testid="proposal-type"]';
const proposalDetails = '[data-testid="proposal-details"]';
const newProposalSubmitButton = '[data-testid="proposal-submit"]';
const proposalVoteDeadline = '[data-testid="proposal-vote-deadline"]';
const proposalParameterSelect = '[data-testid="proposal-parameter-select"]';
const proposalMarketSelect = '[data-testid="proposal-market-select"]';
const newProposalTitle = '[data-testid="proposal-title"]';
const newProposalDescription = '[data-testid="proposal-description"]';
const newProposalTerms = '[data-testid="proposal-terms"]';
const newProposedParameterValue =
'[data-testid="selected-proposal-param-new-value"]';
const minVoteDeadline = '[data-testid="min-vote"]';
const maxVoteDeadline = '[data-testid="max-vote"]';
const minValidationDeadline = '[data-testid="min-validation"]';
const minEnactDeadline = '[data-testid="min-enactment"]';
const maxEnactDeadline = '[data-testid="max-enactment"]';
const inputError = '[data-testid="input-error-text"]';
const enactmentDeadlineError =
'[data-testid="enactment-before-voting-deadline"]';
const proposalDownloadBtn = '[data-testid="proposal-download-json"]';
const feedbackError = '[data-testid="Error"]';
const viewProposalBtn = 'view-proposal-btn';
const liquidityVoteStatus = 'liquidity-votes-status';
@@ -86,20 +88,18 @@ context(
it('Unable to submit network parameter with missing/invalid fields', function () {
navigateTo(navigation.proposals);
goToMakeNewProposal(governanceProposalType.NETWORK_PARAMETER);
cy.getByTestId(proposalDownloadBtn).click();
cy.getByTestId(inputError).should('have.length', 3);
cy.getByTestId(newProposalTitle).type(
cy.get(proposalDownloadBtn).click();
cy.get(inputError).should('have.length', 3);
cy.get(newProposalTitle).type(
'Invalid update network parameter proposal'
);
cy.getByTestId(newProposalDescription).type(
'E2E invalid test for proposals'
);
cy.getByTestId(proposalParameterSelect).select(
cy.get(newProposalDescription).type('E2E invalid test for proposals');
cy.get(proposalParameterSelect).select(
'spam_protection_proposal_min_tokens'
);
cy.getByTestId(newProposedParameterValue).type('0');
cy.getByTestId(proposalVoteDeadline).clear().type('0');
cy.getByTestId(proposalDownloadBtn)
cy.get(newProposedParameterValue).type('0');
cy.get(proposalVoteDeadline).clear().type('0');
cy.get(proposalDownloadBtn)
.click()
.then(() => {
cy.wrap(
@@ -109,7 +109,7 @@ context(
submitUniqueRawProposal({ proposalBody: filePath, submit: false });
});
});
cy.getByTestId(newProposalSubmitButton).click();
cy.get(newProposalSubmitButton).click();
validateDialogContentMsg('PROPOSAL_ERROR_CLOSE_TIME_TOO_SOON');
});
@@ -117,33 +117,29 @@ context(
it('Able to download and submit network param proposal', function () {
goToMakeNewProposal(governanceProposalType.NETWORK_PARAMETER);
// 3007-PNEC-006
cy.getByTestId(newProposalTitle)
cy.get(newProposalTitle)
.siblings()
.should('contain.text', '(100 characters or less)');
// 3007-PNEC-004 3007-PNEC-005
cy.getByTestId(newProposalTitle).type(
'Test update network parameter proposal'
);
cy.get(newProposalTitle).type('Test update network parameter proposal');
// 3007-PNEC-009
cy.getByTestId(newProposalDescription)
cy.get(newProposalDescription)
.siblings()
.should('contain.text', '(20,000 characters or less)');
// 3007-PNEC-007 3007-PNEC-008
cy.getByTestId(newProposalDescription).type(
'E2E test for downloading proposals'
);
cy.get(newProposalDescription).type('E2E test for downloading proposals');
// 3007-PNEC-010
cy.getByTestId(proposalParameterSelect).select(
cy.get(proposalParameterSelect).select(
'governance_proposal_asset_minClose'
);
// 3007-PNEC-011
cy.getByTestId(newProposedParameterValue).type('10s');
cy.get(newProposedParameterValue).type('10s');
// 3007-PNEC-012
cy.getByTestId(proposalVoteDeadline).clear().type('2');
cy.get(proposalVoteDeadline).clear().type('2');
// 3007-PNEC-013 3007-PNEC-014
cy.getByTestId('voting-date').invoke('text').should('not.be.empty');
// 3007-PNEC-015
cy.getByTestId(maxEnactDeadline).click();
cy.get(maxEnactDeadline).click();
// 3007-PNEC-016
cy.getByTestId('enactment-date').invoke('text').should('not.be.empty');
// 3007-PNEC-017
@@ -152,7 +148,7 @@ context(
).should('be.visible');
// 3007-PNE-018
cy.log('Download updated proposal file');
cy.getByTestId(proposalDownloadBtn)
cy.get(proposalDownloadBtn)
.should('be.visible')
.click()
.then(() => {
@@ -182,21 +178,19 @@ context(
it('Unable to submit network parameter proposal with vote deadline above enactment deadline', function () {
navigateTo(navigation.proposals);
goToMakeNewProposal(governanceProposalType.NETWORK_PARAMETER);
cy.getByTestId(newProposalTitle).type(
'Test update network parameter proposal'
);
cy.getByTestId(newProposalDescription).type('invalid deadlines');
cy.getByTestId(proposalParameterSelect).select(
cy.get(newProposalTitle).type('Test update network parameter proposal');
cy.get(newProposalDescription).type('invalid deadlines');
cy.get(proposalParameterSelect).select(
'spam_protection_proposal_min_tokens'
);
cy.getByTestId(newProposedParameterValue).type('0');
cy.getByTestId(proposalVoteDeadline).clear().type('0');
cy.getByTestId(maxVoteDeadline).click();
cy.getByTestId(enactmentDeadlineError).should(
cy.get(newProposedParameterValue).type('0');
cy.get(proposalVoteDeadline).clear().type('0');
cy.get(maxVoteDeadline).click();
cy.get(enactmentDeadlineError).should(
'have.text',
'The proposal will fail if enactment is earlier than the voting deadline'
);
cy.getByTestId(proposalDownloadBtn)
cy.get(proposalDownloadBtn)
.should('be.visible')
.click()
.then(() => {
@@ -207,7 +201,7 @@ context(
submitUniqueRawProposal({ proposalBody: filePath, submit: false });
});
});
cy.getByTestId(newProposalSubmitButton).click();
cy.get(newProposalSubmitButton).click();
validateFeedBackMsg(
'Invalid params: proposal_submission.terms.closing_timestamp (cannot be after enactment time)'
);
@@ -220,16 +214,16 @@ context(
function () {
const proposalTitle = 'Test new market proposal';
goToMakeNewProposal(governanceProposalType.NEW_MARKET);
cy.getByTestId(newProposalTitle).type('Test new market proposal');
cy.getByTestId(newProposalDescription).type('E2E test for proposals');
cy.get(newProposalTitle).type('Test new market proposal');
cy.get(newProposalDescription).type('E2E test for proposals');
cy.fixture('/proposals/new-market').then((newMarketProposal) => {
const newMarketPayload = JSON.stringify(newMarketProposal);
cy.getByTestId(newProposalTerms).type(newMarketPayload, {
cy.get(newProposalTerms).type(newMarketPayload, {
parseSpecialCharSequences: false,
delay: 2,
});
});
cy.getByTestId(proposalDownloadBtn)
cy.get(proposalDownloadBtn)
.should('be.visible')
.click()
.then(() => {
@@ -265,19 +259,19 @@ context(
'Invalid params: the transaction does not use a valid Vega command: unknown field "invalid" in vega.NewMarket';
goToMakeNewProposal(governanceProposalType.NEW_MARKET);
cy.getByTestId(proposalDownloadBtn).should('be.visible').click();
cy.getByTestId(inputError).should('have.length', 3);
cy.getByTestId(newProposalTitle).type('Test new market proposal');
cy.getByTestId(newProposalDescription).type('E2E test for proposals');
cy.get(proposalDownloadBtn).should('be.visible').click();
cy.get(inputError).should('have.length', 3);
cy.get(newProposalTitle).type('Test new market proposal');
cy.get(newProposalDescription).type('E2E test for proposals');
cy.fixture('/proposals/new-market').then((newMarketProposal) => {
newMarketProposal.invalid = 'I am an invalid field';
const newMarketPayload = JSON.stringify(newMarketProposal);
cy.getByTestId(newProposalTerms).type(newMarketPayload, {
cy.get(newProposalTerms).type(newMarketPayload, {
parseSpecialCharSequences: false,
delay: 2,
});
});
cy.getByTestId(proposalDownloadBtn)
cy.get(proposalDownloadBtn)
.should('be.visible')
.click()
.then(() => {
@@ -288,7 +282,7 @@ context(
submitUniqueRawProposal({ proposalBody: filePath, submit: false });
});
});
cy.getByTestId(newProposalSubmitButton).should('be.visible').click();
cy.get(newProposalSubmitButton).should('be.visible').click();
cy.contains('Transaction failed', proposalTimeout).should('be.visible');
validateFeedBackMsg(errorMsg);
});
@@ -299,19 +293,17 @@ context(
switchVegaWalletPubKey();
stakingPageAssociateTokens('1');
goToMakeNewProposal(governanceProposalType.UPDATE_MARKET);
cy.getByTestId(newProposalTitle).type(
'Test update market proposal - rejected'
);
cy.getByTestId(newProposalDescription).type('E2E test for proposals');
cy.getByTestId(proposalMarketSelect).select('Test market 1');
cy.get(newProposalTitle).type('Test update market proposal - rejected');
cy.get(newProposalDescription).type('E2E test for proposals');
cy.get(proposalMarketSelect).select('Test market 1');
cy.fixture('/proposals/update-market').then((updateMarketProposal) => {
const newUpdateMarketProposal = JSON.stringify(updateMarketProposal);
cy.getByTestId(newProposalTerms).type(newUpdateMarketProposal, {
cy.get(newProposalTerms).type(newUpdateMarketProposal, {
parseSpecialCharSequences: false,
delay: 2,
});
});
cy.getByTestId(proposalDownloadBtn)
cy.get(proposalDownloadBtn)
.should('be.visible')
.click()
.then(() => {
@@ -322,7 +314,7 @@ context(
submitUniqueRawProposal({ proposalBody: filePath, submit: false });
});
});
cy.getByTestId(newProposalSubmitButton).should('be.visible').click();
cy.get(newProposalSubmitButton).should('be.visible').click();
cy.contains('Proposal rejected', proposalTimeout).should('be.visible');
validateDialogContentMsg('PROPOSAL_ERROR_INSUFFICIENT_EQUITY_LIKE_SHARE');
closeDialog();
@@ -340,19 +332,17 @@ context(
vegaWalletPublicKey
);
goToMakeNewProposal(governanceProposalType.UPDATE_MARKET);
cy.getByTestId(newProposalTitle).type(
'Test update market proposal - rejected'
);
cy.getByTestId(newProposalDescription).type('E2E test for proposals');
cy.getByTestId(proposalMarketSelect).select('Test market 1');
cy.get(newProposalTitle).type('Test update market proposal - rejected');
cy.get(newProposalDescription).type('E2E test for proposals');
cy.get(proposalMarketSelect).select('Test market 1');
cy.fixture('/proposals/update-market').then((updateMarketProposal) => {
const newUpdateMarketProposal = JSON.stringify(updateMarketProposal);
cy.getByTestId(newProposalTerms).type(newUpdateMarketProposal, {
cy.get(newProposalTerms).type(newUpdateMarketProposal, {
parseSpecialCharSequences: false,
delay: 2,
});
});
cy.getByTestId(proposalDownloadBtn)
cy.get(proposalDownloadBtn)
.should('be.visible')
.click()
.then(() => {
@@ -363,7 +353,7 @@ context(
submitUniqueRawProposal({ proposalBody: filePath, submit: false });
});
});
cy.getByTestId(newProposalSubmitButton).should('be.visible').click();
cy.get(newProposalSubmitButton).should('be.visible').click();
cy.contains('Transaction failed', proposalTimeout).should('be.visible');
validateFeedBackMsg(
'Network error: the network blocked the transaction through the spam protection: party has insufficient associated governance tokens in their staking account to submit proposal request (ABCI code 89)'
@@ -378,9 +368,9 @@ context(
vegaWalletPublicKey
);
goToMakeNewProposal(governanceProposalType.UPDATE_MARKET);
cy.getByTestId(newProposalTitle).type('Test update market proposal');
cy.getByTestId(newProposalDescription).type('E2E test for proposals');
cy.getByTestId(proposalMarketSelect).select('Test market 1');
cy.get(newProposalTitle).type('Test update market proposal');
cy.get(newProposalDescription).type('E2E test for proposals');
cy.get(proposalMarketSelect).select('Test market 1');
cy.get('[data-testid="update-market-details"]').within(() => {
cy.get('dd').eq(0).should('have.text', 'Test market 1');
cy.get('dd').eq(1).should('have.text', 'TEST.24h');
@@ -395,12 +385,12 @@ context(
});
cy.fixture('/proposals/update-market').then((updateMarketProposal) => {
const newUpdateMarketProposal = JSON.stringify(updateMarketProposal);
cy.getByTestId(newProposalTerms).type(newUpdateMarketProposal, {
cy.get(newProposalTerms).type(newUpdateMarketProposal, {
parseSpecialCharSequences: false,
delay: 2,
});
});
cy.getByTestId(proposalDownloadBtn)
cy.get(proposalDownloadBtn)
.should('be.visible')
.click()
.then(() => {
@@ -447,19 +437,19 @@ context(
it('Able to submit new asset proposal using min deadlines', function () {
const proposalTitle = 'Test new asset proposal';
goToMakeNewProposal(governanceProposalType.NEW_ASSET);
cy.getByTestId(newProposalTitle).type(proposalTitle);
cy.getByTestId(newProposalDescription).type('E2E test for proposals');
cy.get(newProposalTitle).type(proposalTitle);
cy.get(newProposalDescription).type('E2E test for proposals');
cy.fixture('/proposals/new-asset').then((newAssetProposal) => {
const newAssetPayload = JSON.stringify(newAssetProposal);
cy.getByTestId(newProposalTerms).type(newAssetPayload, {
cy.get(newProposalTerms).type(newAssetPayload, {
parseSpecialCharSequences: false,
delay: 2,
});
});
cy.getByTestId(minVoteDeadline).click();
cy.getByTestId(minValidationDeadline).click();
cy.getByTestId(minEnactDeadline).click();
cy.getByTestId(proposalDownloadBtn)
cy.get(minVoteDeadline).click();
cy.get(minValidationDeadline).click();
cy.get(minEnactDeadline).click();
cy.get(proposalDownloadBtn)
.should('be.visible')
.click()
.then(() => {
@@ -470,9 +460,9 @@ context(
submitUniqueRawProposal({ proposalBody: filePath, submit: false }); // 3005-PASN-003
});
});
cy.getByTestId(newProposalSubmitButton).should('be.visible').click();
cy.get(newProposalSubmitButton).should('be.visible').click();
closeDialog();
cy.getByTestId(newProposalSubmitButton).should('be.visible').click();
cy.get(newProposalSubmitButton).should('be.visible').click();
// cannot submit a proposal with ERC20 address already in use
cy.contains('Proposal rejected', proposalTimeout).should('be.visible');
validateDialogContentMsg('PROPOSAL_ERROR_ERC20_ADDRESS_ALREADY_IN_USE');
@@ -493,8 +483,8 @@ context(
it('Unable to submit new asset proposal with missing/invalid fields', function () {
goToMakeNewProposal(governanceProposalType.NEW_ASSET);
cy.getByTestId(proposalDownloadBtn).should('be.visible').click();
cy.getByTestId(inputError).should('have.length', 3);
cy.get(proposalDownloadBtn).should('be.visible').click();
cy.get(inputError).should('have.length', 3);
});
it('Able to submit update asset proposal using min deadline', function () {
@@ -503,9 +493,9 @@ context(
goToMakeNewProposal(governanceProposalType.UPDATE_ASSET);
enterUpdateAssetProposalDetails();
cy.getByTestId(minVoteDeadline).click();
cy.getByTestId(minEnactDeadline).click();
cy.getByTestId(proposalDownloadBtn)
cy.get(minVoteDeadline).click();
cy.get(minEnactDeadline).click();
cy.get(proposalDownloadBtn)
.should('be.visible')
.click()
.then(() => {
@@ -517,16 +507,13 @@ context(
});
});
navigateTo(navigation.proposals);
cy.getByTestId(openProposals).within(() => {
cy.getByTestId(proposalType)
cy.get(openProposals).within(() => {
cy.get(proposalType)
.contains('Update asset')
.parentsUntil(proposalListItem)
.last()
.within(() => {
cy.getByTestId(proposalDetails).should(
'contain.text',
assetId.slice(0, 6)
); // 3001-VOTE-029
cy.get(proposalDetails).should('contain.text', assetId.slice(0, 6)); // 3001-VOTE-029
cy.getByTestId(viewProposalBtn).click();
});
});
@@ -546,9 +533,9 @@ context(
it('Able to submit update asset proposal using max deadline', function () {
goToMakeNewProposal(governanceProposalType.UPDATE_ASSET);
enterUpdateAssetProposalDetails();
cy.getByTestId(maxVoteDeadline).click();
cy.getByTestId(maxEnactDeadline).click();
cy.getByTestId(proposalDownloadBtn)
cy.get(maxVoteDeadline).click();
cy.get(maxEnactDeadline).click();
cy.get(proposalDownloadBtn)
.should('be.visible')
.click()
.then(() => {
@@ -563,36 +550,36 @@ context(
it('Unable to submit edit asset proposal with missing/invalid fields', function () {
goToMakeNewProposal(governanceProposalType.UPDATE_ASSET);
cy.getByTestId(proposalDownloadBtn).should('be.visible').click();
cy.getByTestId(inputError).should('have.length', 3);
cy.get(proposalDownloadBtn).should('be.visible').click();
cy.get(inputError).should('have.length', 3);
});
it('Able to download and submit freeform proposal', function () {
goToMakeNewProposal(governanceProposalType.FREEFORM);
// 3008-PFRO-006
cy.getByTestId(newProposalTitle)
cy.get(newProposalTitle)
.siblings()
.should('contain.text', '(100 characters or less)'); // 3008-PFRO-007
// 3008-PFRO-005
cy.getByTestId(newProposalTitle).type('Test freeform proposal form');
cy.get(newProposalTitle).type('Test freeform proposal form');
// 3008-PFRO-009
cy.getByTestId(newProposalDescription)
cy.get(newProposalDescription)
.siblings()
.should('contain.text', '(20,000 characters or less)'); // 3008-PFRO-010
// 3008-PFRO-008 3002-PROP-012 3002-PROP-016
cy.getByTestId(newProposalDescription).type(
cy.get(newProposalDescription).type(
'E2E test for downloading freeform proposal'
);
// 3008-PFRO-012
cy.getByTestId(minVoteDeadline).should('exist'); // 3002-PROP-008
cy.getByTestId(maxVoteDeadline).should('exist');
cy.get(minVoteDeadline).should('exist'); // 3002-PROP-008
cy.get(maxVoteDeadline).should('exist');
// 3008-PFRO-011
cy.getByTestId(proposalVoteDeadline).clear().type('2');
cy.get(proposalVoteDeadline).clear().type('2');
// 3008-PFRO-013 3008-PFRO-014
cy.getByTestId('voting-date').invoke('text').should('not.be.empty');
// 3008-PFRO-015
cy.log('Download updated proposal file');
cy.getByTestId(proposalDownloadBtn)
cy.get(proposalDownloadBtn)
.should('be.visible')
.click()
.then(() => {
@@ -626,11 +613,11 @@ context(
}
function enterUpdateAssetProposalDetails() {
cy.getByTestId(newProposalTitle).type('Test update asset proposal');
cy.getByTestId(newProposalDescription).type('E2E test for proposals');
cy.get(newProposalTitle).type('Test update asset proposal');
cy.get(newProposalDescription).type('E2E test for proposals');
cy.fixture('/proposals/update-asset').then((newAssetProposal) => {
const newAssetPayload = JSON.stringify(newAssetProposal);
cy.getByTestId(newProposalTerms).type(newAssetPayload, {
cy.get(newProposalTerms).type(newAssetPayload, {
parseSpecialCharSequences: false,
delay: 2,
});
@@ -24,12 +24,12 @@ import { ethereumWalletConnect } from '../../support/wallet-eth.functions';
import { vegaWalletSetSpecifiedApprovalAmount } from '../../support/wallet-functions';
const proposalListItem = 'proposals-list-item';
const openProposals = 'open-proposals';
const openProposals = '[data-testid="open-proposals"]';
const voteStatus = 'vote-status';
const proposalType = 'proposal-type';
const proposalStatus = 'proposal-status';
const proposalClosingDate = 'vote-details';
const viewProposalButton = 'view-proposal-btn';
const proposalClosingDate = '[data-testid="vote-details"]';
const viewProposalButton = '[data-testid="view-proposal-btn"]';
const voteBreakDownToggle = 'vote-breakdown-toggle';
describe('Governance flow for proposal list', { tags: '@slow' }, function () {
@@ -62,13 +62,13 @@ describe('Governance flow for proposal list', { tags: '@slow' }, function () {
}
navigateTo(navigation.proposals);
cy.getByTestId(openProposals).within(() => {
cy.getByTestId(proposalClosingDate)
cy.get(openProposals).within(() => {
cy.get(proposalClosingDate)
.first()
.invoke('text')
.should('match', /days|minutes/);
cy.getByTestId(proposalClosingDate).should('contain.text', 'months');
cy.getByTestId(proposalClosingDate).last().should('contain.text', 'year');
cy.get(proposalClosingDate).should('contain.text', 'months');
cy.get(proposalClosingDate).last().should('contain.text', 'year');
});
});
@@ -77,7 +77,7 @@ describe('Governance flow for proposal list', { tags: '@slow' }, function () {
const proposalTitle = generateFreeFormProposalTitle();
submitUniqueRawProposal({ proposalTitle: proposalTitle });
cy.get('[data-testid="proposal-filter-toggle"]').click();
cy.get('[data-testid="set-proposals-filter-visible"]').click();
cy.get('[data-testid="filter-input"]').type(proposerId);
// cy.get(`#${proposalId}`).should('contain', proposalId);
cy.contains(proposalTitle).should('be.visible');
@@ -106,7 +106,7 @@ describe('Governance flow for proposal list', { tags: '@slow' }, function () {
createRawProposal(this.minProposerBalance);
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
getProposalFromTitle(rawProposal.rationale.title).within(() => {
cy.getByTestId(viewProposalButton).should('be.visible');
cy.get(viewProposalButton).should('be.visible');
cy.getByTestId(proposalType).should('have.text', 'Freeform');
cy.getByTestId(proposalStatus).should('have.text', 'Open');
});
@@ -124,13 +124,13 @@ describe('Governance flow for proposal list', { tags: '@slow' }, function () {
'have.text',
'Participation not reached'
);
cy.getByTestId(viewProposalButton).click();
cy.get(viewProposalButton).click();
});
voteForProposal('for');
navigateTo(navigation.proposals);
getProposalFromTitle(proposalTitle).within(() => {
cy.getByTestId(voteStatus).should('have.text', 'Set to pass');
cy.getByTestId(viewProposalButton).click();
cy.get(viewProposalButton).click();
});
cy.getByTestId(voteBreakDownToggle).click();
getProposalInformationFromTable('Token participation met')
@@ -17,7 +17,8 @@ import {
} from '../../support/wallet-functions';
const vegaAssetAddress = '0x67175Da1D5e966e40D11c4B2519392B2058373de';
const vegaWalletUnstakedBalance = 'vega-wallet-balance-unstaked';
const vegaWalletUnstakedBalance =
'[data-testid="vega-wallet-balance-unstaked"]';
const rewardsTable = 'epoch-total-rewards-table';
const txTimeout = Cypress.env('txTimeout');
const rewardsTimeOut = { timeout: 60000 };
@@ -39,7 +40,7 @@ context('rewards - flow', { tags: '@slow' }, function () {
cy.associateTokensToVegaWallet('6000');
navigateTo(navigation.validators);
cy.VegaWalletTopUpRewardsPool();
cy.getByTestId(vegaWalletUnstakedBalance, txTimeout).should(
cy.get(vegaWalletUnstakedBalance, txTimeout).should(
'contain',
'6,000.0',
txTimeout
@@ -4,6 +4,7 @@ import {
verifyStakedBalance,
verifyEthWalletTotalAssociatedBalance,
verifyEthWalletAssociatedBalance,
waitForSpinner,
navigateTo,
navigation,
turnTelemetryOff,
@@ -57,7 +58,8 @@ context(
before('visit staking tab and connect vega wallet', function () {
cy.visit('/');
ethereumWalletConnect();
cy.connectVegaWallet();
// this is a workaround for #2422 which can be removed once issue is resolved
cy.associateTokensToVegaWallet('4');
vegaWalletSetSpecifiedApprovalAmount('1000');
});
@@ -67,9 +69,10 @@ context(
function () {
cy.clearLocalStorage();
turnTelemetryOff();
// Go to homepage to allow wallet teardown without epoch timer refreshing page
navigateTo(navigation.home);
vegaWalletTeardown();
cy.reload();
waitForSpinner();
cy.connectVegaWallet();
ethereumWalletConnect();
navigateTo(navigation.validators);
}
);
@@ -127,7 +130,6 @@ context(
cy.getByTestId('staked-by-user-tooltip')
.first()
.should('have.text', 'Staked by me: 2.00');
waitForBeginningOfEpoch();
cy.getByTestId('total-pending-stake').first().realHover();
cy.getByTestId('pending-user-stake-tooltip')
.first()
@@ -398,7 +400,6 @@ context(
vegaWalletSetSpecifiedApprovalAmount('1000');
cy.reload();
ethereumWalletConnect();
cy.connectVegaWallet();
stakingPageAssociateTokens('3');
verifyUnstakedBalance(3.0);
cy.get('button').contains('Select a validator to nominate').click();
@@ -504,6 +505,11 @@ context(
);
});
afterEach('Teardown Wallet', function () {
navigateTo(navigation.home);
vegaWalletTeardown();
});
function verifyNextEpochValue(amount: number) {
cy.getByTestId('stake-next-epoch', epochTimeout)
.contains(amount, epochTimeout)
@@ -21,24 +21,25 @@ import {
vegaWalletTeardown,
} from '../../support/wallet-functions';
const ethWalletContainer = 'ethereum-wallet';
const vegaWalletAssociatedBalance = 'currency-value';
const vegaWalletUnstakedBalance = 'vega-wallet-balance-unstaked';
const ethWalletAssociateButton = '[data-testid="associate-btn"]:visible';
const associateWalletRadioButton = 'associate-radio-wallet';
const tokenAmountInputBox = 'token-amount-input';
const tokenSubmitButton = 'token-input-submit-button';
const ethWalletDissociateButton = '[href="/token/disassociate"]:visible';
const vestingContractSection = 'vega-in-vesting-contract';
const vegaInWalletSection = 'vega-in-wallet';
const connectedVegaKey = 'connected-vega-key';
const associatedKey = 'associated-key';
const associatedAmount = 'associated-amount';
const associateCompleteText = 'transaction-complete-body';
const disassociationWarning = 'disassociation-warning';
const vegaWallet = 'aside [data-testid="vega-wallet"]';
const vegaWalletPublicKeyShort = Cypress.env('vegaWalletPublicKeyShort');
const ethWalletContainer = '[data-testid="ethereum-wallet"]';
const vegaWalletAssociatedBalance = '[data-testid="currency-value"]';
const vegaWalletUnstakedBalance =
'[data-testid="vega-wallet-balance-unstaked"]';
const txTimeout = Cypress.env('txTimeout');
const vegaWalletPublicKeyShort = Cypress.env('vegaWalletPublicKeyShort');
const ethWalletAssociateButton = '[data-testid="associate-btn"]:visible';
const associateWalletRadioButton = '[data-testid="associate-radio-wallet"]';
const tokenAmountInputBox = '[data-testid="token-amount-input"]';
const tokenSubmitButton = '[data-testid="token-input-submit-button"]';
const ethWalletDissociateButton = '[href="/token/disassociate"]:visible';
const vestingContractSection = '[data-testid="vega-in-vesting-contract"]';
const vegaInWalletSection = '[data-testid="vega-in-wallet"]';
const connectedVegaKey = '[data-testid="connected-vega-key"]';
const associatedKey = '[data-testid="associated-key"]';
const associatedAmount = '[data-testid="associated-amount"]';
const associateCompleteText = '[data-testid="transaction-complete-body"]';
const disassociationWarning = '[data-testid="disassociation-warning"]';
const vegaWallet = 'aside [data-testid="vega-wallet"]';
context(
'Token association flow - with eth and vega wallets connected',
@@ -88,15 +89,12 @@ context(
verifyEthWalletAssociatedBalance('2.0');
verifyEthWalletTotalAssociatedBalance('2.0');
cy.get(vegaWallet).within(() => {
cy.getByTestId(vegaWalletAssociatedBalance, txTimeout).should(
cy.get(vegaWalletAssociatedBalance, txTimeout).should(
'contain',
2.0
);
});
cy.getByTestId(vegaWalletUnstakedBalance, txTimeout).should(
'contain',
2.0
);
cy.get(vegaWalletUnstakedBalance, txTimeout).should('contain', 2.0);
}
);
@@ -129,7 +127,7 @@ context(
verifyEthWalletAssociatedBalance('1,001.00');
verifyEthWalletTotalAssociatedBalance('7,001.00');
cy.get(vegaWallet).within(() => {
cy.getByTestId(vegaWalletAssociatedBalance, txTimeout).should(
cy.get(vegaWalletAssociatedBalance, txTimeout).should(
'contain',
'1,001.00'
);
@@ -139,20 +137,14 @@ context(
it('Able to disassociate a partial amount of tokens currently associated', function () {
stakingPageAssociateTokens('2');
cy.get(vegaWallet).within(() => {
cy.getByTestId(vegaWalletAssociatedBalance, txTimeout).should(
'contain',
2.0
);
cy.get(vegaWalletAssociatedBalance, txTimeout).should('contain', 2.0);
});
cy.get('button').contains('Select a validator to nominate').click();
cy.getByTestId('epoch-countdown').should('be.visible');
stakingPageDisassociateTokens('1');
verifyEthWalletAssociatedBalance('1.0');
cy.get(vegaWallet).within(() => {
cy.getByTestId(vegaWalletAssociatedBalance, txTimeout).should(
'contain',
1.0
);
cy.get(vegaWalletAssociatedBalance, txTimeout).should('contain', 1.0);
});
});
@@ -162,24 +154,21 @@ context(
'Warning: Any tokens that have been nominated to a node will sacrifice rewards they are due for the current epoch. If you do not wish to sacrifice these, you should remove stake from a node at the end of an epoch before disassociation.';
stakingPageAssociateTokens('2');
cy.get(vegaWallet).within(() => {
cy.getByTestId(vegaWalletAssociatedBalance, txTimeout).should(
'contain',
2.0
);
cy.get(vegaWalletAssociatedBalance, txTimeout).should('contain', 2.0);
});
cy.get('button').contains('Select a validator to nominate').click();
cy.getByTestId('epoch-countdown').should('be.visible');
cy.get(ethWalletDissociateButton).click();
cy.getByTestId(disassociationWarning).should('contain', warningText);
cy.get(disassociationWarning).should('contain', warningText);
stakingPageDisassociateAllTokens();
cy.getByTestId(ethWalletContainer)
cy.get(ethWalletContainer)
.first()
.within(() => {
cy.contains(vegaWalletPublicKeyShort, { timeout: 20000 }).should(
'not.exist'
);
});
cy.getByTestId(ethWalletContainer)
cy.get(ethWalletContainer)
.first()
.within(() => {
cy.contains(vegaWalletPublicKeyShort, { timeout: 20000 }).should(
@@ -187,10 +176,7 @@ context(
);
});
cy.get(vegaWallet).within(() => {
cy.getByTestId(vegaWalletAssociatedBalance, txTimeout).should(
'contain',
0.0
);
cy.get(vegaWalletAssociatedBalance, txTimeout).should('contain', 0.0);
});
});
@@ -212,15 +198,9 @@ context(
verifyEthWalletAssociatedBalance('2.0');
verifyEthWalletTotalAssociatedBalance('2.0');
cy.get(vegaWallet).within(() => {
cy.getByTestId(vegaWalletAssociatedBalance, txTimeout).should(
'contain',
2.0
);
cy.get(vegaWalletAssociatedBalance, txTimeout).should('contain', 2.0);
});
cy.getByTestId(vegaWalletUnstakedBalance, txTimeout).should(
'contain',
2.0
);
cy.get(vegaWalletUnstakedBalance, txTimeout).should('contain', 2.0);
stakingPageDisassociateTokens('1', {
type: 'contract',
skipConfirmation: true,
@@ -241,54 +221,45 @@ context(
cy.get('button').contains('Select a validator to nominate').click();
cy.getByTestId('epoch-countdown').should('be.visible');
stakingPageAssociateTokens('37', { type: 'contract' });
cy.getByTestId(vestingContractSection)
cy.get(vestingContractSection)
.first()
.within(() => {
cy.getByTestId(associatedKey).should(
cy.get(associatedKey).should(
'contain',
Cypress.env('vegaWalletPublicKeyShort')
);
cy.getByTestId(associatedAmount, txTimeout).should('contain', 37);
cy.get(associatedAmount, txTimeout).should('contain', 37);
});
cy.getByTestId(vegaInWalletSection)
cy.get(vegaInWalletSection)
.first()
.within(() => {
cy.getByTestId(associatedKey).should(
cy.get(associatedKey).should(
'contain',
Cypress.env('vegaWalletPublicKeyShort')
);
cy.getByTestId(associatedAmount, txTimeout).should('contain', 21);
cy.get(associatedAmount, txTimeout).should('contain', 21);
});
cy.get(vegaWallet).within(() => {
cy.getByTestId(vegaWalletAssociatedBalance, txTimeout).should(
'contain',
58
);
cy.get(vegaWalletAssociatedBalance, txTimeout).should('contain', 58);
});
stakingPageDisassociateTokens('6', { type: 'contract' });
cy.getByTestId(vestingContractSection)
cy.get(vestingContractSection)
.first()
.within(() => {
cy.getByTestId(associatedAmount, txTimeout).should('contain', 31);
cy.get(associatedAmount, txTimeout).should('contain', 31);
});
cy.get(vegaWallet).within(() => {
cy.getByTestId(vegaWalletAssociatedBalance, txTimeout).should(
'contain',
52
);
cy.get(vegaWalletAssociatedBalance, txTimeout).should('contain', 52);
});
navigateTo(navigation.validators);
stakingPageDisassociateTokens('9', { type: 'wallet' });
cy.getByTestId(vegaInWalletSection)
cy.get(vegaInWalletSection)
.first()
.within(() => {
cy.getByTestId(associatedAmount, txTimeout).should('contain', 12);
cy.get(associatedAmount, txTimeout).should('contain', 12);
});
cy.get(vegaWallet).within(() => {
cy.getByTestId(vegaWalletAssociatedBalance, txTimeout).should(
'contain',
43
);
cy.get(vegaWalletAssociatedBalance, txTimeout).should('contain', 43);
});
});
@@ -297,10 +268,10 @@ context(
// 1004-ASSO-010
// No warning visible as described in AC, but the button is disabled
cy.get(ethWalletAssociateButton).click();
cy.getByTestId(associateWalletRadioButton, { timeout: 30000 }).click();
cy.getByTestId(tokenSubmitButton, txTimeout).should('be.disabled'); // button disabled with no input
cy.getByTestId(tokenAmountInputBox, { timeout: 10000 }).type('6500000');
cy.getByTestId(tokenSubmitButton, txTimeout).should('be.disabled');
cy.get(associateWalletRadioButton, { timeout: 30000 }).click();
cy.get(tokenSubmitButton, txTimeout).should('be.disabled'); // button disabled with no input
cy.get(tokenAmountInputBox, { timeout: 10000 }).type('6500000');
cy.get(tokenSubmitButton, txTimeout).should('be.disabled');
});
// 1004-ASSO-004
@@ -325,25 +296,22 @@ context(
it('Able to associate tokens to different public key of connected vega wallet', function () {
cy.get(ethWalletAssociateButton).click();
cy.getByTestId(associateWalletRadioButton).click();
cy.getByTestId(connectedVegaKey).should(
cy.get(associateWalletRadioButton).click();
cy.get(connectedVegaKey).should(
'have.text',
Cypress.env('vegaWalletPublicKey')
);
switchVegaWalletPubKey();
cy.getByTestId(connectedVegaKey).should(
cy.get(connectedVegaKey).should(
'have.text',
Cypress.env('vegaWalletPublicKey2')
);
stakingPageAssociateTokens('2');
cy.get(vegaWallet).within(() => {
cy.getByTestId(vegaWalletAssociatedBalance, txTimeout).should(
'contain',
2.0
);
cy.get(vegaWalletAssociatedBalance, txTimeout).should('contain', 2.0);
});
cy.getByTestId(associateCompleteText).should(
cy.get(associateCompleteText).should(
'have.text',
`Vega key ${Cypress.env(
'vegaWalletPublicKey2Short'
@@ -10,10 +10,9 @@ import {
} from '../../support/governance.functions';
import { mockNetworkUpgradeProposal } from '../../support/proposal.functions';
const proposalDocsLink = 'proposal-docs-link';
const proposalDocumentationLink = 'proposal-documentation-link';
const connectToVegaWalletButton = 'connect-to-vega-wallet-btn';
const proposalDocumentationLink = '[data-testid="proposal-documentation-link"]';
const governanceDocsUrl = 'https://vega.xyz/governance';
const connectToVegaWalletButton = '[data-testid="connect-to-vega-wallet-btn"]';
context(
'Governance Page - verify elements on page',
@@ -42,7 +41,7 @@ context(
it('should be able to see a working link for - find out more about Vega governance', function () {
// 3001-VOTE-001
cy.getByTestId(proposalDocumentationLink)
cy.get(proposalDocumentationLink)
.should('be.visible')
.and('have.text', 'Find out more about Vega governance')
.and('have.attr', 'href')
@@ -65,7 +64,7 @@ context(
// 3007-PNE-021
it('should have documentation links for network parameter proposal', function () {
goToMakeNewProposal(governanceProposalType.NETWORK_PARAMETER);
cy.getByTestId(proposalDocsLink)
cy.getByTestId('proposal-docs-link')
.find('a')
.should('have.attr', 'href')
.and('contain', '/tutorials/proposals/network-parameter-proposal');
@@ -74,7 +73,7 @@ context(
// 3003-PMAN-002 3003-PMAN-005
it('should have documentation links for new market proposal', function () {
goToMakeNewProposal(governanceProposalType.NEW_MARKET);
cy.getByTestId(proposalDocsLink)
cy.getByTestId('proposal-docs-link')
.find('a')
.should('have.attr', 'href')
.and('contain', '/tutorials/proposals/new-market-proposal');
@@ -83,7 +82,7 @@ context(
// 3004-PMAC-005
it('should have documentation links for update market proposal', function () {
goToMakeNewProposal(governanceProposalType.UPDATE_MARKET);
cy.getByTestId(proposalDocsLink)
cy.getByTestId('proposal-docs-link')
.find('a')
.should('have.attr', 'href')
.and('contain', '/tutorials/proposals/update-market-proposal');
@@ -92,7 +91,7 @@ context(
// 3005-PASN-002 005-PASN-005
it('should have documentation links for new asset proposal', function () {
goToMakeNewProposal(governanceProposalType.NEW_ASSET);
cy.getByTestId(proposalDocsLink)
cy.getByTestId('proposal-docs-link')
.find('a')
.should('have.attr', 'href')
.and('contain', '/tutorials/proposals/new-asset-proposal');
@@ -101,7 +100,7 @@ context(
// 3006-PASC-002 3006-PASC-005
it('should have documentation links for update asset proposal', function () {
goToMakeNewProposal(governanceProposalType.UPDATE_ASSET);
cy.getByTestId(proposalDocsLink)
cy.getByTestId('proposal-docs-link')
.find('a')
.should('have.attr', 'href')
.and('contain', '/tutorials/proposals/update-asset-proposal');
@@ -110,7 +109,7 @@ context(
// 3008-PFRO-003 3008-PFRO-017
it('should have documentation links for freeform proposal', function () {
goToMakeNewProposal(governanceProposalType.FREEFORM);
cy.getByTestId(proposalDocsLink)
cy.getByTestId('proposal-docs-link')
.find('a')
.should('have.attr', 'href')
.and('contain', '/tutorials/proposals/freeform-proposal');
@@ -118,7 +117,7 @@ context(
it('should be able to see a connect wallet button - if vega wallet disconnected and user is submitting new proposal', function () {
goToMakeNewProposal(governanceProposalType.RAW);
cy.getByTestId(connectToVegaWalletButton)
cy.get(connectToVegaWalletButton)
.should('be.visible')
.and('have.text', 'Connect Vega wallet');
});
@@ -166,7 +165,6 @@ context(
);
});
});
cy.get('[data-testid="closed-proposals-toggle-networkUpgrades"]').click();
cy.getByTestId('closed-proposals').within(() => {
cy.getByTestId('protocol-upgrade-proposals-list-item').should(
'have.length',
@@ -5,8 +5,8 @@ import {
} from '../../support/common.functions';
import { waitForBeginningOfEpoch } from '../../support/staking.functions';
const viewToggle = 'epoch-reward-view-toggle-total';
const warning = 'callout';
const viewToggle = '[data-testid="epoch-reward-view-toggle-total"]';
const warning = '[data-testid="callout"]';
context(
'Rewards Page - verify elements on page',
@@ -27,7 +27,7 @@ context(
});
it('should have epoch warning', function () {
cy.getByTestId(warning)
cy.get(warning)
.should('be.visible')
.and(
'have.text',
@@ -36,7 +36,7 @@ context(
});
it('should have toggle for seeing total vs individual rewards', function () {
cy.getByTestId(viewToggle).should('be.visible');
cy.get(viewToggle).should('be.visible');
});
// Skipping due to bug #3471 causing flaky failuress
@@ -1,17 +1,18 @@
import { navigateTo, navigation } from '../../support/common.functions';
const tokenDetailsTable = '.token-details';
const address = 'token-address';
const contract = 'token-contract';
const totalSupply = 'total-supply';
const circulatingSupply = 'circulating-supply';
const staked = 'staked';
const tranchesLink = 'tranches-link';
const redeemBtn = 'check-vesting-page-btn';
const getVegaWalletLink = 'get-vega-wallet-link';
const associateVegaLink = 'associate-vega-tokens-link-on-homepage';
const stakingBtn = 'staking-button-on-homepage';
const governanceBtn = 'governance-button-on-homepage';
const address = '[data-testid="token-address"]';
const contract = '[data-testid="token-contract"]';
const totalSupply = '[data-testid="total-supply"]';
const circulatingSupply = '[data-testid="circulating-supply"]';
const staked = '[data-testid="staked"]';
const tranchesLink = '[data-testid="tranches-link"]';
const redeemBtn = '[data-testid="check-vesting-page-btn"]';
const getVegaWalletLink = '[data-testid="get-vega-wallet-link"]';
const associateVegaLink =
'[data-testid="associate-vega-tokens-link-on-homepage"]';
const stakingBtn = '[data-testid="staking-button-on-homepage"]';
const governanceBtn = '[data-testid="governance-button-on-homepage"]';
const vegaTokenAddress = Cypress.env('vegaTokenAddress');
const vegaTokenContractAddress = Cypress.env('vegaTokenContractAddress');
@@ -24,7 +25,7 @@ context('Verify elements on Token page', { tags: '@smoke' }, function () {
describe('THE $VEGA TOKEN table', function () {
it('should have TOKEN ADDRESS', function () {
cy.get(tokenDetailsTable).within(() => {
cy.getByTestId(address)
cy.get(address)
.should('be.visible')
.invoke('text')
.should('be.equal', vegaTokenAddress);
@@ -33,7 +34,7 @@ context('Verify elements on Token page', { tags: '@smoke' }, function () {
it('should have VESTING CONTRACT', function () {
// 1004-ASSO-001
cy.get(tokenDetailsTable).within(() => {
cy.getByTestId(contract)
cy.get(contract)
.should('be.visible')
.invoke('text')
.should('be.equal', vegaTokenContractAddress);
@@ -41,56 +42,56 @@ context('Verify elements on Token page', { tags: '@smoke' }, function () {
});
it('should have TOTAL SUPPLY', function () {
cy.get(tokenDetailsTable).within(() => {
cy.getByTestId(totalSupply).should('be.visible');
cy.get(totalSupply).should('be.visible');
});
});
it('should have CIRCULATING SUPPLY', function () {
cy.get(tokenDetailsTable).within(() => {
cy.getByTestId(circulatingSupply).should('be.visible');
cy.get(circulatingSupply).should('be.visible');
});
});
it('should have STAKED $VEGA', function () {
cy.get(tokenDetailsTable).within(() => {
cy.getByTestId(staked).should('be.visible');
cy.get(staked).should('be.visible');
});
});
});
describe('links and buttons', function () {
it('should have TRANCHES link', function () {
cy.getByTestId(tranchesLink)
cy.get(tranchesLink)
.should('be.visible')
.and('have.attr', 'href')
.and('equal', '/token/tranches');
});
it('should have REDEEM button', function () {
cy.getByTestId(redeemBtn)
cy.get(redeemBtn)
.should('be.visible')
.parent()
.should('have.attr', 'href')
.and('equal', '/token/redeem');
});
it('should have GET VEGA WALLET link', function () {
cy.getByTestId(getVegaWalletLink)
cy.get(getVegaWalletLink)
.should('be.visible')
.and('have.attr', 'href')
.and('equal', 'https://vega.xyz/wallet');
});
it('should have ASSOCIATE VEGA TOKENS link', function () {
cy.getByTestId(associateVegaLink)
cy.get(associateVegaLink)
.should('be.visible')
.and('have.attr', 'href')
.and('equal', '/token/associate');
});
it('should have STAKING button', function () {
cy.getByTestId(stakingBtn)
cy.get(stakingBtn)
.should('be.visible')
.parent()
.should('have.attr', 'href')
.and('equal', '/validators');
});
it('should have GOVERNANCE button', function () {
cy.getByTestId(governanceBtn)
cy.get(governanceBtn)
.should('be.visible')
.parent()
.should('have.attr', 'href')
@@ -1,6 +1,5 @@
/// <reference types="cypress" />
import { aliasGQLQuery } from '@vegaprotocol/cypress';
import {
navigation,
verifyPageHeader,
@@ -10,28 +9,28 @@ import {
clickOnValidatorFromList,
waitForBeginningOfEpoch,
} from '../../support/staking.functions';
import { previousEpochData } from '../../fixtures/mocks/previous-epoch';
const guideLink = 'staking-guide-link';
const validatorTitle = 'validator-node-title';
const validatorId = 'validator-id';
const validatorPubKey = 'validator-public-key';
const ethAddressLink = 'link';
const validatorStatus = 'validator-status';
const totalStake = 'total-stake';
const pendingStake = 'pending-stake';
const stakedByOperator = 'staked-by-operator';
const stakedByDelegates = 'staked-by-delegates';
const stakeShare = 'stake-percentage';
const stakedByOperatorToolTip = 'staked-operator-tooltip';
const stakedByDelegatesToolTip = 'staked-delegates-tooltip';
const totalStakedToolTip = 'total-staked-tooltip';
const unnormalisedVotingPowerToolTip = 'unnormalised-voting-power-tooltip';
const normalisedVotingPowerToolTip = 'normalised-voting-power-tooltip';
const performancePenaltyToolTip = 'performance-penalty-tooltip';
const overstakedPenaltyToolTip = 'overstaked-penalty-tooltip';
const multisigPenaltyToolTip = 'multisig-error-tooltip';
const epochCountDown = 'epoch-countdown';
const guideLink = '[data-testid="staking-guide-link"]';
const validatorTitle = '[data-testid="validator-node-title"]';
const validatorId = '[data-testid="validator-id"]';
const validatorPubKey = '[data-testid="validator-public-key"]';
const ethAddressLink = '[data-testid="link"]';
const validatorStatus = '[data-testid="validator-status"]';
const totalStake = '[data-testid="total-stake"]';
const pendingStake = '[data-testid="pending-stake"]';
const stakedByOperator = '[data-testid="staked-by-operator"]';
const stakedByDelegates = '[data-testid="staked-by-delegates"]';
const stakeShare = '[data-testid="stake-percentage"]';
const stakedByOperatorToolTip = '[data-testid="staked-operator-tooltip"]';
const stakedByDelegatesToolTip = '[data-testid="staked-delegates-tooltip"]';
const totalStakedToolTip = '[data-testid="total-staked-tooltip"]';
const unnormalisedVotingPowerToolTip =
'[data-testid="unnormalised-voting-power-tooltip"]';
const normalisedVotingPowerToolTip =
'[data-testid="normalised-voting-power-tooltip"]';
const performancePenaltyToolTip = '[data-testid="performance-penalty-tooltip"]';
const overstakedPenaltyToolTip = '[data-testid="overstaked-penalty-tooltip"]';
const epochCountDown = '[data-testid="epoch-countdown"]';
const stakeNumberRegex = /^\d{1,3}(,\d{3})*(\.\d+)?$/;
context('Validators Page - verify elements on page', function () {
@@ -50,7 +49,7 @@ context('Validators Page - verify elements on page', function () {
it('Should have Staking Guide link visible', function () {
// 1002-STKE-003
cy.getByTestId(guideLink)
cy.get(guideLink)
.should('be.visible')
.and('have.text', 'Read more about staking on Vega')
.and(
@@ -93,13 +92,13 @@ context('Validators Page - verify elements on page', function () {
waitForBeginningOfEpoch();
cy.getByTestId('total-stake').first().realHover();
cy.getByTestId(stakedByOperatorToolTip)
cy.get(stakedByOperatorToolTip)
.invoke('text')
.should('contain', 'Staked by operator: 3,000.00');
cy.getByTestId(stakedByDelegatesToolTip)
cy.get(stakedByDelegatesToolTip)
.invoke('text')
.should('contain', 'Staked by delegates: 0.00');
cy.getByTestId(totalStakedToolTip)
cy.get(totalStakedToolTip)
.invoke('text')
.should('contain', 'Total stake: 3,000.00');
});
@@ -116,10 +115,10 @@ context('Validators Page - verify elements on page', function () {
waitForBeginningOfEpoch();
cy.getByTestId('normalised-voting-power').first().realHover();
cy.getByTestId(unnormalisedVotingPowerToolTip)
cy.get(unnormalisedVotingPowerToolTip)
.invoke('text')
.should('contain', 'Unnormalised voting power: 20.00%');
cy.getByTestId(normalisedVotingPowerToolTip)
cy.get(normalisedVotingPowerToolTip)
.invoke('text')
.should('contain', 'Normalised voting power: 50.00%');
});
@@ -137,10 +136,10 @@ context('Validators Page - verify elements on page', function () {
waitForBeginningOfEpoch();
cy.getByTestId('total-penalty').realHover();
cy.getByTestId(performancePenaltyToolTip)
cy.get(performancePenaltyToolTip)
.invoke('text')
.should('contain', 'Performance penalty: 0.00%');
cy.getByTestId(overstakedPenaltyToolTip)
cy.get(overstakedPenaltyToolTip)
.invoke('text')
.should('contain', 'Overstaked penalty: 60.00%'); // value not asserted due to #2886
});
@@ -152,22 +151,6 @@ context('Validators Page - verify elements on page', function () {
cy.wrap($pendingStake).should('contain.text', '0.00');
});
});
it('Should be able to see multisig error', function () {
cy.mockGQL((req) => {
aliasGQLQuery(req, 'PreviousEpoch', previousEpochData);
});
waitForBeginningOfEpoch();
cy.getByTestId('total-penalty').first().realHover();
cy.getByTestId(multisigPenaltyToolTip)
.invoke('text')
.should('contain', 'Multisig penalty: 100%');
cy.getByTestId('total-penalty').eq(1).realHover();
cy.getByTestId(multisigPenaltyToolTip)
.invoke('text')
.should('contain', 'Multisig penalty: 100%');
});
}
);
@@ -183,59 +166,53 @@ context('Validators Page - verify elements on page', function () {
// 1002-STKE-006
it('Should be able to see validator name', function () {
cy.getByTestId(validatorTitle).should('not.be.empty');
cy.get(validatorTitle).should('not.be.empty');
});
// 1002-STKE-007
it('Should be able to see validator id', function () {
cy.getByTestId(validatorId).should('not.be.empty');
cy.get(validatorId).should('not.be.empty');
});
// 1002-STKE-008
it('Should be able to see validator public key', function () {
cy.getByTestId(validatorPubKey).should('not.be.empty');
cy.get(validatorPubKey).should('not.be.empty');
});
// 1002-STKE-010
it('Should be able to see Ethereum address', function () {
cy.getByTestId(ethAddressLink)
.should('not.be.empty')
.and('have.attr', 'href');
cy.get(ethAddressLink).should('not.be.empty').and('have.attr', 'href');
});
// TODO validators missing url for more information about them 1002-STKE-09
it('Should be able to see validator status', function () {
cy.getByTestId(validatorStatus).should('have.text', 'Consensus');
cy.get(validatorStatus).should('have.text', 'Consensus');
});
// 1002-STKE-012
it('Should be able to see total stake', function () {
cy.getByTestId(totalStake)
.invoke('text')
.should('match', stakeNumberRegex);
cy.get(totalStake).invoke('text').should('match', stakeNumberRegex);
});
it('Should be able to see pending stake', function () {
cy.getByTestId(pendingStake)
.invoke('text')
.should('match', stakeNumberRegex);
cy.get(pendingStake).invoke('text').should('match', stakeNumberRegex);
});
it('Should be able to see staked by operator', function () {
cy.getByTestId(stakedByOperator)
cy.get(stakedByOperator)
.invoke('text')
.should('match', stakeNumberRegex);
});
it('Should be able to see staked by delegates', function () {
cy.getByTestId(stakedByDelegates)
cy.get(stakedByDelegates)
.invoke('text')
.should('match', stakeNumberRegex);
});
// 1002-STKE-051
it('Should be able to see stake share in percentage', function () {
cy.getByTestId(stakeShare)
cy.get(stakeShare)
.invoke('text')
.then(($stakePercentage) => {
// The pattern must start at a word boundary (\b).
@@ -261,7 +238,7 @@ context('Validators Page - verify elements on page', function () {
const epochTitle = 'h3';
const nextEpochInfo = 'p';
cy.getByTestId(epochCountDown).within(() => {
cy.get(epochCountDown).within(() => {
cy.get(epochTitle).should('not.be.empty');
cy.get(nextEpochInfo).should('contain.text', 'Next epoch');
});
@@ -6,7 +6,7 @@ import {
} from '../../support/common.functions';
import { ethereumWalletConnect } from '../../support/wallet-eth.functions';
const connectButton = 'connect-to-eth-btn';
const connectButton = '[data-testid="connect-to-eth-btn"]';
const lockedTokensInVestingContract = '6,499,972.30';
context(
@@ -29,7 +29,7 @@ context(
// 1005-VEST-018
it('should have connect Eth wallet button', function () {
cy.getByTestId(connectButton)
cy.get(connectButton)
.should('be.visible')
.and('have.text', 'Connect Ethereum wallet');
});
@@ -5,11 +5,11 @@ const walletContainer = 'aside [data-testid="ethereum-wallet"]';
const walletHeader = '[data-testid="wallet-header"] h1';
const connectToEthButton =
'[data-testid="connect-to-eth-wallet-button"]:visible';
const connectorList = 'web3-connector-list';
const connectorList = '[data-testid="web3-connector-list"]';
const associate = '[href="/token/associate"]';
const disassociate = '[href="/token/disassociate"]';
const disconnect = 'disconnect-from-eth-wallet-button';
const accountNo = 'ethereum-account-truncated';
const disconnect = '[data-testid="disconnect-from-eth-wallet-button"]';
const accountNo = '[data-testid="ethereum-account-truncated"]';
const currencyTitle = '[data-testid="currency-title"]:visible';
const currencyValue = '[data-testid="currency-value"]:visible';
const vegaInVesting = '[data-testid="vega-in-vesting-contract"]:visible';
@@ -18,8 +18,8 @@ const progressBar = '[data-testid="progress-bar"]:visible';
const currencyLocked = '[data-testid="currency-locked"]:visible';
const currencyUnlocked = '[data-testid="currency-unlocked"]:visible';
const dialog = '[role="dialog"]:visible';
const dialogHeader = 'dialog-title';
const dialogCloseBtn = 'dialog-close';
const dialogHeader = '[data-testid="dialog-title"]';
const dialogCloseBtn = '[data-testid="dialog-close"]';
context(
'Ethereum Wallet - verify elements on widget',
@@ -59,7 +59,7 @@ context(
it('should have Connect Ethereum header visible', function () {
cy.get(dialog).within(() => {
cy.getByTestId(dialogHeader)
cy.get(dialogHeader)
.should('be.visible')
.and('have.text', 'Connect to your Ethereum wallet');
});
@@ -73,7 +73,7 @@ context(
'WalletConnect',
'WalletConnect Legacy',
];
cy.getByTestId(connectorList).within(() => {
cy.get(connectorList).within(() => {
cy.get('button').each(($btn, i) => {
cy.wrap($btn).should('be.visible').and('have.text', connectList[i]);
});
@@ -83,7 +83,7 @@ context(
after('close popup', function () {
cy.get(dialog)
.within(() => {
cy.getByTestId(dialogCloseBtn).click();
cy.get(dialogCloseBtn).click();
})
.should('not.exist');
});
@@ -106,7 +106,7 @@ context(
// 0004-EWAL-005
it('should have account number visible', function () {
cy.get(walletContainer).within(() => {
cy.getByTestId(accountNo)
cy.get(accountNo)
.should('be.visible')
.and('have.text', Cypress.env('ethWalletPublicKeyTruncated'));
});
@@ -129,7 +129,7 @@ context(
// 0004-EWAL-007
it('should have Disconnect button visible', function () {
cy.get(walletContainer).within(() => {
cy.getByTestId(disconnect)
cy.get(disconnect)
.should('be.visible')
.and('have.text', 'Disconnect');
});
@@ -7,28 +7,28 @@ import {
const walletContainer = 'aside [data-testid="vega-wallet"]';
const walletHeader = '[data-testid="wallet-header"] h1';
const connectButton = 'connect-vega-wallet';
const getVegaLink = 'link';
const connectButton = '[data-testid="connect-vega-wallet"]';
const getVegaLink = '[data-testid="link"]';
const dialog = '[role="dialog"]:visible';
const dialogHeader = 'dialog-title';
const walletDialogHeader = 'wallet-dialog-title';
const connectorsList = 'connectors-list';
const dialogCloseBtn = 'dialog-close';
const restConnectorForm = 'rest-connector-form';
const dialogHeader = '[data-testid="dialog-title"]';
const walletDialogHeader = '[data-testid="wallet-dialog-title"]';
const connectorsList = '[data-testid="connectors-list"]';
const dialogCloseBtn = '[data-testid="dialog-close"]';
const restConnectorForm = '[data-testid="rest-connector-form"]';
const restWallet = '#wallet';
const restPassphrase = '#passphrase';
const restConnectBtn = '[type="submit"]';
const accountNo = 'vega-account-truncated';
const currencyTitle = 'currency-title';
const currencyValue = 'currency-value';
const accountNo = '[data-testid="vega-account-truncated"]';
const currencyTitle = '[data-testid="currency-title"]';
const currencyValue = '[data-testid="currency-value"]';
const vegaUnstaked = '[data-testid="vega-wallet-balance-unstaked"] .text-right';
const governanceBtn = '[href="/proposals"]';
const stakingBtn = '[href="/validators"]';
const manageLink = 'manage-vega-wallet';
const dialogVegaKey = 'vega-public-key-full';
const dialogDisconnectBtn = 'disconnect';
const copyPublicKeyBtn = 'copy-vega-public-key';
const vegaWalletCurrencyTitle = 'currency-title';
const manageLink = '[data-testid="manage-vega-wallet"]';
const dialogVegaKey = '[data-testid="vega-public-key-full"]';
const dialogDisconnectBtn = '[data-testid="disconnect"]';
const copyPublicKeyBtn = '[data-testid="copy-vega-public-key"]';
const vegaWalletCurrencyTitle = '[data-testid="currency-title"]';
const vegaWalletPublicKey = Cypress.env('vegaWalletPublicKey');
const txTimeout = Cypress.env('txTimeout');
@@ -47,10 +47,10 @@ context(
cy.get(walletHeader)
.should('be.visible')
.and('have.text', 'Vega Wallet');
cy.getByTestId(connectButton)
cy.get(connectButton)
.should('be.visible')
.and('have.text', 'Connect Vega wallet to use associated $VEGA');
cy.getByTestId(getVegaLink)
cy.get(getVegaLink)
.should('be.visible')
.and('have.text', 'Get a Vega wallet')
.and('have.attr', 'href', 'https://vega.xyz/wallet');
@@ -61,20 +61,20 @@ context(
describe('when connect button clicked', () => {
before('click connect vega wallet button', () => {
cy.get(walletContainer).within(() => {
cy.getByTestId(connectButton).click();
cy.get(connectButton).click();
});
});
it('should have Connect Vega header visible', () => {
cy.get(dialog).within(() => {
cy.getByTestId(walletDialogHeader)
cy.get(walletDialogHeader)
.should('be.visible')
.and('have.text', 'Connect');
});
});
it('should have jsonRpc and hosted connection options visible on list', function () {
cy.getByTestId(connectorsList).within(() => {
cy.get(connectorsList).within(() => {
cy.getByTestId('connector-jsonRpc')
.should('be.visible')
.and('have.text', 'Connect Vega wallet');
@@ -86,33 +86,33 @@ context(
it('should have close button visible', function () {
cy.get(dialog).within(() => {
cy.getByTestId(dialogCloseBtn).should('be.visible');
cy.get(dialogCloseBtn).should('be.visible');
});
});
});
describe('when rest connector form opened', function () {
before('click hosted wallet app button', function () {
cy.getByTestId(connectorsList).within(() => {
cy.get(connectorsList).within(() => {
cy.getByTestId('connector-hosted').click();
});
});
// 0002-WCON-002
it('should have wallet field visible', function () {
cy.getByTestId(restConnectorForm).within(() => {
cy.get(restConnectorForm).within(() => {
cy.get(restWallet).should('be.visible');
});
});
it('should have password field visible', function () {
cy.getByTestId(restConnectorForm).within(() => {
cy.get(restConnectorForm).within(() => {
cy.get(restPassphrase).should('be.visible');
});
});
it('should have connect button visible', function () {
cy.getByTestId(restConnectorForm).within(() => {
cy.get(restConnectorForm).within(() => {
cy.get(restConnectBtn)
.should('be.visible')
.and('have.text', 'Connect');
@@ -121,12 +121,12 @@ context(
it('should have close button visible', function () {
cy.get(dialog).within(() => {
cy.getByTestId(dialogCloseBtn).should('be.visible');
cy.get(dialogCloseBtn).should('be.visible');
});
});
after('close dialog', function () {
cy.getByTestId(dialogCloseBtn).click().should('not.exist');
cy.get(dialogCloseBtn).click().should('not.exist');
});
});
@@ -152,7 +152,7 @@ context(
{ tags: '@smoke' },
function () {
cy.get(walletContainer).within(() => {
cy.getByTestId(accountNo)
cy.get(accountNo)
.should('be.visible')
.and('have.text', Cypress.env('vegaWalletPublicKeyShort'));
});
@@ -161,7 +161,7 @@ context(
it('should have Vega Associated currency title visible', function () {
cy.get(walletContainer).within(() => {
cy.getByTestId(currencyTitle)
cy.get(currencyTitle)
.should('be.visible')
.and('contain.text', `VEGAAssociated`);
});
@@ -172,7 +172,7 @@ context(
{ tags: '@smoke' },
function () {
cy.get(walletContainer).within(() => {
cy.getByTestId(currencyValue)
cy.get(currencyValue)
.should('be.visible')
.and('contain.text', `0.00`);
});
@@ -204,23 +204,21 @@ context(
it('should have Manage link visible', function () {
cy.get(walletContainer).within(() => {
cy.getByTestId(manageLink)
.should('be.visible')
.and('have.text', 'Manage');
cy.get(manageLink).should('be.visible').and('have.text', 'Manage');
});
});
describe('when Manage dialog opened', function () {
before('click Manage link', function () {
cy.get(walletContainer).within(() => {
cy.getByTestId(manageLink).click();
cy.get(manageLink).click();
});
});
// 0002-WCON-025, 0002-WCON-026
it('should have SELECT A VEGA KEY dialog title visible', function () {
cy.get(dialog).within(() => {
cy.getByTestId(dialogHeader)
cy.get(dialogHeader)
.should('be.visible')
.and('have.text', 'SELECT A VEGA KEY');
});
@@ -240,7 +238,7 @@ context(
'contain.text',
truncatedPubKey1
);
cy.getByTestId(dialogVegaKey)
cy.get(dialogVegaKey)
.should('be.visible')
.and('contain.text', truncatedPubKey1)
.and('contain.text', truncatedPubKey2);
@@ -250,7 +248,7 @@ context(
// 0002-WCON-029
it('should have copy public key button visible', function () {
cy.get(dialog).within(() => {
cy.getByTestId(copyPublicKeyBtn)
cy.get(copyPublicKeyBtn)
.should('be.visible')
.and('contain.text', 'Copy');
});
@@ -258,13 +256,13 @@ context(
it('should have close button visible', function () {
cy.get(dialog).within(() => {
cy.getByTestId(dialogCloseBtn).should('be.visible');
cy.get(dialogCloseBtn).should('be.visible');
});
});
it('should have vega Disconnect all keys button visible', function () {
cy.get(dialog).within(() => {
cy.getByTestId(dialogDisconnectBtn)
cy.get(dialogDisconnectBtn)
.should('be.visible')
.and('have.text', 'Disconnect all keys');
});
@@ -273,10 +271,10 @@ context(
// 0002-WCON-022
it('should be able to disconnect all keys', function () {
cy.get(dialog).within(() => {
cy.getByTestId(dialogDisconnectBtn).click();
cy.get(dialogDisconnectBtn).click();
});
cy.get(walletContainer).within(() => {
cy.getByTestId(connectButton).should('be.visible'); // 0002-WCON-023
cy.get(connectButton).should('be.visible'); // 0002-WCON-023
});
});
});
@@ -284,33 +282,33 @@ context(
// 2002-SINC-016
describe('Vega wallet with assets', function () {
const assets = [
{
id: '816af99af60d684502a40824758f6b5377e6af48e50a9ee8ef478ecb879ea8bc',
name: 'USDC (fake)',
symbol: 'fUSDC',
amount: '1000000',
expectedAmount: 10.0,
},
{
id: '8566db7257222b5b7ef2886394ad28b938b28680a54a169bbc795027b89d6665',
name: 'DAI (fake)',
symbol: 'fDAI',
amount: '200000',
expectedAmount: 2.0,
expectedAmount: '2.00',
},
{
id: '73174a6fb1d5802ba0ac7bd7ab79e0a3a4837b262de0a4e80815a55442692bd0',
name: 'BTC (fake)',
symbol: 'fBTC',
amount: '600000',
expectedAmount: 6.0,
expectedAmount: '6.00',
},
{
id: 'e02d4c15d790d1d2dffaf2dcd1cf06a1fe656656cf4ed18c8ce99f9e83643567',
name: 'EURO (fake)',
symbol: 'fEURO',
amount: '800000',
expectedAmount: 8.0,
expectedAmount: '8.00',
},
{
id: '816af99af60d684502a40824758f6b5377e6af48e50a9ee8ef478ecb879ea8bc',
name: 'USDC (fake)',
symbol: 'fUSDC',
amount: '1000000',
expectedAmount: '10.00',
},
];
@@ -332,20 +330,20 @@ context(
for (const { name, symbol, expectedAmount } of assets) {
it(`should see ${name} within vega wallet`, () => {
cy.get(walletContainer).within(() => {
cy.getByTestId(vegaWalletCurrencyTitle)
cy.get(vegaWalletCurrencyTitle)
.contains(name, txTimeout)
.should('be.visible');
cy.getByTestId(vegaWalletCurrencyTitle)
cy.get(vegaWalletCurrencyTitle)
.contains(name)
.parent()
.siblings()
.then((elementAmount) => {
const displayedAmount = parseFloat(elementAmount.text());
expect(displayedAmount).be.gte(expectedAmount);
});
.invoke('text')
.should('have.length.at.least', 4)
.then(parseFloat)
.should('be.gte', parseFloat(expectedAmount));
cy.getByTestId(vegaWalletCurrencyTitle)
cy.get(vegaWalletCurrencyTitle)
.contains(name)
.parent()
.contains(symbol);
@@ -5,6 +5,8 @@ import {
verifyTabHighlighted,
} from '../../support/common.functions';
const connectToVegaBtn = '[data-testid="connect-to-vega-wallet-btn"]';
context(
'Withdraw Page - verify elements on page',
{ tags: '@smoke' },
@@ -24,7 +26,7 @@ context(
});
it('should have connect Vega wallet button', function () {
cy.getByTestId('connect-to-vega-wallet-btn')
cy.get(connectToVegaBtn)
.should('be.visible')
.and('have.text', 'Connect Vega wallet');
});
@@ -37,7 +37,7 @@ export function navigateTo(page: navigation) {
});
} else {
return cy.get(navigation.section, { timeout: 10000 }).within(() => {
cy.get(page).eq(0).click({ force: true });
cy.get(page).eq(0).click();
});
}
}
@@ -139,9 +139,6 @@ async function vegaWalletTeardownStaking(stakingBridgeContract: StakingBridge) {
$associatedAmount
);
});
// Wait needed to allow Eth transaction to complete - otherwise could result in nonce error
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(2000);
}
});
});
@@ -1,69 +0,0 @@
import { render, fireEvent } from '@testing-library/react';
import { CollapsibleToggle } from './collapsible-toggle';
describe('CollapsibleToggle', () => {
const testId = 'collapsible-toggle';
it('renders without crashing', () => {
const mockSetToggleState = jest.fn();
const { getByTestId, getByText } = render(
<CollapsibleToggle
toggleState={false}
setToggleState={mockSetToggleState}
dataTestId={testId}
>
<div>Test</div>
</CollapsibleToggle>
);
expect(getByTestId(testId)).toBeInTheDocument();
expect(getByText('Test')).toBeInTheDocument();
});
it('calls setToggleState with the opposite of current toggleState when clicked', () => {
const mockSetToggleState = jest.fn();
const { getByTestId } = render(
<CollapsibleToggle
toggleState={false}
setToggleState={mockSetToggleState}
dataTestId={testId}
>
<div>Test</div>
</CollapsibleToggle>
);
fireEvent.click(getByTestId(testId));
expect(mockSetToggleState).toHaveBeenCalledWith(true);
});
it('has the rotate-180 class if toggleState is true', () => {
const mockSetToggleState = jest.fn();
const { getByTestId } = render(
<CollapsibleToggle
toggleState={true}
setToggleState={mockSetToggleState}
dataTestId={testId}
>
<div>Test</div>
</CollapsibleToggle>
);
expect(getByTestId('toggle-icon-wrapper')).toHaveClass('rotate-180');
});
it('does not have the rotate-180 class if toggleState is false', () => {
const mockSetToggleState = jest.fn();
const { getByTestId } = render(
<CollapsibleToggle
toggleState={false}
setToggleState={mockSetToggleState}
dataTestId={testId}
>
<div>Test</div>
</CollapsibleToggle>
);
expect(getByTestId('toggle-icon-wrapper')).not.toHaveClass('rotate-180');
});
});
@@ -1,38 +0,0 @@
import classnames from 'classnames';
import { Icon } from '@vegaprotocol/ui-toolkit';
import type { Dispatch, SetStateAction, ReactNode } from 'react';
interface CollapsibleToggleProps {
toggleState: boolean;
setToggleState: Dispatch<SetStateAction<boolean>>;
children: ReactNode;
dataTestId?: string;
}
export const CollapsibleToggle = ({
toggleState,
setToggleState,
dataTestId,
children,
}: CollapsibleToggleProps) => {
const classes = classnames(
'mb-4 transition-transform ease-in-out duration-300',
{
'rotate-180': toggleState,
}
);
return (
<button
onClick={() => setToggleState(!toggleState)}
data-testid={dataTestId}
>
<div className="flex items-center gap-3">
{children}
<div className={classes} data-testid="toggle-icon-wrapper">
<Icon name="chevron-down" size={8} />
</div>
</div>
</button>
);
};
@@ -1 +0,0 @@
export * from './collapsible-toggle';
@@ -833,8 +833,5 @@
"multisigContractIncorrect": "is incorrectly configured. Validator and delegator rewards will be penalised until this is resolved.",
"learnMore": "Learn more",
"AllValidators": "All validators",
"AllProposals": "All proposals",
"RejectedProposals": "Rejected proposals",
"networkGovernance": "Network governance",
"networkUpgrades": "Network upgrades"
"AllProposals": "All proposals"
}
@@ -0,0 +1,6 @@
import classnames from 'classnames';
export const collapsibleToggleStyles = (toggleState: boolean) =>
classnames('mb-4 transition-transform ease-in-out duration-300', {
'rotate-180': toggleState,
});
@@ -1,9 +1,9 @@
import ReactMarkdown from 'react-markdown';
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { RoundedWrapper } from '@vegaprotocol/ui-toolkit';
import { Icon, RoundedWrapper } from '@vegaprotocol/ui-toolkit';
import { SubHeading } from '../../../../components/heading';
import { CollapsibleToggle } from '../../../../components/collapsible-toggle';
import { collapsibleToggleStyles } from '../../../../lib/collapsible-toggle-styles';
export const ProposalDescription = ({
description,
@@ -15,13 +15,17 @@ export const ProposalDescription = ({
return (
<section data-testid="proposal-description">
<CollapsibleToggle
toggleState={showDescription}
setToggleState={setShowDescription}
dataTestId={'proposal-description-toggle'}
<button
onClick={() => setShowDescription(!showDescription)}
data-testid="proposal-description-toggle"
>
<SubHeading title={t('proposalDescription')} />
</CollapsibleToggle>
<div className="flex items-center gap-3">
<SubHeading title={t('proposalDescription')} />
<div className={collapsibleToggleStyles(showDescription)}>
<Icon name="chevron-down" size={8} />
</div>
</div>
</button>
{showDescription && (
<RoundedWrapper paddingBottom={true} marginBottomLarge={true}>
@@ -1,8 +1,8 @@
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import { Icon, SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
import { SubHeading } from '../../../../components/heading';
import { CollapsibleToggle } from '../../../../components/collapsible-toggle';
import { collapsibleToggleStyles } from '../../../../lib/collapsible-toggle-styles';
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
@@ -16,13 +16,17 @@ export const ProposalJson = ({
return (
<section data-testid="proposal-json">
<CollapsibleToggle
toggleState={showDetails}
setToggleState={setShowDetails}
dataTestId="proposal-json-toggle"
<button
onClick={() => setShowDetails(!showDetails)}
data-testid="proposal-json-toggle"
>
<SubHeading title={t('proposalJson')} />
</CollapsibleToggle>
<div className="flex items-center gap-3">
<SubHeading title={t('proposalJson')} />
<div className={collapsibleToggleStyles(showDetails)}>
<Icon name="chevron-down" size={8} />
</div>
</div>
</button>
{showDetails && <SyntaxHighlighter data={proposal} />}
</section>
@@ -24,7 +24,7 @@ import {
SyntaxHighlighter,
} from '@vegaprotocol/ui-toolkit';
import { SubHeading } from '../../../../components/heading';
import { CollapsibleToggle } from '../../../../components/collapsible-toggle';
import { collapsibleToggleStyles } from '../../../../lib/collapsible-toggle-styles';
import type { MarketInfoWithData } from '@vegaprotocol/markets';
import type { DataSourceDefinition } from '@vegaprotocol/types';
import { create } from 'zustand';
@@ -77,13 +77,17 @@ export const ProposalMarketData = ({
return (
<section className="relative" data-testid="proposal-market-data">
<CollapsibleToggle
toggleState={showDetails}
setToggleState={setShowDetails}
dataTestId="proposal-market-data-toggle"
<button
onClick={() => setShowDetails(!showDetails)}
data-testid="proposal-market-data-toggle"
>
<SubHeading title={t('marketSpecification')} />
</CollapsibleToggle>
<div className="flex items-center gap-3">
<SubHeading title={t('marketSpecification')} />
<div className={collapsibleToggleStyles(showDetails)}>
<Icon name="chevron-down" size={8} />
</div>
</div>
</button>
{showDetails && (
<>
@@ -5,13 +5,14 @@ import {
KeyValueTableRow,
Thumbs,
RoundedWrapper,
Icon,
} from '@vegaprotocol/ui-toolkit';
import { formatNumber, formatNumberPercentage } from '@vegaprotocol/utils';
import { SubHeading } from '../../../../components/heading';
import { useVoteInformation } from '../../hooks';
import { useAppState } from '../../../../contexts/app-state/app-state-context';
import { ProposalType } from '../proposal/proposal';
import { CollapsibleToggle } from '../../../../components/collapsible-toggle';
import { collapsibleToggleStyles } from '../../../../lib/collapsible-toggle-styles';
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
@@ -58,13 +59,17 @@ export const ProposalVotesTable = ({
return (
<>
<CollapsibleToggle
toggleState={showDetails}
setToggleState={setShowDetails}
dataTestId="vote-breakdown-toggle"
<button
onClick={() => setShowDetails(!showDetails)}
data-testid="vote-breakdown-toggle"
>
<SubHeading title={t('voteBreakdown')} />
</CollapsibleToggle>
<div className="flex items-center gap-3">
<SubHeading title={t('voteBreakdown')} />
<div className={collapsibleToggleStyles(showDetails)}>
<Icon name="chevron-down" size={8} />
</div>
</div>
</button>
{showDetails && (
<RoundedWrapper marginBottomLarge={true} paddingBottom={true}>
@@ -3,7 +3,6 @@ import { render, screen } from '@testing-library/react';
import { generateProposal } from '../../test-helpers/generate-proposals';
import { Proposal } from './proposal';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import { ProposalState } from '@vegaprotocol/types';
jest.mock('@vegaprotocol/network-parameters', () => ({
...jest.requireActual('@vegaprotocol/network-parameters'),
@@ -65,17 +64,6 @@ it('Renders with a link back to "all proposals"', async () => {
expect(await screen.findByTestId('all-proposals-link')).toBeInTheDocument();
});
it('Renders a rejected proposals with a link back to "rejected proposals"', async () => {
const proposal = generateProposal({
state: ProposalState.STATE_REJECTED,
});
renderComponent(proposal);
expect(
await screen.findByTestId('rejected-proposals-link')
).toBeInTheDocument();
});
it('renders each section', async () => {
const proposal = generateProposal();
renderComponent(proposal);
@@ -17,7 +17,6 @@ import { ProposalMarketData } from '../proposal-market-data';
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import type { MarketInfoWithData } from '@vegaprotocol/markets';
import { ProposalState } from '@vegaprotocol/types';
export enum ProposalType {
PROPOSAL_NEW_MARKET = 'PROPOSAL_NEW_MARKET',
@@ -92,22 +91,14 @@ export const Proposal = ({
return (
<AsyncRenderer data={params} loading={loading} error={error}>
<section data-testid="proposal">
<div className="flex items-center gap-1">
<div
className="flex items-center gap-1"
data-testid="all-proposals-link"
>
<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>
)}
<Link className="underline" to={Routes.PROPOSALS}>
{t('AllProposals')}
</Link>
</div>
<ProposalHeader proposal={proposal} isListItem={false} />
@@ -1,34 +0,0 @@
import { render, fireEvent, screen } from '@testing-library/react';
import { ProposalsListFilter } from './proposals-list-filter';
describe('ProposalsListFilter', () => {
let setFilterString: jest.Mock;
beforeEach(() => {
setFilterString = jest.fn();
render(
<ProposalsListFilter filterString="" setFilterString={setFilterString} />
);
});
it('renders successfully', () => {
expect(screen.getByTestId('proposals-list-filter')).toBeInTheDocument();
});
it('should handle the filter toggle click', () => {
fireEvent.click(screen.getByTestId('proposal-filter-toggle'));
expect(screen.getByTestId('proposals-list-filter')).toBeInTheDocument();
});
it('should handle input change', () => {
fireEvent.click(screen.getByTestId('proposal-filter-toggle'));
fireEvent.change(screen.getByTestId('filter-input'), {
target: { value: 'test' },
});
expect(setFilterString).toHaveBeenCalledWith('test');
});
// 'clear filter' tests are handled in the proposals-list.spec.tsx file
// as it is responsible for the filter state
});
@@ -1,16 +1,13 @@
import { useTranslation } from 'react-i18next';
import { useState } from 'react';
import { FormGroup, Icon, Input } from '@vegaprotocol/ui-toolkit';
import { CollapsibleToggle } from '../../../../components/collapsible-toggle';
import { ButtonLink, FormGroup, Input } from '@vegaprotocol/ui-toolkit';
import type { Dispatch, SetStateAction } from 'react';
interface ProposalsListFilterProps {
filterString: string;
setFilterString: Dispatch<SetStateAction<string>>;
}
export const ProposalsListFilter = ({
filterString,
setFilterString,
}: ProposalsListFilterProps) => {
const { t } = useTranslation();
@@ -18,39 +15,27 @@ export const ProposalsListFilter = ({
return (
<div data-testid="proposals-list-filter" className="mb-4">
<CollapsibleToggle
toggleState={filterVisible}
setToggleState={setFilterVisible}
dataTestId={'proposal-filter-toggle'}
>
<div className="text-xl mb-4">{t('FilterProposals')}</div>
</CollapsibleToggle>
{!filterVisible && (
<ButtonLink
onClick={() => setFilterVisible(true)}
data-testid="set-proposals-filter-visible"
>
{t('FilterProposals')}
</ButtonLink>
)}
{filterVisible && (
<div data-testid="proposals-list-filter-visible">
<div data-testid="open-proposals-list-filter">
<p>{t('FilterProposalsDescription')}</p>
<FormGroup
label="Filter text input"
labelFor="filter-input"
hideLabel={true}
className="relative"
>
<Input
value={filterString}
data-testid="filter-input"
id="filter-input"
onChange={(e) => setFilterString(e.target.value)}
className="pr-8"
/>
{filterString && filterString.length > 0 && (
<button
className="absolute top-2 right-2"
onClick={() => setFilterString('')}
data-testid="clear-filter"
>
<Icon name="cross" size={6} className="text-vega-light-200" />
</button>
)}
</FormGroup>
</div>
)}
@@ -1,7 +1,4 @@
import {
generateProposal,
generateProtocolUpgradeProposal,
} from '../../test-helpers/generate-proposals';
import { generateProposal } from '../../test-helpers/generate-proposals';
import { MockedProvider } from '@apollo/client/testing';
import { VegaWalletContext } from '@vegaprotocol/wallet';
import { BrowserRouter as Router } from 'react-router-dom';
@@ -18,7 +15,6 @@ import {
nextMonth,
} from '../../test-helpers/mocks';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import type { ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
const openProposalClosesNextMonth = generateProposal({
id: 'proposal1',
@@ -58,22 +54,12 @@ const failedProposalClosedLastMonth = generateProposal({
},
});
const closedProtocolUpgradeProposal = generateProtocolUpgradeProposal({
upgradeBlockHeight: '1',
});
const renderComponent = (
proposals: ProposalQuery['proposal'][],
protocolUpgradeProposals?: ProtocolUpgradeProposalFieldsFragment[]
) => (
const renderComponent = (proposals: ProposalQuery['proposal'][]) => (
<Router>
<MockedProvider mocks={[networkParamsQueryMock]}>
<AppStateProvider>
<VegaWalletContext.Provider value={mockWalletContext}>
<ProposalsList
proposals={proposals}
protocolUpgradeProposals={protocolUpgradeProposals || []}
/>
<ProposalsList proposals={proposals} protocolUpgradeProposals={[]} />
</VegaWalletContext.Provider>
</AppStateProvider>
</MockedProvider>
@@ -157,15 +143,17 @@ describe('Proposals list', () => {
render(
renderComponent([openProposalClosesNextMonth, openProposalClosesNextWeek])
);
fireEvent.click(screen.getByTestId('proposal-filter-toggle'));
expect(screen.getByTestId('proposals-list-filter')).toBeInTheDocument();
fireEvent.click(screen.getByTestId('set-proposals-filter-visible'));
expect(
screen.getByTestId('open-proposals-list-filter')
).toBeInTheDocument();
});
it('Filters list by text - party id', () => {
render(
renderComponent([openProposalClosesNextMonth, openProposalClosesNextWeek])
);
fireEvent.click(screen.getByTestId('proposal-filter-toggle'));
fireEvent.click(screen.getByTestId('set-proposals-filter-visible'));
fireEvent.change(screen.getByTestId('filter-input'), {
target: { value: 'bvcx' },
});
@@ -178,7 +166,7 @@ describe('Proposals list', () => {
render(
renderComponent([openProposalClosesNextMonth, openProposalClosesNextWeek])
);
fireEvent.click(screen.getByTestId('proposal-filter-toggle'));
fireEvent.click(screen.getByTestId('set-proposals-filter-visible'));
fireEvent.change(screen.getByTestId('filter-input'), {
target: { value: 'proposal1' },
});
@@ -191,7 +179,7 @@ describe('Proposals list', () => {
render(
renderComponent([openProposalClosesNextMonth, openProposalClosesNextWeek])
);
fireEvent.click(screen.getByTestId('proposal-filter-toggle'));
fireEvent.click(screen.getByTestId('set-proposals-filter-visible'));
fireEvent.change(screen.getByTestId('filter-input'), {
target: { value: 'osal1' },
});
@@ -199,92 +187,4 @@ describe('Proposals list', () => {
expect(container.querySelector('#proposal1')).toBeInTheDocument();
expect(container.querySelector('#proposal2')).not.toBeInTheDocument();
});
it('When filter is used, clear button is visible', () => {
render(
renderComponent([openProposalClosesNextMonth, openProposalClosesNextWeek])
);
fireEvent.click(screen.getByTestId('proposal-filter-toggle'));
fireEvent.change(screen.getByTestId('filter-input'), {
target: { value: 'test' },
});
expect(screen.getByTestId('clear-filter')).toBeInTheDocument();
});
it('When clear filter button is used, input is cleared', () => {
render(
renderComponent([openProposalClosesNextMonth, openProposalClosesNextWeek])
);
fireEvent.click(screen.getByTestId('proposal-filter-toggle'));
fireEvent.change(screen.getByTestId('filter-input'), {
target: { value: 'test' },
});
fireEvent.click(screen.getByTestId('clear-filter'));
expect((screen.getByTestId('filter-input') as HTMLInputElement).value).toBe(
''
);
});
it('Displays a toggle for closed proposals if there are both closed governance proposals and closed upgrade proposals', () => {
render(
renderComponent(
[enactedProposalClosedLastWeek],
[closedProtocolUpgradeProposal]
)
);
expect(screen.getByTestId('toggle-closed-proposals')).toBeInTheDocument();
});
it('Does not display a toggle for closed proposals if there are only closed upgrade proposals', () => {
render(renderComponent([], [closedProtocolUpgradeProposal]));
expect(
screen.queryByTestId('toggle-closed-proposals')
).not.toBeInTheDocument();
});
it('Does not display a toggle for closed proposals if there are only closed governance proposals', () => {
render(renderComponent([enactedProposalClosedLastWeek]));
expect(
screen.queryByTestId('toggle-closed-proposals')
).not.toBeInTheDocument();
});
it('Does not display a toggle for closed proposals if the proposal filter is engaged', () => {
render(
renderComponent(
[enactedProposalClosedLastWeek],
[closedProtocolUpgradeProposal]
)
);
fireEvent.click(screen.getByTestId('proposal-filter-toggle'));
fireEvent.change(screen.getByTestId('filter-input'), {
target: { value: 'test' },
});
expect(
screen.queryByTestId('toggle-closed-proposals')
).not.toBeInTheDocument();
});
it('Displays closed governance proposals by default due to default for the toggle', () => {
render(
renderComponent(
[enactedProposalClosedLastWeek],
[closedProtocolUpgradeProposal]
)
);
expect(
screen.getByTestId('closed-governance-proposals')
).toBeInTheDocument();
});
it('Displays closed upgrade proposals when the toggle is clicked', () => {
render(
renderComponent(
[enactedProposalClosedLastWeek],
[closedProtocolUpgradeProposal]
)
);
fireEvent.click(screen.getByText('Network upgrades'));
expect(screen.getByTestId('closed-upgrade-proposals')).toBeInTheDocument();
});
});
@@ -7,12 +7,7 @@ import { ProposalsListItem } from '../proposals-list-item';
import { ProtocolUpgradeProposalsListItem } from '../protocol-upgrade-proposals-list-item/protocol-upgrade-proposals-list-item';
import { ProposalsListFilter } from '../proposals-list-filter';
import Routes from '../../../routes';
import {
Button,
Toggle,
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
import { Button, VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
import { Link } from 'react-router-dom';
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
@@ -59,11 +54,6 @@ export const orderByUpgradeBlockHeight = (
['desc', 'desc']
);
enum ClosedProposalsViewOptions {
NetworkGovernance = 'networkGovernance',
NetworkUpgrades = 'networkUpgrades',
}
export const ProposalsList = ({
proposals,
protocolUpgradeProposals,
@@ -71,10 +61,6 @@ export const ProposalsList = ({
}: ProposalsListProps) => {
const { t } = useTranslation();
const [filterString, setFilterString] = useState('');
const [closedProposalsView, setClosedProposalsView] =
useState<ClosedProposalsViewOptions>(
ClosedProposalsViewOptions.NetworkGovernance
);
const sortedProposals: SortedProposalsProps = useMemo(() => {
const initialSorting = proposals.reduce(
@@ -123,7 +109,7 @@ export const ProposalsList = ({
);
return {
open: orderByUpgradeBlockHeight(initialSorting.open),
closed: orderByUpgradeBlockHeight(initialSorting.closed),
closed: orderByUpgradeBlockHeight(initialSorting.closed).reverse(),
};
}, [protocolUpgradeProposals, lastBlockHeight]);
@@ -141,7 +127,6 @@ export const ProposalsList = ({
marginBottom={false}
title={t('pageTitleProposals')}
/>
{DocsLinks && (
<div className="xs:justify-self-end" data-testid="new-proposal-link">
<ExternalLink href={DocsLinks.PROPOSALS_GUIDE}>
@@ -155,7 +140,6 @@ export const ProposalsList = ({
</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.`
@@ -168,26 +152,11 @@ export const ProposalsList = ({
{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
);
}
}}
/>
<ProposalsListFilter setFilterString={setFilterString} />
)}
<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">
@@ -197,7 +166,6 @@ export const ProposalsList = ({
proposal={proposal}
/>
))}
{sortedProposals.open.filter(filterPredicate).map((proposal) => (
<ProposalsListItem key={proposal?.id} proposal={proposal} />
))}
@@ -208,81 +176,22 @@ export const ProposalsList = ({
</p>
)}
</section>
<section className="relative">
<section>
<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">
{sortedProtocolUpgradeProposals.closed.map((proposal) => (
<ProtocolUpgradeProposalsListItem
key={proposal.upgradeBlockHeight}
proposal={proposal}
/>
))}
<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>
)}
</ul>
</>
{sortedProposals.closed.filter(filterPredicate).map((proposal) => (
<ProposalsListItem key={proposal?.id} proposal={proposal} />
))}
</ul>
) : (
<p className="mb-0" data-testid="no-closed-proposals">
{t('noClosedProposals')}
@@ -23,10 +23,7 @@ export const RejectedProposalsList = ({ proposals }: ProposalsListProps) => {
return (
<>
<Heading title={t('pageTitleRejectedProposals')} />
<ProposalsListFilter
filterString={filterString}
setFilterString={setFilterString}
/>
<ProposalsListFilter setFilterString={setFilterString} />
<section>
{proposals.length > 0 ? (
<ul data-testid="rejected-proposals">
@@ -1,5 +1,4 @@
import * as Schema from '@vegaprotocol/types';
import { ProtocolUpgradeProposalStatus } from '@vegaprotocol/types';
import BigNumber from 'bignumber.js';
import * as faker from 'faker';
import isArray from 'lodash/isArray';
@@ -7,40 +6,6 @@ import mergeWith from 'lodash/mergeWith';
import type { PartialDeep } from 'type-fest';
import type { ProposalQuery } from '../proposal/__generated__/Proposal';
import type { ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
export function generateProtocolUpgradeProposal(
override: PartialDeep<ProtocolUpgradeProposalFieldsFragment> = {}
): ProtocolUpgradeProposalFieldsFragment {
const defaultProposal: ProtocolUpgradeProposalFieldsFragment = {
__typename: 'ProtocolUpgradeProposal',
upgradeBlockHeight: '3917600',
vegaReleaseTag: 'v0.71.6',
approvers: [
'0ac70c4ccc7f961614fe49b93e639ddf916269b7dcf8391db264cefeadf5a6b7',
'63a1755006642bda9ab1bfa84660f944d30a113d1609590ca90c50b24aede472',
'68ed0770fc3e67b74d09c05443243d27e29a8513dc0e8628beb98338cd509159',
'a6e6f7daf8610f9242ab6ab46b394f6fb79cf9533d48051ca7a2f142b8b700a8',
'aad2be546ba83cbcab4c1d57ebe22b4a942f294f54333f1a7c2c9ef0e9fe19bb',
'acc55c7205cfcd5480e0235acab56a01487a39dc858a641fc04df6ba016870ee',
'b7e500deb24cc19bd6ebb2311997f0904ca0d9e51541249e9650ab41fd8ac376',
'cf295dff6d9506e8a905d168a44dfcff2f64bd0a6671783a469f8322959c62e2',
'f4686749895bf51c6df4092ef6be4279c384a3c380c24ea7a2fd20afc602a35d',
],
status:
ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_APPROVED,
};
return mergeWith<
ProtocolUpgradeProposalFieldsFragment,
PartialDeep<ProtocolUpgradeProposalFieldsFragment>
>(defaultProposal, override, (objValue, srcValue) => {
if (!isArray(objValue)) {
return;
}
return srcValue;
});
}
export function generateProposal(
override: PartialDeep<ProposalQuery['proposal']> = {}
@@ -1,4 +1,5 @@
import { checkSorting, aliasGQLQuery } from '@vegaprotocol/cypress';
import { checkSorting } from '@vegaprotocol/cypress';
import { aliasGQLQuery } from '@vegaprotocol/cypress';
import { marketsDataQuery } from '@vegaprotocol/mock';
import { positionsQuery } from '@vegaprotocol/mock';
@@ -14,12 +15,13 @@ const toastContent = 'toast-content';
const tooltipContent = 'tooltip-content';
// #endregion
beforeEach(() => {
cy.mockTradingPage();
cy.mockSubscription();
cy.setVegaWallet();
});
describe('positions', { tags: '@smoke', testIsolation: true }, () => {
beforeEach(() => {
cy.mockTradingPage();
cy.mockSubscription();
cy.setVegaWallet();
});
it('renders positions on trading page', () => {
visitAndClickPositions();
// 7004-POSI-001
@@ -62,14 +64,7 @@ describe('positions', { tags: '@smoke', testIsolation: true }, () => {
);
});
});
describe('positions', { tags: '@regression', testIsolation: true }, () => {
beforeEach(() => {
cy.mockTradingPage();
cy.mockSubscription();
cy.setVegaWallet();
});
it('rows should be displayed despite errors', () => {
const errors = [
{
@@ -169,9 +164,8 @@ describe('positions', { tags: '@regression', testIsolation: true }, () => {
);
});
// let elementWidth: number;
it('Resize column', () => {
let elementWidth: number;
visitAndClickPositions();
cy.get('.ag-overlay-loading-wrapper').should('not.be.visible');
cy.get('.ag-header-container').within(() => {
@@ -186,33 +180,29 @@ describe('positions', { tags: '@regression', testIsolation: true }, () => {
cy.get(`[col-id="marketName"]`)
.invoke('width')
.should('be.greaterThan', 250);
});
cy.get(`[col-id="marketName"]`)
.invoke('width')
.then((width) => {
elementWidth = width as number;
})
.then(() => {
let localStorageCopy: Record<string, string>;
cy.window().then((win) => {
localStorageCopy = { ...win.localStorage };
});
// This test depends on the previous one
it('Has persisted column widths', () => {
const width = 400;
cy.reload();
cy.window().then((win) => {
Object.keys(localStorageCopy).forEach((key) => {
win.localStorage.setItem(key, localStorageCopy[key]);
});
});
cy.window().then((win) => {
win.localStorage.setItem(
'vega_positions_store',
JSON.stringify({
state: {
gridStore: {
columnState: [{ colId: 'marketName', width }],
},
},
})
);
});
visitAndClickPositions();
// 7004-POSI-012
cy.get('.ag-center-cols-container .ag-row')
.first()
.find('[col-id="marketName"]')
.invoke('outerWidth')
.should('equal', width);
// 7004-POSI-012
cy.get('[col-id="marketName"]')
.invoke('width')
.should('equal', elementWidth);
});
});
it('Scroll horizontally', () => {
@@ -301,7 +291,6 @@ describe('positions', { tags: '@regression', testIsolation: true }, () => {
cy.getByTestId(dialogContent).should('be.visible');
});
});
function validatePositionsDisplayed(multiKey = false) {
cy.getByTestId('tab-positions').should('be.visible');
cy.getByTestId('tab-positions')
@@ -337,7 +326,6 @@ function validatePositionsDisplayed(multiKey = false) {
cy.getByTestId('close-position').should('be.visible').and('have.length', 3);
}
function assertPNLColor(
pnlSelector: string,
positiveClass: string,
@@ -359,7 +347,6 @@ function assertPNLColor(
}
});
}
function visitAndClickPositions() {
cy.visit('/#/markets/market-0');
cy.getByTestId(positions).click();
@@ -1,5 +1,7 @@
import {
matchFilter,
liquidityProvisionsDataProvider,
LiquidityTable,
lpAggregatedDataProvider,
useCheckLiquidityStatus,
} from '@vegaprotocol/liquidity';
@@ -22,16 +24,18 @@ import {
ExternalLink,
} from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { memo, useEffect, useState } from 'react';
import { memo, useEffect, useRef, useState } from 'react';
import { Header, HeaderStat, HeaderTitle } from '../../components/header';
import type { AgGridReact } from 'ag-grid-react';
import type { Filter } from '@vegaprotocol/liquidity';
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 {
Active = 'active',
@@ -45,6 +49,65 @@ export const Liquidity = () => {
return <LiquidityViewContainer marketId={marketId} />;
};
const useReloadLiquidityData = (marketId: string | undefined) => {
const { reload } = useDataProvider({
dataProvider: liquidityProvisionsDataProvider,
variables: { marketId: marketId || '' },
update: () => true,
skip: !marketId,
});
useEffect(() => {
const interval = setInterval(reload, 30000);
return () => clearInterval(interval);
}, [reload]);
};
export const LiquidityContainer = ({
marketId,
filter,
}: {
marketId: string | undefined;
filter?: Filter;
}) => {
const gridRef = useRef<AgGridReact | null>(null);
const { data: market } = useMarket(marketId);
// To be removed when liquidityProvision subscriptions are working
useReloadLiquidityData(marketId);
const { data, error } = useDataProvider({
dataProvider: lpAggregatedDataProvider,
variables: { marketId: marketId || '', filter },
skip: !marketId,
});
const assetDecimalPlaces =
market?.tradableInstrument.instrument.product.settlementAsset.decimals || 0;
const quantum =
market?.tradableInstrument.instrument.product.settlementAsset.quantum || 0;
const symbol =
market?.tradableInstrument.instrument.product.settlementAsset.symbol;
const { params } = useNetworkParams([
NetworkParams.market_liquidity_stakeToCcyVolume,
]);
const stakeToCcyVolume = params.market_liquidity_stakeToCcyVolume;
return (
<div className="h-full relative">
<LiquidityTable
ref={gridRef}
rowData={data}
symbol={symbol}
assetDecimalPlaces={assetDecimalPlaces}
quantum={quantum}
stakeToCcyVolume={stakeToCcyVolume}
overlayNoRowsTemplate={error ? error.message : t('No data')}
/>
</div>
);
};
const LiquidityViewHeader = memo(({ marketId }: { marketId?: string }) => {
const { data: market } = useMarket(marketId);
const { data: marketData } = useStaticMarketData(marketId);
@@ -19,7 +19,10 @@ import {
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
import {
useMarketClickHandler,
useMarketLiquidityClickHandler,
} from '../../lib/hooks/use-market-click-handler';
import { VegaWalletContainer } from '../../components/vega-wallet-container';
import { HeaderTitle } from '../../components/header';
import {
@@ -46,6 +49,7 @@ const MarketBottomPanel = memo(
const [sizes, handleOnLayoutChange] = usePaneLayout({ id: 'bottom' });
const { screenSize } = useScreenDimensions();
const onMarketClick = useMarketClickHandler(true);
const onOrderTypeClick = useMarketLiquidityClickHandler();
return 'xxxl' === screenSize ? (
<ResizableGrid
@@ -65,6 +69,10 @@ const MarketBottomPanel = memo(
<TradingViews.orders.component
marketId={marketId}
filter={Filter.Open}
onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick}
enforceBottomPlaceholder
storeKey="marketOpenOrders"
/>
</VegaWalletContainer>
</Tab>
@@ -73,6 +81,10 @@ const MarketBottomPanel = memo(
<TradingViews.orders.component
marketId={marketId}
filter={Filter.Closed}
onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick}
enforceBottomPlaceholder
storeKey="marketClosedOrders"
/>
</VegaWalletContainer>
</Tab>
@@ -81,12 +93,22 @@ const MarketBottomPanel = memo(
<TradingViews.orders.component
marketId={marketId}
filter={Filter.Rejected}
onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick}
enforceBottomPlaceholder
storeKey="marketRejectOrders"
/>
</VegaWalletContainer>
</Tab>
<Tab id="orders" name={t('All')}>
<VegaWalletContainer>
<TradingViews.orders.component marketId={marketId} />
<TradingViews.orders.component
marketId={marketId}
onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick}
enforceBottomPlaceholder
storeKey="marketAllOrders"
/>
</VegaWalletContainer>
</Tab>
<Tab id="fills" name={t('Fills')}>
@@ -94,6 +116,7 @@ const MarketBottomPanel = memo(
<TradingViews.fills.component
marketId={marketId}
onMarketClick={onMarketClick}
storeKey="marketFills"
/>
</VegaWalletContainer>
</Tab>
@@ -111,6 +134,8 @@ const MarketBottomPanel = memo(
<VegaWalletContainer>
<TradingViews.positions.component
onMarketClick={onMarketClick}
noBottomPlaceholder
storeKey="marketPositions"
/>
</VegaWalletContainer>
</Tab>
@@ -120,6 +145,7 @@ const MarketBottomPanel = memo(
pinnedAsset={pinnedAsset}
onMarketClick={onMarketClick}
hideButtons
storeKey="marketCollateral"
/>
</VegaWalletContainer>
</Tab>
@@ -132,7 +158,10 @@ const MarketBottomPanel = memo(
<Tabs storageKey="console-trade-grid-bottom">
<Tab id="positions" name={t('Positions')}>
<VegaWalletContainer>
<TradingViews.positions.component onMarketClick={onMarketClick} />
<TradingViews.positions.component
onMarketClick={onMarketClick}
storeKey="marketPositions"
/>
</VegaWalletContainer>
</Tab>
<Tab id="open-orders" name={t('Open')}>
@@ -140,6 +169,10 @@ const MarketBottomPanel = memo(
<TradingViews.orders.component
marketId={marketId}
filter={Filter.Open}
onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick}
enforceBottomPlaceholder
storeKey="marketOpenOrders"
/>
</VegaWalletContainer>
</Tab>
@@ -148,6 +181,10 @@ const MarketBottomPanel = memo(
<TradingViews.orders.component
marketId={marketId}
filter={Filter.Closed}
onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick}
enforceBottomPlaceholder
storeKey="marketClosedOrders"
/>
</VegaWalletContainer>
</Tab>
@@ -156,12 +193,22 @@ const MarketBottomPanel = memo(
<TradingViews.orders.component
marketId={marketId}
filter={Filter.Rejected}
onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick}
enforceBottomPlaceholder
storeKey="marketRejectedOrders"
/>
</VegaWalletContainer>
</Tab>
<Tab id="orders" name={t('All')}>
<VegaWalletContainer>
<TradingViews.orders.component marketId={marketId} />
<TradingViews.orders.component
marketId={marketId}
onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick}
enforceBottomPlaceholder
storeKey="marketAllOrders"
/>
</VegaWalletContainer>
</Tab>
<Tab id="fills" name={t('Fills')}>
@@ -169,6 +216,7 @@ const MarketBottomPanel = memo(
<TradingViews.fills.component
marketId={marketId}
onMarketClick={onMarketClick}
storeKey="marketFills"
/>
</VegaWalletContainer>
</Tab>
@@ -178,6 +226,7 @@ const MarketBottomPanel = memo(
pinnedAsset={pinnedAsset}
onMarketClick={onMarketClick}
hideButtons
storeKey="marketCollateral"
/>
</VegaWalletContainer>
</Tab>
@@ -1,19 +1,18 @@
import type { ComponentProps } from 'react';
import { Splash } from '@vegaprotocol/ui-toolkit';
import { DealTicketContainer } from '@vegaprotocol/deal-ticket';
import { MarketInfoAccordionContainer } from '@vegaprotocol/markets';
import { OrderbookContainer } from '@vegaprotocol/market-depth';
import { OrderListContainer, Filter } from '@vegaprotocol/orders';
import type { OrderListContainerProps } from '@vegaprotocol/orders';
import { FillsContainer } from '@vegaprotocol/fills';
import { PositionsContainer } from '@vegaprotocol/positions';
import { TradesContainer } from '@vegaprotocol/trades';
import type { ComponentProps } from 'react';
import { DepthChartContainer } from '@vegaprotocol/market-depth';
import { CandlesChartContainer } from '@vegaprotocol/candles-chart';
import { OrderbookContainer } from '@vegaprotocol/market-depth';
import { Filter } from '@vegaprotocol/orders';
import { NO_MARKET } from './constants';
import { FillsContainer } from '../../components/fills-container';
import { PositionsContainer } from '../../components/positions-container';
import { Splash } from '@vegaprotocol/ui-toolkit';
import { AccountsContainer } from '../../components/accounts-container';
import { LiquidityContainer } from '../../components/liquidity-container';
import type { OrderContainerProps } from '../../components/orders-container';
import { OrdersContainer } from '../../components/orders-container';
import { NO_MARKET } from './constants';
import { LiquidityContainer } from '../liquidity/liquidity';
type MarketDependantView =
| typeof CandlesChartContainer
@@ -66,25 +65,25 @@ export const TradingViews = {
positions: { label: 'Positions', component: PositionsContainer },
activeOrders: {
label: 'Active',
component: (props: OrderContainerProps) => (
<OrdersContainer {...props} filter={Filter.Open} />
component: (props: OrderListContainerProps) => (
<OrderListContainer {...props} filter={Filter.Open} />
),
},
closedOrders: {
label: 'Closed',
component: (props: OrderContainerProps) => (
<OrdersContainer {...props} filter={Filter.Closed} />
component: (props: OrderListContainerProps) => (
<OrderListContainer {...props} filter={Filter.Closed} />
),
},
rejectedOrders: {
label: 'Rejected',
component: (props: OrderContainerProps) => (
<OrdersContainer {...props} filter={Filter.Rejected} />
component: (props: OrderListContainerProps) => (
<OrderListContainer {...props} filter={Filter.Rejected} />
),
},
orders: {
label: 'All',
component: OrdersContainer,
component: OrderListContainer,
},
collateral: { label: 'Collateral', component: AccountsContainer },
fills: { label: 'Fills', component: FillsContainer },
@@ -313,6 +313,7 @@ const ClosedMarketsDataGrid = ({
minWidth: 100,
}}
overlayNoRowsTemplate={error ? error.message : t('No markets')}
storeKey="closedMarkets"
/>
);
};
@@ -2,6 +2,7 @@ import { Button } from '@vegaprotocol/ui-toolkit';
import { useDepositDialog, DepositsTable } from '@vegaprotocol/deposits';
import { depositsProvider } from '@vegaprotocol/deposits';
import { t } from '@vegaprotocol/i18n';
import { useBottomPlaceholder } from '@vegaprotocol/datagrid';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useRef } from 'react';
@@ -16,13 +17,17 @@ export const DepositsContainer = () => {
skip: !pubKey,
});
const openDepositDialog = useDepositDialog((state) => state.open);
const bottomPlaceholderProps = useBottomPlaceholder({ gridRef });
return (
<div className="h-full">
<DepositsTable
rowData={data}
ref={gridRef}
overlayNoRowsTemplate={error ? error.message : t('No deposits')}
/>
<div className="h-full relative">
<DepositsTable
rowData={data}
ref={gridRef}
{...bottomPlaceholderProps}
overlayNoRowsTemplate={error ? error.message : t('No deposits')}
/>
</div>
{!isReadOnly && (
<div className="h-auto flex justify-end px-[11px] py-2 bottom-0 right-3 absolute dark:bg-black/75 bg-white/75 rounded">
<Button
@@ -1,39 +1,29 @@
import { useEffect } from 'react';
import type { ReactNode } from 'react';
import { LayoutPriority } from 'allotment';
import { titlefy } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { useIncompleteWithdrawals } from '@vegaprotocol/withdraws';
import { usePaneLayout } from '@vegaprotocol/react-helpers';
import { PositionsContainer } from '@vegaprotocol/positions';
import { OrderListContainer } from '@vegaprotocol/orders';
import { Tab, LocalStoragePersistTabs as Tabs } from '@vegaprotocol/ui-toolkit';
import { usePageTitleStore } from '../../stores';
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
import { AccountsContainer } from '../../components/accounts-container';
import { DepositsContainer } from './deposits-container';
import { FillsContainer } from '../../components/fills-container';
import { PositionsContainer } from '../../components/positions-container';
import { WithdrawalsContainer } from './withdrawals-container';
import { OrdersContainer } from '../../components/orders-container';
import { FillsContainer } from '@vegaprotocol/fills';
import type { ReactNode } from 'react';
import { useEffect } from 'react';
import { usePaneLayout } from '@vegaprotocol/react-helpers';
import { VegaWalletContainer } from '../../components/vega-wallet-container';
import { LedgerContainer } from '../../components/ledger-container';
import { DepositsContainer } from './deposits-container';
import { LayoutPriority } from 'allotment';
import { usePageTitleStore } from '../../stores';
import { LedgerContainer } from '@vegaprotocol/ledger';
import { AccountsContainer } from '../../components/accounts-container';
import { AccountHistoryContainer } from './account-history-container';
import {
useMarketClickHandler,
useMarketLiquidityClickHandler,
} from '../../lib/hooks/use-market-click-handler';
import {
ResizableGrid,
ResizableGridPanel,
} from '../../components/resizable-grid';
const WithdrawalsIndicator = () => {
const { ready } = useIncompleteWithdrawals();
if (!ready || ready.length === 0) {
return null;
}
return (
<span className="bg-vega-blue-450 text-white text-[10px] rounded p-[3px] pb-[2px] leading-none">
{ready.length}
</span>
);
};
export const Portfolio = () => {
const { updateTitle } = usePageTitleStore((store) => ({
updateTitle: store.updateTitle,
@@ -44,6 +34,7 @@ export const Portfolio = () => {
}, [updateTitle]);
const onMarketClick = useMarketClickHandler(true);
const onOrderTypeClick = useMarketLiquidityClickHandler();
const [sizes, handleOnLayoutChange] = usePaneLayout({ id: 'portfolio' });
const wrapperClasses = 'h-full max-h-full flex flex-col';
return (
@@ -59,17 +50,29 @@ export const Portfolio = () => {
</Tab>
<Tab id="positions" name={t('Positions')}>
<VegaWalletContainer>
<PositionsContainer onMarketClick={onMarketClick} allKeys />
<PositionsContainer
onMarketClick={onMarketClick}
noBottomPlaceholder
storeKey="portfolioPositions"
allKeys
/>
</VegaWalletContainer>
</Tab>
<Tab id="orders" name={t('Orders')}>
<VegaWalletContainer>
<OrdersContainer />
<OrderListContainer
onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick}
storeKey="portfolioOrders"
/>
</VegaWalletContainer>
</Tab>
<Tab id="fills" name={t('Fills')}>
<VegaWalletContainer>
<FillsContainer onMarketClick={onMarketClick} />
<FillsContainer
onMarketClick={onMarketClick}
storeKey="portfolioFills"
/>
</VegaWalletContainer>
</Tab>
<Tab id="ledger-entries" name={t('Ledger entries')}>
@@ -89,7 +92,10 @@ export const Portfolio = () => {
<Tabs storageKey="console-portfolio-bottom">
<Tab id="collateral" name={t('Collateral')}>
<VegaWalletContainer>
<AccountsContainer />
<AccountsContainer
storeKey="portfolioCollateral"
onMarketClick={onMarketClick}
/>
</VegaWalletContainer>
</Tab>
<Tab id="deposits" name={t('Deposits')}>
@@ -97,11 +103,7 @@ export const Portfolio = () => {
<DepositsContainer />
</VegaWalletContainer>
</Tab>
<Tab
id="withdrawals"
name={t('Withdrawals')}
indicator={<WithdrawalsIndicator />}
>
<Tab id="withdrawals" name={t('Withdrawals')}>
<WithdrawalsContainer />
</Tab>
</Tabs>
@@ -3,7 +3,6 @@ import {
withdrawalProvider,
useWithdrawalDialog,
WithdrawalsTable,
useIncompleteWithdrawals,
} from '@vegaprotocol/withdraws';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { t } from '@vegaprotocol/i18n';
@@ -18,7 +17,6 @@ export const WithdrawalsContainer = () => {
skip: !pubKey,
});
const openWithdrawDialog = useWithdrawalDialog((state) => state.open);
const { ready, delayed } = useIncompleteWithdrawals();
return (
<VegaWalletContainer>
@@ -27,8 +25,6 @@ export const WithdrawalsContainer = () => {
data-testid="withdrawals-history"
rowData={data}
overlayNoRowsTemplate={error ? error.message : t('No withdrawals')}
ready={ready}
delayed={delayed}
/>
</div>
{!isReadOnly && (
@@ -8,19 +8,16 @@ import { useVegaWallet } from '@vegaprotocol/wallet';
import type { PinnedAsset } from '@vegaprotocol/accounts';
import { AccountManager, useTransferDialog } from '@vegaprotocol/accounts';
import { useDepositDialog } from '@vegaprotocol/deposits';
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { useDataGridEvents } from '@vegaprotocol/datagrid';
import type { DataGridSlice } from '../../stores/datagrid-store-slice';
import { createDataGridSlice } from '../../stores/datagrid-store-slice';
export const AccountsContainer = ({
pinnedAsset,
hideButtons,
storeKey,
onMarketClick,
}: {
pinnedAsset?: PinnedAsset;
hideButtons?: boolean;
storeKey?: string;
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
}) => {
const { pubKey, isReadOnly } = useVegaWallet();
@@ -29,12 +26,6 @@ export const AccountsContainer = ({
const openDepositDialog = useDepositDialog((store) => store.open);
const openTransferDialog = useTransferDialog((store) => store.open);
const gridStore = useAccountStore((store) => store.gridStore);
const updateGridStore = useAccountStore((store) => store.updateGridStore);
const gridStoreCallbacks = useDataGridEvents(gridStore, (colState) => {
updateGridStore(colState);
});
const onClickAsset = useCallback(
(assetId?: string) => {
assetId && openAssetDetailsDialog(assetId);
@@ -60,7 +51,7 @@ export const AccountsContainer = ({
onMarketClick={onMarketClick}
isReadOnly={isReadOnly}
pinnedAsset={pinnedAsset}
gridProps={gridStoreCallbacks}
storeKey={storeKey}
/>
{!isReadOnly && !hideButtons && (
<div className="flex gap-2 justify-end p-2 px-[11px] absolute lg:fixed bottom-0 right-3 dark:bg-black/75 bg-white/75 rounded">
@@ -84,9 +75,3 @@ export const AccountsContainer = ({
</div>
);
};
const useAccountStore = create<DataGridSlice>()(
persist(createDataGridSlice, {
name: 'vega_accounts_store',
})
);
@@ -1,16 +1,13 @@
import type { DefaultWeb3ProviderContextShape } from '@vegaprotocol/web3';
import {
useEthereumConfig,
createConnectors,
Web3Provider as Web3ProviderInternal,
useWeb3ConnectStore,
createDefaultProvider,
} from '@vegaprotocol/web3';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import { t } from '@vegaprotocol/i18n';
import { useEnvironment } from '@vegaprotocol/environment';
import type { ReactNode } from 'react';
import { useState } from 'react';
import { useEffect } from 'react';
export const Web3Provider = ({ children }: { children: ReactNode }) => {
@@ -20,13 +17,10 @@ export const Web3Provider = ({ children }: { children: ReactNode }) => {
const connectors = useWeb3ConnectStore((store) => store.connectors);
const initializeConnectors = useWeb3ConnectStore((store) => store.initialize);
const [defaultProvider, setDefaultProvider] = useState<
DefaultWeb3ProviderContextShape['provider'] | undefined
>(undefined);
useEffect(() => {
if (config?.chain_id) {
initializeConnectors(
return initializeConnectors(
createConnectors(
ETHEREUM_PROVIDER_URL,
Number(config?.chain_id),
@@ -35,11 +29,6 @@ export const Web3Provider = ({ children }: { children: ReactNode }) => {
),
Number(config.chain_id)
);
const defaultProvider = createDefaultProvider(
ETHEREUM_PROVIDER_URL,
Number(config?.chain_id)
);
setDefaultProvider(defaultProvider);
}
}, [
config?.chain_id,
@@ -60,10 +49,7 @@ export const Web3Provider = ({ children }: { children: ReactNode }) => {
}}
noDataMessage={t('Could not fetch Ethereum configuration')}
>
<Web3ProviderInternal
connectors={connectors}
defaultProvider={defaultProvider}
>
<Web3ProviderInternal connectors={connectors}>
<>{children}</>
</Web3ProviderInternal>
</AsyncRenderer>
@@ -1,49 +0,0 @@
import { t } from '@vegaprotocol/i18n';
import { Splash } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { FillsManager } from '@vegaprotocol/fills';
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { useDataGridEvents } from '@vegaprotocol/datagrid';
import type { DataGridSlice } from '../../stores/datagrid-store-slice';
import { createDataGridSlice } from '../../stores/datagrid-store-slice';
export const FillsContainer = ({
marketId,
onMarketClick,
}: {
marketId?: string;
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
}) => {
const { pubKey } = useVegaWallet();
const gridStore = useFillsStore((store) => store.gridStore);
const updateGridStore = useFillsStore((store) => store.updateGridStore);
const gridStoreCallbacks = useDataGridEvents(gridStore, (colState) => {
updateGridStore(colState);
});
if (!pubKey) {
return (
<Splash>
<p>{t('Please connect Vega wallet')}</p>
</Splash>
);
}
return (
<FillsManager
partyId={pubKey}
marketId={marketId}
onMarketClick={onMarketClick}
gridProps={gridStoreCallbacks}
/>
);
};
const useFillsStore = create<DataGridSlice>()(
persist(createDataGridSlice, {
name: 'vega_fills_store',
})
);
@@ -1 +0,0 @@
export * from './fills-container';
@@ -1 +0,0 @@
export * from './ledger-container';
@@ -1,36 +0,0 @@
import { useDataGridEvents } from '@vegaprotocol/datagrid';
import { t } from '@vegaprotocol/i18n';
import { LedgerManager } from '@vegaprotocol/ledger';
import { Splash } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet';
import type { DataGridSlice } from '../../stores/datagrid-store-slice';
import { createDataGridSlice } from '../../stores/datagrid-store-slice';
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
export const LedgerContainer = () => {
const { pubKey } = useVegaWallet();
const gridStore = useLedgerStore((store) => store.gridStore);
const updateGridStore = useLedgerStore((store) => store.updateGridStore);
const gridStoreCallbacks = useDataGridEvents(gridStore, (colState) => {
updateGridStore(colState);
});
if (!pubKey) {
return (
<Splash>
<p>{t('Please connect Vega wallet')}</p>
</Splash>
);
}
return <LedgerManager partyId={pubKey} gridProps={gridStoreCallbacks} />;
};
const useLedgerStore = create<DataGridSlice>()(
persist(createDataGridSlice, {
name: 'vega_ledger_store',
})
);
@@ -1 +0,0 @@
export * from './liquidity-container';
@@ -1,93 +0,0 @@
import { useDataProvider } from '@vegaprotocol/data-provider';
import { useDataGridEvents } from '@vegaprotocol/datagrid';
import { t } from '@vegaprotocol/i18n';
import {
lpAggregatedDataProvider,
type Filter,
LiquidityTable,
liquidityProvisionsDataProvider,
} from '@vegaprotocol/liquidity';
import { useMarket } from '@vegaprotocol/markets';
import {
NetworkParams,
useNetworkParams,
} from '@vegaprotocol/network-parameters';
import type { AgGridReact } from 'ag-grid-react';
import type { DataGridSlice } from '../../stores/datagrid-store-slice';
import { createDataGridSlice } from '../../stores/datagrid-store-slice';
import { useEffect, useRef } from 'react';
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
export const LiquidityContainer = ({
marketId,
filter,
}: {
marketId: string | undefined;
filter?: Filter;
}) => {
const gridRef = useRef<AgGridReact | null>(null);
const gridStore = useLiquidityStore((store) => store.gridStore);
const updateGridStore = useLiquidityStore((store) => store.updateGridStore);
const gridStoreCallbacks = useDataGridEvents(gridStore, (colState) => {
updateGridStore(colState);
});
const { data: market } = useMarket(marketId);
// To be removed when liquidityProvision subscriptions are working
useReloadLiquidityData(marketId);
const { data, error } = useDataProvider({
dataProvider: lpAggregatedDataProvider,
variables: { marketId: marketId || '', filter },
skip: !marketId,
});
const assetDecimalPlaces =
market?.tradableInstrument.instrument.product.settlementAsset.decimals || 0;
const quantum =
market?.tradableInstrument.instrument.product.settlementAsset.quantum || 0;
const symbol =
market?.tradableInstrument.instrument.product.settlementAsset.symbol;
const { params } = useNetworkParams([
NetworkParams.market_liquidity_stakeToCcyVolume,
]);
const stakeToCcyVolume = params.market_liquidity_stakeToCcyVolume;
return (
<div className="h-full relative">
<LiquidityTable
ref={gridRef}
rowData={data}
symbol={symbol}
assetDecimalPlaces={assetDecimalPlaces}
quantum={quantum}
stakeToCcyVolume={stakeToCcyVolume}
overlayNoRowsTemplate={error ? error.message : t('No data')}
{...gridStoreCallbacks}
/>
</div>
);
};
const useReloadLiquidityData = (marketId: string | undefined) => {
const { reload } = useDataProvider({
dataProvider: liquidityProvisionsDataProvider,
variables: { marketId: marketId || '' },
update: () => true,
skip: !marketId,
});
useEffect(() => {
const interval = setInterval(reload, 30000);
return () => clearInterval(interval);
}, [reload]);
};
const useLiquidityStore = create<DataGridSlice>()(
persist(createDataGridSlice, {
name: 'vega_ledger_store',
})
);
@@ -1 +0,0 @@
export * from './orders-container';
@@ -1,106 +0,0 @@
import { act, renderHook } from '@testing-library/react';
import {
FilterStatusValue,
STORAGE_KEY,
useOrderListGridState,
} from './orders-container';
import { Filter } from '@vegaprotocol/orders';
import { OrderType } from '@vegaprotocol/types';
describe('useOrderListGridState', () => {
afterAll(() => {
localStorage.clear();
});
const setup = (filter: Filter | undefined) => {
return renderHook(() => useOrderListGridState(filter));
};
it.each(Object.values(Filter))(
'providers correct AgGrid filter for %s',
(filter) => {
const { result } = setup(filter);
expect(typeof result.current.updateGridState).toBe('function');
expect(result.current.gridState).toEqual({
columnState: undefined,
filterModel: {
status: {
value: FilterStatusValue[filter],
},
},
});
}
);
it('provides correct AgGrid filter for all', () => {
const { result } = setup(undefined);
expect(typeof result.current.updateGridState).toBe('function');
expect(result.current.gridState).toEqual({
columnState: undefined,
filterModel: undefined,
});
});
it.each(Object.values(Filter))(
'sets and stores column state and filters for %s',
(filter) => {
const filterModel = {
type: {
value: [OrderType.TYPE_LIMIT],
},
};
const { result } = setup(filter);
act(() => {
result.current.updateGridState(filter, {
filterModel,
});
});
expect(result.current.gridState).toEqual({
columnState: undefined,
filterModel: {
...filterModel,
status: {
value: FilterStatusValue[filter],
},
},
});
const columnState = [{ colId: 'status', width: 200 }];
act(() => {
result.current.updateGridState(filter, {
columnState,
});
});
expect(result.current.gridState).toEqual({
columnState,
filterModel: {
...filterModel,
status: {
value: FilterStatusValue[filter],
},
},
});
const storeKeyMap = {
[Filter.Open]: 'open',
[Filter.Rejected]: 'rejected',
[Filter.Closed]: 'closed',
};
expect(JSON.parse(localStorage.getItem(STORAGE_KEY) || '')).toMatchObject(
{
state: {
[storeKeyMap[filter]]: {
columnState,
filterModel, // no need to check that status is set, hook will return status
},
},
}
);
}
);
});
@@ -1,166 +0,0 @@
import { useDataGridEvents } from '@vegaprotocol/datagrid';
import { t } from '@vegaprotocol/i18n';
import { Filter } from '@vegaprotocol/orders';
import { OrderListManager } from '@vegaprotocol/orders';
import { Splash } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet';
import {
useMarketClickHandler,
useMarketLiquidityClickHandler,
} from '../../lib/hooks/use-market-click-handler';
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import type { DataGridStore } from '../../stores/datagrid-store-slice';
import { OrderStatus } from '@vegaprotocol/types';
export const FilterStatusValue = {
[Filter.Open]: [OrderStatus.STATUS_ACTIVE, OrderStatus.STATUS_PARKED],
[Filter.Closed]: [
OrderStatus.STATUS_CANCELLED,
OrderStatus.STATUS_EXPIRED,
OrderStatus.STATUS_FILLED,
OrderStatus.STATUS_PARTIALLY_FILLED,
OrderStatus.STATUS_STOPPED,
],
[Filter.Rejected]: [OrderStatus.STATUS_REJECTED],
};
export interface OrderContainerProps {
marketId?: string;
filter?: Filter;
}
export const OrdersContainer = ({ marketId, filter }: OrderContainerProps) => {
const { pubKey, isReadOnly } = useVegaWallet();
const onMarketClick = useMarketClickHandler(true);
const onOrderTypeClick = useMarketLiquidityClickHandler();
const { gridState, updateGridState } = useOrderListGridState(filter);
const gridStoreCallbacks = useDataGridEvents(gridState, (newState) => {
updateGridState(filter, newState);
});
if (!pubKey) {
return <Splash>{t('Please connect Vega wallet')}</Splash>;
}
return (
<OrderListManager
partyId={pubKey}
marketId={marketId}
filter={filter}
onMarketClick={onMarketClick}
onOrderTypeClick={onOrderTypeClick}
isReadOnly={isReadOnly}
gridProps={gridStoreCallbacks}
/>
);
};
export const STORAGE_KEY = 'vega_order_list_store';
const useOrderListStore = create<{
open: DataGridStore;
closed: DataGridStore;
rejected: DataGridStore;
all: DataGridStore;
update: (filter: Filter | undefined, gridStore: DataGridStore) => void;
}>()(
persist(
(set) => ({
open: {},
closed: {},
rejected: {},
all: {},
update: (filter, newStore) => {
switch (filter) {
case Filter.Open: {
set((curr) => ({
open: {
...curr.open,
...newStore,
},
}));
return;
}
case Filter.Closed: {
set((curr) => ({
closed: {
...curr.closed,
...newStore,
},
}));
return;
}
case Filter.Rejected: {
set((curr) => ({
rejected: {
...curr.rejected,
...newStore,
},
}));
return;
}
case undefined: {
set((curr) => ({
all: {
...curr.all,
...newStore,
},
}));
return;
}
}
},
}),
{
name: STORAGE_KEY,
}
)
);
export const useOrderListGridState = (filter: Filter | undefined) => {
const updateGridState = useOrderListStore((store) => store.update);
const gridState = useOrderListStore((store) => {
// Return the column/filter state for the given filter but ensuring that
// each filter controlled by the tab is always applied
switch (filter) {
case Filter.Open: {
return {
columnState: store.open.columnState,
filterModel: {
...store.open.filterModel,
status: {
value: FilterStatusValue[Filter.Open],
},
},
};
}
case Filter.Closed: {
return {
columnState: store.closed.columnState,
filterModel: {
...store.closed.filterModel,
status: {
value: FilterStatusValue[Filter.Closed],
},
},
};
}
case Filter.Rejected: {
return {
columnState: store.rejected.columnState,
filterModel: {
...store.rejected.filterModel,
status: {
value: FilterStatusValue[Filter.Rejected],
},
},
};
}
default: {
return store.all;
}
}
});
return { gridState, updateGridState };
};
@@ -1 +0,0 @@
export * from './positions-container';
-5
View File
@@ -3,17 +3,12 @@ import { useUpdateNetworkParametersToasts } from '@vegaprotocol/proposals';
import { useVegaTransactionToasts } from '@vegaprotocol/web3';
import { useEthereumTransactionToasts } from '@vegaprotocol/web3';
import { useEthereumWithdrawApprovalsToasts } from '@vegaprotocol/web3';
import { Routes } from './client-router';
import { useReadyToWithdrawalToasts } from '@vegaprotocol/withdraws';
export const ToastsManager = () => {
useUpdateNetworkParametersToasts();
useVegaTransactionToasts();
useEthereumTransactionToasts();
useEthereumWithdrawApprovalsToasts();
useReadyToWithdrawalToasts({
withdrawalsLink: `${Routes.PORTFOLIO}`,
});
const toasts = useToasts((store) => store.toasts);
return <ToastsContainer order="desc" toasts={toasts} />;
@@ -1,25 +0,0 @@
import type { ColumnState } from 'ag-grid-community';
import type { StateCreator } from 'zustand';
export type DataGridStore = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
filterModel?: { [key: string]: any };
columnState?: ColumnState[];
};
export type DataGridSlice = {
gridStore: DataGridStore;
updateGridStore: (gridStore: DataGridStore) => void;
};
export const createDataGridSlice: StateCreator<DataGridSlice> = (set) => ({
gridStore: {},
updateGridStore: (newStore) => {
set((curr) => ({
gridStore: {
...curr.gridStore,
...newStore,
},
}));
},
});
@@ -7,7 +7,6 @@ import {
} from '@testing-library/react';
import * as helpers from '@vegaprotocol/data-provider';
import { AccountManager } from './accounts-manager';
import type { useDataGridEvents } from '@vegaprotocol/datagrid';
const mockedUseDataProvider = jest.fn();
jest.mock('@vegaprotocol/data-provider', () => ({
@@ -15,13 +14,6 @@ jest.mock('@vegaprotocol/data-provider', () => ({
useDataProvider: jest.fn(() => mockedUseDataProvider()),
}));
const gridProps = {
onGridReady: jest.fn(),
onColumnResized: jest.fn(),
onFilterChanged: jest.fn(),
onSortChanged: jest.fn(),
} as unknown as ReturnType<typeof useDataGridEvents>;
describe('AccountManager', () => {
describe('when rerender', () => {
beforeEach(() => {
@@ -51,7 +43,6 @@ describe('AccountManager', () => {
partyId="partyOne"
onClickAsset={jest.fn}
isReadOnly={false}
gridProps={gridProps}
/>
);
expect(
@@ -64,7 +55,6 @@ describe('AccountManager', () => {
partyId="partyTwo"
onClickAsset={jest.fn}
isReadOnly={false}
gridProps={gridProps}
/>
);
});
@@ -82,7 +72,6 @@ describe('AccountManager', () => {
partyId="partyOne"
onClickAsset={jest.fn}
isReadOnly={false}
gridProps={gridProps}
/>
);
rerenderer = rerender;
@@ -96,7 +85,6 @@ describe('AccountManager', () => {
partyId="partyOne"
onClickAsset={jest.fn}
isReadOnly={false}
gridProps={gridProps}
/>
);
});
@@ -122,7 +110,6 @@ describe('AccountManager', () => {
partyId="partyOne"
onClickAsset={jest.fn}
isReadOnly={false}
gridProps={gridProps}
/>
);
});
+4 -4
View File
@@ -11,7 +11,6 @@ import type { PinnedAsset } from './accounts-table';
import { AccountTable } from './accounts-table';
import { Dialog } from '@vegaprotocol/ui-toolkit';
import BreakdownTable from './breakdown-table';
import type { useDataGridEvents } from '@vegaprotocol/datagrid';
const AccountBreakdown = ({
assetId,
@@ -103,7 +102,7 @@ interface AccountManagerProps {
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
isReadOnly: boolean;
pinnedAsset?: PinnedAsset;
gridProps?: ReturnType<typeof useDataGridEvents>;
storeKey?: string;
}
export const AccountManager = ({
@@ -113,11 +112,12 @@ export const AccountManager = ({
partyId,
isReadOnly,
pinnedAsset,
storeKey,
onMarketClick,
gridProps,
}: AccountManagerProps) => {
const gridRef = useRef<AgGridReact | null>(null);
const [breakdownAssetId, setBreakdownAssetId] = useState<string>();
const { data, error } = useDataProvider({
dataProvider: aggregatedAccountsDataProvider,
variables: { partyId },
@@ -144,8 +144,8 @@ export const AccountManager = ({
onClickBreakdown={setBreakdownAssetId}
isReadOnly={isReadOnly}
pinnedAsset={pinnedAsset}
storeKey={storeKey}
overlayNoRowsTemplate={error ? error.message : t('No accounts')}
{...gridProps}
/>
<AccountBreakdownDialog
assetId={breakdownAssetId}
+2
View File
@@ -73,6 +73,7 @@ export interface AccountTableProps extends AgGridReactProps {
onClickBreakdown?: (assetId: string) => void;
isReadOnly: boolean;
pinnedAsset?: PinnedAsset;
storeKey?: string;
}
export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
@@ -306,6 +307,7 @@ export const AccountTable = forwardRef<AgGridReact, AccountTableProps>(
<AgGrid
{...props}
style={{ width: '100%', height: '100%' }}
overlayNoRowsTemplate={t('No accounts')}
getRowId={({ data }: { data: AccountFields }) => data.asset.id}
ref={ref}
tooltipShowDelay={500}
+1 -9
View File
@@ -1,12 +1,4 @@
{
"presets": [
[
"@nrwl/next/babel",
{
"runtime": "automatic",
"useBuiltIns": "usage"
}
]
],
"presets": ["@nrwl/next/babel"],
"plugins": []
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"],
"ignorePatterns": ["!**/*", "__generated__"],
"ignorePatterns": ["!**/*", "__generated__", "__generated___"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
+1 -1
View File
@@ -8,7 +8,7 @@
"executor": "@nrwl/web:rollup",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/libs/announcements",
"outputPath": "dist/libs/accounts",
"tsConfig": "libs/announcements/tsconfig.lib.json",
"project": "libs/announcements/package.json",
"entryFile": "libs/announcements/src/index.ts",
+1 -1
View File
@@ -131,7 +131,7 @@ export const CandlesChartContainer = ({
return (
<div className="h-full flex flex-col">
<div className="px-4 py-2 flex flex-row flex-wrap gap-2">
<div className="px-4 py-2 flex flex-row flex-wrap gap-4">
<DropdownMenu
trigger={
<DropdownMenuTrigger>
+2 -1
View File
@@ -1,4 +1,5 @@
export * from './lib/ag-grid/ag-grid-lazy';
export * from './lib/ag-grid/use-column-sizes';
export * from './lib/column-definitions';
@@ -23,4 +24,4 @@ export * from './lib/type-helpers';
export * from './lib/cells/grid-progress-bar';
export * from './lib/ag-grid-update';
export * from './lib/use-datagrid-events';
export * from './lib/use-bottom-placeholder';
@@ -2,16 +2,23 @@ import type { AgGridReactProps, AgReactUiProps } from 'ag-grid-react';
import { AgGridReact } from 'ag-grid-react';
import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
import { t } from '@vegaprotocol/i18n';
import { useColumnSizes } from './use-column-sizes';
import classNames from 'classnames';
export const AgGridThemed = ({
style,
gridRef,
storeKey,
...props
}: (AgGridReactProps | AgReactUiProps) & {
style?: React.CSSProperties;
gridRef?: React.ForwardedRef<AgGridReact>;
storeKey?: string;
}) => {
const commonColumnCallbacks = useColumnSizes({
storeKey,
props,
});
const { theme } = useThemeSwitcher();
const defaultProps = {
rowHeight: 22,
@@ -29,7 +36,12 @@ export const AgGridThemed = ({
return (
<div className={wrapperClasses} style={style}>
<AgGridReact {...defaultProps} {...props} ref={gridRef} />
<AgGridReact
{...defaultProps}
{...props}
{...commonColumnCallbacks}
ref={gridRef}
/>
</div>
);
};
@@ -4,6 +4,7 @@ import type { AgGridReactProps, AgGridReact } from 'ag-grid-react';
type Props = AgGridReactProps & {
style?: React.CSSProperties;
gridRef?: React.Ref<AgGridReact>;
storeKey?: string;
};
export const AgGridLazyInternal = lazy(() =>
@@ -0,0 +1,122 @@
import type {
Column,
ColumnResizedEvent,
GridSizeChangedEvent,
GridReadyEvent,
} from 'ag-grid-community';
import { renderHook, act, waitFor } from '@testing-library/react';
import { useColumnSizes } from './use-column-sizes';
import * as reactHelpers from '@vegaprotocol/react-helpers';
const mockApis = {
api: {
sizeColumnsToFit: jest.fn(),
},
columnApi: {
setColumnWidths: jest.fn(),
},
};
const mockValueSetter = jest.fn();
const mockStore = {
sizes: { testid: { col1: 100 } },
valueSetter: mockValueSetter,
};
jest.mock('zustand', () => ({
...jest.requireActual('zustand'),
create: () =>
jest.fn(() =>
jest.fn().mockImplementation((creator) => {
return creator(mockStore);
})
),
}));
describe('UseColumnSizes hook', () => {
const storeKey = 'testid';
beforeEach(() => {
jest.clearAllMocks();
});
it('should return proper methods', () => {
const { result } = renderHook(() =>
useColumnSizes({ storeKey, props: {} })
);
expect(Object.keys(result.current)).toHaveLength(3);
expect(result.current).toStrictEqual({
onColumnResized: expect.any(Function),
onGridReady: expect.any(Function),
onGridSizeChanged: expect.any(Function),
});
});
it('onGridSizeChanged should call setSize', async () => {
jest
.spyOn(reactHelpers, 'useScreenDimensions')
.mockReturnValue({ screenSize: 'xxl' });
const { result } = renderHook(() =>
useColumnSizes({ storeKey, props: {} })
);
await act(() => {
result.current.onGridSizeChanged?.({
clientWidth: 1000,
...mockApis,
} as GridSizeChangedEvent);
});
await waitFor(() => {
expect(mockApis.columnApi.setColumnWidths).toHaveBeenCalledWith([
{ key: 'col1', newWidth: 100 },
]);
});
});
it('onColumnResized should fill up store', async () => {
const columns: Column[] = [
{ getColId: () => 'col1', getActualWidth: () => 100 },
{ getColId: () => 'col2', getActualWidth: () => 200 },
] as Column[];
const sizeObj = { col1: 100, col2: 200, clientWidth: 1000 };
const { result } = renderHook(() =>
useColumnSizes({ storeKey, props: {} })
);
await act(() => {
result.current.onGridSizeChanged?.({
clientWidth: 1000,
...mockApis,
} as GridSizeChangedEvent);
});
await act(() => {
result.current.onColumnResized?.({
columns,
finished: true,
source: 'uiColumnDragged',
...mockApis,
} as ColumnResizedEvent);
});
await waitFor(() => {
expect(mockValueSetter).toHaveBeenCalledWith(storeKey, sizeObj);
});
});
it('onGridReady should call setSizes', async () => {
const props = { onGridReady: jest.fn() };
const { result } = renderHook(() => useColumnSizes({ storeKey, props }));
const obTest = { cool: 1, ...mockApis };
await act(() => {
result.current.onGridReady?.(obTest as GridReadyEvent);
});
expect(props.onGridReady).toHaveBeenCalledWith(obTest);
expect(mockApis.api.sizeColumnsToFit).toHaveBeenCalledWith();
});
it('if no storeKey should be transparent', () => {
const { result } = renderHook(() =>
useColumnSizes({ storeKey: '', props: {} })
);
expect(result.current).toStrictEqual({
onColumnResized: undefined,
onGridReady: undefined,
onGridSizeChanged: undefined,
});
});
});
@@ -0,0 +1,145 @@
import { useCallback, useRef } from 'react';
import type {
GridSizeChangedEvent,
GridReadyEvent,
ColumnResizedEvent,
} from 'ag-grid-community';
import type { AgGridReactProps, AgReactUiProps } from 'ag-grid-react';
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';
import { useScreenDimensions } from '@vegaprotocol/react-helpers';
const STORAGE_KEY = 'vega_columns_sizes_store';
export const useColumnSizesStore = create<{
sizes: Record<string, Record<string, number>>;
valueSetter: (storeKey: string, value: Record<string, number>) => void;
}>()(
persist(
immer((set) => ({
sizes: {},
valueSetter: (storeKey, value) =>
set((state) => {
state.sizes[storeKey] = {
...(state.sizes[storeKey] || {}),
...value,
};
return state;
}),
})),
{ name: STORAGE_KEY }
)
);
interface UseColumnSizesProps {
props: AgGridReactProps | AgReactUiProps;
storeKey?: string;
}
export const useColumnSizes = ({
storeKey = '',
props,
}: UseColumnSizesProps): {
onColumnResized?: (event: ColumnResizedEvent) => void;
onGridReady?: (event: GridReadyEvent) => void;
onGridSizeChanged?: (event: GridSizeChangedEvent) => void;
} => {
const sizes = useColumnSizesStore((store) => store.sizes[storeKey] || {});
const valueSetter = useColumnSizesStore((store) => store.valueSetter);
const widthRef = useRef(sizes['clientWidth'] || 0);
const {
onColumnResized: parentOnColumnResized,
onGridReady: parentOnGridReady,
onGridSizeChanged: parentOnGridSizeChanged,
} = props;
const recalculateSizes = useCallback((sizes: Record<string, number>) => {
if (
widthRef.current &&
sizes['clientWidth'] &&
widthRef.current !== sizes['clientWidth']
) {
const oldWidth = sizes['clientWidth'];
const ratio = widthRef.current / oldWidth;
return {
...Object.entries(sizes).reduce((agg, [key, value]) => {
agg[key] = value * ratio;
return agg;
}, {} as Record<string, number>),
width: widthRef.current,
} as Record<string, number>;
}
return sizes;
}, []);
const onColumnResized = useCallback(
(event: ColumnResizedEvent) => {
parentOnColumnResized?.(event);
if (
storeKey &&
event.source === 'uiColumnDragged' &&
event.finished &&
widthRef.current
) {
const { columns } = event;
if (columns?.length) {
const sizesObj = columns.reduce((aggr, column) => {
aggr[column.getColId()] = column.getActualWidth();
return aggr;
}, {} as Record<string, number>);
sizesObj['clientWidth'] = widthRef.current;
valueSetter(storeKey, sizesObj);
}
}
},
[valueSetter, storeKey, parentOnColumnResized]
);
const { screenSize } = useScreenDimensions();
const largeScreen = ['xl', 'xxl', 'xxxl'].includes(screenSize);
const setSizes = useCallback(
(apiEvent: GridReadyEvent | GridSizeChangedEvent) => {
if (!storeKey || !Object.keys(sizes).length || !widthRef.current) {
largeScreen && apiEvent?.api.sizeColumnsToFit();
} else {
const recalculatedSizes = recalculateSizes(sizes);
const newSizes = Object.entries(recalculatedSizes).map(
([key, size]) => ({
key,
newWidth: size,
})
);
apiEvent.columnApi.setColumnWidths(newSizes);
}
},
[storeKey, recalculateSizes, sizes, largeScreen]
);
const onGridReady = useCallback(
(event: GridReadyEvent) => {
parentOnGridReady?.(event);
setSizes(event);
},
[setSizes, parentOnGridReady]
);
const onGridSizeChanged = useCallback(
(event: GridSizeChangedEvent) => {
parentOnGridSizeChanged?.(event);
widthRef.current = event.clientWidth;
setSizes(event);
},
[parentOnGridSizeChanged, setSizes]
);
if (storeKey) {
return {
onGridReady,
onGridSizeChanged,
onColumnResized,
};
}
return {
onGridReady: parentOnGridReady,
onGridSizeChanged: parentOnGridSizeChanged,
onColumnResized: parentOnColumnResized,
};
};
@@ -0,0 +1,68 @@
import type { RefObject } from 'react';
import { useCallback, useMemo } from 'react';
import type { AgGridReact } from 'ag-grid-react';
import type { IsFullWidthRowParams, RowHeightParams } from 'ag-grid-community';
const NO_HOVER_CSS_RULE = { 'no-hover': 'data?.isLastPlaceholder' };
const ROW_ID = 'bottom-placeholder';
const fullWidthCellRenderer = () => null;
const isFullWidthRow = (params: IsFullWidthRowParams) =>
params.rowNode.data?.isLastPlaceholder;
interface Props {
gridRef: RefObject<AgGridReact>;
disabled?: boolean;
}
// eslint-disable-next-line @typescript-eslint/ban-types
export const useBottomPlaceholder = ({ gridRef, disabled }: Props) => {
const onBodyScrollEnd = useCallback(() => {
const rowCont = gridRef.current?.api.getDisplayedRowCount() ?? 0;
if (rowCont) {
const lastRow = gridRef.current?.api.getDisplayedRowAtIndex(rowCont - 1);
if (lastRow && lastRow.data) {
const placeholderRow = {
...lastRow.data,
isLastPlaceholder: true,
id: ROW_ID,
};
const transaction = gridRef.current?.api.getRowNode(ROW_ID)
? { update: [placeholderRow] }
: { add: [placeholderRow] };
gridRef.current?.api.applyTransaction(transaction);
}
}
}, [gridRef]);
const onRowsChanged = useCallback(() => {
const placeholderNode = gridRef.current?.api.getRowNode(ROW_ID);
if (placeholderNode) {
const transaction = {
remove: [placeholderNode.data],
};
gridRef.current?.api.applyTransaction(transaction);
}
onBodyScrollEnd();
}, [gridRef, onBodyScrollEnd]);
const getRowHeight = useCallback(
(params: RowHeightParams) =>
params.data?.isLastPlaceholder ? 50 : undefined,
[]
);
return useMemo(
() =>
!disabled
? {
onBodyScrollEnd,
rowClassRules: NO_HOVER_CSS_RULE,
isFullWidthRow,
fullWidthCellRenderer,
onSortChanged: onRowsChanged,
onFilterChanged: onRowsChanged,
getRowHeight,
}
: {},
[onBodyScrollEnd, onRowsChanged, disabled, getRowHeight]
);
};
@@ -1,185 +0,0 @@
import { act, render, waitFor } from '@testing-library/react';
import {
useDataGridEvents,
GRID_EVENT_DEBOUNCE_TIME,
} from './use-datagrid-events';
import { AgGridThemed } from './ag-grid/ag-grid-lazy-themed';
import type { MutableRefObject } from 'react';
import { useRef } from 'react';
import type { AgGridReact } from 'ag-grid-react';
const gridProps = {
rowData: [{ id: 1 }],
columnDefs: [
{
field: 'id',
width: 100,
resizable: true,
filter: 'agNumberColumnFilter',
},
],
style: { width: 500, height: 300 },
};
// Not using render hook so I can pass event callbacks
// to a rendered grid
function setup(...args: Parameters<typeof useDataGridEvents>) {
let gridRef;
function TestComponent() {
const hookCallbacks = useDataGridEvents(...args);
gridRef = useRef<AgGridReact | null>(null);
return <AgGridThemed gridRef={gridRef} {...gridProps} {...hookCallbacks} />;
}
render(<TestComponent />);
return gridRef as unknown as MutableRefObject<AgGridReact>;
}
describe('useDataGridEvents', () => {
const originalWarn = console.warn;
beforeAll(() => {
jest.useFakeTimers();
// disabling some ag grid warnings that are caused by test setup only
console.warn = () => undefined;
});
afterAll(() => {
jest.useRealTimers();
console.warn = originalWarn;
});
it('default state is set and callback is called on column or filter event', async () => {
const callback = jest.fn();
const initialState = {
filterModel: undefined,
columnState: undefined,
};
const result = setup(initialState, callback);
// column state was not updated, so the default width provided by the
// col def should be set
expect(result.current.columnApi.getColumnState()[0].width).toEqual(
gridProps.columnDefs[0].width
);
// no filters set
expect(result.current.api.getFilterModel()).toEqual({});
const newWidth = 400;
// Set col width
await act(async () => {
result.current.columnApi.setColumnWidth('id', newWidth);
});
act(() => {
jest.advanceTimersByTime(GRID_EVENT_DEBOUNCE_TIME);
});
expect(callback).toHaveBeenCalledWith({
columnState: [expect.objectContaining({ colId: 'id', width: newWidth })],
filterModel: {},
});
callback.mockClear();
expect(result.current.columnApi.getColumnState()[0].width).toEqual(
newWidth
);
// Set filter
await act(async () => {
result.current.columnApi.applyColumnState({
state: [{ colId: 'id', sort: 'asc' }],
applyOrder: true,
});
});
act(() => {
jest.advanceTimersByTime(GRID_EVENT_DEBOUNCE_TIME);
});
expect(callback).toHaveBeenCalledWith({
columnState: [expect.objectContaining({ colId: 'id', sort: 'asc' })],
filterModel: {},
});
callback.mockClear();
expect(result.current.columnApi.getColumnState()[0].sort).toEqual('asc');
// Set filter
const idFilter = {
filter: 1,
filterType: 'number',
type: 'equals',
};
await act(async () => {
result.current.api.setFilterModel({
id: idFilter,
});
});
act(() => {
jest.advanceTimersByTime(GRID_EVENT_DEBOUNCE_TIME);
});
expect(callback).toHaveBeenCalledWith({
columnState: expect.any(Object),
filterModel: {
id: idFilter,
},
});
callback.mockClear();
expect(result.current.api.getFilterModel()['id']).toEqual(idFilter);
});
it('applies grid state on ready', async () => {
const idFilter = {
filter: 1,
filterType: 'number',
type: 'equals',
};
const colState = { colId: 'id', width: 300, sort: 'desc' as const };
const initialState = {
filterModel: {
id: idFilter,
},
columnState: [colState],
};
const result = setup(initialState, jest.fn());
await waitFor(() => {
expect(result.current.api.getFilterModel()['id']).toEqual(idFilter);
expect(result.current.columnApi.getColumnState()[0]).toEqual(
expect.objectContaining(colState)
);
});
});
it('debounces events', async () => {
const callback = jest.fn();
const initialState = {
filterModel: undefined,
columnState: undefined,
};
const result = setup(initialState, callback);
const newWidth = 400;
// Set col width multiple times
await act(async () => {
result.current.columnApi.setColumnWidth('id', newWidth);
result.current.columnApi.setColumnWidth('id', newWidth);
result.current.columnApi.setColumnWidth('id', newWidth);
});
expect(callback).not.toHaveBeenCalled();
act(() => {
jest.advanceTimersByTime(GRID_EVENT_DEBOUNCE_TIME);
});
expect(callback).toHaveBeenCalledTimes(1);
});
});
@@ -1,66 +0,0 @@
import debounce from 'lodash/debounce';
import type {
ColumnResizedEvent,
ColumnState,
FilterChangedEvent,
GridReadyEvent,
SortChangedEvent,
} from 'ag-grid-community';
import { useCallback, useMemo } from 'react';
type State = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
filterModel?: { [key: string]: any };
columnState?: ColumnState[];
};
type Event = ColumnResizedEvent | FilterChangedEvent | SortChangedEvent;
export const GRID_EVENT_DEBOUNCE_TIME = 300;
export const useDataGridEvents = (
state: State,
callback: (data: State) => void
) => {
// This function can be called very frequently by the onColumnResized
// grid callback, so its memoized to only update after resizing is finished
const onGridChange = useMemo(
() =>
debounce(({ api, columnApi }: Event) => {
if (!api || !columnApi) return;
const columnState = columnApi.getColumnState();
const filterModel = api.getFilterModel();
callback({ columnState, filterModel });
}, GRID_EVENT_DEBOUNCE_TIME),
[callback]
);
// check if we have stored column states or filter models and apply if we do
const onGridReady = useCallback(
({ api, columnApi }: GridReadyEvent) => {
if (!api || !columnApi) return;
if (state.columnState) {
columnApi.applyColumnState({
state: state.columnState,
applyOrder: true,
});
} else {
// ensure columns fit available space if no widths are set
api.sizeColumnsToFit();
}
if (state.filterModel) {
api.setFilterModel(state.filterModel);
}
},
[state]
);
return {
onGridReady,
onColumnResized: onGridChange,
onFilterChanged: onGridChange,
onSortChanged: onGridChange,
};
};
@@ -124,7 +124,7 @@ export const DealTicket = ({
normalizedOrder.size,
market.positionDecimalPlaces
).multipliedBy(toBigNum(price, market.decimalPlaces)),
market.decimalPlaces
asset.decimals
);
}
return null;
@@ -133,6 +133,7 @@ export const DealTicket = ({
normalizedOrder?.size,
market.decimalPlaces,
market.positionDecimalPlaces,
asset.decimals,
]);
const feeEstimate = useEstimateFees(
+3 -1
View File
@@ -24,8 +24,10 @@ export const DepositsTable = forwardRef<
return (
<AgGrid
ref={ref}
defaultColDef={{ flex: 1 }}
defaultColDef={{ resizable: true }}
style={{ width: '100%', height: '100%' }}
suppressCellFocus={true}
storeKey="depositTable"
{...props}
>
<AgGridColumn headerName="Asset" field="asset.symbol" />
+1 -1
View File
@@ -1,3 +1,3 @@
export * from './lib/fills-manager';
export * from './lib/fills-container';
export * from './lib/fills-data-provider';
export * from './lib/__generated__/Fills';
+33
View File
@@ -0,0 +1,33 @@
import { t } from '@vegaprotocol/i18n';
import { Splash } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { FillsManager } from './fills-manager';
export const FillsContainer = ({
marketId,
onMarketClick,
storeKey,
}: {
marketId?: string;
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
storeKey?: string;
}) => {
const { pubKey } = useVegaWallet();
if (!pubKey) {
return (
<Splash>
<p>{t('Please connect Vega wallet')}</p>
</Splash>
);
}
return (
<FillsManager
partyId={pubKey}
marketId={marketId}
onMarketClick={onMarketClick}
storeKey={storeKey}
/>
);
};
+8 -4
View File
@@ -2,7 +2,7 @@ import type { AgGridReact } from 'ag-grid-react';
import { useRef } from 'react';
import { t } from '@vegaprotocol/i18n';
import { FillsTable } from './fills-table';
import type { useDataGridEvents } from '@vegaprotocol/datagrid';
import { useBottomPlaceholder } from '@vegaprotocol/datagrid';
import { useDataProvider } from '@vegaprotocol/data-provider';
import type * as Schema from '@vegaprotocol/types';
import { fillsWithMarketProvider } from './fills-data-provider';
@@ -11,14 +11,14 @@ interface FillsManagerProps {
partyId: string;
marketId?: string;
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
gridProps: ReturnType<typeof useDataGridEvents>;
storeKey?: string;
}
export const FillsManager = ({
partyId,
marketId,
onMarketClick,
gridProps,
storeKey,
}: FillsManagerProps) => {
const gridRef = useRef<AgGridReact | null>(null);
const filter: Schema.TradesFilter | Schema.TradesSubscriptionFilter = {
@@ -38,6 +38,9 @@ export const FillsManager = ({
},
variables: { filter },
});
const bottomPlaceholderProps = useBottomPlaceholder({
gridRef,
});
return (
<FillsTable
@@ -45,8 +48,9 @@ export const FillsManager = ({
rowData={data}
partyId={partyId}
onMarketClick={onMarketClick}
storeKey={storeKey}
{...bottomPlaceholderProps}
overlayNoRowsTemplate={error ? error.message : t('No fills')}
{...gridProps}
/>
);
};
+1
View File
@@ -39,6 +39,7 @@ export type Role = typeof TAKER | typeof MAKER | '-';
export type Props = (AgGridReactProps | AgReactUiProps) & {
partyId: string;
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
storeKey?: string;
};
export const FillsTable = forwardRef<AgGridReact, Props>(
+1
View File
@@ -1,2 +1,3 @@
export * from './lib/ledger-container';
export * from './lib/ledger-manager';
export * from './lib/__generated__/LedgerEntries';
+17
View File
@@ -0,0 +1,17 @@
import { t } from '@vegaprotocol/i18n';
import { Splash } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { LedgerManager } from './ledger-manager';
export const LedgerContainer = () => {
const { pubKey } = useVegaWallet();
if (!pubKey) {
return (
<Splash>
<p>{t('Please connect Vega wallet')}</p>
</Splash>
);
}
return <LedgerManager partyId={pubKey} />;
};
+7 -19
View File
@@ -10,7 +10,6 @@ import { LedgerTable } from './ledger-table';
import { useDataProvider } from '@vegaprotocol/data-provider';
import type * as Types from '@vegaprotocol/types';
import { LedgerExportLink } from './ledger-export-link';
import type { useDataGridEvents } from '@vegaprotocol/datagrid';
export interface Filter {
vegaTime?: {
@@ -25,13 +24,7 @@ const defaultFilter = {
},
};
export const LedgerManager = ({
partyId,
gridProps,
}: {
partyId: string;
gridProps: ReturnType<typeof useDataGridEvents>;
}) => {
export const LedgerManager = ({ partyId }: { partyId: string }) => {
const gridRef = useRef<AgGridReact | null>(null);
const [filter, setFilter] = useState<Filter>(defaultFilter);
@@ -40,7 +33,7 @@ export const LedgerManager = ({
partyId,
dateRange: filter?.vegaTime?.value,
pagination: {
first: 10,
first: 5000,
},
}),
[partyId, filter?.vegaTime?.value]
@@ -52,23 +45,18 @@ export const LedgerManager = ({
skip: !variables.partyId,
});
const onFilterChanged = useCallback(
(event: FilterChangedEvent) => {
const updatedFilter = { ...defaultFilter, ...event.api.getFilterModel() };
setFilter(updatedFilter);
gridProps.onFilterChanged(event);
},
[gridProps]
);
const onFilterChanged = useCallback((event: FilterChangedEvent) => {
const updatedFilter = { ...defaultFilter, ...event.api.getFilterModel() };
setFilter(updatedFilter);
}, []);
return (
<div className="h-full relative">
<LedgerTable
ref={gridRef}
rowData={data}
overlayNoRowsTemplate={error ? error.message : t('No entries')}
{...gridProps}
onFilterChanged={onFilterChanged}
overlayNoRowsTemplate={error ? error.message : t('No entries')}
/>
{data && <LedgerExportLink entries={data} partyId={partyId} />}
</div>
+4 -1
View File
@@ -49,7 +49,7 @@ export const LedgerTable = forwardRef<AgGridReact, LedgerEntryProps>(
(props, ref) => {
return (
<AgGrid
style={{ width: '100%', height: '100%' }}
style={{ width: '100%', height: 'calc(100% - 50px)' }}
ref={ref}
tooltipShowDelay={500}
defaultColDef={{
@@ -61,6 +61,9 @@ export const LedgerTable = forwardRef<AgGridReact, LedgerEntryProps>(
buttons: ['reset'],
},
}}
storeKey="ledgerTable"
suppressLoadingOverlay
suppressNoRowsOverlay
{...props}
>
<AgGridColumn
@@ -1,7 +1,6 @@
# Liquidity Provisions
fragment LiquidityProvisionFields on LiquidityProvision {
id
party {
id
accountsConnection(marketId: $marketId, type: ACCOUNT_TYPE_BOND) {
+2 -3
View File
@@ -3,14 +3,14 @@ import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type LiquidityProvisionFieldsFragment = { __typename?: 'LiquidityProvision', id?: string | null, createdAt: any, updatedAt?: any | null, commitmentAmount: string, fee: string, status: Types.LiquidityProvisionStatus, party: { __typename?: 'Party', id: string, accountsConnection?: { __typename?: 'AccountsConnection', edges?: Array<{ __typename?: 'AccountEdge', node: { __typename?: 'AccountBalance', type: Types.AccountType, balance: string } } | null> | null } | null } };
export type LiquidityProvisionFieldsFragment = { __typename?: 'LiquidityProvision', createdAt: any, updatedAt?: any | null, commitmentAmount: string, fee: string, status: Types.LiquidityProvisionStatus, party: { __typename?: 'Party', id: string, accountsConnection?: { __typename?: 'AccountsConnection', edges?: Array<{ __typename?: 'AccountEdge', node: { __typename?: 'AccountBalance', type: Types.AccountType, balance: string } } | null> | null } | null } };
export type LiquidityProvisionsQueryVariables = Types.Exact<{
marketId: Types.Scalars['ID'];
}>;
export type LiquidityProvisionsQuery = { __typename?: 'Query', market?: { __typename?: 'Market', liquidityProvisionsConnection?: { __typename?: 'LiquidityProvisionsConnection', edges?: Array<{ __typename?: 'LiquidityProvisionsEdge', node: { __typename?: 'LiquidityProvision', id?: string | null, createdAt: any, updatedAt?: any | null, commitmentAmount: string, fee: string, status: Types.LiquidityProvisionStatus, party: { __typename?: 'Party', id: string, accountsConnection?: { __typename?: 'AccountsConnection', edges?: Array<{ __typename?: 'AccountEdge', node: { __typename?: 'AccountBalance', type: Types.AccountType, balance: string } } | null> | null } | null } } } | null> | null } | null } | null };
export type LiquidityProvisionsQuery = { __typename?: 'Query', market?: { __typename?: 'Market', liquidityProvisionsConnection?: { __typename?: 'LiquidityProvisionsConnection', edges?: Array<{ __typename?: 'LiquidityProvisionsEdge', node: { __typename?: 'LiquidityProvision', createdAt: any, updatedAt?: any | null, commitmentAmount: string, fee: string, status: Types.LiquidityProvisionStatus, party: { __typename?: 'Party', id: string, accountsConnection?: { __typename?: 'AccountsConnection', edges?: Array<{ __typename?: 'AccountEdge', node: { __typename?: 'AccountBalance', type: Types.AccountType, balance: string } } | null> | null } | null } } } | null> | null } | null } | null };
export type LiquidityProvisionsUpdateSubscriptionVariables = Types.Exact<{
partyId?: Types.InputMaybe<Types.Scalars['ID']>;
@@ -31,7 +31,6 @@ export type LiquidityProviderFeeShareQuery = { __typename?: 'Query', market?: {
export const LiquidityProvisionFieldsFragmentDoc = gql`
fragment LiquidityProvisionFields on LiquidityProvision {
id
party {
id
accountsConnection(marketId: $marketId, type: ACCOUNT_TYPE_BOND) {
@@ -38,7 +38,8 @@ export const liquidityProvisionsDataProvider = makeDataProvider<
) => {
return produce(data || [], (draft) => {
deltas?.forEach((delta) => {
const index = draft.findIndex((a) => delta.id === a.id);
const id = delta.id;
const index = draft.findIndex((a) => delta.id === id);
if (index !== -1) {
draft[index].commitmentAmount = delta.commitmentAmount;
draft[index].fee = delta.fee;
@@ -46,7 +47,6 @@ export const liquidityProvisionsDataProvider = makeDataProvider<
draft[index].status = delta.status;
} else {
draft.unshift({
id: delta.id,
commitmentAmount: delta.commitmentAmount,
fee: delta.fee,
status: delta.status,
+3 -2
View File
@@ -188,7 +188,7 @@ export const LiquidityTable = forwardRef<AgGridReact, LiquidityTableProps>(
<AgGrid
style={{ width: '100%', height: '100%' }}
overlayNoRowsTemplate={t('No liquidity provisions')}
getRowId={({ data }) => data.id}
getRowId={({ data }) => `${data.party.id}-${data.status}`}
ref={ref}
tooltipShowDelay={500}
defaultColDef={{
@@ -197,9 +197,10 @@ export const LiquidityTable = forwardRef<AgGridReact, LiquidityTableProps>(
tooltipComponent: TooltipCellComponent,
sortable: true,
}}
storeKey="liquidityProvisionTable"
{...props}
columnDefs={colDefs}
/>
></AgGrid>
);
}
);
@@ -61,6 +61,7 @@ export const MarketListTable = forwardRef<
columnDefs={columnDefs}
suppressCellFocus
components={{ PriceFlashCell, MarketName }}
storeKey="allMarkets"
{...props}
/>
);
+3 -62
View File
@@ -1,50 +1,13 @@
import { marketDataErrorPolicyGuard } from '@vegaprotocol/data-provider';
import { makeDataProvider } from '@vegaprotocol/data-provider';
import type {
MarketsDataQuery,
MarketsDataQueryVariables,
} from './__generated__/markets-data';
import type {
MarketDataUpdateSubscription,
MarketDataUpdateFieldsFragment,
MarketDataUpdateSubscriptionVariables,
} from './__generated__/market-data';
import { MarketDataUpdateDocument } from './__generated__/market-data';
import type { MarketsDataQuery } from './__generated__/markets-data';
import { MarketsDataDocument } from './__generated__/markets-data';
import type { MarketData } from './market-data-provider';
const getData = (responseData: MarketsDataQuery | null): MarketData[] =>
const getData = (responseData: MarketsDataQuery | null): MarketData[] | null =>
responseData?.marketsConnection?.edges
.filter((edge) => edge.node.data)
.map((edge) => edge.node.data as MarketData) || [];
export const mapMarketDataUpdateToMarketData = (
delta: MarketDataUpdateFieldsFragment
): MarketData => {
const { marketId, __typename, ...marketData } = delta;
return { ...marketData, market: { id: marketId } };
};
const update = (
data: MarketData[] | null,
delta: MarketDataUpdateFieldsFragment
) => {
const updatedData = data ? [...data] : [];
const item = mapMarketDataUpdateToMarketData(delta);
const index = updatedData.findIndex(
(data) => data.market.id === item.market.id
);
if (index !== -1) {
updatedData[index] = { ...updatedData[index], ...item };
} else {
updatedData.push(item);
}
return updatedData;
};
const getDelta = (
subscriptionData: MarketDataUpdateSubscription
): MarketDataUpdateFieldsFragment => subscriptionData.marketsData[0];
.map((edge) => edge.node.data as MarketData) || null;
export const marketsDataProvider = makeDataProvider<
MarketsDataQuery,
@@ -56,25 +19,3 @@ export const marketsDataProvider = makeDataProvider<
getData,
errorPolicyGuard: marketDataErrorPolicyGuard,
});
type Variables = { marketIds: string[] };
export const marketsLiveDataProvider = makeDataProvider<
MarketsDataQuery,
MarketData[],
MarketDataUpdateSubscription,
MarketDataUpdateFieldsFragment,
Variables,
MarketDataUpdateSubscriptionVariables,
MarketsDataQueryVariables
>({
query: MarketsDataDocument,
subscriptionQuery: MarketDataUpdateDocument,
getData,
getDelta,
update,
errorPolicyGuard: marketDataErrorPolicyGuard,
getQueryVariables: () => ({}),
getSubscriptionVariables: ({ marketIds }: Variables) =>
marketIds.map((marketId) => ({ marketId })),
});
+1 -44
View File
@@ -10,15 +10,10 @@ import type {
} from './__generated__/markets';
import type { MarketsCandlesQueryVariables } from './__generated__/markets-candles';
import {
marketsDataProvider,
marketsLiveDataProvider,
mapMarketDataUpdateToMarketData,
} from './markets-data-provider';
import { marketsDataProvider } from './markets-data-provider';
import { marketDataProvider } from './market-data-provider';
import { marketsCandlesProvider } from './markets-candles-provider';
import type { MarketData } from './market-data-provider';
import type { MarketDataUpdateFieldsFragment } from './__generated__';
import type { MarketCandles } from './markets-candles-provider';
import { useMemo } from 'react';
import * as Schema from '@vegaprotocol/types';
@@ -165,44 +160,6 @@ export const allMarketsWithDataProvider = makeDerivedDataProvider<
addData(parts[0] as Market[], parts[1] as MarketData[])
);
export const allMarketsWithLiveDataProvider = makeDerivedDataProvider<
MarketMaybeWithData[],
MarketMaybeWithData,
{ marketIds: string[] }
>(
[
(callback, client, variables) =>
marketsProvider(callback, client, undefined),
marketsLiveDataProvider,
],
(partsData, variables, prevData, parts) => {
if (prevData && parts[1].isUpdate) {
const data = mapMarketDataUpdateToMarketData(parts[1].delta);
const index = prevData.findIndex(
(market) => market.id === data.market.id
);
if (index !== -1) {
const updatedData = [...prevData];
updatedData[index] = { ...updatedData[index], data };
return updatedData;
} else {
return prevData;
}
}
return addData(partsData[0] as Market[], partsData[1] as MarketData[]);
},
(data, parts) => {
if (!parts[1].isUpdate && parts[1].delta) {
return;
}
return data.find(
(market) =>
market.id ===
(parts[1].delta as MarketDataUpdateFieldsFragment).marketId
);
}
);
export type MarketMaybeWithDataAndCandles = MarketMaybeWithData &
MarketMaybeWithCandles;
+1
View File
@@ -1,4 +1,5 @@
export * from './order-data-provider';
export * from './order-list';
export * from './order-list-manager';
export * from './order-list-container';
export * from './mocks/generate-orders';

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