Compare commits

..
Author SHA1 Message Date
asiaznik 0153ab81ab fix(proposals): truncated text in network param toast 2023-07-04 16:20:38 +02:00
Joe Tsang ce3da1762b chore(governance): add e2e test for proposal filter changes (#4218) 2023-07-04 13:27:12 +01:00
John Walley 5040fbfd07 chore(trading): upgrade pennant to v1.9.0 (#4235) 2023-07-04 12:10:38 +00:00
Edd 74f2cfa4a5 feat(explorer): remove infinite tx loader (#4217) 2023-07-04 12:51:28 +01:00
Art 8c8fe6878a chore(wallet): remove links from the risk disclaimer (#4240) 2023-07-04 12:12:38 +01:00
Bartłomiej Głownia 6e0577aee4 chore(trading): migrate nx to latest version (#4196) 2023-07-04 13:01:25 +02:00
Edd 87b41a30d8 feat(explorer): add pegged order details (#4228) 2023-07-04 11:18:44 +01:00
Edd 0850f31855 fix(governance): reinstate deleted mock (#4232) 2023-07-03 15:27:40 +01:00
Bartłomiej Głownia 43aff8e359 chore(trading): ag-grid upgrade (#4187) 2023-07-01 13:02:23 +02:00
Bartłomiej Głownia 6e9e7c2a5c fix(positions): use market decimal places to format realized and unrealized PnL (#4225) 2023-06-30 14:58:01 +02:00
Mikołaj Młodzikowski f054f4c516 fix(ci): invalidation id without quotes 2023-06-30 11:24:16 +02:00
Mikołaj Młodzikowski f382078ee6 feat(ci): create cloudfront invalidation after deployment (#4223) 2023-06-30 10:46:25 +02:00
Matthew Russell ebc058bcbe chore(trading): vega icons for withrawals table and wallet dropdown (#4203) 2023-06-29 16:48:53 -07:00
Madalina Raicu d3df339696 chore(trading,governance,explorer): release update to vv0.20.19-core-0.71.6 2023-06-29 16:51:57 +03:00
Sam Keen a31008ea26 feat(governance): improve asset proposal details view (#4216) 2023-06-29 14:41:25 +01:00
Edd 5e93e98f07 fix(explorer): fix order amend tx view (#4197) 2023-06-29 12:25:42 +01:00
Ciaran McGhie bf3ff8fb6f fix(ui-toolkit): healthbar tooltips text colour (#4199) 2023-06-29 12:25:14 +01:00
Bartłomiej Głownia 16538ca3a3 feat(trading): show fills across all markets (#4210) 2023-06-29 12:24:51 +01:00
Edd 2fa00dacaa test(governance,markets): tidy up minor test quibbles (#4213) 2023-06-29 11:20:53 +00:00
Ben 45b7c2ad4d test(trading): 6004-CHAR-chart e2e tests (#4179) 2023-06-29 09:14:54 +01:00
Sam Keen ed2c82487d feat(governance): add toggle for closed proposals list (#4190) 2023-06-28 17:53:01 +01:00
Joe Tsang 55143331f1 test(governance): fix staking tooltip test (#4198) 2023-06-28 17:15:06 +01:00
Davide SandEdd cafb3b1c57 fix(explorer): liquidity provision fee percentage (#4183)
Co-authored-by: Edd <edd@vega.xyz>
2023-06-28 15:35:38 +00:00
Mikołaj Młodzikowski 019b2d7d89 feat(ci): fix brakcets 2023-06-28 15:12:03 +02:00
Mikołaj Młodzikowski de0dc4af8e feat(ci): do not check empty arrays 2023-06-28 15:09:17 +02:00
Mikołaj Młodzikowski 6765f4d03a feat: add logic for testing against pull_request_target 2023-06-28 15:06:01 +02:00
Mikołaj Młodzikowski 7bea90e4d1 fix(ci): use jq to generate valid json arrays from bash (#4205) 2023-06-28 14:54:25 +02:00
Matthew Russell ac4762f9d7 chore(trading): reduce space in chart options 2023-06-27 16:35:36 -07:00
Matthew Russell 1195309b61 fix(announcements): update output path for announcements build (#4202) 2023-06-27 15:51:04 -07:00
Mikołaj Młodzikowski 8ce77e23ba feat(ci): support forked repos by checking out using target of pull request (#4193) 2023-06-27 11:18:13 +02:00
m.ray 5cae2ce400 fix(trading): notional size formatting (#4192) 2023-06-27 09:33:42 +01:00
Matthew Russell b8c9f9772e chore(trading): allow window._env_ override (#4159) 2023-06-27 07:40:42 +01:00
Matthew Russell c3e39b6e15 refactor(trading): pane stores for grid state (#4119) 2023-06-26 17:10:22 -07:00
Matthew Russell 77a391448b fix(liquidity): use LiquidityProvision id (#4191) 2023-06-26 20:43:28 +00:00
ArtandBartłomiej Głownia 484d7888cf feat(web3): ready to withdraw toast (#4142)
Co-authored-by: Bartłomiej Głownia <bglownia@gmail.com>
2023-06-26 16:28:14 +02:00
Joe Tsang 187f1929fe chore(governance): increase epoch timeout (#4185) 2023-06-26 14:09:44 +00:00
Matthew Russell 0a4333645b chore(trading,governance,explorer): re-enable nx-cloud (#4182) 2023-06-26 14:47:22 +02:00
Bartłomiej Głownia 310506e5af feat(positions): live update market data in positions table (#4154) 2023-06-26 12:50:57 +02:00
Joe Tsang 0b33ed4299 test(governance): 3802 multisig error test (#4169) 2023-06-26 10:21:13 +01:00
Matthew Russell 13f2e51798 chore(ui-toolkit): remove forwardRef as refs weren't being passed (#4160) 2023-06-23 15:36:21 -07:00
Bartłomiej Głownia fc047feeee fix(trading): avoid items remounting in MarketSelector List (#4162) 2023-06-23 15:35:50 -07:00
Bartłomiej Głownia 005455c870 chore(orders): use columnDefs in orders list, fix sorting by size and filled (#4170) 2023-06-23 15:33:25 -07:00
daro-maj 2c2bc391e8 test(trading): chart depth e2e tests (#4171) 2023-06-23 15:13:28 +02:00
m.ray 445a085190 fix(trading): market info number formatting (#4163) 2023-06-23 12:44:18 +01:00
Sam Keen bb1b236cdf feat(governance): show multisig penalty in tooltip (#4140) 2023-06-22 09:43:46 +01:00
Joe Tsang da66b7b20d test(governance): test market details specifications (#4153) 2023-06-22 08:32:33 +01:00
Ciaran McGhie ec12811f72 fix(ui-toolkit): visual fixes for healthbar - % and text colours (#4156) 2023-06-21 11:13:19 -07:00
Ciaran McGhie 180de8cf25 fix(ui-toolkit): healthbar fee decimals (#4152) 2023-06-21 14:51:17 +01:00
m.ray df88e77cdf fix(trading): liquidity provision table quantum formatting (#4114) 2023-06-21 11:23:07 +00:00
Mikołaj Młodzikowski 9441aee8cf feat(ci): update process tested on fairground 2023-06-21 13:05:42 +02:00
Bartłomiej Głownia 2353812834 fix(fills): fix mapping fill update (#4144) 2023-06-21 10:09:08 +01:00
Mikołaj Młodzikowski bba2b3c177 feat(ci): deploy fairground through interstitial page (#4151) 2023-06-21 11:00:27 +02:00
Joe Tsang ae57bd92f4 chore(governance): add e2e test for voting with different pub keys (#4147) 2023-06-21 09:42:36 +01:00
daro-maj 115b642140 test(trading): cover post and reduce only ACs (#4149) 2023-06-21 10:05:31 +02:00
Sam Keen ce3da97a8a fix(governance): proposal deadline validation missing (#4150) 2023-06-20 23:55:45 +01:00
Sam Keen cabd99d3ef feat(governance): new market details improvements (#4106) 2023-06-20 23:53:53 +01:00
Bartłomiej Głownia 597e07608f feat(positions): add liquidation to positions view (#4115) 2023-06-20 21:49:52 +00:00
Matthew Russell e451dc54b3 chore(trading): disable flakey network switcher test 2023-06-20 13:51:14 -07:00
Matthew Russell 9703c3b7a6 fix(trading): deposit table loading state (#4143) 2023-06-20 13:50:10 -07:00
Mikołaj Młodzikowski c22b6f3ce9 feat(ci): add missing fairground record to switch 2023-06-20 17:33:39 +02:00
559 changed files with 15556 additions and 13225 deletions
+1
View File
@@ -0,0 +1 @@
node_modules
+11 -6
View File
@@ -1,7 +1,7 @@
{ {
"root": true, "root": true,
"ignorePatterns": ["**/*"], "ignorePatterns": ["**/*"],
"plugins": ["@nrwl/nx", "eslint-plugin-unicorn", "jsx-a11y", "jest"], "plugins": ["@nx", "eslint-plugin-unicorn", "jsx-a11y", "jest"],
"settings": { "settings": {
"jsx-a11y": { "jsx-a11y": {
"components": { "components": {
@@ -18,7 +18,7 @@
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"], "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"extends": ["plugin:jsx-a11y/strict"], "extends": ["plugin:jsx-a11y/strict"],
"rules": { "rules": {
"@nrwl/nx/enforce-module-boundaries": [ "@nx/enforce-module-boundaries": [
"error", "error",
{ {
"enforceBuildableLibDependency": true, "enforceBuildableLibDependency": true,
@@ -56,7 +56,7 @@
}, },
{ {
"files": ["*.ts", "*.tsx"], "files": ["*.ts", "*.tsx"],
"extends": ["plugin:@nrwl/nx/typescript"], "extends": ["plugin:@nx/typescript"],
"rules": { "rules": {
"@typescript-eslint/ban-ts-comment": [ "@typescript-eslint/ban-ts-comment": [
"error", "error",
@@ -80,14 +80,19 @@
}, },
{ {
"files": ["*.spec.ts", "*.spec.tsx"], "files": ["*.spec.ts", "*.spec.tsx"],
"extends": ["plugin:@nrwl/nx/typescript", "plugin:jest/recommended"], "extends": ["plugin:@nx/typescript", "plugin:jest/recommended"],
"rules": { "rules": {
"jest/consistent-test-it": ["error", { "fn": "it" }] "jest/consistent-test-it": [
"error",
{
"fn": "it"
}
]
} }
}, },
{ {
"files": ["*.js", "*.jsx"], "files": ["*.js", "*.jsx"],
"extends": ["plugin:@nrwl/nx/javascript"], "extends": ["plugin:@nx/javascript"],
"rules": {} "rules": {}
} }
] ]
+45 -23
View File
@@ -6,7 +6,9 @@ on:
- release/* - release/*
- develop - develop
- main - main
pull_request: # uncomment pull_request and comment pull_request_target to test CI changes against feature branch not target branch (develop)
# pull_request:
pull_request_target:
types: types:
- opened - opened
- ready_for_review - ready_for_review
@@ -20,6 +22,8 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Cache node modules - name: Cache node modules
id: cache id: cache
@@ -45,7 +49,7 @@ jobs:
lint-pr-title: lint-pr-title:
needs: node-modules needs: node-modules
if: ${{ github.event_name == 'pull_request' }} if: ${{ github.event_name == 'pull_request' || github.event_name == 'pull_request_target' }}
name: Verify PR title name: Verify PR title
uses: ./.github/workflows/lint-pr.yml uses: ./.github/workflows/lint-pr.yml
secrets: inherit secrets: inherit
@@ -60,6 +64,7 @@ jobs:
uses: actions/checkout@v3 uses: actions/checkout@v3
with: with:
fetch-depth: 0 fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Setup node - name: Setup node
uses: actions/setup-node@v3 uses: actions/setup-node@v3
@@ -107,65 +112,82 @@ jobs:
echo "Branch slug: ${branch_slug}" echo "Branch slug: ${branch_slug}"
echo ">>>> eof debug" echo ">>>> eof debug"
projects_e2e="" projects_array=()
preview_governance="not deployed" preview_governance="not deployed"
preview_trading="not deployed" preview_trading="not deployed"
preview_explorer="not deployed" preview_explorer="not deployed"
preview_tools="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 if echo "$affected" | grep -q governance; then
echo "Governance is affected" echo "Governance is affected"
projects_e2e+='"governance-e2e" ' projects_array+=("governance")
preview_governance=$(printf "https://%s.%s.vega.rocks" "governance" "$branch_slug") preview_governance=$(printf "https://%s.%s.vega.rocks" "governance" "$branch_slug")
fi fi
if echo "$affected" | grep -q trading; then if echo "$affected" | grep -q trading; then
echo "Trading is affected" echo "Trading is affected"
projects_e2e+='"trading-e2e" ' projects_array+=("trading")
preview_trading=$(printf "https://%s.%s.vega.rocks" "trading" "$branch_slug") preview_trading=$(printf "https://%s.%s.vega.rocks" "trading" "$branch_slug")
fi fi
if echo "$affected" | grep -q explorer; then if echo "$affected" | grep -q explorer; then
echo "Explorer is affected" echo "Explorer is affected"
projects_e2e+='"explorer-e2e" ' projects_array+=("explorer")
preview_explorer=$(printf "https://%s.%s.vega.rocks" "explorer" "$branch_slug") preview_explorer=$(printf "https://%s.%s.vega.rocks" "explorer" "$branch_slug")
fi fi
if [[ -z "$projects_e2e" ]]; then if [[ ${#projects_array[@]} -eq 0 ]]; then
projects_e2e+='"governance-e2e" "trading-e2e" "explorer-e2e" ' projects_array=("governance" "trading" "explorer")
preview_governance=$(printf "https://%s.%s.vega.rocks" "governance" "$branch_slug") preview_governance=$(printf "https://%s.%s.vega.rocks" "governance" "$branch_slug")
preview_trading=$(printf "https://%s.%s.vega.rocks" "trading" "$branch_slug") preview_trading=$(printf "https://%s.%s.vega.rocks" "trading" "$branch_slug")
preview_explorer=$(printf "https://%s.%s.vega.rocks" "explorer" "$branch_slug") preview_explorer=$(printf "https://%s.%s.vega.rocks" "explorer" "$branch_slug")
fi fi
projects="$(echo $projects_e2e | sed 's|-e2e||g')"
# 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
if [[ "${{ github.event_name }}" = "pull_request" ]]; then if [[ "${{ github.event_name }}" = "pull_request" ]]; then
if echo "$affected" | grep -q multisig-signer; then if echo "$affected" | grep -q multisig-signer; then
echo "Tools are affected" echo "Tools are affected"
# tools are only applicable to check previews or deploy from develop to mainnet
echo "Deploying tools on preview" echo "Deploying tools on preview"
preview_tools=$(printf "https://%s.%s.vega.rocks" "tools" "$branch_slug") preview_tools=$(printf "https://%s.%s.vega.rocks" "tools" "$branch_slug")
projects+=' "multisig-signer" '
projects_array+=("multisig-signer")
fi fi
# those apps deploy only from develop to mainnet
elif [[ "${{ github.ref }}" =~ .*develop$ ]]; then elif [[ "${{ github.ref }}" =~ .*develop$ ]]; then
if echo "$affected" | grep -q multisig-signer; then if echo "$affected" | grep -q multisig-signer; then
echo "Tools are affected" echo "Tools are affected"
# tools are only applicable to check previews or deploy from develop to mainnet
echo "Deploying tools on s3" echo "Deploying tools on s3"
projects+=' "multisig-signer" '
projects_array+=("multisig-signer")
fi fi
if echo "$affected" | grep -q static; then if echo "$affected" | grep -q static; then
echo "static is affected" echo "static is affected"
echo "Deploying static on s3" echo "Deploying static on s3"
projects+=' "static" '
projects_array+=("static")
fi fi
if echo "$affected" | grep -q ui-toolkit; then if echo "$affected" | grep -q ui-toolkit; then
echo "ui-toolkit is affected" echo "ui-toolkit is affected"
echo "Deploying ui-toolkit on s3" echo "Deploying ui-toolkit on s3"
projects+=' "ui-toolkit" '
projects_array+=("ui-toolkit")
fi fi
fi fi
projects_e2e=${projects_e2e%?} echo "Projects: ${projects_array[@]}"
projects_e2e=[${projects_e2e// /,}] echo "Projects E2E: ${projects_e2e_array[@]}"
projects=[${projects// /,}] projects_json=$(jq -M --compact-output --null-input '$ARGS.positional' --args -- "${projects_array[@]}")
echo PROJECTS_E2E=$projects_e2e >> $GITHUB_ENV projects_e2e_json=$(jq -M --compact-output --null-input '$ARGS.positional' --args -- "${projects_e2e_array[@]}")
echo PROJECTS=$projects >> $GITHUB_ENV
echo PROJECTS_E2E=$projects_e2e_json >> $GITHUB_ENV
echo PROJECTS=$projects_json >> $GITHUB_ENV
echo PREVIEW_GOVERNANCE=$preview_governance >> $GITHUB_ENV echo PREVIEW_GOVERNANCE=$preview_governance >> $GITHUB_ENV
echo PREVIEW_TRADING=$preview_trading >> $GITHUB_ENV echo PREVIEW_TRADING=$preview_trading >> $GITHUB_ENV
echo PREVIEW_EXPLORER=$preview_explorer >> $GITHUB_ENV echo PREVIEW_EXPLORER=$preview_explorer >> $GITHUB_ENV
@@ -182,7 +204,7 @@ jobs:
cypress: cypress:
needs: lint-test-build needs: lint-test-build
name: '(CI) cypress' 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 uses: ./.github/workflows/cypress-run.yml
secrets: inherit secrets: inherit
with: with:
@@ -192,7 +214,7 @@ jobs:
publish-dist: publish-dist:
needs: lint-test-build needs: lint-test-build
name: '(CD) publish dist' name: '(CD) publish dist'
if: ${{ needs.lint-test-build.outputs.projects != '[]' }} # if: ${{ needs.lint-test-build.outputs.projects != '[]' }}
uses: ./.github/workflows/publish-dist.yml uses: ./.github/workflows/publish-dist.yml
secrets: inherit secrets: inherit
with: with:
@@ -203,7 +225,7 @@ jobs:
needs: needs:
- publish-dist - publish-dist
- lint-test-build - lint-test-build
if: ${{ github.event_name == 'pull_request' }} if: ${{ github.event_name == 'pull_request' || github.event_name == 'pull_request_target' }}
timeout-minutes: 60 timeout-minutes: 60
name: '(CD) comment preview links' name: '(CD) comment preview links'
steps: steps:
+1
View File
@@ -33,6 +33,7 @@ jobs:
with: with:
fetch-depth: 0 fetch-depth: 0
path: './frontend-monorepo' path: './frontend-monorepo'
ref: ${{ github.event.pull_request.head.sha || github.sha }}
# Restore node_modules from cache if possible # Restore node_modules from cache if possible
- name: Restore node_modules from cache - name: Restore node_modules from cache
+2
View File
@@ -11,6 +11,8 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Setup node - name: Setup node
uses: actions/setup-node@v3 uses: actions/setup-node@v3
+44 -61
View File
@@ -19,6 +19,8 @@ jobs:
steps: steps:
- name: Check out code - name: Check out code
uses: actions/checkout@v3 uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Set up QEMU - name: Set up QEMU
id: quemu id: quemu
@@ -31,7 +33,7 @@ jobs:
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v2
- name: Log in to the Container registry (ghcr) - name: Log in to the Container registry (ghcr)
if: ${{ github.event_name == 'pull_request' }} if: ${{ github.event_name == 'pull_request' || github.event_name == 'pull_request_target' }}
uses: docker/login-action@v2 uses: docker/login-action@v2
with: with:
registry: ghcr.io registry: ghcr.io
@@ -111,19 +113,19 @@ jobs:
- name: Build local dist - name: Build local dist
run: | run: |
flags="" envCmd=""
if [[ ! -z "${{ env.ENV_NAME }}" ]]; then if [[ ! -z "${{ env.ENV_NAME }}" ]]; then
flags="--env=${{ env.ENV_NAME }}" envCmd="yarn env-cmd -f ./apps/${{ matrix.app }}/.env.${{ env.ENV_NAME }}"
fi fi
if [ "${{ matrix.app }}" = "trading" ]; then if [ "${{ matrix.app }}" = "trading" ]; then
yarn nx export trading $flags || (yarn install && yarn nx export trading $flags) $envCmd yarn nx export trading || (yarn install && $envCmd yarn nx export trading)
DIST_LOCATION=dist/apps/trading/exported DIST_LOCATION=dist/apps/trading/exported
elif [ "${{ matrix.app }}" = "ui-toolkit" ]; then elif [ "${{ matrix.app }}" = "ui-toolkit" ]; then
NODE_ENV=production yarn nx run ui-toolkit:build-storybook NODE_ENV=production yarn nx run ui-toolkit:build-storybook
DIST_LOCATION=dist/storybook/ui-toolkit DIST_LOCATION=dist/storybook/ui-toolkit
else else
yarn nx build ${{ matrix.app }} $flags || (yarn install && yarn nx build ${{ matrix.app }} $flags) $envCmd yarn nx build ${{ matrix.app }} || (yarn install && $envCmd yarn nx build ${{ matrix.app }})
DIST_LOCATION=dist/apps/${{ matrix.app }} DIST_LOCATION=dist/apps/${{ matrix.app }}
fi fi
mv $DIST_LOCATION dist-result mv $DIST_LOCATION dist-result
@@ -143,7 +145,7 @@ jobs:
ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local
- name: Image digest - name: Image digest
if: ${{ github.event_name == 'pull_request' }} if: ${{ github.event_name == 'pull_request' || github.event_name == 'pull_request_target' }}
run: echo ${{ steps.docker_build.outputs.digest }} run: echo ${{ steps.docker_build.outputs.digest }}
- name: Sanity check docker image - name: Sanity check docker image
@@ -158,7 +160,7 @@ jobs:
uses: docker/build-push-action@v3 uses: docker/build-push-action@v3
continue-on-error: true continue-on-error: true
id: ghcr-push id: ghcr-push
if: ${{ github.event_name == 'pull_request' }} if: ${{ github.event_name == 'pull_request' || github.event_name == 'pull_request_target' }}
with: with:
context: . context: .
file: docker/node-outside-docker.Dockerfile file: docker/node-outside-docker.Dockerfile
@@ -226,9 +228,25 @@ jobs:
AWS_REGION: 'eu-west-1' AWS_REGION: 'eu-west-1'
SOURCE_DIR: 'dist-result' SOURCE_DIR: 'dist-result'
- name: Install aws CLI
if: ${{ github.event_name == 'push' && ( matrix.app != 'trading' || (matrix.app == 'trading' && !( endsWith(github.ref, 'main') || endsWith(github.ref, 'release/testnet') ) ) ) }}
uses: unfor19/install-aws-cli-action@master
- name: Perform cache invalidation
if: ${{ github.event_name == 'push' && ( matrix.app != 'trading' || (matrix.app == 'trading' && !( endsWith(github.ref, 'main') || endsWith(github.ref, 'release/testnet') ) ) ) }}
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_REGION: 'eu-west-1'
run: |
echo "Looking for distribution for bucket: ${{ env.BUCKET_NAME }}"
id=$(aws cloudfront list-distributions | jq -Mrc '.DistributionList.Items | .[] | select(.DefaultCacheBehavior.TargetOriginId == "${{ env.BUCKET_NAME }}") | .Id')
echo "Found id is: ${id}"
aws cloudfront create-invalidation --distribution-id $id --paths "/*"
- name: Add preview label - name: Add preview label
uses: actions-ecosystem/action-add-labels@v1 uses: actions-ecosystem/action-add-labels@v1
if: ${{ github.event_name == 'pull_request' }} if: ${{ github.event_name == 'pull_request' || github.event_name == 'pull_request_target' }}
with: with:
labels: ${{ matrix.app }}-preview labels: ${{ matrix.app }}-preview
number: ${{ github.event.number }} number: ${{ github.event.number }}
@@ -265,7 +283,7 @@ jobs:
fi fi
- name: Check out ipfs-redirect - name: Check out ipfs-redirect
if: ${{ matrix.app == 'trading' && github.event_name == 'push' && endsWith(github.ref, 'main') }} if: ${{ matrix.app == 'trading' && github.event_name == 'push' && ( endsWith(github.ref, 'main') || endsWith(github.ref, 'testnet') ) }}
uses: actions/checkout@v3 uses: actions/checkout@v3
with: with:
repository: 'vegaprotocol/ipfs-redirect' repository: 'vegaprotocol/ipfs-redirect'
@@ -273,11 +291,12 @@ jobs:
fetch-depth: '0' fetch-depth: '0'
token: ${{ secrets.VEGA_CI_BOT_GITHUB_TOKEN }} token: ${{ secrets.VEGA_CI_BOT_GITHUB_TOKEN }}
- name: Update console.vega.xyz DNS to redirect to the new console - name: Update interstitial page to point to the new console
if: ${{ matrix.app == 'trading' && github.event_name == 'push' && endsWith(github.ref, 'main') }} if: ${{ matrix.app == 'trading' && github.event_name == 'push' && ( endsWith(github.ref, 'main') || endsWith(github.ref, 'testnet') ) }}
env: env:
GH_TOKEN: ${{ secrets.VEGA_CI_BOT_GITHUB_TOKEN }} GH_TOKEN: ${{ secrets.VEGA_CI_BOT_GITHUB_TOKEN }}
run: | run: |
# set CID
curl -L https://dist.ipfs.tech/kubo/v0.20.0/kubo_v0.20.0_linux-amd64.tar.gz -o kubo.tgz curl -L https://dist.ipfs.tech/kubo/v0.20.0/kubo_v0.20.0_linux-amd64.tar.gz -o kubo.tgz
tar -xzf kubo.tgz tar -xzf kubo.tgz
export PATH="$PATH:$PWD/kubo" export PATH="$PATH:$PWD/kubo"
@@ -285,64 +304,28 @@ jobs:
new_hash=$(cat ${{ matrix.app }}-ipfs-hash) new_hash=$(cat ${{ matrix.app }}-ipfs-hash)
new_cid=$(ipfs cid format -v 1 -b base32 $new_hash) new_cid=$(ipfs cid format -v 1 -b base32 $new_hash)
ls -al ipfs-redirect
echo $new_hash > ipfs-redirect/cidv0.txt
echo $new_cid > ipfs-redirect/cidv1.txt
( (
cd ipfs-redirect cd ipfs-redirect
# configure git
git status git status
cat .git/config cat .git/config
git config --global user.email "vega-ci-bot@vega.xyz" git config --global user.email "vega-ci-bot@vega.xyz"
git config --global user.name "vega-ci-bot" git config --global user.name "vega-ci-bot"
branch_name="update-hash-${{ github.ref }}" # update CID files
git checkout -b "$branch_name" if echo ${{ github.ref }} | grep -q main; then
echo $new_hash > cidv0-mainnet.txt
echo $new_cid > cidv1-mainnet.txt
git add cidv0-mainnet.txt cidv1-mainnet.txt
elif echo ${{ github.ref }} | grep -q release/testnet; then
echo $new_hash > cidv0-fairground.txt
echo $new_cid > cidv1-fairground.txt
git add cidv0-fairground.txt cidv1-fairground.txt
fi
# create commit
commit_msg="Automated hash update from ${{ github.ref }}" commit_msg="Automated hash update from ${{ github.ref }}"
git add cidv0.txt cidv1.txt
git commit -m "$commit_msg" git commit -m "$commit_msg"
git push -u origin "$branch_name" git push -u origin "main"
pr_url="$(gh pr create --title "${commit_msg}" --body 'automated pull request to update CIDs')"
echo $pr_url
# once auto merge get's enabled on documentation repo let's do follow up
sleep 5
gh pr merge "${pr_url}" --delete-branch --squash --admin
) )
- name: Update console.fairground.wtf DNS to redirect to the new console
if: ${{ matrix.app == 'trading' && github.event_name == 'push' && endsWith(github.ref, 'testnet') }}
run: |
curl -L https://dist.ipfs.tech/kubo/v0.20.0/kubo_v0.20.0_linux-amd64.tar.gz -o kubo.tgz
tar -xzf kubo.tgz
export PATH="$PATH:$PWD/kubo"
which ipfs
new_hash=$(cat ${{ matrix.app }}-ipfs-hash)
new_cid=$(ipfs cid format -v 1 -b base32 $new_hash)
# Generate console URL
new_console_url_type=ipfs
# new_console_url_type=ipns
new_console_url_domain=cf-ipfs.com
# new_console_url_domain=dweb.link
new_console_url="http://${new_cid}.${new_console_url_type}.${new_console_url_domain}/"
echo "new_console_url=${new_console_url}"
# Update record in DNSimple
# docs: https://developer.dnsimple.com/v2/zones/records/#updateZoneRecord
dnsimple_account_id=84895
dnsimple_zone_name=fairground.wtf
dnsimple_record_id=45300615
# see: https://dnsimple.com/a/84895/domains/fairground.wtf/records/45300615/edit
curl --fail -H 'Authorization: Bearer ${{ secrets.DNSIMPLE_API_TOKEN }}' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-X PATCH \
-d "{
\"content\": \"${new_console_url}\"
}" \
https://api.dnsimple.com/v2/${dnsimple_account_id}/zones/${dnsimple_zone_name}/records/${dnsimple_record_id}
-29
View File
@@ -1,29 +0,0 @@
module.exports = {
stories: [],
addons: [
'@storybook/addon-actions',
'@storybook/addon-viewport',
{
name: '@storybook/addon-docs',
options: {
configureJSX: true,
babelOptions: {},
sourceLoaderOptions: null,
transcludeMarkdown: true,
},
},
'@storybook/addon-controls',
'@storybook/addon-backgrounds',
'@storybook/addon-toolbars',
'@storybook/addon-measure',
'@storybook/addon-outline',
'@storybook/addon-a11y',
],
// uncomment the property below if you want to apply some webpack config globally
// webpackFinal: async (config, { configType }) => {
// // Make whatever fine-grained changes you need that should apply to all storybook configs
// // Return the altered config
// return config;
// },
};
-14
View File
@@ -1,14 +0,0 @@
{
"extends": "../tsconfig.base.json",
"exclude": [
"../**/*.spec.js",
"../**/*.test.js",
"../**/*.spec.ts",
"../**/*.test.ts",
"../**/*.spec.tsx",
"../**/*.test.tsx",
"../**/*.spec.jsx",
"../**/*.test.jsx"
],
"include": ["../**/*"]
}
+4 -3
View File
@@ -1,10 +1,11 @@
{ {
"name": "explorer-e2e",
"$schema": "../../node_modules/nx/schemas/project-schema.json", "$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/explorer-e2e/src", "sourceRoot": "apps/explorer-e2e/src",
"projectType": "application", "projectType": "application",
"targets": { "targets": {
"e2e": { "e2e": {
"executor": "@nrwl/cypress:cypress", "executor": "@nx/cypress:cypress",
"options": { "options": {
"cypressConfig": "apps/explorer-e2e/cypress.config.js", "cypressConfig": "apps/explorer-e2e/cypress.config.js",
"devServerTarget": "explorer:serve" "devServerTarget": "explorer:serve"
@@ -16,14 +17,14 @@
} }
}, },
"lint": { "lint": {
"executor": "@nrwl/linter:eslint", "executor": "@nx/linter:eslint",
"outputs": ["{options.outputFile}"], "outputs": ["{options.outputFile}"],
"options": { "options": {
"lintFilePatterns": ["apps/explorer-e2e/**/*.{js,ts}"] "lintFilePatterns": ["apps/explorer-e2e/**/*.{js,ts}"]
} }
}, },
"build": { "build": {
"executor": "@nrwl/workspace:run-commands", "executor": "nx:run-commands",
"outputs": [], "outputs": [],
"options": { "options": {
"command": "yarn tsc --project ./apps/explorer-e2e/" "command": "yarn tsc --project ./apps/explorer-e2e/"
@@ -27,6 +27,11 @@ context('Home Page', function () {
16: 'Chain ID', 16: 'Chain ID',
}; };
/**
* TODO(@nx/cypress): Nesting Cypress commands in a should assertion now throws.
* You should use .then() to chain commands instead.
* More Info: https://docs.cypress.io/guides/references/migration-guide#-should
**/
cy.get('[data-testid="stats-title"]') cy.get('[data-testid="stats-title"]')
.each(($list, index) => { .each(($list, index) => {
cy.wrap($list).should('contain.text', statTitles[index]); cy.wrap($list).should('contain.text', statTitles[index]);
@@ -34,6 +34,11 @@ context('Network parameters page', { tags: '@smoke' }, function () {
const parameterName = network_parameter[0]; const parameterName = network_parameter[0];
const parameterValue = network_parameter[1]; const parameterValue = network_parameter[1];
if (this.networkParameterFormat.json.includes(parameterName)) { if (this.networkParameterFormat.json.includes(parameterName)) {
/**
* TODO(@nx/cypress): Nesting Cypress commands in a should assertion now throws.
* You should use .then() to chain commands instead.
* More Info: https://docs.cypress.io/guides/references/migration-guide#-should
**/
cy.get(tableRows) cy.get(tableRows)
.contains(parameterName) .contains(parameterName)
.should('be.visible') .should('be.visible')
@@ -65,6 +70,11 @@ context('Network parameters page', { tags: '@smoke' }, function () {
if (this.networkParameterFormat.percentage.includes(parameterName)) { if (this.networkParameterFormat.percentage.includes(parameterName)) {
const formattedPercentageParameter = const formattedPercentageParameter =
(parseFloat(parameterValue) * 100).toFixed(0) + '%'; (parseFloat(parameterValue) * 100).toFixed(0) + '%';
/**
* TODO(@nx/cypress): Nesting Cypress commands in a should assertion now throws.
* You should use .then() to chain commands instead.
* More Info: https://docs.cypress.io/guides/references/migration-guide#-should
**/
cy.get(tableRows) cy.get(tableRows)
.contains(parameterName) .contains(parameterName)
.should('be.visible') .should('be.visible')
@@ -148,6 +158,11 @@ context('Network parameters page', { tags: '@smoke' }, function () {
cy.convert_number_to_max_four_decimal(parameterValue) cy.convert_number_to_max_four_decimal(parameterValue)
.add_commas_to_number_if_large_enough() .add_commas_to_number_if_large_enough()
.then((parameterValueFormatted) => { .then((parameterValueFormatted) => {
/**
* TODO(@nx/cypress): Nesting Cypress commands in a should assertion now throws.
* You should use .then() to chain commands instead.
* More Info: https://docs.cypress.io/guides/references/migration-guide#-should
**/
cy.get(tableRows) cy.get(tableRows)
.contains(parameterName) .contains(parameterName)
.should('be.visible') .should('be.visible')
@@ -179,6 +194,11 @@ context('Network parameters page', { tags: '@smoke' }, function () {
cy.convert_number_to_max_eighteen_decimal(parameterValue) cy.convert_number_to_max_eighteen_decimal(parameterValue)
.add_commas_to_number_if_large_enough() .add_commas_to_number_if_large_enough()
.then((parameterValueFormatted) => { .then((parameterValueFormatted) => {
/**
* TODO(@nx/cypress): Nesting Cypress commands in a should assertion now throws.
* You should use .then() to chain commands instead.
* More Info: https://docs.cypress.io/guides/references/migration-guide#-should
**/
cy.get(tableRows) cy.get(tableRows)
.contains(parameterName) .contains(parameterName)
.should('be.visible') .should('be.visible')
@@ -170,6 +170,11 @@ context.skip('Parties page', { tags: '@regression' }, function () {
const sideMenuBackground = '.absolute'; const sideMenuBackground = '.absolute';
// Engage dark mode if not allready set // Engage dark mode if not allready set
/**
* TODO(@nx/cypress): Nesting Cypress commands in a should assertion now throws.
* You should use .then() to chain commands instead.
* More Info: https://docs.cypress.io/guides/references/migration-guide#-should
**/
cy.get(sideMenuBackground) cy.get(sideMenuBackground)
.should('have.css', 'background-color') .should('have.css', 'background-color')
.then((background_color) => { .then((background_color) => {
@@ -60,16 +60,31 @@ context.skip('Transactions page', function () {
}); });
cy.get('block').should('not.be.empty'); cy.get('block').should('not.be.empty');
cy.get('encoded-tnx').should('not.be.empty'); cy.get('encoded-tnx').should('not.be.empty');
/**
* TODO(@nx/cypress): Nesting Cypress commands in a should assertion now throws.
* You should use .then() to chain commands instead.
* More Info: https://docs.cypress.io/guides/references/migration-guide#-should
**/
cy.get('tx-type') cy.get('tx-type')
.should('not.be.empty') .should('not.be.empty')
.invoke('text') .invoke('text')
.then((txTypeTxt) => { .then((txTypeTxt) => {
if (txTypeTxt == 'Order Submission') { if (txTypeTxt == 'Order Submission') {
/**
* TODO(@nx/cypress): Nesting Cypress commands in a should assertion now throws.
* You should use .then() to chain commands instead.
* More Info: https://docs.cypress.io/guides/references/migration-guide#-should
**/
cy.get('.hljs-attr') cy.get('.hljs-attr')
.should('have.length.at.least', 8) .should('have.length.at.least', 8)
.each(($propertyName) => { .each(($propertyName) => {
cy.wrap($propertyName).should('not.be.empty'); cy.wrap($propertyName).should('not.be.empty');
}); });
/**
* TODO(@nx/cypress): Nesting Cypress commands in a should assertion now throws.
* You should use .then() to chain commands instead.
* More Info: https://docs.cypress.io/guides/references/migration-guide#-should
**/
cy.get('.hljs-string') cy.get('.hljs-string')
.should('have.length.at.least', 8) .should('have.length.at.least', 8)
.each(($propertyValue) => { .each(($propertyValue) => {
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"presets": [ "presets": [
[ [
"@nrwl/react/babel", "@nx/react/babel",
{ {
"runtime": "automatic" "runtime": "automatic"
} }
+3 -3
View File
@@ -1,14 +1,14 @@
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8 NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.rocks
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/stagnet1/vegawallet-stagnet1.toml NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/stagnet1/vegawallet-stagnet1.toml
NX_VEGA_ENV=STAGNET1 NX_VEGA_ENV=STAGNET1
NX_VEGA_EXPLORER_URL=https://explorer.stagnet1.vega.rocks NX_VEGA_EXPLORER_URL=https://explorer.stagnet1.vega.rocks
NX_VEGA_TOKEN_URL=https://governance.stagnet1.vega.rocks NX_VEGA_TOKEN_URL=https://governance.stagnet1.vega.rocks
NX_VEGA_WALLET_URL=http://localhost:1789 NX_VEGA_WALLET_URL=http://localhost:1789
NX_TENDERMINT_URL=https://tm.n01.stagnet1.vega.xyz NX_TENDERMINT_URL=https://tm.n01.stagnet1.vega.rocks
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.stagnet1.vega.xyz/websocket NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.stagnet1.vega.xyz/websocket
NX_BLOCK_EXPLORER=https://be.stagnet1.vega.xyz/rest NX_BLOCK_EXPLORER=https://be.stagnet1.vega.rocks/rest
NX_ETHERSCAN_URL=https://sepolia.etherscan.io NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
NX_VEGA_GOVERNANCE_URL=https://governance.stagnet1.vega.rocks NX_VEGA_GOVERNANCE_URL=https://governance.stagnet1.vega.rocks
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"], "extends": ["plugin:@nx/react", "../../.eslintrc.json"],
"ignorePatterns": ["!**/*", "__generated__", "__generated___"], "ignorePatterns": ["!**/*", "__generated__"],
"overrides": [ "overrides": [
{ {
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"], "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
+1 -1
View File
@@ -39,7 +39,7 @@ Example configurations are provided here:
For convenience, you can boot the app injecting one of the configurations above by running: For convenience, you can boot the app injecting one of the configurations above by running:
```bash ```bash
yarn nx run explorer:serve --env={env} # e.g. stagnet1 yarn env-cmd -f .\apps\explorer\.env.{env} yarn nx run explorer:serve # e.g. stagnet1
``` ```
There are a few different configuration options offered for this app: There are a few different configuration options offered for this app:
+2 -2
View File
@@ -4,8 +4,8 @@ export default {
displayName: 'explorer', displayName: 'explorer',
preset: '../../jest.preset.js', preset: '../../jest.preset.js',
transform: { transform: {
'^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nrwl/react/plugins/jest', '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nx/react/plugins/jest',
'^.+\\.[tj]sx?$': 'babel-jest', '^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nx/react/babel'] }],
}, },
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
coverageDirectory: '../../coverage/apps/explorer', coverageDirectory: '../../coverage/apps/explorer',
+15 -8
View File
@@ -1,10 +1,11 @@
{ {
"name": "explorer",
"$schema": "../../node_modules/nx/schemas/project-schema.json", "$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/explorer/src", "sourceRoot": "apps/explorer/src",
"projectType": "application", "projectType": "application",
"targets": { "targets": {
"build": { "build": {
"executor": "./tools/executors/webpack:build", "executor": "@nx/webpack:webpack",
"outputs": ["{options.outputPath}"], "outputs": ["{options.outputPath}"],
"defaultConfiguration": "production", "defaultConfiguration": "production",
"options": { "options": {
@@ -38,7 +39,7 @@
} }
}, },
"serve": { "serve": {
"executor": "./tools/executors/webpack:serve", "executor": "@nx/webpack:dev-server",
"options": { "options": {
"port": 3000, "port": 3000,
"buildTarget": "explorer:build:development", "buildTarget": "explorer:build:development",
@@ -52,22 +53,28 @@
} }
}, },
"lint": { "lint": {
"executor": "@nrwl/linter:eslint", "executor": "@nx/linter:eslint",
"outputs": ["{options.outputFile}"], "outputs": ["{options.outputFile}"],
"options": { "options": {
"lintFilePatterns": ["apps/explorer/**/*.{ts,tsx,js,jsx}"] "lintFilePatterns": ["apps/explorer/**/*.{ts,tsx,js,jsx}"]
} }
}, },
"test": { "test": {
"executor": "@nrwl/jest:jest", "executor": "@nx/jest:jest",
"outputs": ["coverage/apps/explorer"], "outputs": ["{workspaceRoot}/coverage/apps/explorer"],
"options": { "options": {
"jestConfig": "apps/explorer/jest.config.ts", "jestConfig": "apps/explorer/jest.config.ts",
"passWithNoTests": true "passWithNoTests": true
},
"configurations": {
"ci": {
"ci": true,
"codeCoverage": true
}
} }
}, },
"generate-types": { "generate-types": {
"executor": "@nrwl/workspace:run-commands", "executor": "nx:run-commands",
"options": { "options": {
"commands": [ "commands": [
"npx openapi-typescript https://raw.githubusercontent.com/vegaprotocol/documentation/main/specs/v0.71.4/blockexplorer.openapi.json --output apps/explorer/src/types/explorer.d.ts --immutable-types" "npx openapi-typescript https://raw.githubusercontent.com/vegaprotocol/documentation/main/specs/v0.71.4/blockexplorer.openapi.json --output apps/explorer/src/types/explorer.d.ts --immutable-types"
@@ -75,7 +82,7 @@
} }
}, },
"build-netlify": { "build-netlify": {
"executor": "@nrwl/workspace:run-commands", "executor": "nx:run-commands",
"options": { "options": {
"commands": [ "commands": [
"cp apps/explorer/netlify.toml netlify.toml", "cp apps/explorer/netlify.toml netlify.toml",
@@ -84,7 +91,7 @@
} }
}, },
"build-spec": { "build-spec": {
"executor": "@nrwl/workspace:run-commands", "executor": "nx:run-commands",
"outputs": [], "outputs": [],
"options": { "options": {
"command": "yarn tsc --project ./apps/explorer/tsconfig.spec.json" "command": "yarn tsc --project ./apps/explorer/tsconfig.spec.json"
@@ -1,15 +1,15 @@
import { useMemo } from 'react';
import type { AssetFieldsFragment } from '@vegaprotocol/assets'; import type { AssetFieldsFragment } from '@vegaprotocol/assets';
import { AssetTypeMapping, AssetStatusMapping } from '@vegaprotocol/assets'; import { AssetTypeMapping, AssetStatusMapping } from '@vegaprotocol/assets';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { ButtonLink } from '@vegaprotocol/ui-toolkit'; import { ButtonLink } from '@vegaprotocol/ui-toolkit';
import type { AgGridReact } from 'ag-grid-react'; import type { AgGridReact } from 'ag-grid-react';
import { AgGridColumn } from 'ag-grid-react';
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid'; import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
import type { VegaICellRendererParams } from '@vegaprotocol/datagrid'; import type { VegaICellRendererParams } from '@vegaprotocol/datagrid';
import { useRef, useLayoutEffect } from 'react'; import { useRef, useLayoutEffect } from 'react';
import { BREAKPOINT_MD } from '../../config/breakpoints'; import { BREAKPOINT_MD } from '../../config/breakpoints';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import type { RowClickedEvent } from 'ag-grid-community'; import type { RowClickedEvent, ColDef } from 'ag-grid-community';
type AssetsTableProps = { type AssetsTableProps = {
data: AssetFieldsFragment[] | null; data: AssetFieldsFragment[] | null;
@@ -31,6 +31,58 @@ export const AssetsTable = ({ data }: AssetsTableProps) => {
}; };
}, []); }, []);
const columnDefs = useMemo<ColDef[]>(
() => [
{ headerName: t('Symbol'), field: 'symbol' },
{ headerName: t('Name'), field: 'name' },
{
flex: 2,
headerName: t('ID'),
field: 'id',
hide: window.innerWidth < BREAKPOINT_MD,
},
{
colId: 'type',
headerName: t('Type'),
field: 'source.__typename',
hide: window.innerWidth < BREAKPOINT_MD,
valueFormatter: ({ value }: { value?: string }) =>
value ? AssetTypeMapping[value].value : '',
},
{
headerName: t('Status'),
field: 'status',
hide: window.innerWidth < BREAKPOINT_MD,
valueFormatter: ({ value }: { value?: string }) =>
value ? AssetStatusMapping[value].value : '',
},
{
colId: 'actions',
headerName: '',
sortable: false,
filter: false,
resizable: false,
wrapText: true,
field: 'id',
cellRenderer: ({
value,
}: VegaICellRendererParams<AssetFieldsFragment, 'id'>) =>
value ? (
<ButtonLink
onClick={(e) => {
navigate(value);
}}
>
{t('View details')}
</ButtonLink>
) : (
''
),
},
],
[navigate]
);
return ( return (
<AgGrid <AgGrid
ref={ref} ref={ref}
@@ -46,60 +98,11 @@ export const AssetsTable = ({ data }: AssetsTableProps) => {
filterParams: { buttons: ['reset'] }, filterParams: { buttons: ['reset'] },
autoHeight: true, autoHeight: true,
}} }}
columnDefs={columnDefs}
suppressCellFocus={true} suppressCellFocus={true}
onRowClicked={({ data }: RowClickedEvent) => { onRowClicked={({ data }: RowClickedEvent) => {
navigate(data.id); navigate(data.id);
}} }}
> />
<AgGridColumn headerName={t('Symbol')} field="symbol" />
<AgGridColumn headerName={t('Name')} field="name" />
<AgGridColumn
flex="2"
headerName={t('ID')}
field="id"
hide={window.innerWidth < BREAKPOINT_MD}
/>
<AgGridColumn
colId="type"
headerName={t('Type')}
field="source.__typename"
hide={window.innerWidth < BREAKPOINT_MD}
valueFormatter={({ value }: { value?: string }) =>
value && AssetTypeMapping[value].value
}
/>
<AgGridColumn
headerName={t('Status')}
field="status"
hide={window.innerWidth < BREAKPOINT_MD}
valueFormatter={({ value }: { value?: string }) =>
value && AssetStatusMapping[value].value
}
/>
<AgGridColumn
colId="actions"
headerName=""
sortable={false}
filter={false}
resizable={false}
wrapText={true}
field="id"
cellRenderer={({
value,
}: VegaICellRendererParams<AssetFieldsFragment, 'id'>) =>
value ? (
<ButtonLink
onClick={(e) => {
navigate(value);
}}
>
{t('View details')}
</ButtonLink>
) : (
''
)
}
/>
</AgGrid>
); );
}; };
@@ -1,5 +1,4 @@
import { Icon, Tooltip } from '@vegaprotocol/ui-toolkit'; import { Icon, Tooltip } from '@vegaprotocol/ui-toolkit';
import React from 'react';
export interface InfoBlockProps { export interface InfoBlockProps {
title: string; title: string;
@@ -1,33 +1,98 @@
import { render } from '@testing-library/react'; import { render } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom'; import { MemoryRouter } from 'react-router-dom';
import PartyLink from './party-link'; import PartyLink from './party-link';
import { MockedProvider } from '@apollo/client/testing';
import { ExplorerNodeNamesDocument } from '../../../routes/validators/__generated__/NodeNames';
import { act } from 'react-dom/test-utils';
const zeroes =
'0000000000000000000000000000000000000000000000000000000000000000';
const mocks = [
{
request: {
query: ExplorerNodeNamesDocument,
},
result: {
data: {
nodesConnection: {
edges: [
{
node: {
id: '1',
name: 'Validator Node',
pubkey:
'13464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6e',
tmPubkey: 'tmPubkey1',
ethereumAddress: '0x123456789',
},
},
{
node: {
id: '2',
name: 'Node 2',
pubkey: 'pubkey2',
tmPubkey: 'tmPubkey2',
ethereumAddress: '0xabcdef123',
},
},
],
},
},
},
},
];
describe('PartyLink', () => { describe('PartyLink', () => {
it('renders Network for 000.000 party', () => { it('renders Network for 000.000 party', () => {
const zeroes = const screen = render(
'0000000000000000000000000000000000000000000000000000000000000000'; <MockedProvider>
const screen = render(<PartyLink id={zeroes} />); <PartyLink id={zeroes} />
</MockedProvider>
);
expect(screen.getByText('Network')).toBeInTheDocument(); expect(screen.getByText('Network')).toBeInTheDocument();
}); });
it('renders Network for network party', () => { it('renders Network for network party', () => {
const screen = render(<PartyLink id="network" />); const screen = render(
<MockedProvider>
<PartyLink id="network" />
</MockedProvider>
);
expect(screen.getByText('Network')).toBeInTheDocument(); expect(screen.getByText('Network')).toBeInTheDocument();
}); });
it('renders ID with no link for invalid party', () => { it('renders ID with no link for invalid party', () => {
const screen = render(<PartyLink id="this-party-is-not-valid" />); const screen = render(
<MockedProvider>
<PartyLink id="this-party-is-not-valid" />
</MockedProvider>
);
expect(screen.getByTestId('invalid-party')).toBeInTheDocument(); expect(screen.getByTestId('invalid-party')).toBeInTheDocument();
}); });
it('if the key is a validator, render their name instead', async () => {
const screen = render(
<MockedProvider mocks={mocks}>
<MemoryRouter>
<PartyLink id="13464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6e" />
</MemoryRouter>
</MockedProvider>
);
// Wait for hook to update with mock data
await act(() => new Promise((resolve) => setTimeout(resolve, 0)));
await expect(screen.getByText('Validator Node')).toBeInTheDocument();
});
it('links a valid party to the party page', () => { it('links a valid party to the party page', () => {
const aValidParty = const aValidParty =
'13464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6e'; '13464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6e';
const screen = render( const screen = render(
<MemoryRouter> <MockedProvider>
<PartyLink id={aValidParty} /> <MemoryRouter>
</MemoryRouter> <PartyLink id={aValidParty} />
</MemoryRouter>
</MockedProvider>
); );
const el = screen.getByText(aValidParty); const el = screen.getByText(aValidParty);
@@ -1,22 +1,44 @@
import { Routes } from '../../../routes/route-names'; import { Routes } from '../../../routes/route-names';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import type { ComponentProps } from 'react'; import { useMemo, type ComponentProps } from 'react';
import Hash from '../hash'; import Hash from '../hash';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { isValidPartyId } from '../../../routes/parties/id/components/party-id-error'; import { isValidPartyId } from '../../../routes/parties/id/components/party-id-error';
import { truncateMiddle } from '@vegaprotocol/ui-toolkit'; import { Icon, truncateMiddle } from '@vegaprotocol/ui-toolkit';
import { useExplorerNodeNamesQuery } from '../../../routes/validators/__generated__/NodeNames';
import type { ExplorerNodeNamesQuery } from '../../../routes/validators/__generated__/NodeNames';
export const SPECIAL_CASE_NETWORK_ID = export const SPECIAL_CASE_NETWORK_ID =
'0000000000000000000000000000000000000000000000000000000000000000'; '0000000000000000000000000000000000000000000000000000000000000000';
export const SPECIAL_CASE_NETWORK = 'network'; export const SPECIAL_CASE_NETWORK = 'network';
export function getNameForParty(id: string, data?: ExplorerNodeNamesQuery) {
if (!data || data?.nodesConnection?.edges?.length === 0) {
return id;
}
const validator = data.nodesConnection.edges?.find((e) => {
return e?.node.pubkey === id;
});
if (validator) {
return validator.node.name;
}
return id;
}
export type PartyLinkProps = Partial<ComponentProps<typeof Link>> & { export type PartyLinkProps = Partial<ComponentProps<typeof Link>> & {
id: string; id: string;
truncate?: boolean; truncate?: boolean;
}; };
const PartyLink = ({ id, truncate = false, ...props }: PartyLinkProps) => { const PartyLink = ({ id, truncate = false, ...props }: PartyLinkProps) => {
const { data } = useExplorerNodeNamesQuery();
const name = useMemo(() => getNameForParty(id, data), [data, id]);
const useName = name !== id;
// Some transactions will involve the 'network' party, which is alias for '000...000' // Some transactions will involve the 'network' party, which is alias for '000...000'
// The party page does not handle this nicely, so in this case we render the word 'Network' // The party page does not handle this nicely, so in this case we render the word 'Network'
if (id === SPECIAL_CASE_NETWORK || id === SPECIAL_CASE_NETWORK_ID) { if (id === SPECIAL_CASE_NETWORK || id === SPECIAL_CASE_NETWORK_ID) {
@@ -38,13 +60,20 @@ const PartyLink = ({ id, truncate = false, ...props }: PartyLinkProps) => {
} }
return ( return (
<Link <span className="whitespace-nowrap">
className="underline font-mono" {useName && <Icon size={4} name="cube" className="mr-2" />}
{...props} <Link
to={`/${Routes.PARTIES}/${id}`} className="underline font-mono"
> {...props}
<Hash text={truncate ? truncateMiddle(id) : id} /> to={`/${Routes.PARTIES}/${id}`}
</Link> >
{useName ? (
name
) : (
<Hash text={truncate ? truncateMiddle(id, 4, 4) : id} />
)}
</Link>
</span>
); );
}; };
@@ -1,8 +1,9 @@
import { useMemo } from 'react';
import type { MarketFieldsFragment } from '@vegaprotocol/markets'; import type { MarketFieldsFragment } from '@vegaprotocol/markets';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { ButtonLink } from '@vegaprotocol/ui-toolkit'; import { ButtonLink } from '@vegaprotocol/ui-toolkit';
import type { AgGridReact } from 'ag-grid-react'; import type { AgGridReact } from 'ag-grid-react';
import { AgGridColumn } from 'ag-grid-react'; import type { ColDef } from 'ag-grid-community';
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid'; import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
import type { import type {
VegaICellRendererParams, VegaICellRendererParams,
@@ -39,54 +40,34 @@ export const MarketsTable = ({ data }: MarketsTableProps) => {
}; };
}, []); }, []);
return ( const columnDefs = useMemo<ColDef[]>(
<AgGrid () => [
ref={gridRef} {
rowData={data} colId: 'code',
getRowId={({ data }: { data: MarketFieldsFragment }) => data.id} headerName: t('Code'),
overlayNoRowsTemplate={t('This chain has no markets')} field: 'tradableInstrument.instrument.code',
domLayout="autoHeight" },
defaultColDef={{ {
flex: 1, colId: 'name',
resizable: true, headerName: t('Name'),
sortable: true, field: 'tradableInstrument.instrument.name',
filter: true, },
filterParams: { buttons: ['reset'] }, {
autoHeight: true, headerName: t('Status'),
}} field: 'state',
suppressCellFocus={true} hide: window.innerWidth <= BREAKPOINT_MD,
onRowClicked={({ data, event }: RowClickedEvent) => { valueGetter: ({
if ((event?.target as HTMLElement).tagName.toUpperCase() !== 'BUTTON') {
navigate(data.id);
}
}}
>
<AgGridColumn
colId="code"
headerName={t('Code')}
field="tradableInstrument.instrument.code"
/>
<AgGridColumn
colId="name"
headerName={t('Name')}
field="tradableInstrument.instrument.name"
/>
<AgGridColumn
headerName={t('Status')}
field="state"
hide={window.innerWidth <= BREAKPOINT_MD}
valueGetter={({
data, data,
}: VegaValueGetterParams<MarketFieldsFragment, 'state'>) => { }: VegaValueGetterParams<MarketFieldsFragment>) => {
return data?.state ? MarketStateMapping[data?.state] : '-'; return data?.state ? MarketStateMapping[data?.state] : '-';
}} },
/> },
<AgGridColumn {
colId="asset" colId: 'asset',
headerName={t('Settlement asset')} headerName: t('Settlement asset'),
field="tradableInstrument.instrument.product.settlementAsset.symbol" field: 'tradableInstrument.instrument.product.settlementAsset.symbol',
hide={window.innerWidth <= BREAKPOINT_MD} hide: window.innerWidth <= BREAKPOINT_MD,
cellRenderer={({ cellRenderer: ({
data, data,
}: VegaICellRendererParams< }: VegaICellRendererParams<
MarketFieldsFragment, MarketFieldsFragment,
@@ -105,19 +86,19 @@ export const MarketsTable = ({ data }: MarketsTableProps) => {
) : ( ) : (
'' ''
); );
}} },
/> },
<AgGridColumn {
flex={2} flex: 2,
headerName={t('Market ID')} headerName: t('Market ID'),
field="id" field: 'id',
hide={window.innerWidth <= BREAKPOINT_MD} hide: window.innerWidth <= BREAKPOINT_MD,
/> },
<AgGridColumn {
colId="actions" colId: 'actions',
headerName="" headerName: '',
field="id" field: 'id',
cellRenderer={({ cellRenderer: ({
value, value,
}: VegaICellRendererParams<MarketFieldsFragment, 'id'>) => }: VegaICellRendererParams<MarketFieldsFragment, 'id'>) =>
value ? ( value ? (
@@ -126,9 +107,34 @@ export const MarketsTable = ({ data }: MarketsTableProps) => {
</Link> </Link>
) : ( ) : (
'' ''
) ),
},
],
[openAssetDetailsDialog]
);
return (
<AgGrid
ref={gridRef}
rowData={data}
getRowId={({ data }: { data: MarketFieldsFragment }) => data.id}
overlayNoRowsTemplate={t('This chain has no markets')}
domLayout="autoHeight"
defaultColDef={{
flex: 1,
resizable: true,
sortable: true,
filter: true,
filterParams: { buttons: ['reset'] },
autoHeight: true,
}}
columnDefs={columnDefs}
suppressCellFocus={true}
onRowClicked={({ data, event }: RowClickedEvent) => {
if ((event?.target as HTMLElement).tagName.toUpperCase() !== 'BUTTON') {
navigate(data.id);
} }
/> }}
</AgGrid> />
); );
}; };
@@ -13,6 +13,10 @@ fragment ExplorerDeterministicOrderFields on Order {
remaining remaining
size size
rejectionReason rejectionReason
peggedOrder {
reference
offset
}
party { party {
id id
} }
@@ -3,7 +3,7 @@ import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client'; import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client'; import * as Apollo from '@apollo/client';
const defaultOptions = {} as const; const defaultOptions = {} as const;
export type ExplorerDeterministicOrderFieldsFragment = { __typename?: 'Order', id: string, type?: Types.OrderType | null, reference: string, status: Types.OrderStatus, version: string, createdAt: any, updatedAt?: any | null, expiresAt?: any | null, timeInForce: Types.OrderTimeInForce, price: string, side: Types.Side, remaining: string, size: string, rejectionReason?: Types.OrderRejectionReason | null, party: { __typename?: 'Party', id: string }, market: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string } } } } }; export type ExplorerDeterministicOrderFieldsFragment = { __typename?: 'Order', id: string, type?: Types.OrderType | null, reference: string, status: Types.OrderStatus, version: string, createdAt: any, updatedAt?: any | null, expiresAt?: any | null, timeInForce: Types.OrderTimeInForce, price: string, side: Types.Side, remaining: string, size: string, rejectionReason?: Types.OrderRejectionReason | null, peggedOrder?: { __typename?: 'PeggedOrder', reference: Types.PeggedReference, offset: string } | null, party: { __typename?: 'Party', id: string }, market: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string } } } } };
export type ExplorerDeterministicOrderQueryVariables = Types.Exact<{ export type ExplorerDeterministicOrderQueryVariables = Types.Exact<{
orderId: Types.Scalars['ID']; orderId: Types.Scalars['ID'];
@@ -11,7 +11,7 @@ export type ExplorerDeterministicOrderQueryVariables = Types.Exact<{
}>; }>;
export type ExplorerDeterministicOrderQuery = { __typename?: 'Query', orderByID: { __typename?: 'Order', id: string, type?: Types.OrderType | null, reference: string, status: Types.OrderStatus, version: string, createdAt: any, updatedAt?: any | null, expiresAt?: any | null, timeInForce: Types.OrderTimeInForce, price: string, side: Types.Side, remaining: string, size: string, rejectionReason?: Types.OrderRejectionReason | null, party: { __typename?: 'Party', id: string }, market: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string } } } } } }; export type ExplorerDeterministicOrderQuery = { __typename?: 'Query', orderByID: { __typename?: 'Order', id: string, type?: Types.OrderType | null, reference: string, status: Types.OrderStatus, version: string, createdAt: any, updatedAt?: any | null, expiresAt?: any | null, timeInForce: Types.OrderTimeInForce, price: string, side: Types.Side, remaining: string, size: string, rejectionReason?: Types.OrderRejectionReason | null, peggedOrder?: { __typename?: 'PeggedOrder', reference: Types.PeggedReference, offset: string } | null, party: { __typename?: 'Party', id: string }, market: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string } } } } } };
export const ExplorerDeterministicOrderFieldsFragmentDoc = gql` export const ExplorerDeterministicOrderFieldsFragmentDoc = gql`
fragment ExplorerDeterministicOrderFields on Order { fragment ExplorerDeterministicOrderFields on Order {
@@ -29,6 +29,10 @@ export const ExplorerDeterministicOrderFieldsFragmentDoc = gql`
remaining remaining
size size
rejectionReason rejectionReason
peggedOrder {
reference
offset
}
party { party {
id id
} }
@@ -12,7 +12,7 @@ type Amend = components['schemas']['v1OrderAmendment'];
function renderAmendOrderDetails( function renderAmendOrderDetails(
id: string, id: string,
version: number, version: number | undefined,
amend: Amend, amend: Amend,
mocks: MockedResponse[] mocks: MockedResponse[]
) { ) {
@@ -25,7 +25,11 @@ function renderAmendOrderDetails(
); );
} }
function renderExistingAmend(id: string, version: number, amend: Amend) { function renderExistingAmend(
id: string,
version: number | undefined,
amend: Amend
) {
const mocks = [ const mocks = [
{ {
request: { request: {
@@ -49,6 +53,7 @@ function renderExistingAmend(id: string, version: number, amend: Amend) {
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC, timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
price: '200', price: '200',
side: 'BUY', side: 'BUY',
peggedOrder: null,
remaining: '99', remaining: '99',
rejectionReason: 'rejection', rejectionReason: 'rejection',
reference: '123', reference: '123',
@@ -77,6 +82,56 @@ function renderExistingAmend(id: string, version: number, amend: Amend) {
}, },
}, },
}, },
{
request: {
query: ExplorerDeterministicOrderDocument,
variables: {
orderId: '123',
},
},
result: {
data: {
orderByID: {
__typename: 'Order',
id: '123',
type: 'GTT',
status: Schema.OrderStatus.STATUS_ACTIVE,
version: 100,
createdAt: '123',
updatedAt: '456',
expiresAt: '789',
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
peggedOrder: null,
price: '200',
side: 'BUY',
remaining: '99',
rejectionReason: 'rejection',
reference: '123',
size: '200',
party: {
__typename: 'Party',
id: '234',
},
market: {
__typename: 'Market',
id: 'amend-to-order-latest-version',
state: 'STATUS_ACTIVE',
positionDecimalPlaces: 2,
decimalPlaces: '5',
tradableInstrument: {
instrument: {
name: 'amend-to-order-latest-version-test',
product: {
__typename: 'Future',
quoteName: '123',
},
},
},
},
},
},
},
},
{ {
request: { request: {
query: ExplorerMarketDocument, query: ExplorerMarketDocument,
@@ -157,4 +212,15 @@ describe('Amend order details', () => {
expect(await res.findByText('New price')).toBeInTheDocument(); expect(await res.findByText('New price')).toBeInTheDocument();
expect(await res.findByText('-7879')).toBeInTheDocument(); expect(await res.findByText('-7879')).toBeInTheDocument();
}); });
it('Fetches latest version when version is not specified', async () => {
const amend: Amend = {
price: '-7879',
};
const res = renderExistingAmend('123', undefined, amend);
expect(
await res.findByText('amend-to-order-latest-version')
).toBeInTheDocument();
});
}); });
@@ -12,7 +12,7 @@ import { wrapperClasses } from './deterministic-order-details';
export interface AmendOrderDetailsProps { export interface AmendOrderDetailsProps {
id: string; id: string;
amend: components['schemas']['v1OrderAmendment']; amend: components['schemas']['v1OrderAmendment'];
// Version to fetch, with 0 being 'latest' and 1 being 'first'. Defaults to 0 // Version to fetch. Latest is provided by default
version?: number; version?: number;
} }
@@ -34,13 +34,11 @@ export function getSideDeltaColour(delta: string): string {
* @param param0 * @param param0
* @returns * @returns
*/ */
const AmendOrderDetails = ({ const AmendOrderDetails = ({ id, version, amend }: AmendOrderDetailsProps) => {
id, const variables = version ? { orderId: id, version } : { orderId: id };
version = 0,
amend,
}: AmendOrderDetailsProps) => {
const { data, error } = useExplorerDeterministicOrderQuery({ const { data, error } = useExplorerDeterministicOrderQuery({
variables: { orderId: id, version }, variables,
}); });
if (error || (data && !data.orderByID)) { if (error || (data && !data.orderByID)) {
@@ -5,6 +5,7 @@ import PriceInMarket from '../price-in-market/price-in-market';
import { Time } from '../time'; import { Time } from '../time';
import { sideText, statusText, tifFull, tifShort } from './lib/order-labels'; import { sideText, statusText, tifFull, tifShort } from './lib/order-labels';
import SizeInMarket from '../size-in-market/size-in-market'; import SizeInMarket from '../size-in-market/size-in-market';
import { TxOrderPeggedReference } from '../txs/details/order/tx-order-peg';
export interface DeterministicOrderDetailsProps { export interface DeterministicOrderDetailsProps {
id: string; id: string;
@@ -68,25 +69,35 @@ const DeterministicOrderDetails = ({
<span className="mx-5 text-base">@</span> <span className="mx-5 text-base">@</span>
<PriceInMarket price={o.price} marketId={o.market.id} /> <PriceInMarket price={o.price} marketId={o.market.id} />
</h2> </h2>
<p className="text-gray-500 mb-4"> <p className="text-gray-200">
In <MarketLink id={o.market.id} /> at <Time date={o.createdAt} />. In <MarketLink id={o.market.id} /> at <Time date={o.createdAt} />.
</p> </p>
{o.peggedOrder ? (
<p className="text-gray-200">
{t('Price peg')}:{' '}
<TxOrderPeggedReference
side={o.side}
reference={o.peggedOrder.reference}
offset={o.peggedOrder.offset}
marketId={o.market.id}
/>
</p>
) : null}
{o.reference ? ( {o.reference ? (
<p className="text-gray-500 mb-4"> <p className="text-gray-500 mt-4">
<span>{t('Reference')}</span>: {o.reference} <span>{t('Reference')}</span>: {o.reference}
</p> </p>
) : null} ) : null}
<div className="grid md:grid-cols-4 gap-x-6"> <div className="grid md:grid-cols-4 gap-x-6 mt-4">
{version !== 0 ? null : ( <div className="mb-12 md:mb-0">
<div className="mb-12 md:mb-0"> <h2 className="text-2xl font-bold text-dark mb-4">
<h2 className="text-2xl font-bold text-dark mb-4"> {t('Status')}
{t('Status')} </h2>
</h2> <h5 className="text-lg font-medium text-gray-500 mb-0 capitalize">
<h5 className="text-lg font-medium text-gray-500 mb-0 capitalize"> {statusText[o.status]}
{statusText[o.status]} </h5>
</h5> </div>
</div>
)}
<div className="mb-12 md:mb-0"> <div className="mb-12 md:mb-0">
<h2 className="text-2xl font-bold text-dark mb-4">{t('Size')}</h2> <h2 className="text-2xl font-bold text-dark mb-4">{t('Size')}</h2>
@@ -95,17 +106,6 @@ const DeterministicOrderDetails = ({
</h5> </h5>
</div> </div>
{version !== 0 ? null : (
<div className="">
<h2 className="text-2xl font-bold text-dark mb-4">
{t('Remaining')}
</h2>
<h5 className="text-lg font-medium text-gray-500 mb-0">
<SizeInMarket size={o.remaining} marketId={o.market.id} />
</h5>
</div>
)}
<div className=""> <div className="">
<h2 className="text-2xl font-bold text-dark mb-4"> <h2 className="text-2xl font-bold text-dark mb-4">
{t('Version')} {t('Version')}
@@ -31,6 +31,7 @@ const mock = {
side: 'SIDE_BUY', side: 'SIDE_BUY',
remaining: '100', remaining: '100',
size: '100', size: '100',
peggedOrder: null,
party: { party: {
id: '456', id: '456',
}, },
@@ -1,7 +1,6 @@
import type { ProposalListFieldsFragment } from '@vegaprotocol/proposals'; import type { ProposalListFieldsFragment } from '@vegaprotocol/proposals';
import { VoteProgress } from '@vegaprotocol/proposals'; import { VoteProgress } from '@vegaprotocol/proposals';
import type { AgGridReact } from 'ag-grid-react'; import type { AgGridReact } from 'ag-grid-react';
import { AgGridColumn } from 'ag-grid-react';
import { ExternalLink } from '@vegaprotocol/ui-toolkit'; import { ExternalLink } from '@vegaprotocol/ui-toolkit';
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid'; import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
import type { import type {
@@ -9,7 +8,7 @@ import type {
VegaValueFormatterParams, VegaValueFormatterParams,
} from '@vegaprotocol/datagrid'; } from '@vegaprotocol/datagrid';
import { useLayoutEffect, useMemo, useRef, useState } from 'react'; import { useLayoutEffect, useMemo, useRef, useState } from 'react';
import type { RowClickedEvent } from 'ag-grid-community'; import type { RowClickedEvent, ColDef } from 'ag-grid-community';
import { getDateTimeFormat } from '@vegaprotocol/utils'; import { getDateTimeFormat } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { import {
@@ -64,7 +63,128 @@ export const ProposalsTable = ({ data }: ProposalsTableProps) => {
title: '', title: '',
content: null, content: null,
}); });
const columnDefs = useMemo<ColDef[]>(
() => [
{
colId: 'title',
headerName: t('Title'),
field: 'rationale.title',
flex: 2,
wrapText: true,
},
{
colId: 'type',
maxWidth: 180,
hide: window.innerWidth <= BREAKPOINT_MD,
headerName: t('Type'),
field: 'terms.change.__typename',
},
{
maxWidth: 100,
headerName: t('State'),
field: 'state',
valueFormatter: ({
value,
}: VegaValueFormatterParams<ProposalListFieldsFragment, 'state'>) => {
return value ? ProposalStateMapping[value] : '-';
},
},
{
colId: 'voting',
maxWidth: 100,
hide: window.innerWidth <= BREAKPOINT_MD,
headerName: t('Voting'),
cellRenderer: ({
data,
}: VegaICellRendererParams<ProposalListFieldsFragment>) => {
if (data) {
const yesTokens = new BigNumber(data.votes.yes.totalTokens);
const noTokens = new BigNumber(data.votes.no.totalTokens);
const totalTokensVoted = yesTokens.plus(noTokens);
const yesPercentage = totalTokensVoted.isZero()
? new BigNumber(0)
: yesTokens.multipliedBy(100).dividedBy(totalTokensVoted);
return (
<div className="uppercase flex h-full items-center justify-center pt-2">
<VoteProgress
threshold={requiredMajorityPercentage}
progress={yesPercentage}
/>
</div>
);
}
return '-';
},
},
{
colId: 'cDate',
maxWidth: 150,
hide: window.innerWidth <= BREAKPOINT_MD,
headerName: t('Closing date'),
field: 'terms.closingDatetime',
valueFormatter: ({
value,
}: VegaValueFormatterParams<
ProposalListFieldsFragment,
'terms.closingDatetime'
>) => {
return value ? getDateTimeFormat().format(new Date(value)) : '-';
},
},
{
colId: 'eDate',
maxWidth: 150,
hide: window.innerWidth <= BREAKPOINT_MD,
headerName: t('Enactment date'),
field: 'terms.enactmentDatetime',
valueFormatte: ({
value,
}: VegaValueFormatterParams<
ProposalListFieldsFragment,
'terms.enactmentDatetime'
>) => {
return value ? getDateTimeFormat().format(new Date(value)) : '-';
},
},
{
colId: 'actions',
minWidth: window.innerWidth > BREAKPOINT_MD ? 221 : 80,
maxWidth: 221,
sortable: false,
filter: false,
resizable: false,
cellRenderer: ({
data,
}: VegaICellRendererParams<ProposalListFieldsFragment>) => {
const proposalPage = tokenLink(
TOKEN_PROPOSAL.replace(':id', data?.id || '')
);
const openDialog = () => {
if (!data) return;
setDialog({
open: true,
title: data.rationale.title,
content: data.terms,
});
};
return (
<div className="pb-1">
<button className="underline max-md:hidden" onClick={openDialog}>
{t('View terms')}
</button>{' '}
<ExternalLink className="max-md:hidden" href={proposalPage}>
{t('Open in Governance')}
</ExternalLink>
<ExternalLink className="md:hidden" href={proposalPage}>
{t('Open')}
</ExternalLink>
</div>
);
},
},
],
[requiredMajorityPercentage, tokenLink]
);
return ( return (
<> <>
<AgGrid <AgGrid
@@ -83,6 +203,7 @@ export const ProposalsTable = ({ data }: ProposalsTableProps) => {
filterParams: { buttons: ['reset'] }, filterParams: { buttons: ['reset'] },
autoHeight: true, autoHeight: true,
}} }}
columnDefs={columnDefs}
suppressCellFocus={true} suppressCellFocus={true}
onRowClicked={({ data, event }: RowClickedEvent) => { onRowClicked={({ data, event }: RowClickedEvent) => {
if ( if (
@@ -94,128 +215,7 @@ export const ProposalsTable = ({ data }: ProposalsTableProps) => {
window.open(proposalPage, '_blank'); window.open(proposalPage, '_blank');
} }
}} }}
> />
<AgGridColumn
colId="title"
headerName={t('Title')}
field="rationale.title"
flex={2}
wrapText={true}
/>
<AgGridColumn
colId="type"
maxWidth={180}
hide={window.innerWidth <= BREAKPOINT_MD}
headerName={t('Type')}
field="terms.change.__typename"
/>
<AgGridColumn
maxWidth={100}
headerName={t('State')}
field="state"
valueFormatter={({
value,
}: VegaValueFormatterParams<ProposalListFieldsFragment, 'state'>) => {
return value ? ProposalStateMapping[value] : '-';
}}
/>
<AgGridColumn
colId="voting"
maxWidth={100}
hide={window.innerWidth <= BREAKPOINT_MD}
headerName={t('Voting')}
cellRenderer={({
data,
}: VegaICellRendererParams<ProposalListFieldsFragment>) => {
if (data) {
const yesTokens = new BigNumber(data.votes.yes.totalTokens);
const noTokens = new BigNumber(data.votes.no.totalTokens);
const totalTokensVoted = yesTokens.plus(noTokens);
const yesPercentage = totalTokensVoted.isZero()
? new BigNumber(0)
: yesTokens.multipliedBy(100).dividedBy(totalTokensVoted);
return (
<div className="uppercase flex h-full items-center justify-center pt-2">
<VoteProgress
threshold={requiredMajorityPercentage}
progress={yesPercentage}
/>
</div>
);
}
return '-';
}}
/>
<AgGridColumn
colId="cDate"
maxWidth={150}
hide={window.innerWidth <= BREAKPOINT_MD}
headerName={t('Closing date')}
field="terms.closingDatetime"
valueFormatter={({
value,
}: VegaValueFormatterParams<
ProposalListFieldsFragment,
'terms.closingDatetime'
>) => {
return value ? getDateTimeFormat().format(new Date(value)) : '-';
}}
/>
<AgGridColumn
colId="eDate"
maxWidth={150}
hide={window.innerWidth <= BREAKPOINT_MD}
headerName={t('Enactment date')}
field="terms.enactmentDatetime"
valueFormatter={({
value,
}: VegaValueFormatterParams<
ProposalListFieldsFragment,
'terms.enactmentDatetime'
>) => {
return value ? getDateTimeFormat().format(new Date(value)) : '-';
}}
/>
<AgGridColumn
colId="actions"
minWidth={window.innerWidth > BREAKPOINT_MD ? 221 : 80}
maxWidth={221}
sortable={false}
filter={false}
resizable={false}
cellRenderer={({
data,
}: VegaICellRendererParams<ProposalListFieldsFragment>) => {
const proposalPage = tokenLink(
TOKEN_PROPOSAL.replace(':id', data?.id || '')
);
const openDialog = () => {
if (!data) return;
setDialog({
open: true,
title: data.rationale.title,
content: data.terms,
});
};
return (
<div className="pb-1">
<button
className="underline max-md:hidden"
onClick={openDialog}
>
{t('View terms')}
</button>{' '}
<ExternalLink className="max-md:hidden" href={proposalPage}>
{t('Open in Governance')}
</ExternalLink>
<ExternalLink className="md:hidden" href={proposalPage}>
{t('Open')}
</ExternalLink>
</div>
);
}}
/>
</AgGrid>
<JsonViewerDialog <JsonViewerDialog
open={dialog.open} open={dialog.open}
onChange={(isOpen) => setDialog({ ...dialog, open: isOpen })} onChange={(isOpen) => setDialog({ ...dialog, open: isOpen })}
@@ -1,4 +1,4 @@
import { Icon } from '@vegaprotocol/ui-toolkit'; import { Icon, Tooltip } from '@vegaprotocol/ui-toolkit';
// https://github.com/vegaprotocol/vega/blob/develop/core/blockchain/response.go // https://github.com/vegaprotocol/vega/blob/develop/core/blockchain/response.go
export const ErrorCodes = new Map([ export const ErrorCodes = new Map([
@@ -17,6 +17,8 @@ interface ChainResponseCodeProps {
code: number; code: number;
hideLabel?: boolean; hideLabel?: boolean;
error?: string; error?: string;
hideIfOk?: boolean;
small?: boolean;
} }
/** /**
@@ -28,14 +30,21 @@ export const ChainResponseCode = ({
code, code,
hideLabel = false, hideLabel = false,
error, error,
hideIfOk = false,
small = false,
}: ChainResponseCodeProps) => { }: ChainResponseCodeProps) => {
if (hideIfOk && code === 0) {
return null;
}
const isSuccess = successCodes.has(code); const isSuccess = successCodes.has(code);
const size = small ? 3 : 4;
const successColour = const successColour =
code === 71 ? 'fill-vega-orange' : 'fill-vega-green-600'; code === 71 ? '!fill-vega-orange' : '!fill-vega-green-600';
const icon = isSuccess ? ( const icon = isSuccess ? (
<Icon name="tick-circle" className={successColour} /> <Icon size={size} name="tick-circle" className={`${successColour}`} />
) : ( ) : (
<Icon name="cross" className="fill-vega-pink-600" /> <Icon size={size} name="cross" className="!fill-vega-pink-500" />
); );
const label = ErrorCodes.get(code) || 'Unknown response code'; const label = ErrorCodes.get(code) || 'Unknown response code';
@@ -44,18 +53,28 @@ export const ChainResponseCode = ({
error && error.length > 100 ? error.replace(/,/g, ',\r\n') : error; error && error.length > 100 ? error.replace(/,/g, ',\r\n') : error;
return ( return (
<div title={`Response code: ${code} - ${label}`} className=" inline-block"> <Tooltip
<span description={
className="mr-2" <span>
aria-label={isSuccess ? 'Success' : 'Warning'} Response code: {code} - {label}
role="img" </span>
> }
{icon} >
</span> <div className="mt-[-1px] inline-block">
{hideLabel ? null : <span>{label}</span>} <span
{!hideLabel && !!displayError ? ( className="mr-2"
<span className="ml-1 whitespace-pre">&mdash;&nbsp;{displayError}</span> aria-label={isSuccess ? 'Success' : 'Warning'}
) : null} role="img"
</div> >
{icon}
</span>
{hideLabel ? null : <span>{label}</span>}
{!hideLabel && !!displayError ? (
<span className="ml-1 whitespace-pre">
&mdash;&nbsp;{displayError}
</span>
) : null}
</div>
</Tooltip>
); );
}; };
@@ -0,0 +1,113 @@
import { render } from '@testing-library/react';
import type { TxDetailsOrderProps } from './tx-order-peg';
import { TxOrderPeggedReference, getMarketDecimals } from './tx-order-peg';
import { useExplorerMarketQuery } from '../../../links/market-link/__generated__/Market';
import type { ExplorerMarketQuery } from '../../../links/market-link/__generated__/Market';
import { PeggedReference, Side } from '@vegaprotocol/types';
// Mock the useExplorerMarketQuery hook
jest.mock('../../../links/market-link/__generated__/Market', () => ({
useExplorerMarketQuery: jest.fn().mockReturnValue({
data: {
market: { decimalPlaces: 0 },
},
loading: false,
}),
}));
describe('getSettlementAsset', () => {
it('should return the decimal places if data is defined', () => {
const data = {
market: {
__typename: 'Market',
id: '123',
decimalPlaces: 8,
},
};
const result = getMarketDecimals(data as Partial<ExplorerMarketQuery>);
expect(result).toEqual(8);
});
it('should return 0 if data is undefined', () => {
const result = getMarketDecimals(undefined);
expect(result).toEqual(0);
});
});
describe('TxOrderPeggedReference', () => {
beforeEach(() => {
// Mock the useExplorerMarketQuery hook return value
(useExplorerMarketQuery as jest.Mock).mockReturnValue({
data: {
settlementAsset: 'some-settlement-asset',
},
loading: false,
});
});
afterEach(() => {
jest.resetAllMocks();
});
it('should render the offset and reference correctly', () => {
const props: TxDetailsOrderProps = {
side: Side.SIDE_BUY,
offset: '10',
reference: PeggedReference.PEGGED_REFERENCE_MID,
marketId: 'some-market-id',
};
const { getByTestId } = render(<TxOrderPeggedReference {...props} />);
expect(getByTestId('pegged-reference')).toHaveTextContent('Mid + 10');
});
it('should return null if the reference is "PEGGED_REFERENCE_UNSPECIFIED"', () => {
const props: TxDetailsOrderProps = {
side: Side.SIDE_BUY,
offset: '10',
reference: 'PEGGED_REFERENCE_UNSPECIFIED',
marketId: 'some-market-id',
};
const { container } = render(<TxOrderPeggedReference {...props} />);
expect(container.firstChild).toBeNull();
});
it('should render the offset without formatting initially, then render the formatted version', () => {
const props: TxDetailsOrderProps = {
side: Side.SIDE_BUY,
offset: '10',
reference: PeggedReference.PEGGED_REFERENCE_BEST_ASK,
marketId: 'some-market-id',
};
(useExplorerMarketQuery as jest.Mock).mockReturnValue({
data: null,
loading: true,
});
const screen = render(<TxOrderPeggedReference {...props} />);
expect(screen.getByTestId('pegged-reference')).toHaveTextContent(
'Ask + 10'
);
(useExplorerMarketQuery as jest.Mock).mockReturnValue({
data: {
market: {
decimalPlaces: 10,
},
},
loading: false,
});
screen.rerender(<TxOrderPeggedReference {...props} />);
expect(screen.getByTestId('pegged-reference')).toHaveTextContent(
'Ask + 0.000000001'
);
});
});
@@ -0,0 +1,72 @@
import { t } from '@vegaprotocol/i18n';
import { TableCell, TableRow } from '../../../table';
import type { VegaPeggedReference } from '../liquidity-provision/liquidity-provision-details';
import { Side, PeggedReferenceMapping } from '@vegaprotocol/types';
import { useExplorerMarketQuery } from '../../../links/market-link/__generated__/Market';
import type { ExplorerMarketQuery } from '../../../links/market-link/__generated__/Market';
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
export interface TxDetailsOrderProps {
offset: string;
reference: VegaPeggedReference;
marketId: string;
side: Side;
}
export function getMarketDecimals(
data: ExplorerMarketQuery | undefined
): number {
return data?.market?.decimalPlaces || 0;
}
/**
* Summarises an order's peg
*/
export const TxOrderPeggedReferenceRow = ({
offset,
reference,
marketId,
side,
}: TxDetailsOrderProps) => {
return (
<TableRow modifier="bordered">
<TableCell>{t('Pegged order')}</TableCell>
<TableCell>
<TxOrderPeggedReference
side={side}
offset={offset}
reference={reference}
marketId={marketId}
/>
</TableCell>
</TableRow>
);
};
export const TxOrderPeggedReference = ({
offset,
reference,
marketId,
side,
}: TxDetailsOrderProps) => {
const { data, loading } = useExplorerMarketQuery({
variables: { id: marketId },
});
const direction = side === Side.SIDE_BUY ? '+' : '-';
const decimalPlaces = getMarketDecimals(data);
if (reference === 'PEGGED_REFERENCE_UNSPECIFIED') {
return null;
}
return (
<span data-testid="pegged-reference">
{PeggedReferenceMapping[reference]}&nbsp;
{direction}&nbsp;
{!loading && data
? addDecimalsFormatNumber(offset, decimalPlaces)
: offset}
</span>
);
};
@@ -0,0 +1,86 @@
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,6 +7,7 @@ import { TableCell, TableRow, TableWithTbody } from '../../table';
import type { components } from '../../../../types/explorer'; import type { components } from '../../../../types/explorer';
import { LiquidityProvisionDetails } from './liquidity-provision/liquidity-provision-details'; import { LiquidityProvisionDetails } from './liquidity-provision/liquidity-provision-details';
import PriceInMarket from '../../price-in-market/price-in-market'; import PriceInMarket from '../../price-in-market/price-in-market';
import BigNumber from 'bignumber.js';
export type LiquidityAmendment = export type LiquidityAmendment =
components['schemas']['v1LiquidityProvisionAmendment']; components['schemas']['v1LiquidityProvisionAmendment'];
@@ -34,6 +35,10 @@ export const TxDetailsLiquidityAmendment = ({
txData.command.liquidityProvisionAmendment; txData.command.liquidityProvisionAmendment;
const marketId: string = amendment.marketId || '-'; const marketId: string = amendment.marketId || '-';
const fee = amendment.fee
? new BigNumber(amendment.fee).times(100).toString()
: '-';
return ( return (
<> <>
<TableWithTbody className="mb-8" allowWrap={true}> <TableWithTbody className="mb-8" allowWrap={true}>
@@ -63,7 +68,7 @@ export const TxDetailsLiquidityAmendment = ({
{amendment.fee ? ( {amendment.fee ? (
<TableRow modifier="bordered"> <TableRow modifier="bordered">
<TableCell>{t('Fee')}</TableCell> <TableCell>{t('Fee')}</TableCell>
<TableCell>{amendment.fee}%</TableCell> <TableCell>{fee}%</TableCell>
</TableRow> </TableRow>
) : null} ) : null}
</TableWithTbody> </TableWithTbody>
@@ -0,0 +1,86 @@
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,6 +7,7 @@ import { TableCell, TableRow, TableWithTbody } from '../../table';
import type { components } from '../../../../types/explorer'; import type { components } from '../../../../types/explorer';
import { LiquidityProvisionDetails } from './liquidity-provision/liquidity-provision-details'; import { LiquidityProvisionDetails } from './liquidity-provision/liquidity-provision-details';
import PriceInMarket from '../../price-in-market/price-in-market'; import PriceInMarket from '../../price-in-market/price-in-market';
import BigNumber from 'bignumber.js';
export type LiquiditySubmission = export type LiquiditySubmission =
components['schemas']['v1LiquidityProvisionSubmission']; components['schemas']['v1LiquidityProvisionSubmission'];
@@ -33,6 +34,10 @@ export const TxDetailsLiquiditySubmission = ({
txData.command.liquidityProvisionSubmission; txData.command.liquidityProvisionSubmission;
const marketId: string = submission.marketId || '-'; const marketId: string = submission.marketId || '-';
const fee = submission.fee
? new BigNumber(submission.fee).times(100).toString()
: '-';
return ( return (
<> <>
<TableWithTbody className="mb-8" allowWrap={true}> <TableWithTbody className="mb-8" allowWrap={true}>
@@ -62,7 +67,7 @@ export const TxDetailsLiquiditySubmission = ({
{submission.fee ? ( {submission.fee ? (
<TableRow modifier="bordered"> <TableRow modifier="bordered">
<TableCell>{t('Fee')}</TableCell> <TableCell>{t('Fee')}</TableCell>
<TableCell>{submission.fee}%</TableCell> <TableCell>{fee}%</TableCell>
</TableRow> </TableRow>
) : null} ) : null}
</TableWithTbody> </TableWithTbody>
@@ -7,6 +7,7 @@ import { TableCell, TableRow, TableWithTbody } from '../../table';
import { txSignatureToDeterministicId } from '../lib/deterministic-ids'; import { txSignatureToDeterministicId } from '../lib/deterministic-ids';
import DeterministicOrderDetails from '../../order-details/deterministic-order-details'; import DeterministicOrderDetails from '../../order-details/deterministic-order-details';
import Hash from '../../links/hash'; import Hash from '../../links/hash';
import { TxOrderPeggedReferenceRow } from './order/tx-order-peg';
interface TxDetailsOrderProps { interface TxDetailsOrderProps {
txData: BlockExplorerTransactionResult | undefined; txData: BlockExplorerTransactionResult | undefined;
@@ -29,6 +30,8 @@ export const TxDetailsOrder = ({
return <>{t('Awaiting Block Explorer transaction details')}</>; return <>{t('Awaiting Block Explorer transaction details')}</>;
} }
const marketId = txData.command.orderSubmission.marketId || '-'; const marketId = txData.command.orderSubmission.marketId || '-';
const reference = txData.command.orderSubmission.peggedOrder;
const side = txData.command.orderSubmission.side;
let deterministicId = ''; let deterministicId = '';
@@ -63,6 +66,14 @@ export const TxDetailsOrder = ({
<MarketLink id={marketId} /> <MarketLink id={marketId} />
</TableCell> </TableCell>
</TableRow> </TableRow>
{reference ? (
<TxOrderPeggedReferenceRow
side={side}
offset={reference.offset}
reference={reference.reference}
marketId={marketId}
/>
) : null}
</TableWithTbody> </TableWithTbody>
{deterministicId.length > 0 ? ( {deterministicId.length > 0 ? (
@@ -10,13 +10,17 @@ export interface FilterLabelProps {
*/ */
export function FilterLabel({ filters }: FilterLabelProps) { export function FilterLabel({ filters }: FilterLabelProps) {
if (!filters || filters.size !== 1) { if (!filters || filters.size !== 1) {
return <span className="uppercase">{t('Filter')}</span>; return (
<span data-testid="filter-empty" className="uppercase">
{t('Filter')}
</span>
);
} }
return ( return (
<div> <div data-testid="filter-selected">
<span className="uppercase">{t('Filters')}:</span>&nbsp; <span className="uppercase">{t('Filters')}:</span>&nbsp;
<code className="bg-vega-light-150 px-2 rounded-md capitalize"> <code className="bg-vega-light-150 dark:bg-vega-light-300 px-2 rounded-md capitalize dark:text-black">
{Array.from(filters)[0]} {Array.from(filters)[0]}
</code> </code>
</div> </div>
@@ -0,0 +1,21 @@
import { render, screen } from '@testing-library/react';
import { TxsFilter } from './tx-filter';
import type { FilterOption } from './tx-filter';
describe('TxsFilter', () => {
it('renders holding text when nothing is selected', () => {
const filters: Set<FilterOption> = new Set([]);
const setFilters = jest.fn();
render(<TxsFilter filters={filters} setFilters={setFilters} />);
expect(screen.getByTestId('filter-empty')).toBeInTheDocument();
expect(screen.getByText('Filter')).toBeInTheDocument();
});
it('renders the submit order filter as selected', () => {
const filters: Set<FilterOption> = new Set(['Submit Order']);
const setFilters = jest.fn();
render(<TxsFilter filters={filters} setFilters={setFilters} />);
expect(screen.getByTestId('filter-selected')).toBeInTheDocument();
expect(screen.getByText('Submit Order')).toBeInTheDocument();
});
});
@@ -91,7 +91,7 @@ export interface TxFilterProps {
* types. It allows a user to select one transaction type to view. Later * types. It allows a user to select one transaction type to view. Later
* it will support multiple selection, but until the API supports that it is * it will support multiple selection, but until the API supports that it is
* one or all. * one or all.
* @param filters null or Set of tranaction types * @param filters null or Set of transaction types
* @param setFilters A function to update the filters prop * @param setFilters A function to update the filters prop
* @returns * @returns
*/ */
@@ -100,15 +100,15 @@ export const TxsFilter = ({ filters, setFilters }: TxFilterProps) => {
<DropdownMenu <DropdownMenu
modal={false} modal={false}
trigger={ trigger={
<DropdownMenuTrigger className="ml-2"> <DropdownMenuTrigger className="ml-0">
<Button size="xs"> <Button size="xs" data-testid="filter-trigger">
<FilterLabel filters={filters} /> <FilterLabel filters={filters} />
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
} }
> >
<DropdownMenuContent> <DropdownMenuContent>
{filters.size > 1 ? null : ( {filters.size > 0 ? null : (
<> <>
<DropdownMenuCheckboxItem <DropdownMenuCheckboxItem
onCheckedChange={() => setFilters(new Set(AllFilterOptions))} onCheckedChange={() => setFilters(new Set(AllFilterOptions))}
@@ -0,0 +1,113 @@
import { render, screen, fireEvent } from '@testing-library/react';
import { TxsListNavigation } from './tx-list-navigation';
const NOOP = () => {
return;
};
describe('TxsListNavigation', () => {
it('renders transaction list navigation', () => {
render(
<TxsListNavigation
refreshTxs={NOOP}
nextPage={NOOP}
previousPage={NOOP}
hasMoreTxs={true}
hasPreviousPage={true}
>
<span></span>
</TxsListNavigation>
);
expect(screen.getByText('Newer')).toBeInTheDocument();
expect(screen.getByText('Older')).toBeInTheDocument();
});
it('calls previousPage when "Newer" button is clicked', () => {
const previousPageMock = jest.fn();
render(
<TxsListNavigation
refreshTxs={NOOP}
nextPage={NOOP}
previousPage={previousPageMock}
hasMoreTxs={true}
hasPreviousPage={true}
>
<span></span>
</TxsListNavigation>
);
fireEvent.click(screen.getByText('Newer'));
expect(previousPageMock).toHaveBeenCalledTimes(1);
});
it('calls nextPage when "Older" button is clicked', () => {
const nextPageMock = jest.fn();
render(
<TxsListNavigation
refreshTxs={NOOP}
nextPage={nextPageMock}
previousPage={NOOP}
hasMoreTxs={true}
hasPreviousPage={true}
>
<span></span>
</TxsListNavigation>
);
fireEvent.click(screen.getByText('Older'));
expect(nextPageMock).toHaveBeenCalledTimes(1);
});
it('disables "Older" button if hasMoreTxs is false', () => {
render(
<TxsListNavigation
refreshTxs={NOOP}
nextPage={NOOP}
previousPage={NOOP}
hasMoreTxs={false}
hasPreviousPage={false}
>
<span></span>
</TxsListNavigation>
);
expect(screen.getByText('Older')).toBeDisabled();
});
it('disables "Newer" button if hasPreviousPage is false', () => {
render(
<TxsListNavigation
refreshTxs={NOOP}
nextPage={NOOP}
previousPage={NOOP}
hasMoreTxs={true}
hasPreviousPage={false}
>
<span></span>
</TxsListNavigation>
);
expect(screen.getByText('Newer')).toBeDisabled();
});
it('disables both buttons when more and previous are false', () => {
render(
<TxsListNavigation
refreshTxs={NOOP}
nextPage={NOOP}
previousPage={NOOP}
hasMoreTxs={false}
hasPreviousPage={false}
>
<span></span>
</TxsListNavigation>
);
expect(screen.getByText('Newer')).toBeDisabled();
expect(screen.getByText('Older')).toBeDisabled();
});
});
@@ -0,0 +1,63 @@
import { t } from '@vegaprotocol/i18n';
import { BlocksRefetch } from '../blocks';
import { Button } from '@vegaprotocol/ui-toolkit';
export interface TxListNavigationProps {
refreshTxs: () => void;
nextPage: () => void;
previousPage: () => void;
loading?: boolean;
hasPreviousPage: boolean;
hasMoreTxs: boolean;
children: React.ReactNode;
}
/**
* Displays a list of transactions with filters and controls to navigate through the list.
*
* @returns {JSX.Element} Transaction List and controls
*/
export const TxsListNavigation = ({
refreshTxs,
nextPage,
previousPage,
hasMoreTxs,
hasPreviousPage,
children,
loading = false,
}: TxListNavigationProps) => {
return (
<>
<menu className="mb-2 w-full ">{children}</menu>
<menu className="mb-2 w-full">
<BlocksRefetch refetch={refreshTxs} />
<div className="float-right">
<Button
className="mr-2"
size="xs"
disabled={!hasPreviousPage || loading}
onClick={() => {
previousPage();
}}
>
{t('Newer')}
</Button>
<Button
size="xs"
disabled={!hasMoreTxs || loading}
onClick={() => {
nextPage();
}}
>
{t('Older')}
</Button>
</div>
<div className="float-right mr-2">
{loading ? (
<span className="text-vega-light-300">{t('Loading...')}</span>
) : null}
</div>
</menu>
</>
);
};
@@ -24,6 +24,7 @@ const displayString: StringMap = {
LiquidityProvisionSubmission: 'LP order', LiquidityProvisionSubmission: 'LP order',
'Liquidity Provision Order': 'LP order', 'Liquidity Provision Order': 'LP order',
LiquidityProvisionCancellation: 'LP cancel', LiquidityProvisionCancellation: 'LP cancel',
'Cancel LiquidityProvision Order': 'LP cancel',
LiquidityProvisionAmendment: 'LP update', LiquidityProvisionAmendment: 'LP update',
'Amend LiquidityProvision Order': 'Amend LP', 'Amend LiquidityProvision Order': 'Amend LP',
ProposalSubmission: 'Governance Proposal', ProposalSubmission: 'Governance Proposal',
@@ -36,9 +37,12 @@ const displayString: StringMap = {
UndelegateSubmission: 'Undelegation', UndelegateSubmission: 'Undelegation',
KeyRotateSubmission: 'Key Rotation', KeyRotateSubmission: 'Key Rotation',
StateVariableProposal: 'State Variable', StateVariableProposal: 'State Variable',
'State Variable Proposal': 'State Variable',
Transfer: 'Transfer', Transfer: 'Transfer',
CancelTransfer: 'Cancel Transfer', CancelTransfer: 'Cancel Transfer',
'Cancel Transfer Funds': 'Cancel Transfer',
ValidatorHeartbeat: 'Heartbeat', ValidatorHeartbeat: 'Heartbeat',
'Validator Heartbeat': 'Heartbeat',
'Batch Market Instructions': 'Batch', 'Batch Market Instructions': 'Batch',
}; };
@@ -172,7 +176,7 @@ export const TxOrderType = ({ orderType, command }: TxOrderTypeProps) => {
return ( return (
<div <div
data-testid="tx-type" data-testid="tx-type"
className={`text-sm rounded-md leading-none px-2 py-2 inline-block ${colours}`} className={`text-sm rounded-md leading-tight px-2 inline-block whitespace-nowrap ${colours}`}
> >
{type} {type}
</div> </div>
@@ -1,3 +1,4 @@
import { MockedProvider } from '@apollo/client/testing';
import { TxsInfiniteListItem } from './txs-infinite-list-item'; import { TxsInfiniteListItem } from './txs-infinite-list-item';
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom'; import { MemoryRouter } from 'react-router-dom';
@@ -83,21 +84,22 @@ describe('Txs infinite list item', () => {
it('renders data correctly', () => { it('renders data correctly', () => {
render( render(
<MemoryRouter> <MockedProvider>
<TxsInfiniteListItem <MemoryRouter>
type="testType" <TxsInfiniteListItem
submitter="testPubKey" type="testType"
hash="testTxHash" submitter="testPubKey"
block="1" hash="testTxHash"
code={0} block="1"
command={{}} code={0}
/> command={{}}
</MemoryRouter> />
</MemoryRouter>
</MockedProvider>
); );
expect(screen.getByTestId('tx-hash')).toHaveTextContent('testTxHash'); expect(screen.getByTestId('tx-hash')).toHaveTextContent('testTxHash');
expect(screen.getByTestId('pub-key')).toHaveTextContent('testPubKey'); expect(screen.getByTestId('pub-key')).toHaveTextContent('testPubKey');
expect(screen.getByTestId('tx-type')).toHaveTextContent('testType'); expect(screen.getByTestId('tx-type')).toHaveTextContent('testType');
expect(screen.getByTestId('tx-block')).toHaveTextContent('1'); expect(screen.getByTestId('tx-block')).toHaveTextContent('1');
expect(screen.getByTestId('tx-success')).toHaveTextContent('Success');
}); });
}); });
@@ -1,4 +1,3 @@
import React from 'react';
import { TruncatedLink } from '../truncate/truncated-link'; import { TruncatedLink } from '../truncate/truncated-link';
import { Routes } from '../../routes/route-names'; import { Routes } from '../../routes/route-names';
import { TxOrderType } from './tx-order-type'; import { TxOrderType } from './tx-order-type';
@@ -6,8 +5,25 @@ import type { BlockExplorerTransactionResult } from '../../routes/types/block-ex
import { toHex } from '../search/detect-search'; import { toHex } from '../search/detect-search';
import { ChainResponseCode } from './details/chain-response-code/chain-reponse.code'; import { ChainResponseCode } from './details/chain-response-code/chain-reponse.code';
import isNumber from 'lodash/isNumber'; import isNumber from 'lodash/isNumber';
import { PartyLink } from '../links';
import { useScreenDimensions } from '@vegaprotocol/react-helpers';
import type { Screen } from '@vegaprotocol/react-helpers';
import { useMemo } from 'react';
const TRUNCATE_LENGTH = 10; const DEFAULT_TRUNCATE_LENGTH = 7;
export function getIdTruncateLength(screen: Screen): number {
if (['xxxl', 'xxl'].includes(screen)) {
return 64;
} else if (['xl', 'lg', 'md'].includes(screen)) {
return 32;
}
return DEFAULT_TRUNCATE_LENGTH;
}
export function shouldTruncateParty(screen: Screen): boolean {
return !['xxxl', 'xxl', 'xl'].includes(screen);
}
export const TxsInfiniteListItem = ({ export const TxsInfiniteListItem = ({
hash, hash,
@@ -17,6 +33,12 @@ export const TxsInfiniteListItem = ({
block, block,
command, command,
}: Partial<BlockExplorerTransactionResult>) => { }: Partial<BlockExplorerTransactionResult>) => {
const { screenSize } = useScreenDimensions();
const idTruncateLength = useMemo(
() => getIdTruncateLength(screenSize),
[screenSize]
);
if ( if (
!hash || !hash ||
!submitter || !submitter ||
@@ -29,68 +51,40 @@ export const TxsInfiniteListItem = ({
} }
return ( return (
<div <tr
data-testid="transaction-row" data-testid="transaction-row"
className="flex items-center h-full border-t border-neutral-600 dark:border-neutral-800 txs-infinite-list-item grid grid-cols-10" className="transaction-row text-left items-center h-full border-t border-neutral-600 dark:border-neutral-800 txs-infinite-list-item py-[2px]"
> >
<div <td
className="text-sm col-span-10 md:col-span-3 leading-none" className="text-sm leading-none whitespace-nowrap font-mono"
data-testid="tx-hash" data-testid="tx-hash"
> >
<span className="md:hidden uppercase text-vega-dark-300">
ID:&nbsp;
</span>
<TruncatedLink
to={`/${Routes.TX}/${toHex(hash)}`}
text={hash}
startChars={TRUNCATE_LENGTH}
endChars={TRUNCATE_LENGTH}
/>
</div>
<div
className="text-sm col-span-10 md:col-span-3 leading-none"
data-testid="pub-key"
>
<span className="md:hidden uppercase text-vega-dark-300">
By:&nbsp;
</span>
<TruncatedLink
to={`/${Routes.PARTIES}/${submitter}`}
text={submitter}
startChars={TRUNCATE_LENGTH}
endChars={TRUNCATE_LENGTH}
/>
</div>
<div className="text-sm col-span-5 md:col-span-2 leading-none flex items-center">
<TxOrderType orderType={type} command={command} />
</div>
<div
className="text-sm col-span-3 md:col-span-1 leading-none flex items-center"
data-testid="tx-block"
>
<span className="md:hidden uppercase text-vega-dark-300">
Block:&nbsp;
</span>
<TruncatedLink
to={`/${Routes.BLOCKS}/${block}`}
text={block}
startChars={TRUNCATE_LENGTH}
endChars={TRUNCATE_LENGTH}
/>
</div>
<div
className="text-sm col-span-2 md:col-span-1 leading-none flex items-center"
data-testid="tx-success"
>
<span className="md:hidden uppercase text-vega-dark-300">
Success&nbsp;
</span>
{isNumber(code) ? ( {isNumber(code) ? (
<ChainResponseCode code={code} hideLabel={true} /> <ChainResponseCode code={code} hideLabel={true} hideIfOk={true} />
) : ( ) : (
code code
)} )}
</div> <TruncatedLink
</div> to={`/${Routes.TX}/${toHex(hash)}`}
text={hash}
startChars={idTruncateLength}
endChars={0}
/>
</td>
<td className="text-sm leading-none">
<TxOrderType orderType={type} command={command} />
</td>
<td className="text-sm leading-none" data-testid="pub-key">
<PartyLink truncate={shouldTruncateParty(screenSize)} id={submitter} />
</td>
<td className="text-sm items-center font-mono" data-testid="tx-block">
<TruncatedLink
to={`/${Routes.BLOCKS}/${block}`}
text={block}
startChars={5}
endChars={5}
/>
</td>
</tr>
); );
}; };
@@ -1,8 +1,9 @@
import { TxsInfiniteList } from './txs-infinite-list'; import { TxsInfiniteList } from './txs-infinite-list';
import { render, screen, fireEvent, act } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom'; import { MemoryRouter } from 'react-router-dom';
import type { BlockExplorerTransactionResult } from '../../routes/types/block-explorer-response'; import type { BlockExplorerTransactionResult } from '../../routes/types/block-explorer-response';
import { Side } from '@vegaprotocol/types'; import { Side } from '@vegaprotocol/types';
import { MockedProvider } from '@apollo/client/testing';
const generateTxs = (number: number): BlockExplorerTransactionResult[] => { const generateTxs = (number: number): BlockExplorerTransactionResult[] => {
return Array.from(Array(number)).map((_) => ({ return Array.from(Array(number)).map((_) => ({
@@ -40,7 +41,7 @@ describe('Txs infinite list', () => {
it('should display a "no items" message when no items provided', () => { it('should display a "no items" message when no items provided', () => {
render( render(
<TxsInfiniteList <TxsInfiniteList
txs={undefined} txs={undefined as unknown as BlockExplorerTransactionResult[]}
areTxsLoading={false} areTxsLoading={false}
hasMoreTxs={false} hasMoreTxs={false}
loadMoreTxs={() => null} loadMoreTxs={() => null}
@@ -48,23 +49,7 @@ describe('Txs infinite list', () => {
/> />
); );
expect(screen.getByTestId('emptylist')).toBeInTheDocument(); expect(screen.getByTestId('emptylist')).toBeInTheDocument();
expect( expect(screen.getByText('No transactions found')).toBeInTheDocument();
screen.getByText('This chain has 0 transactions')
).toBeInTheDocument();
});
it('error is displayed at item level', () => {
const txs = generateTxs(1);
render(
<TxsInfiniteList
txs={txs}
areTxsLoading={false}
hasMoreTxs={false}
loadMoreTxs={() => null}
error={Error('test error!')}
/>
);
expect(screen.getByText('Cannot fetch transaction')).toBeInTheDocument();
}); });
it('item renders data of n length into list of n length', () => { it('item renders data of n length into list of n length', () => {
@@ -73,85 +58,22 @@ describe('Txs infinite list', () => {
const txs = generateTxs(7); const txs = generateTxs(7);
render( render(
<MemoryRouter> <MemoryRouter>
<TxsInfiniteList <MockedProvider>
txs={txs} <TxsInfiniteList
areTxsLoading={false} txs={txs}
hasMoreTxs={false} areTxsLoading={false}
loadMoreTxs={() => null} hasMoreTxs={false}
error={undefined} loadMoreTxs={() => null}
/> error={undefined}
/>
</MockedProvider>
</MemoryRouter> </MemoryRouter>
); );
expect( expect(
screen screen
.getByTestId('infinite-scroll-wrapper') .getByTestId('transactions-list')
.querySelectorAll('.txs-infinite-list-item') .querySelectorAll('.transaction-row')
).toHaveLength(7); ).toHaveLength(7);
}); });
it('tries to load more items when required to initially fill the list', () => {
// For example, if initially rendering 15, the bottom of the list is
// in view of the viewport, and the callback should be executed
const txs = generateTxs(15);
const callback = jest.fn();
render(
<MemoryRouter>
<TxsInfiniteList
txs={txs}
areTxsLoading={false}
hasMoreTxs={true}
loadMoreTxs={callback}
error={undefined}
/>
</MemoryRouter>
);
expect(callback.mock.calls.length).toEqual(1);
});
it('does not try to load more items if there are no more', () => {
const txs = generateTxs(3);
const callback = jest.fn();
render(
<MemoryRouter>
<TxsInfiniteList
txs={txs}
areTxsLoading={false}
hasMoreTxs={false}
loadMoreTxs={callback}
error={undefined}
/>
</MemoryRouter>
);
expect(callback.mock.calls.length).toEqual(0);
});
it('loads more items is called when scrolled', () => {
const txs = generateTxs(14);
const callback = jest.fn();
render(
<MemoryRouter>
<TxsInfiniteList
txs={txs}
areTxsLoading={false}
hasMoreTxs={true}
loadMoreTxs={callback}
error={undefined}
/>
</MemoryRouter>
);
act(() => {
fireEvent.scroll(screen.getByTestId('infinite-scroll-wrapper'), {
target: { scrollY: 2000 },
});
});
expect(callback.mock.calls.length).toEqual(1);
});
}); });
@@ -1,8 +1,4 @@
import React, { useEffect, useRef } from 'react';
import { FixedSizeList as List } from 'react-window';
import InfiniteLoader from 'react-window-infinite-loader';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { useScreenDimensions } from '@vegaprotocol/react-helpers';
import { TxsInfiniteListItem } from './txs-infinite-list-item'; import { TxsInfiniteListItem } from './txs-infinite-list-item';
import type { BlockExplorerTransactionResult } from '../../routes/types/block-explorer-response'; import type { BlockExplorerTransactionResult } from '../../routes/types/block-explorer-response';
import EmptyList from '../empty-list/empty-list'; import EmptyList from '../empty-list/empty-list';
@@ -11,82 +7,46 @@ import { Loader } from '@vegaprotocol/ui-toolkit';
interface TxsInfiniteListProps { interface TxsInfiniteListProps {
hasMoreTxs: boolean; hasMoreTxs: boolean;
areTxsLoading: boolean | undefined; areTxsLoading: boolean | undefined;
txs: BlockExplorerTransactionResult[] | undefined; txs: BlockExplorerTransactionResult[];
loadMoreTxs: () => void; loadMoreTxs: () => void;
error: Error | undefined; error: Error | undefined;
className?: string; className?: string;
hasFilters?: boolean;
} }
interface ItemProps { interface ItemProps {
index: BlockExplorerTransactionResult; tx: BlockExplorerTransactionResult;
style: React.CSSProperties;
isLoading: boolean;
error: Error | undefined;
} }
// eslint-disable-next-line @typescript-eslint/no-empty-function const Item = ({ tx }: ItemProps) => {
const NOOP = () => {}; const { hash, submitter, type, command, block, code, index: blockIndex } = tx;
return (
const Item = ({ index, style, isLoading, error }: ItemProps) => { <TxsInfiniteListItem
let content; type={type}
if (error) { code={code}
content = t(`Cannot fetch transaction`); command={command}
} else if (isLoading) { submitter={submitter}
content = <Loader />; hash={hash}
} else { block={block}
const { index={blockIndex}
hash, />
submitter, );
type,
command,
block,
code,
index: blockIndex,
} = index;
content = (
<TxsInfiniteListItem
type={type}
code={code}
command={command}
submitter={submitter}
hash={hash}
block={block}
index={blockIndex}
/>
);
}
return <div style={style}>{content}</div>;
}; };
export const TxsInfiniteList = ({ export const TxsInfiniteList = ({
hasMoreTxs,
areTxsLoading, areTxsLoading,
txs, txs,
loadMoreTxs,
error,
className, className,
hasFilters = false,
}: TxsInfiniteListProps) => { }: TxsInfiniteListProps) => {
const { screenSize } = useScreenDimensions(); if (!txs || txs.length === 0) {
const isStacked = ['xs', 'sm'].includes(screenSize);
const infiniteLoaderRef = useRef<InfiniteLoader>(null);
const hasMountedRef = useRef(false);
useEffect(() => {
if (hasMountedRef.current) {
if (infiniteLoaderRef.current) {
infiniteLoaderRef.current.resetloadMoreItemsCache(true);
}
}
hasMountedRef.current = true;
}, [loadMoreTxs]);
if (!txs) {
if (!areTxsLoading) { if (!areTxsLoading) {
return ( return (
<EmptyList <EmptyList
heading={t('This chain has 0 transactions')} heading={t('No transactions found')}
label={t('Check back soon')} label={
hasFilters ? t('Try a different filter') : t('Check back soon')
}
/> />
); );
} else { } else {
@@ -94,57 +54,26 @@ export const TxsInfiniteList = ({
} }
} }
// If there are more items to be loaded then add an extra row to hold a loading indicator.
const itemCount = hasMoreTxs ? txs.length + 1 : txs.length;
// Pass an empty callback to InfiniteLoader in case it asks us to load more than once.
// eslint-disable-next-line @typescript-eslint/no-empty-function
const loadMoreItems = areTxsLoading ? NOOP : loadMoreTxs;
// Every row is loaded except for our loading indicator row.
const isItemLoaded = (index: number) => !hasMoreTxs || index < txs.length;
return ( return (
<div className={className} data-testid="transactions-list"> <div className="overflow-scroll">
<div className="lg:grid grid-cols-10 w-full mb-3 hidden text-vega-dark-300 uppercase"> <table className={className} data-testid="transactions-list">
<div className="col-span-3"> <thead>
<span className="hidden xl:inline">{t('Transaction')} &nbsp;</span> <tr className="w-full mb-3 text-vega-dark-300 uppercase text-left">
<span>ID</span> <th>
</div> <span className="hidden xl:inline">{t('Txn')} &nbsp;</span>
<div className="col-span-3">{t('Submitted By')}</div> <span>ID</span>
<div className="col-span-2">{t('Type')}</div> </th>
<div className="col-span-1">{t('Block')}</div> <th>{t('Type')}</th>
<div className="col-span-1">{t('Success')}</div> <th className="text-left">{t('From')}</th>
</div> <th>{t('Block')}</th>
<div data-testid="infinite-scroll-wrapper"> </tr>
<InfiniteLoader </thead>
isItemLoaded={isItemLoaded} <tbody>
itemCount={itemCount} {txs.map((t) => (
loadMoreItems={loadMoreItems} <Item key={t.hash} tx={t} />
ref={infiniteLoaderRef} ))}
> </tbody>
{({ onItemsRendered, ref }) => ( </table>
<List
className="List"
height={995}
itemCount={itemCount}
itemSize={isStacked ? 134 : 50}
onItemsRendered={onItemsRendered}
ref={ref}
width={'100%'}
>
{({ index, style }) => (
<Item
index={txs[index]}
style={style}
isLoading={!isItemLoaded(index)}
error={error}
/>
)}
</List>
)}
</InfiniteLoader>
</div>
</div> </div>
); );
}; };
@@ -1,23 +1,17 @@
import { Routes } from '../../routes/route-names'; import { Table, TableRow } from '../table';
import { TruncatedLink } from '../truncate/truncated-link';
import { TxOrderType } from './tx-order-type';
import { Table, TableRow, TableCell } from '../table';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { useFetch } from '@vegaprotocol/react-helpers'; import { useFetch } from '@vegaprotocol/react-helpers';
import type { BlockExplorerTransactions } from '../../routes/types/block-explorer-response'; import type { BlockExplorerTransactions } from '../../routes/types/block-explorer-response';
import isNumber from 'lodash/isNumber';
import { ChainResponseCode } from './details/chain-response-code/chain-reponse.code';
import { getTxsDataUrl } from '../../hooks/use-txs-data'; import { getTxsDataUrl } from '../../hooks/use-txs-data';
import { AsyncRenderer, Loader } from '@vegaprotocol/ui-toolkit'; import { AsyncRenderer, Loader } from '@vegaprotocol/ui-toolkit';
import EmptyList from '../empty-list/empty-list'; import EmptyList from '../empty-list/empty-list';
import { TxsInfiniteListItem } from './txs-infinite-list-item';
interface TxsPerBlockProps { interface TxsPerBlockProps {
blockHeight: string; blockHeight: string;
txCount: number; txCount: number;
} }
const truncateLength = 5;
export const TxsPerBlock = ({ blockHeight, txCount }: TxsPerBlockProps) => { export const TxsPerBlock = ({ blockHeight, txCount }: TxsPerBlockProps) => {
const filters = `filters[block.height]=${blockHeight}`; const filters = `filters[block.height]=${blockHeight}`;
const url = getTxsDataUrl({ limit: txCount.toString(), filters }); const url = getTxsDataUrl({ limit: txCount.toString(), filters });
@@ -33,53 +27,23 @@ export const TxsPerBlock = ({ blockHeight, txCount }: TxsPerBlockProps) => {
<thead> <thead>
<TableRow modifier="bordered" className="font-mono"> <TableRow modifier="bordered" className="font-mono">
<td>{t('Transaction')}</td> <td>{t('Transaction')}</td>
<td>{t('From')}</td>
<td>{t('Type')}</td> <td>{t('Type')}</td>
<td>{t('Status')}</td> <td>{t('From')}</td>
<td>{t('Block')}</td>
</TableRow> </TableRow>
</thead> </thead>
<tbody> <tbody>
{data.transactions.map( {data.transactions.map(
({ hash, submitter, type, command, code }) => { ({ hash, submitter, type, command, code, block }) => {
return ( return (
<TableRow <TxsInfiniteListItem
modifier="bordered" block={block}
key={hash} hash={hash}
data-testid="transaction-row" submitter={submitter}
> type={type}
<TableCell command={command}
modifier="bordered" code={code}
className="pr-12 font-mono" />
>
<TruncatedLink
to={`/${Routes.TX}/${hash}`}
text={hash}
startChars={truncateLength}
endChars={truncateLength}
/>
</TableCell>
<TableCell
modifier="bordered"
className="pr-12 font-mono"
>
<TruncatedLink
to={`/${Routes.PARTIES}/${submitter}`}
text={submitter}
startChars={truncateLength}
endChars={truncateLength}
/>
</TableCell>
<TableCell modifier="bordered">
<TxOrderType orderType={type} command={command} />
</TableCell>
<TableCell modifier="bordered" className="text">
{isNumber(code) ? (
<ChainResponseCode code={code} hideLabel={true} />
) : (
code
)}
</TableCell>
</TableRow>
); );
} }
)} )}
@@ -56,10 +56,14 @@ export function VoteIcon({
return ( return (
<div <div
className={`voteicon inline-block my-1 py-1 px-2 py rounded-md text-white leading-one sm align-top ${bg}`} className={`voteicon inline-block py-0 px-2 py rounded-md text-white whitespace-nowrap leading-tight sm align-top ${bg}`}
> >
<Icon name={icon} size={3} className={`mr-2 p-0 fill-${fill}`} /> <Icon
<span className={`text-base text-${text}`} data-testid="label"> name={icon}
size={3}
className={`mr-2 p-0 mb-[-1px] fill-${fill}`}
/>
<span className={`text-${text}`} data-testid="label">
{label} {label}
</span> </span>
</div> </div>
+70 -42
View File
@@ -10,16 +10,18 @@ import isNumber from 'lodash/isNumber';
export interface TxsStateProps { export interface TxsStateProps {
txsData: BlockExplorerTransactionResult[]; txsData: BlockExplorerTransactionResult[];
hasMoreTxs: boolean; hasMoreTxs: boolean;
lastCursor: string; cursor: string;
previousCursors: string[];
hasPreviousPage: boolean;
} }
export interface IUseTxsData { export interface IUseTxsData {
limit?: number; limit: number;
filters?: string; filters?: string;
} }
interface IGetTxsDataUrl { interface IGetTxsDataUrl {
limit?: string; limit: string;
filters?: string; filters?: string;
} }
@@ -40,63 +42,89 @@ export const getTxsDataUrl = ({ limit, filters }: IGetTxsDataUrl) => {
}; };
export const useTxsData = ({ limit, filters }: IUseTxsData) => { export const useTxsData = ({ limit, filters }: IUseTxsData) => {
const [{ txsData, hasMoreTxs, lastCursor }, setTxsState] = const [
useState<TxsStateProps>({ { txsData, hasMoreTxs, cursor, previousCursors, hasPreviousPage },
txsData: [], setTxsState,
hasMoreTxs: true, ] = useState<TxsStateProps>({
lastCursor: '', txsData: [],
}); hasMoreTxs: false,
previousCursors: [],
cursor: '',
hasPreviousPage: false,
});
const url = getTxsDataUrl({ limit: limit?.toString(), filters }); const url = getTxsDataUrl({ limit: limit.toString(), filters });
const { const {
state: { data, error, loading }, state: { data, error, loading },
refetch, refetch,
} = useFetch<BlockExplorerTransactions>(url, {}, false); } = useFetch<BlockExplorerTransactions>(url, {}, true);
useEffect(() => { useEffect(() => {
if (data && isNumber(data?.transactions?.length)) { if (!loading && data && isNumber(data.transactions.length)) {
setTxsState((prev) => ({ setTxsState((prev) => {
txsData: [...prev.txsData, ...data.transactions], return {
hasMoreTxs: data.transactions.length > 0, ...prev,
lastCursor: txsData: data.transactions,
data.transactions[data.transactions.length - 1]?.cursor || '', hasMoreTxs: data.transactions.length >= limit,
})); cursor: data?.transactions.at(-1)?.cursor || '',
};
});
} }
}, [setTxsState, data]); }, [loading, setTxsState, data, limit]);
useEffect(() => { const nextPage = useCallback(() => {
setTxsState((prev) => ({ const c = data?.transactions.at(0)?.cursor;
txsData: [], const newPreviousCursors = c ? [...previousCursors, c] : previousCursors;
hasMoreTxs: true,
lastCursor: '',
}));
}, [filters]);
const loadTxs = useCallback(() => {
return refetch({
limit: limit,
before: lastCursor,
});
}, [lastCursor, limit, refetch]);
const refreshTxs = useCallback(async () => {
setTxsState((prev) => ({ setTxsState((prev) => ({
...prev, ...prev,
lastCursor: '', hasPreviousPage: true,
hasMoreTxs: true, previousCursors: newPreviousCursors,
txsData: [],
})); }));
}, [setTxsState]);
return refetch({
limit,
before: cursor,
});
}, [data, previousCursors, cursor, limit, refetch]);
const previousPage = useCallback(() => {
const previousCursor = [...previousCursors].pop();
const newPreviousCursors = previousCursors.slice(0, -1);
setTxsState((prev) => ({
...prev,
hasPreviousPage: newPreviousCursors.length > 0,
previousCursors: newPreviousCursors,
}));
return refetch({
limit,
before: previousCursor,
});
}, [previousCursors, limit, refetch]);
const refreshTxs = useCallback(async () => {
setTxsState(() => ({
txsData: [],
cursor: '',
previousCursors: [],
hasMoreTxs: false,
hasPreviousPage: false,
}));
refetch({ limit });
}, [setTxsState, limit, refetch, filters]); // eslint-disable-line react-hooks/exhaustive-deps
return { return {
data, txsData,
loading, loading,
error, error,
txsData,
hasMoreTxs, hasMoreTxs,
lastCursor, hasPreviousPage,
previousCursors,
cursor,
refreshTxs, refreshTxs,
loadTxs, nextPage,
previousPage,
}; };
}; };
+3 -1
View File
@@ -21,6 +21,7 @@ import {
import { Footer } from '../components/footer/footer'; import { Footer } from '../components/footer/footer';
import { Header } from '../components/header'; import { Header } from '../components/header';
import { Routes } from './route-names'; import { Routes } from './route-names';
import { useExplorerNodeNamesLazyQuery } from './validators/__generated__/NodeNames';
const DialogsContainer = () => { const DialogsContainer = () => {
const { isOpen, id, trigger, asJson, setOpen } = useAssetDetailsDialogStore(); const { isOpen, id, trigger, asJson, setOpen } = useAssetDetailsDialogStore();
@@ -39,6 +40,7 @@ export const Layout = () => {
const isHome = Boolean(useMatch(Routes.HOME)); const isHome = Boolean(useMatch(Routes.HOME));
const { ANNOUNCEMENTS_CONFIG_URL } = useEnvironment(); const { ANNOUNCEMENTS_CONFIG_URL } = useEnvironment();
const fixedWidthClasses = 'w-full max-w-[1500px] mx-auto'; const fixedWidthClasses = 'w-full max-w-[1500px] mx-auto';
useExplorerNodeNamesLazyQuery();
return ( return (
<> <>
@@ -49,7 +51,7 @@ export const Layout = () => {
'grid grid-rows-[auto_1fr_auto] grid-cols-1', 'grid grid-rows-[auto_1fr_auto] grid-cols-1',
'border-vega-light-200 dark:border-vega-dark-200', 'border-vega-light-200 dark:border-vega-dark-200',
'antialiased text-black dark:text-white', 'antialiased text-black dark:text-white',
'overflow-hidden relative' 'relative'
)} )}
> >
<div> <div>
@@ -2,12 +2,15 @@ import { render } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom'; import { MemoryRouter } from 'react-router-dom';
import type { SourceType } from './oracle'; import type { SourceType } from './oracle';
import { OracleSigners } from './oracle-signers'; import { OracleSigners } from './oracle-signers';
import { MockedProvider } from '@apollo/client/testing';
function renderComponent(sourceType: SourceType) { function renderComponent(sourceType: SourceType) {
return ( return (
<MemoryRouter> <MockedProvider>
<OracleSigners sourceType={sourceType} /> <MemoryRouter>
</MemoryRouter> <OracleSigners sourceType={sourceType} />
</MemoryRouter>
</MockedProvider>
); );
} }
@@ -1,6 +1,6 @@
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { useScreenDimensions } from '@vegaprotocol/react-helpers'; import { useScreenDimensions } from '@vegaprotocol/react-helpers';
import { useMemo } from 'react'; import { useMemo, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
import { SubHeading } from '../../../components/sub-heading'; import { SubHeading } from '../../../components/sub-heading';
import { toNonHex } from '../../../components/search/detect-search'; import { toNonHex } from '../../../components/search/detect-search';
@@ -14,8 +14,11 @@ import { PartyBlockStake } from './components/party-block-stake';
import { PartyBlockAccounts } from './components/party-block-accounts'; import { PartyBlockAccounts } from './components/party-block-accounts';
import { isValidPartyId } from './components/party-id-error'; import { isValidPartyId } from './components/party-id-error';
import { useDataProvider } from '@vegaprotocol/data-provider'; import { useDataProvider } from '@vegaprotocol/data-provider';
import { TxsListNavigation } from '../../../components/txs/tx-list-navigation';
import { AllFilterOptions, TxsFilter } from '../../../components/txs/tx-filter';
const Party = () => { const Party = () => {
const [filters, setFilters] = useState(new Set(AllFilterOptions));
const { party } = useParams<{ party: string }>(); const { party } = useParams<{ party: string }>();
useDocumentTitle(['Public keys', party || '-']); useDocumentTitle(['Public keys', party || '-']);
@@ -24,10 +27,24 @@ const Party = () => {
const partyId = toNonHex(party ? party : ''); const partyId = toNonHex(party ? party : '');
const { isMobile } = useScreenDimensions(); const { isMobile } = useScreenDimensions();
const visibleChars = useMemo(() => (isMobile ? 10 : 14), [isMobile]); const visibleChars = useMemo(() => (isMobile ? 10 : 14), [isMobile]);
const filters = `filters[tx.submitter]=${partyId}`; const baseFilters = `filters[tx.submitter]=${partyId}`;
const { hasMoreTxs, loadTxs, error, txsData, loading } = useTxsData({ const f =
limit: 10, filters && filters.size === 1
filters, ? `${baseFilters}&filters[cmd.type]=${Array.from(filters)[0]}`
: baseFilters;
const {
hasMoreTxs,
nextPage,
previousPage,
error,
refreshTxs,
loading,
txsData,
hasPreviousPage,
} = useTxsData({
limit: 25,
filters: f,
}); });
const variables = useMemo(() => ({ partyId }), [partyId]); const variables = useMemo(() => ({ partyId }), [partyId]);
@@ -81,14 +98,24 @@ const Party = () => {
</div> </div>
<SubHeading>{t('Transactions')}</SubHeading> <SubHeading>{t('Transactions')}</SubHeading>
<TxsListNavigation
refreshTxs={refreshTxs}
nextPage={nextPage}
previousPage={previousPage}
hasPreviousPage={hasPreviousPage}
loading={loading}
hasMoreTxs={hasMoreTxs}
>
<TxsFilter filters={filters} setFilters={setFilters} />
</TxsListNavigation>
{!error && txsData ? ( {!error && txsData ? (
<TxsInfiniteList <TxsInfiniteList
hasMoreTxs={hasMoreTxs} hasMoreTxs={hasMoreTxs}
areTxsLoading={loading} areTxsLoading={loading}
txs={txsData} txs={txsData}
loadMoreTxs={loadTxs} loadMoreTxs={nextPage}
error={error} error={error}
className="mb-28" className="mb-28 w-full"
/> />
) : ( ) : (
<Splash> <Splash>
+32 -13
View File
@@ -1,14 +1,14 @@
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { RouteTitle } from '../../../components/route-title'; import { RouteTitle } from '../../../components/route-title';
import { BlocksRefetch } from '../../../components/blocks';
import { TxsInfiniteList } from '../../../components/txs'; import { TxsInfiniteList } from '../../../components/txs';
import { useTxsData } from '../../../hooks/use-txs-data'; import { useTxsData } from '../../../hooks/use-txs-data';
import { useDocumentTitle } from '../../../hooks/use-document-title'; import { useDocumentTitle } from '../../../hooks/use-document-title';
import { useState } from 'react'; import { useState } from 'react';
import { AllFilterOptions, TxsFilter } from '../../../components/txs/tx-filter'; import { AllFilterOptions, TxsFilter } from '../../../components/txs/tx-filter';
import { TxsListNavigation } from '../../../components/txs/tx-list-navigation';
const BE_TXS_PER_REQUEST = 15; const BE_TXS_PER_REQUEST = 25;
export const TxsList = () => { export const TxsList = () => {
useDocumentTitle(['Transactions']); useDocumentTitle(['Transactions']);
@@ -21,6 +21,11 @@ export const TxsList = () => {
); );
}; };
/**
* Displays a list of transactions with filters and controls to navigate through the list.
*
* @returns {JSX.Element} Transaction List and controls
*/
export const TxsListFiltered = () => { export const TxsListFiltered = () => {
const [filters, setFilters] = useState(new Set(AllFilterOptions)); const [filters, setFilters] = useState(new Set(AllFilterOptions));
@@ -29,26 +34,40 @@ export const TxsListFiltered = () => {
? `filters[cmd.type]=${Array.from(filters)[0]}` ? `filters[cmd.type]=${Array.from(filters)[0]}`
: ''; : '';
const { hasMoreTxs, loadTxs, error, txsData, refreshTxs, loading } = const {
useTxsData({ hasMoreTxs,
limit: BE_TXS_PER_REQUEST, nextPage,
filters: f, previousPage,
}); error,
refreshTxs,
loading,
txsData,
hasPreviousPage,
} = useTxsData({
limit: BE_TXS_PER_REQUEST,
filters: f,
});
return ( return (
<> <>
<menu className="mb-2"> <TxsListNavigation
<BlocksRefetch refetch={refreshTxs} /> refreshTxs={refreshTxs}
nextPage={nextPage}
previousPage={previousPage}
hasPreviousPage={hasPreviousPage}
loading={loading}
hasMoreTxs={hasMoreTxs}
>
<TxsFilter filters={filters} setFilters={setFilters} /> <TxsFilter filters={filters} setFilters={setFilters} />
</menu> </TxsListNavigation>
<TxsInfiniteList <TxsInfiniteList
hasFilters={filters.size > 0}
hasMoreTxs={hasMoreTxs} hasMoreTxs={hasMoreTxs}
areTxsLoading={loading} areTxsLoading={loading}
txs={txsData} txs={txsData}
loadMoreTxs={loadTxs} loadMoreTxs={nextPage}
error={error} error={error}
className="mb-28" className="mb-28 w-full min-w-[400px]"
/> />
</> </>
); );
@@ -1,10 +1,11 @@
import { BrowserRouter as Router } from 'react-router-dom';
import { render, screen } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import { TxDetails } from './tx-details'; import { TxDetails } from './tx-details';
import type { import type {
BlockExplorerTransactionResult, BlockExplorerTransactionResult,
ValidatorHeartbeat, ValidatorHeartbeat,
} from '../../../routes/types/block-explorer-response'; } from '../../../routes/types/block-explorer-response';
import { MemoryRouter } from 'react-router-dom';
import { MockedProvider } from '@apollo/client/testing';
// Note: Long enough that there is a truncated output and a full output // Note: Long enough that there is a truncated output and a full output
const pubKey = const pubKey =
@@ -27,9 +28,11 @@ const txData: BlockExplorerTransactionResult = {
}; };
const renderComponent = (txData: BlockExplorerTransactionResult) => ( const renderComponent = (txData: BlockExplorerTransactionResult) => (
<Router> <MemoryRouter>
<TxDetails txData={txData} pubKey={pubKey} /> <MockedProvider>
</Router> <TxDetails txData={txData} pubKey={pubKey} />
</MockedProvider>
</MemoryRouter>
); );
describe('Transaction details', () => { describe('Transaction details', () => {
@@ -0,0 +1,13 @@
query ExplorerNodeNames {
nodesConnection {
edges {
node {
id
name
pubkey
tmPubkey
ethereumAddress
}
}
}
}
@@ -0,0 +1,53 @@
import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type ExplorerNodeNamesQueryVariables = Types.Exact<{ [key: string]: never; }>;
export type ExplorerNodeNamesQuery = { __typename?: 'Query', nodesConnection: { __typename?: 'NodesConnection', edges?: Array<{ __typename?: 'NodeEdge', node: { __typename?: 'Node', id: string, name: string, pubkey: string, tmPubkey: string, ethereumAddress: string } } | null> | null } };
export const ExplorerNodeNamesDocument = gql`
query ExplorerNodeNames {
nodesConnection {
edges {
node {
id
name
pubkey
tmPubkey
ethereumAddress
}
}
}
}
`;
/**
* __useExplorerNodeNamesQuery__
*
* To run a query within a React component, call `useExplorerNodeNamesQuery` and pass it any options that fit your needs.
* When your component renders, `useExplorerNodeNamesQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useExplorerNodeNamesQuery({
* variables: {
* },
* });
*/
export function useExplorerNodeNamesQuery(baseOptions?: Apollo.QueryHookOptions<ExplorerNodeNamesQuery, ExplorerNodeNamesQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<ExplorerNodeNamesQuery, ExplorerNodeNamesQueryVariables>(ExplorerNodeNamesDocument, options);
}
export function useExplorerNodeNamesLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerNodeNamesQuery, ExplorerNodeNamesQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<ExplorerNodeNamesQuery, ExplorerNodeNamesQueryVariables>(ExplorerNodeNamesDocument, options);
}
export type ExplorerNodeNamesQueryHookResult = ReturnType<typeof useExplorerNodeNamesQuery>;
export type ExplorerNodeNamesLazyQueryHookResult = ReturnType<typeof useExplorerNodeNamesLazyQuery>;
export type ExplorerNodeNamesQueryResult = Apollo.QueryResult<ExplorerNodeNamesQuery, ExplorerNodeNamesQueryVariables>;
+2 -3
View File
@@ -1,6 +1,5 @@
@import 'ag-grid-community/dist/styles/ag-grid.css'; @import 'ag-grid-community/styles/ag-grid.css';
@import 'ag-grid-community/dist/styles/ag-theme-balham.css'; @import 'ag-grid-community/styles/ag-theme-balham.css';
@import 'ag-grid-community/dist/styles/ag-theme-balham-dark.css';
/* You can add global styles to this file, and also import other style files */ /* You can add global styles to this file, and also import other style files */
@tailwind base; @tailwind base;
+1 -1
View File
@@ -1,5 +1,5 @@
const { join } = require('path'); const { join } = require('path');
const { createGlobPatternsForDependencies } = require('@nrwl/next/tailwind'); const { createGlobPatternsForDependencies } = require('@nx/react/tailwind');
const theme = require('../../libs/tailwindcss-config/src/theme'); const theme = require('../../libs/tailwindcss-config/src/theme');
const vegaCustomClasses = require('../../libs/tailwindcss-config/src/vega-custom-classes'); const vegaCustomClasses = require('../../libs/tailwindcss-config/src/vega-custom-classes');
+2 -2
View File
@@ -5,8 +5,8 @@
"types": ["node"] "types": ["node"]
}, },
"files": [ "files": [
"../../node_modules/@nrwl/react/typings/cssmodule.d.ts", "../../node_modules/@nx/react/typings/cssmodule.d.ts",
"../../node_modules/@nrwl/react/typings/image.d.ts" "../../node_modules/@nx/react/typings/image.d.ts"
], ],
"exclude": [ "exclude": [
"**/*.spec.ts", "**/*.spec.ts",
+2 -2
View File
@@ -18,7 +18,7 @@
"jest.config.ts" "jest.config.ts"
], ],
"files": [ "files": [
"../../node_modules/@nrwl/react/typings/cssmodule.d.ts", "../../node_modules/@nx/react/typings/cssmodule.d.ts",
"../../node_modules/@nrwl/react/typings/image.d.ts" "../../node_modules/@nx/react/typings/image.d.ts"
] ]
} }
+4 -2
View File
@@ -1,6 +1,8 @@
const { composePlugins, withNx } = require('@nx/webpack');
const { withReact } = require('@nx/react');
const SentryPlugin = require('@sentry/webpack-plugin'); const SentryPlugin = require('@sentry/webpack-plugin');
module.exports = (config, context) => { module.exports = composePlugins(withNx(), withReact(), (config) => {
const additionalPlugins = process.env.SENTRY_AUTH_TOKEN const additionalPlugins = process.env.SENTRY_AUTH_TOKEN
? [ ? [
new SentryPlugin({ new SentryPlugin({
@@ -14,4 +16,4 @@ module.exports = (config, context) => {
...config, ...config,
plugins: [...additionalPlugins, ...config.plugins], plugins: [...additionalPlugins, ...config.plugins],
}; };
}; });
+1 -1
View File
@@ -49,7 +49,7 @@ module.exports = defineConfig({
vegaTokenContractAddress: '0xF41bD86d462D36b997C0bbb4D97a0a3382f205B7', vegaTokenContractAddress: '0xF41bD86d462D36b997C0bbb4D97a0a3382f205B7',
vegaTokenAddress: '0x67175Da1D5e966e40D11c4B2519392B2058373de', vegaTokenAddress: '0x67175Da1D5e966e40D11c4B2519392B2058373de',
txTimeout: { timeout: 70000 }, txTimeout: { timeout: 70000 },
epochTimeout: { timeout: 10000 }, epochTimeout: { timeout: 12000 },
blockConfirmations: 3, blockConfirmations: 3,
grepTags: '@regression @smoke @slow', grepTags: '@regression @smoke @slow',
grepFilterSpecs: true, grepFilterSpecs: true,
+4 -3
View File
@@ -1,10 +1,11 @@
{ {
"name": "governance-e2e",
"$schema": "../../node_modules/nx/schemas/project-schema.json", "$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/governance-e2e/src", "sourceRoot": "apps/governance-e2e/src",
"projectType": "application", "projectType": "application",
"targets": { "targets": {
"e2e": { "e2e": {
"executor": "@nrwl/cypress:cypress", "executor": "@nx/cypress:cypress",
"options": { "options": {
"cypressConfig": "apps/governance-e2e/cypress.config.js", "cypressConfig": "apps/governance-e2e/cypress.config.js",
"devServerTarget": "governance:serve" "devServerTarget": "governance:serve"
@@ -16,14 +17,14 @@
} }
}, },
"lint": { "lint": {
"executor": "@nrwl/linter:eslint", "executor": "@nx/linter:eslint",
"outputs": ["{options.outputFile}"], "outputs": ["{options.outputFile}"],
"options": { "options": {
"lintFilePatterns": ["apps/governance-e2e/**/*.{js,ts}"] "lintFilePatterns": ["apps/governance-e2e/**/*.{js,ts}"]
} }
}, },
"build": { "build": {
"executor": "@nrwl/workspace:run-commands", "executor": "nx:run-commands",
"outputs": [], "outputs": [],
"options": { "options": {
"command": "yarn tsc --project ./apps/governance-e2e/" "command": "yarn tsc --project ./apps/governance-e2e/"
@@ -0,0 +1,63 @@
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',
},
};
@@ -14,25 +14,31 @@ import {
submitUniqueRawProposal, submitUniqueRawProposal,
voteForProposal, voteForProposal,
} from '../../../../governance-e2e/src/support/governance.functions'; } from '../../../../governance-e2e/src/support/governance.functions';
import { ensureSpecifiedUnstakedTokensAreAssociated } from '../../../../governance-e2e/src/support/staking.functions'; import {
ensureSpecifiedUnstakedTokensAreAssociated,
stakingPageAssociateTokens,
stakingPageDisassociateAllTokens,
} from '../../../../governance-e2e/src/support/staking.functions';
import { ethereumWalletConnect } from '../../../../governance-e2e/src/support/wallet-eth.functions'; import { ethereumWalletConnect } from '../../../../governance-e2e/src/support/wallet-eth.functions';
import { vegaWalletSetSpecifiedApprovalAmount } from '../../../../governance-e2e/src/support/wallet-teardown.functions'; import {
switchVegaWalletPubKey,
vegaWalletSetSpecifiedApprovalAmount,
} from '../../support/wallet-functions';
import type { testFreeformProposal } from '../../support/common-interfaces'; import type { testFreeformProposal } from '../../support/common-interfaces';
import { formatDateWithLocalTimezone } from '@vegaprotocol/utils'; import { formatDateWithLocalTimezone } from '@vegaprotocol/utils';
const proposalVoteProgressForPercentage = const proposalVoteProgressForPercentage =
'[data-testid="vote-progress-indicator-percentage-for"]'; 'vote-progress-indicator-percentage-for';
const proposalVoteProgressAgainstPercentage = const proposalVoteProgressAgainstPercentage =
'[data-testid="vote-progress-indicator-percentage-against"]'; 'vote-progress-indicator-percentage-against';
const proposalVoteProgressForTokens = const proposalVoteProgressForTokens = 'vote-progress-indicator-tokens-for';
'[data-testid="vote-progress-indicator-tokens-for"]';
const proposalVoteProgressAgainstTokens = const proposalVoteProgressAgainstTokens =
'[data-testid="vote-progress-indicator-tokens-against"]'; 'vote-progress-indicator-tokens-against';
const changeVoteButton = '[data-testid="change-vote-button"]'; const changeVoteButton = 'change-vote-button';
const proposalDetailsTitle = '[data-testid="proposal-title"]'; const proposalDetailsTitle = 'proposal-title';
const proposalDetailsDescription = '[data-testid="proposal-description"]'; const proposalDetailsDescription = 'proposal-description';
const openProposals = '[data-testid="open-proposals"]'; const openProposals = 'open-proposals';
const viewProposalButton = '[data-testid="view-proposal-btn"]'; const viewProposalButton = 'view-proposal-btn';
const proposalDescriptionToggle = 'proposal-description-toggle'; const proposalDescriptionToggle = 'proposal-description-toggle';
const voteBreakdownToggle = 'vote-breakdown-toggle'; const voteBreakdownToggle = 'vote-breakdown-toggle';
const proposalTermsToggle = 'proposal-json-toggle'; const proposalTermsToggle = 'proposal-json-toggle';
@@ -44,7 +50,7 @@ describe(
before('connect wallets and set approval limit', function () { before('connect wallets and set approval limit', function () {
cy.visit('/'); cy.visit('/');
ethereumWalletConnect(); ethereumWalletConnect();
cy.associateTokensToVegaWallet('1'); // cy.associateTokensToVegaWallet('1');
}); });
beforeEach('visit proposals tab', function () { beforeEach('visit proposals tab', function () {
@@ -65,18 +71,18 @@ describe(
createRawProposal(); createRawProposal();
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => { cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
cy.get(openProposals).within(() => { cy.getByTestId(openProposals).within(() => {
getProposalFromTitle(rawProposal.rationale.title).within(() => { getProposalFromTitle(rawProposal.rationale.title).within(() => {
cy.get(viewProposalButton).should('be.visible').click(); cy.getByTestId(viewProposalButton).should('be.visible').click();
}); });
}); });
cy.get(proposalDetailsTitle).should( cy.getByTestId(proposalDetailsTitle).should(
'contain.text', 'contain.text',
rawProposal.rationale.title rawProposal.rationale.title
); );
cy.getByTestId(proposalDescriptionToggle).click(); cy.getByTestId(proposalDescriptionToggle).click();
cy.getByTestId('proposal-description-toggle'); cy.getByTestId('proposal-description-toggle');
cy.get(proposalDetailsDescription) cy.getByTestId(proposalDetailsDescription)
.find('p') .find('p')
.should('have.text', proposalDescription); .should('have.text', proposalDescription);
}); });
@@ -92,6 +98,11 @@ describe(
.and('have.length', 64); .and('have.length', 64);
cy.getByTestId(proposalTermsToggle).click(); cy.getByTestId(proposalTermsToggle).click();
// 3001-VOTE-052 3001-VOTE-010 // 3001-VOTE-052 3001-VOTE-010
/**
* TODO(@nx/cypress): Nesting Cypress commands in a should assertion now throws.
* You should use .then() to chain commands instead.
* More Info: https://docs.cypress.io/guides/references/migration-guide#-should
**/
cy.get('code.language-json') cy.get('code.language-json')
.should('exist') .should('exist')
.within(() => { .within(() => {
@@ -110,7 +121,7 @@ describe(
}); });
navigateTo(navigation.proposals); navigateTo(navigation.proposals);
getProposalFromTitle(proposalTitle).within(() => getProposalFromTitle(proposalTitle).within(() =>
cy.get(viewProposalButton).click() cy.getByTestId(viewProposalButton).click()
); );
cy.wrap( cy.wrap(
formatDateWithLocalTimezone(new Date(proposalTimeStamp * 1000)) formatDateWithLocalTimezone(new Date(proposalTimeStamp * 1000))
@@ -132,7 +143,7 @@ describe(
createRawProposal(); createRawProposal();
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => { cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
getProposalFromTitle(rawProposal.rationale.title).within(() => getProposalFromTitle(rawProposal.rationale.title).within(() =>
cy.get(viewProposalButton).click() cy.getByTestId(viewProposalButton).click()
); );
}); });
cy.contains('Participation: Not Met 0.00 0.00%(0.00% Required)').should( cy.contains('Participation: Not Met 0.00 0.00%(0.00% Required)').should(
@@ -159,7 +170,7 @@ describe(
createRawProposal(); createRawProposal();
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => { cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
getProposalFromTitle(rawProposal.rationale.title).within(() => getProposalFromTitle(rawProposal.rationale.title).within(() =>
cy.get(viewProposalButton).click() cy.getByTestId(viewProposalButton).click()
); );
}); });
// 3001-VOTE-080 // 3001-VOTE-080
@@ -176,14 +187,16 @@ describe(
.contains(votedDate) .contains(votedDate)
.should('be.visible'); .should('be.visible');
}); });
cy.get(proposalVoteProgressForPercentage) // 3001-VOTE-072 cy.getByTestId(proposalVoteProgressForPercentage) // 3001-VOTE-072
.contains('100.00%') .contains('100.00%')
.and('be.visible'); .and('be.visible');
cy.get(proposalVoteProgressAgainstPercentage) cy.getByTestId(proposalVoteProgressAgainstPercentage)
.contains('0.00%') .contains('0.00%')
.and('be.visible'); .and('be.visible');
cy.get(proposalVoteProgressForTokens).contains('1.00').and('be.visible'); cy.getByTestId(proposalVoteProgressForTokens)
cy.get(proposalVoteProgressAgainstTokens) .contains('1.00')
.and('be.visible');
cy.getByTestId(proposalVoteProgressAgainstTokens)
.contains('0.00') .contains('0.00')
.and('be.visible'); .and('be.visible');
cy.getByTestId(voteBreakdownToggle).click(); cy.getByTestId(voteBreakdownToggle).click();
@@ -204,15 +217,15 @@ describe(
getProposalInformationFromTable('Number of voting parties') getProposalInformationFromTable('Number of voting parties')
.should('have.text', '1') .should('have.text', '1')
.and('be.visible'); .and('be.visible');
cy.get(changeVoteButton).should('be.visible').click(); cy.getByTestId(changeVoteButton).should('be.visible').click();
voteForProposal('for'); voteForProposal('for');
// 3001-VOTE-064 // 3001-VOTE-064
getProposalInformationFromTable('Tokens for proposal') getProposalInformationFromTable('Tokens for proposal')
.should('have.text', (1).toFixed(2)) .should('have.text', (1).toFixed(2))
.and('be.visible'); .and('be.visible');
cy.get(changeVoteButton).should('be.visible').click(); cy.getByTestId(changeVoteButton).should('be.visible').click();
voteForProposal('against'); voteForProposal('against');
cy.get(proposalVoteProgressAgainstPercentage) cy.getByTestId(proposalVoteProgressAgainstPercentage)
.contains('100.00%') .contains('100.00%')
.and('be.visible'); .and('be.visible');
getProposalInformationFromTable('Tokens against proposal') getProposalInformationFromTable('Tokens against proposal')
@@ -229,13 +242,15 @@ describe(
createRawProposal(); createRawProposal();
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => { cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
getProposalFromTitle(rawProposal.rationale.title).within(() => getProposalFromTitle(rawProposal.rationale.title).within(() =>
cy.get(viewProposalButton).click() cy.getByTestId(viewProposalButton).click()
); );
}); });
voteForProposal('for'); voteForProposal('for');
// 3001-VOTE-079 // 3001-VOTE-079
cy.contains('You voted: For').should('be.visible'); cy.contains('You voted: For').should('be.visible');
cy.get(proposalVoteProgressForTokens).contains('1').and('be.visible'); cy.getByTestId(proposalVoteProgressForTokens)
.contains('1')
.and('be.visible');
cy.getByTestId(voteBreakdownToggle).click(); cy.getByTestId(voteBreakdownToggle).click();
getProposalInformationFromTable('Total Supply') getProposalInformationFromTable('Total Supply')
.invoke('text') .invoke('text')
@@ -251,22 +266,22 @@ describe(
navigateTo(navigation.proposals); navigateTo(navigation.proposals);
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => { cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
getProposalFromTitle(rawProposal.rationale.title).within(() => getProposalFromTitle(rawProposal.rationale.title).within(() =>
cy.get(viewProposalButton).click() cy.getByTestId(viewProposalButton).click()
); );
}); });
cy.get(proposalVoteProgressForPercentage) cy.getByTestId(proposalVoteProgressForPercentage)
.contains('100.00%') .contains('100.00%')
.and('be.visible'); .and('be.visible');
cy.get(proposalVoteProgressAgainstPercentage) cy.getByTestId(proposalVoteProgressAgainstPercentage)
.contains('0.00%') .contains('0.00%')
.and('be.visible'); .and('be.visible');
// 3001-VOTE-065 // 3001-VOTE-065
cy.get(changeVoteButton).should('be.visible').click(); cy.getByTestId(changeVoteButton).should('be.visible').click();
voteForProposal('for'); voteForProposal('for');
cy.get(proposalVoteProgressForTokens) cy.getByTestId(proposalVoteProgressForTokens)
.contains(tokensRequiredToAchieveResult) .contains(tokensRequiredToAchieveResult)
.and('be.visible'); .and('be.visible');
cy.get(proposalVoteProgressAgainstTokens) cy.getByTestId(proposalVoteProgressAgainstTokens)
.contains('0.00') .contains('0.00')
.and('be.visible'); .and('be.visible');
cy.getByTestId(voteBreakdownToggle).click(); cy.getByTestId(voteBreakdownToggle).click();
@@ -297,5 +312,36 @@ describe(
.and('be.visible'); .and('be.visible');
}); });
}); });
it('Able to vote for proposal twice by switching public key', function () {
ensureSpecifiedUnstakedTokensAreAssociated('1');
createRawProposal();
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
getProposalFromTitle(rawProposal.rationale.title).within(() =>
cy.getByTestId(viewProposalButton).click()
);
voteForProposal('for');
cy.contains('You voted: For').should('be.visible');
ethereumWalletConnect();
switchVegaWalletPubKey();
stakingPageAssociateTokens('2');
navigateTo(navigation.proposals);
getProposalFromTitle(rawProposal.rationale.title).within(() =>
cy.getByTestId(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'
);
// Checking vote status for different public keys is displayed correctly
cy.contains('You voted: For').should('be.visible');
});
switchVegaWalletPubKey();
stakingPageDisassociateAllTokens();
});
} }
); );
@@ -16,16 +16,16 @@ import {
} from '../../support/proposal.functions'; } from '../../support/proposal.functions';
import { ensureSpecifiedUnstakedTokensAreAssociated } from '../../support/staking.functions'; import { ensureSpecifiedUnstakedTokensAreAssociated } from '../../support/staking.functions';
import { ethereumWalletConnect } from '../../support/wallet-eth.functions'; import { ethereumWalletConnect } from '../../support/wallet-eth.functions';
import { vegaWalletSetSpecifiedApprovalAmount } from '../../support/wallet-teardown.functions'; import { vegaWalletSetSpecifiedApprovalAmount } from '../../support/wallet-functions';
const proposalListItem = '[data-testid="proposals-list-item"]'; const proposalListItem = '[data-testid="proposals-list-item"]';
const closedProposals = '[data-testid="closed-proposals"]'; const closedProposals = 'closed-proposals';
const proposalStatus = '[data-testid="proposal-status"]'; const proposalStatus = 'proposal-status';
const viewProposalButton = '[data-testid="view-proposal-btn"]'; const viewProposalButton = 'view-proposal-btn';
const votesTable = '[data-testid="votes-table"]'; const votesTable = 'votes-table';
const openProposals = '[data-testid="open-proposals"]'; const openProposals = 'open-proposals';
const proposalVoteProgressForPercentage = const proposalVoteProgressForPercentage =
'[data-testid="vote-progress-indicator-percentage-for"]'; 'vote-progress-indicator-percentage-for';
const proposalTimeout = { timeout: 8000 }; const proposalTimeout = { timeout: 8000 };
context( context(
@@ -55,18 +55,18 @@ context(
cy.createMarket(); cy.createMarket();
cy.reload(); cy.reload();
waitForSpinner(); waitForSpinner();
cy.get(closedProposals).within(() => { cy.getByTestId(closedProposals).within(() => {
cy.contains(proposalTitle) cy.contains(proposalTitle)
.parentsUntil(proposalListItem) .parentsUntil(proposalListItem)
.last() .last()
.within(() => { .within(() => {
cy.get(proposalStatus).should('have.text', 'Enacted'); cy.getByTestId(proposalStatus).should('have.text', 'Enacted');
cy.get(viewProposalButton).click(); cy.getByTestId(viewProposalButton).click();
}); });
}); });
cy.getByTestId('proposal-type').should('have.text', 'New market'); cy.getByTestId('proposal-type').should('have.text', 'New market');
cy.get(proposalStatus).should('have.text', 'Enacted'); cy.getByTestId(proposalStatus).should('have.text', 'Enacted');
cy.get(votesTable).within(() => { cy.getByTestId(votesTable).within(() => {
cy.contains('Vote passed.').should('be.visible'); cy.contains('Vote passed.').should('be.visible');
cy.contains('Voting has ended.').should('be.visible'); cy.contains('Voting has ended.').should('be.visible');
}); });
@@ -81,21 +81,27 @@ context(
navigateTo(navigation.proposals); navigateTo(navigation.proposals);
cy.reload(); cy.reload();
waitForSpinner(); waitForSpinner();
cy.get(openProposals).within(() => { cy.getByTestId(openProposals).within(() => {
cy.contains(proposalTitle) cy.contains(proposalTitle)
.parentsUntil(proposalListItem) .parentsUntil(proposalListItem)
.last() .last()
.within(() => cy.get(viewProposalButton).click()); .within(() => cy.getByTestId(viewProposalButton).click());
}); });
cy.get(proposalStatus).should('have.text', 'Open'); cy.getByTestId(proposalStatus).should('have.text', 'Open');
voteForProposal('for'); voteForProposal('for');
cy.get(proposalStatus, proposalTimeout).should('have.text', 'Passed'); cy.getByTestId(proposalStatus, proposalTimeout)
cy.get(proposalStatus, proposalTimeout).should('have.text', 'Enacted'); .should('have.text', 'Passed')
cy.get(votesTable).within(() => { .then(() => {
cy.getByTestId(proposalStatus, proposalTimeout).should(
'have.text',
'Enacted'
);
});
cy.getByTestId(votesTable).within(() => {
cy.contains('Vote passed.').should('be.visible'); cy.contains('Vote passed.').should('be.visible');
cy.contains('Voting has ended.').should('be.visible'); cy.contains('Voting has ended.').should('be.visible');
}); });
cy.get(proposalVoteProgressForPercentage) cy.getByTestId(proposalVoteProgressForPercentage)
.contains('100.00%') .contains('100.00%')
.and('be.visible'); .and('be.visible');
}); });
@@ -109,15 +115,18 @@ context(
navigateTo(navigation.proposals); navigateTo(navigation.proposals);
cy.reload(); cy.reload();
waitForSpinner(); waitForSpinner();
cy.get(openProposals, { timeout: 6000 }).within(() => { cy.getByTestId(openProposals, { timeout: 6000 }).within(() => {
cy.contains(proposalTitle) cy.contains(proposalTitle)
.parentsUntil(proposalListItem) .parentsUntil(proposalListItem)
.last() .last()
.within(() => cy.get(viewProposalButton).click()); .within(() => cy.getByTestId(viewProposalButton).click());
}); });
cy.get(proposalStatus).should('have.text', 'Open'); cy.getByTestId(proposalStatus).should('have.text', 'Open');
voteForProposal('for'); voteForProposal('for');
cy.get(proposalStatus, proposalTimeout).should('have.text', 'Enacted'); cy.getByTestId(proposalStatus, proposalTimeout).should(
'have.text',
'Enacted'
);
}); });
// 3001-VOTE-048 3001-VOTE-049 3001-VOTE-050 // 3001-VOTE-048 3001-VOTE-049 3001-VOTE-050
@@ -128,14 +137,17 @@ context(
navigateTo(navigation.proposals); navigateTo(navigation.proposals);
cy.reload(); cy.reload();
waitForSpinner(); waitForSpinner();
cy.get(openProposals).within(() => { cy.getByTestId(openProposals).within(() => {
cy.contains(proposalTitle) cy.contains(proposalTitle)
.parentsUntil(proposalListItem) .parentsUntil(proposalListItem)
.last() .last()
.within(() => cy.get(viewProposalButton).click()); .within(() => cy.getByTestId(viewProposalButton).click());
}); });
cy.get(proposalStatus).should('have.text', 'Open'); cy.getByTestId(proposalStatus).should('have.text', 'Open');
cy.get(proposalStatus, proposalTimeout).should('have.text', 'Declined'); cy.getByTestId(proposalStatus, proposalTimeout).should(
'have.text',
'Declined'
);
getProposalInformationFromTable('Rejection reason') getProposalInformationFromTable('Rejection reason')
.contains('PROPOSAL_ERROR_PARTICIPATION_THRESHOLD_NOT_REACHED') .contains('PROPOSAL_ERROR_PARTICIPATION_THRESHOLD_NOT_REACHED')
.and('be.visible'); .and('be.visible');
@@ -32,24 +32,23 @@ import {
import { import {
vegaWalletSetSpecifiedApprovalAmount, vegaWalletSetSpecifiedApprovalAmount,
vegaWalletTeardown, vegaWalletTeardown,
} from '../../support/wallet-teardown.functions'; } from '../../support/wallet-functions';
import { ethereumWalletConnect } from '../../support/wallet-eth.functions'; import { ethereumWalletConnect } from '../../support/wallet-eth.functions';
import type { testFreeformProposal } from '../../support/common-interfaces'; import type { testFreeformProposal } from '../../support/common-interfaces';
const vegaWalletStakedBalances = const vegaWalletStakedBalances = 'vega-wallet-balance-staked-validators';
'[data-testid="vega-wallet-balance-staked-validators"]'; const vegaWalletAssociatedBalance = 'associated-amount';
const vegaWalletAssociatedBalance = '[data-testid="associated-amount"]'; const vegaWalletNameElement = 'wallet-name';
const vegaWalletNameElement = '[data-testid="wallet-name"]'; const vegaWallet = 'vega-wallet';
const vegaWallet = '[data-testid="vega-wallet"]'; const connectToVegaWalletButton = 'connect-to-vega-wallet-btn';
const connectToVegaWalletButton = '[data-testid="connect-to-vega-wallet-btn"]'; const newProposalSubmitButton = 'proposal-submit';
const newProposalSubmitButton = '[data-testid="proposal-submit"]'; const viewProposalButton = 'view-proposal-btn';
const viewProposalButton = '[data-testid="view-proposal-btn"]'; const rawProposalData = 'proposal-data';
const rawProposalData = '[data-testid="proposal-data"]'; const voteButtons = 'vote-buttons';
const voteButtons = '[data-testid="vote-buttons"]';
const rejectProposalsLink = '[href="/proposals/rejected"]'; const rejectProposalsLink = '[href="/proposals/rejected"]';
const feedbackError = '[data-testid="Error"]'; const feedbackError = 'Error';
const noOpenProposals = '[data-testid="no-open-proposals"]'; const noOpenProposals = 'no-open-proposals';
const noClosedProposals = '[data-testid="no-closed-proposals"]'; const noClosedProposals = 'no-closed-proposals';
const txTimeout = Cypress.env('txTimeout'); const txTimeout = Cypress.env('txTimeout');
const epochTimeout = Cypress.env('epochTimeout'); const epochTimeout = Cypress.env('epochTimeout');
const proposalTimeout = { timeout: 14000 }; const proposalTimeout = { timeout: 14000 };
@@ -93,10 +92,10 @@ context(
// Test can only pass if run before other proposal tests. // Test can only pass if run before other proposal tests.
it.skip('Should be able to see that no proposals exist', function () { it.skip('Should be able to see that no proposals exist', function () {
// 3001-VOTE-003 // 3001-VOTE-003
cy.get(noOpenProposals) cy.getByTestId(noOpenProposals)
.should('be.visible') .should('be.visible')
.and('have.text', 'There are no open or yet to enact proposals'); .and('have.text', 'There are no open or yet to enact proposals');
cy.get(noClosedProposals) cy.getByTestId(noClosedProposals)
.should('be.visible') .should('be.visible')
.and('have.text', 'There are no enacted or rejected proposals'); .and('have.text', 'There are no enacted or rejected proposals');
}); });
@@ -126,11 +125,14 @@ context(
stakingValidatorPageAddStake('2'); stakingValidatorPageAddStake('2');
closeStakingDialog(); closeStakingDialog();
cy.get(vegaWalletStakedBalances, txTimeout).should('contain', '2'); cy.getByTestId(vegaWalletStakedBalances, txTimeout).should(
'contain',
'2'
);
createRawProposal(); createRawProposal();
}); });
it.skip('Creating a proposal - proposal rejected - when closing time sooner than system default', function () { it('Creating a proposal - proposal rejected - when closing time sooner than system default', function () {
goToMakeNewProposal(governanceProposalType.FREEFORM); goToMakeNewProposal(governanceProposalType.FREEFORM);
enterUniqueFreeFormProposalBody('0.1', generateFreeFormProposalTitle()); enterUniqueFreeFormProposalBody('0.1', generateFreeFormProposalTitle());
cy.get('input:invalid') cy.get('input:invalid')
@@ -138,7 +140,7 @@ context(
.should('equal', 'Value must be greater than or equal to 1.'); .should('equal', 'Value must be greater than or equal to 1.');
}); });
it.skip('Creating a proposal - proposal rejected - when closing time later than system default', function () { it('Creating a proposal - proposal rejected - when closing time later than system default', function () {
goToMakeNewProposal(governanceProposalType.FREEFORM); goToMakeNewProposal(governanceProposalType.FREEFORM);
enterUniqueFreeFormProposalBody( enterUniqueFreeFormProposalBody(
'100000', '100000',
@@ -168,7 +170,7 @@ context(
getProposalFromTitle(rawProposal.rationale.title).within(() => { getProposalFromTitle(rawProposal.rationale.title).within(() => {
cy.contains('Rejected').should('be.visible'); cy.contains('Rejected').should('be.visible');
cy.contains('Close time too late').should('be.visible'); cy.contains('Close time too late').should('be.visible');
cy.get(viewProposalButton).click(); cy.getByTestId(viewProposalButton).click();
}); });
}); });
cy.getByTestId('proposal-status').should('have.text', 'Rejected'); cy.getByTestId('proposal-status').should('have.text', 'Rejected');
@@ -185,14 +187,14 @@ context(
const errorMsg = 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)'; '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(); vegaWalletTeardown();
cy.get(vegaWalletAssociatedBalance, txTimeout).contains( cy.getByTestId(vegaWalletAssociatedBalance, txTimeout).contains(
'0.00', '0.00',
txTimeout txTimeout
); );
goToMakeNewProposal(governanceProposalType.RAW); goToMakeNewProposal(governanceProposalType.RAW);
enterRawProposalBody(createTenDigitUnixTimeStampForSpecifiedDays(8)); enterRawProposalBody(createTenDigitUnixTimeStampForSpecifiedDays(8));
cy.contains('Transaction failed', proposalTimeout).should('be.visible'); cy.contains('Transaction failed', proposalTimeout).should('be.visible');
cy.get(feedbackError).should('have.text', errorMsg); cy.getByTestId(feedbackError).should('have.text', errorMsg);
closeDialog(); closeDialog();
}); });
@@ -205,7 +207,7 @@ context(
goToMakeNewProposal(governanceProposalType.RAW); goToMakeNewProposal(governanceProposalType.RAW);
enterRawProposalBody(createTenDigitUnixTimeStampForSpecifiedDays(8)); enterRawProposalBody(createTenDigitUnixTimeStampForSpecifiedDays(8));
cy.contains('Transaction failed', proposalTimeout).should('be.visible'); cy.contains('Transaction failed', proposalTimeout).should('be.visible');
cy.get(feedbackError).should('have.text', errorMsg); cy.getByTestId(feedbackError).should('have.text', errorMsg);
closeDialog(); closeDialog();
}); });
@@ -221,17 +223,17 @@ context(
createTenDigitUnixTimeStampForSpecifiedDays(8); createTenDigitUnixTimeStampForSpecifiedDays(8);
freeformProposal.unexpected = `i shouldn't be here`; freeformProposal.unexpected = `i shouldn't be here`;
const proposalPayload = JSON.stringify(freeformProposal); const proposalPayload = JSON.stringify(freeformProposal);
cy.get(rawProposalData).type(proposalPayload, { cy.getByTestId(rawProposalData).type(proposalPayload, {
parseSpecialCharSequences: false, parseSpecialCharSequences: false,
delay: 2, delay: 2,
}); });
}); });
cy.get(newProposalSubmitButton).should('be.visible').click(); cy.getByTestId(newProposalSubmitButton).should('be.visible').click();
cy.contains('Transaction failed', proposalTimeout).should('be.visible'); cy.contains('Transaction failed', proposalTimeout).should('be.visible');
cy.get(feedbackError).should('have.text', errorMsg); cy.getByTestId(feedbackError).should('have.text', errorMsg);
closeDialog(); closeDialog();
cy.get(rawProposalData) cy.getByTestId(rawProposalData)
.invoke('val') .invoke('val')
.should('contain', "i shouldn't be here"); .should('contain', "i shouldn't be here");
}); });
@@ -249,15 +251,15 @@ context(
rawProposal.terms.unexpectedField = `i shouldn't be here`; rawProposal.terms.unexpectedField = `i shouldn't be here`;
const proposalPayload = JSON.stringify(rawProposal); const proposalPayload = JSON.stringify(rawProposal);
cy.get(rawProposalData).type(proposalPayload, { cy.getByTestId(rawProposalData).type(proposalPayload, {
parseSpecialCharSequences: false, parseSpecialCharSequences: false,
delay: 2, delay: 2,
}); });
}); });
cy.get(newProposalSubmitButton).should('be.visible').click(); cy.getByTestId(newProposalSubmitButton).should('be.visible').click();
cy.contains('Transaction failed', proposalTimeout).should('be.visible'); cy.contains('Transaction failed', proposalTimeout).should('be.visible');
cy.get(feedbackError).should('have.text', errorMsg); cy.getByTestId(feedbackError).should('have.text', errorMsg);
closeDialog(); closeDialog();
}); });
@@ -265,10 +267,10 @@ context(
// 3006-PASC-006 3006-PASC-007 3008-PFRO-018 3008-PFRO-019 3003-PMAN-006 3003-PMAN-007 // 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 () { it('Unable to submit proposal without valid json', function () {
goToMakeNewProposal(governanceProposalType.RAW); goToMakeNewProposal(governanceProposalType.RAW);
cy.get(newProposalSubmitButton).click(); cy.getByTestId(newProposalSubmitButton).click();
cy.getByTestId('input-error-text').should('have.text', 'Required'); cy.getByTestId('input-error-text').should('have.text', 'Required');
cy.get(rawProposalData).type('Not a valid json string'); cy.getByTestId(rawProposalData).type('Not a valid json string');
cy.get(newProposalSubmitButton).click(); cy.getByTestId(newProposalSubmitButton).click();
cy.getByTestId('input-error-text').should( cy.getByTestId('input-error-text').should(
'have.text', 'have.text',
'Must be valid JSON' 'Must be valid JSON'
@@ -283,22 +285,22 @@ context(
submitUniqueRawProposal({ proposalTitle: proposalTitle }); submitUniqueRawProposal({ proposalTitle: proposalTitle });
ethereumWalletConnect(); ethereumWalletConnect();
stakingPageDisassociateTokens('0.0001'); stakingPageDisassociateTokens('0.0001');
cy.get(vegaWallet) cy.getByTestId(vegaWallet)
.first() .first()
.within(() => { .within(() => {
cy.get(vegaWalletAssociatedBalance, txTimeout).should( cy.getByTestId(vegaWalletAssociatedBalance, txTimeout).should(
'contain', 'contain',
'0.9999' '0.9999'
); );
}); });
navigateTo(navigation.proposals); navigateTo(navigation.proposals);
getProposalFromTitle(proposalTitle).within(() => getProposalFromTitle(proposalTitle).within(() =>
cy.get(viewProposalButton).click() cy.getByTestId(viewProposalButton).click()
); );
cy.contains('Vote breakdown').should('be.visible', { cy.contains('Vote breakdown').should('be.visible', {
timeout: 10000, timeout: 10000,
}); });
cy.get(voteButtons).should('not.exist'); cy.getByTestId(voteButtons).should('not.exist');
cy.getByTestId('min-proposal-requirements').should( cy.getByTestId('min-proposal-requirements').should(
'have.text', 'have.text',
`You must have at least ${this.minVoterBalance} VEGA associated to vote on this proposal` `You must have at least ${this.minVoterBalance} VEGA associated to vote on this proposal`
@@ -311,20 +313,20 @@ context(
cy.get('[data-testid="disconnect"]').click(); cy.get('[data-testid="disconnect"]').click();
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => { cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
getProposalFromTitle(rawProposal.rationale.title).within(() => getProposalFromTitle(rawProposal.rationale.title).within(() =>
cy.get(viewProposalButton).click() cy.getByTestId(viewProposalButton).click()
); );
}); });
// 3001-VOTE-075 // 3001-VOTE-075
// 3001-VOTE-076 // 3001-VOTE-076
cy.get(connectToVegaWalletButton) cy.getByTestId(connectToVegaWalletButton)
.should('be.visible') .should('be.visible')
.and('have.text', 'Connect Vega wallet') .and('have.text', 'Connect Vega wallet')
.click(); .click();
cy.getByTestId('connector-jsonRpc').click(); cy.getByTestId('connector-jsonRpc').click();
cy.get(vegaWalletNameElement).should('be.visible'); cy.getByTestId(vegaWalletNameElement).should('be.visible');
cy.get(connectToVegaWalletButton).should('not.exist'); cy.getByTestId(connectToVegaWalletButton).should('not.exist');
// 3001-VOTE-100 // 3001-VOTE-100
cy.get(vegaWalletAssociatedBalance, txTimeout).contains( cy.getByTestId(vegaWalletAssociatedBalance, txTimeout).contains(
'1.00', '1.00',
txTimeout txTimeout
); );
@@ -8,6 +8,7 @@ import {
} from '../../support/common.functions'; } from '../../support/common.functions';
import { import {
getDownloadedProposalJsonPath, getDownloadedProposalJsonPath,
getProposalFromTitle,
submitUniqueRawProposal, submitUniqueRawProposal,
} from '../../support/governance.functions'; } from '../../support/governance.functions';
import { import {
@@ -23,38 +24,38 @@ import {
} from '../../support/staking.functions'; } from '../../support/staking.functions';
import { ethereumWalletConnect } from '../../support/wallet-eth.functions'; import { ethereumWalletConnect } from '../../support/wallet-eth.functions';
import { import {
switchVegaWalletPubKey,
vegaWalletFaucetAssetsWithoutCheck,
vegaWalletSetSpecifiedApprovalAmount, vegaWalletSetSpecifiedApprovalAmount,
vegaWalletTeardown, vegaWalletTeardown,
} from '../../support/wallet-teardown.functions'; } from '../../support/wallet-functions';
import { vegaWalletFaucetAssetsWithoutCheck } from '../../support/wallet-vega.functions';
const proposalListItem = '[data-testid="proposals-list-item"]'; const proposalListItem = '[data-testid="proposals-list-item"]';
const openProposals = '[data-testid="open-proposals"]'; const openProposals = 'open-proposals';
const proposalType = '[data-testid="proposal-type"]'; const proposalType = 'proposal-type';
const proposalDetails = '[data-testid="proposal-details"]'; const proposalDetails = 'proposal-details';
const newProposalSubmitButton = '[data-testid="proposal-submit"]'; const newProposalSubmitButton = 'proposal-submit';
const proposalVoteDeadline = '[data-testid="proposal-vote-deadline"]'; const proposalVoteDeadline = 'proposal-vote-deadline';
const proposalParameterSelect = '[data-testid="proposal-parameter-select"]'; const proposalParameterSelect = 'proposal-parameter-select';
const proposalMarketSelect = '[data-testid="proposal-market-select"]'; const proposalMarketSelect = 'proposal-market-select';
const newProposalTitle = '[data-testid="proposal-title"]'; const newProposalTitle = 'proposal-title';
const newProposalDescription = '[data-testid="proposal-description"]'; const newProposalDescription = 'proposal-description';
const newProposalTerms = '[data-testid="proposal-terms"]'; const newProposalTerms = 'proposal-terms';
const newProposedParameterValue = const newProposedParameterValue = 'selected-proposal-param-new-value';
'[data-testid="selected-proposal-param-new-value"]'; const minVoteDeadline = 'min-vote';
const minVoteDeadline = '[data-testid="min-vote"]'; const maxVoteDeadline = 'max-vote';
const maxVoteDeadline = '[data-testid="max-vote"]'; const minValidationDeadline = 'min-validation';
const minValidationDeadline = '[data-testid="min-validation"]'; const minEnactDeadline = 'min-enactment';
const minEnactDeadline = '[data-testid="min-enactment"]'; const maxEnactDeadline = 'max-enactment';
const maxEnactDeadline = '[data-testid="max-enactment"]'; const inputError = 'input-error-text';
const inputError = '[data-testid="input-error-text"]'; const enactmentDeadlineError = 'enactment-before-voting-deadline';
const enactmentDeadlineError = const proposalDownloadBtn = 'proposal-download-json';
'[data-testid="enactment-before-voting-deadline"]';
const proposalDownloadBtn = '[data-testid="proposal-download-json"]';
const feedbackError = '[data-testid="Error"]'; const feedbackError = '[data-testid="Error"]';
const viewProposalBtn = 'view-proposal-btn'; const viewProposalBtn = 'view-proposal-btn';
const liquidityVoteStatus = 'liquidity-votes-status'; const liquidityVoteStatus = 'liquidity-votes-status';
const tokenVoteStatus = 'token-votes-status'; const tokenVoteStatus = 'token-votes-status';
const proposalTermsSection = 'proposal'; const proposalJsonToggle = 'proposal-json-toggle';
const proposalJsonSection = 'proposal-json';
const vegaWalletPublicKey = Cypress.env('vegaWalletPublicKey'); const vegaWalletPublicKey = Cypress.env('vegaWalletPublicKey');
const fUSDCId = const fUSDCId =
'816af99af60d684502a40824758f6b5377e6af48e50a9ee8ef478ecb879ea8bc'; '816af99af60d684502a40824758f6b5377e6af48e50a9ee8ef478ecb879ea8bc';
@@ -85,18 +86,20 @@ context(
it('Unable to submit network parameter with missing/invalid fields', function () { it('Unable to submit network parameter with missing/invalid fields', function () {
navigateTo(navigation.proposals); navigateTo(navigation.proposals);
goToMakeNewProposal(governanceProposalType.NETWORK_PARAMETER); goToMakeNewProposal(governanceProposalType.NETWORK_PARAMETER);
cy.get(proposalDownloadBtn).click(); cy.getByTestId(proposalDownloadBtn).click();
cy.get(inputError).should('have.length', 3); cy.getByTestId(inputError).should('have.length', 3);
cy.get(newProposalTitle).type( cy.getByTestId(newProposalTitle).type(
'Invalid update network parameter proposal' 'Invalid update network parameter proposal'
); );
cy.get(newProposalDescription).type('E2E invalid test for proposals'); cy.getByTestId(newProposalDescription).type(
cy.get(proposalParameterSelect).select( 'E2E invalid test for proposals'
);
cy.getByTestId(proposalParameterSelect).select(
'spam_protection_proposal_min_tokens' 'spam_protection_proposal_min_tokens'
); );
cy.get(newProposedParameterValue).type('0'); cy.getByTestId(newProposedParameterValue).type('0');
cy.get(proposalVoteDeadline).clear().type('0'); cy.getByTestId(proposalVoteDeadline).clear().type('0');
cy.get(proposalDownloadBtn) cy.getByTestId(proposalDownloadBtn)
.click() .click()
.then(() => { .then(() => {
cy.wrap( cy.wrap(
@@ -106,7 +109,7 @@ context(
submitUniqueRawProposal({ proposalBody: filePath, submit: false }); submitUniqueRawProposal({ proposalBody: filePath, submit: false });
}); });
}); });
cy.get(newProposalSubmitButton).click(); cy.getByTestId(newProposalSubmitButton).click();
validateDialogContentMsg('PROPOSAL_ERROR_CLOSE_TIME_TOO_SOON'); validateDialogContentMsg('PROPOSAL_ERROR_CLOSE_TIME_TOO_SOON');
}); });
@@ -114,29 +117,33 @@ context(
it('Able to download and submit network param proposal', function () { it('Able to download and submit network param proposal', function () {
goToMakeNewProposal(governanceProposalType.NETWORK_PARAMETER); goToMakeNewProposal(governanceProposalType.NETWORK_PARAMETER);
// 3007-PNEC-006 // 3007-PNEC-006
cy.get(newProposalTitle) cy.getByTestId(newProposalTitle)
.siblings() .siblings()
.should('contain.text', '(100 characters or less)'); .should('contain.text', '(100 characters or less)');
// 3007-PNEC-004 3007-PNEC-005 // 3007-PNEC-004 3007-PNEC-005
cy.get(newProposalTitle).type('Test update network parameter proposal'); cy.getByTestId(newProposalTitle).type(
'Test update network parameter proposal'
);
// 3007-PNEC-009 // 3007-PNEC-009
cy.get(newProposalDescription) cy.getByTestId(newProposalDescription)
.siblings() .siblings()
.should('contain.text', '(20,000 characters or less)'); .should('contain.text', '(20,000 characters or less)');
// 3007-PNEC-007 3007-PNEC-008 // 3007-PNEC-007 3007-PNEC-008
cy.get(newProposalDescription).type('E2E test for downloading proposals'); cy.getByTestId(newProposalDescription).type(
'E2E test for downloading proposals'
);
// 3007-PNEC-010 // 3007-PNEC-010
cy.get(proposalParameterSelect).select( cy.getByTestId(proposalParameterSelect).select(
'governance_proposal_asset_minClose' 'governance_proposal_asset_minClose'
); );
// 3007-PNEC-011 // 3007-PNEC-011
cy.get(newProposedParameterValue).type('10s'); cy.getByTestId(newProposedParameterValue).type('10s');
// 3007-PNEC-012 // 3007-PNEC-012
cy.get(proposalVoteDeadline).clear().type('2'); cy.getByTestId(proposalVoteDeadline).clear().type('2');
// 3007-PNEC-013 3007-PNEC-014 // 3007-PNEC-013 3007-PNEC-014
cy.getByTestId('voting-date').invoke('text').should('not.be.empty'); cy.getByTestId('voting-date').invoke('text').should('not.be.empty');
// 3007-PNEC-015 // 3007-PNEC-015
cy.get(maxEnactDeadline).click(); cy.getByTestId(maxEnactDeadline).click();
// 3007-PNEC-016 // 3007-PNEC-016
cy.getByTestId('enactment-date').invoke('text').should('not.be.empty'); cy.getByTestId('enactment-date').invoke('text').should('not.be.empty');
// 3007-PNEC-017 // 3007-PNEC-017
@@ -145,7 +152,7 @@ context(
).should('be.visible'); ).should('be.visible');
// 3007-PNE-018 // 3007-PNE-018
cy.log('Download updated proposal file'); cy.log('Download updated proposal file');
cy.get(proposalDownloadBtn) cy.getByTestId(proposalDownloadBtn)
.should('be.visible') .should('be.visible')
.click() .click()
.then(() => { .then(() => {
@@ -175,19 +182,21 @@ context(
it('Unable to submit network parameter proposal with vote deadline above enactment deadline', function () { it('Unable to submit network parameter proposal with vote deadline above enactment deadline', function () {
navigateTo(navigation.proposals); navigateTo(navigation.proposals);
goToMakeNewProposal(governanceProposalType.NETWORK_PARAMETER); goToMakeNewProposal(governanceProposalType.NETWORK_PARAMETER);
cy.get(newProposalTitle).type('Test update network parameter proposal'); cy.getByTestId(newProposalTitle).type(
cy.get(newProposalDescription).type('invalid deadlines'); 'Test update network parameter proposal'
cy.get(proposalParameterSelect).select( );
cy.getByTestId(newProposalDescription).type('invalid deadlines');
cy.getByTestId(proposalParameterSelect).select(
'spam_protection_proposal_min_tokens' 'spam_protection_proposal_min_tokens'
); );
cy.get(newProposedParameterValue).type('0'); cy.getByTestId(newProposedParameterValue).type('0');
cy.get(proposalVoteDeadline).clear().type('0'); cy.getByTestId(proposalVoteDeadline).clear().type('0');
cy.get(maxVoteDeadline).click(); cy.getByTestId(maxVoteDeadline).click();
cy.get(enactmentDeadlineError).should( cy.getByTestId(enactmentDeadlineError).should(
'have.text', 'have.text',
'Proposal will fail if enactment is earlier than the voting deadline' 'The proposal will fail if enactment is earlier than the voting deadline'
); );
cy.get(proposalDownloadBtn) cy.getByTestId(proposalDownloadBtn)
.should('be.visible') .should('be.visible')
.click() .click()
.then(() => { .then(() => {
@@ -198,7 +207,7 @@ context(
submitUniqueRawProposal({ proposalBody: filePath, submit: false }); submitUniqueRawProposal({ proposalBody: filePath, submit: false });
}); });
}); });
cy.get(newProposalSubmitButton).click(); cy.getByTestId(newProposalSubmitButton).click();
validateFeedBackMsg( validateFeedBackMsg(
'Invalid params: proposal_submission.terms.closing_timestamp (cannot be after enactment time)' 'Invalid params: proposal_submission.terms.closing_timestamp (cannot be after enactment time)'
); );
@@ -209,17 +218,18 @@ context(
'Able to submit valid new market proposal', 'Able to submit valid new market proposal',
{ tags: '@smoke' }, { tags: '@smoke' },
function () { function () {
const proposalTitle = 'Test new market proposal';
goToMakeNewProposal(governanceProposalType.NEW_MARKET); goToMakeNewProposal(governanceProposalType.NEW_MARKET);
cy.get(newProposalTitle).type('Test new market proposal'); cy.getByTestId(newProposalTitle).type('Test new market proposal');
cy.get(newProposalDescription).type('E2E test for proposals'); cy.getByTestId(newProposalDescription).type('E2E test for proposals');
cy.fixture('/proposals/new-market').then((newMarketProposal) => { cy.fixture('/proposals/new-market').then((newMarketProposal) => {
const newMarketPayload = JSON.stringify(newMarketProposal); const newMarketPayload = JSON.stringify(newMarketProposal);
cy.get(newProposalTerms).type(newMarketPayload, { cy.getByTestId(newProposalTerms).type(newMarketPayload, {
parseSpecialCharSequences: false, parseSpecialCharSequences: false,
delay: 2, delay: 2,
}); });
}); });
cy.get(proposalDownloadBtn) cy.getByTestId(proposalDownloadBtn)
.should('be.visible') .should('be.visible')
.click() .click()
.then(() => { .then(() => {
@@ -230,6 +240,23 @@ context(
submitUniqueRawProposal({ proposalBody: filePath }); // 3003-PMAN-003 submitUniqueRawProposal({ proposalBody: filePath }); // 3003-PMAN-003
}); });
}); });
navigateTo(navigation.proposals);
getProposalFromTitle(proposalTitle).within(() =>
cy.getByTestId('view-proposal-btn').click()
);
cy.getByTestId('proposal-market-data').within(() => {
cy.getByTestId('proposal-market-data-toggle').click();
cy.contains('Key details').click();
getMarketProposalDetailsFromTable('Name').should(
'have.text',
'Token test market'
);
cy.contains('Settlement asset').click();
// Settlement asset symbol
cy.getByTestId('3_value').should('have.text', 'fBTC');
cy.contains('Oracle').click();
cy.getByTestId('oracle-spec-links').should('have.attr', 'href');
});
} }
); );
@@ -238,19 +265,19 @@ context(
'Invalid params: the transaction does not use a valid Vega command: unknown field "invalid" in vega.NewMarket'; 'Invalid params: the transaction does not use a valid Vega command: unknown field "invalid" in vega.NewMarket';
goToMakeNewProposal(governanceProposalType.NEW_MARKET); goToMakeNewProposal(governanceProposalType.NEW_MARKET);
cy.get(proposalDownloadBtn).should('be.visible').click(); cy.getByTestId(proposalDownloadBtn).should('be.visible').click();
cy.get(inputError).should('have.length', 3); cy.getByTestId(inputError).should('have.length', 3);
cy.get(newProposalTitle).type('Test new market proposal'); cy.getByTestId(newProposalTitle).type('Test new market proposal');
cy.get(newProposalDescription).type('E2E test for proposals'); cy.getByTestId(newProposalDescription).type('E2E test for proposals');
cy.fixture('/proposals/new-market').then((newMarketProposal) => { cy.fixture('/proposals/new-market').then((newMarketProposal) => {
newMarketProposal.invalid = 'I am an invalid field'; newMarketProposal.invalid = 'I am an invalid field';
const newMarketPayload = JSON.stringify(newMarketProposal); const newMarketPayload = JSON.stringify(newMarketProposal);
cy.get(newProposalTerms).type(newMarketPayload, { cy.getByTestId(newProposalTerms).type(newMarketPayload, {
parseSpecialCharSequences: false, parseSpecialCharSequences: false,
delay: 2, delay: 2,
}); });
}); });
cy.get(proposalDownloadBtn) cy.getByTestId(proposalDownloadBtn)
.should('be.visible') .should('be.visible')
.click() .click()
.then(() => { .then(() => {
@@ -261,7 +288,7 @@ context(
submitUniqueRawProposal({ proposalBody: filePath, submit: false }); submitUniqueRawProposal({ proposalBody: filePath, submit: false });
}); });
}); });
cy.get(newProposalSubmitButton).should('be.visible').click(); cy.getByTestId(newProposalSubmitButton).should('be.visible').click();
cy.contains('Transaction failed', proposalTimeout).should('be.visible'); cy.contains('Transaction failed', proposalTimeout).should('be.visible');
validateFeedBackMsg(errorMsg); validateFeedBackMsg(errorMsg);
}); });
@@ -269,21 +296,22 @@ context(
// Will fail if run after 'Able to submit update market proposal and vote for proposal' // Will fail if run after 'Able to submit update market proposal and vote for proposal'
// 3002-PROP-022 // 3002-PROP-022
it('Unable to submit update market proposal without equity-like share in the market', function () { it('Unable to submit update market proposal without equity-like share in the market', function () {
cy.get('[data-testid="manage-vega-wallet"]:visible').click(); switchVegaWalletPubKey();
cy.get('[data-testid="select-keypair-button"]').eq(0).click(); // switch to second wallet pub key
stakingPageAssociateTokens('1'); stakingPageAssociateTokens('1');
goToMakeNewProposal(governanceProposalType.UPDATE_MARKET); goToMakeNewProposal(governanceProposalType.UPDATE_MARKET);
cy.get(newProposalTitle).type('Test update market proposal - rejected'); cy.getByTestId(newProposalTitle).type(
cy.get(newProposalDescription).type('E2E test for proposals'); 'Test update market proposal - rejected'
cy.get(proposalMarketSelect).select('Test market 1'); );
cy.getByTestId(newProposalDescription).type('E2E test for proposals');
cy.getByTestId(proposalMarketSelect).select('Test market 1');
cy.fixture('/proposals/update-market').then((updateMarketProposal) => { cy.fixture('/proposals/update-market').then((updateMarketProposal) => {
const newUpdateMarketProposal = JSON.stringify(updateMarketProposal); const newUpdateMarketProposal = JSON.stringify(updateMarketProposal);
cy.get(newProposalTerms).type(newUpdateMarketProposal, { cy.getByTestId(newProposalTerms).type(newUpdateMarketProposal, {
parseSpecialCharSequences: false, parseSpecialCharSequences: false,
delay: 2, delay: 2,
}); });
}); });
cy.get(proposalDownloadBtn) cy.getByTestId(proposalDownloadBtn)
.should('be.visible') .should('be.visible')
.click() .click()
.then(() => { .then(() => {
@@ -294,14 +322,13 @@ context(
submitUniqueRawProposal({ proposalBody: filePath, submit: false }); submitUniqueRawProposal({ proposalBody: filePath, submit: false });
}); });
}); });
cy.get(newProposalSubmitButton).should('be.visible').click(); cy.getByTestId(newProposalSubmitButton).should('be.visible').click();
cy.contains('Proposal rejected', proposalTimeout).should('be.visible'); cy.contains('Proposal rejected', proposalTimeout).should('be.visible');
validateDialogContentMsg('PROPOSAL_ERROR_INSUFFICIENT_EQUITY_LIKE_SHARE'); validateDialogContentMsg('PROPOSAL_ERROR_INSUFFICIENT_EQUITY_LIKE_SHARE');
closeDialog(); closeDialog();
ethereumWalletConnect(); ethereumWalletConnect();
stakingPageDisassociateAllTokens(); stakingPageDisassociateAllTokens();
cy.get('[data-testid="manage-vega-wallet"]:visible').click(); switchVegaWalletPubKey();
cy.get('[data-testid="select-keypair-button"]').eq(0).click();
}); });
// 3002-PROP-020 // 3002-PROP-020
@@ -313,17 +340,19 @@ context(
vegaWalletPublicKey vegaWalletPublicKey
); );
goToMakeNewProposal(governanceProposalType.UPDATE_MARKET); goToMakeNewProposal(governanceProposalType.UPDATE_MARKET);
cy.get(newProposalTitle).type('Test update market proposal - rejected'); cy.getByTestId(newProposalTitle).type(
cy.get(newProposalDescription).type('E2E test for proposals'); 'Test update market proposal - rejected'
cy.get(proposalMarketSelect).select('Test market 1'); );
cy.getByTestId(newProposalDescription).type('E2E test for proposals');
cy.getByTestId(proposalMarketSelect).select('Test market 1');
cy.fixture('/proposals/update-market').then((updateMarketProposal) => { cy.fixture('/proposals/update-market').then((updateMarketProposal) => {
const newUpdateMarketProposal = JSON.stringify(updateMarketProposal); const newUpdateMarketProposal = JSON.stringify(updateMarketProposal);
cy.get(newProposalTerms).type(newUpdateMarketProposal, { cy.getByTestId(newProposalTerms).type(newUpdateMarketProposal, {
parseSpecialCharSequences: false, parseSpecialCharSequences: false,
delay: 2, delay: 2,
}); });
}); });
cy.get(proposalDownloadBtn) cy.getByTestId(proposalDownloadBtn)
.should('be.visible') .should('be.visible')
.click() .click()
.then(() => { .then(() => {
@@ -334,7 +363,7 @@ context(
submitUniqueRawProposal({ proposalBody: filePath, submit: false }); submitUniqueRawProposal({ proposalBody: filePath, submit: false });
}); });
}); });
cy.get(newProposalSubmitButton).should('be.visible').click(); cy.getByTestId(newProposalSubmitButton).should('be.visible').click();
cy.contains('Transaction failed', proposalTimeout).should('be.visible'); cy.contains('Transaction failed', proposalTimeout).should('be.visible');
validateFeedBackMsg( 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)' '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)'
@@ -349,9 +378,9 @@ context(
vegaWalletPublicKey vegaWalletPublicKey
); );
goToMakeNewProposal(governanceProposalType.UPDATE_MARKET); goToMakeNewProposal(governanceProposalType.UPDATE_MARKET);
cy.get(newProposalTitle).type('Test update market proposal'); cy.getByTestId(newProposalTitle).type('Test update market proposal');
cy.get(newProposalDescription).type('E2E test for proposals'); cy.getByTestId(newProposalDescription).type('E2E test for proposals');
cy.get(proposalMarketSelect).select('Test market 1'); cy.getByTestId(proposalMarketSelect).select('Test market 1');
cy.get('[data-testid="update-market-details"]').within(() => { cy.get('[data-testid="update-market-details"]').within(() => {
cy.get('dd').eq(0).should('have.text', 'Test market 1'); cy.get('dd').eq(0).should('have.text', 'Test market 1');
cy.get('dd').eq(1).should('have.text', 'TEST.24h'); cy.get('dd').eq(1).should('have.text', 'TEST.24h');
@@ -366,12 +395,12 @@ context(
}); });
cy.fixture('/proposals/update-market').then((updateMarketProposal) => { cy.fixture('/proposals/update-market').then((updateMarketProposal) => {
const newUpdateMarketProposal = JSON.stringify(updateMarketProposal); const newUpdateMarketProposal = JSON.stringify(updateMarketProposal);
cy.get(newProposalTerms).type(newUpdateMarketProposal, { cy.getByTestId(newProposalTerms).type(newUpdateMarketProposal, {
parseSpecialCharSequences: false, parseSpecialCharSequences: false,
delay: 2, delay: 2,
}); });
}); });
cy.get(proposalDownloadBtn) cy.getByTestId(proposalDownloadBtn)
.should('be.visible') .should('be.visible')
.click() .click()
.then(() => { .then(() => {
@@ -418,19 +447,19 @@ context(
it('Able to submit new asset proposal using min deadlines', function () { it('Able to submit new asset proposal using min deadlines', function () {
const proposalTitle = 'Test new asset proposal'; const proposalTitle = 'Test new asset proposal';
goToMakeNewProposal(governanceProposalType.NEW_ASSET); goToMakeNewProposal(governanceProposalType.NEW_ASSET);
cy.get(newProposalTitle).type(proposalTitle); cy.getByTestId(newProposalTitle).type(proposalTitle);
cy.get(newProposalDescription).type('E2E test for proposals'); cy.getByTestId(newProposalDescription).type('E2E test for proposals');
cy.fixture('/proposals/new-asset').then((newAssetProposal) => { cy.fixture('/proposals/new-asset').then((newAssetProposal) => {
const newAssetPayload = JSON.stringify(newAssetProposal); const newAssetPayload = JSON.stringify(newAssetProposal);
cy.get(newProposalTerms).type(newAssetPayload, { cy.getByTestId(newProposalTerms).type(newAssetPayload, {
parseSpecialCharSequences: false, parseSpecialCharSequences: false,
delay: 2, delay: 2,
}); });
}); });
cy.get(minVoteDeadline).click(); cy.getByTestId(minVoteDeadline).click();
cy.get(minValidationDeadline).click(); cy.getByTestId(minValidationDeadline).click();
cy.get(minEnactDeadline).click(); cy.getByTestId(minEnactDeadline).click();
cy.get(proposalDownloadBtn) cy.getByTestId(proposalDownloadBtn)
.should('be.visible') .should('be.visible')
.click() .click()
.then(() => { .then(() => {
@@ -441,9 +470,9 @@ context(
submitUniqueRawProposal({ proposalBody: filePath, submit: false }); // 3005-PASN-003 submitUniqueRawProposal({ proposalBody: filePath, submit: false }); // 3005-PASN-003
}); });
}); });
cy.get(newProposalSubmitButton).should('be.visible').click(); cy.getByTestId(newProposalSubmitButton).should('be.visible').click();
closeDialog(); closeDialog();
cy.get(newProposalSubmitButton).should('be.visible').click(); cy.getByTestId(newProposalSubmitButton).should('be.visible').click();
// cannot submit a proposal with ERC20 address already in use // cannot submit a proposal with ERC20 address already in use
cy.contains('Proposal rejected', proposalTimeout).should('be.visible'); cy.contains('Proposal rejected', proposalTimeout).should('be.visible');
validateDialogContentMsg('PROPOSAL_ERROR_ERC20_ADDRESS_ALREADY_IN_USE'); validateDialogContentMsg('PROPOSAL_ERROR_ERC20_ADDRESS_ALREADY_IN_USE');
@@ -455,8 +484,8 @@ context(
.within(() => { .within(() => {
cy.getByTestId(viewProposalBtn).click(); cy.getByTestId(viewProposalBtn).click();
}); });
cy.getByTestId('proposal-terms-toggle').click(); cy.getByTestId(proposalJsonToggle).click();
cy.getByTestId(proposalTermsSection).within(() => { cy.getByTestId(proposalJsonSection).within(() => {
cy.contains('USDT Coin').should('be.visible'); cy.contains('USDT Coin').should('be.visible');
cy.contains('USDT').should('be.visible'); cy.contains('USDT').should('be.visible');
}); });
@@ -464,8 +493,8 @@ context(
it('Unable to submit new asset proposal with missing/invalid fields', function () { it('Unable to submit new asset proposal with missing/invalid fields', function () {
goToMakeNewProposal(governanceProposalType.NEW_ASSET); goToMakeNewProposal(governanceProposalType.NEW_ASSET);
cy.get(proposalDownloadBtn).should('be.visible').click(); cy.getByTestId(proposalDownloadBtn).should('be.visible').click();
cy.get(inputError).should('have.length', 3); cy.getByTestId(inputError).should('have.length', 3);
}); });
it('Able to submit update asset proposal using min deadline', function () { it('Able to submit update asset proposal using min deadline', function () {
@@ -474,9 +503,9 @@ context(
goToMakeNewProposal(governanceProposalType.UPDATE_ASSET); goToMakeNewProposal(governanceProposalType.UPDATE_ASSET);
enterUpdateAssetProposalDetails(); enterUpdateAssetProposalDetails();
cy.get(minVoteDeadline).click(); cy.getByTestId(minVoteDeadline).click();
cy.get(minEnactDeadline).click(); cy.getByTestId(minEnactDeadline).click();
cy.get(proposalDownloadBtn) cy.getByTestId(proposalDownloadBtn)
.should('be.visible') .should('be.visible')
.click() .click()
.then(() => { .then(() => {
@@ -488,13 +517,16 @@ context(
}); });
}); });
navigateTo(navigation.proposals); navigateTo(navigation.proposals);
cy.get(openProposals).within(() => { cy.getByTestId(openProposals).within(() => {
cy.get(proposalType) cy.getByTestId(proposalType)
.contains('Update asset') .contains('Update asset')
.parentsUntil(proposalListItem) .parentsUntil(proposalListItem)
.last() .last()
.within(() => { .within(() => {
cy.get(proposalDetails).should('contain.text', assetId.slice(0, 6)); // 3001-VOTE-029 cy.getByTestId(proposalDetails).should(
'contain.text',
assetId.slice(0, 6)
); // 3001-VOTE-029
cy.getByTestId(viewProposalBtn).click(); cy.getByTestId(viewProposalBtn).click();
}); });
}); });
@@ -502,13 +534,11 @@ context(
.invoke('text') .invoke('text')
.should('not.be.empty'); .should('not.be.empty');
// 3001-VOTE-030 3001-VOTE-031 // 3001-VOTE-030 3001-VOTE-031
cy.getByTestId('proposal-terms-toggle').click(); cy.getByTestId(proposalJsonToggle).click();
cy.getByTestId('proposal-terms').within(() => { cy.getByTestId(proposalJsonSection).within(() => {
getProposalInformationFromTable('assetId').should('have.text', assetId); cy.contains(assetId).should('be.visible');
getProposalInformationFromTable('lifetimeLimit').should( cy.contains('lifetimeLimit').should('be.visible');
'have.text', cy.contains('10').should('be.visible');
'10'
);
}); });
}); });
@@ -516,9 +546,9 @@ context(
it('Able to submit update asset proposal using max deadline', function () { it('Able to submit update asset proposal using max deadline', function () {
goToMakeNewProposal(governanceProposalType.UPDATE_ASSET); goToMakeNewProposal(governanceProposalType.UPDATE_ASSET);
enterUpdateAssetProposalDetails(); enterUpdateAssetProposalDetails();
cy.get(maxVoteDeadline).click(); cy.getByTestId(maxVoteDeadline).click();
cy.get(maxEnactDeadline).click(); cy.getByTestId(maxEnactDeadline).click();
cy.get(proposalDownloadBtn) cy.getByTestId(proposalDownloadBtn)
.should('be.visible') .should('be.visible')
.click() .click()
.then(() => { .then(() => {
@@ -533,36 +563,36 @@ context(
it('Unable to submit edit asset proposal with missing/invalid fields', function () { it('Unable to submit edit asset proposal with missing/invalid fields', function () {
goToMakeNewProposal(governanceProposalType.UPDATE_ASSET); goToMakeNewProposal(governanceProposalType.UPDATE_ASSET);
cy.get(proposalDownloadBtn).should('be.visible').click(); cy.getByTestId(proposalDownloadBtn).should('be.visible').click();
cy.get(inputError).should('have.length', 3); cy.getByTestId(inputError).should('have.length', 3);
}); });
it('Able to download and submit freeform proposal', function () { it('Able to download and submit freeform proposal', function () {
goToMakeNewProposal(governanceProposalType.FREEFORM); goToMakeNewProposal(governanceProposalType.FREEFORM);
// 3008-PFRO-006 // 3008-PFRO-006
cy.get(newProposalTitle) cy.getByTestId(newProposalTitle)
.siblings() .siblings()
.should('contain.text', '(100 characters or less)'); // 3008-PFRO-007 .should('contain.text', '(100 characters or less)'); // 3008-PFRO-007
// 3008-PFRO-005 // 3008-PFRO-005
cy.get(newProposalTitle).type('Test freeform proposal form'); cy.getByTestId(newProposalTitle).type('Test freeform proposal form');
// 3008-PFRO-009 // 3008-PFRO-009
cy.get(newProposalDescription) cy.getByTestId(newProposalDescription)
.siblings() .siblings()
.should('contain.text', '(20,000 characters or less)'); // 3008-PFRO-010 .should('contain.text', '(20,000 characters or less)'); // 3008-PFRO-010
// 3008-PFRO-008 3002-PROP-012 3002-PROP-016 // 3008-PFRO-008 3002-PROP-012 3002-PROP-016
cy.get(newProposalDescription).type( cy.getByTestId(newProposalDescription).type(
'E2E test for downloading freeform proposal' 'E2E test for downloading freeform proposal'
); );
// 3008-PFRO-012 // 3008-PFRO-012
cy.get(minVoteDeadline).should('exist'); // 3002-PROP-008 cy.getByTestId(minVoteDeadline).should('exist'); // 3002-PROP-008
cy.get(maxVoteDeadline).should('exist'); cy.getByTestId(maxVoteDeadline).should('exist');
// 3008-PFRO-011 // 3008-PFRO-011
cy.get(proposalVoteDeadline).clear().type('2'); cy.getByTestId(proposalVoteDeadline).clear().type('2');
// 3008-PFRO-013 3008-PFRO-014 // 3008-PFRO-013 3008-PFRO-014
cy.getByTestId('voting-date').invoke('text').should('not.be.empty'); cy.getByTestId('voting-date').invoke('text').should('not.be.empty');
// 3008-PFRO-015 // 3008-PFRO-015
cy.log('Download updated proposal file'); cy.log('Download updated proposal file');
cy.get(proposalDownloadBtn) cy.getByTestId(proposalDownloadBtn)
.should('be.visible') .should('be.visible')
.click() .click()
.then(() => { .then(() => {
@@ -596,15 +626,23 @@ context(
} }
function enterUpdateAssetProposalDetails() { function enterUpdateAssetProposalDetails() {
cy.get(newProposalTitle).type('Test update asset proposal'); cy.getByTestId(newProposalTitle).type('Test update asset proposal');
cy.get(newProposalDescription).type('E2E test for proposals'); cy.getByTestId(newProposalDescription).type('E2E test for proposals');
cy.fixture('/proposals/update-asset').then((newAssetProposal) => { cy.fixture('/proposals/update-asset').then((newAssetProposal) => {
const newAssetPayload = JSON.stringify(newAssetProposal); const newAssetPayload = JSON.stringify(newAssetProposal);
cy.get(newProposalTerms).type(newAssetPayload, { cy.getByTestId(newProposalTerms).type(newAssetPayload, {
parseSpecialCharSequences: false, parseSpecialCharSequences: false,
delay: 2, delay: 2,
}); });
}); });
} }
function getMarketProposalDetailsFromTable(heading: string) {
return cy
.getByTestId('key-value-table-row')
.contains(heading)
.parent()
.siblings();
}
} }
); );
@@ -21,15 +21,15 @@ import {
} from '../../support/governance.functions'; } from '../../support/governance.functions';
import { ensureSpecifiedUnstakedTokensAreAssociated } from '../../support/staking.functions'; import { ensureSpecifiedUnstakedTokensAreAssociated } from '../../support/staking.functions';
import { ethereumWalletConnect } from '../../support/wallet-eth.functions'; import { ethereumWalletConnect } from '../../support/wallet-eth.functions';
import { vegaWalletSetSpecifiedApprovalAmount } from '../../support/wallet-teardown.functions'; import { vegaWalletSetSpecifiedApprovalAmount } from '../../support/wallet-functions';
const proposalListItem = 'proposals-list-item'; const proposalListItem = 'proposals-list-item';
const openProposals = '[data-testid="open-proposals"]'; const openProposals = 'open-proposals';
const voteStatus = 'vote-status'; const voteStatus = 'vote-status';
const proposalType = 'proposal-type'; const proposalType = 'proposal-type';
const proposalStatus = 'proposal-status'; const proposalStatus = 'proposal-status';
const proposalClosingDate = '[data-testid="vote-details"]'; const proposalClosingDate = 'vote-details';
const viewProposalButton = '[data-testid="view-proposal-btn"]'; const viewProposalButton = 'view-proposal-btn';
const voteBreakDownToggle = 'vote-breakdown-toggle'; const voteBreakDownToggle = 'vote-breakdown-toggle';
describe('Governance flow for proposal list', { tags: '@slow' }, function () { 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); navigateTo(navigation.proposals);
cy.get(openProposals).within(() => { cy.getByTestId(openProposals).within(() => {
cy.get(proposalClosingDate) cy.getByTestId(proposalClosingDate)
.first() .first()
.invoke('text') .invoke('text')
.should('match', /days|minutes/); .should('match', /days|minutes/);
cy.get(proposalClosingDate).should('contain.text', 'months'); cy.getByTestId(proposalClosingDate).should('contain.text', 'months');
cy.get(proposalClosingDate).last().should('contain.text', 'year'); cy.getByTestId(proposalClosingDate).last().should('contain.text', 'year');
}); });
}); });
@@ -77,7 +77,7 @@ describe('Governance flow for proposal list', { tags: '@slow' }, function () {
const proposalTitle = generateFreeFormProposalTitle(); const proposalTitle = generateFreeFormProposalTitle();
submitUniqueRawProposal({ proposalTitle: proposalTitle }); submitUniqueRawProposal({ proposalTitle: proposalTitle });
cy.get('[data-testid="set-proposals-filter-visible"]').click(); cy.get('[data-testid="proposal-filter-toggle"]').click();
cy.get('[data-testid="filter-input"]').type(proposerId); cy.get('[data-testid="filter-input"]').type(proposerId);
// cy.get(`#${proposalId}`).should('contain', proposalId); // cy.get(`#${proposalId}`).should('contain', proposalId);
cy.contains(proposalTitle).should('be.visible'); cy.contains(proposalTitle).should('be.visible');
@@ -106,7 +106,7 @@ describe('Governance flow for proposal list', { tags: '@slow' }, function () {
createRawProposal(this.minProposerBalance); createRawProposal(this.minProposerBalance);
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => { cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
getProposalFromTitle(rawProposal.rationale.title).within(() => { getProposalFromTitle(rawProposal.rationale.title).within(() => {
cy.get(viewProposalButton).should('be.visible'); cy.getByTestId(viewProposalButton).should('be.visible');
cy.getByTestId(proposalType).should('have.text', 'Freeform'); cy.getByTestId(proposalType).should('have.text', 'Freeform');
cy.getByTestId(proposalStatus).should('have.text', 'Open'); cy.getByTestId(proposalStatus).should('have.text', 'Open');
}); });
@@ -124,13 +124,13 @@ describe('Governance flow for proposal list', { tags: '@slow' }, function () {
'have.text', 'have.text',
'Participation not reached' 'Participation not reached'
); );
cy.get(viewProposalButton).click(); cy.getByTestId(viewProposalButton).click();
}); });
voteForProposal('for'); voteForProposal('for');
navigateTo(navigation.proposals); navigateTo(navigation.proposals);
getProposalFromTitle(proposalTitle).within(() => { getProposalFromTitle(proposalTitle).within(() => {
cy.getByTestId(voteStatus).should('have.text', 'Set to pass'); cy.getByTestId(voteStatus).should('have.text', 'Set to pass');
cy.get(viewProposalButton).click(); cy.getByTestId(viewProposalButton).click();
}); });
cy.getByTestId(voteBreakDownToggle).click(); cy.getByTestId(voteBreakDownToggle).click();
getProposalInformationFromTable('Token participation met') getProposalInformationFromTable('Token participation met')
@@ -14,11 +14,10 @@ import { ethereumWalletConnect } from '../../support/wallet-eth.functions';
import { import {
depositAsset, depositAsset,
vegaWalletTeardown, vegaWalletTeardown,
} from '../../support/wallet-teardown.functions'; } from '../../support/wallet-functions';
const vegaAssetAddress = '0x67175Da1D5e966e40D11c4B2519392B2058373de'; const vegaAssetAddress = '0x67175Da1D5e966e40D11c4B2519392B2058373de';
const vegaWalletUnstakedBalance = const vegaWalletUnstakedBalance = 'vega-wallet-balance-unstaked';
'[data-testid="vega-wallet-balance-unstaked"]';
const rewardsTable = 'epoch-total-rewards-table'; const rewardsTable = 'epoch-total-rewards-table';
const txTimeout = Cypress.env('txTimeout'); const txTimeout = Cypress.env('txTimeout');
const rewardsTimeOut = { timeout: 60000 }; const rewardsTimeOut = { timeout: 60000 };
@@ -40,7 +39,7 @@ context('rewards - flow', { tags: '@slow' }, function () {
cy.associateTokensToVegaWallet('6000'); cy.associateTokensToVegaWallet('6000');
navigateTo(navigation.validators); navigateTo(navigation.validators);
cy.VegaWalletTopUpRewardsPool(); cy.VegaWalletTopUpRewardsPool();
cy.get(vegaWalletUnstakedBalance, txTimeout).should( cy.getByTestId(vegaWalletUnstakedBalance, txTimeout).should(
'contain', 'contain',
'6,000.0', '6,000.0',
txTimeout txTimeout
@@ -4,7 +4,6 @@ import {
verifyStakedBalance, verifyStakedBalance,
verifyEthWalletTotalAssociatedBalance, verifyEthWalletTotalAssociatedBalance,
verifyEthWalletAssociatedBalance, verifyEthWalletAssociatedBalance,
waitForSpinner,
navigateTo, navigateTo,
navigation, navigation,
turnTelemetryOff, turnTelemetryOff,
@@ -25,7 +24,7 @@ import { ethereumWalletConnect } from '../../support/wallet-eth.functions';
import { import {
vegaWalletSetSpecifiedApprovalAmount, vegaWalletSetSpecifiedApprovalAmount,
vegaWalletTeardown, vegaWalletTeardown,
} from '../../support/wallet-teardown.functions'; } from '../../support/wallet-functions';
const stakeValidatorListTotalStake = 'total-stake'; const stakeValidatorListTotalStake = 'total-stake';
const stakeValidatorListTotalShare = 'total-stake-share'; const stakeValidatorListTotalShare = 'total-stake-share';
const stakeValidatorListStakePercentage = 'stake-percentage'; const stakeValidatorListStakePercentage = 'stake-percentage';
@@ -58,8 +57,7 @@ context(
before('visit staking tab and connect vega wallet', function () { before('visit staking tab and connect vega wallet', function () {
cy.visit('/'); cy.visit('/');
ethereumWalletConnect(); ethereumWalletConnect();
// this is a workaround for #2422 which can be removed once issue is resolved cy.connectVegaWallet();
cy.associateTokensToVegaWallet('4');
vegaWalletSetSpecifiedApprovalAmount('1000'); vegaWalletSetSpecifiedApprovalAmount('1000');
}); });
@@ -69,10 +67,9 @@ context(
function () { function () {
cy.clearLocalStorage(); cy.clearLocalStorage();
turnTelemetryOff(); turnTelemetryOff();
cy.reload(); // Go to homepage to allow wallet teardown without epoch timer refreshing page
waitForSpinner(); navigateTo(navigation.home);
cy.connectVegaWallet(); vegaWalletTeardown();
ethereumWalletConnect();
navigateTo(navigation.validators); navigateTo(navigation.validators);
} }
); );
@@ -130,6 +127,7 @@ context(
cy.getByTestId('staked-by-user-tooltip') cy.getByTestId('staked-by-user-tooltip')
.first() .first()
.should('have.text', 'Staked by me: 2.00'); .should('have.text', 'Staked by me: 2.00');
waitForBeginningOfEpoch();
cy.getByTestId('total-pending-stake').first().realHover(); cy.getByTestId('total-pending-stake').first().realHover();
cy.getByTestId('pending-user-stake-tooltip') cy.getByTestId('pending-user-stake-tooltip')
.first() .first()
@@ -400,6 +398,7 @@ context(
vegaWalletSetSpecifiedApprovalAmount('1000'); vegaWalletSetSpecifiedApprovalAmount('1000');
cy.reload(); cy.reload();
ethereumWalletConnect(); ethereumWalletConnect();
cy.connectVegaWallet();
stakingPageAssociateTokens('3'); stakingPageAssociateTokens('3');
verifyUnstakedBalance(3.0); verifyUnstakedBalance(3.0);
cy.get('button').contains('Select a validator to nominate').click(); cy.get('button').contains('Select a validator to nominate').click();
@@ -505,11 +504,6 @@ context(
); );
}); });
afterEach('Teardown Wallet', function () {
navigateTo(navigation.home);
vegaWalletTeardown();
});
function verifyNextEpochValue(amount: number) { function verifyNextEpochValue(amount: number) {
cy.getByTestId('stake-next-epoch', epochTimeout) cy.getByTestId('stake-next-epoch', epochTimeout)
.contains(amount, epochTimeout) .contains(amount, epochTimeout)
@@ -14,31 +14,31 @@ import {
} from '../../support/staking.functions'; } from '../../support/staking.functions';
import { ethereumWalletConnect } from '../../support/wallet-eth.functions'; import { ethereumWalletConnect } from '../../support/wallet-eth.functions';
import { import {
switchVegaWalletPubKey,
vegaWalletAssociate, vegaWalletAssociate,
vegaWalletDisassociate, vegaWalletDisassociate,
vegaWalletSetSpecifiedApprovalAmount, vegaWalletSetSpecifiedApprovalAmount,
vegaWalletTeardown, vegaWalletTeardown,
} from '../../support/wallet-teardown.functions'; } from '../../support/wallet-functions';
const ethWalletContainer = '[data-testid="ethereum-wallet"]'; const ethWalletContainer = 'ethereum-wallet';
const vegaWalletAssociatedBalance = '[data-testid="currency-value"]'; const vegaWalletAssociatedBalance = 'currency-value';
const vegaWalletUnstakedBalance = const vegaWalletUnstakedBalance = 'vega-wallet-balance-unstaked';
'[data-testid="vega-wallet-balance-unstaked"]';
const txTimeout = Cypress.env('txTimeout');
const vegaWalletPublicKeyShort = Cypress.env('vegaWalletPublicKeyShort');
const ethWalletAssociateButton = '[data-testid="associate-btn"]:visible'; const ethWalletAssociateButton = '[data-testid="associate-btn"]:visible';
const associateWalletRadioButton = '[data-testid="associate-radio-wallet"]'; const associateWalletRadioButton = 'associate-radio-wallet';
const tokenAmountInputBox = '[data-testid="token-amount-input"]'; const tokenAmountInputBox = 'token-amount-input';
const tokenSubmitButton = '[data-testid="token-input-submit-button"]'; const tokenSubmitButton = 'token-input-submit-button';
const ethWalletDissociateButton = '[href="/token/disassociate"]:visible'; const ethWalletDissociateButton = '[href="/token/disassociate"]:visible';
const vestingContractSection = '[data-testid="vega-in-vesting-contract"]'; const vestingContractSection = 'vega-in-vesting-contract';
const vegaInWalletSection = '[data-testid="vega-in-wallet"]'; const vegaInWalletSection = 'vega-in-wallet';
const connectedVegaKey = '[data-testid="connected-vega-key"]'; const connectedVegaKey = 'connected-vega-key';
const associatedKey = '[data-testid="associated-key"]'; const associatedKey = 'associated-key';
const associatedAmount = '[data-testid="associated-amount"]'; const associatedAmount = 'associated-amount';
const associateCompleteText = '[data-testid="transaction-complete-body"]'; const associateCompleteText = 'transaction-complete-body';
const disassociationWarning = '[data-testid="disassociation-warning"]'; const disassociationWarning = 'disassociation-warning';
const vegaWallet = 'aside [data-testid="vega-wallet"]'; const vegaWallet = 'aside [data-testid="vega-wallet"]';
const vegaWalletPublicKeyShort = Cypress.env('vegaWalletPublicKeyShort');
const txTimeout = Cypress.env('txTimeout');
context( context(
'Token association flow - with eth and vega wallets connected', 'Token association flow - with eth and vega wallets connected',
@@ -88,12 +88,15 @@ context(
verifyEthWalletAssociatedBalance('2.0'); verifyEthWalletAssociatedBalance('2.0');
verifyEthWalletTotalAssociatedBalance('2.0'); verifyEthWalletTotalAssociatedBalance('2.0');
cy.get(vegaWallet).within(() => { cy.get(vegaWallet).within(() => {
cy.get(vegaWalletAssociatedBalance, txTimeout).should( cy.getByTestId(vegaWalletAssociatedBalance, txTimeout).should(
'contain', 'contain',
2.0 2.0
); );
}); });
cy.get(vegaWalletUnstakedBalance, txTimeout).should('contain', 2.0); cy.getByTestId(vegaWalletUnstakedBalance, txTimeout).should(
'contain',
2.0
);
} }
); );
@@ -126,7 +129,7 @@ context(
verifyEthWalletAssociatedBalance('1,001.00'); verifyEthWalletAssociatedBalance('1,001.00');
verifyEthWalletTotalAssociatedBalance('7,001.00'); verifyEthWalletTotalAssociatedBalance('7,001.00');
cy.get(vegaWallet).within(() => { cy.get(vegaWallet).within(() => {
cy.get(vegaWalletAssociatedBalance, txTimeout).should( cy.getByTestId(vegaWalletAssociatedBalance, txTimeout).should(
'contain', 'contain',
'1,001.00' '1,001.00'
); );
@@ -136,14 +139,20 @@ context(
it('Able to disassociate a partial amount of tokens currently associated', function () { it('Able to disassociate a partial amount of tokens currently associated', function () {
stakingPageAssociateTokens('2'); stakingPageAssociateTokens('2');
cy.get(vegaWallet).within(() => { cy.get(vegaWallet).within(() => {
cy.get(vegaWalletAssociatedBalance, txTimeout).should('contain', 2.0); cy.getByTestId(vegaWalletAssociatedBalance, txTimeout).should(
'contain',
2.0
);
}); });
cy.get('button').contains('Select a validator to nominate').click(); cy.get('button').contains('Select a validator to nominate').click();
cy.getByTestId('epoch-countdown').should('be.visible'); cy.getByTestId('epoch-countdown').should('be.visible');
stakingPageDisassociateTokens('1'); stakingPageDisassociateTokens('1');
verifyEthWalletAssociatedBalance('1.0'); verifyEthWalletAssociatedBalance('1.0');
cy.get(vegaWallet).within(() => { cy.get(vegaWallet).within(() => {
cy.get(vegaWalletAssociatedBalance, txTimeout).should('contain', 1.0); cy.getByTestId(vegaWalletAssociatedBalance, txTimeout).should(
'contain',
1.0
);
}); });
}); });
@@ -153,21 +162,24 @@ 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.'; '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'); stakingPageAssociateTokens('2');
cy.get(vegaWallet).within(() => { cy.get(vegaWallet).within(() => {
cy.get(vegaWalletAssociatedBalance, txTimeout).should('contain', 2.0); cy.getByTestId(vegaWalletAssociatedBalance, txTimeout).should(
'contain',
2.0
);
}); });
cy.get('button').contains('Select a validator to nominate').click(); cy.get('button').contains('Select a validator to nominate').click();
cy.getByTestId('epoch-countdown').should('be.visible'); cy.getByTestId('epoch-countdown').should('be.visible');
cy.get(ethWalletDissociateButton).click(); cy.get(ethWalletDissociateButton).click();
cy.get(disassociationWarning).should('contain', warningText); cy.getByTestId(disassociationWarning).should('contain', warningText);
stakingPageDisassociateAllTokens(); stakingPageDisassociateAllTokens();
cy.get(ethWalletContainer) cy.getByTestId(ethWalletContainer)
.first() .first()
.within(() => { .within(() => {
cy.contains(vegaWalletPublicKeyShort, { timeout: 20000 }).should( cy.contains(vegaWalletPublicKeyShort, { timeout: 20000 }).should(
'not.exist' 'not.exist'
); );
}); });
cy.get(ethWalletContainer) cy.getByTestId(ethWalletContainer)
.first() .first()
.within(() => { .within(() => {
cy.contains(vegaWalletPublicKeyShort, { timeout: 20000 }).should( cy.contains(vegaWalletPublicKeyShort, { timeout: 20000 }).should(
@@ -175,7 +187,10 @@ context(
); );
}); });
cy.get(vegaWallet).within(() => { cy.get(vegaWallet).within(() => {
cy.get(vegaWalletAssociatedBalance, txTimeout).should('contain', 0.0); cy.getByTestId(vegaWalletAssociatedBalance, txTimeout).should(
'contain',
0.0
);
}); });
}); });
@@ -197,9 +212,15 @@ context(
verifyEthWalletAssociatedBalance('2.0'); verifyEthWalletAssociatedBalance('2.0');
verifyEthWalletTotalAssociatedBalance('2.0'); verifyEthWalletTotalAssociatedBalance('2.0');
cy.get(vegaWallet).within(() => { cy.get(vegaWallet).within(() => {
cy.get(vegaWalletAssociatedBalance, txTimeout).should('contain', 2.0); cy.getByTestId(vegaWalletAssociatedBalance, txTimeout).should(
'contain',
2.0
);
}); });
cy.get(vegaWalletUnstakedBalance, txTimeout).should('contain', 2.0); cy.getByTestId(vegaWalletUnstakedBalance, txTimeout).should(
'contain',
2.0
);
stakingPageDisassociateTokens('1', { stakingPageDisassociateTokens('1', {
type: 'contract', type: 'contract',
skipConfirmation: true, skipConfirmation: true,
@@ -220,45 +241,54 @@ context(
cy.get('button').contains('Select a validator to nominate').click(); cy.get('button').contains('Select a validator to nominate').click();
cy.getByTestId('epoch-countdown').should('be.visible'); cy.getByTestId('epoch-countdown').should('be.visible');
stakingPageAssociateTokens('37', { type: 'contract' }); stakingPageAssociateTokens('37', { type: 'contract' });
cy.get(vestingContractSection) cy.getByTestId(vestingContractSection)
.first() .first()
.within(() => { .within(() => {
cy.get(associatedKey).should( cy.getByTestId(associatedKey).should(
'contain', 'contain',
Cypress.env('vegaWalletPublicKeyShort') Cypress.env('vegaWalletPublicKeyShort')
); );
cy.get(associatedAmount, txTimeout).should('contain', 37); cy.getByTestId(associatedAmount, txTimeout).should('contain', 37);
}); });
cy.get(vegaInWalletSection) cy.getByTestId(vegaInWalletSection)
.first() .first()
.within(() => { .within(() => {
cy.get(associatedKey).should( cy.getByTestId(associatedKey).should(
'contain', 'contain',
Cypress.env('vegaWalletPublicKeyShort') Cypress.env('vegaWalletPublicKeyShort')
); );
cy.get(associatedAmount, txTimeout).should('contain', 21); cy.getByTestId(associatedAmount, txTimeout).should('contain', 21);
}); });
cy.get(vegaWallet).within(() => { cy.get(vegaWallet).within(() => {
cy.get(vegaWalletAssociatedBalance, txTimeout).should('contain', 58); cy.getByTestId(vegaWalletAssociatedBalance, txTimeout).should(
'contain',
58
);
}); });
stakingPageDisassociateTokens('6', { type: 'contract' }); stakingPageDisassociateTokens('6', { type: 'contract' });
cy.get(vestingContractSection) cy.getByTestId(vestingContractSection)
.first() .first()
.within(() => { .within(() => {
cy.get(associatedAmount, txTimeout).should('contain', 31); cy.getByTestId(associatedAmount, txTimeout).should('contain', 31);
}); });
cy.get(vegaWallet).within(() => { cy.get(vegaWallet).within(() => {
cy.get(vegaWalletAssociatedBalance, txTimeout).should('contain', 52); cy.getByTestId(vegaWalletAssociatedBalance, txTimeout).should(
'contain',
52
);
}); });
navigateTo(navigation.validators); navigateTo(navigation.validators);
stakingPageDisassociateTokens('9', { type: 'wallet' }); stakingPageDisassociateTokens('9', { type: 'wallet' });
cy.get(vegaInWalletSection) cy.getByTestId(vegaInWalletSection)
.first() .first()
.within(() => { .within(() => {
cy.get(associatedAmount, txTimeout).should('contain', 12); cy.getByTestId(associatedAmount, txTimeout).should('contain', 12);
}); });
cy.get(vegaWallet).within(() => { cy.get(vegaWallet).within(() => {
cy.get(vegaWalletAssociatedBalance, txTimeout).should('contain', 43); cy.getByTestId(vegaWalletAssociatedBalance, txTimeout).should(
'contain',
43
);
}); });
}); });
@@ -267,10 +297,10 @@ context(
// 1004-ASSO-010 // 1004-ASSO-010
// No warning visible as described in AC, but the button is disabled // No warning visible as described in AC, but the button is disabled
cy.get(ethWalletAssociateButton).click(); cy.get(ethWalletAssociateButton).click();
cy.get(associateWalletRadioButton, { timeout: 30000 }).click(); cy.getByTestId(associateWalletRadioButton, { timeout: 30000 }).click();
cy.get(tokenSubmitButton, txTimeout).should('be.disabled'); // button disabled with no input cy.getByTestId(tokenSubmitButton, txTimeout).should('be.disabled'); // button disabled with no input
cy.get(tokenAmountInputBox, { timeout: 10000 }).type('6500000'); cy.getByTestId(tokenAmountInputBox, { timeout: 10000 }).type('6500000');
cy.get(tokenSubmitButton, txTimeout).should('be.disabled'); cy.getByTestId(tokenSubmitButton, txTimeout).should('be.disabled');
}); });
// 1004-ASSO-004 // 1004-ASSO-004
@@ -295,23 +325,25 @@ context(
it('Able to associate tokens to different public key of connected vega wallet', function () { it('Able to associate tokens to different public key of connected vega wallet', function () {
cy.get(ethWalletAssociateButton).click(); cy.get(ethWalletAssociateButton).click();
cy.get(associateWalletRadioButton).click(); cy.getByTestId(associateWalletRadioButton).click();
cy.get(connectedVegaKey).should( cy.getByTestId(connectedVegaKey).should(
'have.text', 'have.text',
Cypress.env('vegaWalletPublicKey') Cypress.env('vegaWalletPublicKey')
); );
cy.get('[data-testid="manage-vega-wallet"]:visible').click(); switchVegaWalletPubKey();
cy.get('[data-testid="select-keypair-button"]').eq(0).click(); cy.getByTestId(connectedVegaKey).should(
cy.get(connectedVegaKey).should(
'have.text', 'have.text',
Cypress.env('vegaWalletPublicKey2') Cypress.env('vegaWalletPublicKey2')
); );
stakingPageAssociateTokens('2'); stakingPageAssociateTokens('2');
cy.get(vegaWallet).within(() => { cy.get(vegaWallet).within(() => {
cy.get(vegaWalletAssociatedBalance, txTimeout).should('contain', 2.0); cy.getByTestId(vegaWalletAssociatedBalance, txTimeout).should(
'contain',
2.0
);
}); });
cy.get(associateCompleteText).should( cy.getByTestId(associateCompleteText).should(
'have.text', 'have.text',
`Vega key ${Cypress.env( `Vega key ${Cypress.env(
'vegaWalletPublicKey2Short' 'vegaWalletPublicKey2Short'
@@ -5,7 +5,7 @@ import {
waitForSpinner, waitForSpinner,
} from '../../support/common.functions'; } from '../../support/common.functions';
import { ethereumWalletConnect } from '../../support/wallet-eth.functions'; import { ethereumWalletConnect } from '../../support/wallet-eth.functions';
import { depositAsset } from '../../support/wallet-teardown.functions'; import { depositAsset } from '../../support/wallet-functions';
const withdraw = 'withdraw'; const withdraw = 'withdraw';
const withdrawalForm = 'withdraw-form'; const withdrawalForm = 'withdraw-form';
@@ -117,6 +117,11 @@ context(
cy.getByTestId(submitWithdrawalButton).click(); cy.getByTestId(submitWithdrawalButton).click();
}); });
// assert withdrawal request // assert withdrawal request
/**
* TODO(@nx/cypress): Nesting Cypress commands in a should assertion now throws.
* You should use .then() to chain commands instead.
* More Info: https://docs.cypress.io/guides/references/migration-guide#-should
**/
cy.getByTestId(toast) cy.getByTestId(toast)
.first(txTimeout) .first(txTimeout)
.should('contain.text', 'Funds unlocked') .should('contain.text', 'Funds unlocked')
@@ -130,6 +135,11 @@ context(
cy.getByTestId(toastClose).click(); cy.getByTestId(toastClose).click();
}); });
// withdrawal complete // withdrawal complete
/**
* TODO(@nx/cypress): Nesting Cypress commands in a should assertion now throws.
* You should use .then() to chain commands instead.
* More Info: https://docs.cypress.io/guides/references/migration-guide#-should
**/
cy.getByTestId(toast) cy.getByTestId(toast)
.first(txTimeout) .first(txTimeout)
.should('contain.text', 'The withdrawal has been approved.') .should('contain.text', 'The withdrawal has been approved.')
@@ -139,6 +149,11 @@ context(
'Withdraw 120.00 tUSDC' 'Withdraw 120.00 tUSDC'
); );
}); });
/**
* TODO(@nx/cypress): Nesting Cypress commands in a should assertion now throws.
* You should use .then() to chain commands instead.
* More Info: https://docs.cypress.io/guides/references/migration-guide#-should
**/
cy.getByTestId(toast) cy.getByTestId(toast)
.last(txTimeout) .last(txTimeout)
.should('contain.text', 'Transaction confirmed') .should('contain.text', 'Transaction confirmed')
@@ -146,6 +161,11 @@ context(
cy.getByTestId('external-link').should('exist'); cy.getByTestId('external-link').should('exist');
}); });
// withdrawal history for complete withdrawal displayed // withdrawal history for complete withdrawal displayed
/**
* TODO(@nx/cypress): Nesting Cypress commands in a should assertion now throws.
* You should use .then() to chain commands instead.
* More Info: https://docs.cypress.io/guides/references/migration-guide#-should
**/
cy.get(tableWithdrawnStatus) cy.get(tableWithdrawnStatus)
.eq(1, txTimeout) .eq(1, txTimeout)
.should('have.text', 'Completed') .should('have.text', 'Completed')
@@ -187,6 +207,11 @@ context(
cy.getByTestId(submitWithdrawalButton).click(); cy.getByTestId(submitWithdrawalButton).click();
}); });
/**
* TODO(@nx/cypress): Nesting Cypress commands in a should assertion now throws.
* You should use .then() to chain commands instead.
* More Info: https://docs.cypress.io/guides/references/migration-guide#-should
**/
cy.getByTestId(toast) cy.getByTestId(toast)
.first(txTimeout) .first(txTimeout)
.should('contain.text', 'Funds unlocked') .should('contain.text', 'Funds unlocked')
@@ -198,6 +223,11 @@ context(
); );
cy.getByTestId(toastClose).click(); cy.getByTestId(toastClose).click();
}); });
/**
* TODO(@nx/cypress): Nesting Cypress commands in a should assertion now throws.
* You should use .then() to chain commands instead.
* More Info: https://docs.cypress.io/guides/references/migration-guide#-should
**/
cy.get(tableTxHash) cy.get(tableTxHash)
.eq(1) .eq(1)
.should('have.text', 'Complete withdrawal') .should('have.text', 'Complete withdrawal')
@@ -213,18 +243,33 @@ context(
}); });
ethereumWalletConnect(); ethereumWalletConnect();
cy.getByTestId(completeWithdrawalButton).first().click(); cy.getByTestId(completeWithdrawalButton).first().click();
/**
* TODO(@nx/cypress): Nesting Cypress commands in a should assertion now throws.
* You should use .then() to chain commands instead.
* More Info: https://docs.cypress.io/guides/references/migration-guide#-should
**/
cy.getByTestId(toast) cy.getByTestId(toast)
.last(txTimeout) .last(txTimeout)
.should('contain.text', 'Awaiting confirmation') .should('contain.text', 'Awaiting confirmation')
.within(() => { .within(() => {
cy.getByTestId('external-link').should('exist'); cy.getByTestId('external-link').should('exist');
}); });
/**
* TODO(@nx/cypress): Nesting Cypress commands in a should assertion now throws.
* You should use .then() to chain commands instead.
* More Info: https://docs.cypress.io/guides/references/migration-guide#-should
**/
cy.getByTestId(toast) cy.getByTestId(toast)
.first(txTimeout) .first(txTimeout)
.should('contain.text', 'The withdrawal has been approved.') .should('contain.text', 'The withdrawal has been approved.')
.within(() => { .within(() => {
cy.getByTestId(toastPanel).should('contain.text', '110.00', 'tUSDC'); cy.getByTestId(toastPanel).should('contain.text', '110.00', 'tUSDC');
}); });
/**
* TODO(@nx/cypress): Nesting Cypress commands in a should assertion now throws.
* You should use .then() to chain commands instead.
* More Info: https://docs.cypress.io/guides/references/migration-guide#-should
**/
cy.getByTestId(toast) cy.getByTestId(toast)
.last(txTimeout) .last(txTimeout)
.should('contain.text', 'Transaction confirmed') .should('contain.text', 'Transaction confirmed')
@@ -248,6 +293,11 @@ context(
cy.getByTestId(amountInput).click().type('50'); cy.getByTestId(amountInput).click().type('50');
cy.getByTestId(submitWithdrawalButton).click(); cy.getByTestId(submitWithdrawalButton).click();
}); });
/**
* TODO(@nx/cypress): Nesting Cypress commands in a should assertion now throws.
* You should use .then() to chain commands instead.
* More Info: https://docs.cypress.io/guides/references/migration-guide#-should
**/
cy.getByTestId(toast) cy.getByTestId(toast)
.first(txTimeout) .first(txTimeout)
.should('contain.text', 'Funds unlocked') .should('contain.text', 'Funds unlocked')
@@ -16,6 +16,11 @@ context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
}); });
it('should display announcement banner', function () { it('should display announcement banner', function () {
/**
* TODO(@nx/cypress): Nesting Cypress commands in a should assertion now throws.
* You should use .then() to chain commands instead.
* More Info: https://docs.cypress.io/guides/references/migration-guide#-should
**/
cy.getByTestId('app-announcement') cy.getByTestId('app-announcement')
.should('contain.text', 'TEST ANNOUNCEMENT!') .should('contain.text', 'TEST ANNOUNCEMENT!')
.within(() => { .within(() => {
@@ -35,6 +40,11 @@ context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
waitForSpinner(); waitForSpinner();
} }
}); });
/**
* TODO(@nx/cypress): Nesting Cypress commands in a should assertion now throws.
* You should use .then() to chain commands instead.
* More Info: https://docs.cypress.io/guides/references/migration-guide#-should
**/
cy.getByTestId('proposals-list-item') cy.getByTestId('proposals-list-item')
.should('have.length.at.least', 1) .should('have.length.at.least', 1)
.first() .first()
@@ -94,6 +104,11 @@ context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
}); });
it('should contain link to specific validators', function () { it('should contain link to specific validators', function () {
/**
* TODO(@nx/cypress): Nesting Cypress commands in a should assertion now throws.
* You should use .then() to chain commands instead.
* More Info: https://docs.cypress.io/guides/references/migration-guide#-should
**/
cy.getByTestId('validators') cy.getByTestId('validators')
.should('have.length', '2') .should('have.length', '2')
.each(($validator) => { .each(($validator) => {
@@ -120,6 +135,11 @@ context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
}); });
it('should display network data', function () { it('should display network data', function () {
/**
* TODO(@nx/cypress): Nesting Cypress commands in a should assertion now throws.
* You should use .then() to chain commands instead.
* More Info: https://docs.cypress.io/guides/references/migration-guide#-should
**/
cy.getByTestId('git-network-data') cy.getByTestId('git-network-data')
.should('contain.text', 'Reading network data from') .should('contain.text', 'Reading network data from')
.within(() => { .within(() => {
@@ -131,6 +151,11 @@ context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
}); });
it('should display eth data', function () { it('should display eth data', function () {
/**
* TODO(@nx/cypress): Nesting Cypress commands in a should assertion now throws.
* You should use .then() to chain commands instead.
* More Info: https://docs.cypress.io/guides/references/migration-guide#-should
**/
cy.getByTestId('git-eth-data') cy.getByTestId('git-eth-data')
.should('contain.text', 'Reading Ethereum data from') .should('contain.text', 'Reading Ethereum data from')
.within(() => { .within(() => {
@@ -10,9 +10,13 @@ import {
} from '../../support/governance.functions'; } from '../../support/governance.functions';
import { mockNetworkUpgradeProposal } from '../../support/proposal.functions'; import { mockNetworkUpgradeProposal } from '../../support/proposal.functions';
const proposalDocumentationLink = '[data-testid="proposal-documentation-link"]'; const proposalDocsLink = 'proposal-docs-link';
const proposalDocumentationLink = 'proposal-documentation-link';
const connectToVegaWalletButton = 'connect-to-vega-wallet-btn';
const governanceDocsUrl = 'https://vega.xyz/governance'; const governanceDocsUrl = 'https://vega.xyz/governance';
const connectToVegaWalletButton = '[data-testid="connect-to-vega-wallet-btn"]'; const networkUpgradeProposalListItem = 'protocol-upgrade-proposals-list-item';
const closedProposals = 'closed-proposals';
const closedProposalToggle = 'closed-proposals-toggle-networkUpgrades';
context( context(
'Governance Page - verify elements on page', 'Governance Page - verify elements on page',
@@ -41,7 +45,7 @@ context(
it('should be able to see a working link for - find out more about Vega governance', function () { it('should be able to see a working link for - find out more about Vega governance', function () {
// 3001-VOTE-001 // 3001-VOTE-001
cy.get(proposalDocumentationLink) cy.getByTestId(proposalDocumentationLink)
.should('be.visible') .should('be.visible')
.and('have.text', 'Find out more about Vega governance') .and('have.text', 'Find out more about Vega governance')
.and('have.attr', 'href') .and('have.attr', 'href')
@@ -64,7 +68,7 @@ context(
// 3007-PNE-021 // 3007-PNE-021
it('should have documentation links for network parameter proposal', function () { it('should have documentation links for network parameter proposal', function () {
goToMakeNewProposal(governanceProposalType.NETWORK_PARAMETER); goToMakeNewProposal(governanceProposalType.NETWORK_PARAMETER);
cy.getByTestId('proposal-docs-link') cy.getByTestId(proposalDocsLink)
.find('a') .find('a')
.should('have.attr', 'href') .should('have.attr', 'href')
.and('contain', '/tutorials/proposals/network-parameter-proposal'); .and('contain', '/tutorials/proposals/network-parameter-proposal');
@@ -73,7 +77,7 @@ context(
// 3003-PMAN-002 3003-PMAN-005 // 3003-PMAN-002 3003-PMAN-005
it('should have documentation links for new market proposal', function () { it('should have documentation links for new market proposal', function () {
goToMakeNewProposal(governanceProposalType.NEW_MARKET); goToMakeNewProposal(governanceProposalType.NEW_MARKET);
cy.getByTestId('proposal-docs-link') cy.getByTestId(proposalDocsLink)
.find('a') .find('a')
.should('have.attr', 'href') .should('have.attr', 'href')
.and('contain', '/tutorials/proposals/new-market-proposal'); .and('contain', '/tutorials/proposals/new-market-proposal');
@@ -82,7 +86,7 @@ context(
// 3004-PMAC-005 // 3004-PMAC-005
it('should have documentation links for update market proposal', function () { it('should have documentation links for update market proposal', function () {
goToMakeNewProposal(governanceProposalType.UPDATE_MARKET); goToMakeNewProposal(governanceProposalType.UPDATE_MARKET);
cy.getByTestId('proposal-docs-link') cy.getByTestId(proposalDocsLink)
.find('a') .find('a')
.should('have.attr', 'href') .should('have.attr', 'href')
.and('contain', '/tutorials/proposals/update-market-proposal'); .and('contain', '/tutorials/proposals/update-market-proposal');
@@ -91,7 +95,7 @@ context(
// 3005-PASN-002 005-PASN-005 // 3005-PASN-002 005-PASN-005
it('should have documentation links for new asset proposal', function () { it('should have documentation links for new asset proposal', function () {
goToMakeNewProposal(governanceProposalType.NEW_ASSET); goToMakeNewProposal(governanceProposalType.NEW_ASSET);
cy.getByTestId('proposal-docs-link') cy.getByTestId(proposalDocsLink)
.find('a') .find('a')
.should('have.attr', 'href') .should('have.attr', 'href')
.and('contain', '/tutorials/proposals/new-asset-proposal'); .and('contain', '/tutorials/proposals/new-asset-proposal');
@@ -100,7 +104,7 @@ context(
// 3006-PASC-002 3006-PASC-005 // 3006-PASC-002 3006-PASC-005
it('should have documentation links for update asset proposal', function () { it('should have documentation links for update asset proposal', function () {
goToMakeNewProposal(governanceProposalType.UPDATE_ASSET); goToMakeNewProposal(governanceProposalType.UPDATE_ASSET);
cy.getByTestId('proposal-docs-link') cy.getByTestId(proposalDocsLink)
.find('a') .find('a')
.should('have.attr', 'href') .should('have.attr', 'href')
.and('contain', '/tutorials/proposals/update-asset-proposal'); .and('contain', '/tutorials/proposals/update-asset-proposal');
@@ -109,7 +113,7 @@ context(
// 3008-PFRO-003 3008-PFRO-017 // 3008-PFRO-003 3008-PFRO-017
it('should have documentation links for freeform proposal', function () { it('should have documentation links for freeform proposal', function () {
goToMakeNewProposal(governanceProposalType.FREEFORM); goToMakeNewProposal(governanceProposalType.FREEFORM);
cy.getByTestId('proposal-docs-link') cy.getByTestId(proposalDocsLink)
.find('a') .find('a')
.should('have.attr', 'href') .should('have.attr', 'href')
.and('contain', '/tutorials/proposals/freeform-proposal'); .and('contain', '/tutorials/proposals/freeform-proposal');
@@ -117,7 +121,7 @@ context(
it('should be able to see a connect wallet button - if vega wallet disconnected and user is submitting new proposal', function () { it('should be able to see a connect wallet button - if vega wallet disconnected and user is submitting new proposal', function () {
goToMakeNewProposal(governanceProposalType.RAW); goToMakeNewProposal(governanceProposalType.RAW);
cy.get(connectToVegaWalletButton) cy.getByTestId(connectToVegaWalletButton)
.should('be.visible') .should('be.visible')
.and('have.text', 'Connect Vega wallet'); .and('have.text', 'Connect Vega wallet');
}); });
@@ -126,7 +130,7 @@ context(
mockNetworkUpgradeProposal(); mockNetworkUpgradeProposal();
cy.visit('/'); cy.visit('/');
cy.getByTestId('home-proposal-list').within(() => { cy.getByTestId('home-proposal-list').within(() => {
cy.getByTestId('protocol-upgrade-proposals-list-item').should('exist'); cy.getByTestId(networkUpgradeProposalListItem).should('exist');
cy.getByTestId('protocol-upgrade-proposal-title').should( cy.getByTestId('protocol-upgrade-proposal-title').should(
'have.text', 'have.text',
'Vega release v1' 'Vega release v1'
@@ -138,13 +142,14 @@ context(
mockNetworkUpgradeProposal(); mockNetworkUpgradeProposal();
navigateTo(navigation.proposals); navigateTo(navigation.proposals);
cy.getByTestId('open-proposals').within(() => { cy.getByTestId('open-proposals').within(() => {
/**
* TODO(@nx/cypress): Nesting Cypress commands in a should assertion now throws.
* You should use .then() to chain commands instead.
* More Info: https://docs.cypress.io/guides/references/migration-guide#-should
**/
cy.get('li') cy.get('li')
.eq(0) .eq(0)
.should( .should('have.attr', 'data-testid', networkUpgradeProposalListItem)
'have.attr',
'data-testid',
'protocol-upgrade-proposals-list-item'
)
.within(() => { .within(() => {
cy.get('h2').should('have.text', 'Vega release v1'); cy.get('h2').should('have.text', 'Vega release v1');
cy.getByTestId('protocol-upgrade-proposal-type').should( cy.getByTestId('protocol-upgrade-proposal-type').should(
@@ -165,18 +170,19 @@ context(
); );
}); });
}); });
cy.getByTestId('closed-proposals').within(() => { cy.getByTestId(closedProposals).within(() => {
cy.getByTestId('protocol-upgrade-proposals-list-item').should( cy.getByTestId(networkUpgradeProposalListItem).should('not.exist');
'have.length', });
1 cy.getByTestId(closedProposalToggle).click();
); cy.getByTestId(closedProposals).within(() => {
cy.getByTestId(networkUpgradeProposalListItem).should('have.length', 1);
}); });
}); });
it('should see details of network upgrade proposal', function () { it('should see details of network upgrade proposal', function () {
mockNetworkUpgradeProposal(); mockNetworkUpgradeProposal();
navigateTo(navigation.proposals); navigateTo(navigation.proposals);
cy.getByTestId('protocol-upgrade-proposals-list-item') cy.getByTestId(networkUpgradeProposalListItem)
.first() .first()
.find('[data-testid="view-proposal-btn"]') .find('[data-testid="view-proposal-btn"]')
.click(); .click();
@@ -199,6 +205,11 @@ context(
.should('contain.text', '99.98% approval (% validator voting power)') .should('contain.text', '99.98% approval (% validator voting power)')
.and('contain.text', '(67% voting power required)'); .and('contain.text', '(67% voting power required)');
cy.get('h2').should('contain.text', 'Approvers (4/4 validators)'); cy.get('h2').should('contain.text', 'Approvers (4/4 validators)');
/**
* TODO(@nx/cypress): Nesting Cypress commands in a should assertion now throws.
* You should use .then() to chain commands instead.
* More Info: https://docs.cypress.io/guides/references/migration-guide#-should
**/
cy.getByTestId('validator-name') cy.getByTestId('validator-name')
.should('have.length', 4) .should('have.length', 4)
.each(($validator) => { .each(($validator) => {
@@ -213,5 +224,18 @@ context(
); );
}); });
}); });
it('filtering proposal should not display any network upgrade proposals', function () {
const proposalId =
'd848fc7881f13d366df5f61ab139d5fcfa72bf838151bb51b54381870e357931';
mockNetworkUpgradeProposal();
navigateTo(navigation.proposals);
cy.get('[data-testid="proposal-filter-toggle"]').click();
cy.get('[data-testid="filter-input"]').type(proposalId);
cy.getByTestId(closedProposals).should('have.length', 1);
cy.getByTestId(networkUpgradeProposalListItem).should('not.exist');
cy.getByTestId(closedProposalToggle).should('not.exist');
});
} }
); );
@@ -11,7 +11,7 @@ import {
goToMakeNewProposal, goToMakeNewProposal,
governanceProposalType, governanceProposalType,
} from '../../support/governance.functions'; } from '../../support/governance.functions';
import { vegaWalletFaucetAssetsWithoutCheck } from '../../support/wallet-vega.functions'; import { vegaWalletFaucetAssetsWithoutCheck } from '../../support/wallet-functions';
const vegaWalletPubKey = Cypress.env('vegaWalletPublicKey2'); const vegaWalletPubKey = Cypress.env('vegaWalletPublicKey2');
const vegaPubkeyTruncated = Cypress.env('vegaWalletPublicKey2Short'); const vegaPubkeyTruncated = Cypress.env('vegaWalletPublicKey2Short');
@@ -5,8 +5,8 @@ import {
} from '../../support/common.functions'; } from '../../support/common.functions';
import { waitForBeginningOfEpoch } from '../../support/staking.functions'; import { waitForBeginningOfEpoch } from '../../support/staking.functions';
const viewToggle = '[data-testid="epoch-reward-view-toggle-total"]'; const viewToggle = 'epoch-reward-view-toggle-total';
const warning = '[data-testid="callout"]'; const warning = 'callout';
context( context(
'Rewards Page - verify elements on page', 'Rewards Page - verify elements on page',
@@ -27,7 +27,7 @@ context(
}); });
it('should have epoch warning', function () { it('should have epoch warning', function () {
cy.get(warning) cy.getByTestId(warning)
.should('be.visible') .should('be.visible')
.and( .and(
'have.text', 'have.text',
@@ -36,12 +36,17 @@ context(
}); });
it('should have toggle for seeing total vs individual rewards', function () { it('should have toggle for seeing total vs individual rewards', function () {
cy.get(viewToggle).should('be.visible'); cy.getByTestId(viewToggle).should('be.visible');
}); });
// Skipping due to bug #3471 causing flaky failuress // Skipping due to bug #3471 causing flaky failuress
it.skip('should have option to view go to next and previous page', function () { it.skip('should have option to view go to next and previous page', function () {
waitForBeginningOfEpoch(); waitForBeginningOfEpoch();
/**
* TODO(@nx/cypress): Nesting Cypress commands in a should assertion now throws.
* You should use .then() to chain commands instead.
* More Info: https://docs.cypress.io/guides/references/migration-guide#-should
**/
cy.getByTestId('page-info') cy.getByTestId('page-info')
.should('contain.text', 'Page ') .should('contain.text', 'Page ')
.invoke('text') .invoke('text')
@@ -1,18 +1,17 @@
import { navigateTo, navigation } from '../../support/common.functions'; import { navigateTo, navigation } from '../../support/common.functions';
const tokenDetailsTable = '.token-details'; const tokenDetailsTable = '.token-details';
const address = '[data-testid="token-address"]'; const address = 'token-address';
const contract = '[data-testid="token-contract"]'; const contract = 'token-contract';
const totalSupply = '[data-testid="total-supply"]'; const totalSupply = 'total-supply';
const circulatingSupply = '[data-testid="circulating-supply"]'; const circulatingSupply = 'circulating-supply';
const staked = '[data-testid="staked"]'; const staked = 'staked';
const tranchesLink = '[data-testid="tranches-link"]'; const tranchesLink = 'tranches-link';
const redeemBtn = '[data-testid="check-vesting-page-btn"]'; const redeemBtn = 'check-vesting-page-btn';
const getVegaWalletLink = '[data-testid="get-vega-wallet-link"]'; const getVegaWalletLink = 'get-vega-wallet-link';
const associateVegaLink = const associateVegaLink = 'associate-vega-tokens-link-on-homepage';
'[data-testid="associate-vega-tokens-link-on-homepage"]'; const stakingBtn = 'staking-button-on-homepage';
const stakingBtn = '[data-testid="staking-button-on-homepage"]'; const governanceBtn = 'governance-button-on-homepage';
const governanceBtn = '[data-testid="governance-button-on-homepage"]';
const vegaTokenAddress = Cypress.env('vegaTokenAddress'); const vegaTokenAddress = Cypress.env('vegaTokenAddress');
const vegaTokenContractAddress = Cypress.env('vegaTokenContractAddress'); const vegaTokenContractAddress = Cypress.env('vegaTokenContractAddress');
@@ -25,7 +24,7 @@ context('Verify elements on Token page', { tags: '@smoke' }, function () {
describe('THE $VEGA TOKEN table', function () { describe('THE $VEGA TOKEN table', function () {
it('should have TOKEN ADDRESS', function () { it('should have TOKEN ADDRESS', function () {
cy.get(tokenDetailsTable).within(() => { cy.get(tokenDetailsTable).within(() => {
cy.get(address) cy.getByTestId(address)
.should('be.visible') .should('be.visible')
.invoke('text') .invoke('text')
.should('be.equal', vegaTokenAddress); .should('be.equal', vegaTokenAddress);
@@ -34,7 +33,7 @@ context('Verify elements on Token page', { tags: '@smoke' }, function () {
it('should have VESTING CONTRACT', function () { it('should have VESTING CONTRACT', function () {
// 1004-ASSO-001 // 1004-ASSO-001
cy.get(tokenDetailsTable).within(() => { cy.get(tokenDetailsTable).within(() => {
cy.get(contract) cy.getByTestId(contract)
.should('be.visible') .should('be.visible')
.invoke('text') .invoke('text')
.should('be.equal', vegaTokenContractAddress); .should('be.equal', vegaTokenContractAddress);
@@ -42,56 +41,56 @@ context('Verify elements on Token page', { tags: '@smoke' }, function () {
}); });
it('should have TOTAL SUPPLY', function () { it('should have TOTAL SUPPLY', function () {
cy.get(tokenDetailsTable).within(() => { cy.get(tokenDetailsTable).within(() => {
cy.get(totalSupply).should('be.visible'); cy.getByTestId(totalSupply).should('be.visible');
}); });
}); });
it('should have CIRCULATING SUPPLY', function () { it('should have CIRCULATING SUPPLY', function () {
cy.get(tokenDetailsTable).within(() => { cy.get(tokenDetailsTable).within(() => {
cy.get(circulatingSupply).should('be.visible'); cy.getByTestId(circulatingSupply).should('be.visible');
}); });
}); });
it('should have STAKED $VEGA', function () { it('should have STAKED $VEGA', function () {
cy.get(tokenDetailsTable).within(() => { cy.get(tokenDetailsTable).within(() => {
cy.get(staked).should('be.visible'); cy.getByTestId(staked).should('be.visible');
}); });
}); });
}); });
describe('links and buttons', function () { describe('links and buttons', function () {
it('should have TRANCHES link', function () { it('should have TRANCHES link', function () {
cy.get(tranchesLink) cy.getByTestId(tranchesLink)
.should('be.visible') .should('be.visible')
.and('have.attr', 'href') .and('have.attr', 'href')
.and('equal', '/token/tranches'); .and('equal', '/token/tranches');
}); });
it('should have REDEEM button', function () { it('should have REDEEM button', function () {
cy.get(redeemBtn) cy.getByTestId(redeemBtn)
.should('be.visible') .should('be.visible')
.parent() .parent()
.should('have.attr', 'href') .should('have.attr', 'href')
.and('equal', '/token/redeem'); .and('equal', '/token/redeem');
}); });
it('should have GET VEGA WALLET link', function () { it('should have GET VEGA WALLET link', function () {
cy.get(getVegaWalletLink) cy.getByTestId(getVegaWalletLink)
.should('be.visible') .should('be.visible')
.and('have.attr', 'href') .and('have.attr', 'href')
.and('equal', 'https://vega.xyz/wallet'); .and('equal', 'https://vega.xyz/wallet');
}); });
it('should have ASSOCIATE VEGA TOKENS link', function () { it('should have ASSOCIATE VEGA TOKENS link', function () {
cy.get(associateVegaLink) cy.getByTestId(associateVegaLink)
.should('be.visible') .should('be.visible')
.and('have.attr', 'href') .and('have.attr', 'href')
.and('equal', '/token/associate'); .and('equal', '/token/associate');
}); });
it('should have STAKING button', function () { it('should have STAKING button', function () {
cy.get(stakingBtn) cy.getByTestId(stakingBtn)
.should('be.visible') .should('be.visible')
.parent() .parent()
.should('have.attr', 'href') .should('have.attr', 'href')
.and('equal', '/validators'); .and('equal', '/validators');
}); });
it('should have GOVERNANCE button', function () { it('should have GOVERNANCE button', function () {
cy.get(governanceBtn) cy.getByTestId(governanceBtn)
.should('be.visible') .should('be.visible')
.parent() .parent()
.should('have.attr', 'href') .should('have.attr', 'href')
@@ -21,6 +21,11 @@ context(
// 1005-VEST-001 // 1005-VEST-001
// 1005-VEST-002 // 1005-VEST-002
it('Able to view tranches', function () { it('Able to view tranches', function () {
/**
* TODO(@nx/cypress): Nesting Cypress commands in a should assertion now throws.
* You should use .then() to chain commands instead.
* More Info: https://docs.cypress.io/guides/references/migration-guide#-should
**/
cy.getByTestId('tranche-item') cy.getByTestId('tranche-item')
.should('have.length', 2) .should('have.length', 2)
.first() .first()
@@ -51,6 +56,11 @@ context(
cy.get('span').eq(1).should('have.text', 0); cy.get('span').eq(1).should('have.text', 0);
}); });
cy.getByTestId('key-value-table').within(() => { cy.getByTestId('key-value-table').within(() => {
/**
* TODO(@nx/cypress): Nesting Cypress commands in a should assertion now throws.
* You should use .then() to chain commands instead.
* More Info: https://docs.cypress.io/guides/references/migration-guide#-should
**/
cy.getByTestId('link') cy.getByTestId('link')
.should('have.length', 8) .should('have.length', 8)
.each((ethLink) => { .each((ethLink) => {
@@ -58,6 +68,11 @@ context(
.should('have.attr', 'href') .should('have.attr', 'href')
.and('contain', 'https://sepolia.etherscan.io/address/'); .and('contain', 'https://sepolia.etherscan.io/address/');
}); });
/**
* TODO(@nx/cypress): Nesting Cypress commands in a should assertion now throws.
* You should use .then() to chain commands instead.
* More Info: https://docs.cypress.io/guides/references/migration-guide#-should
**/
cy.getByTestId('redeem-link') cy.getByTestId('redeem-link')
.should('have.length', 8) .should('have.length', 8)
.each((redeemLink) => { .each((redeemLink) => {
@@ -71,6 +86,11 @@ context(
it('Able to view tranches with less than 10 vega', function () { it('Able to view tranches with less than 10 vega', function () {
navigateTo(navigation.supply); navigateTo(navigation.supply);
cy.getByTestId('show-all-tranches').click(); cy.getByTestId('show-all-tranches').click();
/**
* TODO(@nx/cypress): Nesting Cypress commands in a should assertion now throws.
* You should use .then() to chain commands instead.
* More Info: https://docs.cypress.io/guides/references/migration-guide#-should
**/
cy.getByTestId('tranche-item') cy.getByTestId('tranche-item')
.should('have.length', 8) .should('have.length', 8)
.first() .first()
@@ -1,5 +1,6 @@
/// <reference types="cypress" /> /// <reference types="cypress" />
import { aliasGQLQuery } from '@vegaprotocol/cypress';
import { import {
navigation, navigation,
verifyPageHeader, verifyPageHeader,
@@ -9,29 +10,28 @@ import {
clickOnValidatorFromList, clickOnValidatorFromList,
waitForBeginningOfEpoch, waitForBeginningOfEpoch,
} from '../../support/staking.functions'; } from '../../support/staking.functions';
import { previousEpochData } from '../../fixtures/mocks/previous-epoch';
const guideLink = '[data-testid="staking-guide-link"]'; const guideLink = 'staking-guide-link';
const validatorTitle = '[data-testid="validator-node-title"]'; const validatorTitle = 'validator-node-title';
const validatorId = '[data-testid="validator-id"]'; const validatorId = 'validator-id';
const validatorPubKey = '[data-testid="validator-public-key"]'; const validatorPubKey = 'validator-public-key';
const ethAddressLink = '[data-testid="link"]'; const ethAddressLink = 'link';
const validatorStatus = '[data-testid="validator-status"]'; const validatorStatus = 'validator-status';
const totalStake = '[data-testid="total-stake"]'; const totalStake = 'total-stake';
const pendingStake = '[data-testid="pending-stake"]'; const pendingStake = 'pending-stake';
const stakedByOperator = '[data-testid="staked-by-operator"]'; const stakedByOperator = 'staked-by-operator';
const stakedByDelegates = '[data-testid="staked-by-delegates"]'; const stakedByDelegates = 'staked-by-delegates';
const stakeShare = '[data-testid="stake-percentage"]'; const stakeShare = 'stake-percentage';
const stakedByOperatorToolTip = '[data-testid="staked-operator-tooltip"]'; const stakedByOperatorToolTip = 'staked-operator-tooltip';
const stakedByDelegatesToolTip = '[data-testid="staked-delegates-tooltip"]'; const stakedByDelegatesToolTip = 'staked-delegates-tooltip';
const totalStakedToolTip = '[data-testid="total-staked-tooltip"]'; const totalStakedToolTip = 'total-staked-tooltip';
const unnormalisedVotingPowerToolTip = const unnormalisedVotingPowerToolTip = 'unnormalised-voting-power-tooltip';
'[data-testid="unnormalised-voting-power-tooltip"]'; const normalisedVotingPowerToolTip = 'normalised-voting-power-tooltip';
const normalisedVotingPowerToolTip = const performancePenaltyToolTip = 'performance-penalty-tooltip';
'[data-testid="normalised-voting-power-tooltip"]'; const overstakedPenaltyToolTip = 'overstaked-penalty-tooltip';
const performancePenaltyToolTip = '[data-testid="performance-penalty-tooltip"]'; const multisigPenaltyToolTip = 'multisig-error-tooltip';
const overstakedPenaltyToolTip = '[data-testid="overstaked-penalty-tooltip"]'; const epochCountDown = 'epoch-countdown';
const totalPenaltyToolTip = '[data-testid="total-penalty-tooltip"]';
const epochCountDown = '[data-testid="epoch-countdown"]';
const stakeNumberRegex = /^\d{1,3}(,\d{3})*(\.\d+)?$/; const stakeNumberRegex = /^\d{1,3}(,\d{3})*(\.\d+)?$/;
context('Validators Page - verify elements on page', function () { context('Validators Page - verify elements on page', function () {
@@ -50,7 +50,7 @@ context('Validators Page - verify elements on page', function () {
it('Should have Staking Guide link visible', function () { it('Should have Staking Guide link visible', function () {
// 1002-STKE-003 // 1002-STKE-003
cy.get(guideLink) cy.getByTestId(guideLink)
.should('be.visible') .should('be.visible')
.and('have.text', 'Read more about staking on Vega') .and('have.text', 'Read more about staking on Vega')
.and( .and(
@@ -74,6 +74,11 @@ context('Validators Page - verify elements on page', function () {
function () { function () {
// 1002-STKE-050 // 1002-STKE-050
it('Should be able to see validator names', function () { it('Should be able to see validator names', function () {
/**
* TODO(@nx/cypress): Nesting Cypress commands in a should assertion now throws.
* You should use .then() to chain commands instead.
* More Info: https://docs.cypress.io/guides/references/migration-guide#-should
**/
cy.get('[col-id="validator"] > div > span') cy.get('[col-id="validator"] > div > span')
.should('have.length.at.least', 1) .should('have.length.at.least', 1)
.each(($name) => { .each(($name) => {
@@ -82,6 +87,11 @@ context('Validators Page - verify elements on page', function () {
}); });
it('Should be able to see validator stake', function () { it('Should be able to see validator stake', function () {
/**
* TODO(@nx/cypress): Nesting Cypress commands in a should assertion now throws.
* You should use .then() to chain commands instead.
* More Info: https://docs.cypress.io/guides/references/migration-guide#-should
**/
cy.getByTestId('total-stake') cy.getByTestId('total-stake')
.should('have.length.at.least', 1) .should('have.length.at.least', 1)
.each(($stake) => { .each(($stake) => {
@@ -93,18 +103,23 @@ context('Validators Page - verify elements on page', function () {
waitForBeginningOfEpoch(); waitForBeginningOfEpoch();
cy.getByTestId('total-stake').first().realHover(); cy.getByTestId('total-stake').first().realHover();
cy.get(stakedByOperatorToolTip) cy.getByTestId(stakedByOperatorToolTip)
.invoke('text') .invoke('text')
.should('contain', 'Staked by operator: 3,000.00'); .should('contain', 'Staked by operator: 3,000.00');
cy.get(stakedByDelegatesToolTip) cy.getByTestId(stakedByDelegatesToolTip)
.invoke('text') .invoke('text')
.should('contain', 'Staked by delegates: 0.00'); .should('contain', 'Staked by delegates: 0.00');
cy.get(totalStakedToolTip) cy.getByTestId(totalStakedToolTip)
.invoke('text') .invoke('text')
.should('contain', 'Total stake: 3,000.00'); .should('contain', 'Total stake: 3,000.00');
}); });
it('Should be able to see validator normalised voting power', function () { it('Should be able to see validator normalised voting power', function () {
/**
* TODO(@nx/cypress): Nesting Cypress commands in a should assertion now throws.
* You should use .then() to chain commands instead.
* More Info: https://docs.cypress.io/guides/references/migration-guide#-should
**/
cy.getByTestId('normalised-voting-power') cy.getByTestId('normalised-voting-power')
.should('have.length.at.least', 1) .should('have.length.at.least', 1)
.each(($vPower) => { .each(($vPower) => {
@@ -116,16 +131,21 @@ context('Validators Page - verify elements on page', function () {
waitForBeginningOfEpoch(); waitForBeginningOfEpoch();
cy.getByTestId('normalised-voting-power').first().realHover(); cy.getByTestId('normalised-voting-power').first().realHover();
cy.get(unnormalisedVotingPowerToolTip) cy.getByTestId(unnormalisedVotingPowerToolTip)
.invoke('text') .invoke('text')
.should('contain', 'Unnormalised voting power: 20.00%'); .should('contain', 'Unnormalised voting power: 20.00%');
cy.get(normalisedVotingPowerToolTip) cy.getByTestId(normalisedVotingPowerToolTip)
.invoke('text') .invoke('text')
.should('contain', 'Normalised voting power: 50.00%'); .should('contain', 'Normalised voting power: 50.00%');
}); });
// 2002-SINC-018 // 2002-SINC-018
it('Should be able to see validator total penalties', function () { it('Should be able to see validator total penalties', function () {
/**
* TODO(@nx/cypress): Nesting Cypress commands in a should assertion now throws.
* You should use .then() to chain commands instead.
* More Info: https://docs.cypress.io/guides/references/migration-guide#-should
**/
cy.getByTestId('total-penalty') cy.getByTestId('total-penalty')
.should('have.length.at.least', 1) .should('have.length.at.least', 1)
.each(($penalties) => { .each(($penalties) => {
@@ -137,24 +157,42 @@ context('Validators Page - verify elements on page', function () {
waitForBeginningOfEpoch(); waitForBeginningOfEpoch();
cy.getByTestId('total-penalty').realHover(); cy.getByTestId('total-penalty').realHover();
cy.get(performancePenaltyToolTip) cy.getByTestId(performancePenaltyToolTip)
.invoke('text') .invoke('text')
.should('contain', 'Performance penalty: 0.00%'); .should('contain', 'Performance penalty: 0.00%');
cy.get(overstakedPenaltyToolTip) cy.getByTestId(overstakedPenaltyToolTip)
.invoke('text') .invoke('text')
.should('contain', 'Overstaked penalty: 60.00%'); // value not asserted due to #2886 .should('contain', 'Overstaked penalty: 60.00%'); // value not asserted due to #2886
cy.get(totalPenaltyToolTip)
.invoke('text')
.should('contain', 'Total penalties: 60.00%');
}); });
it('Should be able to see validator pending stake', function () { it('Should be able to see validator pending stake', function () {
/**
* TODO(@nx/cypress): Nesting Cypress commands in a should assertion now throws.
* You should use .then() to chain commands instead.
* More Info: https://docs.cypress.io/guides/references/migration-guide#-should
**/
cy.getByTestId('total-pending-stake') cy.getByTestId('total-pending-stake')
.should('have.length.at.least', 1) .should('have.length.at.least', 1)
.each(($pendingStake) => { .each(($pendingStake) => {
cy.wrap($pendingStake).should('contain.text', '0.00'); 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%');
});
} }
); );
@@ -170,53 +208,59 @@ context('Validators Page - verify elements on page', function () {
// 1002-STKE-006 // 1002-STKE-006
it('Should be able to see validator name', function () { it('Should be able to see validator name', function () {
cy.get(validatorTitle).should('not.be.empty'); cy.getByTestId(validatorTitle).should('not.be.empty');
}); });
// 1002-STKE-007 // 1002-STKE-007
it('Should be able to see validator id', function () { it('Should be able to see validator id', function () {
cy.get(validatorId).should('not.be.empty'); cy.getByTestId(validatorId).should('not.be.empty');
}); });
// 1002-STKE-008 // 1002-STKE-008
it('Should be able to see validator public key', function () { it('Should be able to see validator public key', function () {
cy.get(validatorPubKey).should('not.be.empty'); cy.getByTestId(validatorPubKey).should('not.be.empty');
}); });
// 1002-STKE-010 // 1002-STKE-010
it('Should be able to see Ethereum address', function () { it('Should be able to see Ethereum address', function () {
cy.get(ethAddressLink).should('not.be.empty').and('have.attr', 'href'); cy.getByTestId(ethAddressLink)
.should('not.be.empty')
.and('have.attr', 'href');
}); });
// TODO validators missing url for more information about them 1002-STKE-09 // TODO validators missing url for more information about them 1002-STKE-09
it('Should be able to see validator status', function () { it('Should be able to see validator status', function () {
cy.get(validatorStatus).should('have.text', 'Consensus'); cy.getByTestId(validatorStatus).should('have.text', 'Consensus');
}); });
// 1002-STKE-012 // 1002-STKE-012
it('Should be able to see total stake', function () { it('Should be able to see total stake', function () {
cy.get(totalStake).invoke('text').should('match', stakeNumberRegex); cy.getByTestId(totalStake)
.invoke('text')
.should('match', stakeNumberRegex);
}); });
it('Should be able to see pending stake', function () { it('Should be able to see pending stake', function () {
cy.get(pendingStake).invoke('text').should('match', stakeNumberRegex); cy.getByTestId(pendingStake)
.invoke('text')
.should('match', stakeNumberRegex);
}); });
it('Should be able to see staked by operator', function () { it('Should be able to see staked by operator', function () {
cy.get(stakedByOperator) cy.getByTestId(stakedByOperator)
.invoke('text') .invoke('text')
.should('match', stakeNumberRegex); .should('match', stakeNumberRegex);
}); });
it('Should be able to see staked by delegates', function () { it('Should be able to see staked by delegates', function () {
cy.get(stakedByDelegates) cy.getByTestId(stakedByDelegates)
.invoke('text') .invoke('text')
.should('match', stakeNumberRegex); .should('match', stakeNumberRegex);
}); });
// 1002-STKE-051 // 1002-STKE-051
it('Should be able to see stake share in percentage', function () { it('Should be able to see stake share in percentage', function () {
cy.get(stakeShare) cy.getByTestId(stakeShare)
.invoke('text') .invoke('text')
.then(($stakePercentage) => { .then(($stakePercentage) => {
// The pattern must start at a word boundary (\b). // The pattern must start at a word boundary (\b).
@@ -242,7 +286,7 @@ context('Validators Page - verify elements on page', function () {
const epochTitle = 'h3'; const epochTitle = 'h3';
const nextEpochInfo = 'p'; const nextEpochInfo = 'p';
cy.get(epochCountDown).within(() => { cy.getByTestId(epochCountDown).within(() => {
cy.get(epochTitle).should('not.be.empty'); cy.get(epochTitle).should('not.be.empty');
cy.get(nextEpochInfo).should('contain.text', 'Next epoch'); cy.get(nextEpochInfo).should('contain.text', 'Next epoch');
}); });
@@ -6,7 +6,7 @@ import {
} from '../../support/common.functions'; } from '../../support/common.functions';
import { ethereumWalletConnect } from '../../support/wallet-eth.functions'; import { ethereumWalletConnect } from '../../support/wallet-eth.functions';
const connectButton = '[data-testid="connect-to-eth-btn"]'; const connectButton = 'connect-to-eth-btn';
const lockedTokensInVestingContract = '6,499,972.30'; const lockedTokensInVestingContract = '6,499,972.30';
context( context(
@@ -29,7 +29,7 @@ context(
// 1005-VEST-018 // 1005-VEST-018
it('should have connect Eth wallet button', function () { it('should have connect Eth wallet button', function () {
cy.get(connectButton) cy.getByTestId(connectButton)
.should('be.visible') .should('be.visible')
.and('have.text', 'Connect Ethereum wallet'); .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 walletHeader = '[data-testid="wallet-header"] h1';
const connectToEthButton = const connectToEthButton =
'[data-testid="connect-to-eth-wallet-button"]:visible'; '[data-testid="connect-to-eth-wallet-button"]:visible';
const connectorList = '[data-testid="web3-connector-list"]'; const connectorList = 'web3-connector-list';
const associate = '[href="/token/associate"]'; const associate = '[href="/token/associate"]';
const disassociate = '[href="/token/disassociate"]'; const disassociate = '[href="/token/disassociate"]';
const disconnect = '[data-testid="disconnect-from-eth-wallet-button"]'; const disconnect = 'disconnect-from-eth-wallet-button';
const accountNo = '[data-testid="ethereum-account-truncated"]'; const accountNo = 'ethereum-account-truncated';
const currencyTitle = '[data-testid="currency-title"]:visible'; const currencyTitle = '[data-testid="currency-title"]:visible';
const currencyValue = '[data-testid="currency-value"]:visible'; const currencyValue = '[data-testid="currency-value"]:visible';
const vegaInVesting = '[data-testid="vega-in-vesting-contract"]: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 currencyLocked = '[data-testid="currency-locked"]:visible';
const currencyUnlocked = '[data-testid="currency-unlocked"]:visible'; const currencyUnlocked = '[data-testid="currency-unlocked"]:visible';
const dialog = '[role="dialog"]:visible'; const dialog = '[role="dialog"]:visible';
const dialogHeader = '[data-testid="dialog-title"]'; const dialogHeader = 'dialog-title';
const dialogCloseBtn = '[data-testid="dialog-close"]'; const dialogCloseBtn = 'dialog-close';
context( context(
'Ethereum Wallet - verify elements on widget', 'Ethereum Wallet - verify elements on widget',
@@ -59,7 +59,7 @@ context(
it('should have Connect Ethereum header visible', function () { it('should have Connect Ethereum header visible', function () {
cy.get(dialog).within(() => { cy.get(dialog).within(() => {
cy.get(dialogHeader) cy.getByTestId(dialogHeader)
.should('be.visible') .should('be.visible')
.and('have.text', 'Connect to your Ethereum wallet'); .and('have.text', 'Connect to your Ethereum wallet');
}); });
@@ -73,7 +73,7 @@ context(
'WalletConnect', 'WalletConnect',
'WalletConnect Legacy', 'WalletConnect Legacy',
]; ];
cy.get(connectorList).within(() => { cy.getByTestId(connectorList).within(() => {
cy.get('button').each(($btn, i) => { cy.get('button').each(($btn, i) => {
cy.wrap($btn).should('be.visible').and('have.text', connectList[i]); cy.wrap($btn).should('be.visible').and('have.text', connectList[i]);
}); });
@@ -83,7 +83,7 @@ context(
after('close popup', function () { after('close popup', function () {
cy.get(dialog) cy.get(dialog)
.within(() => { .within(() => {
cy.get(dialogCloseBtn).click(); cy.getByTestId(dialogCloseBtn).click();
}) })
.should('not.exist'); .should('not.exist');
}); });
@@ -106,7 +106,7 @@ context(
// 0004-EWAL-005 // 0004-EWAL-005
it('should have account number visible', function () { it('should have account number visible', function () {
cy.get(walletContainer).within(() => { cy.get(walletContainer).within(() => {
cy.get(accountNo) cy.getByTestId(accountNo)
.should('be.visible') .should('be.visible')
.and('have.text', Cypress.env('ethWalletPublicKeyTruncated')); .and('have.text', Cypress.env('ethWalletPublicKeyTruncated'));
}); });
@@ -129,7 +129,7 @@ context(
// 0004-EWAL-007 // 0004-EWAL-007
it('should have Disconnect button visible', function () { it('should have Disconnect button visible', function () {
cy.get(walletContainer).within(() => { cy.get(walletContainer).within(() => {
cy.get(disconnect) cy.getByTestId(disconnect)
.should('be.visible') .should('be.visible')
.and('have.text', 'Disconnect'); .and('have.text', 'Disconnect');
}); });
@@ -1,32 +1,34 @@
import { truncateByChars } from '@vegaprotocol/utils'; import { truncateByChars } from '@vegaprotocol/utils';
import { waitForSpinner } from '../../support/common.functions'; import { waitForSpinner } from '../../support/common.functions';
import { vegaWalletTeardown } from '../../support/wallet-teardown.functions'; import {
import { vegaWalletFaucetAssetsWithoutCheck } from '../../support/wallet-vega.functions'; vegaWalletFaucetAssetsWithoutCheck,
vegaWalletTeardown,
} from '../../support/wallet-functions';
const walletContainer = 'aside [data-testid="vega-wallet"]'; const walletContainer = 'aside [data-testid="vega-wallet"]';
const walletHeader = '[data-testid="wallet-header"] h1'; const walletHeader = '[data-testid="wallet-header"] h1';
const connectButton = '[data-testid="connect-vega-wallet"]'; const connectButton = 'connect-vega-wallet';
const getVegaLink = '[data-testid="link"]'; const getVegaLink = 'link';
const dialog = '[role="dialog"]:visible'; const dialog = '[role="dialog"]:visible';
const dialogHeader = '[data-testid="dialog-title"]'; const dialogHeader = 'dialog-title';
const walletDialogHeader = '[data-testid="wallet-dialog-title"]'; const walletDialogHeader = 'wallet-dialog-title';
const connectorsList = '[data-testid="connectors-list"]'; const connectorsList = 'connectors-list';
const dialogCloseBtn = '[data-testid="dialog-close"]'; const dialogCloseBtn = 'dialog-close';
const restConnectorForm = '[data-testid="rest-connector-form"]'; const restConnectorForm = 'rest-connector-form';
const restWallet = '#wallet'; const restWallet = '#wallet';
const restPassphrase = '#passphrase'; const restPassphrase = '#passphrase';
const restConnectBtn = '[type="submit"]'; const restConnectBtn = '[type="submit"]';
const accountNo = '[data-testid="vega-account-truncated"]'; const accountNo = 'vega-account-truncated';
const currencyTitle = '[data-testid="currency-title"]'; const currencyTitle = 'currency-title';
const currencyValue = '[data-testid="currency-value"]'; const currencyValue = 'currency-value';
const vegaUnstaked = '[data-testid="vega-wallet-balance-unstaked"] .text-right'; const vegaUnstaked = '[data-testid="vega-wallet-balance-unstaked"] .text-right';
const governanceBtn = '[href="/proposals"]'; const governanceBtn = '[href="/proposals"]';
const stakingBtn = '[href="/validators"]'; const stakingBtn = '[href="/validators"]';
const manageLink = '[data-testid="manage-vega-wallet"]'; const manageLink = 'manage-vega-wallet';
const dialogVegaKey = '[data-testid="vega-public-key-full"]'; const dialogVegaKey = 'vega-public-key-full';
const dialogDisconnectBtn = '[data-testid="disconnect"]'; const dialogDisconnectBtn = 'disconnect';
const copyPublicKeyBtn = '[data-testid="copy-vega-public-key"]'; const copyPublicKeyBtn = 'copy-vega-public-key';
const vegaWalletCurrencyTitle = '[data-testid="currency-title"]'; const vegaWalletCurrencyTitle = 'currency-title';
const vegaWalletPublicKey = Cypress.env('vegaWalletPublicKey'); const vegaWalletPublicKey = Cypress.env('vegaWalletPublicKey');
const txTimeout = Cypress.env('txTimeout'); const txTimeout = Cypress.env('txTimeout');
@@ -45,10 +47,10 @@ context(
cy.get(walletHeader) cy.get(walletHeader)
.should('be.visible') .should('be.visible')
.and('have.text', 'Vega Wallet'); .and('have.text', 'Vega Wallet');
cy.get(connectButton) cy.getByTestId(connectButton)
.should('be.visible') .should('be.visible')
.and('have.text', 'Connect Vega wallet to use associated $VEGA'); .and('have.text', 'Connect Vega wallet to use associated $VEGA');
cy.get(getVegaLink) cy.getByTestId(getVegaLink)
.should('be.visible') .should('be.visible')
.and('have.text', 'Get a Vega wallet') .and('have.text', 'Get a Vega wallet')
.and('have.attr', 'href', 'https://vega.xyz/wallet'); .and('have.attr', 'href', 'https://vega.xyz/wallet');
@@ -59,20 +61,20 @@ context(
describe('when connect button clicked', () => { describe('when connect button clicked', () => {
before('click connect vega wallet button', () => { before('click connect vega wallet button', () => {
cy.get(walletContainer).within(() => { cy.get(walletContainer).within(() => {
cy.get(connectButton).click(); cy.getByTestId(connectButton).click();
}); });
}); });
it('should have Connect Vega header visible', () => { it('should have Connect Vega header visible', () => {
cy.get(dialog).within(() => { cy.get(dialog).within(() => {
cy.get(walletDialogHeader) cy.getByTestId(walletDialogHeader)
.should('be.visible') .should('be.visible')
.and('have.text', 'Connect'); .and('have.text', 'Connect');
}); });
}); });
it('should have jsonRpc and hosted connection options visible on list', function () { it('should have jsonRpc and hosted connection options visible on list', function () {
cy.get(connectorsList).within(() => { cy.getByTestId(connectorsList).within(() => {
cy.getByTestId('connector-jsonRpc') cy.getByTestId('connector-jsonRpc')
.should('be.visible') .should('be.visible')
.and('have.text', 'Connect Vega wallet'); .and('have.text', 'Connect Vega wallet');
@@ -84,33 +86,33 @@ context(
it('should have close button visible', function () { it('should have close button visible', function () {
cy.get(dialog).within(() => { cy.get(dialog).within(() => {
cy.get(dialogCloseBtn).should('be.visible'); cy.getByTestId(dialogCloseBtn).should('be.visible');
}); });
}); });
}); });
describe('when rest connector form opened', function () { describe('when rest connector form opened', function () {
before('click hosted wallet app button', function () { before('click hosted wallet app button', function () {
cy.get(connectorsList).within(() => { cy.getByTestId(connectorsList).within(() => {
cy.getByTestId('connector-hosted').click(); cy.getByTestId('connector-hosted').click();
}); });
}); });
// 0002-WCON-002 // 0002-WCON-002
it('should have wallet field visible', function () { it('should have wallet field visible', function () {
cy.get(restConnectorForm).within(() => { cy.getByTestId(restConnectorForm).within(() => {
cy.get(restWallet).should('be.visible'); cy.get(restWallet).should('be.visible');
}); });
}); });
it('should have password field visible', function () { it('should have password field visible', function () {
cy.get(restConnectorForm).within(() => { cy.getByTestId(restConnectorForm).within(() => {
cy.get(restPassphrase).should('be.visible'); cy.get(restPassphrase).should('be.visible');
}); });
}); });
it('should have connect button visible', function () { it('should have connect button visible', function () {
cy.get(restConnectorForm).within(() => { cy.getByTestId(restConnectorForm).within(() => {
cy.get(restConnectBtn) cy.get(restConnectBtn)
.should('be.visible') .should('be.visible')
.and('have.text', 'Connect'); .and('have.text', 'Connect');
@@ -119,12 +121,12 @@ context(
it('should have close button visible', function () { it('should have close button visible', function () {
cy.get(dialog).within(() => { cy.get(dialog).within(() => {
cy.get(dialogCloseBtn).should('be.visible'); cy.getByTestId(dialogCloseBtn).should('be.visible');
}); });
}); });
after('close dialog', function () { after('close dialog', function () {
cy.get(dialogCloseBtn).click().should('not.exist'); cy.getByTestId(dialogCloseBtn).click().should('not.exist');
}); });
}); });
@@ -150,7 +152,7 @@ context(
{ tags: '@smoke' }, { tags: '@smoke' },
function () { function () {
cy.get(walletContainer).within(() => { cy.get(walletContainer).within(() => {
cy.get(accountNo) cy.getByTestId(accountNo)
.should('be.visible') .should('be.visible')
.and('have.text', Cypress.env('vegaWalletPublicKeyShort')); .and('have.text', Cypress.env('vegaWalletPublicKeyShort'));
}); });
@@ -159,7 +161,7 @@ context(
it('should have Vega Associated currency title visible', function () { it('should have Vega Associated currency title visible', function () {
cy.get(walletContainer).within(() => { cy.get(walletContainer).within(() => {
cy.get(currencyTitle) cy.getByTestId(currencyTitle)
.should('be.visible') .should('be.visible')
.and('contain.text', `VEGAAssociated`); .and('contain.text', `VEGAAssociated`);
}); });
@@ -170,7 +172,7 @@ context(
{ tags: '@smoke' }, { tags: '@smoke' },
function () { function () {
cy.get(walletContainer).within(() => { cy.get(walletContainer).within(() => {
cy.get(currencyValue) cy.getByTestId(currencyValue)
.should('be.visible') .should('be.visible')
.and('contain.text', `0.00`); .and('contain.text', `0.00`);
}); });
@@ -202,21 +204,23 @@ context(
it('should have Manage link visible', function () { it('should have Manage link visible', function () {
cy.get(walletContainer).within(() => { cy.get(walletContainer).within(() => {
cy.get(manageLink).should('be.visible').and('have.text', 'Manage'); cy.getByTestId(manageLink)
.should('be.visible')
.and('have.text', 'Manage');
}); });
}); });
describe('when Manage dialog opened', function () { describe('when Manage dialog opened', function () {
before('click Manage link', function () { before('click Manage link', function () {
cy.get(walletContainer).within(() => { cy.get(walletContainer).within(() => {
cy.get(manageLink).click(); cy.getByTestId(manageLink).click();
}); });
}); });
// 0002-WCON-025, 0002-WCON-026 // 0002-WCON-025, 0002-WCON-026
it('should have SELECT A VEGA KEY dialog title visible', function () { it('should have SELECT A VEGA KEY dialog title visible', function () {
cy.get(dialog).within(() => { cy.get(dialog).within(() => {
cy.get(dialogHeader) cy.getByTestId(dialogHeader)
.should('be.visible') .should('be.visible')
.and('have.text', 'SELECT A VEGA KEY'); .and('have.text', 'SELECT A VEGA KEY');
}); });
@@ -236,7 +240,7 @@ context(
'contain.text', 'contain.text',
truncatedPubKey1 truncatedPubKey1
); );
cy.get(dialogVegaKey) cy.getByTestId(dialogVegaKey)
.should('be.visible') .should('be.visible')
.and('contain.text', truncatedPubKey1) .and('contain.text', truncatedPubKey1)
.and('contain.text', truncatedPubKey2); .and('contain.text', truncatedPubKey2);
@@ -246,7 +250,7 @@ context(
// 0002-WCON-029 // 0002-WCON-029
it('should have copy public key button visible', function () { it('should have copy public key button visible', function () {
cy.get(dialog).within(() => { cy.get(dialog).within(() => {
cy.get(copyPublicKeyBtn) cy.getByTestId(copyPublicKeyBtn)
.should('be.visible') .should('be.visible')
.and('contain.text', 'Copy'); .and('contain.text', 'Copy');
}); });
@@ -254,13 +258,13 @@ context(
it('should have close button visible', function () { it('should have close button visible', function () {
cy.get(dialog).within(() => { cy.get(dialog).within(() => {
cy.get(dialogCloseBtn).should('be.visible'); cy.getByTestId(dialogCloseBtn).should('be.visible');
}); });
}); });
it('should have vega Disconnect all keys button visible', function () { it('should have vega Disconnect all keys button visible', function () {
cy.get(dialog).within(() => { cy.get(dialog).within(() => {
cy.get(dialogDisconnectBtn) cy.getByTestId(dialogDisconnectBtn)
.should('be.visible') .should('be.visible')
.and('have.text', 'Disconnect all keys'); .and('have.text', 'Disconnect all keys');
}); });
@@ -269,10 +273,10 @@ context(
// 0002-WCON-022 // 0002-WCON-022
it('should be able to disconnect all keys', function () { it('should be able to disconnect all keys', function () {
cy.get(dialog).within(() => { cy.get(dialog).within(() => {
cy.get(dialogDisconnectBtn).click(); cy.getByTestId(dialogDisconnectBtn).click();
}); });
cy.get(walletContainer).within(() => { cy.get(walletContainer).within(() => {
cy.get(connectButton).should('be.visible'); // 0002-WCON-023 cy.getByTestId(connectButton).should('be.visible'); // 0002-WCON-023
}); });
}); });
}); });
@@ -285,28 +289,28 @@ context(
name: 'USDC (fake)', name: 'USDC (fake)',
symbol: 'fUSDC', symbol: 'fUSDC',
amount: '1000000', amount: '1000000',
expectedAmount: '10.00', expectedAmount: 10.0,
}, },
{ {
id: '8566db7257222b5b7ef2886394ad28b938b28680a54a169bbc795027b89d6665', id: '8566db7257222b5b7ef2886394ad28b938b28680a54a169bbc795027b89d6665',
name: 'DAI (fake)', name: 'DAI (fake)',
symbol: 'fDAI', symbol: 'fDAI',
amount: '200000', amount: '200000',
expectedAmount: '2.00', expectedAmount: 2.0,
}, },
{ {
id: '73174a6fb1d5802ba0ac7bd7ab79e0a3a4837b262de0a4e80815a55442692bd0', id: '73174a6fb1d5802ba0ac7bd7ab79e0a3a4837b262de0a4e80815a55442692bd0',
name: 'BTC (fake)', name: 'BTC (fake)',
symbol: 'fBTC', symbol: 'fBTC',
amount: '600000', amount: '600000',
expectedAmount: '6.00', expectedAmount: 6.0,
}, },
{ {
id: 'e02d4c15d790d1d2dffaf2dcd1cf06a1fe656656cf4ed18c8ce99f9e83643567', id: 'e02d4c15d790d1d2dffaf2dcd1cf06a1fe656656cf4ed18c8ce99f9e83643567',
name: 'EURO (fake)', name: 'EURO (fake)',
symbol: 'fEURO', symbol: 'fEURO',
amount: '800000', amount: '800000',
expectedAmount: '8.00', expectedAmount: 8.0,
}, },
]; ];
@@ -328,20 +332,20 @@ context(
for (const { name, symbol, expectedAmount } of assets) { for (const { name, symbol, expectedAmount } of assets) {
it(`should see ${name} within vega wallet`, () => { it(`should see ${name} within vega wallet`, () => {
cy.get(walletContainer).within(() => { cy.get(walletContainer).within(() => {
cy.get(vegaWalletCurrencyTitle) cy.getByTestId(vegaWalletCurrencyTitle)
.contains(name, txTimeout) .contains(name, txTimeout)
.should('be.visible'); .should('be.visible');
cy.get(vegaWalletCurrencyTitle) cy.getByTestId(vegaWalletCurrencyTitle)
.contains(name) .contains(name)
.parent() .parent()
.siblings() .siblings()
.invoke('text') .then((elementAmount) => {
.should('have.length.at.least', 4) const displayedAmount = parseFloat(elementAmount.text());
.then(parseFloat) expect(displayedAmount).be.gte(expectedAmount);
.should('be.gte', parseFloat(expectedAmount)); });
cy.get(vegaWalletCurrencyTitle) cy.getByTestId(vegaWalletCurrencyTitle)
.contains(name) .contains(name)
.parent() .parent()
.contains(symbol); .contains(symbol);
@@ -5,8 +5,6 @@ import {
verifyTabHighlighted, verifyTabHighlighted,
} from '../../support/common.functions'; } from '../../support/common.functions';
const connectToVegaBtn = '[data-testid="connect-to-vega-wallet-btn"]';
context( context(
'Withdraw Page - verify elements on page', 'Withdraw Page - verify elements on page',
{ tags: '@smoke' }, { tags: '@smoke' },
@@ -26,7 +24,7 @@ context(
}); });
it('should have connect Vega wallet button', function () { it('should have connect Vega wallet button', function () {
cy.get(connectToVegaBtn) cy.getByTestId('connect-to-vega-wallet-btn')
.should('be.visible') .should('be.visible')
.and('have.text', 'Connect Vega wallet'); .and('have.text', 'Connect Vega wallet');
}); });
@@ -37,7 +37,7 @@ export function navigateTo(page: navigation) {
}); });
} else { } else {
return cy.get(navigation.section, { timeout: 10000 }).within(() => { return cy.get(navigation.section, { timeout: 10000 }).within(() => {
cy.get(page).eq(0).click(); cy.get(page).eq(0).click({ force: true });
}); });
} }
} }
+1 -2
View File
@@ -5,8 +5,7 @@ import './common.functions.ts';
import './staking.functions.ts'; import './staking.functions.ts';
import './governance.functions.ts'; import './governance.functions.ts';
import './wallet-eth.functions.ts'; import './wallet-eth.functions.ts';
import './wallet-teardown.functions.ts'; import './wallet-functions.ts';
import './wallet-vega.functions.ts';
import './proposal.functions.ts'; import './proposal.functions.ts';
import 'cypress-mochawesome-reporter/register'; import 'cypress-mochawesome-reporter/register';
import registerCypressGrep from '@cypress/grep'; import registerCypressGrep from '@cypress/grep';
@@ -1,5 +1,5 @@
import { closeDialog } from './common.functions'; import { closeDialog } from './common.functions';
import { vegaWalletTeardown } from './wallet-teardown.functions'; import { vegaWalletTeardown } from './wallet-functions';
const tokenAmountInputBox = '[data-testid="token-amount-input"]'; const tokenAmountInputBox = '[data-testid="token-amount-input"]';
const tokenSubmitButton = '[data-testid="token-input-submit-button"]'; const tokenSubmitButton = '[data-testid="token-input-submit-button"]';
@@ -139,6 +139,9 @@ async function vegaWalletTeardownStaking(stakingBridgeContract: StakingBridge) {
$associatedAmount $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);
} }
}); });
}); });
@@ -173,3 +176,27 @@ export async function vegaWalletDisassociate(amount: string) {
amount = amount + '0'.repeat(18); amount = amount + '0'.repeat(18);
stakingBridgeContract.remove_stake(amount, vegaWalletPubKey); stakingBridgeContract.remove_stake(amount, vegaWalletPubKey);
} }
export function vegaWalletFaucetAssetsWithoutCheck(
asset: string,
amount: string,
vegaWalletPublicKey: string
) {
cy.highlight(`Topping up vega wallet with ${asset}, amount: ${amount}`);
cy.exec(
`curl -X POST -d '{"amount": "${amount}", "asset": "${asset}", "party": "${vegaWalletPublicKey}"}' http://localhost:1790/api/v1/mint`
)
.its('stdout')
.then((response) => {
assert.include(
response,
`"success":true`,
'Ensuring curl command was successfully undertaken'
);
});
}
export function switchVegaWalletPubKey() {
cy.get('[data-testid="manage-vega-wallet"]:visible').click();
cy.get('[data-testid="select-keypair-button"]').eq(0).click();
}
@@ -1,18 +0,0 @@
export function vegaWalletFaucetAssetsWithoutCheck(
asset: string,
amount: string,
vegaWalletPublicKey: string
) {
cy.highlight(`Topping up vega wallet with ${asset}, amount: ${amount}`);
cy.exec(
`curl -X POST -d '{"amount": "${amount}", "asset": "${asset}", "party": "${vegaWalletPublicKey}"}' http://localhost:1790/api/v1/mint`
)
.its('stdout')
.then((response) => {
assert.include(
response,
`"success":true`,
'Ensuring curl command was successfully undertaken'
);
});
}
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"presets": [ "presets": [
[ [
"@nrwl/react/babel", "@nx/react/babel",
{ {
"runtime": "automatic" "runtime": "automatic"
} }
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"], "extends": ["plugin:@nx/react", "../../.eslintrc.json"],
"ignorePatterns": ["!**/*", "__generated__", "__generated___"], "ignorePatterns": ["!**/*", "__generated__"],
"overrides": [ "overrides": [
{ {
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"], "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
+1 -1
View File
@@ -31,7 +31,7 @@ Example configurations are provided here:
For convenience, you can boot the app injecting one of the configurations above by running: For convenience, you can boot the app injecting one of the configurations above by running:
```bash ```bash
yarn nx run governance:serve --env={env} # e.g. stagnet1 yarn env-cmd -f .\apps\governance\.env.{env} yarn nx run governance:serve # e.g. stagnet1
``` ```
There are a few different configuration options offered for this app: There are a few different configuration options offered for this app:
-6
View File
@@ -1,6 +0,0 @@
function ReactMarkdown({ children }) {
// eslint-disable-next-line react/jsx-no-useless-fragment
return <>{children}</>;
}
export default ReactMarkdown;
+2 -3
View File
@@ -3,8 +3,8 @@ export default {
displayName: 'governance', displayName: 'governance',
preset: '../../jest.preset.js', preset: '../../jest.preset.js',
transform: { transform: {
'^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nrwl/react/plugins/jest', '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nx/react/plugins/jest',
'^.+\\.[tj]sx?$': 'babel-jest', '^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nx/react/babel'] }],
}, },
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
coverageDirectory: '../../coverage/apps/governance', coverageDirectory: '../../coverage/apps/governance',
@@ -16,6 +16,5 @@ export default {
'**/*.{ts,tsx}', '**/*.{ts,tsx}',
'!**/node_modules/**', '!**/node_modules/**',
'!**/__generated__/**', '!**/__generated__/**',
'!**/__generated___/**',
], ],
}; };
+14 -7
View File
@@ -1,10 +1,11 @@
{ {
"name": "governance",
"$schema": "../../node_modules/nx/schemas/project-schema.json", "$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/governance/src", "sourceRoot": "apps/governance/src",
"projectType": "application", "projectType": "application",
"targets": { "targets": {
"build": { "build": {
"executor": "./tools/executors/webpack:build", "executor": "@nx/webpack:webpack",
"outputs": ["{options.outputPath}"], "outputs": ["{options.outputPath}"],
"defaultConfiguration": "production", "defaultConfiguration": "production",
"options": { "options": {
@@ -41,7 +42,7 @@
} }
}, },
"serve": { "serve": {
"executor": "./tools/executors/webpack:serve", "executor": "@nx/webpack:dev-server",
"options": { "options": {
"port": 4210, "port": 4210,
"buildTarget": "governance:build:development", "buildTarget": "governance:build:development",
@@ -55,22 +56,28 @@
} }
}, },
"lint": { "lint": {
"executor": "@nrwl/linter:eslint", "executor": "@nx/linter:eslint",
"outputs": ["{options.outputFile}"], "outputs": ["{options.outputFile}"],
"options": { "options": {
"lintFilePatterns": ["apps/governance/**/*.{ts,tsx,js,jsx}"] "lintFilePatterns": ["apps/governance/**/*.{ts,tsx,js,jsx}"]
} }
}, },
"test": { "test": {
"executor": "@nrwl/jest:jest", "executor": "@nx/jest:jest",
"outputs": ["coverage/apps/governance"], "outputs": ["{workspaceRoot}/coverage/apps/governance"],
"options": { "options": {
"jestConfig": "apps/governance/jest.config.ts", "jestConfig": "apps/governance/jest.config.ts",
"passWithNoTests": true "passWithNoTests": true
},
"configurations": {
"ci": {
"ci": true,
"codeCoverage": true
}
} }
}, },
"build-netlify": { "build-netlify": {
"executor": "@nrwl/workspace:run-commands", "executor": "nx:run-commands",
"options": { "options": {
"commands": [ "commands": [
"cp apps/governance/netlify.toml netlify.toml", "cp apps/governance/netlify.toml netlify.toml",
@@ -79,7 +86,7 @@
} }
}, },
"build-spec": { "build-spec": {
"executor": "@nrwl/workspace:run-commands", "executor": "nx:run-commands",
"outputs": [], "outputs": [],
"options": { "options": {
"command": "yarn tsc --project ./apps/governance/tsconfig.spec.json" "command": "yarn tsc --project ./apps/governance/tsconfig.spec.json"
@@ -0,0 +1,69 @@
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');
});
});
@@ -0,0 +1,38 @@
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>
);
};

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