Compare commits

..
716 changed files with 17971 additions and 23498 deletions
-1
View File
@@ -1 +0,0 @@
node_modules
+6 -11
View File
@@ -1,7 +1,7 @@
{ {
"root": true, "root": true,
"ignorePatterns": ["**/*"], "ignorePatterns": ["**/*"],
"plugins": ["@nx", "eslint-plugin-unicorn", "jsx-a11y", "jest"], "plugins": ["@nrwl/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": {
"@nx/enforce-module-boundaries": [ "@nrwl/nx/enforce-module-boundaries": [
"error", "error",
{ {
"enforceBuildableLibDependency": true, "enforceBuildableLibDependency": true,
@@ -56,7 +56,7 @@
}, },
{ {
"files": ["*.ts", "*.tsx"], "files": ["*.ts", "*.tsx"],
"extends": ["plugin:@nx/typescript"], "extends": ["plugin:@nrwl/nx/typescript"],
"rules": { "rules": {
"@typescript-eslint/ban-ts-comment": [ "@typescript-eslint/ban-ts-comment": [
"error", "error",
@@ -80,19 +80,14 @@
}, },
{ {
"files": ["*.spec.ts", "*.spec.tsx"], "files": ["*.spec.ts", "*.spec.tsx"],
"extends": ["plugin:@nx/typescript", "plugin:jest/recommended"], "extends": ["plugin:@nrwl/nx/typescript", "plugin:jest/recommended"],
"rules": { "rules": {
"jest/consistent-test-it": [ "jest/consistent-test-it": ["error", { "fn": "it" }]
"error",
{
"fn": "it"
}
]
} }
}, },
{ {
"files": ["*.js", "*.jsx"], "files": ["*.js", "*.jsx"],
"extends": ["plugin:@nx/javascript"], "extends": ["plugin:@nrwl/nx/javascript"],
"rules": {} "rules": {}
} }
] ]
-28
View File
@@ -1,28 +0,0 @@
---
name: Feature Epic
about: A template to capture and scope user requirements, high level process, and basic mockups for an upcoming feature as part of the initial core spec review process.
title: 'FEATURE EPIC: '
labels: feature-epic
---
## Core Feature
<Name>
## Tasks
- [ ] Define high level requirements
- [ ] Create basic mockups
- [ ] Update "API Requirements" in core spec
- [ ] Update "User-Interface Spec" in relevant front end repo
- [ ] Create detailed user stories using normal template
## High Level Requirements
## Basic Mockups
## Link to API Requirements in Core spec
## Link to User Interface Specs
## Linked User Stories
+1 -1
View File
@@ -1,6 +1,6 @@
# Related issues 🔗 # Related issues 🔗
Issue: #[Issue number here] Closes #[Issue number here]
# Description # Description
@@ -7,13 +7,8 @@ on:
jobs: jobs:
after-release: after-release:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
timeout-minutes: 45 timeout-minutes: 30
steps: steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v2
@@ -30,20 +25,16 @@ jobs:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Wait for publish to complete
uses: lewagon/wait-on-check-action@v1.3.1
with:
ref: ${{ github.event.release.tag_name }}
check-name: '(CD) publish dist / trading'
repo-token: ${{ secrets.GITHUB_TOKEN }}
wait-interval: 10
- name: resolve ipfs hashes for release - name: resolve ipfs hashes for release
run: | run: |
echo "Tag name: ${{ github.event.release.tag_name }}"
echo "Name: ${{ github.event.release.name }}" echo "Name: ${{ github.event.release.name }}"
echo "Description: ${{ github.event.release.body }}" echo "Description: ${{ github.event.release.body }}"
echo "Tag: ${{ github.event.release.tag_name }}" until docker pull vegaprotocol/trading:${{ github.event.release.tag_name }}; do
docker run --rm vegaprotocol/trading:mainnet cat /ipfs-hash > ipfs-hash echo "Image not pushed yet, waiting 60 seconds"
sleep 60
done
docker run --rm vegaprotocol/trading:${{ github.event.release.tag_name }} cat /ipfs-hash > ipfs-hash
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"
+1 -1
View File
@@ -6,7 +6,7 @@ name: 'Add Issues To Project Board'
types: types:
- opened - opened
env: env:
GH_TOKEN: ${{ secrets.PROJECT_MANAGE_ACTION }} GH_TOKEN: ${{ secrets.GH_NEW_CARD_TO_PROJECT }}
PROJECT_ID: ${{ secrets.FRONT_END_PROJECT_ID }} PROJECT_ID: ${{ secrets.FRONT_END_PROJECT_ID }}
ISSUE_ID: ${{ github.event.issue.node_id }} ISSUE_ID: ${{ github.event.issue.node_id }}
USER: ${{ github.actor }} USER: ${{ github.actor }}
+80 -41
View File
@@ -5,6 +5,9 @@ on:
branches: branches:
- release/* - release/*
- develop - develop
- main
tags:
- v*
pull_request: pull_request:
types: types:
- opened - opened
@@ -19,8 +22,6 @@ 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
@@ -61,7 +62,6 @@ 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
@@ -81,22 +81,6 @@ jobs:
with: with:
main-branch-name: develop main-branch-name: develop
# See affected apps
- name: See affected apps
run: |
branch_slug="$(echo '${{ github.head_ref || github.ref_name }}' | sed -r s/[^a-zA-Z0-9]+/-/g | sed -r s/^-+\|-+$//g | cut -c 1-50 )"
python3 tools/ci/check-affected.py --github-ref="${{ github.ref }}" --branch-slug="$branch_slug" --event-name="${{ github.event_name }}"
- name: Verify script result
run: |
echo "Check outputs from script"
echo "projects: ${{ env.PROJECTS }}"
echo "projects-e2e: ${{ env.PROJECTS_E2E }}"
echo "preview_governance: ${{ env.PREVIEW_GOVERNANCE }}"
echo "preview_trading: ${{ env.PREVIEW_TRADING }}"
echo "preview_explorer: ${{ env.PREVIEW_EXPLORER }}"
echo "preview_tools: ${{ env.PREVIEW_TOOLS }}"
- name: Check formatting - name: Check formatting
run: yarn nx format:check run: yarn nx format:check
@@ -112,6 +96,70 @@ jobs:
- name: Build affected - name: Build affected
run: yarn nx affected:build || (yarn install && yarn nx affected:build) run: yarn nx affected:build || (yarn install && yarn nx affected:build)
# See affected apps
- name: See affected apps
run: |
affected="$(yarn nx print-affected --base=${{ env.NX_BASE }} --head=${{ env.NX_HEAD }} --select=projects)"
branch_slug="$(echo '${{ github.head_ref || github.ref_name }}' | sed -r s/[^a-zA-Z0-9]+/-/g | sed -r s/^-+\|-+$//g | cut -c 1-50 )"
echo ">>>> debug"
echo "NX_BASE: ${{ env.NX_BASE }}"
echo "NX_HEAD: ${{ env.NX_HEAD }}"
echo "Affected: ${affected}"
echo "Branch slug: ${branch_slug}"
echo ">>>> eof debug"
projects_e2e=""
preview_governance="not deployed"
preview_trading="not deployed"
preview_explorer="not deployed"
preview_tools="not deployed"
if echo "$affected" | grep -q governance; then
echo "Governance is affected"
projects_e2e+='"governance-e2e" '
preview_governance=$(printf "https://%s.%s.vega.rocks" "governance" "$branch_slug")
fi
if echo "$affected" | grep -q trading; then
echo "Trading is affected"
projects_e2e+='"trading-e2e" '
preview_trading=$(printf "https://%s.%s.vega.rocks" "trading" "$branch_slug")
fi
if echo "$affected" | grep -q explorer; then
echo "Explorer is affected"
projects_e2e+='"explorer-e2e" '
preview_explorer=$(printf "https://%s.%s.vega.rocks" "explorer" "$branch_slug")
fi
if [[ -z "$projects_e2e" ]]; then
projects_e2e+='"governance-e2e" "trading-e2e" "explorer-e2e" '
preview_governance=$(printf "https://%s.%s.vega.rocks" "governance" "$branch_slug")
preview_trading=$(printf "https://%s.%s.vega.rocks" "trading" "$branch_slug")
preview_explorer=$(printf "https://%s.%s.vega.rocks" "explorer" "$branch_slug")
fi
projects="$(echo $projects_e2e | sed 's|-e2e||g')"
if echo "$affected" | grep -q multisig-signer; then
echo "Tools are affected"
# tools are only applicable to check previews or deploy from develop to mainnet
if [[ "${{ github.event_name }}" = "pull_request" ]]; then
echo "Deploying tools on preview"
preview_tools=$(printf "https://%s.%s.vega.rocks" "tools" "$branch_slug")
projects+=' "multisig-signer" '
fi
if [[ "${{ github.ref }}" =~ .*develop$ ]]; then
echo "Deploying tools on s3"
projects+=' "multisig-signer" '
fi
fi
projects_e2e=${projects_e2e%?}
projects_e2e=[${projects_e2e// /,}]
projects=${projects%?}
projects=[${projects// /,}]
echo PROJECTS_E2E=$projects_e2e >> $GITHUB_ENV
echo PROJECTS=$projects >> $GITHUB_ENV
echo PREVIEW_GOVERNANCE=$preview_governance >> $GITHUB_ENV
echo PREVIEW_TRADING=$preview_trading >> $GITHUB_ENV
echo PREVIEW_EXPLORER=$preview_explorer >> $GITHUB_ENV
echo PREVIEW_TOOLS=$preview_tools >> $GITHUB_ENV
outputs: outputs:
projects: ${{ env.PROJECTS }} projects: ${{ env.PROJECTS }}
projects-e2e: ${{ env.PROJECTS_E2E }} projects-e2e: ${{ env.PROJECTS_E2E }}
@@ -120,29 +168,20 @@ jobs:
preview_explorer: ${{ env.PREVIEW_EXPLORER }} preview_explorer: ${{ env.PREVIEW_EXPLORER }}
preview_tools: ${{ env.PREVIEW_TOOLS }} preview_tools: ${{ env.PREVIEW_TOOLS }}
console-e2e:
needs: lint-test-build
name: '(CI) console python'
uses: ./.github/workflows/console-test-run.yml
secrets: inherit
if: ${{ contains(fromJSON(needs.lint-test-build.outputs.projects), 'trading') && github.event_name == 'pull_request' }}
with:
github-sha: ${{ github.event.pull_request.head.sha || github.sha }}
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:
projects: ${{ needs.lint-test-build.outputs.projects-e2e }} projects: ${{ needs.lint-test-build.outputs.projects-e2e }}
tags: '@smoke' tags: '@smoke @regression'
publish-dist: publish-dist:
needs: lint-test-build needs: lint-test-build
name: '(CD) publish dist' name: '(CD) publish dist'
if: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'vegaprotocol/frontend-monorepo') || github.event_name == 'push' }} 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:
@@ -153,7 +192,7 @@ jobs:
needs: needs:
- publish-dist - publish-dist
- lint-test-build - lint-test-build
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == 'vegaprotocol/frontend-monorepo' }} if: ${{ github.event_name == 'pull_request' }}
timeout-minutes: 60 timeout-minutes: 60
name: '(CD) comment preview links' name: '(CD) comment preview links'
steps: steps:
@@ -169,26 +208,26 @@ jobs:
# https://stackoverflow.com/questions/3183444/check-for-valid-link-url # https://stackoverflow.com/questions/3183444/check-for-valid-link-url
regex='(https?|ftp|file)://[-[:alnum:]\+&@#/%?=~_|!:,.;]*[-[:alnum:]\+&@#/%=~_|]' regex='(https?|ftp|file)://[-[:alnum:]\+&@#/%?=~_|!:,.;]*[-[:alnum:]\+&@#/%=~_|]'
if [[ "${{ needs.lint-test-build.outputs.preview_governance }}" =~ $regex ]]; then if [[ "${{ needs.lint-test-build.outputs.preview_governance }}" =~ $regex ]]; then
until curl --insecure --location --fail "${{ needs.lint-test-build.outputs.preview_governance }}"; do until curl -L --fail "${{ needs.lint-test-build.outputs.preview_governance }}"; do
echo "waiting for governance preview: ${{ needs.lint-test-build.outputs.preview_governance }}" echo "waiting for governance preview"
sleep 5 sleep 5
done done
fi fi
if [[ "${{ needs.lint-test-build.outputs.preview_explorer }}" =~ $regex ]]; then if [[ "${{ needs.lint-test-build.outputs.preview_explorer }}" =~ $regex ]]; then
until curl --insecure --location --fail "${{ needs.lint-test-build.outputs.preview_explorer }}"; do until curl -L --fail "${{ needs.lint-test-build.outputs.preview_explorer }}"; do
echo "waiting for explorer preview: ${{ needs.lint-test-build.outputs.preview_explorer }}" echo "waiting for explorer preview"
sleep 5 sleep 5
done done
fi fi
if [[ "${{ needs.lint-test-build.outputs.preview_trading }}" =~ $regex ]]; then if [[ "${{ needs.lint-test-build.outputs.preview_trading }}" =~ $regex ]]; then
until curl --insecure --location --fail "${{ needs.lint-test-build.outputs.preview_trading }}"; do until curl -L --fail "${{ needs.lint-test-build.outputs.preview_trading }}"; do
echo "waiting for trading preview: ${{ needs.lint-test-build.outputs.preview_trading }}" echo "waiting for trading preview"
sleep 5 sleep 5
done done
fi fi
if [[ "${{ needs.lint-test-build.outputs.preview_tools }}" =~ $regex ]]; then if [[ "${{ needs.lint-test-build.outputs.preview_tools }}" =~ $regex ]]; then
until curl --insecure --location --fail "${{ needs.lint-test-build.outputs.preview_tools }}"; do until curl -L --fail "${{ needs.lint-test-build.outputs.preview_tools }}"; do
echo "waiting for tools preview: ${{ needs.lint-test-build.outputs.preview_tools }}" echo "waiting for tools preview"
sleep 5 sleep 5
done done
fi fi
@@ -199,7 +238,7 @@ jobs:
with: with:
issue-number: ${{ github.event.pull_request.number }} issue-number: ${{ github.event.pull_request.number }}
body: | body: |
Previews Previews:
* governance: ${{ needs.lint-test-build.outputs.preview_governance }} * governance: ${{ needs.lint-test-build.outputs.preview_governance }}
* explorer: ${{ needs.lint-test-build.outputs.preview_explorer }} * explorer: ${{ needs.lint-test-build.outputs.preview_explorer }}
* trading: ${{ needs.lint-test-build.outputs.preview_trading }} * trading: ${{ needs.lint-test-build.outputs.preview_trading }}
-142
View File
@@ -1,142 +0,0 @@
name: (CI) Console tests
on:
workflow_call:
inputs:
github-sha:
required: true
type: string
jobs:
run-tests:
name: run-tests
runs-on: 8-cores
timeout-minutes: 20
steps:
#----------------------------------------------
# check-out frontend-monorepo
#----------------------------------------------
- name: Checkout console test repo
uses: actions/checkout@v3
with:
ref: ${{ inputs.github-sha }}
#----------------------------------------------
# cache node modules
#----------------------------------------------
- name: Cache node modules
id: cache
uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.os }}-cache-node-modules-${{ hashFiles('yarn.lock') }}
# comment out "restore-keys" if you need to rebuild yarn from 0
restore-keys: |
${{ runner.os }}-cache-node-modules-
#----------------------------------------------
# setup node
#----------------------------------------------
- name: Setup node
uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
# https://stackoverflow.com/questions/61010294/how-to-cache-yarn-packages-in-github-actions
cache: yarn
#----------------------------------------------
# install deps if cache missing
#----------------------------------------------
- name: yarn install
if: steps.cache.outputs.cache-hit != 'true'
run: yarn install --pure-lockfile
#----------------------------------------------
# build trading
#----------------------------------------------
- name: Build affected spec
run: |
yarn env-cmd -f ./apps/trading/.env.stagnet1 yarn nx export trading
#----------------------------------------------
# run trading server
#----------------------------------------------
- name: Run trading server
run: |
docker run -d -p 80:4200 -v $PWD/docker/nginx.conf:/etc/nginx/conf.d/default.conf -v $PWD/dist/apps/trading/exported:/usr/share/nginx/html nginx:1.23-alpine@sha256:6318314189b40e73145a48060bff4783a116c34cc7241532d0d94198fb2c9629
sleep 5
docker ps
#----------------------------------------------
# check if container persists between runs
#----------------------------------------------
- name: Check server
run: |
docker ps
#----------------------------------------------
# check-out tests repo
#----------------------------------------------
- name: Checkout console test repo
uses: actions/checkout@v3
with:
repository: vegaprotocol/console-test
path: './console-test'
#----------------------------------------------
# set-up python
#----------------------------------------------
- name: Set up python
id: setup-python
uses: actions/setup-python@v4
with:
python-version: '3.10.11'
#----------------------------------------------
# ----- install & configure poetry -----
#----------------------------------------------
- name: Install Poetry
uses: snok/install-poetry@v1
with:
virtualenvs-create: true
virtualenvs-in-project: true
virtualenvs-path: console-test/.venv
#----------------------------------------------
# load cached venv if cache exists
#----------------------------------------------
- name: Load cached venv
id: cached-poetry-dependencies
uses: actions/cache@v3
with:
path: console-test/.venv
key: venv-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }}
#----------------------------------------------
# install dependencies if cache does not exist
#----------------------------------------------
- name: Install dependencies
working-directory: ./console-test
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
run: poetry install --no-interaction --no-root
#----------------------------------------------
# install vega binaries
#----------------------------------------------
- name: Install vega binaries
working-directory: ./console-test
run: poetry run python -m vega_sim.tools.load_binaries --force
#----------------------------------------------
# install playwright
#----------------------------------------------
- name: install playwright
run: poetry run playwright install
working-directory: ./console-test
#----------------------------------------------
# run tests
#----------------------------------------------
- name: Run tests
working-directory: ./console-test
run: poetry run pytest -v -s --numprocesses auto --dist loadfile --durations=20
- name: Check files
run: |
ls -al .
ls -al console-test
#----------------------------------------------
# upload traces
#----------------------------------------------
- name: Upload Playwright Trace
uses: actions/upload-artifact@v3
if: always()
with:
name: playwright-trace
path: ./traces/
retention-days: 15
+1 -2
View File
@@ -20,7 +20,7 @@ jobs:
project: ${{ fromJSON(inputs.projects) }} project: ${{ fromJSON(inputs.projects) }}
name: ${{ matrix.project }} name: ${{ matrix.project }}
runs-on: self-hosted-runner runs-on: self-hosted-runner
timeout-minutes: 120 timeout-minutes: 100
steps: steps:
# Checks if skip cache was requested # Checks if skip cache was requested
- name: Set skip-nx-cache flag - name: Set skip-nx-cache flag
@@ -33,7 +33,6 @@ 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,8 +11,6 @@ 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
+105 -160
View File
@@ -19,47 +19,6 @@ 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: Init variables
run: |
echo IS_PR=false >> $GITHUB_ENV
echo IS_MAINNET_RELEASE=false >> $GITHUB_ENV
echo IS_TESTNET_RELEASE=false >> $GITHUB_ENV
echo IS_IPFS_RELEASE=false >> $GITHUB_ENV
echo IS_S3_RELEASE=false >> $GITHUB_ENV
echo IS_DEV_IMAGE=false >> $GITHUB_ENV
- name: Is dev image
if: ${{ contains(github.ref, 'develop') && github.event_name == 'push' && matrix.app == 'trading' }}
run: |
echo IS_DEV_IMAGE=true >> $GITHUB_ENV
- name: Is PR
if: ${{ github.event_name == 'pull_request' }}
run: |
echo IS_PR=true >> $GITHUB_ENV
- name: Is mainnet release
if: ${{ contains(github.ref, 'release/mainnet') && !contains(github.ref, 'mirror') }}
run: |
echo IS_MAINNET_RELEASE=true >> $GITHUB_ENV
- name: Is testnet release
if: ${{ contains(github.ref, 'release/testnet') }}
run: |
echo IS_TESTNET_RELEASE=true >> $GITHUB_ENV
- name: Is IPFS Release
if: ${{ matrix.app == 'trading' && github.event_name == 'push' && ( env.IS_MAINNET_RELEASE == 'true' || env.IS_TESTNET_RELEASE == 'true' ) }}
run: |
echo IS_IPFS_RELEASE=true >> $GITHUB_ENV
- name: Is S3 Release
if: ${{ env.IS_IPFS_RELEASE == 'false' && github.event_name == 'push' }}
run: |
echo IS_S3_RELEASE=true >> $GITHUB_ENV
- name: Set up QEMU - name: Set up QEMU
id: quemu id: quemu
@@ -72,7 +31,6 @@ 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: ${{ env.IS_PR == 'true' }}
uses: docker/login-action@v2 uses: docker/login-action@v2
with: with:
registry: ghcr.io registry: ghcr.io
@@ -81,8 +39,9 @@ jobs:
- name: Log in to the Container registry (docker hub) - name: Log in to the Container registry (docker hub)
uses: docker/login-action@v2 uses: docker/login-action@v2
if: ${{ env.IS_IPFS_RELEASE == 'true' || env.IS_DEV_IMAGE == 'true' }} if: ${{ startsWith(github.ref, 'refs/tags/v') }}
with: with:
# registry: registry.hub.docker.com
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
@@ -103,33 +62,60 @@ jobs:
- name: Define dist variables - name: Define dist variables
if: ${{ github.event_name == 'push' }} if: ${{ github.event_name == 'push' }}
run: | run: |
python3 tools/ci/define-dist-variables.py --github-ref="${{ github.ref }}" --app="${{ matrix.app }}" envName=''
domain="vega.rocks"
bucketName=''
- name: Verify script result if [[ "${{ github.ref }}" =~ .*release/.* ]]; then
if: ${{ github.event_name == 'push' }} envName="$(echo ${{ github.ref }} | rev | cut -d '/' -f 1 | rev)"
run: | elif [[ "${{ github.ref }}" =~ .*develop$ ]]; then
echo "BUCKET_NAME=${{ env.BUCKET_NAME }}" envName="stagnet1"
echo "ENV_NAME=${{ env.ENV_NAME }}" if [[ "${{ matrix.app }}" = "multisig-signer" ]]; then
envName="mainnet"
bucketName="tools.vega.xyz"
fi
elif [[ "${{ github.ref }}" =~ .*main$ ]]; then
envName="mainnet"
elif [[ "${{ matrix.app}}" = "trading" ]] && [[ "${{ startsWith(github.ref, 'refs/tags/v') && 'true' || 'false' }}" = "true" ]]; then
envName="mainnet"
fi
if [[ "${envName}" = "mainnet" ]]; then
domain="vega.xyz"
if [[ -z "${bucketName}" ]]; then
bucketName="${{ matrix.app }}.${domain}"
fi
elif [[ "${envName}" = "testnet" ]]; then
domain="fairground.wtf"
if [[ -z "${bucketName}" ]]; then
bucketName="${{ matrix.app }}.${domain}"
fi
fi
if [[ -z "${bucketName}" ]]; then
bucketName="${{ matrix.app }}.${envName}.${domain}"
fi
echo "bucket name: ${bucketName}"
echo "env name: ${envName}"
echo BUCKET_NAME=${bucketName} >> $GITHUB_ENV
echo ENV_NAME=${envName} >> $GITHUB_ENV
- name: Build local dist - name: Build local dist
run: | run: |
envCmd="" flags=""
if [[ ! -z "${{ env.ENV_NAME }}" ]]; then if [[ ! -z "${{ env.ENV_NAME }}" ]]; then
envCmd="yarn env-cmd -f ./apps/${{ matrix.app }}/.env.${{ env.ENV_NAME }}" if [[ "${{ env.ENV_NAME }}" != "ops-vega" ]]; then
flags="--env=${{ env.ENV_NAME }}"
fi
fi fi
if [ "${{ matrix.app }}" = "trading" ]; then if [ "${{ matrix.app }}" = "trading" ]; then
$envCmd yarn nx export trading || (yarn install && $envCmd yarn nx export trading) yarn nx export trading $flags || (yarn install && yarn nx export trading $flags)
DIST_LOCATION=dist/apps/trading/exported DIST_LOCATION=dist/apps/trading/exported
elif [ "${{ matrix.app }}" = "ui-toolkit" ]; then
NODE_ENV=production yarn nx run ui-toolkit:build-storybook
DIST_LOCATION=dist/storybook/ui-toolkit
elif [ "${{ matrix.app }}" = "static" ]; then
yarn nx build static || (yarn install && yarn nx build static)
else else
$envCmd yarn nx build ${{ matrix.app }} || (yarn install && $envCmd yarn nx build ${{ matrix.app }}) yarn nx build ${{ matrix.app }} $flags || (yarn install && yarn nx build ${{ matrix.app }} $flags)
fi
if [[ -z "$DIST_LOCATION" ]]; then
DIST_LOCATION=dist/apps/${{ matrix.app }} DIST_LOCATION=dist/apps/${{ matrix.app }}
fi fi
mv $DIST_LOCATION dist-result mv $DIST_LOCATION dist-result
@@ -149,7 +135,7 @@ jobs:
ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:local
- name: Image digest - name: Image digest
if: ${{ env.IS_PR == 'true' }} if: ${{ github.event_name == 'pull_request' }}
run: echo ${{ steps.docker_build.outputs.digest }} run: echo ${{ steps.docker_build.outputs.digest }}
- name: Sanity check docker image - name: Sanity check docker image
@@ -162,9 +148,7 @@ jobs:
- name: Publish dist as docker image (ghcr) - name: Publish dist as docker image (ghcr)
uses: docker/build-push-action@v3 uses: docker/build-push-action@v3
continue-on-error: true if: ${{ github.event_name == 'pull_request' || (matrix.app == 'trading' && github.event_name == 'push' && !startsWith(github.ref, 'refs/tags/v') ) }}
id: ghcr-push
if: ${{ env.IS_PR == 'true' }}
with: with:
context: . context: .
file: docker/node-outside-docker.Dockerfile file: docker/node-outside-docker.Dockerfile
@@ -177,9 +161,7 @@ jobs:
- name: Publish dist as docker image (docker hub) - name: Publish dist as docker image (docker hub)
uses: docker/build-push-action@v3 uses: docker/build-push-action@v3
continue-on-error: true if: ${{ matrix.app == 'trading' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') }}
id: dockerhub-push
if: ${{ env.IS_IPFS_RELEASE == 'true' || env.IS_DEV_IMAGE == 'true' }}
with: with:
context: . context: .
file: docker/node-outside-docker.Dockerfile file: docker/node-outside-docker.Dockerfile
@@ -188,41 +170,13 @@ jobs:
APP=${{ matrix.app }} APP=${{ matrix.app }}
ENV_NAME=${{ env.ENV_NAME }} ENV_NAME=${{ env.ENV_NAME }}
tags: | tags: |
vegaprotocol/${{ matrix.app }}:${{ github.sha }} vegaprotocol/${{ matrix.app }}:${{ github.ref_name }}
vegaprotocol/${{ matrix.app }}:${{ env.IS_MAINNET_RELEASE == 'true' && 'mainnet' || env.IS_TESTNET_RELEASE == 'true' && 'testnet' || env.IS_DEV_IMAGE == 'true' && 'develop' || '' }} vegaprotocol/${{ matrix.app }}:mainnet
- name: Publish dist as docker image (ghcr - retry)
uses: docker/build-push-action@v3
if: ${{ steps.ghcr-push.outcome == 'failure' }}
with:
context: .
file: docker/node-outside-docker.Dockerfile
push: true
build-args: |
APP=${{ matrix.app }}
ENV_NAME=${{ env.ENV_NAME }}
tags: |
ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:${{ github.event.pull_request.head.sha || github.sha }}
- name: Publish dist as docker image (docker hub - retry)
uses: docker/build-push-action@v3
if: ${{ steps.dockerhub-push.outcome == 'failure' }}
with:
context: .
file: docker/node-outside-docker.Dockerfile
push: true
build-args: |
APP=${{ matrix.app }}
ENV_NAME=${{ env.ENV_NAME }}
tags: |
vegaprotocol/${{ matrix.app }}:${{ github.sha }}
vegaprotocol/${{ matrix.app }}:${{ env.IS_MAINNET_RELEASE == 'true' && 'mainnet' || env.IS_TESTNET_RELEASE == 'true' && 'testnet' || '' }}
# bucket creation in github.com/vegaprotocol/terraform//frontend # bucket creation in github.com/vegaprotocol/terraform//frontend
- name: Publish dist to s3 - name: Publish dist to s3
uses: jakejarvis/s3-sync-action@master uses: jakejarvis/s3-sync-action@master
# s3 releases are not happening for trading on mainnet - it's IPFS if: ${{ github.event_name == 'push' && !startsWith(github.ref, 'refs/tags/v') && ( matrix.app != 'trading' || (matrix.app == 'trading' && !endsWith(github.ref, 'main') ) ) }}
if: ${{ env.IS_S3_RELEASE == 'true' }}
with: with:
args: --acl private --follow-symlinks --delete args: --acl private --follow-symlinks --delete
env: env:
@@ -232,62 +186,30 @@ jobs:
AWS_REGION: 'eu-west-1' AWS_REGION: 'eu-west-1'
SOURCE_DIR: 'dist-result' SOURCE_DIR: 'dist-result'
- name: Install aws CLI
if: ${{ env.IS_S3_RELEASE == 'true' }}
uses: unfor19/install-aws-cli-action@master
- name: Perform cache invalidation
if: ${{ env.IS_S3_RELEASE == 'true' }}
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: ${{ env.IS_PR == 'true' }} if: ${{ github.event_name == 'pull_request' }}
with: with:
labels: ${{ matrix.app }}-preview labels: ${{ matrix.app }}-preview
number: ${{ github.event.number }} number: ${{ github.event.number }}
- name: Trigger fleek deployment - name: Trigger fleek deployment
# release to ipfs happens only on mainnet (represented by main branch) for trading if: ${{ matrix.app == 'trading' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') }}
if: ${{ env.IS_IPFS_RELEASE == 'true' }}
run: | run: |
if [[ "${{ env.IS_MAINNET_RELEASE }}" = "true" ]]; then # display info about app
# display info about app curl -H "Authorization: ${{ secrets.FLEEK_API_KEY }}" \
curl --fail -H "Authorization: ${{ secrets.FLEEK_API_KEY }}" \ -H "Content-Type: application/json" \
-H "Content-Type: application/json" \ -d '{"query": "query{getSiteById(siteId:\"f8f2e051-f18e-49e6-b876-0a39369dc0d8\"){id latestDeploy{id status}}}"}' \
-d '{"query": "query{getSiteById(siteId:\"f8f2e051-f18e-49e6-b876-0a39369dc0d8\"){id latestDeploy{id status}}}"}' \ https://api.fleek.co/graphql
https://api.fleek.co/graphql
# trigger new deployment as base image is always set to vegaprotocol/trading:mainnet # trigger new deployment as base image is always set to vegaprotocol/trading:mainnet
curl --fail -H "Authorization: ${{ secrets.FLEEK_API_KEY }}" \ curl -H "Authorization: ${{ secrets.FLEEK_API_KEY }}" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{"query": "mutation{triggerDeploy(siteId:\"f8f2e051-f18e-49e6-b876-0a39369dc0d8\"){id status}}"}' \ -d '{"query": "mutation{triggerDeploy(siteId:\"f8f2e051-f18e-49e6-b876-0a39369dc0d8\"){id status}}"}' \
https://api.fleek.co/graphql https://api.fleek.co/graphql
elif [[ "${{ env.IS_TESTNET_RELEASE }}" = "true" ]]; then
# display info about app
curl --fail -H "Authorization: ${{ secrets.FLEEK_API_KEY }}" \
-H "Content-Type: application/json" \
-d '{"query": "query{getSiteById(siteId:\"79baaeca-1952-4ae7-a256-f668cfc1d68e\"){id latestDeploy{id status}}}"}' \
https://api.fleek.co/graphql
# trigger new deployment as base image is always set to vegaprotocol/trading:mainnet
curl --fail -H "Authorization: ${{ secrets.FLEEK_API_KEY }}" \
-H "Content-Type: application/json" \
-d '{"query": "mutation{triggerDeploy(siteId:\"79baaeca-1952-4ae7-a256-f668cfc1d68e\"){id status}}"}' \
https://api.fleek.co/graphql
fi
- name: Check out ipfs-redirect - name: Check out ipfs-redirect
if: ${{ env.IS_IPFS_RELEASE == 'true' }} if: ${{ matrix.app == 'trading' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') }}
uses: actions/checkout@v3 uses: actions/checkout@v3
with: with:
repository: 'vegaprotocol/ipfs-redirect' repository: 'vegaprotocol/ipfs-redirect'
@@ -295,12 +217,11 @@ jobs:
fetch-depth: '0' fetch-depth: '0'
token: ${{ secrets.VEGA_CI_BOT_GITHUB_TOKEN }} token: ${{ secrets.VEGA_CI_BOT_GITHUB_TOKEN }}
- name: Update interstitial page to point to the new console - name: Update console.vega.xyz DNS to redirect to the new console
if: ${{ env.IS_IPFS_RELEASE == 'true' }} if: ${{ matrix.app == 'trading' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') }}
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"
@@ -308,28 +229,52 @@ 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"
# update CID files branch_name="update-hash-${{ github.ref }}"
if [[ "${{ env.IS_MAINNET_RELEASE }}" = "true" ]]; then git checkout -b "$branch_name"
echo $new_hash > cidv0-mainnet.txt
echo $new_cid > cidv1-mainnet.txt
git add cidv0-mainnet.txt cidv1-mainnet.txt
elif [[ "${{ env.IS_TESTNET_RELEASE }}" = "true" ]]; 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 "main" git push -u origin "$branch_name"
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
) )
# # 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="https://${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=console.vega.xyz
# dnsimple_record_id=44409591
# # see: https://dnsimple.com/a/84895/domains/console.vega.xyz/records/44409591/edit
# curl -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}
-1
View File
@@ -15,7 +15,6 @@ on:
- types - types
- utils - utils
- i18n - i18n
- wallet
jobs: jobs:
publish: publish:
+29
View File
@@ -0,0 +1,29 @@
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
@@ -0,0 +1,14 @@
{
"extends": "../tsconfig.base.json",
"exclude": [
"../**/*.spec.js",
"../**/*.test.js",
"../**/*.spec.ts",
"../**/*.test.ts",
"../**/*.spec.tsx",
"../**/*.test.tsx",
"../**/*.spec.jsx",
"../**/*.test.jsx"
],
"include": ["../**/*"]
}
+3 -4
View File
@@ -1,11 +1,10 @@
{ {
"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": "@nx/cypress:cypress", "executor": "@nrwl/cypress:cypress",
"options": { "options": {
"cypressConfig": "apps/explorer-e2e/cypress.config.js", "cypressConfig": "apps/explorer-e2e/cypress.config.js",
"devServerTarget": "explorer:serve" "devServerTarget": "explorer:serve"
@@ -17,14 +16,14 @@
} }
}, },
"lint": { "lint": {
"executor": "@nx/linter:eslint", "executor": "@nrwl/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": "nx:run-commands", "executor": "@nrwl/workspace:run-commands",
"outputs": [], "outputs": [],
"options": { "options": {
"command": "yarn tsc --project ./apps/explorer-e2e/" "command": "yarn tsc --project ./apps/explorer-e2e/"
@@ -31,7 +31,7 @@ context('Asset page', { tags: '@regression' }, () => {
}); });
}); });
it.skip('should open details page when clicked on "View details"', () => { it('should open details page when clicked on "View details"', () => {
cy.getAssets().then((assets) => { cy.getAssets().then((assets) => {
assets.forEach((asset) => { assets.forEach((asset) => {
cy.get(`[row-id="${asset.id}"] [col-id="actions"] button`) cy.get(`[row-id="${asset.id}"] [col-id="actions"] button`)
@@ -169,6 +169,7 @@ context.skip('Parties page', { tags: '@regression' }, function () {
const jsonFields = '.hljs'; const jsonFields = '.hljs';
const sideMenuBackground = '.absolute'; const sideMenuBackground = '.absolute';
// Engage dark mode if not allready set
cy.get(sideMenuBackground) cy.get(sideMenuBackground)
.should('have.css', 'background-color') .should('have.css', 'background-color')
.then((background_color) => { .then((background_color) => {
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"presets": [ "presets": [
[ [
"@nx/react/babel", "@nrwl/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.rocks NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
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.rocks NX_TENDERMINT_URL=https://tm.n01.stagnet1.vega.xyz
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.rocks/rest NX_BLOCK_EXPLORER=https://be.stagnet1.vega.xyz/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
+1 -1
View File
@@ -1,5 +1,5 @@
# App configuration variables # App configuration variables
NX_TENDERMINT_URL=https://tm.be.devnet1.vega.xyz/ NX_TENDERMINT_URL=https://tm.be.devnet1.vega.xyz/tm
NX_TENDERMINT_WEBSOCKET_URL=wss://be.devnet1.vega.xyz/websocket NX_TENDERMINT_WEBSOCKET_URL=wss://be.devnet1.vega.xyz/websocket
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/devnet1/vegawallet-devnet1.toml NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/devnet1/vegawallet-devnet1.toml
NX_VEGA_ENV=DEVNET NX_VEGA_ENV=DEVNET
+1 -1
View File
@@ -5,7 +5,7 @@ NX_SENTRY_DSN=https://b3a56b03eda842faad731f3ea9dfd1bc@o286262.ingest.sentry.io/
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/mainnet1/mainnet1.toml NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/master/mainnet1/mainnet1.toml
NX_VEGA_URL=https://api.vega.community/graphql NX_VEGA_URL=https://api.vega.community/graphql
NX_VEGA_ENV=MAINNET NX_VEGA_ENV=MAINNET
NX_BLOCK_EXPLORER=https://be.vega.community/rest NX_BLOCK_EXPLORER=https://be.vega.community/rest/
NX_ETHERSCAN_URL=https://etherscan.io NX_ETHERSCAN_URL=https://etherscan.io
NX_VEGA_GOVERNANCE_URL=https://governance.vega.xyz NX_VEGA_GOVERNANCE_URL=https://governance.vega.xyz
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/mainnet/announcements.json NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/mainnet/announcements.json
-13
View File
@@ -1,13 +0,0 @@
# App configuration variables
NX_TENDERMINT_URL=https://be.mainnet-mirror.vega.rocks
NX_TENDERMINT_WEBSOCKET_URL=wss://be.mainnet-mirror.vega.rocks/websocket
NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40@o286262.ingest.sentry.io/5882996
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/mainnet-mirror/vegawallet-mainnet-mirror.toml
NX_VEGA_URL=https://api.mainnet-mirror.vega.rocks/graphql
NX_VEGA_ENV=MAINNET-MIRROR
NX_BLOCK_EXPLORER=https://be.mainnet-mirror.vega.rocks/rest
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_GOVERNANCE_URL=https://governance.mainnet-mirror.vega.rocks
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/mainnet/announcements.json
NX_VEGA_EXPLORER_URL=https://explorer.mainnet-mirror.vega.rocks/
NX_VEGA_CONSOLE_URL=https://console.mainnet-mirror.vega.rocks
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"extends": ["plugin:@nx/react", "../../.eslintrc.json"], "extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"],
"ignorePatterns": ["!**/*", "__generated__"], "ignorePatterns": ["!**/*", "__generated__", "__generated___"],
"overrides": [ "overrides": [
{ {
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"], "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
+1 -2
View File
@@ -32,7 +32,6 @@ yarn nx serve explorer
Example configurations are provided here: Example configurations are provided here:
- [Mainnet](./.env.mainnet) - [Mainnet](./.env.mainnet)
- [Mainnet-mirror](./.env.mainnet-mirror)
- [Devnet](./.env.devnet) - [Devnet](./.env.devnet)
- [Capsule](./.env.capsule) - [Capsule](./.env.capsule)
- [Testnet](./.env.testnet) - [Testnet](./.env.testnet)
@@ -40,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 env-cmd -f .\apps\explorer\.env.{env} yarn nx run explorer:serve # e.g. stagnet1 yarn nx run explorer:serve --env={env} # 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)$)': '@nx/react/plugins/jest', '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nrwl/react/plugins/jest',
'^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nx/react/babel'] }], '^.+\\.[tj]sx?$': 'babel-jest',
}, },
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
coverageDirectory: '../../coverage/apps/explorer', coverageDirectory: '../../coverage/apps/explorer',
+9 -16
View File
@@ -1,11 +1,10 @@
{ {
"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": "@nx/webpack:webpack", "executor": "./tools/executors/webpack:build",
"outputs": ["{options.outputPath}"], "outputs": ["{options.outputPath}"],
"defaultConfiguration": "production", "defaultConfiguration": "production",
"options": { "options": {
@@ -39,7 +38,7 @@
} }
}, },
"serve": { "serve": {
"executor": "@nx/webpack:dev-server", "executor": "./tools/executors/webpack:serve",
"options": { "options": {
"port": 3000, "port": 3000,
"buildTarget": "explorer:build:development", "buildTarget": "explorer:build:development",
@@ -53,36 +52,30 @@
} }
}, },
"lint": { "lint": {
"executor": "@nx/linter:eslint", "executor": "@nrwl/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": "@nx/jest:jest", "executor": "@nrwl/jest:jest",
"outputs": ["{workspaceRoot}/coverage/apps/explorer"], "outputs": ["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": "nx:run-commands", "executor": "@nrwl/workspace:run-commands",
"options": { "options": {
"commands": [ "commands": [
"npx openapi-typescript https://raw.githubusercontent.com/vegaprotocol/documentation/spec-update-v0.72.0-preview.2/specs/v0.72.0-preview.2/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.67.3/blockexplorer.openapi.json --output apps/explorer/src/types/explorer.d.ts --immutable-types"
] ]
} }
}, },
"build-netlify": { "build-netlify": {
"executor": "nx:run-commands", "executor": "@nrwl/workspace:run-commands",
"options": { "options": {
"commands": [ "commands": [
"cp apps/explorer/netlify.toml netlify.toml", "cp apps/explorer/netlify.toml netlify.toml",
@@ -91,7 +84,7 @@
} }
}, },
"build-spec": { "build-spec": {
"executor": "nx:run-commands", "executor": "@nrwl/workspace: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, ColDef } from 'ag-grid-community'; import type { RowClickedEvent } from 'ag-grid-community';
type AssetsTableProps = { type AssetsTableProps = {
data: AssetFieldsFragment[] | null; data: AssetFieldsFragment[] | null;
@@ -31,58 +31,6 @@ 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}
@@ -98,11 +46,60 @@ 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,13 +1,29 @@
import WS from 'jest-websocket-mock'; import WS from 'jest-websocket-mock';
import { render, screen } from '@testing-library/react'; import useWebSocket from 'react-use-websocket';
import {
render,
screen,
fireEvent,
act,
waitFor,
} from '@testing-library/react';
import { TendermintWebsocketContext } from '../../contexts/websocket/tendermint-websocket-context';
import { BlocksRefetch } from './blocks-refetch'; import { BlocksRefetch } from './blocks-refetch';
const BlocksRefetchInWebsocketProvider = ({ const BlocksRefetchInWebsocketProvider = ({
callback, callback,
mocketLocation,
}: { }: {
callback: () => null; callback: () => null;
mocketLocation: string;
}) => { }) => {
return <BlocksRefetch refetch={callback} />; const contextShape = useWebSocket(mocketLocation);
return (
<TendermintWebsocketContext.Provider value={{ ...contextShape }}>
<BlocksRefetch refetch={callback} />
</TendermintWebsocketContext.Provider>
);
}; };
describe('Blocks refetch', () => { describe('Blocks refetch', () => {
@@ -16,8 +32,111 @@ describe('Blocks refetch', () => {
const mocket = new WS(mocketLocation, { jsonProtocol: true }); const mocket = new WS(mocketLocation, { jsonProtocol: true });
new WebSocket(mocketLocation); new WebSocket(mocketLocation);
render(<BlocksRefetchInWebsocketProvider callback={() => null} />); render(
<BlocksRefetchInWebsocketProvider
callback={() => null}
mocketLocation={mocketLocation}
/>
);
await mocket.connected;
expect(screen.getByTestId('new-blocks')).toHaveTextContent('new blocks');
expect(screen.getByTestId('refresh')).toBeInTheDocument(); expect(screen.getByTestId('refresh')).toBeInTheDocument();
mocket.close(); mocket.close();
}); });
it('should initiate callback when the button is clicked', async () => {
const mocketLocation = 'wss:localhost:3003';
const mocket = new WS(mocketLocation, { jsonProtocol: true });
new WebSocket(mocketLocation);
const callback = jest.fn();
render(
<BlocksRefetchInWebsocketProvider
callback={callback}
mocketLocation={mocketLocation}
/>
);
await mocket.connected;
const button = screen.getByTestId('refresh');
act(() => {
fireEvent.click(button);
});
expect(callback.mock.calls.length).toEqual(1);
mocket.close();
});
it('should show new blocks as websocket is correctly updated', async () => {
const mocketLocation = 'wss:localhost:3004';
const mocket = new WS(mocketLocation, { jsonProtocol: true });
new WebSocket(mocketLocation);
render(
<BlocksRefetchInWebsocketProvider
callback={() => null}
mocketLocation={mocketLocation}
/>
);
await mocket.connected;
// Ensuring we send an ID equal to the one the client subscribed with.
await waitFor(() => expect(mocket.messages.length).toEqual(1));
// @ts-ignore id on messages
const id = mocket.messages[0].id;
const newBlockMessage = {
id,
result: {
query: "tm.event = 'NewBlock'",
},
};
expect(screen.getByTestId('new-blocks')).toHaveTextContent('0 new blocks');
act(() => {
mocket.send(newBlockMessage);
});
expect(screen.getByTestId('new-blocks')).toHaveTextContent('1 new blocks');
act(() => {
mocket.send(newBlockMessage);
});
expect(screen.getByTestId('new-blocks')).toHaveTextContent('2 new blocks');
mocket.close();
});
it('will not show new blocks if websocket has wrong ID', async () => {
const mocketLocation = 'wss:localhost:3005';
const mocket = new WS(mocketLocation, { jsonProtocol: true });
new WebSocket(mocketLocation);
render(
<BlocksRefetchInWebsocketProvider
callback={() => null}
mocketLocation={mocketLocation}
/>
);
await mocket.connected;
// Ensuring we send an ID equal to the one the client subscribed with.
await waitFor(() => expect(mocket.messages.length).toEqual(1));
const newBlockMessageBadId = {
id: 'blahblahblah',
result: {
query: "tm.event = 'NewBlock'",
},
};
expect(screen.getByTestId('new-blocks')).toHaveTextContent('0 new blocks');
act(() => {
mocket.send(newBlockMessageBadId);
});
expect(screen.getByTestId('new-blocks')).toHaveTextContent('0 new blocks');
mocket.close();
});
}); });
@@ -1,19 +1,36 @@
import { useState, useEffect } from 'react';
import { useTendermintWebsocket } from '../../hooks/use-tendermint-websocket';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { Button, Icon } from '@vegaprotocol/ui-toolkit'; import { ButtonLink } from '@vegaprotocol/ui-toolkit';
interface BlocksRefetchProps { interface BlocksRefetchProps {
refetch: () => void; refetch: () => void;
} }
export const BlocksRefetch = ({ refetch }: BlocksRefetchProps) => { export const BlocksRefetch = ({ refetch }: BlocksRefetchProps) => {
const [blocksToLoad, setBlocksToLoad] = useState<number>(0);
const { messages } = useTendermintWebsocket({
query: "tm.event = 'NewBlock'",
});
useEffect(() => {
if (messages.length > 0) {
setBlocksToLoad((prev) => prev + 1);
}
}, [messages]);
const refresh = () => { const refresh = () => {
refetch(); refetch();
setBlocksToLoad(0);
}; };
return ( return (
<Button onClick={refresh} data-testid="refresh" size="xs"> <div className="mb-4">
<Icon name="refresh" className="!align-baseline mr-2" size={3} /> <span data-testid="new-blocks">{blocksToLoad} new blocks - </span>
{t('Load new')} <ButtonLink onClick={refresh} data-testid="refresh">
</Button> {t('refresh to see latest')}
</ButtonLink>
</div>
); );
}; };
@@ -1,4 +1,5 @@
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,98 +1,33 @@
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 screen = render( const zeroes =
<MockedProvider> '0000000000000000000000000000000000000000000000000000000000000000';
<PartyLink id={zeroes} /> const screen = render(<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( const screen = render(<PartyLink id="network" />);
<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( const screen = render(<PartyLink id="this-party-is-not-valid" />);
<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(
<MockedProvider> <MemoryRouter>
<MemoryRouter> <PartyLink id={aValidParty} />
<PartyLink id={aValidParty} /> </MemoryRouter>
</MemoryRouter>
</MockedProvider>
); );
const el = screen.getByText(aValidParty); const el = screen.getByText(aValidParty);
@@ -1,44 +1,22 @@
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 { useMemo, type ComponentProps } from 'react'; import 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 { Icon, truncateMiddle } from '@vegaprotocol/ui-toolkit'; import { 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) {
@@ -60,20 +38,13 @@ const PartyLink = ({ id, truncate = false, ...props }: PartyLinkProps) => {
} }
return ( return (
<span className="whitespace-nowrap"> <Link
{useName && <Icon size={4} name="cube" className="mr-2" />} className="underline font-mono"
<Link {...props}
className="underline font-mono" to={`/${Routes.PARTIES}/${id}`}
{...props} >
to={`/${Routes.PARTIES}/${id}`} <Hash text={truncate ? truncateMiddle(id) : id} />
> </Link>
{useName ? (
name
) : (
<Hash text={truncate ? truncateMiddle(id, 4, 4) : id} />
)}
</Link>
</span>
); );
}; };
@@ -1,9 +1,8 @@
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 type { ColDef } from 'ag-grid-community'; import { AgGridColumn } from 'ag-grid-react';
import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid'; import { AgGridLazy as AgGrid } from '@vegaprotocol/datagrid';
import type { import type {
VegaICellRendererParams, VegaICellRendererParams,
@@ -40,34 +39,54 @@ export const MarketsTable = ({ data }: MarketsTableProps) => {
}; };
}, []); }, []);
const columnDefs = useMemo<ColDef[]>( return (
() => [ <AgGrid
{ ref={gridRef}
colId: 'code', rowData={data}
headerName: t('Code'), getRowId={({ data }: { data: MarketFieldsFragment }) => data.id}
field: 'tradableInstrument.instrument.code', overlayNoRowsTemplate={t('This chain has no markets')}
}, domLayout="autoHeight"
{ defaultColDef={{
colId: 'name', flex: 1,
headerName: t('Name'), resizable: true,
field: 'tradableInstrument.instrument.name', sortable: true,
}, filter: true,
{ filterParams: { buttons: ['reset'] },
headerName: t('Status'), autoHeight: true,
field: 'state', }}
hide: window.innerWidth <= BREAKPOINT_MD, suppressCellFocus={true}
valueGetter: ({ onRowClicked={({ data, event }: RowClickedEvent) => {
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>) => { }: VegaValueGetterParams<MarketFieldsFragment, 'state'>) => {
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,
@@ -86,19 +105,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 ? (
@@ -107,34 +126,9 @@ 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,10 +13,6 @@ 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, 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 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 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, 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 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 const ExplorerDeterministicOrderFieldsFragmentDoc = gql` export const ExplorerDeterministicOrderFieldsFragmentDoc = gql`
fragment ExplorerDeterministicOrderFields on Order { fragment ExplorerDeterministicOrderFields on Order {
@@ -29,10 +29,6 @@ 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 | undefined, version: number,
amend: Amend, amend: Amend,
mocks: MockedResponse[] mocks: MockedResponse[]
) { ) {
@@ -25,11 +25,7 @@ function renderAmendOrderDetails(
); );
} }
function renderExistingAmend( function renderExistingAmend(id: string, version: number, amend: Amend) {
id: string,
version: number | undefined,
amend: Amend
) {
const mocks = [ const mocks = [
{ {
request: { request: {
@@ -53,7 +49,6 @@ function renderExistingAmend(
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',
@@ -82,56 +77,6 @@ function renderExistingAmend(
}, },
}, },
}, },
{
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,
@@ -212,15 +157,4 @@ 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. Latest is provided by default // Version to fetch, with 0 being 'latest' and 1 being 'first'. Defaults to 0
version?: number; version?: number;
} }
@@ -34,11 +34,13 @@ export function getSideDeltaColour(delta: string): string {
* @param param0 * @param param0
* @returns * @returns
*/ */
const AmendOrderDetails = ({ id, version, amend }: AmendOrderDetailsProps) => { const AmendOrderDetails = ({
const variables = version ? { orderId: id, version } : { orderId: id }; id,
version = 0,
amend,
}: AmendOrderDetailsProps) => {
const { data, error } = useExplorerDeterministicOrderQuery({ const { data, error } = useExplorerDeterministicOrderQuery({
variables, variables: { orderId: id, version },
}); });
if (error || (data && !data.orderByID)) { if (error || (data && !data.orderByID)) {
@@ -5,7 +5,6 @@ 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;
@@ -69,35 +68,25 @@ 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-200"> <p className="text-gray-500 mb-4">
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 mt-4"> <p className="text-gray-500 mb-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 mt-4"> <div className="grid md:grid-cols-4 gap-x-6">
<div className="mb-12 md:mb-0"> {version !== 0 ? null : (
<h2 className="text-2xl font-bold text-dark mb-4"> <div className="mb-12 md:mb-0">
{t('Status')} <h2 className="text-2xl font-bold text-dark mb-4">
</h2> {t('Status')}
<h5 className="text-lg font-medium text-gray-500 mb-0 capitalize"> </h2>
{statusText[o.status]} <h5 className="text-lg font-medium text-gray-500 mb-0 capitalize">
</h5> {statusText[o.status]}
</div> </h5>
</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>
@@ -106,6 +95,17 @@ 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,7 +31,6 @@ 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,6 +1,7 @@
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 {
@@ -8,7 +9,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, ColDef } from 'ag-grid-community'; import type { RowClickedEvent } 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 {
@@ -63,128 +64,7 @@ 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
@@ -203,7 +83,6 @@ 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 (
@@ -215,7 +94,128 @@ 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, Tooltip } from '@vegaprotocol/ui-toolkit'; import { Icon } 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,8 +17,6 @@ interface ChainResponseCodeProps {
code: number; code: number;
hideLabel?: boolean; hideLabel?: boolean;
error?: string; error?: string;
hideIfOk?: boolean;
small?: boolean;
} }
/** /**
@@ -30,21 +28,14 @@ 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 size={size} name="tick-circle" className={`${successColour}`} /> <Icon name="tick-circle" className={successColour} />
) : ( ) : (
<Icon size={size} name="cross" className="!fill-vega-pink-500" /> <Icon name="cross" className="fill-vega-pink-600" />
); );
const label = ErrorCodes.get(code) || 'Unknown response code'; const label = ErrorCodes.get(code) || 'Unknown response code';
@@ -53,28 +44,18 @@ 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 (
<Tooltip <div title={`Response code: ${code} - ${label}`} className=" inline-block">
description={ <span
<span> className="mr-2"
Response code: {code} - {label} aria-label={isSuccess ? 'Success' : 'Warning'}
</span> role="img"
} >
> {icon}
<div className="mt-[-1px] inline-block"> </span>
<span {hideLabel ? null : <span>{label}</span>}
className="mr-2" {!hideLabel && !!displayError ? (
aria-label={isSuccess ? 'Success' : 'Warning'} <span className="ml-1 whitespace-pre">&mdash;&nbsp;{displayError}</span>
role="img" ) : null}
> </div>
{icon}
</span>
{hideLabel ? null : <span>{label}</span>}
{!hideLabel && !!displayError ? (
<span className="ml-1 whitespace-pre">
&mdash;&nbsp;{displayError}
</span>
) : null}
</div>
</Tooltip>
); );
}; };
@@ -1,113 +0,0 @@
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'
);
});
});
@@ -1,72 +0,0 @@
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>
);
};
@@ -6,32 +6,10 @@ import {
SPECIAL_CASE_NETWORK_ID, SPECIAL_CASE_NETWORK_ID,
} from '../../../../links/party-link/party-link'; } from '../../../../links/party-link/party-link';
import SizeInAsset from '../../../../size-in-asset/size-in-asset'; import SizeInAsset from '../../../../size-in-asset/size-in-asset';
import { AccountTypeMapping } from '@vegaprotocol/types';
import { AccountType } from '@vegaprotocol/types';
import { headerClasses, wrapperClasses } from '../transfer-details'; import { headerClasses, wrapperClasses } from '../transfer-details';
import type { components } from '../../../../../../types/explorer'; import type { Transfer } from '../transfer-details';
type Transfer = components['schemas']['commandsv1Transfer'];
type AccountTypes = components['schemas']['vegaAccountType'];
const AccountType: Record<AccountTypes, string> = {
ACCOUNT_TYPE_UNSPECIFIED: 'Unspecified',
ACCOUNT_TYPE_INSURANCE: 'Insurance',
ACCOUNT_TYPE_SETTLEMENT: 'Settlement',
ACCOUNT_TYPE_MARGIN: 'Margin',
ACCOUNT_TYPE_GENERAL: 'General',
ACCOUNT_TYPE_FEES_INFRASTRUCTURE: 'Infrastructure',
ACCOUNT_TYPE_FEES_LIQUIDITY: 'Liquidity',
ACCOUNT_TYPE_FEES_MAKER: 'Maker',
ACCOUNT_TYPE_BOND: 'Bond',
ACCOUNT_TYPE_EXTERNAL: 'External',
ACCOUNT_TYPE_GLOBAL_INSURANCE: 'Global Insurance',
ACCOUNT_TYPE_GLOBAL_REWARD: 'Global Reward',
ACCOUNT_TYPE_PENDING_TRANSFERS: 'Pending Transfers',
ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES: 'Maker Paid Fees',
ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES: 'Maker Received Fees',
ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES: 'LP Received Fees',
ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS: 'Market Proposers',
ACCOUNT_TYPE_HOLDING: 'Holding',
};
interface TransferParticipantsProps { interface TransferParticipantsProps {
transfer: Transfer; transfer: Transfer;
@@ -52,22 +30,22 @@ export function TransferParticipants({
}: TransferParticipantsProps) { }: TransferParticipantsProps) {
// This mapping is required as the global account types require a type to be set, while // This mapping is required as the global account types require a type to be set, while
// the underlying protobufs allow for every field to be undefined. // the underlying protobufs allow for every field to be undefined.
const fromAcct: AccountTypes = const fromAcct =
transfer.fromAccountType && transfer.fromAccountType &&
transfer.fromAccountType !== 'ACCOUNT_TYPE_UNSPECIFIED' transfer.fromAccountType !== 'ACCOUNT_TYPE_UNSPECIFIED'
? transfer.fromAccountType ? AccountType[transfer.fromAccountType]
: 'ACCOUNT_TYPE_GENERAL'; : AccountType.ACCOUNT_TYPE_GENERAL;
const fromAccountTypeLabel: string = transfer.fromAccountType const fromAccountTypeLabel = transfer.fromAccountType
? AccountType[fromAcct] ? AccountTypeMapping[fromAcct]
: 'Unknown'; : 'Unknown';
const toAcct: AccountTypes = const toAcct =
transfer.toAccountType && transfer.toAccountType &&
transfer.toAccountType !== 'ACCOUNT_TYPE_UNSPECIFIED' transfer.toAccountType !== 'ACCOUNT_TYPE_UNSPECIFIED'
? transfer.toAccountType ? AccountType[transfer.toAccountType]
: 'ACCOUNT_TYPE_GENERAL'; : AccountType.ACCOUNT_TYPE_GENERAL;
const toAccountTypeLabel = transfer.fromAccountType const toAccountTypeLabel = transfer.fromAccountType
? AccountType[toAcct] ? AccountTypeMapping[toAcct]
: 'Unknown'; : 'Unknown';
return ( return (
@@ -27,9 +27,9 @@ export function TransferRepeat({ recurring }: TransferRepeatProps) {
<div className={wrapperClasses}> <div className={wrapperClasses}>
<h2 className={headerClasses}>{t('Active epochs')}</h2> <h2 className={headerClasses}>{t('Active epochs')}</h2>
<div className="relative block rounded-lg py-6 text-center p-6"> <div className="relative block rounded-lg py-6 text-center p-6">
<div> <p>
<EpochOverview id={recurring.startEpoch} /> <EpochOverview id={recurring.startEpoch} />
</div> </p>
<p className="leading-10 my-2"> <p className="leading-10 my-2">
<IconForEpoch <IconForEpoch
start={recurring.startEpoch} start={recurring.startEpoch}
@@ -37,13 +37,13 @@ export function TransferRepeat({ recurring }: TransferRepeatProps) {
current={data?.epoch.id} current={data?.epoch.id}
/> />
</p> </p>
<div> <p>
{recurring.endEpoch ? ( {recurring.endEpoch ? (
<EpochOverview id={recurring.endEpoch} /> <EpochOverview id={recurring.endEpoch} />
) : ( ) : (
<span>{t('Forever')}</span> <span>{t('Forever')}</span>
)} )}
</div> </p>
</div> </div>
</div> </div>
); );
@@ -8,7 +8,7 @@ import { DispatchMetricLabels } from '@vegaprotocol/types';
export type Metric = components['schemas']['vegaDispatchMetric']; export type Metric = components['schemas']['vegaDispatchMetric'];
export type Strategy = components['schemas']['vegaDispatchStrategy']; export type Strategy = components['schemas']['vegaDispatchStrategy'];
const metricLabels: Record<Metric, string> = { const metricLabels = {
DISPATCH_METRIC_UNSPECIFIED: 'Unknown metric', DISPATCH_METRIC_UNSPECIFIED: 'Unknown metric',
...DispatchMetricLabels, ...DispatchMetricLabels,
}; };
@@ -3,7 +3,7 @@ import { TransferRepeat } from './blocks/transfer-repeat';
import { TransferRewards } from './blocks/transfer-rewards'; import { TransferRewards } from './blocks/transfer-rewards';
import { TransferParticipants } from './blocks/transfer-participants'; import { TransferParticipants } from './blocks/transfer-participants';
export type Recurring = components['schemas']['commandsv1RecurringTransfer']; export type Recurring = components['schemas']['v1RecurringTransfer'];
export type Metric = components['schemas']['vegaDispatchMetric']; export type Metric = components['schemas']['vegaDispatchMetric'];
export const wrapperClasses = export const wrapperClasses =
@@ -1,86 +0,0 @@
import { render } from '@testing-library/react';
import { TxDetailsLiquidityAmendment } from './tx-liquidity-amend';
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
import { MockedProvider } from '@apollo/client/testing';
import { MemoryRouter } from 'react-router-dom';
describe('TxDetailsLiquidityAmendment', () => {
const mockTxData = {
hash: 'test',
command: {
liquidityProvisionAmendment: {
marketId: 'BTC-USD',
commitmentAmount: 100,
fee: '0.01',
},
},
};
const mockPubKey = '123';
const mockBlockData = {
result: {
block: {
header: {
height: '123',
},
},
},
};
it('should render the component with correct data', () => {
const { getByText } = render(
<MockedProvider>
<MemoryRouter>
<TxDetailsLiquidityAmendment
txData={mockTxData as BlockExplorerTransactionResult}
pubKey={mockPubKey}
blockData={mockBlockData as TendermintBlocksResponse}
/>
</MemoryRouter>
</MockedProvider>
);
expect(getByText('Market')).toBeInTheDocument();
expect(getByText('BTC-USD')).toBeInTheDocument();
expect(getByText('Commitment amount')).toBeInTheDocument();
expect(getByText('100')).toBeInTheDocument();
expect(getByText('Fee')).toBeInTheDocument();
expect(getByText('1%')).toBeInTheDocument();
});
it('should display awaiting message when tx data is undefined', () => {
const { getByText } = render(
<MockedProvider>
<MemoryRouter>
<TxDetailsLiquidityAmendment
txData={undefined}
pubKey={mockPubKey}
blockData={mockBlockData as TendermintBlocksResponse}
/>
</MemoryRouter>
</MockedProvider>
);
expect(
getByText('Awaiting Block Explorer transaction details')
).toBeInTheDocument();
});
it('should display awaiting message when liquidityProvisionAmendment is undefined', () => {
const { getByText } = render(
<MockedProvider>
<MemoryRouter>
<TxDetailsLiquidityAmendment
txData={{ command: {} } as BlockExplorerTransactionResult}
pubKey={mockPubKey}
blockData={mockBlockData as TendermintBlocksResponse}
/>
</MemoryRouter>
</MockedProvider>
);
expect(
getByText('Awaiting Block Explorer transaction details')
).toBeInTheDocument();
});
});
@@ -7,7 +7,6 @@ import { TableCell, TableRow, TableWithTbody } from '../../table';
import type { components } from '../../../../types/explorer'; import 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'];
@@ -35,10 +34,6 @@ 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}>
@@ -68,7 +63,7 @@ export const TxDetailsLiquidityAmendment = ({
{amendment.fee ? ( {amendment.fee ? (
<TableRow modifier="bordered"> <TableRow modifier="bordered">
<TableCell>{t('Fee')}</TableCell> <TableCell>{t('Fee')}</TableCell>
<TableCell>{fee}%</TableCell> <TableCell>{amendment.fee}%</TableCell>
</TableRow> </TableRow>
) : null} ) : null}
</TableWithTbody> </TableWithTbody>
@@ -1,86 +0,0 @@
import { render } from '@testing-library/react';
import { TxDetailsLiquiditySubmission } from './tx-liquidity-submission';
import type { TendermintBlocksResponse } from '../../../routes/blocks/tendermint-blocks-response';
import type { BlockExplorerTransactionResult } from '../../../routes/types/block-explorer-response';
import { MockedProvider } from '@apollo/client/testing';
import { MemoryRouter } from 'react-router-dom';
describe('TxDetailsLiquiditySubmission', () => {
const mockTxData = {
hash: 'test',
command: {
liquidityProvisionSubmission: {
marketId: 'BTC-USD',
commitmentAmount: 100,
fee: '0.01',
},
},
};
const mockPubKey = '123';
const mockBlockData = {
result: {
block: {
header: {
height: '123',
},
},
},
};
it('should render the component with correct data', () => {
const { getByText } = render(
<MockedProvider>
<MemoryRouter>
<TxDetailsLiquiditySubmission
txData={mockTxData as BlockExplorerTransactionResult}
pubKey={mockPubKey}
blockData={mockBlockData as TendermintBlocksResponse}
/>
</MemoryRouter>
</MockedProvider>
);
expect(getByText('Market')).toBeInTheDocument();
expect(getByText('BTC-USD')).toBeInTheDocument();
expect(getByText('Commitment amount')).toBeInTheDocument();
expect(getByText('100')).toBeInTheDocument();
expect(getByText('Fee')).toBeInTheDocument();
expect(getByText('1%')).toBeInTheDocument();
});
it('should display awaiting message when tx data is undefined', () => {
const { getByText } = render(
<MockedProvider>
<MemoryRouter>
<TxDetailsLiquiditySubmission
txData={undefined}
pubKey={mockPubKey}
blockData={mockBlockData as TendermintBlocksResponse}
/>
</MemoryRouter>
</MockedProvider>
);
expect(
getByText('Awaiting Block Explorer transaction details')
).toBeInTheDocument();
});
it('should display awaiting message when liquidityProvisionSubmission is undefined', () => {
const { getByText } = render(
<MockedProvider>
<MemoryRouter>
<TxDetailsLiquiditySubmission
txData={{ command: {} } as BlockExplorerTransactionResult}
pubKey={mockPubKey}
blockData={mockBlockData as TendermintBlocksResponse}
/>
</MemoryRouter>
</MockedProvider>
);
expect(
getByText('Awaiting Block Explorer transaction details')
).toBeInTheDocument();
});
});
@@ -7,7 +7,6 @@ import { TableCell, TableRow, TableWithTbody } from '../../table';
import type { components } from '../../../../types/explorer'; import 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'];
@@ -34,10 +33,6 @@ 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}>
@@ -67,7 +62,7 @@ export const TxDetailsLiquiditySubmission = ({
{submission.fee ? ( {submission.fee ? (
<TableRow modifier="bordered"> <TableRow modifier="bordered">
<TableCell>{t('Fee')}</TableCell> <TableCell>{t('Fee')}</TableCell>
<TableCell>{fee}%</TableCell> <TableCell>{submission.fee}%</TableCell>
</TableRow> </TableRow>
) : null} ) : null}
</TableWithTbody> </TableWithTbody>
@@ -7,7 +7,6 @@ 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;
@@ -30,8 +29,6 @@ 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 = '';
@@ -66,14 +63,6 @@ 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 ? (
@@ -1,28 +0,0 @@
import { t } from '@vegaprotocol/i18n';
export interface FilterLabelProps {
filters: Set<string>;
}
/**
* Renders the list (currently limited to 1) of filters set by the
* Transaction Filter
*/
export function FilterLabel({ filters }: FilterLabelProps) {
if (!filters || filters.size !== 1) {
return (
<span data-testid="filter-empty" className="uppercase">
{t('Filter')}
</span>
);
}
return (
<div data-testid="filter-selected">
<span className="uppercase">{t('Filters')}:</span>&nbsp;
<code className="bg-vega-light-150 dark:bg-vega-light-300 px-2 rounded-md capitalize dark:text-black">
{Array.from(filters)[0]}
</code>
</div>
);
}
@@ -1,21 +0,0 @@
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();
});
});
@@ -1,164 +0,0 @@
import { t } from '@vegaprotocol/i18n';
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuItemIndicator,
DropdownMenuSeparator,
DropdownMenuSub,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
DropdownMenuSubContent,
Icon,
Button,
} from '@vegaprotocol/ui-toolkit';
import type { Dispatch, SetStateAction } from 'react';
import { FilterLabel } from './tx-filter-label';
// All possible transaction types. Should be generated.
export type FilterOption =
| 'Amend LiquidityProvision Order'
| 'Amend Order'
| 'Batch Market Instructions'
| 'Cancel LiquidityProvision Order'
| 'Cancel Order'
| 'Cancel Transfer Funds'
| 'Chain Event'
| 'Delegate'
| 'Ethereum Key Rotate Submission'
| 'Issue Signatures'
| 'Key Rotate Submission'
| 'Liquidity Provision Order'
| 'Node Signature'
| 'Node Vote'
| 'Proposal'
| 'Protocol Upgrade'
| 'Register new Node'
| 'State Variable Proposal'
| 'Submit Oracle Data'
| 'Submit Order'
| 'Transfer Funds'
| 'Undelegate'
| 'Validator Heartbeat'
| 'Vote on Proposal'
| 'Withdraw';
// Alphabetised list of transaction types to appear at the top level
export const PrimaryFilterOptions: FilterOption[] = [
'Amend LiquidityProvision Order',
'Amend Order',
'Batch Market Instructions',
'Cancel LiquidityProvision Order',
'Cancel Order',
'Cancel Transfer Funds',
'Delegate',
'Liquidity Provision Order',
'Proposal',
'Submit Oracle Data',
'Submit Order',
'Transfer Funds',
'Undelegate',
'Vote on Proposal',
'Withdraw',
];
// Alphabetised list of transaction types to nest under a 'More...' submenu
export const SecondaryFilterOptions: FilterOption[] = [
'Chain Event',
'Ethereum Key Rotate Submission',
'Issue Signatures',
'Key Rotate Submission',
'Node Signature',
'Node Vote',
'Protocol Upgrade',
'Register new Node',
'State Variable Proposal',
'Validator Heartbeat',
];
export const AllFilterOptions: FilterOption[] = [
...PrimaryFilterOptions,
...SecondaryFilterOptions,
];
export interface TxFilterProps {
filters: Set<FilterOption>;
setFilters: Dispatch<SetStateAction<Set<FilterOption>>>;
}
/**
* Renders a structured dropdown menu of all of the available transaction
* 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
* one or all.
* @param filters null or Set of transaction types
* @param setFilters A function to update the filters prop
* @returns
*/
export const TxsFilter = ({ filters, setFilters }: TxFilterProps) => {
return (
<DropdownMenu
modal={false}
trigger={
<DropdownMenuTrigger className="ml-0">
<Button size="xs" data-testid="filter-trigger">
<FilterLabel filters={filters} />
</Button>
</DropdownMenuTrigger>
}
>
<DropdownMenuContent>
{filters.size > 0 ? null : (
<>
<DropdownMenuCheckboxItem
onCheckedChange={() => setFilters(new Set(AllFilterOptions))}
>
{t('Clear filters')} <Icon name="cross" />
</DropdownMenuCheckboxItem>
<DropdownMenuSeparator />
</>
)}
{PrimaryFilterOptions.map((f) => (
<DropdownMenuCheckboxItem
key={f}
checked={filters.has(f)}
onCheckedChange={() => {
// NOTE: These act like radio buttons until the API supports multiple filters
setFilters(new Set([f]));
}}
id={`radio-${f}`}
>
{f}
<DropdownMenuItemIndicator>
<Icon name="tick-circle" />
</DropdownMenuItemIndicator>
</DropdownMenuCheckboxItem>
))}
<DropdownMenuSub>
<DropdownMenuSubTrigger>
{t('More Types')}
<Icon name="chevron-right" />
</DropdownMenuSubTrigger>
<DropdownMenuSubContent>
{SecondaryFilterOptions.map((f) => (
<DropdownMenuCheckboxItem
key={f}
checked={filters.has(f)}
onCheckedChange={(checked) => {
// NOTE: These act like radio buttons until the API supports multiple filters
setFilters(new Set([f]));
}}
id={`radio-${f}`}
>
{f}
<DropdownMenuItemIndicator>
<Icon name="tick-circle" className="inline" />
</DropdownMenuItemIndicator>
</DropdownMenuCheckboxItem>
))}
</DropdownMenuSubContent>
</DropdownMenuSub>
</DropdownMenuContent>
</DropdownMenu>
);
};
@@ -1,113 +0,0 @@
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();
});
});
@@ -1,63 +0,0 @@
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>
</>
);
};
@@ -16,7 +16,7 @@ interface StringMap {
const displayString: StringMap = { const displayString: StringMap = {
OrderSubmission: 'Order Submission', OrderSubmission: 'Order Submission',
'Submit Order': 'Order', 'Submit Order': 'Order',
OrderCancellation: 'Cancel order', OrderCancellation: 'Order Cancellation',
OrderAmendment: 'Order Amendment', OrderAmendment: 'Order Amendment',
VoteSubmission: 'Vote Submission', VoteSubmission: 'Vote Submission',
WithdrawSubmission: 'Withdraw Submission', WithdrawSubmission: 'Withdraw Submission',
@@ -24,7 +24,6 @@ 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',
@@ -37,34 +36,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',
'Stop Orders Submission': 'Stop',
StopOrdersSubmission: 'Stop',
StopOrdersCancellation: 'Cancel stop',
'Stop Orders Cancellation': 'Cancel stop',
}; };
export function getLabelForOrderType(
orderType: string,
command: components['schemas']['v1InputData']
): string {
if (command.orderSubmission) {
if (command.orderSubmission.peggedOrder) {
return 'Peg';
}
if (command.orderSubmission.icebergOpts) {
return 'Iceberg';
}
}
return 'Order';
}
/** /**
* Given a proposal, will return a specific label * Given a proposal, will return a specific label
* @param chainEvent * @param chainEvent
@@ -136,8 +113,6 @@ export function getLabelForChainEvent(
return t('Signer threshold'); return t('Signer threshold');
} }
return t('Multisig update'); return t('Multisig update');
} else if (chainEvent.contractCall) {
return t('Contract call');
} }
return t('Chain Event'); return t('Chain Event');
} }
@@ -197,7 +172,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-tight px-2 inline-block whitespace-nowrap ${colours}`} className={`text-sm rounded-md leading-none px-2 py-2 inline-block ${colours}`}
> >
{type} {type}
</div> </div>
@@ -1,4 +1,3 @@
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';
@@ -84,22 +83,21 @@ describe('Txs infinite list item', () => {
it('renders data correctly', () => { it('renders data correctly', () => {
render( render(
<MockedProvider> <MemoryRouter>
<MemoryRouter> <TxsInfiniteListItem
<TxsInfiniteListItem type="testType"
type="testType" submitter="testPubKey"
submitter="testPubKey" hash="testTxHash"
hash="testTxHash" block="1"
block="1" code={0}
code={0} command={{}}
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,3 +1,4 @@
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';
@@ -5,25 +6,8 @@ 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 DEFAULT_TRUNCATE_LENGTH = 7; const TRUNCATE_LENGTH = 10;
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,
@@ -33,12 +17,6 @@ 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 ||
@@ -51,40 +29,68 @@ export const TxsInfiniteListItem = ({
} }
return ( return (
<tr <div
data-testid="transaction-row" data-testid="transaction-row"
className="transaction-row text-left items-center h-full border-t border-neutral-600 dark:border-neutral-800 txs-infinite-list-item py-[2px]" className="flex items-center h-full border-t border-neutral-600 dark:border-neutral-800 txs-infinite-list-item grid grid-cols-10"
> >
<td <div
className="text-sm leading-none whitespace-nowrap font-mono" className="text-sm col-span-10 md:col-span-3 leading-none"
data-testid="tx-hash" data-testid="tx-hash"
> >
{isNumber(code) ? ( <span className="md:hidden uppercase text-vega-dark-300">
<ChainResponseCode code={code} hideLabel={true} hideIfOk={true} /> ID:&nbsp;
) : ( </span>
code
)}
<TruncatedLink <TruncatedLink
to={`/${Routes.TX}/${toHex(hash)}`} to={`/${Routes.TX}/${toHex(hash)}`}
text={hash} text={hash}
startChars={idTruncateLength} startChars={TRUNCATE_LENGTH}
endChars={0} endChars={TRUNCATE_LENGTH}
/> />
</td> </div>
<td className="text-sm leading-none"> <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} /> <TxOrderType orderType={type} command={command} />
</td> </div>
<td className="text-sm leading-none" data-testid="pub-key"> <div
<PartyLink truncate={shouldTruncateParty(screenSize)} id={submitter} /> className="text-sm col-span-3 md:col-span-1 leading-none flex items-center"
</td> data-testid="tx-block"
<td className="text-sm items-center font-mono" data-testid="tx-block"> >
<span className="md:hidden uppercase text-vega-dark-300">
Block:&nbsp;
</span>
<TruncatedLink <TruncatedLink
to={`/${Routes.BLOCKS}/${block}`} to={`/${Routes.BLOCKS}/${block}`}
text={block} text={block}
startChars={5} startChars={TRUNCATE_LENGTH}
endChars={5} endChars={TRUNCATE_LENGTH}
/> />
</td> </div>
</tr> <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) ? (
<ChainResponseCode code={code} hideLabel={true} />
) : (
code
)}
</div>
</div>
); );
}; };
@@ -1,9 +1,8 @@
import { TxsInfiniteList } from './txs-infinite-list'; import { TxsInfiniteList } from './txs-infinite-list';
import { render, screen } from '@testing-library/react'; import { render, screen, fireEvent, act } 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((_) => ({
@@ -41,7 +40,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 as unknown as BlockExplorerTransactionResult[]} txs={undefined}
areTxsLoading={false} areTxsLoading={false}
hasMoreTxs={false} hasMoreTxs={false}
loadMoreTxs={() => null} loadMoreTxs={() => null}
@@ -49,7 +48,23 @@ describe('Txs infinite list', () => {
/> />
); );
expect(screen.getByTestId('emptylist')).toBeInTheDocument(); expect(screen.getByTestId('emptylist')).toBeInTheDocument();
expect(screen.getByText('No transactions found')).toBeInTheDocument(); expect(
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', () => {
@@ -58,22 +73,85 @@ describe('Txs infinite list', () => {
const txs = generateTxs(7); const txs = generateTxs(7);
render( render(
<MemoryRouter> <MemoryRouter>
<MockedProvider> <TxsInfiniteList
<TxsInfiniteList txs={txs}
txs={txs} areTxsLoading={false}
areTxsLoading={false} hasMoreTxs={false}
hasMoreTxs={false} loadMoreTxs={() => null}
loadMoreTxs={() => null} error={undefined}
error={undefined} />
/>
</MockedProvider>
</MemoryRouter> </MemoryRouter>
); );
expect( expect(
screen screen
.getByTestId('transactions-list') .getByTestId('infinite-scroll-wrapper')
.querySelectorAll('.transaction-row') .querySelectorAll('.txs-infinite-list-item')
).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,4 +1,8 @@
import React 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';
@@ -7,46 +11,71 @@ import { Loader } from '@vegaprotocol/ui-toolkit';
interface TxsInfiniteListProps { interface TxsInfiniteListProps {
hasMoreTxs: boolean; hasMoreTxs: boolean;
areTxsLoading: boolean | undefined; areTxsLoading: boolean | undefined;
txs: BlockExplorerTransactionResult[]; txs: BlockExplorerTransactionResult[] | undefined;
loadMoreTxs: () => void; loadMoreTxs: () => void;
error: Error | undefined; error: Error | undefined;
className?: string; className?: string;
hasFilters?: boolean;
} }
interface ItemProps { interface ItemProps {
tx: BlockExplorerTransactionResult; index: BlockExplorerTransactionResult;
style: React.CSSProperties;
isLoading: boolean;
error: Error | undefined;
} }
const Item = ({ tx }: ItemProps) => { // eslint-disable-next-line @typescript-eslint/no-empty-function
const { hash, submitter, type, command, block, code, index: blockIndex } = tx; const NOOP = () => {};
return (
<TxsInfiniteListItem const Item = ({ index, style, isLoading, error }: ItemProps) => {
type={type} let content;
code={code} if (error) {
command={command} content = t(`Cannot fetch transaction`);
submitter={submitter} } else if (isLoading) {
hash={hash} content = <Loader />;
block={block} } else {
index={blockIndex} const {
/> 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) => {
if (!txs || txs.length === 0) { const { screenSize } = useScreenDimensions();
const isStacked = ['xs', 'sm'].includes(screenSize);
if (!txs) {
if (!areTxsLoading) { if (!areTxsLoading) {
return ( return (
<EmptyList <EmptyList
heading={t('No transactions found')} heading={t('This chain has 0 transactions')}
label={ label={t('Check back soon')}
hasFilters ? t('Try a different filter') : t('Check back soon')
}
/> />
); );
} else { } else {
@@ -54,26 +83,56 @@ 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="overflow-scroll"> <div className={className} data-testid="transactions-list">
<table className={className} data-testid="transactions-list"> <div className="lg:grid grid-cols-10 w-full mb-3 hidden text-vega-dark-300 uppercase">
<thead> <div className="col-span-3">
<tr className="w-full mb-3 text-vega-dark-300 uppercase text-left"> <span className="hidden xl:inline">{t('Transaction')} &nbsp;</span>
<th> <span>ID</span>
<span className="hidden xl:inline">{t('Txn')} &nbsp;</span> </div>
<span>ID</span> <div className="col-span-3">{t('Submitted By')}</div>
</th> <div className="col-span-2">{t('Type')}</div>
<th>{t('Type')}</th> <div className="col-span-1">{t('Block')}</div>
<th className="text-left">{t('From')}</th> <div className="col-span-1">{t('Success')}</div>
<th>{t('Block')}</th> </div>
</tr> <div data-testid="infinite-scroll-wrapper">
</thead> <InfiniteLoader
<tbody> isItemLoaded={isItemLoaded}
{txs.map((t) => ( itemCount={itemCount}
<Item key={t.hash} tx={t} /> loadMoreItems={loadMoreItems}
))} >
</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,17 +1,23 @@
import { Table, TableRow } from '../table'; import { Routes } from '../../routes/route-names';
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 });
@@ -27,23 +33,53 @@ 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('Type')}</td>
<td>{t('From')}</td> <td>{t('From')}</td>
<td>{t('Block')}</td> <td>{t('Type')}</td>
<td>{t('Status')}</td>
</TableRow> </TableRow>
</thead> </thead>
<tbody> <tbody>
{data.transactions.map( {data.transactions.map(
({ hash, submitter, type, command, code, block }) => { ({ hash, submitter, type, command, code }) => {
return ( return (
<TxsInfiniteListItem <TableRow
block={block} modifier="bordered"
hash={hash} key={hash}
submitter={submitter} data-testid="transaction-row"
type={type} >
command={command} <TableCell
code={code} modifier="bordered"
/> 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,14 +56,10 @@ export function VoteIcon({
return ( return (
<div <div
className={`voteicon inline-block py-0 px-2 py rounded-md text-white whitespace-nowrap leading-tight sm align-top ${bg}`} className={`voteicon inline-block my-1 py-1 px-2 py rounded-md text-white leading-one sm align-top ${bg}`}
> >
<Icon <Icon name={icon} size={3} className={`mr-2 p-0 fill-${fill}`} />
name={icon} <span className={`text-base text-${text}`} data-testid="label">
size={3}
className={`mr-2 p-0 mb-[-1px] fill-${fill}`}
/>
<span className={`text-${text}`} data-testid="label">
{label} {label}
</span> </span>
</div> </div>
+33 -69
View File
@@ -10,18 +10,16 @@ import isNumber from 'lodash/isNumber';
export interface TxsStateProps { export interface TxsStateProps {
txsData: BlockExplorerTransactionResult[]; txsData: BlockExplorerTransactionResult[];
hasMoreTxs: boolean; hasMoreTxs: boolean;
cursor: string; lastCursor: 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;
} }
@@ -35,96 +33,62 @@ export const getTxsDataUrl = ({ limit, filters }: IGetTxsDataUrl) => {
// Hacky fix for param as array // Hacky fix for param as array
let urlAsString = url.toString(); let urlAsString = url.toString();
if (filters) { if (filters) {
urlAsString += '&' + filters.replace(' ', '%20'); urlAsString += '&' + filters;
} }
return urlAsString; return urlAsString;
}; };
export const useTxsData = ({ limit, filters }: IUseTxsData) => { export const useTxsData = ({ limit, filters }: IUseTxsData) => {
const [ const [{ txsData, hasMoreTxs, lastCursor }, setTxsState] =
{ txsData, hasMoreTxs, cursor, previousCursors, hasPreviousPage }, useState<TxsStateProps>({
setTxsState, txsData: [],
] = useState<TxsStateProps>({ hasMoreTxs: true,
txsData: [], lastCursor: '',
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, {}, true); } = useFetch<BlockExplorerTransactions>(url, {}, false);
useEffect(() => { useEffect(() => {
if (!loading && data && isNumber(data.transactions.length)) { if (data && isNumber(data?.transactions?.length)) {
setTxsState((prev) => { setTxsState((prev) => ({
return { txsData: [...prev.txsData, ...data.transactions],
...prev, hasMoreTxs: data.transactions.length > 0,
txsData: data.transactions, lastCursor:
hasMoreTxs: data.transactions.length >= limit, data.transactions[data.transactions.length - 1]?.cursor || '',
cursor: data?.transactions.at(-1)?.cursor || '', }));
};
});
} }
}, [loading, setTxsState, data, limit]); }, [setTxsState, data]);
const nextPage = useCallback(() => {
const c = data?.transactions.at(0)?.cursor;
const newPreviousCursors = c ? [...previousCursors, c] : previousCursors;
setTxsState((prev) => ({
...prev,
hasPreviousPage: true,
previousCursors: newPreviousCursors,
}));
const loadTxs = useCallback(() => {
return refetch({ return refetch({
limit, limit: limit,
before: cursor, before: lastCursor,
}); });
}, [data, previousCursors, cursor, limit, refetch]); }, [lastCursor, 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 () => { const refreshTxs = useCallback(async () => {
setTxsState(() => ({ setTxsState((prev) => ({
...prev,
lastCursor: '',
hasMoreTxs: true,
txsData: [], txsData: [],
cursor: '',
previousCursors: [],
hasMoreTxs: false,
hasPreviousPage: false,
})); }));
}, [setTxsState]);
refetch({ limit });
}, [setTxsState, limit, refetch, filters]); // eslint-disable-line react-hooks/exhaustive-deps
return { return {
txsData, data,
loading, loading,
error, error,
txsData,
hasMoreTxs, hasMoreTxs,
hasPreviousPage, lastCursor,
previousCursors,
cursor,
refreshTxs, refreshTxs,
nextPage, loadTxs,
previousPage,
}; };
}; };
+1 -3
View File
@@ -21,7 +21,6 @@ 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();
@@ -40,7 +39,6 @@ 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 (
<> <>
@@ -51,7 +49,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',
'relative' 'overflow-hidden relative'
)} )}
> >
<div> <div>
@@ -2,15 +2,12 @@ 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 (
<MockedProvider> <MemoryRouter>
<MemoryRouter> <OracleSigners sourceType={sourceType} />
<OracleSigners sourceType={sourceType} /> </MemoryRouter>
</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, useState } from 'react'; import { useMemo } 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,11 +14,8 @@ 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 || '-']);
@@ -27,24 +24,10 @@ 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 baseFilters = `filters[tx.submitter]=${partyId}`; const filters = `filters[tx.submitter]=${partyId}`;
const f = const { hasMoreTxs, loadTxs, error, txsData, loading } = useTxsData({
filters && filters.size === 1 limit: 10,
? `${baseFilters}&filters[cmd.type]=${Array.from(filters)[0]}` filters,
: 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]);
@@ -98,24 +81,14 @@ 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={nextPage} loadMoreTxs={loadTxs}
error={error} error={error}
className="mb-28 w-full" className="mb-28"
/> />
) : ( ) : (
<Splash> <Splash>
@@ -1,74 +1,29 @@
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'; const BE_TXS_PER_REQUEST = 20;
import { AllFilterOptions, TxsFilter } from '../../../components/txs/tx-filter';
import { TxsListNavigation } from '../../../components/txs/tx-list-navigation';
const BE_TXS_PER_REQUEST = 25;
export const TxsList = () => { export const TxsList = () => {
useDocumentTitle(['Transactions']); useDocumentTitle(['Transactions']);
const { hasMoreTxs, loadTxs, error, txsData, refreshTxs, loading } =
useTxsData({ limit: BE_TXS_PER_REQUEST });
return ( return (
<section className="md:p-2 lg:p-4 xl:p-6 relative"> <section className="md:p-2 lg:p-4 xl:p-6">
<RouteTitle>{t('Transactions')}</RouteTitle> <RouteTitle>{t('Transactions')}</RouteTitle>
<TxsListFiltered /> <BlocksRefetch refetch={refreshTxs} />
</section>
);
};
/**
* Displays a list of transactions with filters and controls to navigate through the list.
*
* @returns {JSX.Element} Transaction List and controls
*/
export const TxsListFiltered = () => {
const [filters, setFilters] = useState(new Set(AllFilterOptions));
const f =
filters && filters.size === 1
? `filters[cmd.type]=${Array.from(filters)[0]}`
: '';
const {
hasMoreTxs,
nextPage,
previousPage,
error,
refreshTxs,
loading,
txsData,
hasPreviousPage,
} = useTxsData({
limit: BE_TXS_PER_REQUEST,
filters: f,
});
return (
<>
<TxsListNavigation
refreshTxs={refreshTxs}
nextPage={nextPage}
previousPage={previousPage}
hasPreviousPage={hasPreviousPage}
loading={loading}
hasMoreTxs={hasMoreTxs}
>
<TxsFilter filters={filters} setFilters={setFilters} />
</TxsListNavigation>
<TxsInfiniteList <TxsInfiniteList
hasFilters={filters.size > 0}
hasMoreTxs={hasMoreTxs} hasMoreTxs={hasMoreTxs}
areTxsLoading={loading} areTxsLoading={loading}
txs={txsData} txs={txsData}
loadMoreTxs={nextPage} loadMoreTxs={loadTxs}
error={error} error={error}
className="mb-28 w-full min-w-[400px]" className="mb-28"
/> />
</> </section>
); );
}; };
@@ -1,11 +1,10 @@
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 =
@@ -28,11 +27,9 @@ const txData: BlockExplorerTransactionResult = {
}; };
const renderComponent = (txData: BlockExplorerTransactionResult) => ( const renderComponent = (txData: BlockExplorerTransactionResult) => (
<MemoryRouter> <Router>
<MockedProvider> <TxDetails txData={txData} pubKey={pubKey} />
<TxDetails txData={txData} pubKey={pubKey} /> </Router>
</MockedProvider>
</MemoryRouter>
); );
describe('Transaction details', () => { describe('Transaction details', () => {
@@ -1,13 +0,0 @@
query ExplorerNodeNames {
nodesConnection {
edges {
node {
id
name
pubkey
tmPubkey
ethereumAddress
}
}
}
}
@@ -1,53 +0,0 @@
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>;
+3 -2
View File
@@ -1,5 +1,6 @@
@import 'ag-grid-community/styles/ag-grid.css'; @import 'ag-grid-community/dist/styles/ag-grid.css';
@import 'ag-grid-community/styles/ag-theme-balham.css'; @import 'ag-grid-community/dist/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;
+466 -799
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,5 +1,5 @@
const { join } = require('path'); const { join } = require('path');
const { createGlobPatternsForDependencies } = require('@nx/react/tailwind'); const { createGlobPatternsForDependencies } = require('@nrwl/next/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/@nx/react/typings/cssmodule.d.ts", "../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
"../../node_modules/@nx/react/typings/image.d.ts" "../../node_modules/@nrwl/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/@nx/react/typings/cssmodule.d.ts", "../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
"../../node_modules/@nx/react/typings/image.d.ts" "../../node_modules/@nrwl/react/typings/image.d.ts"
] ]
} }
+2 -5
View File
@@ -1,8 +1,6 @@
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 = composePlugins(withNx(), withReact(), (config) => { module.exports = (config, context) => {
const additionalPlugins = process.env.SENTRY_AUTH_TOKEN const additionalPlugins = process.env.SENTRY_AUTH_TOKEN
? [ ? [
new SentryPlugin({ new SentryPlugin({
@@ -15,6 +13,5 @@ module.exports = composePlugins(withNx(), withReact(), (config) => {
return { return {
...config, ...config,
plugins: [...additionalPlugins, ...config.plugins], plugins: [...additionalPlugins, ...config.plugins],
ignoreWarnings: [/Failed to parse source map/],
}; };
}); };
+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: 12000 }, epochTimeout: { timeout: 6000 },
blockConfirmations: 3, blockConfirmations: 3,
grepTags: '@regression @smoke @slow', grepTags: '@regression @smoke @slow',
grepFilterSpecs: true, grepFilterSpecs: true,
+3 -4
View File
@@ -1,11 +1,10 @@
{ {
"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": "@nx/cypress:cypress", "executor": "@nrwl/cypress:cypress",
"options": { "options": {
"cypressConfig": "apps/governance-e2e/cypress.config.js", "cypressConfig": "apps/governance-e2e/cypress.config.js",
"devServerTarget": "governance:serve" "devServerTarget": "governance:serve"
@@ -17,14 +16,14 @@
} }
}, },
"lint": { "lint": {
"executor": "@nx/linter:eslint", "executor": "@nrwl/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": "nx:run-commands", "executor": "@nrwl/workspace:run-commands",
"outputs": [], "outputs": [],
"options": { "options": {
"command": "yarn tsc --project ./apps/governance-e2e/" "command": "yarn tsc --project ./apps/governance-e2e/"
@@ -1,63 +0,0 @@
export const previousEpochData = {
epoch: {
id: '7611',
validatorsConnection: {
edges: [
{
node: {
id: 'cd96782bc0ad5679869cf69fe7838a92212da7f53b4a214bed68067117494122',
stakedTotal: '3154229668720612941799',
rewardScore: {
rawValidatorScore: '0.2',
performanceScore: '1',
multisigScore: '0',
validatorScore: '0.2',
normalisedScore: '0.2007216887087119',
validatorStatus: 'VALIDATOR_NODE_STATUS_TENDERMINT',
__typename: 'RewardScore',
},
rankingScore: {
status: 'VALIDATOR_NODE_STATUS_TENDERMINT',
previousStatus: 'VALIDATOR_NODE_STATUS_TENDERMINT',
rankingScore: '0.211713765544955625',
stakeScore: '0.2016321576618625',
performanceScore: '1',
votingPower: '2007',
__typename: 'RankingScore',
},
__typename: 'Node',
},
__typename: 'NodeEdge',
},
{
node: {
id: '887d936f797a47032eceb572a13b69b581d3b1fa595a7d021a3e3cf2a5d2acfd',
stakedTotal: '3151161904761904764551',
rewardScore: {
rawValidatorScore: '0.2',
performanceScore: '1',
multisigScore: '1',
validatorScore: '0.2',
normalisedScore: '0.2007216887087119',
validatorStatus: 'VALIDATOR_NODE_STATUS_TENDERMINT',
__typename: 'RewardScore',
},
rankingScore: {
status: 'VALIDATOR_NODE_STATUS_TENDERMINT',
previousStatus: 'VALIDATOR_NODE_STATUS_TENDERMINT',
rankingScore: '0.211507855409133255',
stakeScore: '0.2014360527706031',
performanceScore: '1',
votingPower: '2007',
__typename: 'RankingScore',
},
__typename: 'Node',
},
__typename: 'NodeEdge',
},
],
__typename: 'NodesConnection',
},
__typename: 'Epoch',
},
};
@@ -14,31 +14,25 @@ import {
submitUniqueRawProposal, submitUniqueRawProposal,
voteForProposal, voteForProposal,
} from '../../../../governance-e2e/src/support/governance.functions'; } from '../../../../governance-e2e/src/support/governance.functions';
import { import { ensureSpecifiedUnstakedTokensAreAssociated } from '../../../../governance-e2e/src/support/staking.functions';
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 { import { vegaWalletSetSpecifiedApprovalAmount } from '../../../../governance-e2e/src/support/wallet-teardown.functions';
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 =
'vote-progress-indicator-percentage-for'; '[data-testid="vote-progress-indicator-percentage-for"]';
const proposalVoteProgressAgainstPercentage = const proposalVoteProgressAgainstPercentage =
'vote-progress-indicator-percentage-against'; '[data-testid="vote-progress-indicator-percentage-against"]';
const proposalVoteProgressForTokens = 'vote-progress-indicator-tokens-for'; const proposalVoteProgressForTokens =
'[data-testid="vote-progress-indicator-tokens-for"]';
const proposalVoteProgressAgainstTokens = const proposalVoteProgressAgainstTokens =
'vote-progress-indicator-tokens-against'; '[data-testid="vote-progress-indicator-tokens-against"]';
const changeVoteButton = 'change-vote-button'; const changeVoteButton = '[data-testid="change-vote-button"]';
const proposalDetailsTitle = 'proposal-title'; const proposalDetailsTitle = '[data-testid="proposal-title"]';
const proposalDetailsDescription = 'proposal-description'; const proposalDetailsDescription = '[data-testid="proposal-description"]';
const openProposals = 'open-proposals'; const openProposals = '[data-testid="open-proposals"]';
const viewProposalButton = 'view-proposal-btn'; const viewProposalButton = '[data-testid="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';
@@ -50,7 +44,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 () {
@@ -71,18 +65,18 @@ describe(
createRawProposal(); createRawProposal();
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => { cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
cy.getByTestId(openProposals).within(() => { cy.get(openProposals).within(() => {
getProposalFromTitle(rawProposal.rationale.title).within(() => { getProposalFromTitle(rawProposal.rationale.title).within(() => {
cy.getByTestId(viewProposalButton).should('be.visible').click(); cy.get(viewProposalButton).should('be.visible').click();
}); });
}); });
cy.getByTestId(proposalDetailsTitle).should( cy.get(proposalDetailsTitle).should(
'contain.text', '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.getByTestId(proposalDetailsDescription) cy.get(proposalDetailsDescription)
.find('p') .find('p')
.should('have.text', proposalDescription); .should('have.text', proposalDescription);
}); });
@@ -116,7 +110,7 @@ describe(
}); });
navigateTo(navigation.proposals); navigateTo(navigation.proposals);
getProposalFromTitle(proposalTitle).within(() => getProposalFromTitle(proposalTitle).within(() =>
cy.getByTestId(viewProposalButton).click() cy.get(viewProposalButton).click()
); );
cy.wrap( cy.wrap(
formatDateWithLocalTimezone(new Date(proposalTimeStamp * 1000)) formatDateWithLocalTimezone(new Date(proposalTimeStamp * 1000))
@@ -138,7 +132,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.getByTestId(viewProposalButton).click() cy.get(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(
@@ -165,7 +159,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.getByTestId(viewProposalButton).click() cy.get(viewProposalButton).click()
); );
}); });
// 3001-VOTE-080 // 3001-VOTE-080
@@ -182,16 +176,14 @@ describe(
.contains(votedDate) .contains(votedDate)
.should('be.visible'); .should('be.visible');
}); });
cy.getByTestId(proposalVoteProgressForPercentage) // 3001-VOTE-072 cy.get(proposalVoteProgressForPercentage) // 3001-VOTE-072
.contains('100.00%') .contains('100.00%')
.and('be.visible'); .and('be.visible');
cy.getByTestId(proposalVoteProgressAgainstPercentage) cy.get(proposalVoteProgressAgainstPercentage)
.contains('0.00%') .contains('0.00%')
.and('be.visible'); .and('be.visible');
cy.getByTestId(proposalVoteProgressForTokens) cy.get(proposalVoteProgressForTokens).contains('1.00').and('be.visible');
.contains('1.00') cy.get(proposalVoteProgressAgainstTokens)
.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();
@@ -212,15 +204,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.getByTestId(changeVoteButton).should('be.visible').click(); cy.get(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.getByTestId(changeVoteButton).should('be.visible').click(); cy.get(changeVoteButton).should('be.visible').click();
voteForProposal('against'); voteForProposal('against');
cy.getByTestId(proposalVoteProgressAgainstPercentage) cy.get(proposalVoteProgressAgainstPercentage)
.contains('100.00%') .contains('100.00%')
.and('be.visible'); .and('be.visible');
getProposalInformationFromTable('Tokens against proposal') getProposalInformationFromTable('Tokens against proposal')
@@ -237,15 +229,13 @@ 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.getByTestId(viewProposalButton).click() cy.get(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.getByTestId(proposalVoteProgressForTokens) cy.get(proposalVoteProgressForTokens).contains('1').and('be.visible');
.contains('1')
.and('be.visible');
cy.getByTestId(voteBreakdownToggle).click(); cy.getByTestId(voteBreakdownToggle).click();
getProposalInformationFromTable('Total Supply') getProposalInformationFromTable('Total Supply')
.invoke('text') .invoke('text')
@@ -261,22 +251,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.getByTestId(viewProposalButton).click() cy.get(viewProposalButton).click()
); );
}); });
cy.getByTestId(proposalVoteProgressForPercentage) cy.get(proposalVoteProgressForPercentage)
.contains('100.00%') .contains('100.00%')
.and('be.visible'); .and('be.visible');
cy.getByTestId(proposalVoteProgressAgainstPercentage) cy.get(proposalVoteProgressAgainstPercentage)
.contains('0.00%') .contains('0.00%')
.and('be.visible'); .and('be.visible');
// 3001-VOTE-065 // 3001-VOTE-065
cy.getByTestId(changeVoteButton).should('be.visible').click(); cy.get(changeVoteButton).should('be.visible').click();
voteForProposal('for'); voteForProposal('for');
cy.getByTestId(proposalVoteProgressForTokens) cy.get(proposalVoteProgressForTokens)
.contains(tokensRequiredToAchieveResult) .contains(tokensRequiredToAchieveResult)
.and('be.visible'); .and('be.visible');
cy.getByTestId(proposalVoteProgressAgainstTokens) cy.get(proposalVoteProgressAgainstTokens)
.contains('0.00') .contains('0.00')
.and('be.visible'); .and('be.visible');
cy.getByTestId(voteBreakdownToggle).click(); cy.getByTestId(voteBreakdownToggle).click();
@@ -307,36 +297,5 @@ 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-functions'; import { vegaWalletSetSpecifiedApprovalAmount } from '../../support/wallet-teardown.functions';
const proposalListItem = '[data-testid="proposals-list-item"]'; const proposalListItem = '[data-testid="proposals-list-item"]';
const closedProposals = 'closed-proposals'; const closedProposals = '[data-testid="closed-proposals"]';
const proposalStatus = 'proposal-status'; const proposalStatus = '[data-testid="proposal-status"]';
const viewProposalButton = 'view-proposal-btn'; const viewProposalButton = '[data-testid="view-proposal-btn"]';
const votesTable = 'votes-table'; const votesTable = '[data-testid="votes-table"]';
const openProposals = 'open-proposals'; const openProposals = '[data-testid="open-proposals"]';
const proposalVoteProgressForPercentage = const proposalVoteProgressForPercentage =
'vote-progress-indicator-percentage-for'; '[data-testid="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.getByTestId(closedProposals).within(() => { cy.get(closedProposals).within(() => {
cy.contains(proposalTitle) cy.contains(proposalTitle)
.parentsUntil(proposalListItem) .parentsUntil(proposalListItem)
.last() .last()
.within(() => { .within(() => {
cy.getByTestId(proposalStatus).should('have.text', 'Enacted'); cy.get(proposalStatus).should('have.text', 'Enacted');
cy.getByTestId(viewProposalButton).click(); cy.get(viewProposalButton).click();
}); });
}); });
cy.getByTestId('proposal-type').should('have.text', 'New market'); cy.getByTestId('proposal-type').should('have.text', 'New market');
cy.getByTestId(proposalStatus).should('have.text', 'Enacted'); cy.get(proposalStatus).should('have.text', 'Enacted');
cy.getByTestId(votesTable).within(() => { cy.get(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,27 +81,21 @@ context(
navigateTo(navigation.proposals); navigateTo(navigation.proposals);
cy.reload(); cy.reload();
waitForSpinner(); waitForSpinner();
cy.getByTestId(openProposals).within(() => { cy.get(openProposals).within(() => {
cy.contains(proposalTitle) cy.contains(proposalTitle)
.parentsUntil(proposalListItem) .parentsUntil(proposalListItem)
.last() .last()
.within(() => cy.getByTestId(viewProposalButton).click()); .within(() => cy.get(viewProposalButton).click());
}); });
cy.getByTestId(proposalStatus).should('have.text', 'Open'); cy.get(proposalStatus).should('have.text', 'Open');
voteForProposal('for'); voteForProposal('for');
cy.getByTestId(proposalStatus, proposalTimeout) cy.get(proposalStatus, proposalTimeout).should('have.text', 'Passed');
.should('have.text', 'Passed') cy.get(proposalStatus, proposalTimeout).should('have.text', 'Enacted');
.then(() => { cy.get(votesTable).within(() => {
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.getByTestId(proposalVoteProgressForPercentage) cy.get(proposalVoteProgressForPercentage)
.contains('100.00%') .contains('100.00%')
.and('be.visible'); .and('be.visible');
}); });
@@ -115,18 +109,15 @@ context(
navigateTo(navigation.proposals); navigateTo(navigation.proposals);
cy.reload(); cy.reload();
waitForSpinner(); waitForSpinner();
cy.getByTestId(openProposals, { timeout: 6000 }).within(() => { cy.get(openProposals, { timeout: 6000 }).within(() => {
cy.contains(proposalTitle) cy.contains(proposalTitle)
.parentsUntil(proposalListItem) .parentsUntil(proposalListItem)
.last() .last()
.within(() => cy.getByTestId(viewProposalButton).click()); .within(() => cy.get(viewProposalButton).click());
}); });
cy.getByTestId(proposalStatus).should('have.text', 'Open'); cy.get(proposalStatus).should('have.text', 'Open');
voteForProposal('for'); voteForProposal('for');
cy.getByTestId(proposalStatus, proposalTimeout).should( cy.get(proposalStatus, proposalTimeout).should('have.text', 'Enacted');
'have.text',
'Enacted'
);
}); });
// 3001-VOTE-048 3001-VOTE-049 3001-VOTE-050 // 3001-VOTE-048 3001-VOTE-049 3001-VOTE-050
@@ -137,17 +128,14 @@ context(
navigateTo(navigation.proposals); navigateTo(navigation.proposals);
cy.reload(); cy.reload();
waitForSpinner(); waitForSpinner();
cy.getByTestId(openProposals).within(() => { cy.get(openProposals).within(() => {
cy.contains(proposalTitle) cy.contains(proposalTitle)
.parentsUntil(proposalListItem) .parentsUntil(proposalListItem)
.last() .last()
.within(() => cy.getByTestId(viewProposalButton).click()); .within(() => cy.get(viewProposalButton).click());
}); });
cy.getByTestId(proposalStatus).should('have.text', 'Open'); cy.get(proposalStatus).should('have.text', 'Open');
cy.getByTestId(proposalStatus, proposalTimeout).should( cy.get(proposalStatus, proposalTimeout).should('have.text', 'Declined');
'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,23 +32,24 @@ import {
import { import {
vegaWalletSetSpecifiedApprovalAmount, vegaWalletSetSpecifiedApprovalAmount,
vegaWalletTeardown, vegaWalletTeardown,
} from '../../support/wallet-functions'; } from '../../support/wallet-teardown.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 = 'vega-wallet-balance-staked-validators'; const vegaWalletStakedBalances =
const vegaWalletAssociatedBalance = 'associated-amount'; '[data-testid="vega-wallet-balance-staked-validators"]';
const vegaWalletNameElement = 'wallet-name'; const vegaWalletAssociatedBalance = '[data-testid="associated-amount"]';
const vegaWallet = 'vega-wallet'; const vegaWalletNameElement = '[data-testid="wallet-name"]';
const connectToVegaWalletButton = 'connect-to-vega-wallet-btn'; const vegaWallet = '[data-testid="vega-wallet"]';
const newProposalSubmitButton = 'proposal-submit'; const connectToVegaWalletButton = '[data-testid="connect-to-vega-wallet-btn"]';
const viewProposalButton = 'view-proposal-btn'; const newProposalSubmitButton = '[data-testid="proposal-submit"]';
const rawProposalData = 'proposal-data'; const viewProposalButton = '[data-testid="view-proposal-btn"]';
const voteButtons = 'vote-buttons'; const rawProposalData = '[data-testid="proposal-data"]';
const voteButtons = '[data-testid="vote-buttons"]';
const rejectProposalsLink = '[href="/proposals/rejected"]'; const rejectProposalsLink = '[href="/proposals/rejected"]';
const feedbackError = 'Error'; const feedbackError = '[data-testid="Error"]';
const noOpenProposals = 'no-open-proposals'; const noOpenProposals = '[data-testid="no-open-proposals"]';
const noClosedProposals = 'no-closed-proposals'; const noClosedProposals = '[data-testid="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 };
@@ -92,10 +93,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.getByTestId(noOpenProposals) cy.get(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.getByTestId(noClosedProposals) cy.get(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');
}); });
@@ -125,14 +126,11 @@ context(
stakingValidatorPageAddStake('2'); stakingValidatorPageAddStake('2');
closeStakingDialog(); closeStakingDialog();
cy.getByTestId(vegaWalletStakedBalances, txTimeout).should( cy.get(vegaWalletStakedBalances, txTimeout).should('contain', '2');
'contain',
'2'
);
createRawProposal(); createRawProposal();
}); });
it('Creating a proposal - proposal rejected - when closing time sooner than system default', function () { it.skip('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')
@@ -140,7 +138,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('Creating a proposal - proposal rejected - when closing time later than system default', function () { it.skip('Creating a proposal - proposal rejected - when closing time later than system default', function () {
goToMakeNewProposal(governanceProposalType.FREEFORM); goToMakeNewProposal(governanceProposalType.FREEFORM);
enterUniqueFreeFormProposalBody( enterUniqueFreeFormProposalBody(
'100000', '100000',
@@ -170,7 +168,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.getByTestId(viewProposalButton).click(); cy.get(viewProposalButton).click();
}); });
}); });
cy.getByTestId('proposal-status').should('have.text', 'Rejected'); cy.getByTestId('proposal-status').should('have.text', 'Rejected');
@@ -187,14 +185,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.getByTestId(vegaWalletAssociatedBalance, txTimeout).contains( cy.get(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.getByTestId(feedbackError).should('have.text', errorMsg); cy.get(feedbackError).should('have.text', errorMsg);
closeDialog(); closeDialog();
}); });
@@ -207,7 +205,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.getByTestId(feedbackError).should('have.text', errorMsg); cy.get(feedbackError).should('have.text', errorMsg);
closeDialog(); closeDialog();
}); });
@@ -223,17 +221,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.getByTestId(rawProposalData).type(proposalPayload, { cy.get(rawProposalData).type(proposalPayload, {
parseSpecialCharSequences: false, parseSpecialCharSequences: false,
delay: 2, delay: 2,
}); });
}); });
cy.getByTestId(newProposalSubmitButton).should('be.visible').click(); cy.get(newProposalSubmitButton).should('be.visible').click();
cy.contains('Transaction failed', proposalTimeout).should('be.visible'); cy.contains('Transaction failed', proposalTimeout).should('be.visible');
cy.getByTestId(feedbackError).should('have.text', errorMsg); cy.get(feedbackError).should('have.text', errorMsg);
closeDialog(); closeDialog();
cy.getByTestId(rawProposalData) cy.get(rawProposalData)
.invoke('val') .invoke('val')
.should('contain', "i shouldn't be here"); .should('contain', "i shouldn't be here");
}); });
@@ -251,15 +249,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.getByTestId(rawProposalData).type(proposalPayload, { cy.get(rawProposalData).type(proposalPayload, {
parseSpecialCharSequences: false, parseSpecialCharSequences: false,
delay: 2, delay: 2,
}); });
}); });
cy.getByTestId(newProposalSubmitButton).should('be.visible').click(); cy.get(newProposalSubmitButton).should('be.visible').click();
cy.contains('Transaction failed', proposalTimeout).should('be.visible'); cy.contains('Transaction failed', proposalTimeout).should('be.visible');
cy.getByTestId(feedbackError).should('have.text', errorMsg); cy.get(feedbackError).should('have.text', errorMsg);
closeDialog(); closeDialog();
}); });
@@ -267,10 +265,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.getByTestId(newProposalSubmitButton).click(); cy.get(newProposalSubmitButton).click();
cy.getByTestId('input-error-text').should('have.text', 'Required'); cy.getByTestId('input-error-text').should('have.text', 'Required');
cy.getByTestId(rawProposalData).type('Not a valid json string'); cy.get(rawProposalData).type('Not a valid json string');
cy.getByTestId(newProposalSubmitButton).click(); cy.get(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'
@@ -285,22 +283,22 @@ context(
submitUniqueRawProposal({ proposalTitle: proposalTitle }); submitUniqueRawProposal({ proposalTitle: proposalTitle });
ethereumWalletConnect(); ethereumWalletConnect();
stakingPageDisassociateTokens('0.0001'); stakingPageDisassociateTokens('0.0001');
cy.getByTestId(vegaWallet) cy.get(vegaWallet)
.first() .first()
.within(() => { .within(() => {
cy.getByTestId(vegaWalletAssociatedBalance, txTimeout).should( cy.get(vegaWalletAssociatedBalance, txTimeout).should(
'contain', 'contain',
'0.9999' '0.9999'
); );
}); });
navigateTo(navigation.proposals); navigateTo(navigation.proposals);
getProposalFromTitle(proposalTitle).within(() => getProposalFromTitle(proposalTitle).within(() =>
cy.getByTestId(viewProposalButton).click() cy.get(viewProposalButton).click()
); );
cy.contains('Vote breakdown').should('be.visible', { cy.contains('Vote breakdown').should('be.visible', {
timeout: 10000, timeout: 10000,
}); });
cy.getByTestId(voteButtons).should('not.exist'); cy.get(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`
@@ -313,20 +311,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.getByTestId(viewProposalButton).click() cy.get(viewProposalButton).click()
); );
}); });
// 3001-VOTE-075 // 3001-VOTE-075
// 3001-VOTE-076 // 3001-VOTE-076
cy.getByTestId(connectToVegaWalletButton) cy.get(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.getByTestId(vegaWalletNameElement).should('be.visible'); cy.get(vegaWalletNameElement).should('be.visible');
cy.getByTestId(connectToVegaWalletButton).should('not.exist'); cy.get(connectToVegaWalletButton).should('not.exist');
// 3001-VOTE-100 // 3001-VOTE-100
cy.getByTestId(vegaWalletAssociatedBalance, txTimeout).contains( cy.get(vegaWalletAssociatedBalance, txTimeout).contains(
'1.00', '1.00',
txTimeout txTimeout
); );
@@ -8,7 +8,6 @@ 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 {
@@ -24,38 +23,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-functions'; } from '../../support/wallet-teardown.functions';
import { vegaWalletFaucetAssetsWithoutCheck } from '../../support/wallet-vega.functions';
const proposalListItem = '[data-testid="proposals-list-item"]'; const proposalListItem = '[data-testid="proposals-list-item"]';
const openProposals = 'open-proposals'; const openProposals = '[data-testid="open-proposals"]';
const proposalType = 'proposal-type'; const proposalType = '[data-testid="proposal-type"]';
const proposalDetails = 'proposal-details'; const proposalDetails = '[data-testid="proposal-details"]';
const newProposalSubmitButton = 'proposal-submit'; const newProposalSubmitButton = '[data-testid="proposal-submit"]';
const proposalVoteDeadline = 'proposal-vote-deadline'; const proposalVoteDeadline = '[data-testid="proposal-vote-deadline"]';
const proposalParameterSelect = 'proposal-parameter-select'; const proposalParameterSelect = '[data-testid="proposal-parameter-select"]';
const proposalMarketSelect = 'proposal-market-select'; const proposalMarketSelect = '[data-testid="proposal-market-select"]';
const newProposalTitle = 'proposal-title'; const newProposalTitle = '[data-testid="proposal-title"]';
const newProposalDescription = 'proposal-description'; const newProposalDescription = '[data-testid="proposal-description"]';
const newProposalTerms = 'proposal-terms'; const newProposalTerms = '[data-testid="proposal-terms"]';
const newProposedParameterValue = 'selected-proposal-param-new-value'; const newProposedParameterValue =
const minVoteDeadline = 'min-vote'; '[data-testid="selected-proposal-param-new-value"]';
const maxVoteDeadline = 'max-vote'; const minVoteDeadline = '[data-testid="min-vote"]';
const minValidationDeadline = 'min-validation'; const maxVoteDeadline = '[data-testid="max-vote"]';
const minEnactDeadline = 'min-enactment'; const minValidationDeadline = '[data-testid="min-validation"]';
const maxEnactDeadline = 'max-enactment'; const minEnactDeadline = '[data-testid="min-enactment"]';
const inputError = 'input-error-text'; const maxEnactDeadline = '[data-testid="max-enactment"]';
const enactmentDeadlineError = 'enactment-before-voting-deadline'; const inputError = '[data-testid="input-error-text"]';
const proposalDownloadBtn = 'proposal-download-json'; const enactmentDeadlineError =
'[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 proposalJsonToggle = 'proposal-json-toggle'; const proposalTermsSection = 'proposal';
const proposalJsonSection = 'proposal-json';
const vegaWalletPublicKey = Cypress.env('vegaWalletPublicKey'); const vegaWalletPublicKey = Cypress.env('vegaWalletPublicKey');
const fUSDCId = const fUSDCId =
'816af99af60d684502a40824758f6b5377e6af48e50a9ee8ef478ecb879ea8bc'; '816af99af60d684502a40824758f6b5377e6af48e50a9ee8ef478ecb879ea8bc';
@@ -86,20 +85,18 @@ 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.getByTestId(proposalDownloadBtn).click(); cy.get(proposalDownloadBtn).click();
cy.getByTestId(inputError).should('have.length', 3); cy.get(inputError).should('have.length', 3);
cy.getByTestId(newProposalTitle).type( cy.get(newProposalTitle).type(
'Invalid update network parameter proposal' 'Invalid update network parameter proposal'
); );
cy.getByTestId(newProposalDescription).type( cy.get(newProposalDescription).type('E2E invalid test for proposals');
'E2E invalid test for proposals' cy.get(proposalParameterSelect).select(
);
cy.getByTestId(proposalParameterSelect).select(
'spam_protection_proposal_min_tokens' 'spam_protection_proposal_min_tokens'
); );
cy.getByTestId(newProposedParameterValue).type('0'); cy.get(newProposedParameterValue).type('0');
cy.getByTestId(proposalVoteDeadline).clear().type('0'); cy.get(proposalVoteDeadline).clear().type('0');
cy.getByTestId(proposalDownloadBtn) cy.get(proposalDownloadBtn)
.click() .click()
.then(() => { .then(() => {
cy.wrap( cy.wrap(
@@ -109,7 +106,7 @@ context(
submitUniqueRawProposal({ proposalBody: filePath, submit: false }); submitUniqueRawProposal({ proposalBody: filePath, submit: false });
}); });
}); });
cy.getByTestId(newProposalSubmitButton).click(); cy.get(newProposalSubmitButton).click();
validateDialogContentMsg('PROPOSAL_ERROR_CLOSE_TIME_TOO_SOON'); validateDialogContentMsg('PROPOSAL_ERROR_CLOSE_TIME_TOO_SOON');
}); });
@@ -117,33 +114,29 @@ 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.getByTestId(newProposalTitle) cy.get(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.getByTestId(newProposalTitle).type( cy.get(newProposalTitle).type('Test update network parameter proposal');
'Test update network parameter proposal'
);
// 3007-PNEC-009 // 3007-PNEC-009
cy.getByTestId(newProposalDescription) cy.get(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.getByTestId(newProposalDescription).type( cy.get(newProposalDescription).type('E2E test for downloading proposals');
'E2E test for downloading proposals'
);
// 3007-PNEC-010 // 3007-PNEC-010
cy.getByTestId(proposalParameterSelect).select( cy.get(proposalParameterSelect).select(
'governance_proposal_asset_minClose' 'governance_proposal_asset_minClose'
); );
// 3007-PNEC-011 // 3007-PNEC-011
cy.getByTestId(newProposedParameterValue).type('10s'); cy.get(newProposedParameterValue).type('10s');
// 3007-PNEC-012 // 3007-PNEC-012
cy.getByTestId(proposalVoteDeadline).clear().type('2'); cy.get(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.getByTestId(maxEnactDeadline).click(); cy.get(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
@@ -152,7 +145,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.getByTestId(proposalDownloadBtn) cy.get(proposalDownloadBtn)
.should('be.visible') .should('be.visible')
.click() .click()
.then(() => { .then(() => {
@@ -182,21 +175,19 @@ 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.getByTestId(newProposalTitle).type( cy.get(newProposalTitle).type('Test update network parameter proposal');
'Test update network parameter proposal' cy.get(newProposalDescription).type('invalid deadlines');
); 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.getByTestId(newProposedParameterValue).type('0'); cy.get(newProposedParameterValue).type('0');
cy.getByTestId(proposalVoteDeadline).clear().type('0'); cy.get(proposalVoteDeadline).clear().type('0');
cy.getByTestId(maxVoteDeadline).click(); cy.get(maxVoteDeadline).click();
cy.getByTestId(enactmentDeadlineError).should( cy.get(enactmentDeadlineError).should(
'have.text', 'have.text',
'The proposal will fail if enactment is earlier than the voting deadline' 'Proposal will fail if enactment is earlier than the voting deadline'
); );
cy.getByTestId(proposalDownloadBtn) cy.get(proposalDownloadBtn)
.should('be.visible') .should('be.visible')
.click() .click()
.then(() => { .then(() => {
@@ -207,77 +198,55 @@ context(
submitUniqueRawProposal({ proposalBody: filePath, submit: false }); submitUniqueRawProposal({ proposalBody: filePath, submit: false });
}); });
}); });
cy.getByTestId(newProposalSubmitButton).click(); cy.get(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)'
); );
}); });
// 3003-PMAN-001 // 3003-PMAN-001
it( it('Able to submit valid new market proposal', function () {
'Able to submit valid new market proposal', goToMakeNewProposal(governanceProposalType.NEW_MARKET);
{ tags: '@smoke' }, cy.get(newProposalTitle).type('Test new market proposal');
function () { cy.get(newProposalDescription).type('E2E test for proposals');
const proposalTitle = 'Test new market proposal'; cy.fixture('/proposals/new-market').then((newMarketProposal) => {
goToMakeNewProposal(governanceProposalType.NEW_MARKET); const newMarketPayload = JSON.stringify(newMarketProposal);
cy.getByTestId(newProposalTitle).type('Test new market proposal'); cy.get(newProposalTerms).type(newMarketPayload, {
cy.getByTestId(newProposalDescription).type('E2E test for proposals'); parseSpecialCharSequences: false,
cy.fixture('/proposals/new-market').then((newMarketProposal) => { delay: 2,
const newMarketPayload = JSON.stringify(newMarketProposal); });
cy.getByTestId(newProposalTerms).type(newMarketPayload, { });
parseSpecialCharSequences: false, cy.get(proposalDownloadBtn)
delay: 2, .should('be.visible')
.click()
.then(() => {
cy.wrap(
getDownloadedProposalJsonPath('vega-new-market-proposal-')
).then((filePath) => {
goToMakeNewProposal(governanceProposalType.RAW);
submitUniqueRawProposal({ proposalBody: filePath }); // 3003-PMAN-003
}); });
}); });
cy.getByTestId(proposalDownloadBtn) });
.should('be.visible')
.click()
.then(() => {
cy.wrap(
getDownloadedProposalJsonPath('vega-new-market-proposal-')
).then((filePath) => {
goToMakeNewProposal(governanceProposalType.RAW);
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');
});
}
);
it('Unable to submit new market proposal with missing/invalid fields', function () { it('Unable to submit new market proposal with missing/invalid fields', function () {
const errorMsg = const errorMsg =
'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.getByTestId(proposalDownloadBtn).should('be.visible').click(); cy.get(proposalDownloadBtn).should('be.visible').click();
cy.getByTestId(inputError).should('have.length', 3); cy.get(inputError).should('have.length', 3);
cy.getByTestId(newProposalTitle).type('Test new market proposal'); cy.get(newProposalTitle).type('Test new market proposal');
cy.getByTestId(newProposalDescription).type('E2E test for proposals'); cy.get(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.getByTestId(newProposalTerms).type(newMarketPayload, { cy.get(newProposalTerms).type(newMarketPayload, {
parseSpecialCharSequences: false, parseSpecialCharSequences: false,
delay: 2, delay: 2,
}); });
}); });
cy.getByTestId(proposalDownloadBtn) cy.get(proposalDownloadBtn)
.should('be.visible') .should('be.visible')
.click() .click()
.then(() => { .then(() => {
@@ -288,30 +257,29 @@ context(
submitUniqueRawProposal({ proposalBody: filePath, submit: false }); submitUniqueRawProposal({ proposalBody: filePath, submit: false });
}); });
}); });
cy.getByTestId(newProposalSubmitButton).should('be.visible').click(); cy.get(newProposalSubmitButton).should('be.visible').click();
cy.contains('Transaction failed', proposalTimeout).should('be.visible'); cy.contains('Transaction failed', proposalTimeout).should('be.visible');
validateFeedBackMsg(errorMsg); validateFeedBackMsg(errorMsg);
}); });
// 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.skip('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 () {
switchVegaWalletPubKey(); cy.get('[data-testid="manage-vega-wallet"]:visible').click();
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.getByTestId(newProposalTitle).type( cy.get(newProposalTitle).type('Test update market proposal - rejected');
'Test update market proposal - rejected' cy.get(newProposalDescription).type('E2E test for proposals');
); 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.getByTestId(newProposalTerms).type(newUpdateMarketProposal, { cy.get(newProposalTerms).type(newUpdateMarketProposal, {
parseSpecialCharSequences: false, parseSpecialCharSequences: false,
delay: 2, delay: 2,
}); });
}); });
cy.getByTestId(proposalDownloadBtn) cy.get(proposalDownloadBtn)
.should('be.visible') .should('be.visible')
.click() .click()
.then(() => { .then(() => {
@@ -322,13 +290,14 @@ context(
submitUniqueRawProposal({ proposalBody: filePath, submit: false }); submitUniqueRawProposal({ proposalBody: filePath, submit: false });
}); });
}); });
cy.getByTestId(newProposalSubmitButton).should('be.visible').click(); cy.get(newProposalSubmitButton).should('be.visible').click();
cy.contains('Proposal rejected', proposalTimeout).should('be.visible'); 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();
switchVegaWalletPubKey(); cy.get('[data-testid="manage-vega-wallet"]:visible').click();
cy.get('[data-testid="select-keypair-button"]').eq(0).click();
}); });
// 3002-PROP-020 // 3002-PROP-020
@@ -340,19 +309,17 @@ context(
vegaWalletPublicKey vegaWalletPublicKey
); );
goToMakeNewProposal(governanceProposalType.UPDATE_MARKET); goToMakeNewProposal(governanceProposalType.UPDATE_MARKET);
cy.getByTestId(newProposalTitle).type( cy.get(newProposalTitle).type('Test update market proposal - rejected');
'Test update market proposal - rejected' cy.get(newProposalDescription).type('E2E test for proposals');
); 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.getByTestId(newProposalTerms).type(newUpdateMarketProposal, { cy.get(newProposalTerms).type(newUpdateMarketProposal, {
parseSpecialCharSequences: false, parseSpecialCharSequences: false,
delay: 2, delay: 2,
}); });
}); });
cy.getByTestId(proposalDownloadBtn) cy.get(proposalDownloadBtn)
.should('be.visible') .should('be.visible')
.click() .click()
.then(() => { .then(() => {
@@ -363,7 +330,7 @@ context(
submitUniqueRawProposal({ proposalBody: filePath, submit: false }); submitUniqueRawProposal({ proposalBody: filePath, submit: false });
}); });
}); });
cy.getByTestId(newProposalSubmitButton).should('be.visible').click(); cy.get(newProposalSubmitButton).should('be.visible').click();
cy.contains('Transaction failed', proposalTimeout).should('be.visible'); 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)'
@@ -378,9 +345,9 @@ context(
vegaWalletPublicKey vegaWalletPublicKey
); );
goToMakeNewProposal(governanceProposalType.UPDATE_MARKET); goToMakeNewProposal(governanceProposalType.UPDATE_MARKET);
cy.getByTestId(newProposalTitle).type('Test update market proposal'); cy.get(newProposalTitle).type('Test update market proposal');
cy.getByTestId(newProposalDescription).type('E2E test for proposals'); cy.get(newProposalDescription).type('E2E test for proposals');
cy.getByTestId(proposalMarketSelect).select('Test market 1'); cy.get(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');
@@ -395,12 +362,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.getByTestId(newProposalTerms).type(newUpdateMarketProposal, { cy.get(newProposalTerms).type(newUpdateMarketProposal, {
parseSpecialCharSequences: false, parseSpecialCharSequences: false,
delay: 2, delay: 2,
}); });
}); });
cy.getByTestId(proposalDownloadBtn) cy.get(proposalDownloadBtn)
.should('be.visible') .should('be.visible')
.click() .click()
.then(() => { .then(() => {
@@ -447,19 +414,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.getByTestId(newProposalTitle).type(proposalTitle); cy.get(newProposalTitle).type(proposalTitle);
cy.getByTestId(newProposalDescription).type('E2E test for proposals'); cy.get(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.getByTestId(newProposalTerms).type(newAssetPayload, { cy.get(newProposalTerms).type(newAssetPayload, {
parseSpecialCharSequences: false, parseSpecialCharSequences: false,
delay: 2, delay: 2,
}); });
}); });
cy.getByTestId(minVoteDeadline).click(); cy.get(minVoteDeadline).click();
cy.getByTestId(minValidationDeadline).click(); cy.get(minValidationDeadline).click();
cy.getByTestId(minEnactDeadline).click(); cy.get(minEnactDeadline).click();
cy.getByTestId(proposalDownloadBtn) cy.get(proposalDownloadBtn)
.should('be.visible') .should('be.visible')
.click() .click()
.then(() => { .then(() => {
@@ -470,9 +437,9 @@ context(
submitUniqueRawProposal({ proposalBody: filePath, submit: false }); // 3005-PASN-003 submitUniqueRawProposal({ proposalBody: filePath, submit: false }); // 3005-PASN-003
}); });
}); });
cy.getByTestId(newProposalSubmitButton).should('be.visible').click(); cy.get(newProposalSubmitButton).should('be.visible').click();
closeDialog(); closeDialog();
cy.getByTestId(newProposalSubmitButton).should('be.visible').click(); cy.get(newProposalSubmitButton).should('be.visible').click();
// cannot submit a proposal with ERC20 address already in use // 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');
@@ -484,8 +451,8 @@ context(
.within(() => { .within(() => {
cy.getByTestId(viewProposalBtn).click(); cy.getByTestId(viewProposalBtn).click();
}); });
cy.getByTestId(proposalJsonToggle).click(); cy.getByTestId('proposal-terms-toggle').click();
cy.getByTestId(proposalJsonSection).within(() => { cy.getByTestId(proposalTermsSection).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');
}); });
@@ -493,8 +460,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.getByTestId(proposalDownloadBtn).should('be.visible').click(); cy.get(proposalDownloadBtn).should('be.visible').click();
cy.getByTestId(inputError).should('have.length', 3); cy.get(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 () {
@@ -503,9 +470,9 @@ context(
goToMakeNewProposal(governanceProposalType.UPDATE_ASSET); goToMakeNewProposal(governanceProposalType.UPDATE_ASSET);
enterUpdateAssetProposalDetails(); enterUpdateAssetProposalDetails();
cy.getByTestId(minVoteDeadline).click(); cy.get(minVoteDeadline).click();
cy.getByTestId(minEnactDeadline).click(); cy.get(minEnactDeadline).click();
cy.getByTestId(proposalDownloadBtn) cy.get(proposalDownloadBtn)
.should('be.visible') .should('be.visible')
.click() .click()
.then(() => { .then(() => {
@@ -517,16 +484,13 @@ context(
}); });
}); });
navigateTo(navigation.proposals); navigateTo(navigation.proposals);
cy.getByTestId(openProposals).within(() => { cy.get(openProposals).within(() => {
cy.getByTestId(proposalType) cy.get(proposalType)
.contains('Update asset') .contains('Update asset')
.parentsUntil(proposalListItem) .parentsUntil(proposalListItem)
.last() .last()
.within(() => { .within(() => {
cy.getByTestId(proposalDetails).should( cy.get(proposalDetails).should('contain.text', assetId.slice(0, 6)); // 3001-VOTE-029
'contain.text',
assetId.slice(0, 6)
); // 3001-VOTE-029
cy.getByTestId(viewProposalBtn).click(); cy.getByTestId(viewProposalBtn).click();
}); });
}); });
@@ -534,11 +498,13 @@ 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(proposalJsonToggle).click(); cy.getByTestId('proposal-terms-toggle').click();
cy.getByTestId(proposalJsonSection).within(() => { cy.getByTestId('proposal-terms').within(() => {
cy.contains(assetId).should('be.visible'); getProposalInformationFromTable('assetId').should('have.text', assetId);
cy.contains('lifetimeLimit').should('be.visible'); getProposalInformationFromTable('lifetimeLimit').should(
cy.contains('10').should('be.visible'); 'have.text',
'10'
);
}); });
}); });
@@ -546,9 +512,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.getByTestId(maxVoteDeadline).click(); cy.get(maxVoteDeadline).click();
cy.getByTestId(maxEnactDeadline).click(); cy.get(maxEnactDeadline).click();
cy.getByTestId(proposalDownloadBtn) cy.get(proposalDownloadBtn)
.should('be.visible') .should('be.visible')
.click() .click()
.then(() => { .then(() => {
@@ -563,36 +529,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.getByTestId(proposalDownloadBtn).should('be.visible').click(); cy.get(proposalDownloadBtn).should('be.visible').click();
cy.getByTestId(inputError).should('have.length', 3); cy.get(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.getByTestId(newProposalTitle) cy.get(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.getByTestId(newProposalTitle).type('Test freeform proposal form'); cy.get(newProposalTitle).type('Test freeform proposal form');
// 3008-PFRO-009 // 3008-PFRO-009
cy.getByTestId(newProposalDescription) cy.get(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.getByTestId(newProposalDescription).type( cy.get(newProposalDescription).type(
'E2E test for downloading freeform proposal' 'E2E test for downloading freeform proposal'
); );
// 3008-PFRO-012 // 3008-PFRO-012
cy.getByTestId(minVoteDeadline).should('exist'); // 3002-PROP-008 cy.get(minVoteDeadline).should('exist'); // 3002-PROP-008
cy.getByTestId(maxVoteDeadline).should('exist'); cy.get(maxVoteDeadline).should('exist');
// 3008-PFRO-011 // 3008-PFRO-011
cy.getByTestId(proposalVoteDeadline).clear().type('2'); cy.get(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.getByTestId(proposalDownloadBtn) cy.get(proposalDownloadBtn)
.should('be.visible') .should('be.visible')
.click() .click()
.then(() => { .then(() => {
@@ -626,23 +592,15 @@ context(
} }
function enterUpdateAssetProposalDetails() { function enterUpdateAssetProposalDetails() {
cy.getByTestId(newProposalTitle).type('Test update asset proposal'); cy.get(newProposalTitle).type('Test update asset proposal');
cy.getByTestId(newProposalDescription).type('E2E test for proposals'); cy.get(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.getByTestId(newProposalTerms).type(newAssetPayload, { cy.get(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-functions'; import { vegaWalletSetSpecifiedApprovalAmount } from '../../support/wallet-teardown.functions';
const proposalListItem = 'proposals-list-item'; const proposalListItem = 'proposals-list-item';
const openProposals = 'open-proposals'; const openProposals = '[data-testid="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 = 'vote-details'; const proposalClosingDate = '[data-testid="vote-details"]';
const viewProposalButton = 'view-proposal-btn'; const viewProposalButton = '[data-testid="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.getByTestId(openProposals).within(() => { cy.get(openProposals).within(() => {
cy.getByTestId(proposalClosingDate) cy.get(proposalClosingDate)
.first() .first()
.invoke('text') .invoke('text')
.should('match', /days|minutes/); .should('match', /days|minutes/);
cy.getByTestId(proposalClosingDate).should('contain.text', 'months'); cy.get(proposalClosingDate).should('contain.text', 'months');
cy.getByTestId(proposalClosingDate).last().should('contain.text', 'year'); cy.get(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="proposal-filter-toggle"]').click(); cy.get('[data-testid="set-proposals-filter-visible"]').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.getByTestId(viewProposalButton).should('be.visible'); cy.get(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.getByTestId(viewProposalButton).click(); cy.get(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.getByTestId(viewProposalButton).click(); cy.get(viewProposalButton).click();
}); });
cy.getByTestId(voteBreakDownToggle).click(); cy.getByTestId(voteBreakDownToggle).click();
getProposalInformationFromTable('Token participation met') getProposalInformationFromTable('Token participation met')
@@ -14,10 +14,11 @@ import { ethereumWalletConnect } from '../../support/wallet-eth.functions';
import { import {
depositAsset, depositAsset,
vegaWalletTeardown, vegaWalletTeardown,
} from '../../support/wallet-functions'; } from '../../support/wallet-teardown.functions';
const vegaAssetAddress = '0x67175Da1D5e966e40D11c4B2519392B2058373de'; const vegaAssetAddress = '0x67175Da1D5e966e40D11c4B2519392B2058373de';
const vegaWalletUnstakedBalance = 'vega-wallet-balance-unstaked'; const vegaWalletUnstakedBalance =
'[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 };
@@ -39,7 +40,7 @@ context('rewards - flow', { tags: '@slow' }, function () {
cy.associateTokensToVegaWallet('6000'); cy.associateTokensToVegaWallet('6000');
navigateTo(navigation.validators); navigateTo(navigation.validators);
cy.VegaWalletTopUpRewardsPool(); cy.VegaWalletTopUpRewardsPool();
cy.getByTestId(vegaWalletUnstakedBalance, txTimeout).should( cy.get(vegaWalletUnstakedBalance, txTimeout).should(
'contain', 'contain',
'6,000.0', '6,000.0',
txTimeout txTimeout
@@ -4,6 +4,7 @@ import {
verifyStakedBalance, verifyStakedBalance,
verifyEthWalletTotalAssociatedBalance, verifyEthWalletTotalAssociatedBalance,
verifyEthWalletAssociatedBalance, verifyEthWalletAssociatedBalance,
waitForSpinner,
navigateTo, navigateTo,
navigation, navigation,
turnTelemetryOff, turnTelemetryOff,
@@ -24,7 +25,7 @@ import { ethereumWalletConnect } from '../../support/wallet-eth.functions';
import { import {
vegaWalletSetSpecifiedApprovalAmount, vegaWalletSetSpecifiedApprovalAmount,
vegaWalletTeardown, vegaWalletTeardown,
} from '../../support/wallet-functions'; } from '../../support/wallet-teardown.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';
@@ -53,11 +54,12 @@ context(
'Staking Tab - with eth and vega wallets connected', 'Staking Tab - with eth and vega wallets connected',
{ tags: '@slow' }, { tags: '@slow' },
function () { function () {
// 1002-STKE-002, 1002-STKE-032 // 2001-STKE-002, 2001-STKE-032
before('visit staking tab and connect vega wallet', function () { before('visit staking tab and connect vega wallet', function () {
cy.visit('/'); cy.visit('/');
ethereumWalletConnect(); ethereumWalletConnect();
cy.connectVegaWallet(); // this is a workaround for #2422 which can be removed once issue is resolved
cy.associateTokensToVegaWallet('4');
vegaWalletSetSpecifiedApprovalAmount('1000'); vegaWalletSetSpecifiedApprovalAmount('1000');
}); });
@@ -67,48 +69,33 @@ context(
function () { function () {
cy.clearLocalStorage(); cy.clearLocalStorage();
turnTelemetryOff(); turnTelemetryOff();
// Go to homepage to allow wallet teardown without epoch timer refreshing page cy.reload();
navigateTo(navigation.home); waitForSpinner();
vegaWalletTeardown(); cy.connectVegaWallet();
ethereumWalletConnect();
navigateTo(navigation.validators); navigateTo(navigation.validators);
} }
); );
// 1002-STKE-035 1002-STKE-036
it('Unable to stake against a validator with less than minimum and more than associated amount', function () {
ensureSpecifiedUnstakedTokensAreAssociated('3');
verifyUnstakedBalance(3.0);
verifyEthWalletTotalAssociatedBalance('3.0');
verifyEthWalletAssociatedBalance('3.0');
cy.get('button').contains('Select a validator to nominate').click();
clickOnValidatorFromList(0);
cy.getByTestId(stakeAddStakeRadioButton, epochTimeout).click({
force: true,
});
cy.getByTestId(stakeTokenAmountInputBox).type('0.001');
cy.getByTestId(stakeTokenSubmitButton).should('be.disabled');
cy.getByTestId(stakeTokenAmountInputBox).clear().type('4');
cy.getByTestId(stakeTokenSubmitButton).should('be.disabled');
});
it('Able to stake against a validator - using vega from wallet', function () { it('Able to stake against a validator - using vega from wallet', function () {
ensureSpecifiedUnstakedTokensAreAssociated('3'); ensureSpecifiedUnstakedTokensAreAssociated('3');
verifyUnstakedBalance(3.0); verifyUnstakedBalance(3.0);
verifyEthWalletTotalAssociatedBalance('3.0'); verifyEthWalletTotalAssociatedBalance('3.0');
verifyEthWalletAssociatedBalance('3.0'); verifyEthWalletAssociatedBalance('3.0');
cy.get('button').contains('Select a validator to nominate').click(); cy.get('button').contains('Select a validator to nominate').click();
// 1002-STKE-031 // 2001-STKE-031
clickOnValidatorFromList(0); clickOnValidatorFromList(0);
// 1002-STKE-033, 1002-STKE-034, 1002-STKE-037 // 2001-STKE-033, 2001-STKE-034, 2001-STKE-037
stakingValidatorPageAddStake('2'); stakingValidatorPageAddStake('2');
verifyUnstakedBalance(1.0); verifyUnstakedBalance(1.0);
// 1002-STKE-039 // 2001-STKE-039
verifyStakedBalance(2.0); verifyStakedBalance(2.0);
verifyNextEpochValue(2.0); // 1002-STKE-016 1002-STKE-038 verifyNextEpochValue(2.0); // 2001-STKE-016 2001-STKE-038
verifyThisEpochValue(2.0); // 1002-STKE-013 verifyThisEpochValue(2.0); // 2001-STKE-013
closeStakingDialog(); closeStakingDialog();
navigateTo(navigation.validators); navigateTo(navigation.validators);
// 2002-SINC-007 1002-STKE-015 1002-STKE-017 1002-STKE-052 // 2002-SINC-007
validateValidatorListTotalStakeAndShare('0', '3,002.00', '50.02%'); validateValidatorListTotalStakeAndShare('0', '3,002.00', '50.02%');
}); });
@@ -127,7 +114,6 @@ 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()
@@ -232,7 +218,7 @@ context(
}); });
}); });
// 1002-STKE-041 1002-STKE-053 // 2001-STKE-041
it( it(
'Able to remove part of a stake against a validator', 'Able to remove part of a stake against a validator',
{ tags: '@smoke' }, { tags: '@smoke' },
@@ -245,11 +231,11 @@ context(
verifyUnstakedBalance(1.0); verifyUnstakedBalance(1.0);
closeStakingDialog(); closeStakingDialog();
navigateTo(navigation.validators); navigateTo(navigation.validators);
// 1002-STKE-040 // 2001-STKE-040
clickOnValidatorFromList(0); clickOnValidatorFromList(0);
// 1002-STKE-044, 1002-STKE-048 // 2001-STKE-044, 2001-STKE-048
stakingValidatorPageRemoveStake('1'); stakingValidatorPageRemoveStake('1');
// 1002-STKE-049 // 2001-STKE-049
verifyNextEpochValue(2.0); verifyNextEpochValue(2.0);
verifyUnstakedBalance(2.0); verifyUnstakedBalance(2.0);
verifyStakedBalance(2.0); verifyStakedBalance(2.0);
@@ -269,7 +255,7 @@ context(
} }
); );
// 1002-STKE-045 // 2001-STKE-045
it('Able to remove a full stake against a validator', function () { it('Able to remove a full stake against a validator', function () {
stakingPageAssociateTokens('3'); stakingPageAssociateTokens('3');
verifyUnstakedBalance(3.0); verifyUnstakedBalance(3.0);
@@ -317,7 +303,6 @@ context(
.and('be.visible'); .and('be.visible');
}); });
// 1002-STKE-046 1002-STKE-047
it('Unable to remove a stake greater than staked amount next epoch for a validator', function () { it('Unable to remove a stake greater than staked amount next epoch for a validator', function () {
stakingPageAssociateTokens('3'); stakingPageAssociateTokens('3');
verifyUnstakedBalance(3.0); verifyUnstakedBalance(3.0);
@@ -396,9 +381,6 @@ context(
it('Disassociating some tokens - prioritizes unstaked tokens', function () { it('Disassociating some tokens - prioritizes unstaked tokens', function () {
vegaWalletSetSpecifiedApprovalAmount('1000'); vegaWalletSetSpecifiedApprovalAmount('1000');
cy.reload();
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();
@@ -421,7 +403,7 @@ context(
}); });
it('Associating wallet tokens - when some already staked - auto stakes tokens to staked validator', function () { it('Associating wallet tokens - when some already staked - auto stakes tokens to staked validator', function () {
// 1002-STKE-004 // 2001-STKE-004
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();
@@ -435,7 +417,7 @@ context(
}); });
it('Associating vesting contract tokens - when some already staked - auto stakes tokens to staked validator', function () { it('Associating vesting contract tokens - when some already staked - auto stakes tokens to staked validator', function () {
// 1002-STKE-004 // 2001-STKE-004
stakingPageAssociateTokens('3', { type: 'contract' }); stakingPageAssociateTokens('3', { type: 'contract' });
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();
@@ -449,7 +431,7 @@ context(
}); });
it('Associating vesting contract tokens - when wallet tokens already staked - auto stakes tokens to staked validator', function () { it('Associating vesting contract tokens - when wallet tokens already staked - auto stakes tokens to staked validator', function () {
// 1002-STKE-004 // 2001-STKE-004
stakingPageAssociateTokens('3', { type: 'wallet' }); stakingPageAssociateTokens('3', { type: 'wallet' });
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();
@@ -463,7 +445,7 @@ context(
}); });
it('Associating tokens - with multiple validators already staked - auto stakes to staked validators - abiding by existing stake ratio', function () { it('Associating tokens - with multiple validators already staked - auto stakes to staked validators - abiding by existing stake ratio', function () {
// 1002-STKE-004 // 2001-STKE-004
stakingPageAssociateTokens('6'); stakingPageAssociateTokens('6');
verifyUnstakedBalance(6.0); verifyUnstakedBalance(6.0);
cy.get('button').contains('Select a validator to nominate').click(); cy.get('button').contains('Select a validator to nominate').click();
@@ -504,6 +486,11 @@ 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)
@@ -511,7 +498,7 @@ context(
} }
function verifyThisEpochValue(amount: number) { function verifyThisEpochValue(amount: number) {
cy.getByTestId('stake-this-epoch', epochTimeout) // 1002-STKE-013 cy.getByTestId('stake-this-epoch', epochTimeout) // 2001-STKE-013
.contains(amount, epochTimeout) .contains(amount, epochTimeout)
.should('be.visible'); .should('be.visible');
} }
@@ -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-functions'; } from '../../support/wallet-teardown.functions';
const ethWalletContainer = 'ethereum-wallet'; const ethWalletContainer = '[data-testid="ethereum-wallet"]';
const vegaWalletAssociatedBalance = 'currency-value'; const vegaWalletAssociatedBalance = '[data-testid="currency-value"]';
const vegaWalletUnstakedBalance = 'vega-wallet-balance-unstaked'; const vegaWalletUnstakedBalance =
const ethWalletAssociateButton = '[data-testid="associate-btn"]:visible'; '[data-testid="vega-wallet-balance-unstaked"]';
const associateWalletRadioButton = 'associate-radio-wallet';
const tokenAmountInputBox = 'token-amount-input';
const tokenSubmitButton = 'token-input-submit-button';
const ethWalletDissociateButton = '[href="/token/disassociate"]:visible';
const vestingContractSection = 'vega-in-vesting-contract';
const vegaInWalletSection = 'vega-in-wallet';
const connectedVegaKey = 'connected-vega-key';
const associatedKey = 'associated-key';
const associatedAmount = 'associated-amount';
const associateCompleteText = 'transaction-complete-body';
const disassociationWarning = 'disassociation-warning';
const vegaWallet = 'aside [data-testid="vega-wallet"]';
const vegaWalletPublicKeyShort = Cypress.env('vegaWalletPublicKeyShort');
const txTimeout = Cypress.env('txTimeout'); const txTimeout = Cypress.env('txTimeout');
const vegaWalletPublicKeyShort = Cypress.env('vegaWalletPublicKeyShort');
const ethWalletAssociateButton = '[data-testid="associate-btn"]:visible';
const associateWalletRadioButton = '[data-testid="associate-radio-wallet"]';
const tokenAmountInputBox = '[data-testid="token-amount-input"]';
const tokenSubmitButton = '[data-testid="token-input-submit-button"]';
const ethWalletDissociateButton = '[href="/token/disassociate"]:visible';
const vestingContractSection = '[data-testid="vega-in-vesting-contract"]';
const vegaInWalletSection = '[data-testid="vega-in-wallet"]';
const connectedVegaKey = '[data-testid="connected-vega-key"]';
const associatedKey = '[data-testid="associated-key"]';
const associatedAmount = '[data-testid="associated-amount"]';
const associateCompleteText = '[data-testid="transaction-complete-body"]';
const disassociationWarning = '[data-testid="disassociation-warning"]';
const vegaWallet = 'aside [data-testid="vega-wallet"]';
context( context(
'Token association flow - with eth and vega wallets connected', 'Token association flow - with eth and vega wallets connected',
@@ -64,41 +64,31 @@ context(
} }
); );
it( it('Able to associate tokens - from wallet', function () {
'Able to associate tokens - from wallet', //1004-ASSO-003
{ tags: '@smoke' }, //1004-ASSO-005
function () { //1004-ASSO-009
//1004-ASSO-003 //1004-ASSO-030
//1004-ASSO-005 //1004-ASSO-012
//1004-ASSO-009 //1004-ASSO-013
//1004-ASSO-030 //1004-ASSO-014
//1004-ASSO-012 //1004-ASSO-015
//1004-ASSO-013 //1004-ASSO-030
//1004-ASSO-014 //0005-ETXN-006
//1004-ASSO-015 //0005-ETXN-003
//1004-ASSO-030 //0005-ETXN-005
//0005-ETXN-006 stakingPageAssociateTokens('2', { skipConfirmation: true });
//0005-ETXN-003 validateWalletCurrency('Associated', '0.00');
//0005-ETXN-005 validateWalletCurrency('Pending association', '2.00');
stakingPageAssociateTokens('2', { skipConfirmation: true }); validateWalletCurrency('Total associated after pending', '2.00');
validateWalletCurrency('Associated', '0.00'); // 0005-ETXN-002
validateWalletCurrency('Pending association', '2.00'); verifyEthWalletAssociatedBalance('2.0');
validateWalletCurrency('Total associated after pending', '2.00'); verifyEthWalletTotalAssociatedBalance('2.0');
// 0005-ETXN-002 cy.get(vegaWallet).within(() => {
verifyEthWalletAssociatedBalance('2.0'); cy.get(vegaWalletAssociatedBalance, txTimeout).should('contain', 2.0);
verifyEthWalletTotalAssociatedBalance('2.0'); });
cy.get(vegaWallet).within(() => { cy.get(vegaWalletUnstakedBalance, txTimeout).should('contain', 2.0);
cy.getByTestId(vegaWalletAssociatedBalance, txTimeout).should( });
'contain',
2.0
);
});
cy.getByTestId(vegaWalletUnstakedBalance, txTimeout).should(
'contain',
2.0
);
}
);
it('Able to disassociate all associated tokens - manually', function () { it('Able to disassociate all associated tokens - manually', function () {
// 1004-ASSO-025 // 1004-ASSO-025
@@ -129,7 +119,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.getByTestId(vegaWalletAssociatedBalance, txTimeout).should( cy.get(vegaWalletAssociatedBalance, txTimeout).should(
'contain', 'contain',
'1,001.00' '1,001.00'
); );
@@ -139,20 +129,14 @@ 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.getByTestId(vegaWalletAssociatedBalance, txTimeout).should( cy.get(vegaWalletAssociatedBalance, txTimeout).should('contain', 2.0);
'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.getByTestId(vegaWalletAssociatedBalance, txTimeout).should( cy.get(vegaWalletAssociatedBalance, txTimeout).should('contain', 1.0);
'contain',
1.0
);
}); });
}); });
@@ -162,24 +146,21 @@ context(
'Warning: Any tokens that have been nominated to a node will sacrifice rewards they are due for the current epoch. If you do not wish to sacrifice these, you should remove stake from a node at the end of an epoch before disassociation.'; '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.getByTestId(vegaWalletAssociatedBalance, txTimeout).should( cy.get(vegaWalletAssociatedBalance, txTimeout).should('contain', 2.0);
'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.getByTestId(disassociationWarning).should('contain', warningText); cy.get(disassociationWarning).should('contain', warningText);
stakingPageDisassociateAllTokens(); stakingPageDisassociateAllTokens();
cy.getByTestId(ethWalletContainer) cy.get(ethWalletContainer)
.first() .first()
.within(() => { .within(() => {
cy.contains(vegaWalletPublicKeyShort, { timeout: 20000 }).should( cy.contains(vegaWalletPublicKeyShort, { timeout: 20000 }).should(
'not.exist' 'not.exist'
); );
}); });
cy.getByTestId(ethWalletContainer) cy.get(ethWalletContainer)
.first() .first()
.within(() => { .within(() => {
cy.contains(vegaWalletPublicKeyShort, { timeout: 20000 }).should( cy.contains(vegaWalletPublicKeyShort, { timeout: 20000 }).should(
@@ -187,10 +168,7 @@ context(
); );
}); });
cy.get(vegaWallet).within(() => { cy.get(vegaWallet).within(() => {
cy.getByTestId(vegaWalletAssociatedBalance, txTimeout).should( cy.get(vegaWalletAssociatedBalance, txTimeout).should('contain', 0.0);
'contain',
0.0
);
}); });
}); });
@@ -212,15 +190,9 @@ context(
verifyEthWalletAssociatedBalance('2.0'); verifyEthWalletAssociatedBalance('2.0');
verifyEthWalletTotalAssociatedBalance('2.0'); verifyEthWalletTotalAssociatedBalance('2.0');
cy.get(vegaWallet).within(() => { cy.get(vegaWallet).within(() => {
cy.getByTestId(vegaWalletAssociatedBalance, txTimeout).should( cy.get(vegaWalletAssociatedBalance, txTimeout).should('contain', 2.0);
'contain',
2.0
);
}); });
cy.getByTestId(vegaWalletUnstakedBalance, txTimeout).should( cy.get(vegaWalletUnstakedBalance, txTimeout).should('contain', 2.0);
'contain',
2.0
);
stakingPageDisassociateTokens('1', { stakingPageDisassociateTokens('1', {
type: 'contract', type: 'contract',
skipConfirmation: true, skipConfirmation: true,
@@ -241,54 +213,45 @@ 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.getByTestId(vestingContractSection) cy.get(vestingContractSection)
.first() .first()
.within(() => { .within(() => {
cy.getByTestId(associatedKey).should( cy.get(associatedKey).should(
'contain', 'contain',
Cypress.env('vegaWalletPublicKeyShort') Cypress.env('vegaWalletPublicKeyShort')
); );
cy.getByTestId(associatedAmount, txTimeout).should('contain', 37); cy.get(associatedAmount, txTimeout).should('contain', 37);
}); });
cy.getByTestId(vegaInWalletSection) cy.get(vegaInWalletSection)
.first() .first()
.within(() => { .within(() => {
cy.getByTestId(associatedKey).should( cy.get(associatedKey).should(
'contain', 'contain',
Cypress.env('vegaWalletPublicKeyShort') Cypress.env('vegaWalletPublicKeyShort')
); );
cy.getByTestId(associatedAmount, txTimeout).should('contain', 21); cy.get(associatedAmount, txTimeout).should('contain', 21);
}); });
cy.get(vegaWallet).within(() => { cy.get(vegaWallet).within(() => {
cy.getByTestId(vegaWalletAssociatedBalance, txTimeout).should( cy.get(vegaWalletAssociatedBalance, txTimeout).should('contain', 58);
'contain',
58
);
}); });
stakingPageDisassociateTokens('6', { type: 'contract' }); stakingPageDisassociateTokens('6', { type: 'contract' });
cy.getByTestId(vestingContractSection) cy.get(vestingContractSection)
.first() .first()
.within(() => { .within(() => {
cy.getByTestId(associatedAmount, txTimeout).should('contain', 31); cy.get(associatedAmount, txTimeout).should('contain', 31);
}); });
cy.get(vegaWallet).within(() => { cy.get(vegaWallet).within(() => {
cy.getByTestId(vegaWalletAssociatedBalance, txTimeout).should( cy.get(vegaWalletAssociatedBalance, txTimeout).should('contain', 52);
'contain',
52
);
}); });
navigateTo(navigation.validators); navigateTo(navigation.validators);
stakingPageDisassociateTokens('9', { type: 'wallet' }); stakingPageDisassociateTokens('9', { type: 'wallet' });
cy.getByTestId(vegaInWalletSection) cy.get(vegaInWalletSection)
.first() .first()
.within(() => { .within(() => {
cy.getByTestId(associatedAmount, txTimeout).should('contain', 12); cy.get(associatedAmount, txTimeout).should('contain', 12);
}); });
cy.get(vegaWallet).within(() => { cy.get(vegaWallet).within(() => {
cy.getByTestId(vegaWalletAssociatedBalance, txTimeout).should( cy.get(vegaWalletAssociatedBalance, txTimeout).should('contain', 43);
'contain',
43
);
}); });
}); });
@@ -297,10 +260,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.getByTestId(associateWalletRadioButton, { timeout: 30000 }).click(); cy.get(associateWalletRadioButton, { timeout: 30000 }).click();
cy.getByTestId(tokenSubmitButton, txTimeout).should('be.disabled'); // button disabled with no input cy.get(tokenSubmitButton, txTimeout).should('be.disabled'); // button disabled with no input
cy.getByTestId(tokenAmountInputBox, { timeout: 10000 }).type('6500000'); cy.get(tokenAmountInputBox, { timeout: 10000 }).type('6500000');
cy.getByTestId(tokenSubmitButton, txTimeout).should('be.disabled'); cy.get(tokenSubmitButton, txTimeout).should('be.disabled');
}); });
// 1004-ASSO-004 // 1004-ASSO-004
@@ -325,25 +288,23 @@ 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.getByTestId(associateWalletRadioButton).click(); cy.get(associateWalletRadioButton).click();
cy.getByTestId(connectedVegaKey).should( cy.get(connectedVegaKey).should(
'have.text', 'have.text',
Cypress.env('vegaWalletPublicKey') Cypress.env('vegaWalletPublicKey')
); );
switchVegaWalletPubKey(); cy.get('[data-testid="manage-vega-wallet"]:visible').click();
cy.getByTestId(connectedVegaKey).should( cy.get('[data-testid="select-keypair-button"]').eq(0).click();
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.getByTestId(vegaWalletAssociatedBalance, txTimeout).should( cy.get(vegaWalletAssociatedBalance, txTimeout).should('contain', 2.0);
'contain',
2.0
);
}); });
cy.getByTestId(associateCompleteText).should( cy.get(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-functions'; import { depositAsset } from '../../support/wallet-teardown.functions';
const withdraw = 'withdraw'; const withdraw = 'withdraw';
const withdrawalForm = 'withdraw-form'; const withdrawalForm = 'withdraw-form';
@@ -55,10 +55,6 @@ context(
navigateTo(navigation.withdraw); navigateTo(navigation.withdraw);
cy.connectVegaWallet(); cy.connectVegaWallet();
ethereumWalletConnect(); ethereumWalletConnect();
cy.getByTestId('currency-title', txTimeout).should(
'contain.text',
usdtName
);
}); });
it('Able to open withdrawal form with vega wallet connected', function () { it('Able to open withdrawal form with vega wallet connected', function () {
@@ -73,10 +69,7 @@ context(
it('Unable to submit withdrawal with invalid fields', function () { it('Unable to submit withdrawal with invalid fields', function () {
cy.getByTestId(withdraw).should('be.visible').click(); cy.getByTestId(withdraw).should('be.visible').click();
cy.getByTestId(withdrawalForm, txTimeout).within(() => { cy.getByTestId(withdrawalForm, txTimeout).within(() => {
cy.getByTestId('select-asset').click(); cy.get('select').select(usdtSelectValue, { force: true });
cy.get('select')
.select(usdtSelectValue, { force: true })
.should('have.value', usdtSelectValue);
cy.getByTestId(balanceAvailable, txTimeout).should('exist'); cy.getByTestId(balanceAvailable, txTimeout).should('exist');
cy.getByTestId(submitWithdrawalButton).click(); cy.getByTestId(submitWithdrawalButton).click();
cy.getByTestId(formValidationError).should('have.length', 1); cy.getByTestId(formValidationError).should('have.length', 1);
@@ -96,74 +89,68 @@ context(
}); });
}); });
it( it('Able to withdraw asset: -eth wallet connected -withdraw funds button', function () {
'Able to withdraw asset: -eth wallet connected -withdraw funds button', // fill in withdrawal form
{ tags: '@smoke' }, cy.getByTestId(withdraw).should('be.visible').click();
function () { cy.getByTestId(withdrawalForm, txTimeout).within(() => {
// fill in withdrawal form cy.get('select').select(usdtSelectValue, { force: true });
cy.getByTestId(withdraw).should('be.visible').click(); cy.getByTestId(balanceAvailable, txTimeout).should('exist');
cy.getByTestId(withdrawalForm, txTimeout).within(() => { cy.getByTestId(withdrawalThreshold).should(
cy.getByTestId('select-asset').click(); 'have.text',
cy.get('select') '100,000.00000T'
.select(usdtSelectValue, { force: true }) );
.should('have.value', usdtSelectValue); cy.getByTestId(delayTime).should('have.text', 'None');
cy.getByTestId(balanceAvailable, txTimeout).should('exist'); cy.getByTestId(amountInput).click().type('120');
cy.getByTestId(withdrawalThreshold).should( cy.getByTestId(submitWithdrawalButton).click();
'have.text', });
'100,000.00000T' // assert withdrawal request
cy.getByTestId(toast)
.first(txTimeout)
.should('contain.text', 'Funds unlocked')
.within(() => {
cy.getByTestId('external-link').should('exist');
cy.getByTestId(toastPanel).should(
'contain.text',
'Withdraw 120.00 tUSDC'
); );
cy.getByTestId(delayTime).should('have.text', 'None'); cy.getByTestId(toastCompleteWithdrawal).click();
cy.getByTestId(amountInput).click().type('120'); cy.getByTestId(toastClose).click();
cy.getByTestId(submitWithdrawalButton).click();
}); });
cy.getByTestId(toast) // withdrawal complete
.first(txTimeout) cy.getByTestId(toast)
.should('contain.text', 'Funds unlocked') .first(txTimeout)
.within(() => { .should('contain.text', 'The withdrawal has been approved.')
cy.getByTestId('external-link').should('exist'); .within(() => {
cy.getByTestId(toastPanel).should( cy.getByTestId(toastPanel).should(
'contain.text', 'contain.text',
'Withdraw 120.00 tUSDC' 'Withdraw 120.00 tUSDC'
); );
cy.getByTestId(toastCompleteWithdrawal).click(); });
cy.getByTestId(toastClose).click(); cy.getByTestId(toast)
}); .last(txTimeout)
// withdrawal complete .should('contain.text', 'Transaction confirmed')
cy.getByTestId(toast) .within(() => {
.first(txTimeout) cy.getByTestId('external-link').should('exist');
.should('contain.text', 'The withdrawal has been approved.') });
.within(() => { // withdrawal history for complete withdrawal displayed
cy.getByTestId(toastPanel).should( cy.get(tableWithdrawnStatus)
'contain.text', .eq(1, txTimeout)
'Withdraw 120.00 tUSDC' .should('have.text', 'Completed')
); .parent()
}); .within(() => {
cy.getByTestId(toast) cy.get(tableAssetSymbol).should('have.text', usdcSymbol);
.last(txTimeout) cy.get(tableAmount).should('have.text', '120.00');
.should('contain.text', 'Transaction confirmed') cy.get(tableReceiverAddress)
.within(() => { .find('a')
cy.getByTestId('external-link').should('exist'); .should('have.attr', 'href')
}); .and('contain', 'https://sepolia.etherscan.io/address/');
// withdrawal history for complete withdrawal displayed cy.get(tableWithdrawnTimeStamp).should('not.be.empty');
cy.get(tableWithdrawnStatus) cy.get(tableTxHash)
.eq(1, txTimeout) .find('a')
.should('have.text', 'Completed') .should('have.attr', 'href')
.parent() .and('contain', 'https://sepolia.etherscan.io/tx/');
.within(() => { });
cy.get(tableAssetSymbol).should('have.text', usdcSymbol); });
cy.get(tableAmount).should('have.text', '120.00');
cy.get(tableReceiverAddress)
.find('a')
.should('have.attr', 'href')
.and('contain', 'https://sepolia.etherscan.io/address/');
cy.get(tableWithdrawnTimeStamp).should('not.be.empty');
cy.get(tableTxHash)
.find('a')
.should('have.attr', 'href')
.and('contain', 'https://sepolia.etherscan.io/tx/');
});
}
);
it('Able to withdraw asset: -eth wallet not connected', function () { it('Able to withdraw asset: -eth wallet not connected', function () {
const ethWalletAddress = Cypress.env('ethWalletPublicKey'); const ethWalletAddress = Cypress.env('ethWalletPublicKey');
@@ -172,10 +159,7 @@ context(
// fill in withdrawal form // fill in withdrawal form
cy.getByTestId(withdraw).should('be.visible').click(); cy.getByTestId(withdraw).should('be.visible').click();
cy.getByTestId(withdrawalForm, txTimeout).within(() => { cy.getByTestId(withdrawalForm, txTimeout).within(() => {
cy.getByTestId('select-asset').click(); cy.get('select').select(usdtSelectValue, { force: true });
cy.get('select')
.select(usdtSelectValue, { force: true })
.should('have.value', usdtSelectValue);
cy.getByTestId(ethAddressInput).should('be.empty'); cy.getByTestId(ethAddressInput).should('be.empty');
cy.getByTestId(amountInput).click().type('110'); cy.getByTestId(amountInput).click().type('110');
cy.getByTestId(submitWithdrawalButton).click(); cy.getByTestId(submitWithdrawalButton).click();
@@ -235,10 +219,7 @@ context(
it('Should be able to see withdrawal details from toast', function () { it('Should be able to see withdrawal details from toast', function () {
cy.getByTestId(withdraw).click(); cy.getByTestId(withdraw).click();
cy.getByTestId(withdrawalForm, txTimeout).within(() => { cy.getByTestId(withdrawalForm, txTimeout).within(() => {
cy.getByTestId('select-asset').click(); cy.get('select').select(usdtSelectValue, { force: true });
cy.get('select')
.select(usdtSelectValue, { force: true })
.should('have.value', usdtSelectValue);
cy.getByTestId(balanceAvailable, txTimeout).should('exist'); cy.getByTestId(balanceAvailable, txTimeout).should('exist');
cy.getByTestId(withdrawalThreshold).should( cy.getByTestId(withdrawalThreshold).should(
'have.text', 'have.text',
@@ -293,10 +274,7 @@ context(
cy.connectPublicKey(vegaWalletPubKey); cy.connectPublicKey(vegaWalletPubKey);
cy.getByTestId(withdraw).should('be.visible').click(); cy.getByTestId(withdraw).should('be.visible').click();
cy.getByTestId(withdrawalForm, txTimeout).within(() => { cy.getByTestId(withdrawalForm, txTimeout).within(() => {
cy.getByTestId('select-asset').click(); cy.get('select').select(usdtSelectValue, { force: true });
cy.get('select')
.select(usdtSelectValue, { force: true })
.should('have.value', usdtSelectValue);
cy.getByTestId(balanceAvailable, txTimeout).should('exist'); cy.getByTestId(balanceAvailable, txTimeout).should('exist');
cy.getByTestId(amountInput).click().type('100'); cy.getByTestId(amountInput).click().type('100');
cy.pause(); cy.pause();
@@ -10,13 +10,9 @@ import {
} from '../../support/governance.functions'; } from '../../support/governance.functions';
import { mockNetworkUpgradeProposal } from '../../support/proposal.functions'; import { mockNetworkUpgradeProposal } from '../../support/proposal.functions';
const proposalDocsLink = 'proposal-docs-link'; const proposalDocumentationLink = '[data-testid="proposal-documentation-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 networkUpgradeProposalListItem = 'protocol-upgrade-proposals-list-item'; const connectToVegaWalletButton = '[data-testid="connect-to-vega-wallet-btn"]';
const closedProposals = 'closed-proposals';
const closedProposalToggle = 'closed-proposals-toggle-networkUpgrades';
context( context(
'Governance Page - verify elements on page', 'Governance Page - verify elements on page',
@@ -45,7 +41,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.getByTestId(proposalDocumentationLink) cy.get(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')
@@ -68,7 +64,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(proposalDocsLink) cy.getByTestId('proposal-docs-link')
.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');
@@ -77,7 +73,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(proposalDocsLink) cy.getByTestId('proposal-docs-link')
.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');
@@ -86,7 +82,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(proposalDocsLink) cy.getByTestId('proposal-docs-link')
.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');
@@ -95,7 +91,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(proposalDocsLink) cy.getByTestId('proposal-docs-link')
.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');
@@ -104,7 +100,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(proposalDocsLink) cy.getByTestId('proposal-docs-link')
.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');
@@ -113,7 +109,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(proposalDocsLink) cy.getByTestId('proposal-docs-link')
.find('a') .find('a')
.should('have.attr', 'href') .should('have.attr', 'href')
.and('contain', '/tutorials/proposals/freeform-proposal'); .and('contain', '/tutorials/proposals/freeform-proposal');
@@ -121,7 +117,7 @@ context(
it('should be able to see a connect wallet button - if vega wallet disconnected and user is submitting new proposal', function () { 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.getByTestId(connectToVegaWalletButton) cy.get(connectToVegaWalletButton)
.should('be.visible') .should('be.visible')
.and('have.text', 'Connect Vega wallet'); .and('have.text', 'Connect Vega wallet');
}); });
@@ -130,7 +126,7 @@ context(
mockNetworkUpgradeProposal(); mockNetworkUpgradeProposal();
cy.visit('/'); cy.visit('/');
cy.getByTestId('home-proposal-list').within(() => { cy.getByTestId('home-proposal-list').within(() => {
cy.getByTestId(networkUpgradeProposalListItem).should('exist'); cy.getByTestId('protocol-upgrade-proposals-list-item').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'
@@ -144,7 +140,11 @@ context(
cy.getByTestId('open-proposals').within(() => { cy.getByTestId('open-proposals').within(() => {
cy.get('li') cy.get('li')
.eq(0) .eq(0)
.should('have.attr', 'data-testid', networkUpgradeProposalListItem) .should(
'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,19 +165,18 @@ context(
); );
}); });
}); });
cy.getByTestId(closedProposals).within(() => { cy.getByTestId('closed-proposals').within(() => {
cy.getByTestId(networkUpgradeProposalListItem).should('not.exist'); cy.getByTestId('protocol-upgrade-proposals-list-item').should(
}); 'have.length',
cy.getByTestId(closedProposalToggle).click(); 1
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(networkUpgradeProposalListItem) cy.getByTestId('protocol-upgrade-proposals-list-item')
.first() .first()
.find('[data-testid="view-proposal-btn"]') .find('[data-testid="view-proposal-btn"]')
.click(); .click();
@@ -214,18 +213,5 @@ 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-functions'; import { vegaWalletFaucetAssetsWithoutCheck } from '../../support/wallet-vega.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 = 'epoch-reward-view-toggle-total'; const viewToggle = '[data-testid="epoch-reward-view-toggle-total"]';
const warning = 'callout'; const warning = '[data-testid="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.getByTestId(warning) cy.get(warning)
.should('be.visible') .should('be.visible')
.and( .and(
'have.text', 'have.text',
@@ -36,7 +36,7 @@ context(
}); });
it('should have toggle for seeing total vs individual rewards', function () { it('should have toggle for seeing total vs individual rewards', function () {
cy.getByTestId(viewToggle).should('be.visible'); cy.get(viewToggle).should('be.visible');
}); });
// Skipping due to bug #3471 causing flaky failuress // Skipping due to bug #3471 causing flaky failuress
@@ -1,17 +1,18 @@
import { navigateTo, navigation } from '../../support/common.functions'; import { navigateTo, navigation } from '../../support/common.functions';
const tokenDetailsTable = '.token-details'; const tokenDetailsTable = '.token-details';
const address = 'token-address'; const address = '[data-testid="token-address"]';
const contract = 'token-contract'; const contract = '[data-testid="token-contract"]';
const totalSupply = 'total-supply'; const totalSupply = '[data-testid="total-supply"]';
const circulatingSupply = 'circulating-supply'; const circulatingSupply = '[data-testid="circulating-supply"]';
const staked = 'staked'; const staked = '[data-testid="staked"]';
const tranchesLink = 'tranches-link'; const tranchesLink = '[data-testid="tranches-link"]';
const redeemBtn = 'check-vesting-page-btn'; const redeemBtn = '[data-testid="check-vesting-page-btn"]';
const getVegaWalletLink = 'get-vega-wallet-link'; const getVegaWalletLink = '[data-testid="get-vega-wallet-link"]';
const associateVegaLink = 'associate-vega-tokens-link-on-homepage'; const associateVegaLink =
const stakingBtn = 'staking-button-on-homepage'; '[data-testid="associate-vega-tokens-link-on-homepage"]';
const governanceBtn = 'governance-button-on-homepage'; const stakingBtn = '[data-testid="staking-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');
@@ -24,7 +25,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.getByTestId(address) cy.get(address)
.should('be.visible') .should('be.visible')
.invoke('text') .invoke('text')
.should('be.equal', vegaTokenAddress); .should('be.equal', vegaTokenAddress);
@@ -33,7 +34,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.getByTestId(contract) cy.get(contract)
.should('be.visible') .should('be.visible')
.invoke('text') .invoke('text')
.should('be.equal', vegaTokenContractAddress); .should('be.equal', vegaTokenContractAddress);
@@ -41,56 +42,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.getByTestId(totalSupply).should('be.visible'); cy.get(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.getByTestId(circulatingSupply).should('be.visible'); cy.get(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.getByTestId(staked).should('be.visible'); cy.get(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.getByTestId(tranchesLink) cy.get(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.getByTestId(redeemBtn) cy.get(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.getByTestId(getVegaWalletLink) cy.get(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.getByTestId(associateVegaLink) cy.get(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.getByTestId(stakingBtn) cy.get(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.getByTestId(governanceBtn) cy.get(governanceBtn)
.should('be.visible') .should('be.visible')
.parent() .parent()
.should('have.attr', 'href') .should('have.attr', 'href')
@@ -1,37 +1,34 @@
/// <reference types="cypress" /> /// <reference types="cypress" />
import { aliasGQLQuery } from '@vegaprotocol/cypress';
import { import {
navigation, navigation,
verifyPageHeader, verifyPageHeader,
verifyTabHighlighted, verifyTabHighlighted,
} from '../../support/common.functions'; } from '../../support/common.functions';
import { import { clickOnValidatorFromList } from '../../support/staking.functions';
clickOnValidatorFromList,
waitForBeginningOfEpoch,
} from '../../support/staking.functions';
import { previousEpochData } from '../../fixtures/mocks/previous-epoch';
const guideLink = 'staking-guide-link'; const guideLink = '[data-testid="staking-guide-link"]';
const validatorTitle = 'validator-node-title'; const validatorTitle = '[data-testid="validator-node-title"]';
const validatorId = 'validator-id'; const validatorId = '[data-testid="validator-id"]';
const validatorPubKey = 'validator-public-key'; const validatorPubKey = '[data-testid="validator-public-key"]';
const ethAddressLink = 'link'; const ethAddressLink = '[data-testid="link"]';
const validatorStatus = 'validator-status'; const validatorStatus = '[data-testid="validator-status"]';
const totalStake = 'total-stake'; const totalStake = '[data-testid="total-stake"]';
const pendingStake = 'pending-stake'; const pendingStake = '[data-testid="pending-stake"]';
const stakedByOperator = 'staked-by-operator'; const stakedByOperator = '[data-testid="staked-by-operator"]';
const stakedByDelegates = 'staked-by-delegates'; const stakedByDelegates = '[data-testid="staked-by-delegates"]';
const stakeShare = 'stake-percentage'; const stakeShare = '[data-testid="stake-percentage"]';
const stakedByOperatorToolTip = 'staked-operator-tooltip'; const stakedByOperatorToolTip = '[data-testid="staked-operator-tooltip"]';
const stakedByDelegatesToolTip = 'staked-delegates-tooltip'; const stakedByDelegatesToolTip = '[data-testid="staked-delegates-tooltip"]';
const totalStakedToolTip = 'total-staked-tooltip'; const totalStakedToolTip = '[data-testid="total-staked-tooltip"]';
const unnormalisedVotingPowerToolTip = 'unnormalised-voting-power-tooltip'; const unnormalisedVotingPowerToolTip =
const normalisedVotingPowerToolTip = 'normalised-voting-power-tooltip'; '[data-testid="unnormalised-voting-power-tooltip"]';
const performancePenaltyToolTip = 'performance-penalty-tooltip'; const normalisedVotingPowerToolTip =
const overstakedPenaltyToolTip = 'overstaked-penalty-tooltip'; '[data-testid="normalised-voting-power-tooltip"]';
const multisigPenaltyToolTip = 'multisig-error-tooltip'; const performancePenaltyToolTip = '[data-testid="performance-penalty-tooltip"]';
const epochCountDown = 'epoch-countdown'; const overstakedPenaltyToolTip = '[data-testid="overstaked-penalty-tooltip"]';
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 () {
@@ -40,232 +37,206 @@ context('Validators Page - verify elements on page', function () {
}); });
describe('with wallets disconnected', { tags: '@smoke' }, function () { describe('with wallets disconnected', { tags: '@smoke' }, function () {
it('Should have validators tab highlighted', function () { describe('description section', function () {
verifyTabHighlighted(navigation.validators); it('Should have validators tab highlighted', function () {
}); verifyTabHighlighted(navigation.validators);
});
it('Should have validators ON VEGA header visible', function () { it('Should have validators ON VEGA header visible', function () {
verifyPageHeader('Validators'); verifyPageHeader('Validators');
}); });
it('Should have Staking Guide link visible', function () { it('Should have Staking Guide link visible', function () {
// 1002-STKE-003 // 2001-STKE-003
cy.getByTestId(guideLink) cy.get(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(
'have.attr', 'have.attr',
'href', 'href',
'https://docs.vega.xyz/mainnet/concepts/vega-chain/#staking-on-vega' 'https://docs.vega.xyz/mainnet/concepts/vega-chain/#staking-on-vega'
); );
});
}); });
describe(
'Should be able to see validator list from the staking page',
{ tags: '@regression' },
function () {
// 2001-STKE-050
it('Should be able to see validator names', function () {
cy.get('[col-id="validator"] > div > span')
.should('have.length.at.least', 1)
.each(($name) => {
cy.wrap($name).should('not.be.empty');
});
});
// 1002-STKE-032 it('Should be able to see validator stake', function () {
it('Should have button to connect vega wallet in validator page', function () { cy.getByTestId('total-stake')
clickOnValidatorFromList(0); .should('have.length.at.least', 1)
cy.getByTestId('connect-to-vega-wallet-btn').should('be.visible'); .each(($stake) => {
cy.visit('/validators'); cy.wrap($stake).should('not.be.empty');
}); });
});
it('Should be able to see validator stake tooltip', function () {
cy.getByTestId('total-stake').first().realHover();
cy.get(stakedByOperatorToolTip)
.invoke('text')
.should('contain', 'Staked by operator: 3,000.00');
cy.get(stakedByDelegatesToolTip)
.invoke('text')
.should('contain', 'Staked by delegates: 0.00');
cy.get(totalStakedToolTip)
.invoke('text')
.should('contain', 'Total stake: 3,000.00');
});
it('Should be able to see validator normalised voting power', function () {
cy.getByTestId('normalised-voting-power')
.should('have.length.at.least', 1)
.each(($vPower) => {
cy.wrap($vPower).should('not.be.empty');
});
});
it('Should be able to see validator normalised voting power tooltip', function () {
cy.getByTestId('normalised-voting-power').first().realHover();
cy.get(unnormalisedVotingPowerToolTip)
.invoke('text')
.should('contain', 'Unnormalised voting power: 20.00%');
cy.get(normalisedVotingPowerToolTip)
.invoke('text')
.should('contain', 'Normalised voting power: 50.00%');
});
// 2002-SINC-018
it('Should be able to see validator total penalties', function () {
cy.getByTestId('total-penalty')
.should('have.length.at.least', 1)
.each(($penalties) => {
cy.wrap($penalties).should('contain.text', '0%');
});
});
it('Should be able to see validator penalties tooltip', function () {
cy.getByTestId('total-penalty').realHover();
cy.get(performancePenaltyToolTip)
.invoke('text')
.should('contain', 'Performance penalty: 0.00%');
cy.get(overstakedPenaltyToolTip)
.invoke('text')
.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 () {
cy.getByTestId('total-pending-stake')
.should('have.length.at.least', 1)
.each(($pendingStake) => {
cy.wrap($pendingStake).should('contain.text', '0.00');
});
});
}
);
// 2001-STKE-050
describe(
'Should be able to see static information about a validator',
{ tags: '@smoke' },
function () {
before('connect wallets and click on validator', function () {
cy.connectVegaWallet();
clickOnValidatorFromList(0);
});
// 2001-STKE-006
it('Should be able to see validator name', function () {
cy.get(validatorTitle).should('not.be.empty');
});
// 2001-STKE-007
it('Should be able to see validator id', function () {
cy.get(validatorId).should('not.be.empty');
});
// 2001-STKE-008
it('Should be able to see validator public key', function () {
cy.get(validatorPubKey).should('not.be.empty');
});
// 2001-STKE-010
it('Should be able to see Ethereum address', function () {
cy.get(ethAddressLink)
.should('not.be.empty')
.and('have.attr', 'href');
});
// TODO validators missing url for more information about them 2001-STKE-09
it('Should be able to see validator status', function () {
cy.get(validatorStatus).should('have.text', 'Consensus');
});
// 2001-STKE-012
it('Should be able to see total stake', function () {
cy.get(totalStake).invoke('text').should('match', stakeNumberRegex);
});
it('Should be able to see pending stake', function () {
cy.get(pendingStake).invoke('text').should('match', stakeNumberRegex);
});
it('Should be able to see staked by operator', function () {
cy.get(stakedByOperator)
.invoke('text')
.should('match', stakeNumberRegex);
});
it('Should be able to see staked by delegates', function () {
cy.get(stakedByDelegates)
.invoke('text')
.should('match', stakeNumberRegex);
});
// 2001-STKE-051
it('Should be able to see stake share in percentage', function () {
cy.get(stakeShare)
.invoke('text')
.then(($stakePercentage) => {
// The pattern must start at a word boundary (\b).
// The pattern cannot be immediately preceded by a dot ((?<!\.)).
// The pattern can be one of the following:
// A percentage value of zero (0%), or
// A non-zero percentage value that can be:
// A single digit (\d) between 0 and 9, or
// A two-digit number between 0 and 99 (\d{1,2}), or
// The number 100.
// The pattern can optionally include a decimal point and one or more digits after the decimal point ((?:(?<!100)\.\d+)?). However, if the number is 100, it cannot have a decimal point.
// The pattern must end with a percentage sign (%).
cy.wrap($stakePercentage).should(
'match',
/\b(?<!\.)(?:0+(?:\.0+)?%|(?:\d|\d{1,2}|100)(?:(?<!100)\.\d+)?)%/
);
});
});
// 2001-STKE-011 2002-SINC-001 2002-SINC-002
it('Should be able to see epoch information', function () {
const epochTitle = 'h3';
const nextEpochInfo = 'p';
cy.get(epochCountDown).within(() => {
cy.get(epochTitle).should('not.be.empty');
cy.get(nextEpochInfo).should('contain.text', 'Next epoch');
});
});
}
);
}); });
// 1002-STKE-020 1002-STKE-021 1002-STKE-022 1002-STKE-023 1002-STKE-024
describe(
'Should be able to see validator list from the staking page',
{ tags: '@regression' },
function () {
// 1002-STKE-050
it('Should be able to see validator names', function () {
cy.get('[col-id="validator"] > div > span')
.should('have.length.at.least', 1)
.each(($name) => {
cy.wrap($name).should('not.be.empty');
});
});
it('Should be able to see validator stake', function () {
cy.getByTestId('total-stake')
.should('have.length.at.least', 1)
.each(($stake) => {
cy.wrap($stake).should('not.be.empty');
});
});
it('Should be able to see validator stake tooltip', function () {
waitForBeginningOfEpoch();
cy.getByTestId('total-stake').first().realHover();
cy.getByTestId(stakedByOperatorToolTip)
.invoke('text')
.should('contain', 'Staked by operator: 3,000.00');
cy.getByTestId(stakedByDelegatesToolTip)
.invoke('text')
.should('contain', 'Staked by delegates: 0.00');
cy.getByTestId(totalStakedToolTip)
.invoke('text')
.should('contain', 'Total stake: 3,000.00');
});
it('Should be able to see validator normalised voting power', function () {
cy.getByTestId('normalised-voting-power')
.should('have.length.at.least', 1)
.each(($vPower) => {
cy.wrap($vPower).should('not.be.empty');
});
});
it('Should be able to see validator normalised voting power tooltip', function () {
waitForBeginningOfEpoch();
cy.getByTestId('normalised-voting-power').first().realHover();
cy.getByTestId(unnormalisedVotingPowerToolTip)
.invoke('text')
.should('contain', 'Unnormalised voting power: 20.00%');
cy.getByTestId(normalisedVotingPowerToolTip)
.invoke('text')
.should('contain', 'Normalised voting power: 50.00%');
});
// 2002-SINC-018
it('Should be able to see validator total penalties', function () {
cy.getByTestId('total-penalty')
.should('have.length.at.least', 1)
.each(($penalties) => {
cy.wrap($penalties).should('contain.text', '0%');
});
});
it('Should be able to see validator penalties tooltip', function () {
waitForBeginningOfEpoch();
cy.getByTestId('total-penalty').realHover();
cy.getByTestId(performancePenaltyToolTip)
.invoke('text')
.should('contain', 'Performance penalty: 0.00%');
cy.getByTestId(overstakedPenaltyToolTip)
.invoke('text')
.should('contain', 'Overstaked penalty: 60.00%'); // value not asserted due to #2886
});
it('Should be able to see validator pending stake', function () {
cy.getByTestId('total-pending-stake')
.should('have.length.at.least', 1)
.each(($pendingStake) => {
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%');
});
}
);
// 1002-STKE-050
describe(
'Should be able to see static information about a validator',
{ tags: '@smoke' },
function () {
before('connect wallets and click on validator', function () {
cy.connectVegaWallet();
clickOnValidatorFromList(0);
});
// 1002-STKE-006
it('Should be able to see validator name', function () {
cy.getByTestId(validatorTitle).should('not.be.empty');
});
// 1002-STKE-007
it('Should be able to see validator id', function () {
cy.getByTestId(validatorId).should('not.be.empty');
});
// 1002-STKE-008
it('Should be able to see validator public key', function () {
cy.getByTestId(validatorPubKey).should('not.be.empty');
});
// 1002-STKE-010
it('Should be able to see Ethereum address', function () {
cy.getByTestId(ethAddressLink)
.should('not.be.empty')
.and('have.attr', 'href');
});
// TODO validators missing url for more information about them 1002-STKE-09
it('Should be able to see validator status', function () {
cy.getByTestId(validatorStatus).should('have.text', 'Consensus');
});
// 1002-STKE-012
it('Should be able to see total stake', function () {
cy.getByTestId(totalStake)
.invoke('text')
.should('match', stakeNumberRegex);
});
it('Should be able to see pending stake', function () {
cy.getByTestId(pendingStake)
.invoke('text')
.should('match', stakeNumberRegex);
});
it('Should be able to see staked by operator', function () {
cy.getByTestId(stakedByOperator)
.invoke('text')
.should('match', stakeNumberRegex);
});
it('Should be able to see staked by delegates', function () {
cy.getByTestId(stakedByDelegates)
.invoke('text')
.should('match', stakeNumberRegex);
});
// 1002-STKE-051
it('Should be able to see stake share in percentage', function () {
cy.getByTestId(stakeShare)
.invoke('text')
.then(($stakePercentage) => {
// The pattern must start at a word boundary (\b).
// The pattern cannot be immediately preceded by a dot ((?<!\.)).
// The pattern can be one of the following:
// A percentage value of zero (0%), or
// A non-zero percentage value that can be:
// A single digit (\d) between 0 and 9, or
// A two-digit number between 0 and 99 (\d{1,2}), or
// The number 100.
// The pattern can optionally include a decimal point and one or more digits after the decimal point ((?:(?<!100)\.\d+)?). However, if the number is 100, it cannot have a decimal point.
// The pattern must end with a percentage sign (%).
cy.wrap($stakePercentage).should(
'match',
/\b(?<!\.)(?:0+(?:\.0+)?%|(?:\d|\d{1,2}|100)(?:(?<!100)\.\d+)?)%/
);
});
});
// 1002-STKE-011 2002-SINC-001 2002-SINC-002
it('Should be able to see epoch information', function () {
const epochTitle = 'h3';
const nextEpochInfo = 'p';
cy.getByTestId(epochCountDown).within(() => {
cy.get(epochTitle).should('not.be.empty');
cy.get(nextEpochInfo).should('contain.text', 'Next epoch');
});
});
}
);
}); });
@@ -6,7 +6,7 @@ import {
} from '../../support/common.functions'; } from '../../support/common.functions';
import { ethereumWalletConnect } from '../../support/wallet-eth.functions'; import { ethereumWalletConnect } from '../../support/wallet-eth.functions';
const connectButton = 'connect-to-eth-btn'; const connectButton = '[data-testid="connect-to-eth-btn"]';
const lockedTokensInVestingContract = '6,499,972.30'; 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.getByTestId(connectButton) cy.get(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 = 'web3-connector-list'; const connectorList = '[data-testid="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 = 'disconnect-from-eth-wallet-button'; const disconnect = '[data-testid="disconnect-from-eth-wallet-button"]';
const accountNo = 'ethereum-account-truncated'; const accountNo = '[data-testid="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 = 'dialog-title'; const dialogHeader = '[data-testid="dialog-title"]';
const dialogCloseBtn = 'dialog-close'; const dialogCloseBtn = '[data-testid="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.getByTestId(dialogHeader) cy.get(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.getByTestId(connectorList).within(() => { cy.get(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.getByTestId(dialogCloseBtn).click(); cy.get(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.getByTestId(accountNo) cy.get(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.getByTestId(disconnect) cy.get(disconnect)
.should('be.visible') .should('be.visible')
.and('have.text', 'Disconnect'); .and('have.text', 'Disconnect');
}); });
@@ -1,34 +1,32 @@
import { truncateByChars } from '@vegaprotocol/utils'; import { truncateByChars } from '@vegaprotocol/utils';
import { waitForSpinner } from '../../support/common.functions'; import { waitForSpinner } from '../../support/common.functions';
import { import { vegaWalletTeardown } from '../../support/wallet-teardown.functions';
vegaWalletFaucetAssetsWithoutCheck, import { vegaWalletFaucetAssetsWithoutCheck } from '../../support/wallet-vega.functions';
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 = 'connect-vega-wallet'; const connectButton = '[data-testid="connect-vega-wallet"]';
const getVegaLink = 'link'; const getVegaLink = '[data-testid="link"]';
const dialog = '[role="dialog"]:visible'; const dialog = '[role="dialog"]:visible';
const dialogHeader = 'dialog-title'; const dialogHeader = '[data-testid="dialog-title"]';
const walletDialogHeader = 'wallet-dialog-title'; const walletDialogHeader = '[data-testid="wallet-dialog-title"]';
const connectorsList = 'connectors-list'; const connectorsList = '[data-testid="connectors-list"]';
const dialogCloseBtn = 'dialog-close'; const dialogCloseBtn = '[data-testid="dialog-close"]';
const restConnectorForm = 'rest-connector-form'; const restConnectorForm = '[data-testid="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 = 'vega-account-truncated'; const accountNo = '[data-testid="vega-account-truncated"]';
const currencyTitle = 'currency-title'; const currencyTitle = '[data-testid="currency-title"]';
const currencyValue = 'currency-value'; const currencyValue = '[data-testid="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 = 'manage-vega-wallet'; const manageLink = '[data-testid="manage-vega-wallet"]';
const dialogVegaKey = 'vega-public-key-full'; const dialogVegaKey = '[data-testid="vega-public-key-full"]';
const dialogDisconnectBtn = 'disconnect'; const dialogDisconnectBtn = '[data-testid="disconnect"]';
const copyPublicKeyBtn = 'copy-vega-public-key'; const copyPublicKeyBtn = '[data-testid="copy-vega-public-key"]';
const vegaWalletCurrencyTitle = 'currency-title'; const vegaWalletCurrencyTitle = '[data-testid="currency-title"]';
const vegaWalletPublicKey = Cypress.env('vegaWalletPublicKey'); const vegaWalletPublicKey = Cypress.env('vegaWalletPublicKey');
const txTimeout = Cypress.env('txTimeout'); const txTimeout = Cypress.env('txTimeout');
@@ -47,10 +45,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.getByTestId(connectButton) cy.get(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.getByTestId(getVegaLink) cy.get(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');
@@ -61,20 +59,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.getByTestId(connectButton).click(); cy.get(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.getByTestId(walletDialogHeader) cy.get(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.getByTestId(connectorsList).within(() => { cy.get(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');
@@ -86,33 +84,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.getByTestId(dialogCloseBtn).should('be.visible'); cy.get(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.getByTestId(connectorsList).within(() => { cy.get(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.getByTestId(restConnectorForm).within(() => { cy.get(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.getByTestId(restConnectorForm).within(() => { cy.get(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.getByTestId(restConnectorForm).within(() => { cy.get(restConnectorForm).within(() => {
cy.get(restConnectBtn) cy.get(restConnectBtn)
.should('be.visible') .should('be.visible')
.and('have.text', 'Connect'); .and('have.text', 'Connect');
@@ -121,12 +119,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.getByTestId(dialogCloseBtn).should('be.visible'); cy.get(dialogCloseBtn).should('be.visible');
}); });
}); });
after('close dialog', function () { after('close dialog', function () {
cy.getByTestId(dialogCloseBtn).click().should('not.exist'); cy.get(dialogCloseBtn).click().should('not.exist');
}); });
}); });
@@ -152,7 +150,7 @@ context(
{ tags: '@smoke' }, { tags: '@smoke' },
function () { function () {
cy.get(walletContainer).within(() => { cy.get(walletContainer).within(() => {
cy.getByTestId(accountNo) cy.get(accountNo)
.should('be.visible') .should('be.visible')
.and('have.text', Cypress.env('vegaWalletPublicKeyShort')); .and('have.text', Cypress.env('vegaWalletPublicKeyShort'));
}); });
@@ -161,7 +159,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.getByTestId(currencyTitle) cy.get(currencyTitle)
.should('be.visible') .should('be.visible')
.and('contain.text', `VEGAAssociated`); .and('contain.text', `VEGAAssociated`);
}); });
@@ -172,7 +170,7 @@ context(
{ tags: '@smoke' }, { tags: '@smoke' },
function () { function () {
cy.get(walletContainer).within(() => { cy.get(walletContainer).within(() => {
cy.getByTestId(currencyValue) cy.get(currencyValue)
.should('be.visible') .should('be.visible')
.and('contain.text', `0.00`); .and('contain.text', `0.00`);
}); });
@@ -204,23 +202,21 @@ context(
it('should have Manage link visible', function () { it('should have Manage link visible', function () {
cy.get(walletContainer).within(() => { cy.get(walletContainer).within(() => {
cy.getByTestId(manageLink) cy.get(manageLink).should('be.visible').and('have.text', 'Manage');
.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.getByTestId(manageLink).click(); cy.get(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.getByTestId(dialogHeader) cy.get(dialogHeader)
.should('be.visible') .should('be.visible')
.and('have.text', 'SELECT A VEGA KEY'); .and('have.text', 'SELECT A VEGA KEY');
}); });
@@ -240,7 +236,7 @@ context(
'contain.text', 'contain.text',
truncatedPubKey1 truncatedPubKey1
); );
cy.getByTestId(dialogVegaKey) cy.get(dialogVegaKey)
.should('be.visible') .should('be.visible')
.and('contain.text', truncatedPubKey1) .and('contain.text', truncatedPubKey1)
.and('contain.text', truncatedPubKey2); .and('contain.text', truncatedPubKey2);
@@ -250,7 +246,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.getByTestId(copyPublicKeyBtn) cy.get(copyPublicKeyBtn)
.should('be.visible') .should('be.visible')
.and('contain.text', 'Copy'); .and('contain.text', 'Copy');
}); });
@@ -258,13 +254,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.getByTestId(dialogCloseBtn).should('be.visible'); cy.get(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.getByTestId(dialogDisconnectBtn) cy.get(dialogDisconnectBtn)
.should('be.visible') .should('be.visible')
.and('have.text', 'Disconnect all keys'); .and('have.text', 'Disconnect all keys');
}); });
@@ -273,10 +269,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.getByTestId(dialogDisconnectBtn).click(); cy.get(dialogDisconnectBtn).click();
}); });
cy.get(walletContainer).within(() => { cy.get(walletContainer).within(() => {
cy.getByTestId(connectButton).should('be.visible'); // 0002-WCON-023 cy.get(connectButton).should('be.visible'); // 0002-WCON-023
}); });
}); });
}); });
@@ -289,28 +285,28 @@ context(
name: 'USDC (fake)', name: 'USDC (fake)',
symbol: 'fUSDC', symbol: 'fUSDC',
amount: '1000000', amount: '1000000',
expectedAmount: 10.0, expectedAmount: '10.00',
}, },
{ {
id: '8566db7257222b5b7ef2886394ad28b938b28680a54a169bbc795027b89d6665', id: '8566db7257222b5b7ef2886394ad28b938b28680a54a169bbc795027b89d6665',
name: 'DAI (fake)', name: 'DAI (fake)',
symbol: 'fDAI', symbol: 'fDAI',
amount: '200000', amount: '200000',
expectedAmount: 2.0, expectedAmount: '2.00',
}, },
{ {
id: '73174a6fb1d5802ba0ac7bd7ab79e0a3a4837b262de0a4e80815a55442692bd0', id: '73174a6fb1d5802ba0ac7bd7ab79e0a3a4837b262de0a4e80815a55442692bd0',
name: 'BTC (fake)', name: 'BTC (fake)',
symbol: 'fBTC', symbol: 'fBTC',
amount: '600000', amount: '600000',
expectedAmount: 6.0, expectedAmount: '6.00',
}, },
{ {
id: 'e02d4c15d790d1d2dffaf2dcd1cf06a1fe656656cf4ed18c8ce99f9e83643567', id: 'e02d4c15d790d1d2dffaf2dcd1cf06a1fe656656cf4ed18c8ce99f9e83643567',
name: 'EURO (fake)', name: 'EURO (fake)',
symbol: 'fEURO', symbol: 'fEURO',
amount: '800000', amount: '800000',
expectedAmount: 8.0, expectedAmount: '8.00',
}, },
]; ];
@@ -332,20 +328,19 @@ 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.getByTestId(vegaWalletCurrencyTitle) cy.get(vegaWalletCurrencyTitle)
.contains(name, txTimeout) .contains(name, txTimeout)
.should('be.visible'); .should('be.visible');
cy.getByTestId(vegaWalletCurrencyTitle) cy.get(vegaWalletCurrencyTitle)
.contains(name) .contains(name)
.parent() .parent()
.siblings() .siblings()
.should((elementAmount) => { .invoke('text')
const displayedAmount = parseFloat(elementAmount.text()); .then(parseFloat)
expect(displayedAmount).be.gte(expectedAmount); .should('be.gte', parseFloat(expectedAmount));
});
cy.getByTestId(vegaWalletCurrencyTitle) cy.get(vegaWalletCurrencyTitle)
.contains(name) .contains(name)
.parent() .parent()
.contains(symbol); .contains(symbol);
@@ -5,6 +5,8 @@ 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' },
@@ -24,7 +26,7 @@ context(
}); });
it('should have connect Vega wallet button', function () { it('should have connect Vega wallet button', function () {
cy.getByTestId('connect-to-vega-wallet-btn') cy.get(connectToVegaBtn)
.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({ force: true }); cy.get(page).eq(0).click();
}); });
} }
} }

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