Compare commits

...
Author SHA1 Message Date
sam-keen 3528bd4c7c fix(4276): fixes settlement oracle not showing in governance, brings data request inline with trading 2023-08-10 16:01:53 +01:00
Maciek 1566fc3984 chore: fulfill spec to 4323 (#4520) 2023-08-10 09:20:26 +02:00
Ben 39962a0246 chore(trading): update console-test runners (#4513) 2023-08-09 14:18:24 +01:00
Sam KeenandJoe 8d31510d5e feat(governance,markets,ui-toolkit): highlight successor market changes (#4449)
Co-authored-by: Joe <joe@vega.xyz>
2023-08-09 14:05:37 +01:00
Bartłomiej Głownia 26d5a67604 feat(trading): use custom toast content for stop order related transactions (#4476) 2023-08-09 14:47:10 +02:00
Bartłomiej Głownia 9cc8f5a377 feat(trading): use instrument code instead of name in positions, fills and accounts breakdown tables (#4478) 2023-08-09 12:12:07 +02:00
Matthew Russell 2e11cf4dfa fix(trading): fix persitance of resizable grid top section (#4508) 2023-08-09 11:04:32 +01:00
Bartłomiej Głownia 9e4ba9f275 feat(trading): merge deal ticket stores (#4494) 2023-08-09 11:54:16 +02:00
m.ray aac0c25d09 fix(trading): positions tooltips update (#4504) 2023-08-08 13:27:17 +01:00
Art d20c2a08ae chore(specs): succession line specs (#4505) 2023-08-08 11:23:28 +01:00
daro-maj 4e6ba5fe3d test(trading): update cypress tests (#4503) 2023-08-08 10:36:10 +02:00
Art 276d1d7c7b feat(governance): view as button (#4501) 2023-08-08 09:50:19 +02:00
m.ray 4990c5808d fix(trading): order iceberg fills (#4502) 2023-08-07 16:07:24 +00:00
Art cdd91c24f2 feat(markets): succession line (#4491) 2023-08-07 11:46:03 +00:00
Sam Keen e3eb13ca72 fix(explorer): footer visual tweaks (#4499) 2023-08-07 11:54:46 +01:00
Matthew Russell 3f0ebbf33d fix(trading): dont render get started if connected (#4490) 2023-08-07 11:35:55 +01:00
Matthew Russell 026aa3964b chore(trading): update pennant (#4493) 2023-08-07 11:06:49 +01:00
daro-maj aa4c5a4a57 test(trading): update cypress tests (#4496) 2023-08-07 10:28:13 +02:00
Matthew Russell 0874314d2a chore: add specs (#4497) 2023-08-07 08:41:25 +01:00
Matthew Russell a70a7bcc8e fix(trading): dont show connect wallet message in deal ticket (#4482) 2023-08-04 11:09:43 +01:00
Matthew Russell 4461a6a497 fix(trading): remove hover state from disabled view as button (#4487) 2023-08-04 11:08:48 +01:00
Matthew Russell 1041864ad8 chore(trading): remove unnecessary forwarding of grid refs (#4486) 2023-08-04 11:08:28 +01:00
Matthew Russell 015e0188ec chore: enable iceberg orders 2023-08-03 12:38:51 +01:00
Matthew Russell 228236c152 chore(deal-ticket): type dropdown icon (#4468) 2023-08-03 12:15:21 +01:00
Art cd6e906dc6 feat(trading): view as button in sidebar (#4470) 2023-08-03 12:14:35 +01:00
Maciek 8a47a92dbd fix(trading): add info splashes on deposit and withdrawals list containers (#4479) 2023-08-03 12:01:42 +01:00
Matthew Russell af14c27df9 chore(trading): responsive welcome dialog content and content update (#4475) 2023-08-03 12:01:09 +01:00
Matthew Russell 38edcd88c0 chore(trading): fix alignment of expire (#4477) 2023-08-03 12:00:12 +01:00
m.ray a74cf02030 feat(trading): order details dialog (#4281) 2023-08-03 10:05:12 +00:00
Joe Tsang 6686755763 chore(governance): fix failing tests from nightly run (#4453) 2023-08-03 10:01:00 +01:00
Joe Tsang 79cf8163be chore(governance): add e2e test for successor market (#4471) 2023-08-03 09:54:51 +01:00
Matthew Russell 1688e26f4b feat(trading): add fairground texture background to logo (#4474) 2023-08-03 09:28:01 +01:00
Art 5e101a8eaf feat(trading): deposit page (#4472) 2023-08-02 19:06:49 +02:00
Matthew Russell 65071f57d0 fix(trading,governance): eager connection for browser wallet (#4444) 2023-08-02 17:16:09 +01:00
daro-maj b87c981a90 test(trading): fix for key-to-key test (#4469) 2023-08-02 18:11:31 +02:00
Joe TsangandMatthew Russell b509e5b347 test(explorer): e2e proposal test (#4379)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
2023-08-02 16:37:55 +01:00
Maciek bb9fce4831 feat(trading): 4322 get started steps (#4440) 2023-08-02 15:34:04 +00:00
Matthew Russell 918825aca8 chore: adjust spacing and text size of long/short type selectors 2023-08-02 15:36:39 +01:00
Matthew Russell 74bf183fad feat(trading): 3114 pane context (#4442) 2023-08-02 15:29:41 +01:00
m.ray 674baa1ee3 fix(trading): iceberg order fills (#4460) 2023-08-02 16:55:49 +03:00
Matthew Russell d18a0c7e15 fix(trading): lp table forward ref breaking resize grid (#4447) 2023-08-02 13:59:05 +01:00
m.ray 76a6005b77 fix(trading): replace all markets with open markets (#4452) 2023-08-02 13:58:26 +01:00
JonRay15andMatthew Russell 79414614c2 chore(trading,governance,explorer): fix issue template (#4459)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
2023-08-02 12:52:51 +01:00
m.ray fde49e5446 fix(trading): order stopped misleading message (#4455) 2023-08-02 11:52:07 +01:00
JonRay15andMatthew Russell 45283161aa chore: create feature-epic issue template (#4456)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
2023-08-02 11:48:23 +01:00
Bartłomiej GłowniaandDariusz Majcherczyk 375d447fa8 feat(trading): add stop orders table and form (#4265)
Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com>
2023-08-02 11:28:33 +01:00
Mikołaj Młodzikowski 1dd97a2bce feat(ci): add develop docker image (#4458) 2023-08-02 10:58:17 +02:00
Edd d4a1a9b193 fix(explorer): add tests and fix transactions url generation (#4454) 2023-08-01 16:35:40 +01:00
Edd 376c5241a8 feat(explorer): improve transaction pagination (#4435) 2023-08-01 14:41:16 +00:00
Ciaran McGhie 8e6d8517cf feat(ui-toolkit,tailwindcss-config): healthbar auction level indicator (#4406) 2023-08-01 09:44:03 +01:00
Matthew Russell 5f9ec222c1 feat(trading): navigation (#4375) 2023-07-31 17:08:55 +01:00
Edd 8954c41c0a fix(governance): remove incorrect individual reward percentage (#4443) 2023-07-31 15:23:52 +00:00
Mikołaj Młodzikowski 4414ab4f47 feat(ci): wait for publish job to complete via github API 2023-07-31 15:04:34 +02:00
Mikołaj Młodzikowski d1d4bacc68 feat(ci): patch mainnet release 2023-07-31 13:09:00 +02:00
Joe Tsang f476688c7f chore(governance): fix failing proposal test (#4434) 2023-07-31 09:58:52 +01:00
Sam Keen 8845222700 feat(governance): remove max limit on associate function (#4427) 2023-07-28 11:12:29 +00:00
Radosław Szpiech e64c464091 chore(trading): move cumulative vol testid in orderbook (#4420) 2023-07-28 11:15:40 +02:00
Joe Tsang b2c2d0d7d6 test(governance): add e2e test and acs for proposal enhancements (#4421) 2023-07-28 09:34:39 +01:00
Joe Tsang 1fb61d313c chore(governance): add acs for network upgrade banner and estimate (#4402) 2023-07-28 09:34:30 +01:00
Mikołaj Młodzikowski ccf5ff632c feat(ci): setup new tests for console (#4400) 2023-07-28 07:32:07 +02:00
Maciek 54d6aaf56f feat(trading): add market successor proposal banner (#4401) 2023-07-27 14:54:00 +00:00
Mikołaj Młodzikowski 736b262947 fix(ci): publish of static dist (#4414) 2023-07-27 13:52:00 +02:00
Sam KeenandMatthew Russell 6a07127185 feat(governance): diff highlighter for update market proposals (#4260)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
2023-07-27 11:26:32 +01:00
Sam Keen 45f0ba7c0e fix(explorer): explorer validator page filtering (#4354) 2023-07-27 09:04:54 +00:00
John WalleyandMatthew Russell 6f4ea68778 chore(trading): upgrade pennant to v1.10.0 (#4331)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
2023-07-26 15:35:57 +01:00
Mikołaj Młodzikowski 90dda20891 feat(ci): migrate ci-cd trigger to python (#4330) 2023-07-26 16:08:57 +02:00
Art 278eb01c2f feat(governance): successor market proposal details (#4393) 2023-07-26 14:52:12 +02:00
Sam Keen c3157de146 fix(governance): removed icons test from proposal list item details (#4392) 2023-07-25 15:59:58 +00:00
d28db485c5 chore(trading): market relationship - parent market in proposals list (#4339)
Co-authored-by: asiaznik <artur@vegaprotocol.io>
Co-authored-by: Edd <edd@vega.xyz>
2023-07-25 15:32:40 +00:00
Maciek caa0076396 fix(deal-ticket): 4046 wrong margin requirement in order (#4378) 2023-07-25 14:52:32 +00:00
403 changed files with 14970 additions and 5503 deletions
+2 -1
View File
@@ -51,7 +51,8 @@
"ul": ["list"],
"ol": ["list"]
}
]
],
"no-console": ["error", { "allow": ["warn", "error"] }]
}
},
{
+28
View File
@@ -0,0 +1,28 @@
---
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: '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
@@ -7,7 +7,7 @@ on:
jobs:
after-release:
runs-on: ubuntu-22.04
timeout-minutes: 30
timeout-minutes: 45
steps:
- name: Checkout
uses: actions/checkout@v3
@@ -30,18 +30,20 @@ jobs:
username: ${{ secrets.DOCKERHUB_USERNAME }}
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
run: |
echo "Name: ${{ github.event.release.name }}"
echo "Description: ${{ github.event.release.body }}"
echo "Tag: ${{ github.event.release.tag_name }}"
commit="$(git rev-list -n 1 ${{ github.event.release.tag_name }})"
echo "Commit: $commit"
until docker pull vegaprotocol/trading:$commit; do
echo "Image not pushed yet, waiting 60 seconds"
sleep 60
done
docker run --rm vegaprotocol/trading:$commit cat /ipfs-hash > ipfs-hash
docker run --rm vegaprotocol/trading:mainnet 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
tar -xzf kubo.tgz
export PATH="$PATH:$PWD/kubo"
+25 -120
View File
@@ -81,6 +81,22 @@ jobs:
with:
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
run: yarn nx format:check
@@ -96,126 +112,6 @@ jobs:
- name: Build affected
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 "Current ref: ${{ github.ref }}"
echo ">>>> eof debug"
projects_array=()
preview_governance="not deployed"
preview_trading="not deployed"
preview_explorer="not deployed"
preview_tools="not deployed"
# parse if affected is any of three main applications, if none - use all of them
if echo "$affected" | grep -q governance; then
echo "Governance is affected"
projects_array+=("governance")
preview_governance=$(printf "https://%s.%s.vega.rocks" "governance" "$branch_slug")
fi
if echo "$affected" | grep -q trading; then
echo "Trading is affected"
projects_array+=("trading")
preview_trading=$(printf "https://%s.%s.vega.rocks" "trading" "$branch_slug")
fi
if echo "$affected" | grep -q explorer; then
echo "Explorer is affected"
projects_array+=("explorer")
preview_explorer=$(printf "https://%s.%s.vega.rocks" "explorer" "$branch_slug")
fi
if [[ ${#projects_array[@]} -eq 0 ]]; then
projects_array=("governance" "trading" "explorer")
preview_governance=$(printf "https://%s.%s.vega.rocks" "governance" "$branch_slug")
preview_trading=$(printf "https://%s.%s.vega.rocks" "trading" "$branch_slug")
preview_explorer=$(printf "https://%s.%s.vega.rocks" "explorer" "$branch_slug")
fi
# applications parsed before this loop are applicable for running e2e-tests
projects_e2e_array=()
for project in "${projects_array[@]}"; do
projects_e2e_array+=("${project}-e2e")
done
# all applications below this loop are not applicable for running e2e-test
# check if pull request event to deploy tools
if [[ "${{ github.event_name }}" = "pull_request" ]]; then
if echo "$affected" | grep -q multisig-signer; then
echo "Tools are affected"
echo "Deploying tools on preview"
preview_tools=$(printf "https://%s.%s.vega.rocks" "tools" "$branch_slug")
projects_array+=("multisig-signer")
fi
# those apps deploy only from develop to mainnet
elif [[ "${{ github.ref }}" =~ .*develop$ ]]; then
if echo "$affected" | grep -q multisig-signer; then
echo "Tools are affected"
echo "Deploying tools on s3"
projects_array+=("multisig-signer")
fi
if echo "$affected" | grep -q static; then
echo "static is affected"
echo "Deploying static on s3"
projects_array+=("static")
fi
if echo "$affected" | grep -q ui-toolkit; then
echo "ui-toolkit is affected"
echo "Deploying ui-toolkit on s3"
projects_array+=("ui-toolkit")
fi
fi
# if branch starts with release/ and ends with trading / governance or explorer - overwrite the array of affected projects with fixed single application
if [[ "${{ github.ref }}" == *release* ]]; then
echo ">> This is a relase branch"
case "${{ github.ref }}" in
*trading)
echo ">> Only trading will be deployed"
projects_array=(trading)
projects_e2e_array=(trading)
;;
*governance)
echo ">> Only governance will be deployed"
projects_array=(governance)
projects_e2e_array=(governance)
;;
*explorer)
echo ">> Only explorer will be deployed"
projects_array=(explorer)
projects_e2e_array=(explorer)
;;
*)
echo ">> All apps will be deployed"
;;
esac
fi
echo "Projects: ${projects_array[@]}"
echo "Projects E2E: ${projects_e2e_array[@]}"
projects_json=$(jq -M --compact-output --null-input '$ARGS.positional' --args -- "${projects_array[@]}")
projects_e2e_json=$(jq -M --compact-output --null-input '$ARGS.positional' --args -- "${projects_e2e_array[@]}")
echo PROJECTS_E2E=$projects_e2e_json >> $GITHUB_ENV
echo PROJECTS=$projects_json >> $GITHUB_ENV
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:
projects: ${{ env.PROJECTS }}
projects-e2e: ${{ env.PROJECTS_E2E }}
@@ -224,6 +120,15 @@ jobs:
preview_explorer: ${{ env.PREVIEW_EXPLORER }}
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:
needs: lint-test-build
name: '(CI) cypress'
+110 -27
View File
@@ -1,55 +1,138 @@
name: console-test-run
name: (CI) Console tests
on:
workflow_call:
inputs:
github-sha:
required: true
type: string
jobs:
console-test:
timeout-minutes: 5
runs-on: self-hosted-runner
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'
- name: Set up Python
#----------------------------------------------
# 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
run: |
poetry install --no-root
working-directory: ./console-test
- name: load Binaries
run: |
poetry run python -m vega_sim.tools.load_binaries
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
- name: pull console
run: |
poetry run docker pull ghcr.io/vegaprotocol/frontend/trading:${{ inputs.github-sha }}
- name: Update container_name in config.py
run: |
sed -i "s/container_name = \".*\"/container_name = \"vegaprotocol\/frontend\/trading:${{ inputs.github-sha }}\"/g" config.py
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
- name: run tests
run: poetry run pytest --numprocesses auto
#----------------------------------------------
# run tests
#----------------------------------------------
- name: Run tests
working-directory: ./console-test
run: poetry run pytest -s --numprocesses auto
- name: Check files
run: |
ls -al .
ls -al console-test
#----------------------------------------------
# upload traces
#----------------------------------------------
- name: Upload Playwright Trace
uses: actions/upload-artifact@v3
if: always()
+20 -49
View File
@@ -29,6 +29,12 @@ jobs:
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' }}
@@ -36,7 +42,7 @@ jobs:
echo IS_PR=true >> $GITHUB_ENV
- name: Is mainnet release
if: ${{ contains(github.ref, 'release/mainnnet') && !contains(github.ref, 'mirror') }}
if: ${{ contains(github.ref, 'release/mainnet') && !contains(github.ref, 'mirror') }}
run: |
echo IS_MAINNET_RELEASE=true >> $GITHUB_ENV
@@ -75,7 +81,7 @@ jobs:
- name: Log in to the Container registry (docker hub)
uses: docker/login-action@v2
if: ${{ env.IS_IPFS_RELEASE == 'true' }}
if: ${{ env.IS_IPFS_RELEASE == 'true' || env.IS_DEV_IMAGE == 'true' }}
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
@@ -97,52 +103,13 @@ jobs:
- name: Define dist variables
if: ${{ github.event_name == 'push' }}
run: |
envName=''
domain="vega.rocks"
bucketName=''
python3 tools/ci/define-dist-variables.py --github-ref="${{ github.ref }}" --app="${{ matrix.app }}"
if [[ "${{ github.ref }}" =~ .*release/.* ]]; then
# remove prefixing release/ and take the first string limited by - which is supposed to be name of the environment for releasing (format: release/testnet-trading)
envName="$(echo ${{ github.ref }} | sed -e "s|refs/heads/release/||" | cut -d '-' -f 1 )"
elif [[ "${{ github.ref }}" =~ .*develop$ ]]; then
envName="stagnet1"
if [[ "${{ matrix.app }}" = "multisig-signer" ]]; then
envName="mainnet"
bucketName="tools.vega.xyz"
fi
if [[ "${{ matrix.app }}" = "static" ]]; then
envName="mainnet"
bucketName="static.vega.xyz"
fi
if [[ "${{ matrix.app }}" = "ui-toolkit" ]]; then
envName="mainnet"
bucketName="ui.vega.rocks"
fi
elif [[ "${{ github.ref }}" =~ .*mainnet$ ]]; 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: Verify script result
if: ${{ github.event_name == 'push' }}
run: |
echo "BUCKET_NAME=${{ env.BUCKET_NAME }}"
echo "ENV_NAME=${{ env.ENV_NAME }}"
- name: Build local dist
run: |
@@ -157,8 +124,12 @@ jobs:
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
$envCmd yarn nx build ${{ matrix.app }} || (yarn install && $envCmd yarn nx build ${{ matrix.app }})
fi
if [[ -z "$DIST_LOCATION" ]]; then
DIST_LOCATION=dist/apps/${{ matrix.app }}
fi
mv $DIST_LOCATION dist-result
@@ -208,7 +179,7 @@ jobs:
uses: docker/build-push-action@v3
continue-on-error: true
id: dockerhub-push
if: ${{ env.IS_IPFS_RELEASE == 'true' }}
if: ${{ env.IS_IPFS_RELEASE == 'true' || env.IS_DEV_IMAGE == 'true' }}
with:
context: .
file: docker/node-outside-docker.Dockerfile
@@ -218,7 +189,7 @@ jobs:
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' || '' }}
vegaprotocol/${{ matrix.app }}:${{ env.IS_MAINNET_RELEASE == 'true' && 'mainnet' || env.IS_TESTNET_RELEASE == 'true' && 'testnet' || env.IS_DEV_IMAGE == 'true' && 'develop' || '' }}
- name: Publish dist as docker image (ghcr - retry)
uses: docker/build-push-action@v3
@@ -0,0 +1,46 @@
context('Proposal page', { tags: '@smoke' }, function () {
describe('Verify elements on page', function () {
const proposalHeading = 'proposals-heading';
const dateTimeRegex =
/(\d{1,2})\/(\d{1,2})\/(\d{4}), (\d{1,2}):(\d{1,2}):(\d{1,2})/gm;
const proposalTitle = 'Add Lorem Ipsum market';
before('Create market proposal', function () {
cy.visit('/');
cy.createMarket();
});
it('Able to view proposal', function () {
cy.navigate_to('governanceProposals');
cy.getByTestId(proposalHeading).should('be.visible');
// get first proposal in list
cy.get('[row-index="0"]').within(() => {
cy.get_element_by_col_id('title').should('have.text', proposalTitle);
cy.get_element_by_col_id('type').should('have.text', 'NewMarket');
cy.get_element_by_col_id('state').should('have.text', 'Enacted');
cy.getByTestId('vote-progress').should('be.visible');
cy.get('[col-id="cDate"]')
.invoke('text')
.should('match', dateTimeRegex);
cy.get('[col-id="eDate"]')
.invoke('text')
.should('match', dateTimeRegex);
cy.getByTestId('external-link')
.should('have.attr', 'href')
.and('contains', 'https://governance.fairground.wtf/proposals/');
cy.contains('View terms').should('exist').click();
});
cy.getByTestId('dialog-title').should('have.text', proposalTitle);
cy.get('.language-json').should('exist');
});
it('Proposal page displayed on mobile', function () {
cy.common_switch_to_mobile_and_click_toggle();
cy.navigate_to('governanceProposals', true);
cy.getByTestId(proposalHeading).should('be.visible');
cy.get('[row-index="0"]').within(() => {
cy.get_element_by_col_id('title').should('have.text', proposalTitle);
});
});
});
});
@@ -7,7 +7,11 @@ context('Validator page', { tags: '@smoke' }, function () {
it('should be able to see validator tiles', function () {
cy.getNodes().then((nodes) => {
nodes.forEach((node) => {
cy.get(`[validator-id="${node.id}"]`).should('be.visible');
if (node.rankingScore.performanceScore > 0) {
cy.get(`[validator-id="${node.id}"]`).should('be.visible');
} else {
cy.get(`[validator-id="${node.id}"]`).should('not.exist');
}
});
});
});
@@ -23,7 +23,7 @@ export const Footer = () => {
);
return (
<footer className="grid grid-rows-2 lg:grid-cols-[1fr_auto] text-xs md:text-md lg:flex md:col-span-2 px-4 py-2 gap-4 border-t border-vega-light-200 dark:border-vega-dark-200">
<footer className="grid grid-cols-[1fr_auto] items-center text-xs md:text-md lg:flex md:col-span-2 px-4 pt-2 pb-3 gap-4 border-t border-vega-light-200 dark:border-vega-dark-200">
<div className="flex justify-between gap-2 align-middle">
{GIT_COMMIT_HASH && (
<div className="content-center flex border-r border-neutral-700 dark:border-neutral-300 pr-4">
@@ -43,9 +43,12 @@ export const Footer = () => {
</div>
)}
<div className="content-center flex pl-2 md:border-r border-neutral-700 dark:border-neutral-300 pr-4">
{VEGA_URL && <NodeUrl url={VEGA_URL} />}
<Link className="ml-2" onClick={() => setNodeSwitcherOpen(true)}>
<div className="content-center flex pr-4 md:border-r border-neutral-700 dark:border-neutral-300">
<span className="pr-2">{VEGA_URL && <NodeUrl url={VEGA_URL} />}</span>
<Link
className="ml-2 underline-offset-4"
onClick={() => setNodeSwitcherOpen(true)}
>
{t('Change')}
</Link>
</div>
@@ -59,7 +62,10 @@ export const Footer = () => {
) : null}
</div>
<div className="pl-2 align-center lg:align-right lg:flex lg:justify-end gap-2 align-middle lg:max-w-xs lg:ml-auto">
<RouteLink to={`/${Routes.DISCLAIMER}`} className="underline">
<RouteLink
to={`/${Routes.DISCLAIMER}`}
className="underline underline-offset-4"
>
Disclaimer
</RouteLink>
</div>
@@ -10,7 +10,6 @@ import {
DropdownMenuTrigger,
DropdownMenuSubContent,
Icon,
Button,
} from '@vegaprotocol/ui-toolkit';
import type { Dispatch, SetStateAction } from 'react';
import { FilterLabel } from './tx-filter-label';
@@ -100,15 +99,13 @@ export const TxsFilter = ({ filters, setFilters }: TxFilterProps) => {
<DropdownMenu
modal={false}
trigger={
<DropdownMenuTrigger className="ml-0">
<Button size="xs" data-testid="filter-trigger">
<FilterLabel filters={filters} />
</Button>
<DropdownMenuTrigger>
<FilterLabel filters={filters} />
</DropdownMenuTrigger>
}
>
<DropdownMenuContent>
{filters.size > 0 ? null : (
{filters.size > 1 ? null : (
<>
<DropdownMenuCheckboxItem
onCheckedChange={() => setFilters(new Set(AllFilterOptions))}
@@ -44,7 +44,7 @@ export const TxsListNavigation = ({
</Button>
<Button
size="xs"
disabled={!hasMoreTxs || loading}
disabled={!hasMoreTxs}
onClick={() => {
nextPage();
}}
@@ -86,14 +86,18 @@ describe('Txs infinite list item', () => {
render(
<MockedProvider>
<MemoryRouter>
<TxsInfiniteListItem
type="testType"
submitter="testPubKey"
hash="testTxHash"
block="1"
code={0}
command={{}}
/>
<table>
<tbody>
<TxsInfiniteListItem
type="testType"
submitter="testPubKey"
hash="testTxHash"
block="1"
code={0}
command={{}}
/>
</tbody>
</table>
</MemoryRouter>
</MockedProvider>
);
@@ -14,9 +14,9 @@ const DEFAULT_TRUNCATE_LENGTH = 7;
export function getIdTruncateLength(screen: Screen): number {
if (['xxxl', 'xxl'].includes(screen)) {
return 64;
} else if (['xl', 'lg', 'md'].includes(screen)) {
return 32;
} else if (['xl', 'lg', 'md'].includes(screen)) {
return 16;
}
return DEFAULT_TRUNCATE_LENGTH;
}
@@ -5,11 +5,17 @@ import type { BlockExplorerTransactionResult } from '../../routes/types/block-ex
import { Side } from '@vegaprotocol/types';
import { MockedProvider } from '@apollo/client/testing';
const generateHash = (): string =>
Array.from(
{ length: 64 },
() => '0123456789ABCDEF'[Math.floor(Math.random() * 16)]
).join('');
const generateTxs = (number: number): BlockExplorerTransactionResult[] => {
return Array.from(Array(number)).map((_) => ({
block: '87901',
index: 2,
hash: '0F8B98DA0923A50786B852D9CA11E051CACC4C733E1DB93D535C7D81DBD10F6F',
hash: generateHash(),
submitter:
'4b782482f587d291e8614219eb9a5ee9280fa2c58982dee71d976782a9be1964',
type: 'Submit Order',
@@ -2,7 +2,7 @@ import { Table, TableRow } from '../table';
import { t } from '@vegaprotocol/i18n';
import { useFetch } from '@vegaprotocol/react-helpers';
import type { BlockExplorerTransactions } from '../../routes/types/block-explorer-response';
import { getTxsDataUrl } from '../../hooks/use-txs-data';
import { getTxsDataUrl } from '../../hooks/get-txs-data-url';
import { AsyncRenderer, Loader } from '@vegaprotocol/ui-toolkit';
import EmptyList from '../empty-list/empty-list';
import { TxsInfiniteListItem } from './txs-infinite-list-item';
@@ -14,7 +14,7 @@ interface TxsPerBlockProps {
export const TxsPerBlock = ({ blockHeight, txCount }: TxsPerBlockProps) => {
const filters = `filters[block.height]=${blockHeight}`;
const url = getTxsDataUrl({ limit: txCount.toString(), filters });
const url = getTxsDataUrl({ filters, count: txCount });
const {
state: { data, loading, error },
} = useFetch<BlockExplorerTransactions>(url);
@@ -0,0 +1,59 @@
import { DATA_SOURCES } from '../config';
type IGetTxsDataFirstPage = {
baseUrl?: string;
count?: number;
party?: string;
filters?: string;
};
interface IGetTxsDataPrevious extends IGetTxsDataFirstPage {
before: string;
}
interface IGetTxsDataNext extends IGetTxsDataFirstPage {
after: string;
}
type IGetTxsDataUrl =
| IGetTxsDataPrevious
| IGetTxsDataNext
| IGetTxsDataFirstPage;
export const BE_TXS_PER_REQUEST = 25;
/**
* Properly encodes the filters and parameters for a request to the block explorer
* API for transactions. As the API uses a slightly less common format for encoding
* filters, some of it is more manual than you might expect.
*
* @param params An object containing the pagination and filters
* @returns string URL to call
*/
export const getTxsDataUrl = (params: IGetTxsDataUrl) => {
const baseUrl =
params.baseUrl || `${DATA_SOURCES.blockExplorerUrl}/transactions`;
const url = new URL(baseUrl);
const count = `${params.count || BE_TXS_PER_REQUEST}`;
if ('before' in params && params.before?.length > 0) {
url.searchParams.append('last', count);
url.searchParams.append('before', params.before);
} else if ('after' in params && params.after?.length > 0) {
url.searchParams.append('first', count);
url.searchParams.append('after', params.after);
} else {
url.searchParams.append('first', count);
}
// Hacky fix for param as array
let urlAsString = url.toString();
if (params.filters && params.filters?.length > 0) {
urlAsString += '&' + params.filters.replaceAll(' ', '%20');
}
if (params.party && params.party?.length > 0) {
urlAsString += `&filters[tx.submitter]=${params.party}`;
}
return urlAsString;
};
@@ -0,0 +1,48 @@
import { getTxsDataUrl } from './get-txs-data-url'; // import the function to be tested
describe('getTxsDataUrl', () => {
it('should return the correct URL without filters and party', () => {
const params = {
count: 10,
baseUrl: 'https://example.com/transactions',
};
const expectedUrl = 'https://example.com/transactions?first=10';
expect(getTxsDataUrl(params)).toEqual(expectedUrl);
});
it('should return the correct URL with "before" in params', () => {
const params = {
count: 5,
before: '100.1',
baseUrl: 'https://example.com/transactions',
};
const expectedUrl = 'https://example.com/transactions?last=5&before=100.1';
expect(getTxsDataUrl(params)).toEqual(expectedUrl);
});
it('should return the correct URL with "after" in params', () => {
const params = {
count: 5,
after: '222.1',
baseUrl: 'https://example.com/transactions',
};
const expectedUrl = 'https://example.com/transactions?first=5&after=222.1';
expect(getTxsDataUrl(params)).toEqual(expectedUrl);
});
it('should return the correct URL with filters and party', () => {
const params = {
count: 10,
filters: 'filters[cmd.type]=Made Up Transaction',
party: '1234',
baseUrl: 'https://example.com/transactions',
};
const expectedUrl =
'https://example.com/transactions?first=10&filters[cmd.type]=Made%20Up%20Transaction&filters[tx.submitter]=1234';
expect(getTxsDataUrl(params)).toEqual(expectedUrl);
});
});
+103 -92
View File
@@ -1,130 +1,141 @@
import { useCallback, useEffect, useState } from 'react';
import { useSearchParams } from 'react-router-dom';
import type { URLSearchParamsInit } from 'react-router-dom';
import { useCallback } from 'react';
import { useFetch } from '@vegaprotocol/react-helpers';
import type {
BlockExplorerTransactionResult,
BlockExplorerTransactions,
} from '../routes/types/block-explorer-response';
import { DATA_SOURCES } from '../config';
import isNumber from 'lodash/isNumber';
import { AllFilterOptions } from '../components/txs/tx-filter';
import type { FilterOption } from '../components/txs/tx-filter';
import { BE_TXS_PER_REQUEST, getTxsDataUrl } from './get-txs-data-url';
export interface TxsStateProps {
txsData: BlockExplorerTransactionResult[];
hasMoreTxs: boolean;
cursor: string;
previousCursors: string[];
hasPreviousPage: boolean;
export function getTypeFilters(filters?: Set<FilterOption>) {
if (!filters) {
return '';
} else if (filters.size > 1) {
return '';
}
const forcedSingleFilter = Array.from(filters)[0];
return `filters[cmd.type]=${forcedSingleFilter}`;
}
export interface IUseTxsData {
limit: number;
filters?: string;
count?: number;
before?: string;
after?: string;
party?: string;
filters?: Set<FilterOption>;
}
interface IGetTxsDataUrl {
limit: string;
filters?: string;
}
export const useTxsData = ({
count = 25,
before,
after,
filters,
party,
}: IUseTxsData) => {
const [, setSearchParams] = useSearchParams();
let hasMoreTxs = true;
let txsData: BlockExplorerTransactionResult[] = [];
export const getTxsDataUrl = ({ limit, filters }: IGetTxsDataUrl) => {
const url = new URL(`${DATA_SOURCES.blockExplorerUrl}/transactions`);
if (limit) {
url.searchParams.append('limit', limit);
}
// Hacky fix for param as array
let urlAsString = url.toString();
if (filters) {
urlAsString += '&' + filters.replace(' ', '%20');
}
return urlAsString;
};
export const useTxsData = ({ limit, filters }: IUseTxsData) => {
const [
{ txsData, hasMoreTxs, cursor, previousCursors, hasPreviousPage },
setTxsState,
] = useState<TxsStateProps>({
txsData: [],
hasMoreTxs: false,
previousCursors: [],
cursor: '',
hasPreviousPage: false,
const url = getTxsDataUrl({
filters: getTypeFilters(filters),
count,
before,
after,
party,
});
const url = getTxsDataUrl({ limit: limit.toString(), filters });
const {
state: { data, error, loading },
refetch,
} = useFetch<BlockExplorerTransactions>(url, {}, true);
useEffect(() => {
if (!loading && data && isNumber(data.transactions.length)) {
setTxsState((prev) => {
return {
...prev,
txsData: data.transactions,
hasMoreTxs: data.transactions.length >= limit,
cursor: data?.transactions.at(-1)?.cursor || '',
};
});
}
}, [loading, setTxsState, data, limit]);
if (!loading && data && isNumber(data.transactions.length)) {
hasMoreTxs = data.transactions.length >= count;
txsData = data.transactions;
}
const nextPage = useCallback(() => {
const c = data?.transactions.at(0)?.cursor;
const newPreviousCursors = c ? [...previousCursors, c] : previousCursors;
setTxsState((prev) => ({
...prev,
hasPreviousPage: true,
previousCursors: newPreviousCursors,
}));
return refetch({
limit,
before: cursor,
});
}, [data, previousCursors, cursor, limit, refetch]);
const after = data?.transactions.at(-1)?.cursor || '';
const params: URLSearchParamsInit = { after };
if (filters) {
params.filters = Array.from(filters).join(',');
}
setSearchParams(params);
}, [filters, data, setSearchParams]);
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 before = data?.transactions[0]?.cursor || '';
const params: URLSearchParamsInit = { before };
if (filters && filters.size > 0 && filters.size === 1) {
params.filters = Array.from(filters)[0];
}
setSearchParams(params);
}, [filters, data, setSearchParams]);
const refreshTxs = useCallback(async () => {
setTxsState(() => ({
txsData: [],
cursor: '',
previousCursors: [],
hasMoreTxs: false,
hasPreviousPage: false,
}));
const params: URLSearchParamsInit = {};
if (filters && filters.size > 0 && filters.size === 1) {
params.filters = Array.from(filters)[0];
}
setSearchParams(params);
refetch({ limit });
}, [setTxsState, limit, refetch, filters]); // eslint-disable-line react-hooks/exhaustive-deps
refetch({ count: BE_TXS_PER_REQUEST });
}, [setSearchParams, refetch, filters]);
const updateFilters = useCallback(
(newFilters: Set<FilterOption>) => {
const params: URLSearchParamsInit = {};
if (newFilters && newFilters.size === 1) {
params.filters = Array.from(newFilters)[0];
}
setSearchParams(params);
},
[setSearchParams]
);
return {
updateFilters,
txsData,
loading,
error,
hasMoreTxs,
hasPreviousPage,
previousCursors,
cursor,
refreshTxs,
nextPage,
previousPage,
};
};
/**
* Returns a Set of filters based on the URLSearchParams, or
* defaults to all.
* @param params
* @returns Set
*/
export function getInitialFilters(params: URLSearchParams): Set<FilterOption> {
const defaultFilters = new Set(AllFilterOptions);
const p = params.get('filters');
if (!p) {
return defaultFilters;
}
const filters = new Set<FilterOption>();
p.split(',').forEach((f) => {
if (AllFilterOptions.includes(f as FilterOption)) {
filters.add(f as FilterOption);
}
});
if (filters.size === 0) {
return defaultFilters;
}
return filters;
}
@@ -204,6 +204,7 @@ describe('Block', () => {
(useFetch as jest.Mock).mockReturnValue({
state: { data: createBlockResponse(1), loading: false, error: null },
});
render(renderComponent(1));
await waitFor(() => screen.getByTestId('block-header'));
expect(screen.getByTestId('previous-block-button')).toHaveAttribute(
@@ -15,9 +15,13 @@ import { PartyBlockAccounts } from './components/party-block-accounts';
import { isValidPartyId } from './components/party-id-error';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { TxsListNavigation } from '../../../components/txs/tx-list-navigation';
import type { FilterOption } from '../../../components/txs/tx-filter';
import { AllFilterOptions, TxsFilter } from '../../../components/txs/tx-filter';
import { useSearchParams } from 'react-router-dom';
const Party = () => {
const [params] = useSearchParams();
const [filters, setFilters] = useState(new Set(AllFilterOptions));
const { party } = useParams<{ party: string }>();
@@ -27,24 +31,21 @@ const Party = () => {
const partyId = toNonHex(party ? party : '');
const { isMobile } = useScreenDimensions();
const visibleChars = useMemo(() => (isMobile ? 10 : 14), [isMobile]);
const baseFilters = `filters[tx.submitter]=${partyId}`;
const f =
filters && filters.size === 1
? `${baseFilters}&filters[cmd.type]=${Array.from(filters)[0]}`
: baseFilters;
const {
hasMoreTxs,
nextPage,
refreshTxs,
previousPage,
error,
refreshTxs,
loading,
txsData,
hasPreviousPage,
hasMoreTxs,
updateFilters,
} = useTxsData({
limit: 25,
filters: f,
filters: filters.size === 1 ? filters : undefined,
before: params.get('before') || undefined,
after: !params.get('before') ? params.get('after') || undefined : undefined,
party: partyId,
});
const variables = useMemo(() => ({ partyId }), [partyId]);
@@ -102,15 +103,21 @@ const Party = () => {
refreshTxs={refreshTxs}
nextPage={nextPage}
previousPage={previousPage}
hasPreviousPage={hasPreviousPage}
hasPreviousPage={true}
loading={loading}
hasMoreTxs={hasMoreTxs}
>
<TxsFilter filters={filters} setFilters={setFilters} />
<TxsFilter
filters={filters}
setFilters={(f) => {
setFilters(f);
updateFilters(f as Set<FilterOption>);
}}
/>
</TxsListNavigation>
{!error && txsData ? (
<TxsInfiniteList
hasMoreTxs={hasMoreTxs}
hasMoreTxs={true}
areTxsLoading={loading}
txs={txsData}
loadMoreTxs={nextPage}
+19 -16
View File
@@ -1,14 +1,14 @@
import { t } from '@vegaprotocol/i18n';
import { RouteTitle } from '../../../components/route-title';
import { TxsInfiniteList } from '../../../components/txs';
import { useTxsData } from '../../../hooks/use-txs-data';
import { useTxsData, getInitialFilters } from '../../../hooks/use-txs-data';
import { useDocumentTitle } from '../../../hooks/use-document-title';
import { useState } from 'react';
import { AllFilterOptions, TxsFilter } from '../../../components/txs/tx-filter';
import { TxsFilter } from '../../../components/txs/tx-filter';
import type { FilterOption } from '../../../components/txs/tx-filter';
import { TxsListNavigation } from '../../../components/txs/tx-list-navigation';
const BE_TXS_PER_REQUEST = 25;
import { useSearchParams } from 'react-router-dom';
export const TxsList = () => {
useDocumentTitle(['Transactions']);
@@ -27,25 +27,22 @@ export const TxsList = () => {
* @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 [params] = useSearchParams();
const [filters, setFilters] = useState(getInitialFilters(params));
const {
hasMoreTxs,
nextPage,
previousPage,
error,
refreshTxs,
loading,
txsData,
hasPreviousPage,
hasMoreTxs,
updateFilters,
} = useTxsData({
limit: BE_TXS_PER_REQUEST,
filters: f,
filters: filters.size === 1 ? filters : undefined,
before: params.get('before') || undefined,
after: !params.get('before') ? params.get('after') || undefined : undefined,
});
return (
@@ -54,11 +51,17 @@ export const TxsListFiltered = () => {
refreshTxs={refreshTxs}
nextPage={nextPage}
previousPage={previousPage}
hasPreviousPage={hasPreviousPage}
hasPreviousPage={true}
loading={loading}
hasMoreTxs={hasMoreTxs}
>
<TxsFilter filters={filters} setFilters={setFilters} />
<TxsFilter
filters={filters}
setFilters={(f) => {
setFilters(f);
updateFilters(f as Set<FilterOption>);
}}
/>
</TxsListNavigation>
<TxsInfiniteList
hasFilters={filters.size > 0}
@@ -15,6 +15,9 @@ query ExplorerNodes {
stakedByDelegates
stakedTotal
pendingStake
rankingScore {
performanceScore
}
epochData {
total
offline
@@ -6,7 +6,7 @@ const defaultOptions = {} as const;
export type ExplorerNodesQueryVariables = Types.Exact<{ [key: string]: never; }>;
export type ExplorerNodesQuery = { __typename?: 'Query', nodesConnection: { __typename?: 'NodesConnection', edges?: Array<{ __typename?: 'NodeEdge', node: { __typename?: 'Node', id: string, name: string, infoUrl: string, avatarUrl?: string | null, pubkey: string, tmPubkey: string, ethereumAddress: string, location: string, status: Types.NodeStatus, stakedByOperator: string, stakedByDelegates: string, stakedTotal: string, pendingStake: string, epochData?: { __typename?: 'EpochData', total: number, offline: number, online: number } | null } } | null> | null } };
export type ExplorerNodesQuery = { __typename?: 'Query', nodesConnection: { __typename?: 'NodesConnection', edges?: Array<{ __typename?: 'NodeEdge', node: { __typename?: 'Node', id: string, name: string, infoUrl: string, avatarUrl?: string | null, pubkey: string, tmPubkey: string, ethereumAddress: string, location: string, status: Types.NodeStatus, stakedByOperator: string, stakedByDelegates: string, stakedTotal: string, pendingStake: string, rankingScore: { __typename?: 'RankingScore', performanceScore: string }, epochData?: { __typename?: 'EpochData', total: number, offline: number, online: number } | null } } | null> | null } };
export const ExplorerNodesDocument = gql`
@@ -27,6 +27,9 @@ export const ExplorerNodesDocument = gql`
stakedByDelegates
stakedTotal
pendingStake
rankingScore {
performanceScore
}
epochData {
total
offline
@@ -91,7 +91,15 @@ export const ValidatorsPage = () => {
const { data: tmData } = useTendermintValidators(5000);
const { data, loading, error, refetch } = useExplorerNodesQuery();
const validators = compact(data?.nodesConnection.edges?.map((e) => e?.node));
const validators = compact(
data?.nodesConnection.edges
?.map((e) => e?.node)
.filter(
(node) =>
node?.rankingScore?.performanceScore &&
new BigNumber(node.rankingScore.performanceScore).isGreaterThan(0)
)
);
// voting power
const powers = compact(tmData?.result.validators).map(
@@ -0,0 +1,107 @@
{
"changes": {
"decimalPlaces": "5",
"positionDecimalPlaces": "5",
"linearSlippageFactor": "0.001",
"quadraticSlippageFactor": "0",
"lpPriceRange": "10",
"instrument": {
"name": "Token test market",
"code": "Token.24h",
"future": {
"settlementAsset": "816af99af60d684502a40824758f6b5377e6af48e50a9ee8ef478ecb879ea8bc",
"quoteName": "fUSDC",
"dataSourceSpecForSettlementData": {
"external": {
"oracle": {
"signers": [
{
"pubKey": {
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
}
}
],
"filters": [
{
"key": {
"name": "prices.BTC.value",
"type": "TYPE_INTEGER",
"numberDecimalPlaces": "0"
},
"conditions": [
{
"operator": "OPERATOR_GREATER_THAN",
"value": "0"
}
]
}
]
}
}
},
"dataSourceSpecForTradingTermination": {
"external": {
"oracle": {
"signers": [
{
"pubKey": {
"key": "70d14a321e02e71992fd115563df765000ccc4775cbe71a0e2f9ff5a3b9dc680"
}
}
],
"filters": [
{
"key": {
"name": "trading.terminated.ETH5",
"type": "TYPE_BOOLEAN"
},
"conditions": [
{
"operator": "OPERATOR_EQUALS",
"value": "true"
}
]
}
]
}
}
},
"dataSourceSpecBinding": {
"settlementDataProperty": "prices.BTC.value",
"tradingTerminationProperty": "trading.terminated.ETH5"
}
}
},
"metadata": ["sector:food", "sector:materials", "source:docs.vega.xyz"],
"priceMonitoringParameters": {
"triggers": [
{
"horizon": "43200",
"probability": "0.9999999",
"auctionExtension": "600"
}
]
},
"liquidityMonitoringParameters": {
"targetStakeParameters": {
"timeWindow": "3600",
"scalingFactor": 10
},
"triggeringRatio": "0.7",
"auctionExtension": "1"
},
"logNormal": {
"tau": 0.0001140771161,
"riskAversionParameter": 0.001,
"params": {
"mu": 0,
"r": 0.016,
"sigma": 0.8
}
},
"successor": {
"parentMarketId": "",
"insurancePoolFraction": "0.75"
}
}
}
@@ -218,17 +218,33 @@ describe(
getProposalInformationFromTable('Tokens for proposal')
.should('have.text', (1).toFixed(2))
.and('be.visible');
navigateTo(navigation.proposals);
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
getProposalFromTitle(rawProposal.rationale.title).within(() => {
// 3002-PROP-021
cy.getByTestId('user-voted-yes').should('exist');
cy.getByTestId(viewProposalButton).click();
});
});
cy.getByTestId(changeVoteButton).should('be.visible').click();
voteForProposal('against');
cy.getByTestId(proposalVoteProgressAgainstPercentage)
.contains('100.00%')
.and('be.visible');
cy.getByTestId(voteBreakdownToggle).click();
getProposalInformationFromTable('Tokens against proposal')
.should('have.text', (1).toFixed(2))
.and('be.visible');
getProposalInformationFromTable('Number of voting parties')
.should('have.text', '1')
.and('be.visible');
navigateTo(navigation.proposals);
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
getProposalFromTitle(rawProposal.rationale.title).within(() => {
cy.getByTestId('user-voted-no').should('exist');
cy.getByTestId(viewProposalButton).click();
});
});
});
// 3001-VOTE-042, 3001-VOTE-057, 3001-VOTE-058, 3001-VOTE-059, 3001-VOTE-060
@@ -26,6 +26,8 @@ const votesTable = 'votes-table';
const openProposals = 'open-proposals';
const proposalVoteProgressForPercentage =
'vote-progress-indicator-percentage-for';
const majorityVoteReached = 'majority-reached';
const minParticipationReached = 'participation-reached';
const proposalTimeout = { timeout: 8000 };
context(
@@ -72,7 +74,7 @@ context(
});
});
// 3001-VOTE-046 3001-VOTE-044 3001-VOTE-074 3001-VOTE-074
// 3001-VOTE-020 3001-VOTE-021 3001-VOTE-046 3001-VOTE-044 3001-VOTE-074 3001-VOTE-074
it('Able to enact proposal by voting', function () {
const proposalTitle = 'Add New proposal with short enactment';
const proposalTx = createUpdateNetworkProposalTxBody();
@@ -85,10 +87,18 @@ context(
cy.contains(proposalTitle)
.parentsUntil(proposalListItem)
.last()
.within(() => cy.getByTestId(viewProposalButton).click());
.within(() => {
// 3001-VOTE-019 time to vote is highlighted red
cy.getByTestId('vote-details')
.find('span')
.should('have.class', 'text-vega-pink');
cy.getByTestId(viewProposalButton).click();
});
});
cy.getByTestId(proposalStatus).should('have.text', 'Open');
voteForProposal('for');
cy.getByTestId(majorityVoteReached).should('exist');
cy.getByTestId(minParticipationReached).should('exist');
cy.getByTestId(proposalStatus, proposalTimeout)
.should('have.text', 'Passed')
.then(() => {
@@ -104,6 +114,14 @@ context(
cy.getByTestId(proposalVoteProgressForPercentage)
.contains('100.00%')
.and('be.visible');
navigateTo(navigation.proposals);
cy.contains(proposalTitle)
.parentsUntil(proposalListItem)
.last()
.within(() => {
cy.getByTestId(majorityVoteReached).should('exist');
cy.getByTestId(minParticipationReached).should('exist');
});
});
// 3001-VOTE-047
@@ -220,7 +220,7 @@ context(
function () {
const proposalTitle = 'Test new market proposal';
goToMakeNewProposal(governanceProposalType.NEW_MARKET);
cy.getByTestId(newProposalTitle).type('Test new market proposal');
cy.getByTestId(newProposalTitle).type(proposalTitle);
cy.getByTestId(newProposalDescription).type('E2E test for proposals');
cy.fixture('/proposals/new-market').then((newMarketProposal) => {
const newMarketPayload = JSON.stringify(newMarketProposal);
@@ -606,6 +606,94 @@ context(
});
});
it('able to submit successor market proposal', function () {
const proposalTitle = 'Test successor market proposal';
cy.createMarket();
cy.reload();
waitForSpinner();
cy.getByTestId('closed-proposals').within(() => {
cy.contains('Add Lorem Ipsum market')
.parentsUntil(proposalListItem)
.last()
.within(() => {
cy.getByTestId(viewProposalBtn).click();
});
});
getProposalInformationFromTable('ID').invoke('text').as('parentMarketId');
goToMakeNewProposal(governanceProposalType.NEW_MARKET);
cy.getByTestId(newProposalTitle).type(proposalTitle);
cy.getByTestId(newProposalDescription).type(
'E2E test for successor market'
);
cy.fixture('/proposals/successor-market').then((newMarketProposal) => {
newMarketProposal.changes.successor.parentMarketId =
this.parentMarketId;
const newMarketPayload = JSON.stringify(newMarketProposal);
cy.getByTestId(newProposalTerms).type(newMarketPayload, {
parseSpecialCharSequences: false,
delay: 2,
});
});
cy.getByTestId(proposalDownloadBtn)
.should('be.visible')
.click()
.then(() => {
cy.wrap(
getDownloadedProposalJsonPath('vega-new-market-proposal-')
).then((filePath) => {
goToMakeNewProposal(governanceProposalType.RAW);
submitUniqueRawProposal({ proposalBody: filePath });
});
});
navigateTo(navigation.proposals);
getProposalFromTitle(proposalTitle).within(() => {
// 3003-PMAN-008
cy.getByTestId('proposal-successor-info')
.should('have.text', 'Successor market to: TEST.24h')
.find('a')
.should('have.attr', 'href')
.and('contain', this.parentMarketId);
cy.getByTestId('view-proposal-btn').click();
});
// #3003-PMAN-010
cy.getByTestId(proposalJsonToggle).click();
cy.get('.language-json').within(() => {
cy.get('.hljs-attr').should('contain.text', 'parentMarketId');
cy.get('.hljs-string').should('contain.text', this.parentMarketId);
cy.get('.hljs-attr').should('contain.text', 'insurancePoolFraction');
cy.get('.hljs-string').should('contain.text', '0.75');
});
cy.getByTestId('proposal-market-data').within(() => {
cy.getByTestId('proposal-market-data-toggle').click();
cy.contains('Key details').click();
// 3003-PMAN-009
getMarketProposalDetailsFromTable('Parent Market ID').should(
'have.text',
this.parentMarketId
);
getMarketProposalDetailsFromTable('Insurance Pool Fraction').should(
'have.text',
'0.75'
);
getMarketProposalDetailsFromTable('Trading Mode').should(
'have.text',
'No trading'
);
});
// 3003-PMAN-011
cy.contains('Parent Market ID').realHover();
cy.getByTestId('tooltip-content').should(
'contain.text',
'The ID of the market this market succeeds.'
);
cy.contains('Insurance Pool Fraction').realHover();
cy.getByTestId('tooltip-content').should(
'contain.text',
'The fraction of the insurance pool balance that is carried over from the parent market to the successor.'
);
});
after('Disassociate from second wallet key if present', function () {
cy.reload();
waitForSpinner();
@@ -642,6 +730,7 @@ context(
.getByTestId('key-value-table-row')
.contains(heading)
.parent()
.parent()
.siblings();
}
}
@@ -49,8 +49,8 @@ describe('Governance flow for proposal list', { tags: '@slow' }, function () {
navigateTo(navigation.proposals);
});
// 3001-VOTE-018
it('Newly created proposals list - proposals closest to closing date appear higher in list', function () {
// 3001-VOTE-005
const proposalDays = [364, 50, 2];
for (let index = 0; index < proposalDays.length; index++) {
goToMakeNewProposal(governanceProposalType.RAW);
@@ -120,16 +120,24 @@ describe('Governance flow for proposal list', { tags: '@slow' }, function () {
submitUniqueRawProposal({ proposalTitle: proposalTitle });
getProposalFromTitle(proposalTitle).within(() => {
// 3001-VOTE-039
cy.getByTestId(voteStatus).should(
cy.getByTestId('participation-not-reached').should(
'have.text',
'Participation not reached'
'Min. participation not reached'
);
cy.getByTestId(viewProposalButton).click();
});
voteForProposal('for');
navigateTo(navigation.proposals);
getProposalFromTitle(proposalTitle).within(() => {
cy.getByTestId(voteStatus).should('have.text', 'Set to pass');
cy.getByTestId(voteStatus).should(
'have.text',
'Currently expected to pass'
);
cy.getByTestId('user-voted-yes').should('exist');
cy.getByTestId('participation-reached').should(
'have.text',
'Min. participation reached'
);
cy.getByTestId(viewProposalButton).click();
});
cy.getByTestId(voteBreakDownToggle).click();
@@ -96,12 +96,14 @@ context('rewards - flow', { tags: '@slow' }, function () {
.within(() => {
cy.get('h2').first().should('contain.text', 'EPOCH');
cy.getByTestId('individual-rewards-asset').should('have.text', 'Vega');
cy.getByTestId('ACCOUNT_TYPE_GLOBAL_REWARD', rewardsTimeOut)
.should('contain.text', '0.4415')
.and('contain.text', '(44.15%)');
cy.getByTestId('ACCOUNT_TYPE_FEES_INFRASTRUCTURE')
.should('contain.text', '0.0004')
.and('contain.text', '(44.15%)');
cy.getByTestId('ACCOUNT_TYPE_GLOBAL_REWARD', rewardsTimeOut).should(
'contain.text',
'0.4415'
);
cy.getByTestId('ACCOUNT_TYPE_FEES_INFRASTRUCTURE').should(
'contain.text',
'0.0004'
);
cy.getByTestId('total').should('have.text', '0.4419');
});
});
@@ -351,6 +351,12 @@ context(
);
stakingPageDisassociateAllTokens();
});
it('Able to associate over 1 million tokens', function () {
cy.get(ethWalletAssociateButton).click();
stakingPageAssociateTokens('2000000', { approve: true });
stakingPageDisassociateAllTokens();
});
});
}
);
@@ -17,6 +17,7 @@ const governanceDocsUrl = 'https://vega.xyz/governance';
const networkUpgradeProposalListItem = 'protocol-upgrade-proposals-list-item';
const closedProposals = 'closed-proposals';
const closedProposalToggle = 'closed-proposals-toggle-networkUpgrades';
const protocolUpgradeTime = 'protocol-upgrade-time';
context(
'Governance Page - verify elements on page',
@@ -174,6 +175,7 @@ context(
});
});
// 3009-NTWU-003 3009-NTWU-004 3009-NTWU-007
it('should see details of network upgrade proposal', function () {
mockNetworkUpgradeProposal();
navigateTo(navigation.proposals);
@@ -228,6 +230,7 @@ context(
cy.getByTestId(closedProposalToggle).should('not.exist');
});
// 3009-NTWU-001 3009-NTWU-002 3009-NTWU-006 3009-NTWU-009
it('should display network upgrade banner with estimate', function () {
mockNetworkUpgradeProposal();
cy.visit('/');
@@ -259,10 +262,18 @@ context(
.as('displayedEstimate');
cy.get('@displayedEstimate').then((estimateText) => {
// Estimated time should automatically update every second
cy.getByTestId('protocol-upgrade-time')
cy.getByTestId(protocolUpgradeTime)
.invoke('text')
.should('not.eq', estimateText);
});
// time estimate on proposal detail
cy.getByTestId('protocol-upgrade-proposal').within(() => {
cy.get('@displayedEstimate').then((estimateText) => {
cy.getByTestId(protocolUpgradeTime)
.invoke('text')
.should('not.eq', estimateText);
});
});
});
}
);
@@ -339,7 +339,7 @@ context(
cy.getByTestId(vegaWalletCurrencyTitle)
.contains(name)
.parent()
.siblings()
.siblings(txTimeout)
.should((elementAmount) => {
const displayedAmount = parseFloat(elementAmount.text());
expect(displayedAmount).be.gte(expectedAmount);
@@ -107,7 +107,7 @@ export function dissociateFromSecondWalletKey() {
cy.getByTestId('vega-in-wallet')
.first()
.within(() => {
cy.getByTestId('eth-wallet-associated-balances')
cy.getByTestId('eth-wallet-associated-balances', txTimeout)
.last()
.within(() => {
cy.getByTestId('associated-key')
@@ -78,13 +78,21 @@ export function stakingPageAssociateTokens(
}
cy.get(tokenAmountInputBox, epochTimeout).type(amount);
if (approve) {
cy.get(tokenInputApprove, txTimeout).should('be.enabled').click();
cy.contains('Approve $VEGA Tokens for staking on Vega').should(
'be.visible'
);
cy.contains('Approve $VEGA Tokens for staking on Vega', txTimeout).should(
'not.exist'
);
cy.getByTestId('wallet-associate').then((walletAssociateField) => {
if (
walletAssociateField.find('[data-testid="token-input-approve-button"]')
.length
) {
cy.get(tokenInputApprove, txTimeout).should('be.enabled').click();
cy.contains('Approve $VEGA Tokens for staking on Vega').should(
'be.visible'
);
cy.contains(
'Approve $VEGA Tokens for staking on Vega',
txTimeout
).should('not.exist');
}
});
}
cy.get(tokenSubmitButton, txTimeout).should('be.enabled').click();
+3
View File
@@ -9,6 +9,7 @@ NX_VEGA_NETWORKS='{"DEVNET":"https://dev.governance.vega.xyz","STAGNET1":"https:
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_VEGA_WALLET_URL=http://localhost:1789
NX_VEGA_EXPLORER_URL=https://explorer.stagnet1.vega.rocks
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
@@ -25,3 +26,5 @@ NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.stagnet1.vega.xyz/websocket
CYPRESS_FAIRGROUND=false
LC_ALL="en_US.UTF-8"
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=true
+2
View File
@@ -25,3 +25,5 @@ NX_TENDERMINT_WEBSOCKET_URL=wss://localhost:26617/websocket
#Test configuration variables
CYPRESS_FAIRGROUND=false
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=false
+3
View File
@@ -16,3 +16,6 @@ NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40@o286262.ingest.sentry.io/
NX_TENDERMINT_URL=https://tm.be.devnet1.vega.xyz/
NX_TENDERMINT_WEBSOCKET_URL=wss://be.devnet1.vega.xyz/websocket
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=true
+3
View File
@@ -17,3 +17,6 @@ NX_VEGA_REST_URL=https://api.vega.community/api/v2/
NX_TENDERMINT_URL=https://be.vega.community
NX_TENDERMINT_WEBSOCKET_URL=wss://be.vega.community/websocket
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=false
+3
View File
@@ -16,3 +16,6 @@ NX_VEGA_REST_URL=https://api.mainnet-mirror.vega.rocks/api/v2/
NX_TENDERMINT_URL=https://be.mainnet-mirror.vega.rocks
NX_TENDERMINT_WEBSOCKET_URL=wss://be.mainnet-mirror.vega.rocks/websocket
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=false
+4 -1
View File
@@ -11,4 +11,7 @@ NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/annou
NX_VEGA_REST_URL=https://api.n00.stagnet1.vega.xyz/api/v2/
NX_TENDERMINT_URL=https://tm.n01.stagnet1.vega.rocks
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.stagnet1.vega.xyz/websocket
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.stagnet1.vega.xyz/websocket
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=true
+4 -1
View File
@@ -16,4 +16,7 @@ NX_VEGA_REST_URL=https://api.n07.testnet.vega.xyz/api/v2/
NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40@o286262.ingest.sentry.io/5882996
NX_TENDERMINT_URL=https://tm.be.testnet.vega.xyz
NX_TENDERMINT_WEBSOCKET_URL=wss://be.testnet.vega.xyz/websocket
NX_TENDERMINT_WEBSOCKET_URL=wss://be.testnet.vega.xyz/websocket
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=true
+4 -1
View File
@@ -13,4 +13,7 @@ NX_VEGA_REST_URL=https://api-validators-testnet.vega.rocks/api/v2/
NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40@o286262.ingest.sentry.io/5882996
NX_TENDERMINT_URL=https://tm.be.validators-testnet.vega.rocks
NX_TENDERMINT_WEBSOCKET_URL=wss://be.validators-testnet.vega.
NX_TENDERMINT_WEBSOCKET_URL=wss://be.validators-testnet.vega.
# Cosmic elevator flags
NX_SUCCESSOR_MARKETS=false
@@ -0,0 +1 @@
export * from './json-diff';
@@ -0,0 +1,30 @@
import { render, screen, cleanup } from '@testing-library/react';
import { JsonDiff } from './json-diff';
describe('JsonDiff', () => {
afterEach(cleanup);
it('renders without crashing', () => {
render(<JsonDiff left={{}} right={{}} />);
expect(screen.getByTestId('json-diff')).toBeInTheDocument();
});
it('shows the correct message when both objects are identical', () => {
render(<JsonDiff left={{}} right={{}} />);
expect(screen.getByText('Data is identical')).toBeInTheDocument();
});
it('does not show the "identical" message when both objects are not identical', () => {
render(
<JsonDiff
left={{
name: 'test',
}}
right={{
name: 'test2',
}}
/>
);
expect(screen.queryByText('Data is identical')).not.toBeInTheDocument();
});
});
@@ -0,0 +1,46 @@
import { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { formatters, create } from 'jsondiffpatch';
import 'jsondiffpatch/dist/formatters-styles/html.css';
import 'jsondiffpatch/dist/formatters-styles/annotated.css';
export type JsonValue =
| string
| number
| boolean
| null
| JsonValue[]
| { [key: string]: JsonValue };
interface JsonDiffProps {
left: JsonValue;
right: JsonValue;
objectHash?: (obj: unknown) => string | undefined;
}
export const JsonDiff = ({ right, left, objectHash }: JsonDiffProps) => {
const { t } = useTranslation();
const [html, setHtml] = useState<string | undefined>();
useEffect(() => {
const delta = create({
objectHash,
}).diff(left, right);
if (delta) {
const deltaHtml = formatters.html.format(delta, left);
formatters.html.hideUnchanged();
setHtml(deltaHtml);
} else {
setHtml(undefined);
}
}, [right, left, objectHash]);
return html ? (
<div data-testid="json-diff" dangerouslySetInnerHTML={{ __html: html }} />
) : (
<p data-testid="json-diff">{t('dataIsIdentical')}</p>
);
};
+2 -7
View File
@@ -18,10 +18,7 @@ import { VegaWallet } from '../vega-wallet';
import { useLocation, useMatch } from 'react-router-dom';
import { useEffect } from 'react';
import { useTelemetryDialog } from '../telemetry-dialog/telemetry-dialog';
import {
ProtocolUpgradeCountdown,
ProtocolUpgradeCountdownMode,
} from '@vegaprotocol/proposals';
import { ProtocolUpgradeCountdown } from '@vegaprotocol/proposals';
export const SettingsLink = () => {
const { open, isOpen, close } = useTelemetryDialog();
@@ -68,9 +65,7 @@ export const Nav = ({ theme }: Pick<NavigationProps, 'theme'>) => {
actions={
<>
<SettingsLink />
<ProtocolUpgradeCountdown
mode={ProtocolUpgradeCountdownMode.IN_ESTIMATED_TIME_REMAINING}
/>
<ProtocolUpgradeCountdown />
</>
}
>
@@ -1,4 +1,8 @@
import { VegaConnectDialog, VegaManageDialog } from '@vegaprotocol/wallet';
import {
VegaConnectDialog,
VegaManageDialog,
ViewAsDialog,
} from '@vegaprotocol/wallet';
import {
AppStateActionType,
useAppState,
@@ -25,6 +29,8 @@ export const VegaWalletDialogs = () => {
})
}
/>
<ViewAsDialog connector={Connectors.view} />
</>
);
};
@@ -1,17 +0,0 @@
import { Link } from '@vegaprotocol/ui-toolkit';
import { useTranslation } from 'react-i18next';
import { ExternalLinks } from '@vegaprotocol/environment';
export const DownloadWalletPrompt = () => {
const { t } = useTranslation();
return (
<>
<h3 className="mt-4 mb-2">{t('getWallet')}</h3>
<p>
<Link className="text-neutral-500" href={ExternalLinks.VEGA_WALLET_URL}>
{t('getWalletLink')}
</Link>
</p>
</>
);
};
@@ -0,0 +1,25 @@
import { ButtonLink, Link } from '@vegaprotocol/ui-toolkit';
import { useTranslation } from 'react-i18next';
import { ExternalLinks } from '@vegaprotocol/environment';
import { useViewAsDialog } from '@vegaprotocol/wallet';
export const VegaWalletPrompt = () => {
const { t } = useTranslation();
const setViewAsDialog = useViewAsDialog((state) => state.setOpen);
return (
<>
<h3 className="mt-4 mb-2">{t('getWallet')}</h3>
<div className="flex flex-row gap-4">
<Link className="text-neutral-500" href={ExternalLinks.VEGA_WALLET_URL}>
{t('getWalletLink')}
</Link>
<ButtonLink
className="text-neutral-500"
onClick={() => setViewAsDialog(true)}
>
{t('viewAsParty')}
</ButtonLink>
</div>
</>
);
};
@@ -20,7 +20,7 @@ import {
WalletCardHeader,
WalletCardRow,
} from '../wallet-card';
import { DownloadWalletPrompt } from './download-wallet-prompt';
import { VegaWalletPrompt } from './vega-wallet-prompt';
import { usePollForDelegations } from './hooks';
import { useVegaWallet, useVegaWalletDialogStore } from '@vegaprotocol/wallet';
import { Button, ButtonLink } from '@vegaprotocol/ui-toolkit';
@@ -85,7 +85,7 @@ const VegaWalletNotConnected = () => {
>
{t('connectVegaWalletToUseAssociated')}
</Button>
<DownloadWalletPrompt />
<VegaWalletPrompt />
</>
);
};
@@ -853,5 +853,8 @@
"consensusNodes": "consensus nodes",
"activeNodes": "active nodes",
"Estimated time to upgrade": "Estimated time to upgrade",
"Upgraded at": "Upgraded at"
"Upgraded at": "Upgraded at",
"dataIsIdentical": "Data is identical",
"updatesToMarket": "Updates to market",
"viewAsParty": "View as party"
}
+1 -1
View File
@@ -20,6 +20,6 @@ export const downloadJson = (jsonString: string, proposalTitle: string) => {
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
} catch (error) {
console.log(error);
console.error(error);
}
};
@@ -22,6 +22,16 @@ import {
} from '../../test-helpers/mocks';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import type { MockedResponse } from '@apollo/client/testing';
import { FLAGS } from '@vegaprotocol/environment';
import { BrowserRouter } from 'react-router-dom';
jest.mock('@vegaprotocol/proposals', () => ({
...jest.requireActual('@vegaprotocol/proposals'),
useSuccessorMarketProposalDetails: () => ({
code: 'PARENT_CODE',
parentMarketId: 'PARENT_ID',
}),
}));
const renderComponent = (
proposal: ProposalQuery['proposal'],
@@ -30,20 +40,27 @@ const renderComponent = (
) =>
render(
<AppStateProvider>
<MockedProvider mocks={mocks}>
<VegaWalletContext.Provider value={mockWalletContext}>
<ProposalHeader
proposal={proposal}
isListItem={isListItem}
networkParams={mockNetworkParams}
/>
</VegaWalletContext.Provider>
</MockedProvider>
<BrowserRouter>
<MockedProvider mocks={mocks}>
<VegaWalletContext.Provider value={mockWalletContext}>
<ProposalHeader
proposal={proposal}
isListItem={isListItem}
networkParams={mockNetworkParams}
/>
</VegaWalletContext.Provider>
</MockedProvider>
</BrowserRouter>
</AppStateProvider>
);
describe('Proposal header', () => {
afterAll(() => {
jest.clearAllMocks();
});
it('Renders New market proposal', () => {
const mockedFlags = jest.mocked(FLAGS);
mockedFlags.SUCCESSOR_MARKETS = true;
renderComponent(
generateProposal({
rationale: {
@@ -76,6 +93,9 @@ describe('Proposal header', () => {
expect(screen.getByTestId('proposal-details')).toHaveTextContent(
'tGBP settled future.'
);
expect(screen.getByTestId('proposal-successor-info')).toHaveTextContent(
'PARENT_CODE'
);
});
it('Renders Update market proposal', () => {
@@ -11,6 +11,10 @@ import { ProposalInfoLabel } from '../proposal-info-label';
import { useUserVote } from '../vote-details/use-user-vote';
import { ProposalVotingStatus } from '../proposal-voting-status';
import type { NetworkParamsResult } from '@vegaprotocol/network-parameters';
import { useSuccessorMarketProposalDetails } from '@vegaprotocol/proposals';
import { FLAGS } from '@vegaprotocol/environment';
import Routes from '../../../routes';
import { Link } from 'react-router-dom';
export const ProposalHeader = ({
proposal,
@@ -39,6 +43,9 @@ export const ProposalHeader = ({
fallbackTitle = t('NewMarketProposal');
details = (
<>
{FLAGS.SUCCESSOR_MARKETS && (
<SuccessorCode proposalId={proposal?.id} />
)}
<span>
{t('Code')}: {change.instrument.code}.
</span>{' '}
@@ -138,7 +145,7 @@ export const ProposalHeader = ({
className="flex items-center gap-2"
data-testid={`user-voted-${voteState.toLowerCase()}`}
>
<div className="text-vega-green">
<div className="text-vega-green" data-testid="you-voted-icon">
<VegaIcon name={VegaIconNames.VOTE} size={24} />
</div>
<div>
@@ -181,3 +188,20 @@ export const ProposalHeader = ({
</>
);
};
const SuccessorCode = ({ proposalId }: { proposalId?: string | null }) => {
const { t } = useTranslation();
const successor = useSuccessorMarketProposalDetails(proposalId);
return successor.parentMarketId || successor.code ? (
<span className="block" data-testid="proposal-successor-info">
{t('Successor market to')}:{' '}
<Link
to={`${Routes.PROPOSALS}/${successor.parentMarketId}`}
className="hover:underline"
>
{successor.code || successor.parentMarketId}
</Link>
</span>
) : null;
};
@@ -0,0 +1 @@
export * from './proposal-market-changes';
@@ -0,0 +1,91 @@
import { render, fireEvent } from '@testing-library/react';
import {
ProposalMarketChanges,
applyImmutableKeysFromEarlierVersion,
} from './proposal-market-changes';
import type { JsonValue } from '../../../../components/json-diff';
describe('applyImmutableKeysFromEarlierVersion', () => {
it('returns an empty object if any argument is not an object or null', () => {
const earlierVersion: JsonValue = null;
const updatedVersion: JsonValue = null;
expect(
applyImmutableKeysFromEarlierVersion(earlierVersion, updatedVersion)
).toEqual({});
});
it('overrides updatedVersion with values from earlierVersion for immutable keys', () => {
const earlierVersion: JsonValue = {
decimalPlaces: 2,
positionDecimalPlaces: 3,
instrument: {
name: 'Instrument1',
future: {
settlementAsset: 'Asset1',
},
},
};
const updatedVersion: JsonValue = {
decimalPlaces: 3, // should be overridden by 2
positionDecimalPlaces: 4, // should be overridden by 3
instrument: {
name: 'Instrument2', // should be overridden by 'Instrument1'
future: {
settlementAsset: 'Asset2', // should be overridden by 'Asset1'
},
},
};
const expected: JsonValue = {
decimalPlaces: 2,
positionDecimalPlaces: 3,
instrument: {
name: 'Instrument1',
future: {
settlementAsset: 'Asset1',
},
},
};
expect(
applyImmutableKeysFromEarlierVersion(earlierVersion, updatedVersion)
).toEqual(expected);
});
});
describe('ProposalMarketChanges', () => {
it('renders correctly', () => {
const { getByTestId } = render(
<ProposalMarketChanges
originalProposal={{}}
latestEnactedProposal={{}}
updatedProposal={{}}
/>
);
expect(getByTestId('proposal-market-changes')).toBeInTheDocument();
});
it('JsonDiff is not visible when showChanges is false', () => {
const { queryByTestId } = render(
<ProposalMarketChanges
originalProposal={{}}
latestEnactedProposal={{}}
updatedProposal={{}}
/>
);
expect(queryByTestId('json-diff')).not.toBeInTheDocument();
});
it('JsonDiff is visible when showChanges is true', async () => {
const { getByTestId } = render(
<ProposalMarketChanges
originalProposal={{}}
latestEnactedProposal={{}}
updatedProposal={{}}
/>
);
fireEvent.click(getByTestId('proposal-market-changes-toggle'));
expect(getByTestId('json-diff')).toBeInTheDocument();
});
});
@@ -0,0 +1,86 @@
import cloneDeep from 'lodash/cloneDeep';
import set from 'lodash/set';
import get from 'lodash/get';
import { JsonDiff } from '../../../../components/json-diff';
import { useTranslation } from 'react-i18next';
import { useState } from 'react';
import { CollapsibleToggle } from '../../../../components/collapsible-toggle';
import { SubHeading } from '../../../../components/heading';
import type { JsonValue } from '../../../../components/json-diff';
const immutableKeys = [
'decimalPlaces',
'positionDecimalPlaces',
'instrument.name',
'instrument.future.settlementAsset',
];
export const applyImmutableKeysFromEarlierVersion = (
earlierVersion: JsonValue,
updatedVersion: JsonValue
) => {
if (
typeof earlierVersion !== 'object' ||
earlierVersion === null ||
typeof updatedVersion !== 'object' ||
updatedVersion === null
) {
// If either version is not an object or is null, return null or throw an error
return {};
}
const updatedVersionCopy = cloneDeep(updatedVersion);
// Overwrite the immutable keys in the updatedVersionCopy with the earlier values
immutableKeys.forEach((key) => {
set(updatedVersionCopy, key, get(earlierVersion, key));
});
return updatedVersionCopy;
};
interface ProposalMarketChangesProps {
originalProposal: JsonValue;
latestEnactedProposal: JsonValue | undefined;
updatedProposal: JsonValue;
}
export const ProposalMarketChanges = ({
originalProposal,
latestEnactedProposal,
updatedProposal,
}: ProposalMarketChangesProps) => {
const { t } = useTranslation();
const [showChanges, setShowChanges] = useState(false);
return (
<section data-testid="proposal-market-changes">
<CollapsibleToggle
toggleState={showChanges}
setToggleState={setShowChanges}
dataTestId={'proposal-market-changes-toggle'}
>
<SubHeading title={t('updatesToMarket')} />
</CollapsibleToggle>
{showChanges && (
<div className="mb-6">
<JsonDiff
left={latestEnactedProposal || originalProposal}
right={
latestEnactedProposal
? applyImmutableKeysFromEarlierVersion(
latestEnactedProposal,
updatedProposal
)
: applyImmutableKeysFromEarlierVersion(
originalProposal,
updatedProposal
)
}
/>
</div>
)}
</section>
);
};
@@ -25,7 +25,7 @@ import {
} from '@vegaprotocol/ui-toolkit';
import { SubHeading } from '../../../../components/heading';
import { CollapsibleToggle } from '../../../../components/collapsible-toggle';
import type { MarketInfoWithData } from '@vegaprotocol/markets';
import type { MarketInfo } from '@vegaprotocol/markets';
import type { DataSourceDefinition } from '@vegaprotocol/types';
import { create } from 'zustand';
@@ -45,8 +45,10 @@ export const useMarketDataDialogStore = create<MarketDataDialogState>(
export const ProposalMarketData = ({
marketData,
parentMarketData,
}: {
marketData: MarketInfoWithData;
marketData: MarketInfo;
parentMarketData?: MarketInfo;
}) => {
const { t } = useTranslation();
const { isOpen, open, close } = useMarketDataDialogStore();
@@ -58,8 +60,21 @@ export const ProposalMarketData = ({
const settlementData = marketData.tradableInstrument.instrument.product
.dataSourceSpecForSettlementData.data as DataSourceDefinition;
const parentSettlementData =
parentMarketData?.tradableInstrument.instrument?.product
?.dataSourceSpecForSettlementData?.data;
const terminationData = marketData.tradableInstrument.instrument.product
.dataSourceSpecForTradingTermination.data as DataSourceDefinition;
const parentTerminationData =
parentMarketData?.tradableInstrument.instrument?.product
?.dataSourceSpecForTradingTermination?.data;
const isParentSettlementDataEqual =
parentSettlementData !== undefined &&
isEqual(settlementData, parentSettlementData);
const isParentTerminationDataEqual =
parentTerminationData !== undefined &&
isEqual(terminationData, parentTerminationData);
const getSigners = (data: DataSourceDefinition) => {
if (data.sourceType.__typename === 'DataSourceDefinitionExternal') {
@@ -97,12 +112,22 @@ export const ProposalMarketData = ({
<AccordionItem
itemId="key-details"
title={t('Key details')}
content={<KeyDetailsInfoPanel market={marketData} />}
content={
<KeyDetailsInfoPanel
market={marketData}
parentMarket={parentMarketData}
/>
}
/>
<AccordionItem
itemId="instrument"
title={t('Instrument')}
content={<InstrumentInfoPanel market={marketData} />}
content={
<InstrumentInfoPanel
market={marketData}
parentMarket={parentMarketData}
/>
}
/>
{isEqual(
getSigners(settlementData),
@@ -115,6 +140,11 @@ export const ProposalMarketData = ({
<OracleInfoPanel
market={marketData}
type="settlementData"
parentMarket={
isParentSettlementDataEqual
? undefined
: parentMarketData
}
/>
}
/>
@@ -127,6 +157,11 @@ export const ProposalMarketData = ({
<OracleInfoPanel
market={marketData}
type="settlementData"
parentMarket={
isParentSettlementDataEqual
? undefined
: parentMarketData
}
/>
}
/>
@@ -135,11 +170,21 @@ export const ProposalMarketData = ({
itemId="termination-oracle"
title={t('Termination Oracle')}
content={
<OracleInfoPanel market={marketData} type="termination" />
<OracleInfoPanel
market={marketData}
type="termination"
parentMarket={
isParentTerminationDataEqual
? undefined
: parentMarketData
}
/>
}
/>
</>
)}
{/*Note: successor markets will not differ in their settlement*/}
{/*assets, so no need to pass in parent market data for comparison.*/}
<AccordionItem
itemId="settlement-asset"
title={t('Settlement asset')}
@@ -148,22 +193,42 @@ export const ProposalMarketData = ({
<AccordionItem
itemId="metadata"
title={t('Metadata')}
content={<MetadataInfoPanel market={marketData} />}
content={
<MetadataInfoPanel
market={marketData}
parentMarket={parentMarketData}
/>
}
/>
<AccordionItem
itemId="risk-model"
title={t('Risk model')}
content={<RiskModelInfoPanel market={marketData} />}
content={
<RiskModelInfoPanel
market={marketData}
parentMarket={parentMarketData}
/>
}
/>
<AccordionItem
itemId="risk-parameters"
title={t('Risk parameters')}
content={<RiskParametersInfoPanel market={marketData} />}
content={
<RiskParametersInfoPanel
market={marketData}
parentMarket={parentMarketData}
/>
}
/>
<AccordionItem
itemId="risk-factors"
title={t('Risk factors')}
content={<RiskFactorsInfoPanel market={marketData} />}
content={
<RiskFactorsInfoPanel
market={marketData}
parentMarket={parentMarketData}
/>
}
/>
{(
marketData.priceMonitoringSettings?.parameters?.triggers || []
@@ -174,6 +239,7 @@ export const ProposalMarketData = ({
content={
<PriceMonitoringBoundsInfoPanel
market={marketData}
parentMarket={parentMarketData}
triggerIndex={triggerIndex}
/>
}
@@ -183,13 +249,21 @@ export const ProposalMarketData = ({
itemId="liqudity-monitoring-parameters"
title={t('Liquidity monitoring parameters')}
content={
<LiquidityMonitoringParametersInfoPanel market={marketData} />
<LiquidityMonitoringParametersInfoPanel
market={marketData}
parentMarket={parentMarketData}
/>
}
/>
<AccordionItem
itemId="liquidity-price-range"
title={t('Liquidity price range')}
content={<LiquidityPriceRangeInfoPanel market={marketData} />}
content={
<LiquidityPriceRangeInfoPanel
market={marketData}
parentMarket={parentMarketData}
/>
}
/>
</Accordion>
</div>
@@ -200,7 +274,7 @@ export const ProposalMarketData = ({
title={marketData.tradableInstrument.instrument.code}
open={isOpen}
onChange={(isOpen) => (isOpen ? open() : close())}
size="medium"
size="large"
dataTestId="market-json-dialog"
>
<CopyWithTooltip text={JSON.stringify(marketData)}>
@@ -13,10 +13,11 @@ import Routes from '../../../routes';
import { ProposalMarketData } from '../proposal-market-data';
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import type { MarketInfoWithData } from '@vegaprotocol/markets';
import type { MarketInfo } from '@vegaprotocol/markets';
import type { AssetQuery } from '@vegaprotocol/assets';
import { removePaginationWrapper } from '@vegaprotocol/utils';
import { ProposalState } from '@vegaprotocol/types';
import { ProposalMarketChanges } from '../proposal-market-changes';
import type { NetworkParamsResult } from '@vegaprotocol/network-parameters';
export enum ProposalType {
@@ -30,10 +31,15 @@ export enum ProposalType {
export interface ProposalProps {
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
networkParams: Partial<NetworkParamsResult>;
newMarketData?: MarketInfoWithData | null;
newMarketData?: MarketInfo | null;
parentMarketData?: MarketInfo | null;
assetData?: AssetQuery | null;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
restData: any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
originalMarketProposalRestData?: any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
mostRecentlyEnactedAssociatedMarketProposal?: any;
}
export const Proposal = ({
@@ -41,7 +47,10 @@ export const Proposal = ({
networkParams,
restData,
newMarketData,
parentMarketData,
assetData,
originalMarketProposalRestData,
mostRecentlyEnactedAssociatedMarketProposal,
}: ProposalProps) => {
const { t } = useTranslation();
@@ -150,7 +159,28 @@ export const Proposal = ({
{newMarketData && (
<div className="mb-4">
<ProposalMarketData marketData={newMarketData} />
<ProposalMarketData
marketData={newMarketData}
parentMarketData={parentMarketData ? parentMarketData : undefined}
/>
</div>
)}
{proposal.terms.change.__typename === 'UpdateMarket' && (
<div className="mb-4">
<ProposalMarketChanges
originalProposal={
originalMarketProposalRestData?.data?.proposal?.terms?.newMarket
?.changes || {}
}
latestEnactedProposal={
mostRecentlyEnactedAssociatedMarketProposal?.node?.proposal
?.terms?.updateMarket?.changes || {}
}
updatedProposal={
restData?.data?.proposal?.terms?.updateMarket?.changes || {}
}
/>
</div>
)}
@@ -1,5 +1,5 @@
import { Link } from 'react-router-dom';
import { Button, VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
import { Button } from '@vegaprotocol/ui-toolkit';
import { differenceInHours, format, formatDistanceToNowStrict } from 'date-fns';
import { useTranslation } from 'react-i18next';
import { DATE_FORMAT_DETAILED } from '../../../../lib/date-formats';
@@ -115,10 +115,6 @@ export const ProposalsListItemDetails = ({
return (
<div className="mt-4 items-start text-sm">
<div className="text-vega-green">
<VegaIcon size={16} name={VegaIconNames.VOTE} />
<VegaIcon size={16} name={VegaIconNames.TICKET} />
</div>
<div className="flex items-center gap-2 text-vega-light-300 mb-2">
{voteDetails && <span data-testid="vote-details">{voteDetails}</span>}
{voteDetails && voteStatus && <span>&middot;</span>}
@@ -1,5 +1,5 @@
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import { useEffect } from 'react';
import { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import { Proposal } from '../components/proposal';
@@ -8,15 +8,23 @@ import { useProposalQuery } from './__generated__/Proposal';
import { useFetch } from '@vegaprotocol/react-helpers';
import { ENV } from '../../../config';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { marketInfoWithDataProvider } from '@vegaprotocol/markets';
import { marketInfoProvider } from '@vegaprotocol/markets';
import { useAssetQuery } from '@vegaprotocol/assets';
import {
NetworkParams,
useNetworkParams,
} from '@vegaprotocol/network-parameters';
import { useParentMarketIdQuery } from '@vegaprotocol/markets';
import { FLAGS } from '@vegaprotocol/environment';
import { useSuccessorMarketProposalDetails } from '@vegaprotocol/proposals';
export const ProposalContainer = () => {
const [
mostRecentlyEnactedAssociatedMarketProposal,
setMostRecentlyEnactedAssociatedMarketProposal,
] = useState(undefined);
const params = useParams<{ proposalId: string }>();
const {
params: networkParams,
loading: networkParamsLoading,
@@ -37,9 +45,11 @@ export const ProposalContainer = () => {
NetworkParams.governance_proposal_updateNetParam_requiredMajority,
NetworkParams.governance_proposal_freeform_requiredMajority,
]);
const {
state: { data: restData },
state: { data: restData, loading: restLoading, error: restError },
} = useFetch(`${ENV.rest}governance?proposalId=${params.proposalId}`);
const { data, loading, error, refetch } = useProposalQuery({
fetchPolicy: 'network-only',
errorPolicy: 'ignore',
@@ -47,12 +57,45 @@ export const ProposalContainer = () => {
skip: !params.proposalId,
});
const successor = useSuccessorMarketProposalDetails(params.proposalId);
const isSuccessor = !!successor?.parentMarketId || !!successor.code;
const {
state: {
data: originalMarketProposalRestData,
loading: originalMarketProposalRestLoading,
error: originalMarketProposalRestError,
},
} = useFetch(
`${ENV.rest}governance?proposalId=${
data?.proposal?.terms.change.__typename === 'UpdateMarket' &&
data?.proposal.terms.change.marketId
}`,
undefined,
true,
data?.proposal?.terms.change.__typename !== 'UpdateMarket'
);
const {
state: {
data: previouslyEnactedMarketProposalsRestData,
loading: previouslyEnactedMarketProposalsRestLoading,
error: previouslyEnactedMarketProposalsRestError,
},
} = useFetch(
`${ENV.rest}governances?proposalState=STATE_ENACTED&proposalType=TYPE_UPDATE_MARKET`,
undefined,
true,
data?.proposal?.terms.change.__typename !== 'UpdateMarket'
);
const {
data: newMarketData,
loading: newMarketLoading,
error: newMarketError,
} = useDataProvider({
dataProvider: marketInfoWithDataProvider,
dataProvider: marketInfoProvider,
skipUpdates: true,
variables: {
marketId: data?.proposal?.id || '',
@@ -60,6 +103,33 @@ export const ProposalContainer = () => {
},
});
const {
data: parentMarketId,
loading: parentMarketIdLoading,
error: parentMarketIdError,
} = useParentMarketIdQuery({
variables: {
marketId: newMarketData?.id || '',
},
skip: !FLAGS.SUCCESSOR_MARKETS || !isSuccessor || !newMarketData?.id,
});
const {
data: parentMarketData,
loading: parentMarketLoading,
error: parentMarketError,
} = useDataProvider({
dataProvider: marketInfoProvider,
skipUpdates: true,
variables: {
marketId: parentMarketId?.market?.parentMarketID || '',
skip:
!FLAGS.SUCCESSOR_MARKETS ||
!isSuccessor ||
!parentMarketId?.market?.parentMarketID,
},
});
const {
data: assetData,
loading: assetLoading,
@@ -79,6 +149,39 @@ export const ProposalContainer = () => {
),
});
useEffect(() => {
if (
previouslyEnactedMarketProposalsRestData &&
data?.proposal?.terms.change.__typename === 'UpdateMarket'
) {
const change = data?.proposal?.terms?.change as { marketId: string };
const filteredProposals =
// @ts-ignore rest data is not typed
previouslyEnactedMarketProposalsRestData.connection.edges.filter(
// @ts-ignore rest data is not typed
({ node }) =>
node?.proposal?.terms?.updateMarket?.marketId === change.marketId
);
const sortedProposals = filteredProposals.sort(
// @ts-ignore rest data is not typed
(a, b) =>
new Date(a?.node?.terms?.enactmentTimestamp).getTime() -
new Date(b?.node?.terms?.enactmentTimestamp).getTime()
);
setMostRecentlyEnactedAssociatedMarketProposal(
sortedProposals[sortedProposals.length - 1]
);
}
}, [
previouslyEnactedMarketProposalsRestData,
params.proposalId,
data?.proposal?.terms.change.__typename,
data?.proposal?.terms.change,
]);
useEffect(() => {
const interval = setInterval(refetch, 2000);
return () => clearInterval(interval);
@@ -87,14 +190,44 @@ export const ProposalContainer = () => {
return (
<AsyncRenderer
loading={
loading || newMarketLoading || assetLoading || networkParamsLoading
loading ||
newMarketLoading ||
assetLoading ||
networkParamsLoading ||
parentMarketIdLoading ||
parentMarketLoading ||
(restLoading ? (restLoading as boolean) : false) ||
(originalMarketProposalRestLoading
? (originalMarketProposalRestLoading as boolean)
: false) ||
(previouslyEnactedMarketProposalsRestLoading
? (previouslyEnactedMarketProposalsRestLoading as boolean)
: false)
}
error={
error ||
newMarketError ||
assetError ||
networkParamsError ||
parentMarketIdError ||
parentMarketError ||
restError ||
originalMarketProposalRestError ||
previouslyEnactedMarketProposalsRestError
}
error={error || newMarketError || assetError || networkParamsError}
data={{
...data,
...networkParams,
...(newMarketData ? { newMarketData } : {}),
...(parentMarketData ? { parentMarketData } : {}),
...(assetData ? { assetData } : {}),
...(restData ? { restData } : {}),
...(originalMarketProposalRestData
? { originalMarketProposalRestData }
: {}),
...(previouslyEnactedMarketProposalsRestData
? { previouslyEnactedMarketProposalsRestData }
: {}),
}}
>
{data?.proposal ? (
@@ -103,7 +236,12 @@ export const ProposalContainer = () => {
networkParams={networkParams}
restData={restData}
newMarketData={newMarketData}
parentMarketData={parentMarketData}
assetData={assetData}
originalMarketProposalRestData={originalMarketProposalRestData}
mostRecentlyEnactedAssociatedMarketProposal={
mostRecentlyEnactedAssociatedMarketProposal
}
/>
) : (
<ProposalNotFound />
@@ -5,7 +5,6 @@ import {
RewardsTable,
} from '../shared-rewards-table-assets/shared-rewards-table-assets';
import type { EpochIndividualReward } from './generate-epoch-individual-rewards-list';
import { useTranslation } from 'react-i18next';
interface EpochIndividualRewardsGridProps {
data: EpochIndividualReward;
@@ -22,14 +21,11 @@ interface RewardItemProps {
const DisplayReward = ({
reward,
decimals,
percentageOfTotal,
}: {
reward: string;
decimals: number;
percentageOfTotal?: string;
}) => {
const { t } = useTranslation();
if (Number(reward) === 0) {
return <span className="text-vega-dark-300">-</span>;
}
@@ -39,23 +35,12 @@ const DisplayReward = ({
description={
<div className="flex flex-col items-start">
<span>{formatNumber(toBigNum(reward, decimals), decimals)}</span>
{percentageOfTotal && (
<span className="text-vega-dark-300">
({percentageOfTotal}% {t('ofTotalDistributed')})
</span>
)}
</div>
}
>
<button>
<div className="flex flex-col items-start">
<span>{formatNumber(toBigNum(reward, decimals), 4)}</span>
{percentageOfTotal && (
<span className="text-vega-dark-300">
({formatNumber(percentageOfTotal, 4).toString()}
%)
</span>
)}
</div>
</button>
</Tooltip>
@@ -168,17 +168,6 @@ describe('generateEpochIndividualRewardsList', () => {
expect(result2[1].epoch).toEqual(1);
});
it('correctly calculates the total value of rewards for an asset', () => {
const rewards = [reward1, reward4];
const result = generateEpochIndividualRewardsList({
rewards,
epochId: 1,
epochRewardSummaries: [],
});
expect(result[0].rewards[0].totalAmount).toEqual('200');
});
it('returns data in the expected shape', () => {
// Just sanity checking the whole structure here
const rewards = [reward1, reward2, reward3, reward4];
@@ -458,69 +447,4 @@ describe('generateEpochIndividualRewardsList', () => {
},
]);
});
it('correctly calculates the percentage of two or more rewards by referencing the total rewards amount', () => {
const result = generateEpochIndividualRewardsList({
rewards: [
// reward1 is 100 usd, which is 10% of the total rewards amount
reward1,
{
rewardType: AccountType.ACCOUNT_TYPE_GLOBAL_REWARD,
amount: '200',
percentageOfTotal: '0.2',
receivedAt: new Date(),
asset: { id: 'usd', symbol: 'USD', name: 'USD', decimals: 6 },
party: { id: 'blah' },
epoch: { id: '1' },
},
],
epochId: 1,
epochRewardSummaries: [
{
__typename: 'EpochRewardSummary',
epoch: 1,
assetId: 'usd',
amount: '1000',
rewardType: AccountType.ACCOUNT_TYPE_GLOBAL_REWARD,
},
],
});
expect(result[0]).toEqual({
epoch: 1,
rewards: [
{
asset: 'USD',
decimals: 6,
totalAmount: '300',
rewardTypes: {
[AccountType.ACCOUNT_TYPE_FEES_INFRASTRUCTURE]: {
amount: '0',
percentageOfTotal: '0',
},
[AccountType.ACCOUNT_TYPE_REWARD_LP_RECEIVED_FEES]: {
amount: '0',
percentageOfTotal: '0',
},
[AccountType.ACCOUNT_TYPE_GLOBAL_REWARD]: {
amount: '300',
percentageOfTotal: '30',
},
[AccountType.ACCOUNT_TYPE_REWARD_MAKER_PAID_FEES]: {
amount: '0',
percentageOfTotal: '0',
},
[AccountType.ACCOUNT_TYPE_REWARD_MAKER_RECEIVED_FEES]: {
amount: '0',
percentageOfTotal: '0',
},
[AccountType.ACCOUNT_TYPE_REWARD_MARKET_PROPOSERS]: {
amount: '0',
percentageOfTotal: '0',
},
},
},
],
});
});
});
@@ -59,7 +59,6 @@ export const generateEpochIndividualRewardsList = ({
const epochIndividualRewards = rewards.reduce((acc, reward) => {
const epochId = reward.epoch.id;
const assetName = reward.asset.name;
const assetId = reward.asset.id;
const assetDecimals = reward.asset.decimals;
const rewardType = reward.rewardType;
const amount = reward.amount;
@@ -77,13 +76,6 @@ export const generateEpochIndividualRewardsList = ({
const epoch = acc.get(epochId);
// matchingTotalReward is the total awarded for all users for the reward type in the epoch of the asset
const matchingTotalRewardAmount = epochRewardSummaries.find(
(summary) =>
summary.epoch === Number(epochId) &&
summary.assetId === assetId &&
summary.rewardType === rewardType
)?.amount;
let asset = epoch?.rewards.find((r) => r.asset === assetName);
if (!asset) {
@@ -106,14 +98,7 @@ export const generateEpochIndividualRewardsList = ({
asset.rewardTypes[rewardType] = {
amount: newAmount,
percentageOfTotal: matchingTotalRewardAmount
? new BigNumber(newAmount)
.dividedBy(matchingTotalRewardAmount)
.multipliedBy(100)
.toString()
: // this should never be reached, if there's an individual reward there should
// always be a reward total from the api too, but set it as a fallback just in case
percentageOfTotal,
percentageOfTotal: percentageOfTotal,
};
}
@@ -61,8 +61,6 @@ export const RewardsPage = () => {
error: paramsError,
} = useNetworkParams([NetworkParams.reward_staking_delegation_payoutDelay]);
console.log('params', params);
const payoutDuration = useMemo(() => {
if (!params) {
return 0;
@@ -8,9 +8,10 @@ import { TxState } from '../../../hooks/transaction-reducer';
import { useTransaction } from '../../../hooks/use-transaction';
import { BigNumber } from '../../../lib/bignumber';
import { AssociateInfo } from './associate-info';
import { removeDecimal, toBigNum } from '@vegaprotocol/utils';
import { toBigNum } from '@vegaprotocol/utils';
import type { EthereumConfig } from '@vegaprotocol/web3';
import { useBalances } from '../../../lib/balances/balances-store';
import { MaxUint256 } from '@ethersproject/constants';
export const WalletAssociate = ({
perform,
@@ -42,7 +43,7 @@ export const WalletAssociate = ({
} = useTransaction(() => {
return token.approve(
ethereumConfig.staking_bridge_contract.address,
removeDecimal('1000000', decimals).toString()
MaxUint256.toString()
);
});
+31 -1
View File
@@ -62,7 +62,7 @@
cursor: pointer;
}
/* Styles required to (effectively) un-override the
/* Styles required to (effectively) un-override the
* reset styles so that the Proposal description fields
* render as you'd expect them to.
*
@@ -97,3 +97,33 @@
.react-markdown-container ul li {
list-style: circle;
}
.jsondiffpatch-delta,
.jsondiffpatch-delta pre {
font-family: 'Roboto Mono', monospace !important;
}
.jsondiffpatch-delta pre {
padding: 0 0.25em !important;
}
.jsondiffpatch-added .jsondiffpatch-property-name,
.jsondiffpatch-added .jsondiffpatch-value pre,
.jsondiffpatch-modified .jsondiffpatch-right-value pre,
.jsondiffpatch-textdiff-added {
background: theme(colors.vega.green[650]) !important;
color: theme(colors.white) !important;
}
.jsondiffpatch-deleted .jsondiffpatch-property-name,
.jsondiffpatch-deleted pre,
.jsondiffpatch-modified .jsondiffpatch-left-value pre,
.jsondiffpatch-textdiff-deleted {
background: theme(colors.vega.pink[650]) !important;
color: theme(colors.white) !important;
}
.jsondiffpatch-moved .jsondiffpatch-moved-destination {
background: theme(colors.vega.yellow[350]) !important;
color: theme(colors.vega.dark[200]) !important;
}
@@ -57,7 +57,7 @@ const ROWS = [
export const HealthDialog = ({ onChange, isOpen }: HealthDialogProps) => {
return (
<Dialog size="medium" open={isOpen} onChange={onChange}>
<Dialog size="large" open={isOpen} onChange={onChange}>
<h1 className="text-2xl mb-5 pr-2 font-medium font-alpha uppercase">
{t('Health')}
</h1>
@@ -535,6 +535,7 @@ function checkIfDataAndTimeOfCreationAndUpdateIsEqual(date: string) {
// unexpected latency
const minBefore = subSeconds(new Date(), 5);
const maxAfter = addSeconds(new Date(), 5);
// eslint-disable-next-line no-console
console.log(maxAfter);
const date = new Date($dateTime.toString());
expect(isAfter(date, minBefore) && isBefore(date, maxAfter)).to.equal(
@@ -2,14 +2,14 @@ describe('charts', { tags: '@smoke' }, () => {
before(() => {
cy.mockTradingPage();
cy.mockSubscription();
cy.setOnBoardingViewed();
cy.visit('/#/markets/market-0');
cy.wait('@Markets');
cy.getByTestId('Depth').click();
});
it('can see market depth chart', () => {
// 6006-DEPC-001
cy.getByTestId('Depth').click();
cy.getByTestId('tab-depth').should('be.visible');
cy.get('.depth-chart-module_canvas__260De').should('be.visible');
});
});
@@ -202,6 +202,7 @@ describe('Closed markets', { tags: '@smoke' }, () => {
const specDataConnection = createDataConnection();
before(() => {
cy.setOnBoardingViewed();
cy.mockGQL((req) => {
aliasGQLQuery(req, 'ChainId', chainIdQuery());
aliasGQLQuery(req, 'Statistics', statisticsQuery());
@@ -363,12 +364,11 @@ describe('Closed markets', { tags: '@smoke' }, () => {
.first()
.find('button svg')
.should('exist');
if (Cypress.env('NX_SUCCESSOR_MARKETS')) {
cy.get(rowSelector)
.find('[col-id="successorMarketID"]')
.find('[col-id="successorMarket"]')
.first()
.should('have.text', ' - ');
.should('have.text', '-');
}
});
@@ -456,6 +456,7 @@ describe('no closed markets', { tags: '@smoke', testIsolation: true }, () => {
before(() => {
cy.mockTradingPage();
cy.mockSubscription();
cy.setOnBoardingViewed();
cy.visit('/#/markets/all');
cy.get('[data-testid="Closed markets"]').click();
});
@@ -104,11 +104,11 @@ describe('deposit actions', { tags: '@smoke' }, () => {
cy.visit('/#/markets/market-1');
});
it('Deposit to trade is visble', () => {
it('Deposit to trade is visible', () => {
cy.getByTestId('Collateral').click();
cy.contains('[data-testid="deposit"]', 'Deposit')
.should('be.visible')
.click();
cy.get('[row-id="asset-id"]').contains('tEURO').should('be.visible');
cy.contains('[data-testid="deposit"]', 'Deposit').should('be.visible');
cy.contains('[data-testid="deposit"]', 'Deposit').click();
cy.getByTestId('deposit-submit').should('be.visible');
});
});
@@ -4,6 +4,7 @@ const nodeHealthTrigger = 'node-health-trigger';
describe('home', { tags: '@regression' }, () => {
before(() => {
cy.setOnBoardingViewed();
cy.mockTradingPage();
cy.mockSubscription();
cy.visit('/');
@@ -68,22 +69,4 @@ describe('home', { tags: '@regression' }, () => {
cy.getByTestId('connect').click();
});
});
describe('Network switcher', () => {
before(() => {
cy.mockTradingPage();
cy.mockSubscription();
cy.visit('/');
});
it('switch to fairground network and check status & incidents link', () => {
// 0006-NETW-002
// 0006-NETW-003
cy.getByTestId('navigation')
.find('[data-testid="network-switcher"]')
.should('have.text', 'Custom')
.click();
cy.getByTestId('network-item').contains('Fairground testnet');
});
});
});
+38 -37
View File
@@ -2,7 +2,7 @@ import { aliasGQLQuery } from '@vegaprotocol/cypress';
import type { ProposalListFieldsFragment } from '@vegaprotocol/proposals';
import * as Schema from '@vegaprotocol/types';
const dialogContent = 'dialog-content';
const dialogContent = 'welcome-dialog';
const generateProposal = (code: string): ProposalListFieldsFragment => ({
__typename: 'Proposal',
@@ -140,43 +140,43 @@ describe('home', { tags: '@regression' }, () => {
cy.wait('@MarketsData');
});
it('redirects to market/all and displays welcome notice', () => {
it('close welcome dialog should redirect to market/all', () => {
cy.url().should('eq', Cypress.config().baseUrl + `/#/markets/all`);
cy.getByTestId('welcome-notice-title').should(
'contain.text',
'Welcome to Console'
);
});
});
describe('no proposal nor markets found', () => {
it('there are welcome text and a link to propose market', () => {
cy.mockGQL((req) => {
const data = {
marketsConnection: {
__typename: 'MarketConnection',
edges: [],
},
};
aliasGQLQuery(req, 'Markets', data);
aliasGQLQuery(req, 'MarketsData', data);
aliasGQLQuery(req, 'ProposalsList', {
proposalsConnection: {
__typename: 'ProposalsConnection',
edges: null,
},
});
cy.getByTestId('welcome-dialog').should('be.visible');
cy.getByTestId('welcome-title').should('contain.text', 'Console CUSTOM');
cy.getByTestId('browse-markets-button').should('not.be.disabled');
cy.getByTestId('get-started-banner').should('be.visible');
cy.getByTestId('get-started-button').should('not.be.disabled');
cy.getByTestId('dialog-close').click();
cy.url().should('eq', Cypress.config().baseUrl + `/#/markets/all`);
cy.window().then((window) => {
expect(window.localStorage.getItem('vega_onboarding_viewed')).to.equal(
'true'
);
});
cy.visit('/');
cy.wait('@Markets');
cy.wait('@MarketsData');
cy.getByTestId('welcome-notice-title').should(
'contain.text',
'Welcome to Console'
);
cy.getByTestId('external-link')
.contains('Propose a market')
.should('exist');
});
it('click browse markets button should redirect to market/all', () => {
cy.getByTestId('welcome-dialog').should('be.visible');
cy.getByTestId('browse-markets-button').click();
cy.url().should('eq', Cypress.config().baseUrl + `/#/markets/all`);
cy.window().then((window) => {
expect(window.localStorage.getItem('vega_onboarding_viewed')).to.equal(
'true'
);
});
});
it('click get started button should open connect dialog', () => {
cy.getByTestId('welcome-dialog').should('be.visible');
cy.getByTestId('get-started-button').click();
cy.url().should('eq', Cypress.config().baseUrl + `/#/markets/all`);
cy.window().then((window) => {
expect(window.localStorage.getItem('vega_onboarding_viewed')).to.equal(
'true'
);
});
cy.getByTestId('wallet-dialog-title').should('contain.text', 'Connect');
});
});
@@ -185,7 +185,7 @@ describe('home', { tags: '@regression' }, () => {
cy.window().then((window) => {
window.localStorage.setItem('marketId', 'market-1');
cy.visit('/');
cy.wait('@Markets');
cy.getByTestId('dialog-close').click();
cy.location('hash').should('equal', '#/markets/market-1');
cy.getByTestId(dialogContent).should('not.exist');
});
@@ -199,6 +199,7 @@ describe('home', { tags: '@regression' }, () => {
});
cy.visit('/');
cy.wait('@Markets');
cy.getByTestId('dialog-close').click();
cy.location('hash').should('equal', '#/markets/market-not-existing');
cy.getByTestId(dialogContent).should('not.exist');
});
@@ -3,12 +3,13 @@ import type { MarketsQuery } from '@vegaprotocol/markets';
import * as Schema from '@vegaprotocol/types';
const rowSelector =
'[data-testid="tab-all-markets"] .ag-center-cols-container .ag-row';
'[data-testid="tab-open-markets"] .ag-center-cols-container .ag-row';
const colInstrumentCode = '[col-id="tradableInstrument.instrument.code"]';
describe('markets all table', { tags: '@smoke' }, () => {
beforeEach(() => {
cy.clearLocalStorage().then(() => {
cy.setOnBoardingViewed();
cy.mockTradingPage(
Schema.MarketState.STATE_ACTIVE,
Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION,
@@ -27,13 +28,14 @@ describe('markets all table', { tags: '@smoke' }, () => {
'Description',
'Trading mode',
'Status',
'Successor market',
'Best bid',
'Best offer',
'Mark price',
'Settlement asset',
'',
];
cy.getByTestId('tab-all-markets').within(($headers) => {
cy.getByTestId('tab-open-markets').within(($headers) => {
cy.wrap($headers)
.get('.ag-header-cell-text')
.each(($header, i) => {
@@ -43,7 +45,7 @@ describe('markets all table', { tags: '@smoke' }, () => {
});
it('markets tab should be rendered properly', () => {
cy.get('[data-testid="All markets"]').should(
cy.get('[data-testid="Open markets"]').should(
'have.attr',
'data-state',
'active'
@@ -165,7 +167,7 @@ describe('markets all table', { tags: '@smoke' }, () => {
'ETHBTC.QM21',
'SOLUSD',
];
cy.get('[data-testid="All markets"]').click({ force: true });
cy.get('[data-testid="Open markets"]').click({ force: true });
cy.url().should('eq', Cypress.config('baseUrl') + '/#/markets/all');
cy.contains('AAPL.MF21').should('be.visible');
cy.get('.ag-header-cell-label').contains('Market').click(); // sort by market name
@@ -190,7 +192,7 @@ describe('markets all table', { tags: '@smoke' }, () => {
});
});
describe('no all markets', { tags: '@smoke', testIsolation: true }, () => {
describe('no open markets', { tags: '@smoke', testIsolation: true }, () => {
before(() => {
cy.mockTradingPage();
const markets: MarketsQuery = {};
@@ -198,11 +200,12 @@ describe('no all markets', { tags: '@smoke', testIsolation: true }, () => {
aliasGQLQuery(req, 'Markets', markets);
});
cy.mockSubscription();
cy.setOnBoardingViewed();
cy.visit('/#/markets/all');
});
it.skip('can see no markets message', () => {
// 6001-MARK-048
cy.getByTestId('tab-all-markets').should('contain.text', 'No markets');
cy.getByTestId('tab-open-markets').should('contain.text', 'No markets');
});
});
@@ -1,5 +1,6 @@
import { MarketTradingModeMapping } from '@vegaprotocol/types';
import { MarketState } from '@vegaprotocol/types';
import compact from 'lodash/compact';
const accordionContent = 'accordion-content';
const blockExplorerLink = 'block-explorer-link';
@@ -20,6 +21,7 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
});
before(() => {
cy.setOnBoardingViewed();
cy.mockTradingPage(MarketState.STATE_ACTIVE);
cy.mockSubscription();
cy.visit('/#/markets/market-0');
@@ -66,20 +68,24 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
// 6002-MDET-201
cy.getByTestId(marketTitle).contains('Key details').click();
validateMarketDataRow(0, 'Name', 'BTCUSD Monthly (30 Jun 2022)');
validateMarketDataRow(1, 'Market ID', 'market-0');
const rows: [string, string][] = compact([
['Name', 'BTCUSD Monthly (30 Jun 2022)'],
['Market ID', 'market-0'],
Cypress.env('NX_SUCCESSOR_MARKETS') && ['Parent Market ID', 'PARENT-A'],
Cypress.env('NX_SUCCESSOR_MARKETS') && [
'Insurance Pool Fraction',
'0.75',
],
['Trading Mode', MarketTradingModeMapping.TRADING_MODE_CONTINUOUS],
['Market Decimal Places', '5'],
['Position Decimal Places', '0'],
['Settlement Asset Decimal Places', '5'],
]);
if (Cypress.env('NX_SUCCESSOR_MARKETS')) {
validateMarketDataRow(2, 'Parent Market ID', 'PARENT-A');
for (const rowNumber in rows) {
const [name, value] = rows[rowNumber];
validateMarketDataRow(Number(rowNumber), name, value);
}
validateMarketDataRow(
3,
'Trading Mode',
MarketTradingModeMapping.TRADING_MODE_CONTINUOUS
);
validateMarketDataRow(4, 'Market Decimal Places', '5');
validateMarketDataRow(5, 'Position Decimal Places', '0');
validateMarketDataRow(6, 'Settlement Asset Decimal Places', '5');
});
it('instrument displayed', () => {
@@ -36,6 +36,7 @@ const headers = [
describe('liquidity table - trading', { tags: '@smoke' }, () => {
before(() => {
cy.setOnBoardingViewed();
cy.mockSubscription();
cy.mockTradingPage(
Schema.MarketState.STATE_ACTIVE,
@@ -119,6 +120,7 @@ describe('liquidity table - trading', { tags: '@smoke' }, () => {
describe('liquidity table view', { tags: '@smoke' }, () => {
before(() => {
cy.setOnBoardingViewed();
cy.mockSubscription();
cy.mockTradingPage(
Schema.MarketState.STATE_ACTIVE,
@@ -12,6 +12,7 @@ describe('markets selector', { tags: '@smoke' }, () => {
cy.window().then((window) => {
window.localStorage.setItem('marketId', 'market-1');
});
cy.setOnBoardingViewed();
cy.mockTradingPage(
MarketState.STATE_ACTIVE,
MarketTradingMode.TRADING_MODE_MONITORING_AUCTION,
@@ -30,6 +30,7 @@ describe('Market trading page', () => {
Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY_TARGET_NOT_MET
);
cy.mockSubscription();
cy.setOnBoardingViewed();
cy.visit('/#/markets/market-0');
cy.wait('@MarketData');
cy.getByTestId(marketSummaryBlock).should('be.visible');
@@ -9,6 +9,7 @@ describe('markets proposed table', { tags: '@smoke' }, () => {
before(() => {
cy.mockTradingPage();
cy.mockSubscription();
cy.setOnBoardingViewed();
cy.visit('/#/markets/all');
cy.get('[data-testid="Proposed markets"]').click();
});
@@ -19,6 +20,7 @@ describe('markets proposed table', { tags: '@smoke' }, () => {
'Description',
'Settlement asset',
'State',
'Parent market',
'Voting',
'Closing date',
'Enactment date',
@@ -83,7 +85,7 @@ describe('markets proposed table', { tags: '@smoke' }, () => {
.find('button')
.click();
const dropdownContent = '[data-testid="market-actions-content"]';
const dropdownContent = '[data-testid="proposal-actions-content"]';
const dropdownContentItem = '[role="menuitem"]';
// 6001-MARK-059
@@ -99,7 +101,6 @@ describe('markets proposed table', { tags: '@smoke' }, () => {
'VEGA_TOKEN_URL'
)}/proposals/e9ec6d5c46a7e7bcabf9ba7a893fa5a5eeeec08b731f06f7a6eb7bf0e605b829`
);
cy.getByTestId('market-actions-content').click();
});
// 6001-MARK-060
@@ -213,11 +214,13 @@ describe('no markets proposed', { tags: '@smoke', testIsolation: true }, () => {
aliasGQLQuery(req, 'ProposalsList', proposal);
});
cy.mockSubscription();
cy.visit('/#/markets/all');
cy.get('[data-testid="Proposed markets"]').click();
cy.setOnBoardingViewed();
});
it('can see no markets message', () => {
cy.visit('/#/markets/all');
cy.get('[data-testid="Proposed markets"]').click();
// 6001-MARK-061
cy.getByTestId('tab-proposed-markets').should('contain.text', 'No markets');
});
@@ -12,6 +12,7 @@ describe('markets table', { tags: '@smoke' }, () => {
Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY_TARGET_NOT_MET
);
cy.mockSubscription();
cy.setOnBoardingViewed();
cy.visit('/#/markets/all');
});
});
@@ -1,114 +0,0 @@
import { mockConnectWallet } from '@vegaprotocol/cypress';
describe('Navbar', { tags: '@smoke' }, () => {
beforeEach(() => {
cy.clearAllLocalStorage();
cy.mockTradingPage();
cy.mockSubscription();
cy.visit('/');
cy.wait('@Markets');
cy.wait('@MarketsData');
});
const pages = [
{ name: 'Markets', link: '#/markets/all' },
{ name: 'Trading', link: '#/markets' },
{ name: 'Portfolio', link: '#/portfolio' },
];
describe('desktop view', () => {
pages.forEach(({ name, link }) => {
it(`${name} should be correctly rendered`, () => {
cy.get('nav')
.find(`a[data-testid=${name}]:visible`)
.then((element) => {
cy.wrap(element).click();
cy.location('hash').should('contain', link);
});
});
});
it('Resources dropdown should be correctly rendered', () => {
const resourceSelector = 'ul li:contains(Resources)';
['Docs', 'Give Feedback'].forEach((text, index) => {
cy.get('nav').find(resourceSelector).contains('Resources').click();
cy.get('nav')
.find(resourceSelector)
.find('.navigation-content li')
.eq(index)
.find('a')
.then((element) => {
expect(element.attr('target')).to.eq('_blank');
expect(element.attr('href')).to.not.be.empty;
expect(element.text()).to.eq(text);
});
});
});
it('Disclaimer should be presented after choosing from menu', () => {
cy.get('nav')
.find('ul li:contains(Resources)')
.contains('Resources')
.click();
cy.getByTestId('Disclaimer').eq(0).click();
cy.location('hash').should('equal', '#/disclaimer');
cy.get('p').contains(
'Vega is a decentralised peer-to-peer protocol that can be used to trade derivatives with cryptoassets.'
);
});
});
describe('mobile view', () => {
const viewportHeight = Cypress.config('viewportHeight');
const viewportWidth = Cypress.config('viewportWidth');
before(() => {
// a little hack to keep the viewport size between tests (cypress bug)
Cypress.config({
viewportWidth: 560,
viewportHeight: 890,
});
cy.viewport(560, 890);
});
describe('wallet drawer', () => {
it('wallet drawer should be correctly rendered', () => {
mockConnectWallet();
cy.connectVegaWallet(true);
cy.getByTestId('connect-vega-wallet-mobile').click();
cy.getByTestId('wallets-drawer').should('be.visible');
cy.getByTestId('wallets-drawer').within((el) => {
cy.wrap(el).get('button').contains('Disconnect').click();
});
cy.getByTestId('wallets-drawer').should('not.be.visible');
});
});
describe('menu drawer', () => {
pages.forEach(({ name, link }) => {
it(`${name} should be correctly rendered`, () => {
cy.getByTestId('button-menu-drawer').click();
cy.getByTestId('menu-drawer').should('be.visible');
cy.getByTestId('menu-drawer').within((el) => {
cy.wrap(el).getByTestId(name).click();
cy.location('hash').should('contain', link);
});
});
});
it('Menu drawer should not be visible until opened', () => {
cy.getByTestId('menu-drawer').should('not.be.visible');
cy.getByTestId('button-menu-drawer').click();
cy.getByTestId('menu-drawer').should('be.visible');
cy.getByTestId('button-menu-drawer').click();
cy.getByTestId('menu-drawer').should('not.be.visible');
});
});
after(() => {
// a little hack to keep the viewport size between tests (cypress bug)
Cypress.config({
viewportWidth,
viewportHeight,
});
});
});
});
@@ -6,6 +6,7 @@ const oracleFullProfile = 'oracle-full-profile';
describe('oracle information', { tags: '@smoke' }, () => {
before(() => {
cy.setOnBoardingViewed();
cy.mockTradingPage(
MarketState.STATE_ACTIVE,
undefined,
@@ -14,6 +14,7 @@ const resPrice = 'price-990';
describe('order book', { tags: '@smoke' }, () => {
before(() => {
cy.setOnBoardingViewed();
cy.mockTradingPage();
cy.mockSubscription();
cy.visit('/#/markets/market-0');
@@ -4,6 +4,7 @@ describe('Settings page', { tags: '@smoke' }, () => {
cy.mockTradingPage();
cy.mockSubscription();
cy.setOnBoardingViewed();
cy.visit('/');
// Only click if not already active otherwise sidebar will close
@@ -77,6 +77,7 @@ function getButtonSelectorByText(text: string): string {
beforeEach(() => {
cy.mockTradingPage();
cy.mockSubscription();
cy.setOnBoardingViewed();
cy.visit('/#/markets/market-0');
cy.wait('@Markets');
});
@@ -127,7 +128,7 @@ describe(
{
name: 'Price monitoring bounds',
infoText:
'Price Monitoring Bounds: Min 162.56291Max 182.96869Reference 172.47489',
'Price Monitoring Bounds 1: Min 162.56291Max 182.96869Reference 172.47489',
},
];
@@ -154,7 +155,10 @@ describe(
{ name: 'Volume', infoText: 'Volume: 55,000' },
];
cy.get(indicatorInfo).eq(1).realHover();
cy.get('.close-button-module_closeButton__2ifkl').click({ force: true });
cy.get('.chart__wrapper [data-testid="split-view-view"]')
.last()
.find('[role="button"][title="Close"]')
.click({ force: true });
cy.get(indicatorInfo).should('have.length', 1);
checkMenuItemCheckbox('Studies', studyInfo);
@@ -171,6 +175,7 @@ describe(
.invoke('text')
.then((text) => {
const actualDate = text.slice(0, -67);
// eslint-disable-next-line no-console
console.log(actualDate);
const actualOhlc = text.slice(-67);
assert.isTrue(expectedDateRegex.test(actualDate));
@@ -14,6 +14,7 @@ describe('deal ticket basics', { tags: '@smoke' }, () => {
cy.mockTradingPage();
cy.mockSubscription();
cy.clearAllLocalStorage();
cy.setOnBoardingViewed();
cy.visit('/#/markets/market-0');
cy.wait('@Markets');
});
@@ -21,37 +22,36 @@ describe('deal ticket basics', { tags: '@smoke' }, () => {
it('must show place order button and connect wallet if wallet is not connected', () => {
// 0003-WTXN-001
cy.getByTestId('connect-vega-wallet'); // Not connected
cy.getByTestId('order-connect-wallet').should('exist');
cy.getByTestId(placeOrderBtn).should('exist');
cy.getByTestId('deal-ticket-connect-wallet').should('exist');
cy.getByTestId('get-started-button').should('exist');
});
it('must be able to select order direction - long/short', function () {
// 7002-SORD-004
cy.getByTestId(toggleShort).click().children('input').should('be.checked');
cy.getByTestId(toggleLong).click().children('input').should('be.checked');
cy.getByTestId(toggleShort).click().next('input').should('be.checked');
cy.getByTestId(toggleLong).click().next('input').should('be.checked');
});
it('must be able to select order type - limit/market', function () {
// 7002-SORD-005
// 7002-SORD-006
// 7002-SORD-007
cy.getByTestId(toggleLimit).click().children('input').should('be.checked');
cy.getByTestId(toggleMarket).click().children('input').should('be.checked');
cy.getByTestId(toggleLimit).click().next('input').should('be.checked');
cy.getByTestId(toggleMarket).click().next('input').should('be.checked');
});
it('order connect vega wallet button should connect', () => {
mockConnectWallet();
cy.getByTestId(toggleLimit).click();
cy.getByTestId(orderPriceField).clear().type('101');
cy.getByTestId('order-connect-wallet').click();
cy.getByTestId('get-started-button').click();
cy.getByTestId('dialog-content').should('be.visible');
cy.getByTestId('connectors-list')
.find('[data-testid="connector-jsonRpc"]')
.click();
cy.wait('@walletReq');
cy.getByTestId(placeOrderBtn).should('be.visible');
cy.getByTestId(toggleLimit).children('input').should('be.checked');
cy.getByTestId(toggleLimit).next('input').should('be.checked');
cy.getByTestId(orderPriceField).should('have.value', '101');
});
});
@@ -63,6 +63,7 @@ describe('deal ticker order validation', { tags: '@smoke' }, () => {
cy.getByTestId(orderTIFDropDown).select('TIME_IN_FORCE_GTC');
cy.getByTestId(orderSizeField).clear().type('1');
cy.getByTestId(orderPriceField).clear().type('1.123456');
cy.getByTestId(placeOrderBtn).click();
cy.getByTestId('deal-ticket-error-message-price-limit').should(
'have.text',
'Price accepts up to 5 decimal places'
@@ -73,6 +74,7 @@ describe('deal ticker order validation', { tags: '@smoke' }, () => {
describe('market order', () => {
before(() => {
cy.getByTestId(toggleMarket).click();
cy.getByTestId(placeOrderBtn).click();
});
it('must not see the price unit', function () {
@@ -48,6 +48,7 @@ describe('suspended market validation', { tags: '@regression' }, () => {
cy.getByTestId(orderPriceField).clear().type('0.1');
cy.getByTestId(orderSizeField).clear().type('1');
cy.getByTestId(placeOrderBtn).should('be.enabled');
cy.getByTestId(placeOrderBtn).click();
cy.getByTestId('deal-ticket-warning-auction').should(
'have.text',
'Any orders placed now will not trade until the auction ends'
@@ -60,6 +61,7 @@ describe('suspended market validation', { tags: '@regression' }, () => {
TIFlist.filter((item) => item.code === 'FOK')[0].value
);
cy.getByTestId(placeOrderBtn).should('be.enabled');
cy.getByTestId(placeOrderBtn).click();
cy.getByTestId('deal-ticket-error-message-tif').should(
'have.text',
'This market is in auction until it reaches sufficient liquidity. Until the auction ends, you can only place GFA, GTT, or GTC limit orders'
@@ -15,6 +15,7 @@ describe('time in force validation', { tags: '@smoke' }, () => {
cy.mockTradingPage();
cy.mockSubscription();
cy.clearAllLocalStorage();
cy.setOnBoardingViewed();
cy.visit('/#/markets/market-0');
cy.wait('@Markets');
});
@@ -53,7 +53,7 @@ describe('fills', { tags: '@regression' }, () => {
cy.getByTestId(tabFills).contains('Market');
cy.getByTestId(tabFills)
.get(
'[role="gridcell"][col-id="market.tradableInstrument.instrument.name"]'
'[role="gridcell"][col-id="market.tradableInstrument.instrument.code"]'
)
.each(($marketSymbol) => {
cy.wrap($marketSymbol).invoke('text').should('not.be.empty');
@@ -7,7 +7,7 @@ const closePosition = 'close-position';
const dialogCloseX = 'dialog-close';
const dialogContent = 'dialog-content';
const dropDownMenu = 'dropdown-menu';
const marketActionsContent = 'market-actions-content';
const marketActionsContent = 'position-actions-content';
const positions = 'Positions';
const tabPositions = 'tab-positions';
const toastContent = 'toast-content';
@@ -142,22 +142,22 @@ describe('positions', { tags: '@regression', testIsolation: true }, () => {
it('sorting by Market', () => {
visitAndClickPositions();
const marketsSortedDefault = [
'ACTIVE MARKET',
'Apple Monthly (30 Jun 2022)',
'ETHBTC Quarterly (30 Jun 2022)',
'SUSPENDED MARKET',
'AAPL.MF21',
'BTCUSD.MF21',
'ETHBTC.QM21',
'SOLUSD',
];
const marketsSortedAsc = [
'ACTIVE MARKET',
'Apple Monthly (30 Jun 2022)',
'ETHBTC Quarterly (30 Jun 2022)',
'SUSPENDED MARKET',
'AAPL.MF21',
'BTCUSD.MF21',
'ETHBTC.QM21',
'SOLUSD',
];
const marketsSortedDesc = [
'SUSPENDED MARKET',
'ETHBTC Quarterly (30 Jun 2022)',
'Apple Monthly (30 Jun 2022)',
'ACTIVE MARKET',
'SOLUSD',
'ETHBTC.QM21',
'BTCUSD.MF21',
'AAPL.MF21',
];
cy.getByTestId(positions).click();
// 7004-POSI-003
@@ -251,11 +251,13 @@ describe('positions', { tags: '@regression', testIsolation: true }, () => {
it('I can see warnings', () => {
visitAndClickPositions();
cy.get('[col-id="openVolume"]').within(() => {
cy.get('[aria-label="warning-sign icon"]')
.should('be.visible')
.realHover();
});
cy.get('[col-id="openVolume"]')
.eq(3)
.within(() => {
cy.get('[aria-label="warning-sign icon"]')
.should('be.visible')
.realHover();
});
// 7004-POSI-011
cy.getByTestId(tooltipContent).should('be.visible');
});
@@ -289,9 +291,11 @@ describe('positions', { tags: '@regression', testIsolation: true }, () => {
it('View settlement asset', () => {
visitAndClickPositions();
cy.get('[col-id="asset"]').within(() => {
cy.get('button[type="button"]').first().click();
});
cy.get('[col-id="asset"]')
.eq(3)
.within(() => {
cy.get('button[type="button"]').click();
});
// 7004-POSI-008
cy.getByTestId(dialogContent).should('be.visible');
cy.getByTestId(dialogCloseX).click();
@@ -306,7 +310,7 @@ function validatePositionsDisplayed(multiKey = false) {
cy.getByTestId('tab-positions').should('be.visible');
cy.getByTestId('tab-positions')
.get('.ag-center-cols-container .ag-row')
.first()
.eq(multiKey ? 3 : 1)
.within(() => {
cy.get('[col-id="marketName"]')
.should('be.visible')
@@ -9,6 +9,7 @@ describe('trades', { tags: '@smoke' }, () => {
before(() => {
cy.mockTradingPage();
cy.mockSubscription();
cy.setOnBoardingViewed();
cy.intercept('POST', '/graphql', (req) => {
if (req.body.operationName === 'Trades') {
req.alias = '@Trades';
@@ -17,6 +17,7 @@ describe(
cy.visit('/#/portfolio');
cy.mockTradingPage();
cy.mockSubscription();
cy.setOnBoardingViewed();
cy.get('[data-testid="pathname-/portfolio"]').should('exist');
});
@@ -122,6 +123,7 @@ describe('connect vega wallet', { tags: '@smoke', testIsolation: true }, () => {
cy.visit('/#/portfolio');
cy.mockTradingPage();
cy.mockSubscription();
cy.setOnBoardingViewed();
cy.get('[data-testid="pathname-/portfolio"]').should('exist');
});
@@ -0,0 +1,119 @@
import { selectAsset } from '../support/helpers';
const amountField = 'input[name="amount"]';
const includeTransferFeeRadioBtn = 'include-transfer-fee';
const manageVegaWallet = 'manage-vega-wallet';
const toAddressField = '[name="toAddress"]';
const totalTransferfee = 'total-transfer-fee';
const transferAmount = 'transfer-amount';
const transferForm = 'transfer-form';
const transferFee = 'transfer-fee';
const walletTransfer = 'wallet-transfer';
const ASSET_SEPOLIA_TBTC = 2;
describe.skip(
'transfer fees',
{ tags: '@regression', testIsolation: true },
() => {
beforeEach(() => {
cy.mockWeb3Provider();
cy.mockTradingPage();
cy.mockSubscription();
cy.setVegaWallet();
cy.visit('/');
cy.getByTestId(manageVegaWallet).click();
cy.getByTestId(walletTransfer).click();
cy.wait('@Assets');
cy.wait('@Accounts');
cy.mockVegaWalletTransaction();
});
it('transfer fees tooltips', () => {
// 1003-TRAN-015
// 1003-TRAN-016
// 1003-TRAN-017
// 1003-TRAN-018
// 1003-TRAN-019
cy.getByTestId(transferForm);
cy.contains('Enter manually').click();
cy.getByTestId(transferForm)
.find(toAddressField)
.type(
'7f9cf07d3a9905b1a61a1069f7a758855da428bc0f4a97de87f48644bfc25535'
);
selectAsset(ASSET_SEPOLIA_TBTC);
cy.getByTestId(transferForm)
.find(amountField)
.type('1', { delay: 100, force: true });
/// Check Include Transfer Fee tooltip
cy.get('label[for="include-transfer-fee"] div').realHover();
cy.get('[data-side="bottom"] div')
.should('be.visible')
.should('not.be.empty');
//Check Transfer Fee tooltip
cy.contains('div', 'Transfer fee').realHover();
cy.get('[data-side="bottom"] div')
.should('be.visible')
.should('not.be.empty');
//Check Amount to be transferred tooltip
cy.contains('div', 'Amount to be transferred').realHover();
cy.get('[data-side="bottom"] div')
.should('be.visible')
.should('not.be.empty');
//Check Total amount (with fee) tooltip
cy.contains('div', 'Total amount (with fee)').realHover();
cy.get('[data-side="bottom"] div')
.should('be.visible')
.should('not.be.empty');
});
it('transfer fees', () => {
// 1003-TRAN-020
// 1003-TRAN-021
// 1003-TRAN-022
// 1003-TRAN-023
cy.getByTestId(transferForm);
cy.contains('Enter manually').click();
cy.getByTestId(transferForm)
.find(toAddressField)
.type(
'7f9cf07d3a9905b1a61a1069f7a758855da428bc0f4a97de87f48644bfc25535'
);
selectAsset(ASSET_SEPOLIA_TBTC);
cy.getByTestId(includeTransferFeeRadioBtn).should('be.disabled');
cy.getByTestId(transferForm)
.find(amountField)
.type('1', { delay: 100, force: true });
cy.getByTestId(transferFee)
.should('be.visible')
.should('contain.text', '0.01');
cy.getByTestId(transferAmount)
.should('be.visible')
.should('contain.text', '1.00');
cy.getByTestId(totalTransferfee)
.should('be.visible')
.should('contain.text', '1.01');
cy.getByTestId(includeTransferFeeRadioBtn).click();
cy.getByTestId(transferFee)
.should('be.visible')
.should('contain.text', '0.01');
cy.getByTestId(transferAmount)
.should('be.visible')
.should('contain.text', '0.99');
cy.getByTestId(totalTransferfee)
.should('be.visible')
.should('contain.text', '1.00');
});
}
);
@@ -0,0 +1,91 @@
import { selectAsset } from '../support/helpers';
const amountField = 'input[name="amount"]';
const transferText = 'transfer-intro-text';
const errorText = 'input-error-text';
const formFieldError = 'input-error-text';
const keyID = `[data-testid="${transferText}"] > .rounded-md`;
const manageVegaWallet = 'manage-vega-wallet';
const submitTransferBtn = '[type="submit"]';
const toAddressField = '[name="toAddress"]';
const transferForm = 'transfer-form';
const walletTransfer = 'wallet-transfer';
const ASSET_EURO = 1;
const ASSET_SEPOLIA_TBTC = 2;
describe(
'transfer form validation',
{ tags: '@regression', testIsolation: true },
() => {
beforeEach(() => {
cy.mockWeb3Provider();
cy.mockTradingPage();
cy.mockSubscription();
cy.setVegaWallet();
cy.visit('/#/portfolio');
cy.getByTestId(manageVegaWallet).click();
cy.getByTestId(walletTransfer).click();
cy.wait('@Accounts');
cy.wait('@Assets');
cy.mockVegaWalletTransaction();
});
it('transfer Text', () => {
// 1003-TRAN-003
cy.getByTestId(transferText)
.should('exist')
.get(keyID)
.invoke('text')
.should('match', /[\w.]{6}…[\w.]{6}/);
});
it('invalid vega key validation', () => {
//1003-TRAN-013
//1003-TRAN-004
cy.getByTestId(transferForm).should('be.visible');
cy.contains('Enter manually').click();
cy.getByTestId(transferForm).find(toAddressField).type('asd');
cy.getByTestId(transferForm).find(submitTransferBtn).click();
cy.getByTestId(formFieldError).should('contain.text', 'Invalid Vega key');
cy.contains('label', 'Vega key').should('be.visible');
cy.contains('label', 'Asset').should('be.visible');
cy.contains('label', 'Amount').should('be.visible');
});
it('empty fields', () => {
// 1003-TRAN-012
cy.getByTestId(transferForm).find(submitTransferBtn).click();
cy.getByTestId(formFieldError).should('contain.text', 'Required');
cy.getByTestId(formFieldError).should('have.length', 3);
});
it('min amount', () => {
// 1002-WITH-010
// 1003-TRAN-014
selectAsset(ASSET_SEPOLIA_TBTC);
cy.get(amountField).clear().type('0');
cy.getByTestId(transferForm).find(submitTransferBtn).click();
cy.getByTestId(errorText).should(
'contain.text',
'Value is below minimum'
);
});
it('max amount', () => {
// 1003-TRAN-002
// 1003-TRAN-011
// 1003-TRAN-002
selectAsset(ASSET_EURO); // Will be above maximum because the vega wallet doesn't have any collateral
cy.get(amountField).clear().type('1001', { delay: 100 });
cy.getByTestId(transferForm).find(submitTransferBtn).click();
cy.getByTestId(errorText).should(
'contain.text',
'You cannot transfer more than your available collateral'
);
});
}
);
@@ -5,205 +5,15 @@ const amountShortName = 'input[name="amount"] + div + span.text-xs';
const assetSelection = 'select-asset';
const assetBalance = 'asset-balance';
const assetOption = 'rich-select-option';
const transferText = 'transfer-intro-text';
const errorText = 'input-error-text';
const formFieldError = 'input-error-text';
const includeTransferFeeRadioBtn = 'include-transfer-fee';
const keyID = `[data-testid="${transferText}"] > .rounded-md`;
const manageVegaWallet = 'manage-vega-wallet';
const openTransferButton = 'open-transfer';
const submitTransferBtn = '[type="submit"]';
const toAddressField = '[name="toAddress"]';
const totalTransferfee = 'total-transfer-fee';
const transferAmount = 'transfer-amount';
const transferForm = 'transfer-form';
const transferFee = 'transfer-fee';
const walletTransfer = 'wallet-transfer';
const ASSET_EURO = 1;
const ASSET_SEPOLIA_TBTC = 2;
const collateralTab = 'Collateral';
const toastCloseBtn = 'toast-close';
const toastContent = 'toast-content';
describe('transfer fees', { tags: '@regression', testIsolation: true }, () => {
beforeEach(() => {
cy.mockWeb3Provider();
cy.mockTradingPage();
cy.mockSubscription();
cy.setVegaWallet();
cy.visit('/');
cy.wait('@Assets');
cy.wait('@Accounts');
cy.mockVegaWalletTransaction();
// Only click if not already active otherwise sidebar will close
cy.get('[data-testid="sidebar-content"]').then(($sidebarContent) => {
if ($sidebarContent.find('h2').text() !== 'Transfer') {
cy.get('[data-testid="sidebar"] [data-testid="Transfer"]').click();
}
});
});
it('transfer fees tooltips', () => {
// 1003-TRAN-015
// 1003-TRAN-016
// 1003-TRAN-017
// 1003-TRAN-018
// 1003-TRAN-019
cy.getByTestId(transferForm);
cy.contains('Enter manually').click();
cy.getByTestId(transferForm)
.find(toAddressField)
.type('7f9cf07d3a9905b1a61a1069f7a758855da428bc0f4a97de87f48644bfc25535');
selectAsset(ASSET_SEPOLIA_TBTC);
cy.getByTestId(transferForm)
.find(amountField)
.type('1', { delay: 100, force: true });
/// Check Include Transfer Fee tooltip
cy.get('label[for="include-transfer-fee"] div').realHover();
cy.get('[data-side="bottom"] div')
.should('be.visible')
.should('not.be.empty');
//Check Transfer Fee tooltip
cy.contains('div', 'Transfer fee').realHover();
cy.get('[data-side="bottom"] div')
.should('be.visible')
.should('not.be.empty');
//Check Amount to be transferred tooltip
cy.contains('div', 'Amount to be transferred').realHover();
cy.get('[data-side="bottom"] div')
.should('be.visible')
.should('not.be.empty');
//Check Total amount (with fee) tooltip
cy.contains('div', 'Total amount (with fee)').realHover();
cy.get('[data-side="bottom"] div')
.should('be.visible')
.should('not.be.empty');
});
it('transfer fees', () => {
// 1003-TRAN-020
// 1003-TRAN-021
// 1003-TRAN-022
// 1003-TRAN-023
cy.getByTestId(transferForm);
cy.contains('Enter manually').click();
cy.getByTestId(transferForm)
.find(toAddressField)
.type('7f9cf07d3a9905b1a61a1069f7a758855da428bc0f4a97de87f48644bfc25535');
selectAsset(ASSET_SEPOLIA_TBTC);
cy.getByTestId(includeTransferFeeRadioBtn).should('be.disabled');
cy.getByTestId(transferForm)
.find(amountField)
.type('1', { delay: 100, force: true });
cy.getByTestId(transferFee)
.should('be.visible')
.should('contain.text', '0.01');
cy.getByTestId(transferAmount)
.should('be.visible')
.should('contain.text', '1.00');
cy.getByTestId(totalTransferfee)
.should('be.visible')
.should('contain.text', '1.01');
cy.getByTestId(includeTransferFeeRadioBtn).click();
cy.getByTestId(transferFee)
.should('be.visible')
.should('contain.text', '0.01');
cy.getByTestId(transferAmount)
.should('be.visible')
.should('contain.text', '0.99');
cy.getByTestId(totalTransferfee)
.should('be.visible')
.should('contain.text', '1.00');
});
});
describe(
'transfer form validation',
{ tags: '@regression', testIsolation: true },
() => {
beforeEach(() => {
cy.mockWeb3Provider();
cy.mockTradingPage();
cy.mockSubscription();
cy.setVegaWallet();
cy.visit('/#/portfolio');
cy.getByTestId(manageVegaWallet).click();
cy.getByTestId(walletTransfer).click();
cy.wait('@Accounts');
cy.wait('@Assets');
});
it('transfer Text', () => {
// 1003-TRAN-003
cy.getByTestId(transferText)
.should('exist')
.get(keyID)
.invoke('text')
.should('match', /[\w.]{6}…[\w.]{6}/);
});
it('invalid vega key validation', () => {
//1003-TRAN-013
//1003-TRAN-004
cy.getByTestId(transferForm).should('be.visible');
cy.contains('Enter manually').click();
cy.getByTestId(transferForm).find(toAddressField).type('asd');
cy.getByTestId(transferForm).find(submitTransferBtn).click();
cy.getByTestId(formFieldError).should('contain.text', 'Invalid Vega key');
cy.contains('label', 'Vega key').should('be.visible');
cy.contains('label', 'Asset').should('be.visible');
cy.contains('label', 'Amount').should('be.visible');
});
it('empty fields', () => {
// 1003-TRAN-012
cy.getByTestId(transferForm).find(submitTransferBtn).click();
cy.getByTestId(formFieldError).should('contain.text', 'Required');
cy.getByTestId(formFieldError).should('have.length', 3);
});
it('min amount', () => {
// 1002-WITH-010
// 1003-TRAN-014
selectAsset(ASSET_SEPOLIA_TBTC);
cy.get(amountField).clear().type('0');
cy.getByTestId(transferForm).find(submitTransferBtn).click();
cy.getByTestId(errorText).should(
'contain.text',
'Value is below minimum'
);
});
it('max amount', () => {
// 1003-TRAN-002
// 1003-TRAN-011
// 1003-TRAN-002
selectAsset(ASSET_EURO); // Will be above maximum because the vega wallet doesn't have any collateral
cy.get(amountField).clear().type('1001', { delay: 100 });
cy.getByTestId(transferForm).find(submitTransferBtn).click();
cy.getByTestId(errorText).should(
'contain.text',
'You cannot transfer more than your available collateral'
);
});
}
);
describe('withdraw actions', { tags: '@smoke', testIsolation: true }, () => {
beforeEach(() => {
cy.mockWeb3Provider();
+4 -1
View File
@@ -1,3 +1,4 @@
import { OrderType } from '@vegaprotocol/types';
import type { OrderSubmission } from '@vegaprotocol/wallet';
const orderSizeField = 'order-size';
@@ -9,7 +10,9 @@ export const createOrder = (order: OrderSubmission): void => {
cy.log('Placing order', order);
const { type, side, size, price, timeInForce, expiresAt } = order;
cy.getByTestId(`order-type-${type}`).click();
cy.getByTestId(
`order-type-${type === OrderType.TYPE_LIMIT ? 'Limit' : 'Market'}`
).click();
cy.getByTestId(`order-side-${side}`).click();
cy.getByTestId(orderSizeField).clear().type(size);
if (price) {
+2 -2
View File
@@ -6,8 +6,8 @@ export const orderTIFDropDown = 'order-tif';
export const placeOrderBtn = 'place-order';
export const toggleShort = 'order-side-SIDE_SELL';
export const toggleLong = 'order-side-SIDE_BUY';
export const toggleLimit = 'order-type-TYPE_LIMIT';
export const toggleMarket = 'order-type-TYPE_MARKET';
export const toggleLimit = 'order-type-Limit';
export const toggleMarket = 'order-type-Market';
export const TIFlist = Object.values(Schema.OrderTimeInForce).map((value) => {
return {

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