Merge remote-tracking branch 'origin/develop' into fix/3862-you-voted-should-update-on-wallet-change
This commit is contained in:
@@ -113,15 +113,19 @@ jobs:
|
||||
preview_governance="not deployed"
|
||||
preview_trading="not deployed"
|
||||
preview_explorer="not deployed"
|
||||
if [[ $affected == *"governance"* ]]; then
|
||||
preview_tools="not deployed"
|
||||
if echo "$affected" | grep -q governance; then
|
||||
echo "Governance is affected"
|
||||
projects_e2e+='"governance-e2e" '
|
||||
preview_governance=$(printf "https://%s.%s.vega.rocks" "governance" "$branch_slug")
|
||||
fi
|
||||
if [[ $affected == *"trading"* ]]; then
|
||||
if echo "$affected" | grep -q trading; then
|
||||
echo "Trading is affected"
|
||||
projects_e2e+='"trading-e2e" '
|
||||
preview_trading=$(printf "https://%s.%s.vega.rocks" "trading" "$branch_slug")
|
||||
fi
|
||||
if [[ $affected == *"explorer"* ]]; then
|
||||
if echo "$affected" | grep -q explorer; then
|
||||
echo "Explorer is affected"
|
||||
projects_e2e+='"explorer-e2e" '
|
||||
preview_explorer=$(printf "https://%s.%s.vega.rocks" "explorer" "$branch_slug")
|
||||
fi
|
||||
@@ -131,13 +135,43 @@ jobs:
|
||||
preview_trading=$(printf "https://%s.%s.vega.rocks" "trading" "$branch_slug")
|
||||
preview_explorer=$(printf "https://%s.%s.vega.rocks" "explorer" "$branch_slug")
|
||||
fi
|
||||
projects="$(echo $projects_e2e | sed 's|-e2e||g')"
|
||||
if [[ "${{ github.event_name }}" = "pull_request" ]]; then
|
||||
if echo "$affected" | grep -q multisig-signer; then
|
||||
echo "Tools are affected"
|
||||
# tools are only applicable to check previews or deploy from develop to mainnet
|
||||
echo "Deploying tools on preview"
|
||||
preview_tools=$(printf "https://%s.%s.vega.rocks" "tools" "$branch_slug")
|
||||
projects+=' "multisig-signer" '
|
||||
fi
|
||||
elif [[ "${{ github.ref }}" =~ .*develop$ ]]; then
|
||||
if echo "$affected" | grep -q multisig-signer; then
|
||||
echo "Tools are affected"
|
||||
# tools are only applicable to check previews or deploy from develop to mainnet
|
||||
echo "Deploying tools on s3"
|
||||
projects+=' "multisig-signer" '
|
||||
fi
|
||||
if echo "$affected" | grep -q static; then
|
||||
echo "static is affected"
|
||||
echo "Deploying static on s3"
|
||||
projects+=' "static" '
|
||||
fi
|
||||
if echo "$affected" | grep -q ui-toolkit; then
|
||||
echo "ui-toolkit is affected"
|
||||
echo "Deploying ui-toolkit on s3"
|
||||
projects+=' "ui-toolkit" '
|
||||
fi
|
||||
fi
|
||||
|
||||
projects_e2e=${projects_e2e%?}
|
||||
projects_e2e=[${projects_e2e// /,}]
|
||||
projects=[${projects// /,}]
|
||||
echo PROJECTS_E2E=$projects_e2e >> $GITHUB_ENV
|
||||
echo PROJECTS=$(echo $projects_e2e | sed 's|-e2e||g') >> $GITHUB_ENV
|
||||
echo PROJECTS=$projects >> $GITHUB_ENV
|
||||
echo PREVIEW_GOVERNANCE=$preview_governance >> $GITHUB_ENV
|
||||
echo PREVIEW_TRADING=$preview_trading >> $GITHUB_ENV
|
||||
echo PREVIEW_EXPLORER=$preview_explorer >> $GITHUB_ENV
|
||||
echo PREVIEW_TOOLS=$preview_tools >> $GITHUB_ENV
|
||||
|
||||
outputs:
|
||||
projects: ${{ env.PROJECTS }}
|
||||
@@ -145,11 +179,12 @@ jobs:
|
||||
preview_governance: ${{ env.PREVIEW_GOVERNANCE }}
|
||||
preview_trading: ${{ env.PREVIEW_TRADING }}
|
||||
preview_explorer: ${{ env.PREVIEW_EXPLORER }}
|
||||
preview_tools: ${{ env.PREVIEW_TOOLS }}
|
||||
|
||||
cypress:
|
||||
needs: lint-test-build
|
||||
name: '(CI) cypress'
|
||||
if: ${{ needs.lint-test-build.outputs.projects != '[]' }}
|
||||
if: ${{ needs.lint-test-build.outputs.projects-e2e != '[]' }}
|
||||
uses: ./.github/workflows/cypress-run.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
@@ -203,6 +238,12 @@ jobs:
|
||||
sleep 5
|
||||
done
|
||||
fi
|
||||
if [[ "${{ needs.lint-test-build.outputs.preview_tools }}" =~ $regex ]]; then
|
||||
until curl -L --fail "${{ needs.lint-test-build.outputs.preview_tools }}"; do
|
||||
echo "waiting for tools preview"
|
||||
sleep 5
|
||||
done
|
||||
fi
|
||||
|
||||
- name: Create comment
|
||||
uses: peter-evans/create-or-update-comment@v3
|
||||
@@ -214,6 +255,7 @@ jobs:
|
||||
* governance: ${{ needs.lint-test-build.outputs.preview_governance }}
|
||||
* explorer: ${{ needs.lint-test-build.outputs.preview_explorer }}
|
||||
* trading: ${{ needs.lint-test-build.outputs.preview_trading }}
|
||||
* tools: ${{ needs.lint-test-build.outputs.preview_tools }}
|
||||
|
||||
# Report single result at the end, to avoid mess with required checks in PR
|
||||
cypress-check:
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
project: ${{ fromJSON(inputs.projects) }}
|
||||
name: ${{ matrix.project }}
|
||||
runs-on: self-hosted-runner
|
||||
timeout-minutes: 60
|
||||
timeout-minutes: 100
|
||||
steps:
|
||||
# Checks if skip cache was requested
|
||||
- name: Set skip-nx-cache flag
|
||||
|
||||
@@ -70,6 +70,18 @@ jobs:
|
||||
envName="$(echo ${{ github.ref }} | rev | cut -d '/' -f 1 | rev)"
|
||||
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 }}" =~ .*main$ ]]; then
|
||||
envName="mainnet"
|
||||
elif [[ "${{ matrix.app}}" = "trading" ]] && [[ "${{ startsWith(github.ref, 'refs/tags/v') && 'true' || 'false' }}" = "true" ]]; then
|
||||
@@ -78,10 +90,14 @@ jobs:
|
||||
|
||||
if [[ "${envName}" = "mainnet" ]]; then
|
||||
domain="vega.xyz"
|
||||
bucketName="${{ matrix.app }}.${domain}"
|
||||
if [[ -z "${bucketName}" ]]; then
|
||||
bucketName="${{ matrix.app }}.${domain}"
|
||||
fi
|
||||
elif [[ "${envName}" = "testnet" ]]; then
|
||||
domain="fairground.wtf"
|
||||
bucketName="${{ matrix.app }}.${domain}"
|
||||
if [[ -z "${bucketName}" ]]; then
|
||||
bucketName="${{ matrix.app }}.${domain}"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -z "${bucketName}" ]]; then
|
||||
@@ -106,6 +122,9 @@ jobs:
|
||||
if [ "${{ matrix.app }}" = "trading" ]; then
|
||||
yarn nx export trading $flags || (yarn install && yarn nx export trading $flags)
|
||||
DIST_LOCATION=dist/apps/trading/exported
|
||||
elif [ "${{ matrix.app }}" = "ui-toolkit" ]; then
|
||||
NODE_ENV=production yarn nx run ui-toolkit:build-storybook
|
||||
DIST_LOCATION=dist/storybook/ui-toolkit
|
||||
else
|
||||
yarn nx build ${{ matrix.app }} $flags || (yarn install && yarn nx build ${{ matrix.app }} $flags)
|
||||
DIST_LOCATION=dist/apps/${{ matrix.app }}
|
||||
@@ -140,6 +159,8 @@ jobs:
|
||||
|
||||
- name: Publish dist as docker image (ghcr)
|
||||
uses: docker/build-push-action@v3
|
||||
continue-on-error: true
|
||||
id: ghcr-push
|
||||
if: ${{ github.event_name == 'pull_request' || (matrix.app == 'trading' && github.event_name == 'push' && !startsWith(github.ref, 'refs/tags/v') ) }}
|
||||
with:
|
||||
context: .
|
||||
@@ -153,6 +174,8 @@ jobs:
|
||||
|
||||
- name: Publish dist as docker image (docker hub)
|
||||
uses: docker/build-push-action@v3
|
||||
continue-on-error: true
|
||||
id: dockerhub-push
|
||||
if: ${{ matrix.app == 'trading' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') }}
|
||||
with:
|
||||
context: .
|
||||
@@ -165,6 +188,33 @@ jobs:
|
||||
vegaprotocol/${{ matrix.app }}:${{ github.ref_name }}
|
||||
vegaprotocol/${{ matrix.app }}:mainnet
|
||||
|
||||
- name: Publish dist as docker image (ghcr - retry)
|
||||
uses: docker/build-push-action@v3
|
||||
if: ${{ steps.ghcr-push.outcome == 'failure' }}
|
||||
with:
|
||||
context: .
|
||||
file: docker/node-outside-docker.Dockerfile
|
||||
push: true
|
||||
build-args: |
|
||||
APP=${{ matrix.app }}
|
||||
ENV_NAME=${{ env.ENV_NAME }}
|
||||
tags: |
|
||||
ghcr.io/vegaprotocol/frontend/${{ matrix.app }}:${{ github.event.pull_request.head.sha || github.sha }}
|
||||
|
||||
- name: Publish dist as docker image (docker hub - retry)
|
||||
uses: docker/build-push-action@v3
|
||||
if: ${{ steps.dockerhub-push.outcome == 'failure' }}
|
||||
with:
|
||||
context: .
|
||||
file: docker/node-outside-docker.Dockerfile
|
||||
push: true
|
||||
build-args: |
|
||||
APP=${{ matrix.app }}
|
||||
ENV_NAME=${{ env.ENV_NAME }}
|
||||
tags: |
|
||||
vegaprotocol/${{ matrix.app }}:${{ github.ref_name }}
|
||||
vegaprotocol/${{ matrix.app }}:mainnet
|
||||
|
||||
# bucket creation in github.com/vegaprotocol/terraform//frontend
|
||||
- name: Publish dist to s3
|
||||
uses: jakejarvis/s3-sync-action@master
|
||||
|
||||
@@ -15,6 +15,7 @@ NX_VEGA_GOVERNANCE_URL=https://governance.stagnet1.vega.rocks
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
|
||||
NX_VEGA_NETWORKS='{"TESTNET":"https://explorer.fairground.wtf","MAINNET":"https://explorer.vega.xyz"}'
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases/tag/
|
||||
|
||||
# App flags
|
||||
NX_EXPLORER_ASSETS=1
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import type { MarketInfoWithData } from '@vegaprotocol/markets';
|
||||
import { PriceMonitoringBoundsInfoPanel } from '@vegaprotocol/markets';
|
||||
import {
|
||||
LiquidityInfoPanel,
|
||||
LiquidityMonitoringParametersInfoPanel,
|
||||
@@ -39,133 +40,69 @@ export const MarketDetails = ({ market }: { market: MarketInfoWithData }) => {
|
||||
return [];
|
||||
};
|
||||
|
||||
const oraclePanels = isEqual(
|
||||
const showTwoOracles = isEqual(
|
||||
getSigners(settlementData),
|
||||
getSigners(terminationData)
|
||||
)
|
||||
? [
|
||||
{
|
||||
title: t('Settlement Oracle'),
|
||||
content: (
|
||||
<OracleInfoPanel
|
||||
noBorder={false}
|
||||
market={market}
|
||||
type="settlementData"
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t('Termination Oracle'),
|
||||
content: (
|
||||
<OracleInfoPanel
|
||||
noBorder={false}
|
||||
market={market}
|
||||
type="termination"
|
||||
/>
|
||||
),
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
title: t('Oracle'),
|
||||
content: (
|
||||
<OracleInfoPanel
|
||||
noBorder={false}
|
||||
market={market}
|
||||
type="settlementData"
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
);
|
||||
|
||||
const panels = [
|
||||
{
|
||||
title: t('Key details'),
|
||||
content: <KeyDetailsInfoPanel noBorder={false} market={market} />,
|
||||
},
|
||||
{
|
||||
title: t('Instrument'),
|
||||
content: <InstrumentInfoPanel noBorder={false} market={market} />,
|
||||
},
|
||||
{
|
||||
title: t('Settlement asset'),
|
||||
content: <SettlementAssetInfoPanel market={market} noBorder={false} />,
|
||||
},
|
||||
{
|
||||
title: t('Metadata'),
|
||||
content: <MetadataInfoPanel noBorder={false} market={market} />,
|
||||
},
|
||||
{
|
||||
title: t('Risk model'),
|
||||
content: <RiskModelInfoPanel noBorder={false} market={market} />,
|
||||
},
|
||||
{
|
||||
title: t('Risk parameters'),
|
||||
content: <RiskParametersInfoPanel noBorder={false} market={market} />,
|
||||
},
|
||||
{
|
||||
title: t('Risk factors'),
|
||||
content: <RiskFactorsInfoPanel noBorder={false} market={market} />,
|
||||
},
|
||||
...(market.priceMonitoringSettings?.parameters?.triggers || []).map(
|
||||
(trigger, i) => ({
|
||||
title: t(`Price monitoring trigger ${i + 1}`),
|
||||
content: <MarketInfoTable noBorder={false} data={trigger} />,
|
||||
})
|
||||
),
|
||||
...(market.data?.priceMonitoringBounds || []).map((trigger, i) => ({
|
||||
title: t(`Price monitoring bound ${i + 1}`),
|
||||
content: (
|
||||
<>
|
||||
<MarketInfoTable
|
||||
noBorder={false}
|
||||
data={{
|
||||
maxValidPrice: trigger.maxValidPrice,
|
||||
minValidPrice: trigger.minValidPrice,
|
||||
}}
|
||||
decimalPlaces={market.decimalPlaces}
|
||||
/>
|
||||
<MarketInfoTable
|
||||
noBorder={false}
|
||||
data={{ referencePrice: trigger.referencePrice }}
|
||||
decimalPlaces={
|
||||
market.tradableInstrument.instrument.product.settlementAsset
|
||||
.decimals
|
||||
}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
})),
|
||||
{
|
||||
title: t('Liquidity monitoring parameters'),
|
||||
content: (
|
||||
<LiquidityMonitoringParametersInfoPanel
|
||||
noBorder={false}
|
||||
market={market}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t('Liquidity'),
|
||||
content: <LiquidityInfoPanel market={market} noBorder={false} />,
|
||||
},
|
||||
{
|
||||
title: t('Liquidity price range'),
|
||||
content: (
|
||||
<LiquidityPriceRangeInfoPanel market={market} noBorder={false} />
|
||||
),
|
||||
},
|
||||
...oraclePanels,
|
||||
];
|
||||
const headerClassName = 'font-alpha calt text-xl mt-4 border-b-2 pb-2';
|
||||
|
||||
return (
|
||||
<>
|
||||
{panels.map((p) => (
|
||||
<div key={p.title} className="mb-3">
|
||||
<h2 className="font-alpha calt text-xl">{p.title}</h2>
|
||||
{p.content}
|
||||
</div>
|
||||
<div>
|
||||
<h2 className={headerClassName}>{t('Key details')}</h2>
|
||||
<KeyDetailsInfoPanel market={market} />
|
||||
<h2 className={headerClassName}>{t('Instrument')}</h2>
|
||||
<InstrumentInfoPanel market={market} />
|
||||
<h2 className={headerClassName}>{t('Settlement asset')}</h2>
|
||||
<SettlementAssetInfoPanel market={market} />
|
||||
<h2 className={headerClassName}>{t('Metadata')}</h2>
|
||||
<MetadataInfoPanel market={market} />
|
||||
<h2 className={headerClassName}>{t('Risk model')}</h2>
|
||||
<RiskModelInfoPanel market={market} />
|
||||
<h2 className={headerClassName}>{t('Risk parameters')}</h2>
|
||||
<RiskParametersInfoPanel market={market} />
|
||||
<h2 className={headerClassName}>{t('Risk factors')}</h2>
|
||||
<RiskFactorsInfoPanel market={market} />
|
||||
{(market.data?.priceMonitoringBounds || []).map((trigger, i) => (
|
||||
<>
|
||||
<h2 className={headerClassName}>
|
||||
{t('Price monitoring bounds %s', [(i + 1).toString()])}
|
||||
</h2>
|
||||
<PriceMonitoringBoundsInfoPanel
|
||||
market={market}
|
||||
triggerIndex={i + 1}
|
||||
/>
|
||||
</>
|
||||
))}
|
||||
</>
|
||||
{(market.priceMonitoringSettings?.parameters?.triggers || []).map(
|
||||
(trigger, i) => (
|
||||
<>
|
||||
<h2 className={headerClassName}>
|
||||
{t('Price monitoring settings %s', [(i + 1).toString()])}
|
||||
</h2>
|
||||
<MarketInfoTable data={trigger} key={i} />
|
||||
</>
|
||||
)
|
||||
)}
|
||||
<h2 className={headerClassName}>{t('Liquidity monitoring')}</h2>
|
||||
<LiquidityMonitoringParametersInfoPanel market={market} />
|
||||
<h2 className={headerClassName}>{t('Liquidity')}</h2>
|
||||
<LiquidityInfoPanel market={market} />
|
||||
<h2 className={headerClassName}>{t('Liquidity price range')}</h2>
|
||||
<LiquidityPriceRangeInfoPanel market={market} />
|
||||
{showTwoOracles ? (
|
||||
<>
|
||||
<h2 className={headerClassName}>{t('Settlement oracle')}</h2>
|
||||
<OracleInfoPanel market={market} type="settlementData" />
|
||||
<h2 className={headerClassName}>{t('Termination oracle')}</h2>
|
||||
<OracleInfoPanel market={market} type="termination" />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<h2 className={headerClassName}>{t('Oracle')}</h2>
|
||||
<OracleInfoPanel market={market} type="settlementData" />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -103,8 +103,6 @@ describe(
|
||||
it('Newly created freeform proposal details - shows proposed and closing dates', function () {
|
||||
const proposalTitle = generateFreeFormProposalTitle();
|
||||
const proposalTimeStamp = createTenDigitUnixTimeStampForSpecifiedDays(3);
|
||||
// const currentDate = new Date(createTenDigitUnixTimeStampForSpecifiedDays(0) * 1000)
|
||||
// const proposedDate = new Date(currentDate.getTime() + 60000)
|
||||
|
||||
submitUniqueRawProposal({
|
||||
proposalTitle: proposalTitle,
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
createUpdateNetworkProposalTxBody,
|
||||
createFreeFormProposalTxBody,
|
||||
} from '../../support/proposal.functions';
|
||||
import { ensureSpecifiedUnstakedTokensAreAssociated } from '../../support/staking.functions';
|
||||
import { ethereumWalletConnect } from '../../support/wallet-eth.functions';
|
||||
import { vegaWalletSetSpecifiedApprovalAmount } from '../../support/wallet-teardown.functions';
|
||||
|
||||
@@ -43,6 +44,7 @@ context(
|
||||
waitForSpinner();
|
||||
cy.connectVegaWallet();
|
||||
ethereumWalletConnect();
|
||||
ensureSpecifiedUnstakedTokensAreAssociated('1');
|
||||
navigateTo(navigation.proposals);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
closeDialog,
|
||||
dissociateFromSecondWalletKey,
|
||||
navigateTo,
|
||||
navigation,
|
||||
turnTelemetryOff,
|
||||
@@ -15,7 +16,11 @@ import {
|
||||
governanceProposalType,
|
||||
voteForProposal,
|
||||
} from '../../support/governance.functions';
|
||||
import { ensureSpecifiedUnstakedTokensAreAssociated } from '../../support/staking.functions';
|
||||
import {
|
||||
ensureSpecifiedUnstakedTokensAreAssociated,
|
||||
stakingPageAssociateTokens,
|
||||
stakingPageDisassociateAllTokens,
|
||||
} from '../../support/staking.functions';
|
||||
import { ethereumWalletConnect } from '../../support/wallet-eth.functions';
|
||||
import {
|
||||
vegaWalletSetSpecifiedApprovalAmount,
|
||||
@@ -100,9 +105,9 @@ context(
|
||||
goToMakeNewProposal(governanceProposalType.RAW);
|
||||
submitUniqueRawProposal({ proposalBody: filePath, submit: false });
|
||||
});
|
||||
cy.get(newProposalSubmitButton).click();
|
||||
validateDialogContentMsg('PROPOSAL_ERROR_CLOSE_TIME_TOO_SOON');
|
||||
});
|
||||
cy.get(newProposalSubmitButton).click();
|
||||
validateDialogContentMsg('PROPOSAL_ERROR_CLOSE_TIME_TOO_SOON');
|
||||
});
|
||||
|
||||
// 3007-PNEC-001 3007-PNEC-003
|
||||
@@ -182,13 +187,17 @@ context(
|
||||
'have.text',
|
||||
'Proposal will fail if enactment is earlier than the voting deadline'
|
||||
);
|
||||
cy.get(proposalDownloadBtn).click();
|
||||
cy.wrap(
|
||||
getDownloadedProposalJsonPath('vega-network-param-proposal-')
|
||||
).then((filePath) => {
|
||||
goToMakeNewProposal(governanceProposalType.RAW);
|
||||
submitUniqueRawProposal({ proposalBody: filePath, submit: false });
|
||||
});
|
||||
cy.get(proposalDownloadBtn)
|
||||
.should('be.visible')
|
||||
.click()
|
||||
.then(() => {
|
||||
cy.wrap(
|
||||
getDownloadedProposalJsonPath('vega-network-param-proposal-')
|
||||
).then((filePath) => {
|
||||
goToMakeNewProposal(governanceProposalType.RAW);
|
||||
submitUniqueRawProposal({ proposalBody: filePath, submit: false });
|
||||
});
|
||||
});
|
||||
cy.get(newProposalSubmitButton).click();
|
||||
validateFeedBackMsg(
|
||||
'Invalid params: proposal_submission.terms.closing_timestamp (cannot be after enactment time)'
|
||||
@@ -207,13 +216,17 @@ context(
|
||||
delay: 2,
|
||||
});
|
||||
});
|
||||
cy.get(proposalDownloadBtn).should('be.visible').click();
|
||||
cy.wrap(getDownloadedProposalJsonPath('vega-new-market-proposal-')).then(
|
||||
(filePath) => {
|
||||
goToMakeNewProposal(governanceProposalType.RAW);
|
||||
submitUniqueRawProposal({ proposalBody: filePath }); // 3003-PMAN-003
|
||||
}
|
||||
);
|
||||
cy.get(proposalDownloadBtn)
|
||||
.should('be.visible')
|
||||
.click()
|
||||
.then(() => {
|
||||
cy.wrap(
|
||||
getDownloadedProposalJsonPath('vega-new-market-proposal-')
|
||||
).then((filePath) => {
|
||||
goToMakeNewProposal(governanceProposalType.RAW);
|
||||
submitUniqueRawProposal({ proposalBody: filePath }); // 3003-PMAN-003
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Unable to submit new market proposal with missing/invalid fields', function () {
|
||||
@@ -233,13 +246,17 @@ context(
|
||||
delay: 2,
|
||||
});
|
||||
});
|
||||
cy.get(proposalDownloadBtn).should('be.visible').click();
|
||||
cy.wrap(getDownloadedProposalJsonPath('vega-new-market-proposal-')).then(
|
||||
(filePath) => {
|
||||
goToMakeNewProposal(governanceProposalType.RAW);
|
||||
submitUniqueRawProposal({ proposalBody: filePath, submit: false });
|
||||
}
|
||||
);
|
||||
cy.get(proposalDownloadBtn)
|
||||
.should('be.visible')
|
||||
.click()
|
||||
.then(() => {
|
||||
cy.wrap(
|
||||
getDownloadedProposalJsonPath('vega-new-market-proposal-')
|
||||
).then((filePath) => {
|
||||
goToMakeNewProposal(governanceProposalType.RAW);
|
||||
submitUniqueRawProposal({ proposalBody: filePath, submit: false });
|
||||
});
|
||||
});
|
||||
cy.get(newProposalSubmitButton).should('be.visible').click();
|
||||
cy.contains('Transaction failed', proposalTimeout).should('be.visible');
|
||||
validateFeedBackMsg(errorMsg);
|
||||
@@ -248,6 +265,9 @@ context(
|
||||
// Will fail if run after 'Able to submit update market proposal and vote for proposal'
|
||||
// 3002-PROP-022
|
||||
it('Unable to submit update market proposal without equity-like share in the market', function () {
|
||||
cy.get('[data-testid="manage-vega-wallet"]:visible').click();
|
||||
cy.get('[data-testid="select-keypair-button"]').eq(0).click(); // switch to second wallet pub key
|
||||
stakingPageAssociateTokens('1');
|
||||
goToMakeNewProposal(governanceProposalType.UPDATE_MARKET);
|
||||
cy.get(newProposalTitle).type('Test update market proposal - rejected');
|
||||
cy.get(newProposalDescription).type('E2E test for proposals');
|
||||
@@ -259,17 +279,25 @@ context(
|
||||
delay: 2,
|
||||
});
|
||||
});
|
||||
cy.get(proposalDownloadBtn).should('be.visible').click();
|
||||
cy.wrap(
|
||||
getDownloadedProposalJsonPath('vega-update-market-proposal-')
|
||||
).then((filePath) => {
|
||||
goToMakeNewProposal(governanceProposalType.RAW);
|
||||
submitUniqueRawProposal({ proposalBody: filePath, submit: false });
|
||||
});
|
||||
cy.get(proposalDownloadBtn)
|
||||
.should('be.visible')
|
||||
.click()
|
||||
.then(() => {
|
||||
cy.wrap(
|
||||
getDownloadedProposalJsonPath('vega-update-market-proposal-')
|
||||
).then((filePath) => {
|
||||
goToMakeNewProposal(governanceProposalType.RAW);
|
||||
submitUniqueRawProposal({ proposalBody: filePath, submit: false });
|
||||
});
|
||||
});
|
||||
cy.get(newProposalSubmitButton).should('be.visible').click();
|
||||
cy.contains('Proposal rejected', proposalTimeout).should('be.visible');
|
||||
validateDialogContentMsg('PROPOSAL_ERROR_INSUFFICIENT_EQUITY_LIKE_SHARE');
|
||||
ensureSpecifiedUnstakedTokensAreAssociated('1');
|
||||
closeDialog();
|
||||
ethereumWalletConnect();
|
||||
stakingPageDisassociateAllTokens();
|
||||
cy.get('[data-testid="manage-vega-wallet"]:visible').click();
|
||||
cy.get('[data-testid="select-keypair-button"]').eq(0).click();
|
||||
});
|
||||
|
||||
// 3002-PROP-020
|
||||
@@ -291,13 +319,17 @@ context(
|
||||
delay: 2,
|
||||
});
|
||||
});
|
||||
cy.get(proposalDownloadBtn).should('be.visible').click();
|
||||
cy.wrap(
|
||||
getDownloadedProposalJsonPath('vega-update-market-proposal-')
|
||||
).then((filePath) => {
|
||||
goToMakeNewProposal(governanceProposalType.RAW);
|
||||
submitUniqueRawProposal({ proposalBody: filePath, submit: false });
|
||||
});
|
||||
cy.get(proposalDownloadBtn)
|
||||
.should('be.visible')
|
||||
.click()
|
||||
.then(() => {
|
||||
cy.wrap(
|
||||
getDownloadedProposalJsonPath('vega-update-market-proposal-')
|
||||
).then((filePath) => {
|
||||
goToMakeNewProposal(governanceProposalType.RAW);
|
||||
submitUniqueRawProposal({ proposalBody: filePath, submit: false });
|
||||
});
|
||||
});
|
||||
cy.get(newProposalSubmitButton).should('be.visible').click();
|
||||
cy.contains('Transaction failed', proposalTimeout).should('be.visible');
|
||||
validateFeedBackMsg(
|
||||
@@ -335,13 +367,17 @@ context(
|
||||
delay: 2,
|
||||
});
|
||||
});
|
||||
cy.get(proposalDownloadBtn).should('be.visible').click();
|
||||
cy.wrap(
|
||||
getDownloadedProposalJsonPath('vega-update-market-proposal-')
|
||||
).then((filePath) => {
|
||||
goToMakeNewProposal(governanceProposalType.RAW);
|
||||
submitUniqueRawProposal({ proposalBody: filePath });
|
||||
});
|
||||
cy.get(proposalDownloadBtn)
|
||||
.should('be.visible')
|
||||
.click()
|
||||
.then(() => {
|
||||
cy.wrap(
|
||||
getDownloadedProposalJsonPath('vega-update-market-proposal-')
|
||||
).then((filePath) => {
|
||||
goToMakeNewProposal(governanceProposalType.RAW);
|
||||
submitUniqueRawProposal({ proposalBody: filePath });
|
||||
});
|
||||
});
|
||||
navigateTo(navigation.proposals);
|
||||
cy.get('@EnactedMarketId').then((marketId) => {
|
||||
cy.contains(String(marketId).slice(0, 6))
|
||||
@@ -390,14 +426,17 @@ context(
|
||||
cy.get(minVoteDeadline).click();
|
||||
cy.get(minValidationDeadline).click();
|
||||
cy.get(minEnactDeadline).click();
|
||||
|
||||
cy.get(proposalDownloadBtn).should('be.visible').click();
|
||||
cy.wrap(getDownloadedProposalJsonPath('vega-new-asset-proposal-')).then(
|
||||
(filePath) => {
|
||||
goToMakeNewProposal(governanceProposalType.RAW);
|
||||
submitUniqueRawProposal({ proposalBody: filePath, submit: false }); // 3005-PASN-003
|
||||
}
|
||||
);
|
||||
cy.get(proposalDownloadBtn)
|
||||
.should('be.visible')
|
||||
.click()
|
||||
.then(() => {
|
||||
cy.wrap(
|
||||
getDownloadedProposalJsonPath('vega-new-asset-proposal-')
|
||||
).then((filePath) => {
|
||||
goToMakeNewProposal(governanceProposalType.RAW);
|
||||
submitUniqueRawProposal({ proposalBody: filePath, submit: false }); // 3005-PASN-003
|
||||
});
|
||||
});
|
||||
cy.get(newProposalSubmitButton).should('be.visible').click();
|
||||
closeDialog();
|
||||
cy.get(newProposalSubmitButton).should('be.visible').click();
|
||||
@@ -433,13 +472,17 @@ context(
|
||||
enterUpdateAssetProposalDetails();
|
||||
cy.get(minVoteDeadline).click();
|
||||
cy.get(minEnactDeadline).click();
|
||||
cy.get(proposalDownloadBtn).should('be.visible').click();
|
||||
cy.wrap(
|
||||
getDownloadedProposalJsonPath('vega-update-asset-proposal-')
|
||||
).then((filePath) => {
|
||||
goToMakeNewProposal(governanceProposalType.RAW);
|
||||
submitUniqueRawProposal({ proposalBody: filePath });
|
||||
});
|
||||
cy.get(proposalDownloadBtn)
|
||||
.should('be.visible')
|
||||
.click()
|
||||
.then(() => {
|
||||
cy.wrap(
|
||||
getDownloadedProposalJsonPath('vega-update-asset-proposal-')
|
||||
).then((filePath) => {
|
||||
goToMakeNewProposal(governanceProposalType.RAW);
|
||||
submitUniqueRawProposal({ proposalBody: filePath });
|
||||
});
|
||||
});
|
||||
navigateTo(navigation.proposals);
|
||||
cy.get(openProposals).within(() => {
|
||||
cy.get(proposalType)
|
||||
@@ -471,13 +514,17 @@ context(
|
||||
enterUpdateAssetProposalDetails();
|
||||
cy.get(maxVoteDeadline).click();
|
||||
cy.get(maxEnactDeadline).click();
|
||||
cy.get(proposalDownloadBtn).should('be.visible').click();
|
||||
cy.wrap(
|
||||
getDownloadedProposalJsonPath('vega-update-asset-proposal-')
|
||||
).then((filePath) => {
|
||||
goToMakeNewProposal(governanceProposalType.RAW);
|
||||
submitUniqueRawProposal({ proposalBody: filePath });
|
||||
});
|
||||
cy.get(proposalDownloadBtn)
|
||||
.should('be.visible')
|
||||
.click()
|
||||
.then(() => {
|
||||
cy.wrap(
|
||||
getDownloadedProposalJsonPath('vega-update-asset-proposal-')
|
||||
).then((filePath) => {
|
||||
goToMakeNewProposal(governanceProposalType.RAW);
|
||||
submitUniqueRawProposal({ proposalBody: filePath });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Unable to submit edit asset proposal with missing/invalid fields', function () {
|
||||
@@ -525,6 +572,13 @@ context(
|
||||
});
|
||||
});
|
||||
|
||||
after('Disassociate from second wallet key if present', function () {
|
||||
cy.reload();
|
||||
waitForSpinner();
|
||||
ethereumWalletConnect();
|
||||
dissociateFromSecondWalletKey();
|
||||
});
|
||||
|
||||
function validateDialogContentMsg(expectedMsg: string) {
|
||||
cy.getByTestId('dialog-content')
|
||||
.last()
|
||||
|
||||
@@ -86,11 +86,12 @@ describe('Governance flow for proposal list', { tags: '@slow' }, function () {
|
||||
});
|
||||
|
||||
it('Newly created proposals list - shows title and portion of summary', function () {
|
||||
const proposalPath = '/proposals/new-market-raw.json';
|
||||
const enactmentTimestamp = createTenDigitUnixTimeStampForSpecifiedDays(3);
|
||||
const proposalPath = 'src/fixtures/proposals/new-market-raw.json';
|
||||
const proposalTimestamp = createTenDigitUnixTimeStampForSpecifiedDays(3);
|
||||
submitUniqueRawProposal({
|
||||
proposalBody: proposalPath,
|
||||
enactmentTimestamp: enactmentTimestamp,
|
||||
enactmentTimestamp: proposalTimestamp,
|
||||
closingTimestamp: proposalTimestamp,
|
||||
}); // 3001-VOTE-052
|
||||
// 3001-VOTE-008
|
||||
// 3001-VOTE-034
|
||||
|
||||
@@ -29,13 +29,17 @@ context('rewards - flow', { tags: '@slow' }, function () {
|
||||
turnTelemetryOff();
|
||||
cy.visit('/');
|
||||
waitForSpinner();
|
||||
depositAsset(vegaAssetAddress, '1000', 18);
|
||||
ethereumWalletConnect();
|
||||
cy.connectVegaWallet();
|
||||
depositAsset(vegaAssetAddress, '1000', 18);
|
||||
cy.getByTestId('currency-title', txTimeout).should(
|
||||
'contain.text',
|
||||
'Collateral'
|
||||
);
|
||||
vegaWalletTeardown();
|
||||
cy.associateTokensToVegaWallet('6000');
|
||||
cy.VegaWalletTopUpRewardsPool(30, 200);
|
||||
navigateTo(navigation.validators);
|
||||
cy.VegaWalletTopUpRewardsPool();
|
||||
cy.get(vegaWalletUnstakedBalance, txTimeout).should(
|
||||
'contain',
|
||||
'6,000.0',
|
||||
|
||||
@@ -63,7 +63,7 @@ context(
|
||||
vegaWalletSetSpecifiedApprovalAmount('1000');
|
||||
});
|
||||
|
||||
describe('Eth wallet - contains VEGA tokens', function () {
|
||||
describe.skip('Eth wallet - contains VEGA tokens', function () {
|
||||
beforeEach(
|
||||
'teardown wallet & drill into a specific validator',
|
||||
function () {
|
||||
@@ -109,6 +109,7 @@ context(
|
||||
cy.getByTestId(userStake, epochTimeout)
|
||||
.first()
|
||||
.should('have.text', '2.00');
|
||||
waitForBeginningOfEpoch();
|
||||
cy.getByTestId('total-stake').first().realHover();
|
||||
cy.getByTestId('staked-by-user-tooltip')
|
||||
.first()
|
||||
@@ -379,6 +380,7 @@ context(
|
||||
});
|
||||
|
||||
it('Disassociating some tokens - prioritizes unstaked tokens', function () {
|
||||
vegaWalletSetSpecifiedApprovalAmount('1000');
|
||||
stakingPageAssociateTokens('3');
|
||||
verifyUnstakedBalance(3.0);
|
||||
cy.get('button').contains('Select a validator to nominate').click();
|
||||
@@ -485,6 +487,7 @@ context(
|
||||
});
|
||||
|
||||
afterEach('Teardown Wallet', function () {
|
||||
navigateTo(navigation.home);
|
||||
vegaWalletTeardown();
|
||||
});
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@ const ethWalletContainer = '[data-testid="ethereum-wallet"]';
|
||||
const vegaWalletAssociatedBalance = '[data-testid="currency-value"]';
|
||||
const vegaWalletUnstakedBalance =
|
||||
'[data-testid="vega-wallet-balance-unstaked"]';
|
||||
const currencyTitle = '[data-testid="currency-title"]:visible';
|
||||
const txTimeout = Cypress.env('txTimeout');
|
||||
const vegaWalletPublicKeyShort = Cypress.env('vegaWalletPublicKeyShort');
|
||||
const ethWalletAssociateButton = '[data-testid="associate-btn"]:visible';
|
||||
@@ -39,7 +38,7 @@ const associatedKey = '[data-testid="associated-key"]';
|
||||
const associatedAmount = '[data-testid="associated-amount"]';
|
||||
const associateCompleteText = '[data-testid="transaction-complete-body"]';
|
||||
const disassociationWarning = '[data-testid="disassociation-warning"]';
|
||||
const vegaWallet = '[data-testid="vega-wallet"]';
|
||||
const vegaWallet = 'aside [data-testid="vega-wallet"]';
|
||||
|
||||
context(
|
||||
'Token association flow - with eth and vega wallets connected',
|
||||
@@ -79,27 +78,15 @@ context(
|
||||
//0005-ETXN-003
|
||||
//0005-ETXN-005
|
||||
stakingPageAssociateTokens('2', { skipConfirmation: true });
|
||||
|
||||
cy.get(currencyTitle, txTimeout).should('have.length.above', 4);
|
||||
validateWalletCurrency('Associated', '0.00');
|
||||
validateWalletCurrency('Pending association', '2.00');
|
||||
validateWalletCurrency('Total associated after pending', '2.00');
|
||||
cy.get(currencyTitle, txTimeout).should('have.length.at.least', 6);
|
||||
|
||||
// 0005-ETXN-002
|
||||
verifyEthWalletAssociatedBalance('2.0');
|
||||
|
||||
verifyEthWalletTotalAssociatedBalance('2.0');
|
||||
|
||||
cy.get(vegaWallet)
|
||||
.first()
|
||||
.within(() => {
|
||||
cy.get(vegaWalletAssociatedBalance, txTimeout).should(
|
||||
'contain',
|
||||
2.0
|
||||
);
|
||||
});
|
||||
|
||||
cy.get(vegaWallet).within(() => {
|
||||
cy.get(vegaWalletAssociatedBalance, txTimeout).should('contain', 2.0);
|
||||
});
|
||||
cy.get(vegaWalletUnstakedBalance, txTimeout).should('contain', 2.0);
|
||||
});
|
||||
|
||||
@@ -114,12 +101,11 @@ context(
|
||||
verifyEthWalletAssociatedBalance('2.0');
|
||||
verifyEthWalletTotalAssociatedBalance('6,002.00');
|
||||
cy.get('button').contains('Select a validator to nominate').click();
|
||||
cy.getByTestId('epoch-countdown').should('be.visible');
|
||||
stakingPageDisassociateTokens('2');
|
||||
cy.get(currencyTitle, txTimeout).should('have.length.above', 4);
|
||||
validateWalletCurrency('Associated', '2.00');
|
||||
validateWalletCurrency('Pending association', '2.00');
|
||||
validateWalletCurrency('Total associated after pending', '0.00');
|
||||
cy.get(currencyTitle, txTimeout).should('have.length.at.least', 6);
|
||||
cy.get(
|
||||
'[data-testid="eth-wallet-associated-balances"]:visible',
|
||||
txTimeout
|
||||
@@ -132,38 +118,26 @@ context(
|
||||
stakingPageAssociateTokens('1001', { approve: true });
|
||||
verifyEthWalletAssociatedBalance('1,001.00');
|
||||
verifyEthWalletTotalAssociatedBalance('7,001.00');
|
||||
cy.get(vegaWallet)
|
||||
.last()
|
||||
.within(() => {
|
||||
cy.get(vegaWalletAssociatedBalance, txTimeout).should(
|
||||
'contain',
|
||||
'1,001.00'
|
||||
);
|
||||
});
|
||||
cy.get(vegaWallet).within(() => {
|
||||
cy.get(vegaWalletAssociatedBalance, txTimeout).should(
|
||||
'contain',
|
||||
'1,001.00'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('Able to disassociate a partial amount of tokens currently associated', function () {
|
||||
stakingPageAssociateTokens('2');
|
||||
cy.get(vegaWallet)
|
||||
.first()
|
||||
.within(() => {
|
||||
cy.get(vegaWalletAssociatedBalance, txTimeout).should(
|
||||
'contain',
|
||||
2.0
|
||||
);
|
||||
});
|
||||
|
||||
cy.get(vegaWallet).within(() => {
|
||||
cy.get(vegaWalletAssociatedBalance, txTimeout).should('contain', 2.0);
|
||||
});
|
||||
cy.get('button').contains('Select a validator to nominate').click();
|
||||
cy.getByTestId('epoch-countdown').should('be.visible');
|
||||
stakingPageDisassociateTokens('1');
|
||||
verifyEthWalletAssociatedBalance('1.0');
|
||||
cy.get(vegaWallet)
|
||||
.first()
|
||||
.within(() => {
|
||||
cy.get(vegaWalletAssociatedBalance, txTimeout).should(
|
||||
'contain',
|
||||
1.0
|
||||
);
|
||||
});
|
||||
cy.get(vegaWallet).within(() => {
|
||||
cy.get(vegaWalletAssociatedBalance, txTimeout).should('contain', 1.0);
|
||||
});
|
||||
});
|
||||
|
||||
it('Able to disassociate all tokens - using max', function () {
|
||||
@@ -171,15 +145,11 @@ context(
|
||||
const warningText =
|
||||
'Warning: Any tokens that have been nominated to a node will sacrifice rewards they are due for the current epoch. If you do not wish to sacrifice these, you should remove stake from a node at the end of an epoch before disassociation.';
|
||||
stakingPageAssociateTokens('2');
|
||||
cy.get(vegaWallet)
|
||||
.first()
|
||||
.within(() => {
|
||||
cy.get(vegaWalletAssociatedBalance, txTimeout).should(
|
||||
'contain',
|
||||
2.0
|
||||
);
|
||||
});
|
||||
cy.get(vegaWallet).within(() => {
|
||||
cy.get(vegaWalletAssociatedBalance, txTimeout).should('contain', 2.0);
|
||||
});
|
||||
cy.get('button').contains('Select a validator to nominate').click();
|
||||
cy.getByTestId('epoch-countdown').should('be.visible');
|
||||
cy.get(ethWalletDissociateButton).click();
|
||||
cy.get(disassociationWarning).should('contain', warningText);
|
||||
stakingPageDisassociateAllTokens();
|
||||
@@ -197,14 +167,9 @@ context(
|
||||
'not.exist'
|
||||
);
|
||||
});
|
||||
cy.get(vegaWallet)
|
||||
.first()
|
||||
.within(() => {
|
||||
cy.get(vegaWalletAssociatedBalance, txTimeout).should(
|
||||
'contain',
|
||||
0.0
|
||||
);
|
||||
});
|
||||
cy.get(vegaWallet).within(() => {
|
||||
cy.get(vegaWalletAssociatedBalance, txTimeout).should('contain', 0.0);
|
||||
});
|
||||
});
|
||||
|
||||
it('Able to associate and disassociate vesting contract tokens', function () {
|
||||
@@ -219,32 +184,22 @@ context(
|
||||
type: 'contract',
|
||||
skipConfirmation: true,
|
||||
});
|
||||
|
||||
cy.get(currencyTitle, txTimeout).should('have.length.above', 4);
|
||||
validateWalletCurrency('Associated', '0.00');
|
||||
validateWalletCurrency('Pending association', '2.00');
|
||||
validateWalletCurrency('Total associated after pending', '2.00');
|
||||
cy.get(currencyTitle, txTimeout).should('have.length.at.least', 6);
|
||||
verifyEthWalletAssociatedBalance('2.0');
|
||||
verifyEthWalletTotalAssociatedBalance('2.0');
|
||||
cy.get(vegaWallet)
|
||||
.first()
|
||||
.within(() => {
|
||||
cy.get(vegaWalletAssociatedBalance, txTimeout).should(
|
||||
'contain',
|
||||
2.0
|
||||
);
|
||||
});
|
||||
cy.get(vegaWallet).within(() => {
|
||||
cy.get(vegaWalletAssociatedBalance, txTimeout).should('contain', 2.0);
|
||||
});
|
||||
cy.get(vegaWalletUnstakedBalance, txTimeout).should('contain', 2.0);
|
||||
stakingPageDisassociateTokens('1', {
|
||||
type: 'contract',
|
||||
skipConfirmation: true,
|
||||
});
|
||||
cy.get(currencyTitle, txTimeout).should('have.length.above', 4);
|
||||
validateWalletCurrency('Associated', '2.00');
|
||||
validateWalletCurrency('Pending association', '1.00');
|
||||
validateWalletCurrency('Total associated after pending', '1.00');
|
||||
cy.get(currencyTitle, txTimeout).should('have.length.at.least', 6);
|
||||
verifyEthWalletAssociatedBalance('1.0');
|
||||
verifyEthWalletTotalAssociatedBalance('1.0');
|
||||
});
|
||||
@@ -256,6 +211,7 @@ context(
|
||||
// 1004-ASSO-022
|
||||
stakingPageAssociateTokens('21', { type: 'wallet' });
|
||||
cy.get('button').contains('Select a validator to nominate').click();
|
||||
cy.getByTestId('epoch-countdown').should('be.visible');
|
||||
stakingPageAssociateTokens('37', { type: 'contract' });
|
||||
cy.get(vestingContractSection)
|
||||
.first()
|
||||
@@ -275,28 +231,18 @@ context(
|
||||
);
|
||||
cy.get(associatedAmount, txTimeout).should('contain', 21);
|
||||
});
|
||||
cy.get(vegaWallet)
|
||||
.first()
|
||||
.within(() => {
|
||||
cy.get(vegaWalletAssociatedBalance, txTimeout).should(
|
||||
'contain',
|
||||
58
|
||||
);
|
||||
});
|
||||
cy.get(vegaWallet).within(() => {
|
||||
cy.get(vegaWalletAssociatedBalance, txTimeout).should('contain', 58);
|
||||
});
|
||||
stakingPageDisassociateTokens('6', { type: 'contract' });
|
||||
cy.get(vestingContractSection)
|
||||
.first()
|
||||
.within(() => {
|
||||
cy.get(associatedAmount, txTimeout).should('contain', 31);
|
||||
});
|
||||
cy.get(vegaWallet)
|
||||
.first()
|
||||
.within(() => {
|
||||
cy.get(vegaWalletAssociatedBalance, txTimeout).should(
|
||||
'contain',
|
||||
52
|
||||
);
|
||||
});
|
||||
cy.get(vegaWallet).within(() => {
|
||||
cy.get(vegaWalletAssociatedBalance, txTimeout).should('contain', 52);
|
||||
});
|
||||
navigateTo(navigation.validators);
|
||||
stakingPageDisassociateTokens('9', { type: 'wallet' });
|
||||
cy.get(vegaInWalletSection)
|
||||
@@ -304,14 +250,9 @@ context(
|
||||
.within(() => {
|
||||
cy.get(associatedAmount, txTimeout).should('contain', 12);
|
||||
});
|
||||
cy.get(vegaWallet)
|
||||
.first()
|
||||
.within(() => {
|
||||
cy.get(vegaWalletAssociatedBalance, txTimeout).should(
|
||||
'contain',
|
||||
43
|
||||
);
|
||||
});
|
||||
cy.get(vegaWallet).within(() => {
|
||||
cy.get(vegaWalletAssociatedBalance, txTimeout).should('contain', 43);
|
||||
});
|
||||
});
|
||||
|
||||
it('Not able to associate more tokens than owned', function () {
|
||||
@@ -328,11 +269,9 @@ context(
|
||||
// 1004-ASSO-004
|
||||
it('Pending association outside of app is shown', function () {
|
||||
vegaWalletAssociate('2');
|
||||
cy.get(currencyTitle, txTimeout).should('have.length.above', 4);
|
||||
validateWalletCurrency('Associated', '0.00');
|
||||
validateWalletCurrency('Pending association', '2.00');
|
||||
validateWalletCurrency('Total associated after pending', '2.00');
|
||||
cy.get(currencyTitle, txTimeout).should('have.length.at.least', 6);
|
||||
validateWalletCurrency('Associated', '2.00');
|
||||
});
|
||||
|
||||
@@ -341,11 +280,9 @@ context(
|
||||
cy.wrap(validateWalletCurrency('Associated', '2.00')).then(() => {
|
||||
vegaWalletDisassociate('2');
|
||||
});
|
||||
cy.get(currencyTitle, txTimeout).should('have.length.above', 4);
|
||||
validateWalletCurrency('Associated', '2.00');
|
||||
validateWalletCurrency('Pending association', '2.00');
|
||||
validateWalletCurrency('Total associated after pending', '0.00');
|
||||
cy.get(currencyTitle, txTimeout).should('have.length.at.least', 6);
|
||||
validateWalletCurrency('Associated', '0.00');
|
||||
});
|
||||
|
||||
@@ -364,14 +301,9 @@ context(
|
||||
Cypress.env('vegaWalletPublicKey2')
|
||||
);
|
||||
stakingPageAssociateTokens('2');
|
||||
cy.get(vegaWallet)
|
||||
.first()
|
||||
.within(() => {
|
||||
cy.get(vegaWalletAssociatedBalance, txTimeout).should(
|
||||
'contain',
|
||||
2.0
|
||||
);
|
||||
});
|
||||
cy.get(vegaWallet).within(() => {
|
||||
cy.get(vegaWalletAssociatedBalance, txTimeout).should('contain', 2.0);
|
||||
});
|
||||
cy.get(associateCompleteText).should(
|
||||
'have.text',
|
||||
`Vega key ${Cypress.env(
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { stakingPageDisassociateAllTokens } from './staking.functions';
|
||||
|
||||
const tokenDropDown = 'state-trigger';
|
||||
const txTimeout = Cypress.env('txTimeout');
|
||||
|
||||
export enum navigation {
|
||||
section = 'nav',
|
||||
home = '[href="/"]',
|
||||
vesting = '[href="/token/redeem"]',
|
||||
validators = '[href="/validators"]',
|
||||
rewards = '[href="/rewards"]',
|
||||
@@ -18,6 +21,7 @@ export function convertTokenValueToNumber(subject: string) {
|
||||
}
|
||||
|
||||
const topLevelRoutes = [
|
||||
navigation.home,
|
||||
navigation.proposals,
|
||||
navigation.validators,
|
||||
navigation.rewards,
|
||||
@@ -97,3 +101,25 @@ export function turnTelemetryOff() {
|
||||
win.localStorage.setItem('vega_telemetry_on', 'false')
|
||||
);
|
||||
}
|
||||
|
||||
export function dissociateFromSecondWalletKey() {
|
||||
const secondWalletKey = Cypress.env('vegaWalletPublicKey2Short');
|
||||
cy.getByTestId('vega-in-wallet')
|
||||
.first()
|
||||
.within(() => {
|
||||
cy.getByTestId('eth-wallet-associated-balances')
|
||||
.last()
|
||||
.within(() => {
|
||||
cy.getByTestId('associated-key')
|
||||
.invoke('text')
|
||||
.as('associatedPubKey');
|
||||
});
|
||||
});
|
||||
cy.get('@associatedPubKey').then((associatedPubKey) => {
|
||||
if (associatedPubKey == secondWalletKey) {
|
||||
cy.get('[data-testid="manage-vega-wallet"]:visible').click();
|
||||
cy.get('[data-testid="select-keypair-button"]').eq(0).click();
|
||||
stakingPageDisassociateAllTokens();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -59,29 +59,29 @@ export function submitUniqueRawProposal(proposalFields: {
|
||||
submit?: boolean;
|
||||
}) {
|
||||
goToMakeNewProposal(governanceProposalType.RAW);
|
||||
let proposalBodyPath = '/proposals/raw.json';
|
||||
let proposalBodyPath = 'src/fixtures/proposals/raw.json';
|
||||
if (proposalFields.proposalBody) {
|
||||
proposalBodyPath = proposalFields.proposalBody;
|
||||
}
|
||||
cy.readFile(proposalBodyPath).then((rawProposal) => {
|
||||
if (!proposalFields.proposalBody) {
|
||||
if (proposalFields.proposalTitle) {
|
||||
rawProposal.rationale.title = proposalFields.proposalTitle;
|
||||
cy.wrap(proposalFields.proposalTitle).as('proposalTitle');
|
||||
}
|
||||
if (proposalFields.proposalDescription) {
|
||||
rawProposal.rationale.description = proposalFields.proposalDescription;
|
||||
}
|
||||
if (proposalFields.closingTimestamp) {
|
||||
rawProposal.terms.closingTimestamp = proposalFields.closingTimestamp;
|
||||
} else {
|
||||
const minTimeStamp = createTenDigitUnixTimeStampForSpecifiedDays(2);
|
||||
rawProposal.terms.closingTimestamp = minTimeStamp;
|
||||
}
|
||||
if (proposalFields.enactmentTimestamp) {
|
||||
rawProposal.terms.enactmentTimestamp =
|
||||
proposalFields.enactmentTimestamp;
|
||||
}
|
||||
if (proposalFields.proposalTitle) {
|
||||
rawProposal.rationale.title = proposalFields.proposalTitle;
|
||||
cy.wrap(proposalFields.proposalTitle).as('proposalTitle');
|
||||
}
|
||||
if (proposalFields.proposalDescription) {
|
||||
rawProposal.rationale.description = proposalFields.proposalDescription;
|
||||
}
|
||||
if (proposalFields.closingTimestamp) {
|
||||
rawProposal.terms.closingTimestamp = proposalFields.closingTimestamp;
|
||||
} else if (
|
||||
!proposalFields.closingTimestamp &&
|
||||
!proposalFields.proposalBody
|
||||
) {
|
||||
const minTimeStamp = createTenDigitUnixTimeStampForSpecifiedDays(2);
|
||||
rawProposal.terms.closingTimestamp = minTimeStamp;
|
||||
}
|
||||
if (proposalFields.enactmentTimestamp) {
|
||||
rawProposal.terms.enactmentTimestamp = proposalFields.enactmentTimestamp;
|
||||
}
|
||||
|
||||
const proposalPayload = JSON.stringify(rawProposal);
|
||||
|
||||
@@ -236,8 +236,8 @@ export function validateWalletCurrency(
|
||||
currencyTitle: string,
|
||||
expectedAmount: string
|
||||
) {
|
||||
cy.get("[data-testid='currency-title']")
|
||||
.contains(currencyTitle)
|
||||
cy.get("[data-testid='currency-title']", txTimeout)
|
||||
.contains(currencyTitle, txTimeout)
|
||||
.parent()
|
||||
.parent()
|
||||
.within(() => {
|
||||
|
||||
@@ -19,7 +19,7 @@ const ethStakingBridgeContractAddress = Cypress.env(
|
||||
);
|
||||
const ethProviderUrl = Cypress.env('ethProviderUrl');
|
||||
const getAccount = (number = 0) => `m/44'/60'/0'/0/${number}`;
|
||||
const transactionTimeout = 100000;
|
||||
const transactionTimeout = { timeout: 100000, log: false };
|
||||
const Erc20BridgeAddress = '0x9708FF7510D4A7B9541e1699d15b53Ecb1AFDc54';
|
||||
|
||||
const provider = new ethers.providers.JsonRpcProvider({ url: ethProviderUrl });
|
||||
@@ -43,10 +43,7 @@ export async function depositAsset(
|
||||
const faucet = new Token(assetEthAddress, signer);
|
||||
cy.wrap(
|
||||
faucet.approve(Erc20BridgeAddress, amount + '0'.repeat(decimalPlaces + 1)),
|
||||
{
|
||||
timeout: transactionTimeout,
|
||||
log: false,
|
||||
}
|
||||
transactionTimeout
|
||||
).then(() => {
|
||||
const collateralBridge = new CollateralBridge(Erc20BridgeAddress, signer);
|
||||
cy.wrap(
|
||||
@@ -55,7 +52,7 @@ export async function depositAsset(
|
||||
amount + '0'.repeat(decimalPlaces),
|
||||
'0x' + vegaWalletPubKey
|
||||
),
|
||||
{ timeout: transactionTimeout, log: false }
|
||||
transactionTimeout
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -79,13 +76,13 @@ export async function vegaWalletTeardown() {
|
||||
}
|
||||
});
|
||||
cy.get(vegaWalletContainer).within(() => {
|
||||
cy.get(associatedAmountInWallet, {
|
||||
timeout: transactionTimeout,
|
||||
})
|
||||
.should('have.length', 1, { timeout: transactionTimeout })
|
||||
.contains('0.00', {
|
||||
timeout: transactionTimeout,
|
||||
});
|
||||
cy.get(associatedAmountInWallet, transactionTimeout).should(
|
||||
'have.length',
|
||||
1
|
||||
);
|
||||
cy.get(associatedAmountInWallet)
|
||||
.first(transactionTimeout)
|
||||
.should('have.text', '0.00');
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -109,7 +106,7 @@ async function vegaWalletTeardownStaking(stakingBridgeContract: StakingBridge) {
|
||||
cy.highlight('Tearing down staking tokens from vega wallet if present');
|
||||
cy.wrap(
|
||||
stakingBridgeContract.stake_balance(ethWalletPubKey, vegaWalletPubKey),
|
||||
{ timeout: transactionTimeout }
|
||||
transactionTimeout
|
||||
).then((stakeBalance) => {
|
||||
if (Number(stakeBalance) != 0) {
|
||||
cy.get(vegaWalletContainer).within(() => {
|
||||
@@ -122,31 +119,25 @@ async function vegaWalletTeardownStaking(stakingBridgeContract: StakingBridge) {
|
||||
String(stakeBalance),
|
||||
vegaWalletPubKey
|
||||
),
|
||||
{ timeout: transactionTimeout }
|
||||
transactionTimeout
|
||||
);
|
||||
cy.wrap(
|
||||
vestingContract.stake_balance(ethWalletPubKey, vegaWalletPubKey),
|
||||
{
|
||||
timeout: transactionTimeout,
|
||||
log: false,
|
||||
}
|
||||
transactionTimeout
|
||||
).then((vestingAmount) => {
|
||||
if (Number(vestingAmount) != 0) {
|
||||
cy.contains('Associated', {
|
||||
timeout: transactionTimeout,
|
||||
})
|
||||
cy.contains('Associated', transactionTimeout)
|
||||
.parent()
|
||||
.parent()
|
||||
.within(() => {
|
||||
cy.getByTestId('currency-value', {
|
||||
timeout: transactionTimeout,
|
||||
})
|
||||
.should('have.length', 1)
|
||||
cy.getByTestId('currency-value', transactionTimeout)
|
||||
.first()
|
||||
.invoke('text')
|
||||
.as('displayedAmount');
|
||||
cy.get('@displayedAmount', {
|
||||
timeout: transactionTimeout,
|
||||
}).should('not.eq', $associatedAmount);
|
||||
cy.get('@displayedAmount', transactionTimeout).should(
|
||||
'not.eq',
|
||||
$associatedAmount
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -158,14 +149,14 @@ async function vegaWalletTeardownStaking(stakingBridgeContract: StakingBridge) {
|
||||
|
||||
async function vegaWalletTeardownVesting(vestingContract: TokenVesting) {
|
||||
cy.highlight('Tearing down vesting tokens from vega wallet if present');
|
||||
cy.wrap(vestingContract.stake_balance(ethWalletPubKey, vegaWalletPubKey), {
|
||||
timeout: transactionTimeout,
|
||||
log: false,
|
||||
}).then((vestingAmount) => {
|
||||
cy.wrap(
|
||||
vestingContract.stake_balance(ethWalletPubKey, vegaWalletPubKey),
|
||||
transactionTimeout
|
||||
).then((vestingAmount) => {
|
||||
if (Number(vestingAmount) != 0) {
|
||||
cy.wrap(
|
||||
vestingContract.remove_stake(String(vestingAmount), vegaWalletPubKey),
|
||||
{ timeout: transactionTimeout }
|
||||
transactionTimeout
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -6,7 +6,7 @@ NX_VEGA_NETWORKS='{"DEVNET":"https://dev.governance.vega.xyz","TESTNET":"https:/
|
||||
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_VEGA_EXPLORER_URL=https://dev.explorer.vega.xyz
|
||||
NX_VEGA_EXPLORER_URL=#
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_DELEGATIONS_PAGINATION=50
|
||||
NX_TRANCHES_SERVICE_URL=https://tranches-devnet1-k8s.ops.vega.xyz
|
||||
|
||||
@@ -7,7 +7,7 @@ NX_VEGA_NETWORKS='{"DEVNET":"https://dev.governance.vega.xyz","TESTNET":"https:/
|
||||
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||
NX_VEGA_EXPLORER_URL=https://dev.explorer.vega.xyz
|
||||
NX_VEGA_EXPLORER_URL=https://explorer.validators-testnet.vega.rocks/
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
|
||||
NX_VEGA_REST_URL=https://api-validators-testnet.vega.rocks/api/v2/
|
||||
NX_SENTRY_DSN=https://4b8c8a8ba07742648aa4dfe1b8d17e40@o286262.ingest.sentry.io/5882996
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from './multisig-incorrect-notice';
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { useEthereumConfig } from '@vegaprotocol/web3';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { MultisigIncorrectNotice } from './multisig-incorrect-notice';
|
||||
|
||||
jest.mock('@vegaprotocol/web3', () => ({
|
||||
useEthereumConfig: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('@vegaprotocol/environment', () => ({
|
||||
useEnvironment: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('MultisigIncorrectNotice', () => {
|
||||
it('renders correctly when config is provided', () => {
|
||||
(useEthereumConfig as jest.Mock).mockReturnValue({
|
||||
config: {
|
||||
multisig_control_contract: {
|
||||
address: '0x1234',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
(useEnvironment as unknown as jest.Mock).mockReturnValue({
|
||||
ETHERSCAN_URL: 'https://etherscan.io',
|
||||
});
|
||||
|
||||
render(<MultisigIncorrectNotice />);
|
||||
|
||||
expect(screen.getByTestId('multisig-contract-link')).toHaveAttribute(
|
||||
'href',
|
||||
'https://etherscan.io/address/0x1234'
|
||||
);
|
||||
expect(screen.getByTestId('multisig-contract-link')).toHaveAttribute(
|
||||
'title',
|
||||
'0x1234'
|
||||
);
|
||||
expect(
|
||||
screen.getByTestId('multisig-validators-learn-more')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does not render when config is not provided', () => {
|
||||
(useEthereumConfig as jest.Mock).mockReturnValue({
|
||||
config: null,
|
||||
});
|
||||
|
||||
const { container } = render(<MultisigIncorrectNotice />);
|
||||
|
||||
expect(container.firstChild).toBeNull();
|
||||
});
|
||||
});
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Callout, Intent, Link } from '@vegaprotocol/ui-toolkit';
|
||||
import { useEthereumConfig } from '@vegaprotocol/web3';
|
||||
import { useEnvironment, DocsLinks } from '@vegaprotocol/environment';
|
||||
import type { EthereumConfig } from '@vegaprotocol/web3';
|
||||
|
||||
export const MultisigIncorrectNotice = () => {
|
||||
const { t } = useTranslation();
|
||||
const { config } = useEthereumConfig();
|
||||
const { ETHERSCAN_URL } = useEnvironment();
|
||||
|
||||
if (!config) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const contract = config[
|
||||
'multisig_control_contract' as keyof EthereumConfig
|
||||
] as {
|
||||
address: string;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mb-10">
|
||||
<Callout intent={Intent.Warning}>
|
||||
<div>
|
||||
<Link
|
||||
title={contract.address}
|
||||
href={`${ETHERSCAN_URL}/address/${contract.address}`}
|
||||
target="_blank"
|
||||
data-testid="multisig-contract-link"
|
||||
>
|
||||
{t('multisigContractLink')}
|
||||
</Link>{' '}
|
||||
{t('multisigContractIncorrect')}
|
||||
</div>
|
||||
|
||||
<div className="mt-2">
|
||||
<Link
|
||||
href={DocsLinks?.VALIDATOR_SCORES_REWARDS}
|
||||
target="_blank"
|
||||
data-testid="multisig-validators-learn-more"
|
||||
>
|
||||
{t('learnMore')}
|
||||
</Link>
|
||||
</div>
|
||||
</Callout>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -116,7 +116,7 @@
|
||||
"Showing tranches with <{{trancheMinimum}} VEGA, click to hide these tranches": "Showing tranches with ≤{{trancheMinimum}} $VEGA, click to hide these tranches",
|
||||
"Not showing tranches with <{{trancheMinimum}} VEGA, click to show all tranches": "Not showing tranches with ≤{{trancheMinimum}} $VEGA, click to show all tranches",
|
||||
"the holder": "the holder",
|
||||
"We couldn't seem to load your data.": "We couldn't seem to load your data.",
|
||||
"Your data couldn't be loaded": "Your data couldn't be loaded",
|
||||
"Vesting VEGA": "Vesting VEGA",
|
||||
"All the tokens in this tranche are locked and can not be redeemed yet.": "All the tokens in this tranche are locked and can not be redeemed yet.",
|
||||
"Redeem unlocked VEGA from tranche {{id}}": "Redeem unlocked $VEGA from tranche {{id}}",
|
||||
@@ -728,7 +728,7 @@
|
||||
"ThisWillSetEnactmentDeadlineTo": "This will set the enactment date to",
|
||||
"ThisWillSetValidationDeadlineTo": "This will set the validation deadline to",
|
||||
"Hours": "hours",
|
||||
"ThisWillAdd2MinutesToAllowTimeToConfirmInWallet": "Note: we add 2 minutes of extra time when you choose the minimum value. This gives you time to confirm the proposal in your wallet.",
|
||||
"ThisWillAdd2MinutesToAllowTimeToConfirmInWallet": "Note: 2 minutes of extra time are added when you choose the minimum value. This gives you time to confirm the proposal in your wallet.",
|
||||
"ProposalWillFailIfEnactmentIsEarlierThanVotingDeadline": "Proposal will fail if enactment is earlier than the voting deadline",
|
||||
"SelectAMarketToChange": "Select a market to change",
|
||||
"MarketName": "Market name",
|
||||
@@ -821,5 +821,8 @@
|
||||
"disclaimer2": "The Vega Governance App is free, public and open source software. Software upgrades may contain bugs or security vulnerabilities that might result in loss of functionality.",
|
||||
"disclaimer3": "The Vega Governance App uses data obtained from nodes on the Vega Blockchain. The developers of the Vega Governance App do not operate or run the Vega Blockchain or any other blockchain.",
|
||||
"disclaimer4": "The Vega Governance App is provided “as is”. The developers of the Vega Governance App make no representations or warranties of any kind, whether express or implied, statutory or otherwise regarding the Vega Governance App. They disclaim all warranties of merchantability, quality, fitness for purpose. They disclaim all warranties that the Vega Governance App is free of harmful components or errors.",
|
||||
"disclaimer5": "No developer of the Vega Governance App accepts any responsibility for, or liability to users in connection with their use of the Vega Governance App."
|
||||
"disclaimer5": "No developer of the Vega Governance App accepts any responsibility for, or liability to users in connection with their use of the Vega Governance App.",
|
||||
"multisigContractLink": "Ethereum Multisig Contract",
|
||||
"multisigContractIncorrect": "is incorrectly configured. Validator and delegator rewards will be penalised until this is resolved.",
|
||||
"learnMore": "Learn more"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
import {
|
||||
getMultisigStatusInfo,
|
||||
MultisigStatus,
|
||||
} from './get-multisig-status-info';
|
||||
import type { PreviousEpochQuery } from '../routes/staking/__generated__/PreviousEpoch';
|
||||
|
||||
const createNode = (id: string, multisigScore: string) => ({
|
||||
node: {
|
||||
id,
|
||||
stakedTotal: '1000',
|
||||
rewardScore: { multisigScore },
|
||||
},
|
||||
});
|
||||
|
||||
describe('getMultisigStatus', () => {
|
||||
it('should return MultisigStatus.noNodes when no nodes are present', () => {
|
||||
const result = getMultisigStatusInfo({
|
||||
epoch: { id: '1', validatorsConnection: { edges: [] } },
|
||||
} as PreviousEpochQuery);
|
||||
expect(result).toEqual({
|
||||
multisigStatus: MultisigStatus.noNodes,
|
||||
showMultisigStatusError: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should return MultisigStatus.correct when all nodes have multisigScore of 1', () => {
|
||||
const result = getMultisigStatusInfo({
|
||||
epoch: {
|
||||
id: '1',
|
||||
validatorsConnection: {
|
||||
edges: [createNode('1', '1'), createNode('2', '1')],
|
||||
},
|
||||
},
|
||||
} as PreviousEpochQuery);
|
||||
expect(result).toEqual({
|
||||
multisigStatus: MultisigStatus.correct,
|
||||
showMultisigStatusError: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should return MultisigStatus.nodeNeedsRemoving when all nodes have multisigScore of 0', () => {
|
||||
const result = getMultisigStatusInfo({
|
||||
epoch: {
|
||||
id: '1',
|
||||
validatorsConnection: {
|
||||
edges: [createNode('1', '0'), createNode('2', '0')],
|
||||
},
|
||||
},
|
||||
} as PreviousEpochQuery);
|
||||
expect(result).toEqual({
|
||||
multisigStatus: MultisigStatus.nodeNeedsRemoving,
|
||||
showMultisigStatusError: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should return MultisigStatus.nodeNeedsAdding when some nodes have multisigScore of 0 and others have 1', () => {
|
||||
const result = getMultisigStatusInfo({
|
||||
epoch: {
|
||||
id: '1',
|
||||
validatorsConnection: {
|
||||
edges: [createNode('1', '0'), createNode('2', '1')],
|
||||
},
|
||||
},
|
||||
} as PreviousEpochQuery);
|
||||
expect(result).toEqual({
|
||||
multisigStatus: MultisigStatus.nodeNeedsAdding,
|
||||
showMultisigStatusError: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
import { removePaginationWrapper } from '@vegaprotocol/utils';
|
||||
import type { PreviousEpochQuery } from '../routes/staking/__generated__/PreviousEpoch';
|
||||
|
||||
export enum MultisigStatus {
|
||||
'correct' = 'correct',
|
||||
'nodeNeedsAdding' = 'nodeNeedsAdding',
|
||||
'nodeNeedsRemoving' = 'nodeNeedsRemoving ',
|
||||
'noNodes' = 'noNodes',
|
||||
}
|
||||
|
||||
export const getMultisigStatusInfo = (
|
||||
previousEpochData: PreviousEpochQuery
|
||||
) => {
|
||||
let status = MultisigStatus.noNodes;
|
||||
|
||||
const allNodesInPreviousEpoch = removePaginationWrapper(
|
||||
previousEpochData?.epoch.validatorsConnection?.edges
|
||||
);
|
||||
|
||||
const hasZero = allNodesInPreviousEpoch.some(
|
||||
(node) => Number(node?.rewardScore?.multisigScore) === 0
|
||||
);
|
||||
const hasOne = allNodesInPreviousEpoch.some(
|
||||
(node) => Number(node?.rewardScore?.multisigScore) === 1
|
||||
);
|
||||
|
||||
if (hasZero && hasOne) {
|
||||
// If any individual node has 0 it means that node is missing from the multisig and needs to be added
|
||||
status = MultisigStatus.nodeNeedsAdding;
|
||||
} else if (hasZero) {
|
||||
// If all nodes have 0 it means there is an incorrect address in the multisig that needs to be removed
|
||||
status = MultisigStatus.nodeNeedsRemoving;
|
||||
} else if (allNodesInPreviousEpoch.length > 0) {
|
||||
// If all nodes have 1 it means the multisig is correct
|
||||
status = MultisigStatus.correct;
|
||||
}
|
||||
|
||||
return {
|
||||
showMultisigStatusError: status !== MultisigStatus.correct,
|
||||
multisigStatus: status,
|
||||
};
|
||||
};
|
||||
@@ -102,7 +102,8 @@ export const Proposal = ({ proposal, restData }: ProposalProps) => {
|
||||
</div>
|
||||
|
||||
{proposal.terms.change.__typename !== 'NewMarket' &&
|
||||
proposal.terms.change.__typename !== 'UpdateMarket' && (
|
||||
proposal.terms.change.__typename !== 'UpdateMarket' &&
|
||||
proposal.terms.change.__typename !== 'NewFreeform' && (
|
||||
<div className="mb-4">
|
||||
<ProposalTerms data={proposal.terms} />
|
||||
</div>
|
||||
|
||||
+1
-1
@@ -146,7 +146,7 @@ describe('Proposal form vote, validation and enactment deadline', () => {
|
||||
it('should show the correct datetimes', () => {
|
||||
renderComponent();
|
||||
// Should be adding 2 mins to the vote deadline as the minimum is set by
|
||||
// default, and we add 2 mins for wallet confirmation
|
||||
// default, and 2 mins are added for wallet confirmation
|
||||
expect(screen.getByTestId('voting-date')).toHaveTextContent(
|
||||
'2022-01-01T01:02:00.000Z'
|
||||
);
|
||||
|
||||
@@ -23,6 +23,9 @@ import { useRefreshAfterEpoch } from '../../../hooks/use-refresh-after-epoch';
|
||||
import { DocsLinks } from '@vegaprotocol/environment';
|
||||
import { ConnectToSeeRewards } from '../connect-to-see-rewards';
|
||||
import { EpochTotalRewards } from '../epoch-total-rewards/epoch-total-rewards';
|
||||
import { usePreviousEpochQuery } from '../../staking/__generated__/PreviousEpoch';
|
||||
import { getMultisigStatusInfo } from '../../../lib/get-multisig-status-info';
|
||||
import { MultisigIncorrectNotice } from '../../../components/multisig-incorrect-notice';
|
||||
|
||||
type RewardsView = 'total' | 'individual';
|
||||
|
||||
@@ -41,12 +44,25 @@ export const RewardsPage = () => {
|
||||
|
||||
useRefreshAfterEpoch(epochData?.epoch.timestamps.expiry, refetch);
|
||||
|
||||
const { data: previousEpochData } = usePreviousEpochQuery({
|
||||
variables: {
|
||||
epochId: (Number(epochData?.epoch.id) - 1).toString(),
|
||||
},
|
||||
skip: !epochData?.epoch.id,
|
||||
});
|
||||
|
||||
const multisigStatus = previousEpochData
|
||||
? getMultisigStatusInfo(previousEpochData)
|
||||
: undefined;
|
||||
|
||||
const {
|
||||
params,
|
||||
loading: paramsLoading,
|
||||
error: paramsError,
|
||||
} = useNetworkParams([NetworkParams.reward_staking_delegation_payoutDelay]);
|
||||
|
||||
console.log('params', params);
|
||||
|
||||
const payoutDuration = useMemo(() => {
|
||||
if (!params) {
|
||||
return 0;
|
||||
@@ -78,14 +94,18 @@ export const RewardsPage = () => {
|
||||
)}
|
||||
</p>
|
||||
|
||||
{payoutDuration ? (
|
||||
{multisigStatus?.showMultisigStatusError ? (
|
||||
<MultisigIncorrectNotice />
|
||||
) : null}
|
||||
|
||||
{!multisigStatus?.showMultisigStatusError && payoutDuration ? (
|
||||
<div className="my-8">
|
||||
<Callout
|
||||
title={t('rewardsCallout', {
|
||||
duration: formatDistance(new Date(0), payoutDuration),
|
||||
})}
|
||||
headingLevel={3}
|
||||
intent={Intent.Warning}
|
||||
intent={Intent.Primary}
|
||||
>
|
||||
<p className="mb-0">{t('rewardsCalloutDetail')}</p>
|
||||
</Callout>
|
||||
|
||||
@@ -7,6 +7,8 @@ import { ValidatorTables } from './validator-tables';
|
||||
import { useRefreshAfterEpoch } from '../../../hooks/use-refresh-after-epoch';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { ENV } from '../../../config';
|
||||
import { getMultisigStatusInfo } from '../../../lib/get-multisig-status-info';
|
||||
import { MultisigIncorrectNotice } from '../../../components/multisig-incorrect-notice';
|
||||
|
||||
export const EpochData = () => {
|
||||
// errorPolicy due to vegaprotocol/vega issue 5898
|
||||
@@ -46,12 +48,20 @@ export const EpochData = () => {
|
||||
userStakingRefetch();
|
||||
});
|
||||
|
||||
const multisigStatus = previousEpochData
|
||||
? getMultisigStatusInfo(previousEpochData)
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<AsyncRenderer
|
||||
loading={nodesLoading || userStakingLoading}
|
||||
error={nodesError || userStakingError}
|
||||
data={nodesData}
|
||||
>
|
||||
{multisigStatus?.showMultisigStatusError ? (
|
||||
<MultisigIncorrectNotice />
|
||||
) : null}
|
||||
|
||||
{nodesData?.epoch &&
|
||||
nodesData.epoch.timestamps.start &&
|
||||
nodesData?.epoch.timestamps.expiry && (
|
||||
|
||||
@@ -5,4 +5,4 @@ NX_VEGA_ENV=DEVNET
|
||||
NX_VEGA_NETWORKS={\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"https://trading.stagnet1.vega.rocks\"}
|
||||
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
|
||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||
NX_VEGA_EXPLORER_URL=https://dev.explorer.vega.xyz
|
||||
NX_VEGA_EXPLORER_URL=#
|
||||
|
||||
@@ -7,31 +7,30 @@ import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
getFeeLevels,
|
||||
sumLiquidityCommitted,
|
||||
marketLiquidityDataProvider,
|
||||
lpAggregatedDataProvider,
|
||||
} from '@vegaprotocol/liquidity';
|
||||
import type { MarketLpQuery } from '@vegaprotocol/liquidity';
|
||||
import { marketWithDataProvider } from '@vegaprotocol/markets';
|
||||
import type { MarketWithData } from '@vegaprotocol/markets';
|
||||
|
||||
import { Market } from './market';
|
||||
import { Header } from './header';
|
||||
import { LPProvidersGrid } from './providers';
|
||||
|
||||
const formatMarket = (data: MarketLpQuery) => {
|
||||
const formatMarket = (market: MarketWithData) => {
|
||||
return {
|
||||
name: data?.market?.tradableInstrument.instrument.name,
|
||||
name: market?.tradableInstrument.instrument.name,
|
||||
symbol:
|
||||
data?.market?.tradableInstrument.instrument.product.settlementAsset
|
||||
.symbol,
|
||||
market?.tradableInstrument.instrument.product.settlementAsset.symbol,
|
||||
settlementAsset:
|
||||
data?.market?.tradableInstrument.instrument.product.settlementAsset,
|
||||
targetStake: data?.market?.data?.targetStake,
|
||||
tradingMode: data?.market?.data?.marketTradingMode,
|
||||
trigger: data?.market?.data?.trigger,
|
||||
market?.tradableInstrument.instrument.product.settlementAsset,
|
||||
targetStake: market?.data?.targetStake,
|
||||
tradingMode: market?.data?.marketTradingMode,
|
||||
trigger: market?.data?.trigger,
|
||||
};
|
||||
};
|
||||
|
||||
export const lpDataProvider = makeDerivedDataProvider(
|
||||
[marketLiquidityDataProvider, lpAggregatedDataProvider],
|
||||
[marketWithDataProvider, lpAggregatedDataProvider],
|
||||
([market, lpAggregatedData]) => ({
|
||||
market: { ...formatMarket(market) },
|
||||
liquidityProviders: lpAggregatedData || [],
|
||||
|
||||
@@ -3,3 +3,6 @@ NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks/maste
|
||||
NX_VEGA_URL=https://api.vega.community/graphql
|
||||
NX_VEGA_NETWORKS='{"TESTNET":"https://multisig-signer.fairground.wtf","MAINNET":"https://multisig-signer.vega.xyz"}'
|
||||
NX_VEGA_ENV=MAINNET
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# Static
|
||||
|
||||
A static CDN for Vega assets
|
||||
A static CDN for Vega assets: `static.vega.xyz`
|
||||
|
||||
prepare assets by running: `yarn nx build static`
|
||||
|
||||
@@ -26,27 +26,27 @@
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:first-child {
|
||||
animation-delay: -50ms;
|
||||
animation-delay: -0.1s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(2) {
|
||||
animation-delay: 0.2s;
|
||||
animation-delay: 0.3s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(3) {
|
||||
animation-delay: -0.6s;
|
||||
animation-delay: -0.45s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(4) {
|
||||
animation-delay: 0.4s;
|
||||
animation-delay: 1s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(5) {
|
||||
animation-delay: -0.5s;
|
||||
animation-delay: -0.75s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(6) {
|
||||
animation-delay: 0.3s;
|
||||
animation-delay: 0.9s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(7) {
|
||||
@@ -54,11 +54,11 @@
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(8) {
|
||||
animation-delay: 2s;
|
||||
animation-delay: 1.6s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(9) {
|
||||
animation-delay: -0.9s;
|
||||
animation-delay: -0.45s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(10) {
|
||||
@@ -66,7 +66,7 @@
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(11) {
|
||||
animation-delay: -0.55s;
|
||||
animation-delay: -2.75s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(12) {
|
||||
@@ -74,21 +74,57 @@
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(13) {
|
||||
animation-delay: -0.65s;
|
||||
animation-delay: -1.95s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(14) {
|
||||
animation-delay: 0.7s;
|
||||
animation-delay: 2.8s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(15) {
|
||||
animation-delay: -3.75s;
|
||||
animation-delay: -0.75s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(16) {
|
||||
animation-delay: 1.6s;
|
||||
animation-delay: 4s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(17) {
|
||||
animation-delay: -0.85s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(18) {
|
||||
animation-delay: 1.8s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(19) {
|
||||
animation-delay: -1.9s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(20) {
|
||||
animation-delay: 5s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(21) {
|
||||
animation-delay: -5.25s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(22) {
|
||||
animation-delay: 4.4s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(23) {
|
||||
animation-delay: -5.75s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(24) {
|
||||
animation-delay: 4.8s;
|
||||
animation-direction: reverse;
|
||||
}
|
||||
.pre-loader .loader-item:nth-child(25) {
|
||||
animation-delay: -5s;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
.pre-loader .loader-item {
|
||||
animation: flickering 0.4s linear infinite alternate;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
.pre-loader {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
.loader-item {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: black;
|
||||
}
|
||||
.pre-loader-center {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.pre-loader-wrapper {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
@for $i from 0 through 25 {
|
||||
.loader-item:nth-child(#{$i}) {
|
||||
@if $i % 2 == 0 {
|
||||
animation-delay: #{$i * 50 * random(5)}ms;
|
||||
animation-direction: reverse;
|
||||
} @else {
|
||||
animation-delay: #{$i * -50 * random(5)}ms;
|
||||
animation-direction: alternate;
|
||||
}
|
||||
}
|
||||
}
|
||||
.loader-item {
|
||||
animation: flickering 0.4s linear alternate infinite;
|
||||
}
|
||||
@keyframes flickering {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
25% {
|
||||
opacity: 1;
|
||||
}
|
||||
26% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
html.dark {
|
||||
.pre-loader {
|
||||
.loader-item {
|
||||
background: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -450,3 +450,17 @@ describe('Closed markets', { tags: '@smoke' }, () => {
|
||||
.should('have.text', 'View on Explorer');
|
||||
});
|
||||
});
|
||||
|
||||
describe('no closed markets', { tags: '@smoke', testIsolation: true }, () => {
|
||||
before(() => {
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.visit('/#/markets/all');
|
||||
cy.get('[data-testid="Closed markets"]').click();
|
||||
});
|
||||
|
||||
it('can see no markets message', () => {
|
||||
// 6001-MARK-034
|
||||
cy.getByTestId('tab-closed-markets').should('contain.text', 'No markets');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { aliasGQLQuery } from '@vegaprotocol/cypress';
|
||||
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';
|
||||
const colInstrumentCode = '[col-id="tradableInstrument.instrument.code"]';
|
||||
|
||||
describe('markets all table', { tags: '@smoke' }, () => {
|
||||
beforeEach(() => {
|
||||
@@ -60,7 +63,7 @@ describe('markets all table', { tags: '@smoke' }, () => {
|
||||
// 6001-MARK-035
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find('[col-id="tradableInstrument.instrument.code"]')
|
||||
.find(colInstrumentCode)
|
||||
.should('have.text', 'SOLUSD');
|
||||
|
||||
// 6001-MARK-036
|
||||
@@ -155,6 +158,7 @@ describe('markets all table', { tags: '@smoke' }, () => {
|
||||
});
|
||||
|
||||
it('able to open and sort full market list - market page', () => {
|
||||
// 6001-MARK-064
|
||||
const ExpectedSortedMarkets = [
|
||||
'AAPL.MF21',
|
||||
'BTCUSD.MF21',
|
||||
@@ -167,8 +171,38 @@ describe('markets all table', { tags: '@smoke' }, () => {
|
||||
cy.get('.ag-header-cell-label').contains('Market').click(); // sort by market name
|
||||
for (let i = 0; i < ExpectedSortedMarkets.length; i++) {
|
||||
cy.get(`[row-index=${i}]`)
|
||||
.find('[col-id="tradableInstrument.instrument.code"]')
|
||||
.find(colInstrumentCode)
|
||||
.should('have.text', ExpectedSortedMarkets[i]);
|
||||
}
|
||||
});
|
||||
|
||||
it('can drag and drop columns', () => {
|
||||
// 6001-MARK-065
|
||||
cy.get('.ag-overlay-loading-wrapper').should('not.be.visible');
|
||||
cy.get(colInstrumentCode)
|
||||
.realMouseDown()
|
||||
.realMouseMove(700, 15)
|
||||
.realMouseUp();
|
||||
cy.get(colInstrumentCode).should(($element) => {
|
||||
const attributeValue = $element.attr('aria-colindex');
|
||||
expect(attributeValue).not.to.equal('1');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('no all markets', { tags: '@smoke', testIsolation: true }, () => {
|
||||
before(() => {
|
||||
cy.mockTradingPage();
|
||||
const markets: MarketsQuery = {};
|
||||
cy.mockGQL((req) => {
|
||||
aliasGQLQuery(req, 'Markets', markets);
|
||||
});
|
||||
cy.mockSubscription();
|
||||
cy.visit('/#/markets/all');
|
||||
});
|
||||
|
||||
it('can see no markets message', () => {
|
||||
// 6001-MARK-048
|
||||
cy.getByTestId('tab-all-markets').should('contain.text', 'No markets');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import { MarketTradingModeMapping } from '@vegaprotocol/types';
|
||||
import { MarketState } from '@vegaprotocol/types';
|
||||
|
||||
const marketInfoBtn = 'Info';
|
||||
const row = 'key-value-table-row';
|
||||
const marketTitle = 'accordion-title';
|
||||
const externalLink = 'external-link';
|
||||
const accordionContent = 'accordion-content';
|
||||
const blockExplorerLink = 'block-explorer-link';
|
||||
const dialogClose = 'dialog-close';
|
||||
const dialogContent = 'dialog-content';
|
||||
const externalLink = 'external-link';
|
||||
const githubLink = 'github-link';
|
||||
const liquidityLink = 'view-liquidity-link';
|
||||
const marketInfoBtn = 'Info';
|
||||
const marketTitle = 'accordion-title';
|
||||
const providerName = 'provider-name';
|
||||
const oracleBannerStatus = 'oracle-banner-status';
|
||||
const oracleBannerDialogTrigger = 'oracle-banner-dialog-trigger';
|
||||
const oracleFullProfile = 'oracle-full-profile';
|
||||
const row = 'key-value-table-row';
|
||||
const verifiedProofs = 'verified-proofs';
|
||||
|
||||
describe('market info is displayed', { tags: '@smoke' }, () => {
|
||||
beforeEach(() => {
|
||||
@@ -17,12 +20,7 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
|
||||
});
|
||||
|
||||
before(() => {
|
||||
cy.mockTradingPage(
|
||||
MarketState.STATE_ACTIVE,
|
||||
undefined,
|
||||
undefined,
|
||||
'COMPROMISED'
|
||||
);
|
||||
cy.mockTradingPage(MarketState.STATE_ACTIVE);
|
||||
cy.mockSubscription();
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.wait('@Markets');
|
||||
@@ -30,16 +28,8 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
|
||||
cy.wait('@MarketInfo');
|
||||
});
|
||||
|
||||
it('show oracle banner', () => {
|
||||
cy.getByTestId(marketTitle).contains('Oracle').click();
|
||||
cy.getByTestId(oracleBannerStatus).should('contain.text', 'COMPROMISED');
|
||||
cy.getByTestId(oracleBannerDialogTrigger)
|
||||
.should('contain.text', 'Show more')
|
||||
.click();
|
||||
cy.getByTestId(oracleFullProfile).should('exist');
|
||||
});
|
||||
|
||||
it('current fees displayed', () => {
|
||||
// 6002-MDET-101
|
||||
cy.getByTestId(marketTitle).contains('Current fees').click();
|
||||
validateMarketDataRow(0, 'Maker Fee', '0.02%');
|
||||
validateMarketDataRow(1, 'Infrastructure Fee', '0.05%');
|
||||
@@ -48,6 +38,7 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
|
||||
});
|
||||
|
||||
it('market price', () => {
|
||||
// 6002-MDET-102
|
||||
cy.getByTestId(marketTitle).contains('Market price').click();
|
||||
validateMarketDataRow(0, 'Mark Price', '46,126.90058');
|
||||
validateMarketDataRow(1, 'Best Bid Price', '44,126.90058 ');
|
||||
@@ -55,12 +46,9 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
|
||||
validateMarketDataRow(3, 'Quote Unit', 'BTC');
|
||||
});
|
||||
|
||||
// TODO: fix this test
|
||||
// New volume check logic, added by https://github.com/vegaprotocol/frontend-monorepo/pull/3870 has caused the
|
||||
// 24hr volume assertion to fail as it now reads 'Unknown'
|
||||
it.skip('market volume displayed', () => {
|
||||
it('market volume displayed', () => {
|
||||
// 6002-MDET-103
|
||||
cy.getByTestId(marketTitle).contains('Market volume').click();
|
||||
validateMarketDataRow(0, '24 Hour Volume', '1');
|
||||
validateMarketDataRow(1, 'Open Interest', '-');
|
||||
validateMarketDataRow(2, 'Best Bid Volume', '1');
|
||||
validateMarketDataRow(3, 'Best Offer Volume', '3');
|
||||
@@ -69,11 +57,13 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
|
||||
});
|
||||
|
||||
it('insurance pool displayed', () => {
|
||||
// 6002-MDET-104
|
||||
cy.getByTestId(marketTitle).contains('Insurance pool').click();
|
||||
validateMarketDataRow(0, 'Balance', '0');
|
||||
});
|
||||
|
||||
it('key details displayed', () => {
|
||||
// 6002-MDET-201
|
||||
cy.getByTestId(marketTitle).contains('Key details').click();
|
||||
|
||||
validateMarketDataRow(0, 'Name', 'BTCUSD Monthly (30 Jun 2022)');
|
||||
@@ -89,6 +79,7 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
|
||||
});
|
||||
|
||||
it('instrument displayed', () => {
|
||||
// 6002-MDET-202
|
||||
cy.getByTestId(marketTitle).contains('Instrument').click();
|
||||
|
||||
validateMarketDataRow(0, 'Market Name', 'BTCUSD Monthly (30 Jun 2022)');
|
||||
@@ -97,7 +88,30 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
|
||||
validateMarketDataRow(3, 'Quote Name', 'BTC');
|
||||
});
|
||||
|
||||
it('oracle displayed', () => {
|
||||
// 6002-MDET-203
|
||||
cy.getByTestId(marketTitle).contains('Oracle').click();
|
||||
|
||||
cy.getByTestId(accordionContent)
|
||||
.getByTestId(providerName)
|
||||
.and('contain', 'Another oracle');
|
||||
|
||||
cy.getByTestId(providerName).should('be.visible').click();
|
||||
cy.getByTestId(dialogContent)
|
||||
.eq(1)
|
||||
.within(() => {
|
||||
cy.getByTestId(blockExplorerLink).contains('Block explorer');
|
||||
cy.getByTestId(githubLink).contains('Oracle repository');
|
||||
});
|
||||
cy.getByTestId(dialogClose).click();
|
||||
|
||||
cy.getByTestId(accordionContent)
|
||||
.getByTestId(verifiedProofs)
|
||||
.and('contain', '1');
|
||||
});
|
||||
|
||||
it('settlement asset displayed', () => {
|
||||
// 6002-MDET-206
|
||||
cy.getByTestId(marketTitle).contains('Settlement asset').click();
|
||||
cy.window().then((win) => {
|
||||
cy.stub(win, 'prompt').returns('DISABLED WINDOW PROMPT');
|
||||
@@ -116,9 +130,12 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
|
||||
);
|
||||
validateMarketDataRow(8, 'Withdrawal threshold', '0.0005');
|
||||
validateMarketDataRow(9, 'Lifetime limit', '1,230');
|
||||
validateMarketDataRow(10, 'Infrastructure fee account balance', '0.00001');
|
||||
validateMarketDataRow(11, 'Global reward pool account balance', '0.00002');
|
||||
});
|
||||
|
||||
it('metadata displayed', () => {
|
||||
// 6002-MDET-207
|
||||
cy.getByTestId(marketTitle).contains('Metadata').click();
|
||||
|
||||
validateMarketDataRow(0, 'Formerly', '076BB86A5AA41E3E');
|
||||
@@ -129,18 +146,21 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
|
||||
});
|
||||
|
||||
it('risk model displayed', () => {
|
||||
// 6002-MDET-208
|
||||
cy.getByTestId(marketTitle).contains('Risk model').click();
|
||||
validateMarketDataRow(0, 'Tau', '0.0001140771161');
|
||||
validateMarketDataRow(1, 'Risk Aversion Parameter', '0.01');
|
||||
});
|
||||
|
||||
it('risk parameters displayed', () => {
|
||||
// 6002-MDET-209
|
||||
cy.getByTestId(marketTitle).contains('Risk parameters').click();
|
||||
validateMarketDataRow(0, 'R', '0.016');
|
||||
validateMarketDataRow(1, 'Sigma', '0.3');
|
||||
});
|
||||
|
||||
it('risk factors displayed', () => {
|
||||
// 6002-MDET-210
|
||||
cy.getByTestId(marketTitle).contains('Risk factors').click();
|
||||
|
||||
validateMarketDataRow(0, 'Short', '0.008571790367285281');
|
||||
@@ -148,6 +168,7 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
|
||||
});
|
||||
|
||||
it('price monitoring bounds displayed', () => {
|
||||
// 6002-MDET-211
|
||||
cy.getByTestId(marketTitle).contains('Price monitoring bounds 1').click();
|
||||
cy.get('p.col-span-1').contains('99.99999% probability price bounds');
|
||||
cy.get('p.col-span-1').contains('Within 43,200 seconds');
|
||||
@@ -156,6 +177,7 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
|
||||
});
|
||||
|
||||
it('liquidity monitoring parameters displayed', () => {
|
||||
// 6002-MDET-212
|
||||
cy.getByTestId(marketTitle)
|
||||
.contains('Liquidity monitoring parameters')
|
||||
.click();
|
||||
@@ -166,6 +188,7 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
|
||||
});
|
||||
|
||||
it('liquidity displayed', () => {
|
||||
// 6002-MDET-213
|
||||
cy.getByTestId(marketTitle)
|
||||
.contains(/Liquidity(?! m)/)
|
||||
.click();
|
||||
@@ -173,14 +196,14 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
|
||||
validateMarketDataRow(0, 'Target Stake', '10.00 tBTC');
|
||||
validateMarketDataRow(1, 'Supplied Stake', '0.01 tBTC');
|
||||
validateMarketDataRow(2, 'Market Value Proxy', '20.00 tBTC');
|
||||
|
||||
cy.getByTestId('view-liquidity-link').should(
|
||||
cy.getByTestId(liquidityLink).should(
|
||||
'have.text',
|
||||
'View liquidity provision table'
|
||||
);
|
||||
});
|
||||
|
||||
it('liquidity price range displayed', () => {
|
||||
// 6002-MDET-214
|
||||
cy.getByTestId(marketTitle).contains('Liquidity price range').click();
|
||||
|
||||
validateMarketDataRow(0, 'Liquidity Price Range', '2.00% of mid price');
|
||||
@@ -188,29 +211,8 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
|
||||
validateMarketDataRow(2, 'Highest Price', '47,049.438 BTC');
|
||||
});
|
||||
|
||||
it('oracle displayed', () => {
|
||||
cy.getByTestId(marketTitle).contains('Oracle').click();
|
||||
|
||||
cy.getByTestId(accordionContent)
|
||||
.getByTestId(providerName)
|
||||
.and('contain', 'Another oracle');
|
||||
|
||||
cy.getByTestId(providerName).should('be.visible').click();
|
||||
|
||||
cy.getByTestId('dialog-content')
|
||||
.eq(1)
|
||||
.within(() => {
|
||||
cy.getByTestId('block-explorer-link').contains('Block explorer');
|
||||
cy.getByTestId('github-link').contains('Oracle repository');
|
||||
});
|
||||
cy.getByTestId('dialog-close').click();
|
||||
|
||||
cy.getByTestId(accordionContent)
|
||||
.getByTestId('verified-proofs')
|
||||
.and('contain', '1');
|
||||
});
|
||||
|
||||
it('proposal displayed', () => {
|
||||
// 6002-MDET-301
|
||||
cy.getByTestId(marketTitle).contains('Proposal').click();
|
||||
|
||||
cy.getByTestId(accordionContent)
|
||||
|
||||
@@ -0,0 +1,327 @@
|
||||
import { checkSorting } from '@vegaprotocol/cypress';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
|
||||
const liquidityTab = 'Liquidity';
|
||||
const rowSelector =
|
||||
'[data-testid="tab-liquidity"] .ag-center-cols-container .ag-row';
|
||||
const rowSelectorLiquidityActive =
|
||||
'[data-testid="tab-active"] .ag-center-cols-container .ag-row';
|
||||
const rowSelectorLiquidityInactive =
|
||||
'[data-testid="tab-inactive"] .ag-center-cols-container .ag-row';
|
||||
const marketSummaryBlock = 'header-summary';
|
||||
const itemValue = 'item-value';
|
||||
const itemHeader = 'item-header';
|
||||
const colCommitmentAmount = '[col-id="commitmentAmount"]';
|
||||
const colAverageEntryValuation = '[col-id="averageEntryValuation"]';
|
||||
const colEquityLikeShare = '[col-id="equityLikeShare"]';
|
||||
const colFee = '[col-id="fee"]';
|
||||
const colCommitmentAmount_1 = '[col-id="commitmentAmount_1"]';
|
||||
const colBalance = '[col-id="balance"]';
|
||||
const colStatus = '[col-id="status"]';
|
||||
const colCreatedAt = '[col-id="createdAt"] button';
|
||||
const colUpdatedAt = '[col-id="updatedAt"] button';
|
||||
|
||||
const headers = [
|
||||
'Party',
|
||||
'Commitment (tDAI)',
|
||||
'Share',
|
||||
'Proposed fee',
|
||||
'Market valuation at entry',
|
||||
'Obligation',
|
||||
'Supplied',
|
||||
'Status',
|
||||
'Created',
|
||||
'Updated',
|
||||
];
|
||||
|
||||
describe('liquidity table - trading', { tags: '@smoke' }, () => {
|
||||
before(() => {
|
||||
cy.mockSubscription();
|
||||
cy.mockTradingPage(
|
||||
Schema.MarketState.STATE_ACTIVE,
|
||||
Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION,
|
||||
Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY_TARGET_NOT_MET
|
||||
);
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.wait('@MarketData');
|
||||
cy.getByTestId(liquidityTab).click();
|
||||
cy.wait('@LiquidityProvisions');
|
||||
});
|
||||
|
||||
it('can see table headers', () => {
|
||||
// 5002-LIQP-001
|
||||
cy.getByTestId('tab-liquidity').within(($headers) => {
|
||||
cy.wrap($headers)
|
||||
.get('.ag-header-cell-text')
|
||||
.each(($header, i) => {
|
||||
cy.wrap($header).should('have.text', headers[i]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('renders liquidity table correctly', () => {
|
||||
// 5002-LIQP-002
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find('[col-id="party.id"]')
|
||||
.should(
|
||||
'have.text',
|
||||
'69464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f'
|
||||
);
|
||||
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find(colCommitmentAmount)
|
||||
.should('have.text', '4,000.00');
|
||||
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find(colEquityLikeShare)
|
||||
.should('have.text', '100.00%');
|
||||
|
||||
cy.get(rowSelector).first().find(colFee).should('have.text', '0.09%');
|
||||
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find(colAverageEntryValuation)
|
||||
.should('have.text', '685,852.93692');
|
||||
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find(colCommitmentAmount_1)
|
||||
.should('have.text', '4,000.00');
|
||||
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find(colBalance)
|
||||
.scrollIntoView()
|
||||
.should('have.text', '4,000.00');
|
||||
|
||||
cy.get(rowSelector).first().find(colStatus).should('have.text', 'Active');
|
||||
|
||||
cy.get(rowSelector).first().find(colCreatedAt).should('not.be.empty');
|
||||
cy.get(rowSelector).first().find(colUpdatedAt).should('not.be.empty');
|
||||
});
|
||||
// #4079
|
||||
it.skip('liquidity status column should be sorted properly', () => {
|
||||
// 5002-LIQP-003
|
||||
const liquidityColDefault = ['Active', 'Pending'];
|
||||
const liquidityColAsc = ['Active', 'Pending'];
|
||||
const liquidityColDesc = ['Pending', 'Active'];
|
||||
checkSorting(
|
||||
'status',
|
||||
liquidityColDefault,
|
||||
liquidityColAsc,
|
||||
liquidityColDesc
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('liquidity table view', { tags: '@smoke' }, () => {
|
||||
before(() => {
|
||||
cy.mockSubscription();
|
||||
cy.mockTradingPage(
|
||||
Schema.MarketState.STATE_ACTIVE,
|
||||
Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION,
|
||||
Schema.AuctionTrigger.AUCTION_TRIGGER_LIQUIDITY_TARGET_NOT_MET
|
||||
);
|
||||
cy.visit('/#/liquidity/market-0');
|
||||
cy.wait('@LiquidityProvisions');
|
||||
});
|
||||
|
||||
it('can see header title', () => {
|
||||
// 5002-LIQP-004
|
||||
// 5002-LIQP-005
|
||||
cy.getByTestId('header-title')
|
||||
.should('contain.text', 'BTCUSD.MF21 liquidity provision')
|
||||
.and('contain.text', 'Go to trading');
|
||||
});
|
||||
|
||||
it('can see target stake', () => {
|
||||
// 5002-LIQP-006
|
||||
cy.getByTestId(marketSummaryBlock).within(() => {
|
||||
cy.getByTestId('target-stake').within(() => {
|
||||
cy.getByTestId(itemHeader).should('have.text', 'Target stake');
|
||||
cy.getByTestId(itemValue).should('have.text', '10.00 tDAI').realHover();
|
||||
});
|
||||
});
|
||||
cy.getByTestId('tooltip-content').should(
|
||||
'contain.text',
|
||||
`The market's liquidity requirement which is derived from the maximum open interest observed over a rolling time window.The market's liquidity requirement which is derived from the maximum open interest observed over a rolling time window.`
|
||||
);
|
||||
});
|
||||
|
||||
it('can see supplied stake', () => {
|
||||
// 5002-LIQP-007
|
||||
cy.getByTestId(marketSummaryBlock).within(() => {
|
||||
cy.getByTestId('supplied-stake').within(() => {
|
||||
cy.getByTestId(itemHeader).should('have.text', 'Supplied stake');
|
||||
cy.getByTestId(itemValue).should('have.text', '0.01 tDAI').realHover();
|
||||
});
|
||||
});
|
||||
cy.getByTestId('tooltip-content').should(
|
||||
'contain.text',
|
||||
'The current amount of liquidity supplied for this market.'
|
||||
);
|
||||
});
|
||||
|
||||
it('can see liquidity supplied', () => {
|
||||
//// 5002-LIQP-008
|
||||
cy.getByTestId(marketSummaryBlock).within(() => {
|
||||
cy.getByTestId('liquidity-supplied').within(() => {
|
||||
cy.getByTestId(itemHeader).should('have.text', 'Liquidity supplied');
|
||||
cy.getByTestId('indicator').should('be.visible');
|
||||
cy.getByTestId(itemValue).should('have.text', '0.10%').realHover();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('can see market id', () => {
|
||||
// 5002-LIQP-009
|
||||
cy.getByTestId(marketSummaryBlock).within(() => {
|
||||
cy.getByTestId('liquidity-market-id').within(() => {
|
||||
cy.getByTestId(itemHeader).should('have.text', 'Market ID');
|
||||
cy.getByTestId(itemValue).should('have.text', 'market-0');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('can see market id', () => {
|
||||
// 5002-LIQP-010
|
||||
cy.getByTestId(marketSummaryBlock).within(() => {
|
||||
cy.getByTestId('liquidity-learn-more').within(() => {
|
||||
cy.getByTestId(itemHeader).should('have.text', 'Learn more');
|
||||
cy.getByTestId(itemValue).should('have.text', 'Providing liquidity');
|
||||
cy.getByTestId('external-link')
|
||||
.should('have.attr', 'href')
|
||||
.and(
|
||||
'include',
|
||||
'https://docs.vega.xyz/testnet/concepts/liquidity/provision'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('liquidity table view', { tags: '@smoke' }, () => {
|
||||
it('can see table headers', () => {
|
||||
cy.getByTestId('tab-active').within(($headers) => {
|
||||
cy.wrap($headers)
|
||||
.get('.ag-header-cell-text')
|
||||
.each(($header, i) => {
|
||||
cy.wrap($header).should('have.text', headers[i]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('renders liquidity active table correctly', () => {
|
||||
// 5002-LIQP-011
|
||||
cy.get(rowSelectorLiquidityActive)
|
||||
.first()
|
||||
.find('[col-id="party.id"]')
|
||||
.should(
|
||||
'have.text',
|
||||
'69464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f'
|
||||
);
|
||||
|
||||
cy.get(rowSelectorLiquidityActive)
|
||||
.first()
|
||||
.find(colCommitmentAmount)
|
||||
.should('have.text', '4,000.00');
|
||||
|
||||
cy.get(rowSelectorLiquidityActive)
|
||||
.first()
|
||||
.find(colEquityLikeShare)
|
||||
.should('have.text', '100.00%');
|
||||
|
||||
cy.get(rowSelectorLiquidityActive)
|
||||
.first()
|
||||
.find(colFee)
|
||||
.should('have.text', '0.09%');
|
||||
|
||||
cy.get(rowSelectorLiquidityActive)
|
||||
.first()
|
||||
.find(colAverageEntryValuation)
|
||||
.should('have.text', '685,852.93692');
|
||||
|
||||
cy.get(rowSelectorLiquidityActive)
|
||||
.first()
|
||||
.find(colCommitmentAmount_1)
|
||||
.should('have.text', '4,000.00');
|
||||
|
||||
cy.get(rowSelectorLiquidityActive)
|
||||
.first()
|
||||
.find(colBalance)
|
||||
.should('have.text', '4,000.00');
|
||||
|
||||
cy.get(rowSelectorLiquidityActive)
|
||||
.first()
|
||||
.find(colStatus)
|
||||
.should('have.text', 'Active');
|
||||
|
||||
cy.get(rowSelectorLiquidityActive)
|
||||
.first()
|
||||
.find(colCreatedAt)
|
||||
.should('not.be.empty');
|
||||
cy.get(rowSelectorLiquidityActive)
|
||||
.first()
|
||||
.find(colUpdatedAt)
|
||||
.should('not.be.empty');
|
||||
});
|
||||
|
||||
it('renders liquidity inactive table correctly', () => {
|
||||
//// 5002-LIQP-012
|
||||
cy.getByTestId('Inactive').click();
|
||||
cy.get(rowSelectorLiquidityInactive)
|
||||
.first()
|
||||
.find('[col-id="party.id"]')
|
||||
.should(
|
||||
'have.text',
|
||||
'cc464e35bcb8e8a2900ca0f87acaf252d50cf2ab2fc73694845a16b7c8a0dc6f'
|
||||
);
|
||||
|
||||
cy.get(rowSelectorLiquidityInactive)
|
||||
.first()
|
||||
.find(colCommitmentAmount)
|
||||
.should('have.text', '4,000.00');
|
||||
|
||||
cy.get(rowSelectorLiquidityInactive)
|
||||
.first()
|
||||
.find(colEquityLikeShare)
|
||||
.should('have.text', '100.00%');
|
||||
|
||||
cy.get(rowSelectorLiquidityInactive)
|
||||
.first()
|
||||
.find(colFee)
|
||||
.should('have.text', '0.40%');
|
||||
|
||||
cy.get(rowSelectorLiquidityInactive)
|
||||
.first()
|
||||
.find(colAverageEntryValuation)
|
||||
.should('have.text', '685,852.93692');
|
||||
|
||||
cy.get(rowSelectorLiquidityInactive)
|
||||
.first()
|
||||
.find(colCommitmentAmount_1)
|
||||
.should('have.text', '4,000.00');
|
||||
|
||||
cy.get(rowSelectorLiquidityInactive)
|
||||
.first()
|
||||
.find(colBalance)
|
||||
.should('have.text', '2,000.00');
|
||||
|
||||
cy.get(rowSelectorLiquidityInactive)
|
||||
.first()
|
||||
.find(colStatus)
|
||||
.should('have.text', 'Pending');
|
||||
|
||||
cy.get(rowSelectorLiquidityInactive)
|
||||
.first()
|
||||
.find(colCreatedAt)
|
||||
.should('not.be.empty');
|
||||
cy.get(rowSelectorLiquidityInactive)
|
||||
.first()
|
||||
.find(colUpdatedAt)
|
||||
.should('not.be.empty');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -25,6 +25,7 @@ describe('markets selector', { tags: '@smoke' }, () => {
|
||||
cy.wait('@MarketsCandles');
|
||||
});
|
||||
|
||||
// 6001-MARK-066
|
||||
it('can toggle the sidebar', () => {
|
||||
cy.getByTestId('market-selector').should('be.visible');
|
||||
cy.getByTestId('sidebar-toggle').click();
|
||||
@@ -40,26 +41,26 @@ describe('markets selector', { tags: '@smoke' }, () => {
|
||||
{
|
||||
code: 'SOLUSD',
|
||||
markPrice: '84.41XYZalpha',
|
||||
change: '+200.00%',
|
||||
vol: '324h vol',
|
||||
change: '',
|
||||
vol: '0.0024h vol',
|
||||
},
|
||||
{
|
||||
code: 'ETHBTC.QM21',
|
||||
markPrice: '46,126.90058tBTC',
|
||||
change: '+200.00%',
|
||||
vol: '324h vol',
|
||||
change: '',
|
||||
vol: '0.0024h vol',
|
||||
},
|
||||
{
|
||||
code: 'BTCUSD.MF21',
|
||||
markPrice: '46,126.90058tDAI',
|
||||
change: '+200.00%',
|
||||
vol: '324h vol',
|
||||
change: '',
|
||||
vol: '0.0024h vol',
|
||||
},
|
||||
{
|
||||
code: 'AAPL.MF21',
|
||||
markPrice: '46,126.90058tUSDC',
|
||||
change: '+200.00%',
|
||||
vol: '324h vol',
|
||||
change: '',
|
||||
vol: '0.0024h vol',
|
||||
},
|
||||
];
|
||||
cy.getByTestId(list)
|
||||
@@ -80,11 +81,11 @@ describe('markets selector', { tags: '@smoke' }, () => {
|
||||
market.change
|
||||
);
|
||||
// 6001-MARK-025
|
||||
expect(item.find('[data-testid="sparkline-svg"]')).to.exist;
|
||||
expect(item.find('[data-testid="sparkline-svg"]')).to.not.exist;
|
||||
});
|
||||
});
|
||||
|
||||
// 6001-MARK-27
|
||||
// 6001-MARK-027
|
||||
it('can use the filter options', () => {
|
||||
// product type
|
||||
cy.getByTestId('product-Spot').click();
|
||||
@@ -94,7 +95,7 @@ describe('markets selector', { tags: '@smoke' }, () => {
|
||||
cy.getByTestId('product-Future').click();
|
||||
cy.getByTestId(list).find('a').should('have.length', 4);
|
||||
|
||||
// 6001-MARK-29
|
||||
// 6001-MARK-029
|
||||
cy.getByTestId(searchInput).clear().type('btc');
|
||||
cy.getByTestId(list).find('a').should('have.length', 2);
|
||||
cy.getByTestId(list).find('a').eq(1).contains('BTCUSD.MF21');
|
||||
|
||||
@@ -1,17 +1,25 @@
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
|
||||
const marketSummaryBlock = 'header-summary';
|
||||
const marketExpiry = 'market-expiry';
|
||||
const marketPrice = 'market-price';
|
||||
const marketChange = 'market-change';
|
||||
const marketVolume = 'market-volume';
|
||||
const marketMode = 'market-trading-mode';
|
||||
const marketState = 'market-state';
|
||||
const marketSettlement = 'market-settlement-asset';
|
||||
const percentageValue = 'price-change-percentage';
|
||||
const priceChangeValue = 'price-change';
|
||||
const expirtyTooltip = 'expiry-tooltip';
|
||||
const externalLink = 'external-link';
|
||||
const itemHeader = 'item-header';
|
||||
const itemValue = 'item-value';
|
||||
const link = 'link';
|
||||
const liquidityLink = 'view-liquidity-link';
|
||||
const liquiditySupplied = 'liquidity-supplied';
|
||||
const liquiditySuppliedTooltip = 'liquidity-supplied-tooltip';
|
||||
const marketChange = 'market-change';
|
||||
const marketExpiry = 'market-expiry';
|
||||
const marketMode = 'market-trading-mode';
|
||||
const marketName = 'header-title';
|
||||
const marketPrice = 'market-price';
|
||||
const marketSettlement = 'market-settlement-asset';
|
||||
const marketState = 'market-state';
|
||||
const marketSummaryBlock = 'header-summary';
|
||||
const marketVolume = 'market-volume';
|
||||
const percentageValue = 'price-change-percentage';
|
||||
const priceChangeValue = 'price-change';
|
||||
const tradingModeTooltip = 'trading-mode-tooltip';
|
||||
|
||||
describe('Market trading page', () => {
|
||||
before(() => {
|
||||
@@ -31,10 +39,12 @@ describe('Market trading page', () => {
|
||||
// 7002-SORD-001
|
||||
// 7002-SORD-002
|
||||
it('must display market name', () => {
|
||||
cy.getByTestId('header-title').should('not.be.empty');
|
||||
// 6002-MDET-001
|
||||
cy.getByTestId(marketName).should('not.be.empty');
|
||||
});
|
||||
|
||||
it('must see market expiry', () => {
|
||||
// 6002-MDET-002
|
||||
cy.getByTestId(marketSummaryBlock).within(() => {
|
||||
cy.getByTestId(marketExpiry).within(() => {
|
||||
cy.getByTestId(itemHeader).should('have.text', 'Expiry');
|
||||
@@ -44,6 +54,7 @@ describe('Market trading page', () => {
|
||||
});
|
||||
|
||||
it('must see market price', () => {
|
||||
// 6002-MDET-003
|
||||
cy.getByTestId(marketSummaryBlock).within(() => {
|
||||
cy.getByTestId(marketPrice).within(() => {
|
||||
cy.getByTestId(itemHeader).should('have.text', 'Price');
|
||||
@@ -53,6 +64,7 @@ describe('Market trading page', () => {
|
||||
});
|
||||
|
||||
it('must see market change', () => {
|
||||
// 6002-MDET-004
|
||||
cy.getByTestId(marketSummaryBlock).within(() => {
|
||||
cy.getByTestId(marketChange).within(() => {
|
||||
cy.getByTestId(itemHeader).should('have.text', 'Change (24h)');
|
||||
@@ -63,6 +75,7 @@ describe('Market trading page', () => {
|
||||
});
|
||||
|
||||
it('must see market volume', () => {
|
||||
// 6002-MDET-005
|
||||
cy.getByTestId(marketSummaryBlock).within(() => {
|
||||
cy.getByTestId(marketVolume).within(() => {
|
||||
cy.getByTestId(itemHeader).should('have.text', 'Volume (24h)');
|
||||
@@ -72,16 +85,21 @@ describe('Market trading page', () => {
|
||||
});
|
||||
|
||||
it('must see market mode', () => {
|
||||
// 6002-MDET-006
|
||||
cy.getByTestId(marketSummaryBlock).within(() => {
|
||||
cy.getByTestId(marketMode).within(() => {
|
||||
cy.getByTestId(itemHeader).should('have.text', 'Trading mode');
|
||||
cy.getByTestId(itemValue).should('not.be.empty');
|
||||
cy.getByTestId(itemValue).should(
|
||||
'have.text',
|
||||
'Monitoring auction - liquidity (target not met)'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('must see market state', () => {
|
||||
//7002-SORD-061
|
||||
it('must see market status', () => {
|
||||
// 6002-MDET-007
|
||||
// 7002-SORD-061
|
||||
cy.getByTestId(marketSummaryBlock).within(() => {
|
||||
cy.getByTestId(marketState).within(() => {
|
||||
cy.getByTestId(itemHeader).should('have.text', 'Status');
|
||||
@@ -91,6 +109,7 @@ describe('Market trading page', () => {
|
||||
});
|
||||
|
||||
it('must see market settlement', () => {
|
||||
// 6002-MDET-008
|
||||
cy.getByTestId(marketSummaryBlock).within(() => {
|
||||
cy.getByTestId(marketSettlement).within(() => {
|
||||
cy.getByTestId(itemHeader).should('have.text', 'Settlement asset');
|
||||
@@ -98,15 +117,12 @@ describe('Market trading page', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('must see market mode', () => {
|
||||
it('must see market liquidity supplied', () => {
|
||||
// 6002-MDET-009
|
||||
cy.getByTestId(marketSummaryBlock).within(() => {
|
||||
cy.getByTestId(marketMode).within(() => {
|
||||
cy.getByTestId(itemHeader).should('have.text', 'Trading mode');
|
||||
cy.getByTestId(itemValue).should(
|
||||
'have.text',
|
||||
'Monitoring auction - liquidity (target not met)'
|
||||
);
|
||||
cy.getByTestId(liquiditySupplied).within(() => {
|
||||
cy.getByTestId(itemHeader).should('have.text', 'Liquidity supplied');
|
||||
cy.getByTestId(itemValue).should('not.be.empty');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -121,14 +137,14 @@ describe('Market trading page', () => {
|
||||
.realHover();
|
||||
});
|
||||
});
|
||||
cy.getByTestId('expiry-tooltip')
|
||||
cy.getByTestId(expirtyTooltip)
|
||||
.eq(0)
|
||||
.should(
|
||||
'contain.text',
|
||||
'This market expires when triggered by its oracle, not on a set date.'
|
||||
)
|
||||
.within(() => {
|
||||
cy.getByTestId('link')
|
||||
cy.getByTestId(link)
|
||||
.should('have.attr', 'href')
|
||||
.and('include', Cypress.env('EXPLORER_URL'));
|
||||
});
|
||||
@@ -154,15 +170,14 @@ describe('Market trading page', () => {
|
||||
.realHover();
|
||||
});
|
||||
});
|
||||
|
||||
cy.getByTestId('trading-mode-tooltip')
|
||||
cy.getByTestId(tradingModeTooltip)
|
||||
.should(
|
||||
'contain.text',
|
||||
'This market is in auction until it reaches sufficient liquidity.'
|
||||
)
|
||||
.eq(0)
|
||||
.within(() => {
|
||||
cy.getByTestId('external-link')
|
||||
cy.getByTestId(externalLink)
|
||||
.should('have.attr', 'href')
|
||||
.and('include', Cypress.env('TRADING_MODE_LINK'));
|
||||
|
||||
@@ -174,5 +189,27 @@ describe('Market trading page', () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should see liquidity supplied tooltip', () => {
|
||||
cy.getByTestId(marketSummaryBlock).within(() => {
|
||||
cy.getByTestId(liquiditySupplied).within(() => {
|
||||
cy.getByTestId(itemValue).realHover();
|
||||
});
|
||||
});
|
||||
cy.getByTestId(liquiditySuppliedTooltip)
|
||||
.should('contain.text', 'Supplied stake')
|
||||
.and('contain.text', 'Target stake')
|
||||
.first()
|
||||
.within(() => {
|
||||
cy.getByTestId(liquidityLink).should(
|
||||
'have.text',
|
||||
'View liquidity provision table'
|
||||
);
|
||||
cy.getByTestId(externalLink).should(
|
||||
'have.text',
|
||||
'Learn about providing liquidity'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { checkSorting } from '@vegaprotocol/cypress';
|
||||
import { aliasGQLQuery, checkSorting } from '@vegaprotocol/cypress';
|
||||
import type { ProposalsListQuery } from '@vegaprotocol/proposals';
|
||||
|
||||
const rowSelector =
|
||||
'[data-testid="tab-proposed-markets"] .ag-center-cols-container .ag-row';
|
||||
const colMarketId = '[col-id="market"]';
|
||||
|
||||
describe('markets proposed table', { tags: '@smoke' }, () => {
|
||||
beforeEach(() => {
|
||||
cy.clearLocalStorage().then(() => {
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.visit('/#/markets/all');
|
||||
cy.get('[data-testid="Proposed markets"]').click();
|
||||
});
|
||||
before(() => {
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.visit('/#/markets/all');
|
||||
cy.get('[data-testid="Proposed markets"]').click();
|
||||
});
|
||||
|
||||
it('can see table headers', () => {
|
||||
@@ -35,10 +35,7 @@ describe('markets proposed table', { tags: '@smoke' }, () => {
|
||||
|
||||
it('renders markets correctly', () => {
|
||||
// 6001-MARK-049
|
||||
cy.get(rowSelector)
|
||||
.first()
|
||||
.find('[col-id="market"]')
|
||||
.should('have.text', 'ETHUSD');
|
||||
cy.get(rowSelector).first().find(colMarketId).should('have.text', 'ETHUSD');
|
||||
|
||||
// 6001-MARK-050
|
||||
cy.get(rowSelector)
|
||||
@@ -119,6 +116,7 @@ describe('markets proposed table', { tags: '@smoke' }, () => {
|
||||
);
|
||||
});
|
||||
it('proposed markets tab should be sorted properly', () => {
|
||||
// 6001-MARK-062
|
||||
cy.get('[data-testid="Proposed markets"]').click({ force: true });
|
||||
const marketColDefault = [
|
||||
'ETHUSD',
|
||||
@@ -196,4 +194,31 @@ describe('markets proposed table', { tags: '@smoke' }, () => {
|
||||
];
|
||||
checkSorting('state', stateColDefault, stateColAsc, stateColDesc);
|
||||
});
|
||||
|
||||
it('can drag and drop columns', () => {
|
||||
// 6001-MARK-063
|
||||
cy.get(colMarketId).realMouseDown().realMouseMove(700, 15).realMouseUp();
|
||||
cy.get(colMarketId).should(($element) => {
|
||||
const attributeValue = $element.attr('aria-colindex');
|
||||
expect(attributeValue).not.to.equal('1');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('no markets proposed', { tags: '@smoke', testIsolation: true }, () => {
|
||||
before(() => {
|
||||
cy.mockTradingPage();
|
||||
const proposal: ProposalsListQuery = {};
|
||||
cy.mockGQL((req) => {
|
||||
aliasGQLQuery(req, 'ProposalsList', proposal);
|
||||
});
|
||||
cy.mockSubscription();
|
||||
cy.visit('/#/markets/all');
|
||||
cy.get('[data-testid="Proposed markets"]').click();
|
||||
});
|
||||
|
||||
it('can see no markets message', () => {
|
||||
// 6001-MARK-061
|
||||
cy.getByTestId('tab-proposed-markets').should('contain.text', 'No markets');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { MarketState } from '@vegaprotocol/types';
|
||||
|
||||
const oracleBannerDialogTrigger = 'oracle-banner-dialog-trigger';
|
||||
const oracleBannerStatus = 'oracle-banner-status';
|
||||
const oracleFullProfile = 'oracle-full-profile';
|
||||
|
||||
describe('oracle information', { tags: '@smoke' }, () => {
|
||||
before(() => {
|
||||
cy.mockTradingPage(
|
||||
MarketState.STATE_ACTIVE,
|
||||
undefined,
|
||||
undefined,
|
||||
'COMPROMISED'
|
||||
);
|
||||
cy.mockSubscription();
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.wait('@Markets');
|
||||
});
|
||||
|
||||
it('show oracle banner', () => {
|
||||
cy.getByTestId(oracleBannerStatus).should('contain.text', 'COMPROMISED');
|
||||
cy.getByTestId(oracleBannerDialogTrigger)
|
||||
.should('contain.text', 'Show more')
|
||||
.click();
|
||||
cy.getByTestId(oracleFullProfile).should('exist');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,102 @@
|
||||
const orderbookTab = 'Orderbook';
|
||||
const orderbookTable = 'tab-orderbook';
|
||||
const askPrice = 'price-9894585';
|
||||
const bidPrice = 'price-9889001';
|
||||
const askVolume = 'ask-vol-9894585';
|
||||
const bidVolume = 'bid-vol-9889001';
|
||||
const askCumulative = 'cumulative-vol-9894585';
|
||||
const bidCumulative = 'cumulative-vol-9889001';
|
||||
const midPrice = 'middle-mark-price-4612690000';
|
||||
const priceResolution = 'resolution';
|
||||
const dealTicketPrice = 'order-price';
|
||||
const resPrice = 'price-990';
|
||||
|
||||
describe('order book', { tags: '@smoke' }, () => {
|
||||
before(() => {
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.wait('@Markets');
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.mockTradingPage();
|
||||
});
|
||||
|
||||
it('show order book', () => {
|
||||
// 6003-ORDB-001
|
||||
// 6003-ORDB-002
|
||||
cy.getByTestId(orderbookTab).click();
|
||||
cy.getByTestId(orderbookTable).should('be.visible');
|
||||
cy.getByTestId(orderbookTable).should('not.be.empty');
|
||||
});
|
||||
|
||||
it('show orders prices', () => {
|
||||
// 6003-ORDB-003
|
||||
cy.getByTestId(askPrice).should('have.text', '98.94585');
|
||||
cy.getByTestId(bidPrice).should('have.text', '98.89001');
|
||||
});
|
||||
|
||||
it('show prices volumes', () => {
|
||||
// 6003-ORDB-004
|
||||
cy.getByTestId(askVolume).should('have.text', '1');
|
||||
cy.getByTestId(bidVolume).should('have.text', '1');
|
||||
});
|
||||
|
||||
it('show prices cumulative volumes', () => {
|
||||
// 6003-ORDB-005
|
||||
cy.getByTestId(askCumulative).should('have.text', '39');
|
||||
cy.getByTestId(bidCumulative).should('have.text', '7');
|
||||
});
|
||||
|
||||
it('show mid price', () => {
|
||||
// 6003-ORDB-006
|
||||
cy.getByTestId(midPrice).should('have.text', '46,126.90');
|
||||
});
|
||||
|
||||
it('sort prices descending', () => {
|
||||
// 6003-ORDB-007
|
||||
const prices: number[] = [];
|
||||
cy.getByTestId(orderbookTable).within(() => {
|
||||
cy.get('[data-testid*=price]')
|
||||
.each(($el) => {
|
||||
prices.push(Number($el.text()));
|
||||
})
|
||||
.then(() => {
|
||||
expect(prices).to.deep.equal(prices.sort((a, b) => b - a));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('copy price to deal ticket form', () => {
|
||||
// 6003-ORDB-009
|
||||
cy.getByTestId(askPrice).click();
|
||||
cy.getByTestId(dealTicketPrice).should('have.value', '98.94585');
|
||||
});
|
||||
|
||||
it('change price resolution', () => {
|
||||
// 6003-ORDB-008
|
||||
const resolutions = [
|
||||
'0.00000',
|
||||
'0.0000',
|
||||
'0.000',
|
||||
'0.00',
|
||||
'0.0',
|
||||
'0',
|
||||
'10',
|
||||
'100',
|
||||
'1,000',
|
||||
'10,000',
|
||||
];
|
||||
cy.getByTestId(priceResolution)
|
||||
.find('option')
|
||||
.each(($el, index) => {
|
||||
expect($el.text()).to.equal(resolutions[index]);
|
||||
});
|
||||
|
||||
cy.getByTestId(priceResolution).select('0.0');
|
||||
cy.getByTestId(resPrice).should('have.text', '99.0');
|
||||
cy.getByTestId(askPrice).should('not.exist');
|
||||
cy.getByTestId(bidPrice).should('not.exist');
|
||||
});
|
||||
});
|
||||
@@ -88,7 +88,8 @@ describe(
|
||||
.pop()
|
||||
?.toLowerCase()} and not accepting orders`
|
||||
);
|
||||
cy.getByTestId('place-order').should('be.disabled');
|
||||
// 7002-SORD-060
|
||||
cy.getByTestId('place-order').should('be.enabled');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -77,7 +77,8 @@ describe('deal ticker order validation', { tags: '@smoke' }, () => {
|
||||
it('must warn if order size input has too many digits after the decimal place', function () {
|
||||
// 7002-SORD-016
|
||||
cy.getByTestId(orderSizeField).clear().type('1.234');
|
||||
cy.getByTestId(placeOrderBtn).should('be.disabled');
|
||||
// 7002-SORD-060
|
||||
cy.getByTestId(placeOrderBtn).should('be.enabled');
|
||||
cy.getByTestId('dealticket-error-message-size-market').should(
|
||||
'have.text',
|
||||
'Size must be whole numbers for this market'
|
||||
@@ -86,12 +87,13 @@ describe('deal ticker order validation', { tags: '@smoke' }, () => {
|
||||
|
||||
it('must warn if order size is set to 0', function () {
|
||||
cy.getByTestId(orderSizeField).clear().type('0');
|
||||
cy.getByTestId(placeOrderBtn).should('be.disabled');
|
||||
cy.getByTestId(placeOrderBtn).should('be.enabled');
|
||||
cy.getByTestId('dealticket-error-message-size-market').should(
|
||||
'have.text',
|
||||
'Size cannot be lower than 1'
|
||||
);
|
||||
});
|
||||
|
||||
it('must have total margin available', () => {
|
||||
// 7001-COLL-011
|
||||
cy.getByTestId('tab-ticket')
|
||||
@@ -100,9 +102,8 @@ describe('deal ticker order validation', { tags: '@smoke' }, () => {
|
||||
.within(() => {
|
||||
cy.get('[data-state="closed"]').should(
|
||||
'have.text',
|
||||
'Total margin available'
|
||||
'Total margin available100,000.01 tDAI'
|
||||
);
|
||||
cy.get('.text-neutral-500').should('have.text', '100,000.01 tDAI');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -23,13 +23,14 @@ describe(
|
||||
});
|
||||
|
||||
it('should show an error if your balance is zero', () => {
|
||||
cy.getByTestId('place-order').should('be.disabled');
|
||||
// 7002-SORD-060
|
||||
cy.getByTestId('place-order').should('be.enabled');
|
||||
// 7002-SORD-003
|
||||
cy.getByTestId('dealticket-error-message-zero-balance').should(
|
||||
'have.text',
|
||||
'You need ' +
|
||||
'tDAI' +
|
||||
' in your wallet to trade in this market.See all your collateral.Make a deposit'
|
||||
' in your wallet to trade in this market. See all your collateral.Make a deposit'
|
||||
);
|
||||
cy.getByTestId('deal-ticket-deposit-dialog-button').should('exist');
|
||||
});
|
||||
|
||||
@@ -34,9 +34,9 @@ describe('suspended market validation', { tags: '@regression' }, () => {
|
||||
|
||||
it('should show warning for market order', function () {
|
||||
cy.getByTestId(toggleMarket).click();
|
||||
cy.getByTestId(placeOrderBtn).should('not.be.disabled');
|
||||
// 7002-SORD-060
|
||||
cy.getByTestId(placeOrderBtn).should('be.enabled');
|
||||
cy.getByTestId(placeOrderBtn).click();
|
||||
cy.getByTestId(placeOrderBtn).should('be.disabled');
|
||||
cy.getByTestId('dealticket-error-message-type').should(
|
||||
'have.text',
|
||||
'This market is in auction until it reaches sufficient liquidity. Only limit orders are permitted when market is in auction'
|
||||
@@ -59,7 +59,7 @@ describe('suspended market validation', { tags: '@regression' }, () => {
|
||||
cy.getByTestId(orderTIFDropDown).select(
|
||||
TIFlist.filter((item) => item.code === 'FOK')[0].value
|
||||
);
|
||||
cy.getByTestId(placeOrderBtn).should('be.disabled');
|
||||
cy.getByTestId(placeOrderBtn).should('be.enabled');
|
||||
cy.getByTestId('dealticket-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'
|
||||
|
||||
@@ -3,6 +3,18 @@ import { aliasGQLQuery } from '@vegaprotocol/cypress';
|
||||
import { marketsDataQuery } from '@vegaprotocol/mock';
|
||||
import { positionsQuery } from '@vegaprotocol/mock';
|
||||
|
||||
// #region consts
|
||||
const closePosition = 'close-position';
|
||||
const dialogCloseX = 'dialog-close';
|
||||
const dialogContent = 'dialog-content';
|
||||
const dropDownMenu = 'dropdown-menu';
|
||||
const marketActionsContent = 'market-actions-content';
|
||||
const positions = 'Positions';
|
||||
const tabPositions = 'tab-positions';
|
||||
const toastContent = 'toast-content';
|
||||
const tooltipContent = 'tooltip-content';
|
||||
// #endregion
|
||||
|
||||
beforeEach(() => {
|
||||
cy.mockTradingPage();
|
||||
cy.mockSubscription();
|
||||
@@ -11,8 +23,9 @@ beforeEach(() => {
|
||||
|
||||
describe('positions', { tags: '@smoke', testIsolation: true }, () => {
|
||||
it('renders positions on trading page', () => {
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.getByTestId('Positions').click();
|
||||
visitAndClickPositions();
|
||||
// 7004-POSI-001
|
||||
// 7004-POSI-002
|
||||
validatePositionsDisplayed();
|
||||
});
|
||||
|
||||
@@ -35,183 +48,313 @@ describe('positions', { tags: '@smoke', testIsolation: true }, () => {
|
||||
}
|
||||
aliasGQLQuery(req, 'Positions', positions);
|
||||
});
|
||||
cy.visit('/#/portfolio');
|
||||
cy.getByTestId('Positions').click();
|
||||
visitAndClickPositions();
|
||||
// 7004-POSI-001
|
||||
// 7004-POSI-002
|
||||
validatePositionsDisplayed(true);
|
||||
});
|
||||
describe('renders position among some graphql errors', () => {
|
||||
it('rows should be displayed despite errors', () => {
|
||||
const errors = [
|
||||
{
|
||||
message:
|
||||
'no market data for market: 9c55fb644c6f7de5422d40d691a62bffd5898384c70135bab29ba1e3e2e5280a',
|
||||
path: ['marketsConnection', 'edges'],
|
||||
extensions: {
|
||||
code: 13,
|
||||
type: 'Internal',
|
||||
},
|
||||
},
|
||||
];
|
||||
const marketData = marketsDataQuery();
|
||||
const edges = marketData.marketsConnection?.edges.map((market) => {
|
||||
const replace =
|
||||
market.node.data?.market.id === 'market-2' ? null : market.node.data;
|
||||
return { ...market, node: { ...market.node, data: replace } };
|
||||
});
|
||||
const overrides = {
|
||||
...marketData,
|
||||
marketsConnection: { ...marketData.marketsConnection, edges },
|
||||
};
|
||||
cy.mockGQL((req) => {
|
||||
aliasGQLQuery(req, 'MarketsData', overrides, errors);
|
||||
});
|
||||
cy.visit('/#/markets/market-0');
|
||||
const emptyCells = [
|
||||
'notional',
|
||||
'markPrice',
|
||||
'currentLeverage',
|
||||
'averageEntryPrice',
|
||||
];
|
||||
cy.getByTestId('tab-positions')
|
||||
.first()
|
||||
.within(() => {
|
||||
cy.get(
|
||||
'[row-id="02eceaba4df2bef76ea10caf728d8a099a2aa846cced25737cccaa9812342f65-market-2"]'
|
||||
)
|
||||
.eq(1)
|
||||
.within(() => {
|
||||
emptyCells.forEach((cell) => {
|
||||
cy.get(`[col-id="${cell}"]`).should('contain.text', '-');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
it('error message should be displayed', () => {
|
||||
const errors = [
|
||||
{
|
||||
message:
|
||||
'no market data for asset: 9c55fb644c6f7de5422d40d691a62bffd5898384c70135bab29ba1e3e2e5280a',
|
||||
path: ['assets', 'edges'],
|
||||
extensions: {
|
||||
code: 13,
|
||||
type: 'Internal',
|
||||
},
|
||||
},
|
||||
];
|
||||
const marketData = marketsDataQuery();
|
||||
const edges = marketData.marketsConnection?.edges.map((market) => {
|
||||
const replace =
|
||||
market.node.data?.market.id === 'market-2' ? null : market.node.data;
|
||||
return { ...market, node: { ...market.node, data: replace } };
|
||||
});
|
||||
const overrides = {
|
||||
...marketData,
|
||||
marketsConnection: { ...marketData.marketsConnection, edges },
|
||||
};
|
||||
cy.mockGQL((req) => {
|
||||
aliasGQLQuery(req, 'MarketsData', overrides, errors);
|
||||
});
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.get('.pointer-events-none.absolute.inset-0').contains(
|
||||
'Something went wrong:'
|
||||
);
|
||||
});
|
||||
|
||||
it('Close my position', () => {
|
||||
visitAndClickPositions();
|
||||
cy.getByTestId(closePosition).first().click();
|
||||
// 7004-POSI-010
|
||||
cy.getByTestId(toastContent).should(
|
||||
'contain.text',
|
||||
'Awaiting confirmation'
|
||||
);
|
||||
});
|
||||
|
||||
describe('sorting by ag-grid columns should work well', () => {
|
||||
it('sorting by Market', () => {
|
||||
cy.visit('/#/markets/market-0');
|
||||
const marketsSortedDefault = [
|
||||
'ACTIVE MARKET',
|
||||
'Apple Monthly (30 Jun 2022)',
|
||||
'SUSPENDED MARKET',
|
||||
];
|
||||
const marketsSortedAsc = ['ACTIVE MARKET', 'Apple Monthly (30 Jun 2022)'];
|
||||
const marketsSortedDesc = [
|
||||
'SUSPENDED MARKET',
|
||||
'Apple Monthly (30 Jun 2022)',
|
||||
'ACTIVE MARKET',
|
||||
];
|
||||
cy.getByTestId('Positions').click();
|
||||
checkSorting(
|
||||
'marketName',
|
||||
marketsSortedDefault,
|
||||
marketsSortedAsc,
|
||||
marketsSortedDesc
|
||||
);
|
||||
});
|
||||
it('sorting by notional', () => {
|
||||
cy.visit('/#/markets/market-0');
|
||||
const marketsSortedDefault = [
|
||||
'276,761.40348',
|
||||
'46,126.90058',
|
||||
'1,688.20',
|
||||
];
|
||||
const marketsSortedAsc = ['1,688.20', '46,126.90058', '276,761.40348'];
|
||||
const marketsSortedDesc = ['276,761.40348', '46,126.90058', '1,688.20'];
|
||||
cy.getByTestId('Positions').click();
|
||||
checkSorting(
|
||||
'notional',
|
||||
marketsSortedDefault,
|
||||
marketsSortedAsc,
|
||||
marketsSortedDesc
|
||||
);
|
||||
});
|
||||
it('sorting by unrealisedPNL', () => {
|
||||
cy.visit('/#/markets/market-0');
|
||||
const marketsSortedDefault = ['8.95', '-0.22519', '8.95'];
|
||||
const marketsSortedAsc = ['-0.22519', '8.95', '8.95'];
|
||||
const marketsSortedDesc = ['8.95', '8.95', '-0.22519'];
|
||||
cy.getByTestId('Positions').click();
|
||||
checkSorting(
|
||||
'unrealisedPNL',
|
||||
marketsSortedDefault,
|
||||
marketsSortedAsc,
|
||||
marketsSortedDesc
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
function validatePositionsDisplayed(multiKey = false) {
|
||||
cy.getByTestId('tab-positions').should('be.visible');
|
||||
cy.getByTestId('tab-positions').within(() => {
|
||||
cy.get('[col-id="marketName"]')
|
||||
.should('be.visible')
|
||||
.each(($marketSymbol) => {
|
||||
cy.wrap($marketSymbol).invoke('text').should('not.be.empty');
|
||||
});
|
||||
|
||||
cy.get('.ag-center-cols-container [col-id="openVolume"]').each(
|
||||
($openVolume) => {
|
||||
cy.wrap($openVolume).invoke('text').should('not.be.empty');
|
||||
}
|
||||
);
|
||||
|
||||
// includes average entry price, mark price, realised PNL & leverage
|
||||
cy.getByTestId('flash-cell').each(($prices) => {
|
||||
cy.wrap($prices).invoke('text').should('not.be.empty');
|
||||
});
|
||||
|
||||
if (!multiKey) {
|
||||
cy.get('[col-id="currentLeverage"]').should('contain.text', '2.846.1');
|
||||
cy.get('[col-id="marginAccountBalance"]') // margin allocated
|
||||
.should('contain.text', '0.01');
|
||||
}
|
||||
|
||||
cy.get('[col-id="unrealisedPNL"]').each(($unrealisedPnl) => {
|
||||
cy.wrap($unrealisedPnl).invoke('text').should('not.be.empty');
|
||||
});
|
||||
|
||||
cy.get('[col-id="notional"]').should('contain.text', '276,761.40348'); // Total tDAI position
|
||||
cy.get('[col-id="realisedPNL"]').should('contain.text', '2.30'); // Total Realised PNL
|
||||
cy.get('[col-id="unrealisedPNL"]').should('contain.text', '8.95'); // Total Unrealised PNL
|
||||
|
||||
cy.get('.ag-header-row [col-id="notional"]')
|
||||
.should('contain.text', 'Notional')
|
||||
.realHover();
|
||||
cy.get('.ag-popup').should('contain.text', 'Mark price x open volume');
|
||||
});
|
||||
|
||||
cy.getByTestId('close-position').should('be.visible').and('have.length', 3);
|
||||
}
|
||||
});
|
||||
describe('positions', { tags: '@regression', testIsolation: true }, () => {
|
||||
it('rows should be displayed despite errors', () => {
|
||||
const errors = [
|
||||
{
|
||||
message:
|
||||
'no market data for market: 9c55fb644c6f7de5422d40d691a62bffd5898384c70135bab29ba1e3e2e5280a',
|
||||
path: ['marketsConnection', 'edges'],
|
||||
extensions: {
|
||||
code: 13,
|
||||
type: 'Internal',
|
||||
},
|
||||
},
|
||||
];
|
||||
const marketData = marketsDataQuery();
|
||||
const edges = marketData.marketsConnection?.edges.map((market) => {
|
||||
const replace =
|
||||
market.node.data?.market.id === 'market-2' ? null : market.node.data;
|
||||
return { ...market, node: { ...market.node, data: replace } };
|
||||
});
|
||||
const overrides = {
|
||||
...marketData,
|
||||
marketsConnection: { ...marketData.marketsConnection, edges },
|
||||
};
|
||||
cy.mockGQL((req) => {
|
||||
aliasGQLQuery(req, 'MarketsData', overrides, errors);
|
||||
});
|
||||
cy.visit('/#/markets/market-0');
|
||||
const emptyCells = [
|
||||
'notional',
|
||||
'markPrice',
|
||||
'currentLeverage',
|
||||
'averageEntryPrice',
|
||||
];
|
||||
cy.getByTestId(tabPositions)
|
||||
.first()
|
||||
.within(() => {
|
||||
cy.get(
|
||||
'[row-id="02eceaba4df2bef76ea10caf728d8a099a2aa846cced25737cccaa9812342f65-market-2"]'
|
||||
)
|
||||
.eq(1)
|
||||
.within(() => {
|
||||
emptyCells.forEach((cell) => {
|
||||
cy.get(`[col-id="${cell}"]`).should('contain.text', '-');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('error message should be displayed', () => {
|
||||
const errors = [
|
||||
{
|
||||
message:
|
||||
'no market data for asset: 9c55fb644c6f7de5422d40d691a62bffd5898384c70135bab29ba1e3e2e5280a',
|
||||
path: ['assets', 'edges'],
|
||||
extensions: {
|
||||
code: 13,
|
||||
type: 'Internal',
|
||||
},
|
||||
},
|
||||
];
|
||||
const overrides = {
|
||||
marketsConnection: { edges: [] },
|
||||
};
|
||||
cy.mockGQL((req) => {
|
||||
aliasGQLQuery(req, 'MarketsData', overrides, errors);
|
||||
});
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.getByTestId(tabPositions).contains('no market data');
|
||||
});
|
||||
|
||||
it('sorting by Market', () => {
|
||||
visitAndClickPositions();
|
||||
const marketsSortedDefault = [
|
||||
'ACTIVE MARKET',
|
||||
'Apple Monthly (30 Jun 2022)',
|
||||
'ETHBTC Quarterly (30 Jun 2022)',
|
||||
'SUSPENDED MARKET',
|
||||
];
|
||||
const marketsSortedAsc = [
|
||||
'ACTIVE MARKET',
|
||||
'Apple Monthly (30 Jun 2022)',
|
||||
'ETHBTC Quarterly (30 Jun 2022)',
|
||||
'SUSPENDED MARKET',
|
||||
];
|
||||
const marketsSortedDesc = [
|
||||
'SUSPENDED MARKET',
|
||||
'ETHBTC Quarterly (30 Jun 2022)',
|
||||
'Apple Monthly (30 Jun 2022)',
|
||||
'ACTIVE MARKET',
|
||||
];
|
||||
cy.getByTestId(positions).click();
|
||||
// 7004-POSI-003
|
||||
checkSorting(
|
||||
'marketName',
|
||||
marketsSortedDefault,
|
||||
marketsSortedAsc,
|
||||
marketsSortedDesc
|
||||
);
|
||||
});
|
||||
|
||||
it('Resize column', () => {
|
||||
let elementWidth: number;
|
||||
visitAndClickPositions();
|
||||
cy.get('.ag-overlay-loading-wrapper').should('not.be.visible');
|
||||
cy.get('.ag-header-container').within(() => {
|
||||
cy.get(`[col-id="marketName"]`)
|
||||
.find('.ag-header-cell-resize')
|
||||
.realMouseDown()
|
||||
.realMouseMove(250, 0)
|
||||
.realMouseUp();
|
||||
});
|
||||
|
||||
// 7004-POSI-006
|
||||
cy.get(`[col-id="marketName"]`)
|
||||
.invoke('width')
|
||||
.should('be.greaterThan', 250);
|
||||
cy.get(`[col-id="marketName"]`)
|
||||
.invoke('width')
|
||||
.then((width) => {
|
||||
elementWidth = width as number;
|
||||
})
|
||||
.then(() => {
|
||||
let localStorageCopy: Record<string, string>;
|
||||
cy.window().then((win) => {
|
||||
localStorageCopy = { ...win.localStorage };
|
||||
});
|
||||
|
||||
cy.reload();
|
||||
cy.window().then((win) => {
|
||||
Object.keys(localStorageCopy).forEach((key) => {
|
||||
win.localStorage.setItem(key, localStorageCopy[key]);
|
||||
});
|
||||
});
|
||||
|
||||
// 7004-POSI-012
|
||||
cy.get('[col-id="marketName"]')
|
||||
.invoke('width')
|
||||
.should('equal', elementWidth);
|
||||
});
|
||||
});
|
||||
|
||||
it('Scroll horizontally', () => {
|
||||
visitAndClickPositions();
|
||||
|
||||
cy.get('.ag-header-container').within(() => {
|
||||
cy.get(`[col-id="marketName"]`)
|
||||
.find('.ag-header-cell-resize')
|
||||
.realMouseDown()
|
||||
.realMouseMove(400, 0)
|
||||
.realMouseUp();
|
||||
});
|
||||
cy.get('[col-id="marketName"]').should('be.visible');
|
||||
cy.get('.ag-body-horizontal-scroll-viewport').realMouseWheel({
|
||||
deltaX: 500,
|
||||
});
|
||||
// 7004-POSI-004
|
||||
cy.get('[col-id="updatedAt"]').should('be.visible');
|
||||
});
|
||||
|
||||
it('Drag and drop columns', () => {
|
||||
visitAndClickPositions();
|
||||
cy.get('.ag-overlay-loading-wrapper').should('not.be.visible');
|
||||
cy.get('[col-id="marketName"]')
|
||||
.realMouseDown()
|
||||
.realMouseMove(700, 15)
|
||||
.realMouseUp();
|
||||
|
||||
// 7004-POSI-005
|
||||
cy.get('[col-id="marketName"]').should(($element) => {
|
||||
const attributeValue = $element.attr('aria-colindex');
|
||||
expect(attributeValue).not.to.equal('1');
|
||||
});
|
||||
});
|
||||
|
||||
it('I can see warnings', () => {
|
||||
visitAndClickPositions();
|
||||
|
||||
cy.get('[col-id="openVolume"]').within(() => {
|
||||
cy.get('[aria-label="warning-sign icon"]')
|
||||
.should('be.visible')
|
||||
.realHover();
|
||||
});
|
||||
// 7004-POSI-011
|
||||
cy.getByTestId(tooltipContent).should('be.visible');
|
||||
});
|
||||
|
||||
it('Positive and Negative color change', () => {
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.getByTestId(positions).click();
|
||||
// 7004-POSI-007
|
||||
cy.get('.ag-center-cols-container').within(() => {
|
||||
assertPNLColor(
|
||||
'[col-id="realisedPNL"]',
|
||||
'text-vega-green',
|
||||
'text-vega-pink'
|
||||
);
|
||||
});
|
||||
cy.get('.ag-center-cols-container').within(() => {
|
||||
assertPNLColor(
|
||||
'[col-id="unrealisedPNL"]',
|
||||
'text-vega-green',
|
||||
'text-vega-pink'
|
||||
);
|
||||
});
|
||||
cy.get('.ag-center-cols-container').within(() => {
|
||||
assertPNLColor(
|
||||
'[col-id="openVolume"]',
|
||||
'text-vega-green',
|
||||
'text-vega-pink'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('View settlement asset', () => {
|
||||
visitAndClickPositions();
|
||||
cy.get('[col-id="asset"]').within(() => {
|
||||
cy.get('button[type="button"]').first().click();
|
||||
});
|
||||
// 7004-POSI-008
|
||||
cy.getByTestId(dialogContent).should('be.visible');
|
||||
cy.getByTestId(dialogCloseX).click();
|
||||
cy.getByTestId(dropDownMenu).first().click();
|
||||
cy.getByTestId(marketActionsContent).click();
|
||||
// 7004-POSI-009
|
||||
cy.getByTestId(dialogContent).should('be.visible');
|
||||
});
|
||||
});
|
||||
function validatePositionsDisplayed(multiKey = false) {
|
||||
cy.getByTestId('tab-positions').should('be.visible');
|
||||
cy.getByTestId('tab-positions').within(() => {
|
||||
cy.get('[col-id="marketName"]')
|
||||
.should('be.visible')
|
||||
.each(($marketSymbol) => {
|
||||
cy.wrap($marketSymbol).invoke('text').should('not.be.empty');
|
||||
});
|
||||
|
||||
cy.get('.ag-center-cols-container [col-id="openVolume"]').each(
|
||||
($openVolume) => {
|
||||
cy.wrap($openVolume).invoke('text').should('not.be.empty');
|
||||
}
|
||||
);
|
||||
|
||||
// includes average entry price, mark price, realised PNL & leverage
|
||||
cy.getByTestId('flash-cell').each(($prices) => {
|
||||
cy.wrap($prices).invoke('text').should('not.be.empty');
|
||||
});
|
||||
|
||||
if (!multiKey) {
|
||||
cy.get('[col-id="currentLeverage"]').should('contain.text', '2.846.1');
|
||||
cy.get('[col-id="marginAccountBalance"]') // margin allocated
|
||||
.should('contain.text', '0.01');
|
||||
}
|
||||
|
||||
cy.get('[col-id="unrealisedPNL"]').each(($unrealisedPnl) => {
|
||||
cy.wrap($unrealisedPnl).invoke('text').should('not.be.empty');
|
||||
});
|
||||
|
||||
cy.get('[col-id="notional"]').should('contain.text', '276,761.40348'); // Total tDAI position
|
||||
cy.get('[col-id="realisedPNL"]').should('contain.text', '2.30'); // Total Realised PNL
|
||||
cy.get('[col-id="unrealisedPNL"]').should('contain.text', '8.95'); // Total Unrealised PNL
|
||||
|
||||
cy.get('.ag-header-row [col-id="notional"]')
|
||||
.should('contain.text', 'Notional')
|
||||
.realHover();
|
||||
cy.get('.ag-popup').should('contain.text', 'Mark price x open volume');
|
||||
});
|
||||
|
||||
cy.getByTestId('close-position').should('be.visible').and('have.length', 3);
|
||||
}
|
||||
function assertPNLColor(
|
||||
pnlSelector: string,
|
||||
positiveClass: string,
|
||||
negativeClass: string
|
||||
) {
|
||||
cy.get(pnlSelector).each(($el) => {
|
||||
const value = parseFloat($el.text());
|
||||
|
||||
if (value > 0) {
|
||||
cy.wrap($el).invoke('attr', 'class').should('contain', positiveClass);
|
||||
} else if (value < 0) {
|
||||
cy.wrap($el).invoke('attr', 'class').should('contain', negativeClass);
|
||||
} else if (value == 0) {
|
||||
cy.wrap($el)
|
||||
.invoke('attr', 'class')
|
||||
.should('not.contain', negativeClass, positiveClass);
|
||||
} else {
|
||||
throw new Error('Unexpected value');
|
||||
}
|
||||
});
|
||||
}
|
||||
function visitAndClickPositions() {
|
||||
cy.visit('/#/markets/market-0');
|
||||
cy.getByTestId(positions).click();
|
||||
}
|
||||
|
||||
@@ -31,6 +31,8 @@ import {
|
||||
protocolUpgradeProposalsQuery,
|
||||
blockStatisticsQuery,
|
||||
networkParamQuery,
|
||||
liquidityProvisionsQuery,
|
||||
liquidityProviderFeeShareQuery,
|
||||
} from '@vegaprotocol/mock';
|
||||
import type { PartialDeep } from 'type-fest';
|
||||
import type { MarketDataQuery, MarketsQuery } from '@vegaprotocol/markets';
|
||||
@@ -158,6 +160,12 @@ const mockTradingPage = (
|
||||
);
|
||||
aliasGQLQuery(req, 'Trades', tradesQuery());
|
||||
aliasGQLQuery(req, 'Chart', chartQuery());
|
||||
aliasGQLQuery(req, 'LiquidityProvisions', liquidityProvisionsQuery());
|
||||
aliasGQLQuery(
|
||||
req,
|
||||
'LiquidityProviderFeeShare',
|
||||
liquidityProviderFeeShareQuery
|
||||
);
|
||||
aliasGQLQuery(req, 'Candles', candlesQuery());
|
||||
aliasGQLQuery(req, 'Withdrawals', withdrawalsQuery());
|
||||
aliasGQLQuery(req, 'NetworkParams', networkParamsQuery());
|
||||
|
||||
@@ -9,7 +9,7 @@ NX_VEGA_EXPLORER_URL=https://dev.explorer.vega.xyz
|
||||
NX_VEGA_NETWORKS={\"MAINNET\":\"https://console.vega.xyz\",\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"https://trading.stagnet1.vega.rocks\"}
|
||||
NX_VEGA_TOKEN_URL=https://governance.fairground.wtf
|
||||
NX_VEGA_WALLET_URL=http://localhost:1789
|
||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||
NX_VEGA_DOCS_URL=#
|
||||
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega-dev-releases/releases
|
||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
|
||||
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
|
||||
|
||||
@@ -14,4 +14,4 @@ NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/annou
|
||||
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
|
||||
NX_VEGA_CONSOLE_URL=https://console.vega.xyz
|
||||
# TAG name of the current app version - TODO: bump to the latest upon release
|
||||
NX_APP_VERSION=v0.20.16-core-0.71.5
|
||||
NX_APP_VERSION=v0.20.18-core-0.71.8
|
||||
|
||||
@@ -11,14 +11,12 @@ import {
|
||||
formatNumberPercentage,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { updateGridData } from '@vegaprotocol/datagrid';
|
||||
import {
|
||||
NetworkParams,
|
||||
useNetworkParams,
|
||||
} from '@vegaprotocol/network-parameters';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import {
|
||||
AsyncRenderer,
|
||||
Tab,
|
||||
Tabs,
|
||||
Link as UiToolkitLink,
|
||||
@@ -26,14 +24,13 @@ import {
|
||||
ExternalLink,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import { memo, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { memo, useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { Header, HeaderStat, HeaderTitle } from '../../components/header';
|
||||
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import type { IGetRowsParams } from 'ag-grid-community';
|
||||
|
||||
import type { LiquidityProvisionData, Filter } from '@vegaprotocol/liquidity';
|
||||
import type { Filter } from '@vegaprotocol/liquidity';
|
||||
import { Link, useParams } from 'react-router-dom';
|
||||
import { Links, Routes } from '../../pages/client-router';
|
||||
|
||||
@@ -74,21 +71,12 @@ export const LiquidityContainer = ({
|
||||
}) => {
|
||||
const gridRef = useRef<AgGridReact | null>(null);
|
||||
const { data: market } = useMarket(marketId);
|
||||
const dataRef = useRef<LiquidityProvisionData[] | null>(null);
|
||||
|
||||
// To be removed when liquidityProvision subscriptions are working
|
||||
useReloadLiquidityData(marketId);
|
||||
|
||||
const update = useCallback(
|
||||
({ data }: { data: LiquidityProvisionData[] | null }) => {
|
||||
return updateGridData(dataRef, data, gridRef);
|
||||
},
|
||||
[gridRef]
|
||||
);
|
||||
|
||||
const { data, loading, error } = useDataProvider({
|
||||
const { data, error } = useDataProvider({
|
||||
dataProvider: lpAggregatedDataProvider,
|
||||
update,
|
||||
variables: { marketId: marketId || '', filter },
|
||||
skip: !marketId,
|
||||
});
|
||||
@@ -103,36 +91,16 @@ export const LiquidityContainer = ({
|
||||
]);
|
||||
const stakeToCcyVolume = params.market_liquidity_stakeToCcyVolume;
|
||||
|
||||
const getRows = useCallback(
|
||||
async ({ successCallback, startRow, endRow }: IGetRowsParams) => {
|
||||
const rowsThisBlock = dataRef.current
|
||||
? dataRef.current.slice(startRow, endRow)
|
||||
: [];
|
||||
const lastRow = dataRef.current ? dataRef.current.length : 0;
|
||||
successCallback(rowsThisBlock, lastRow);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="h-full relative">
|
||||
<LiquidityTable
|
||||
ref={gridRef}
|
||||
datasource={{ getRows }}
|
||||
rowModelType="infinite"
|
||||
rowData={data}
|
||||
symbol={symbol}
|
||||
assetDecimalPlaces={assetDecimalPlaces}
|
||||
stakeToCcyVolume={stakeToCcyVolume}
|
||||
overlayNoRowsTemplate={error ? error.message : t('No data')}
|
||||
/>
|
||||
<div className="pointer-events-none absolute inset-0">
|
||||
<AsyncRenderer
|
||||
loading={loading}
|
||||
error={error}
|
||||
data={data}
|
||||
noDataMessage={t('No liquidity provisions')}
|
||||
noDataCondition={(data) => !data?.length}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -182,6 +150,7 @@ const LiquidityViewHeader = memo(({ marketId }: { marketId?: string }) => {
|
||||
<HeaderStat
|
||||
heading={t('Target stake')}
|
||||
description={tooltipMapping['targetStake']}
|
||||
testId="target-stake"
|
||||
>
|
||||
<div>
|
||||
{targetStake
|
||||
@@ -195,6 +164,7 @@ const LiquidityViewHeader = memo(({ marketId }: { marketId?: string }) => {
|
||||
<HeaderStat
|
||||
heading={t('Supplied stake')}
|
||||
description={tooltipMapping['suppliedStake']}
|
||||
testId="supplied-stake"
|
||||
>
|
||||
<div>
|
||||
{suppliedStake
|
||||
@@ -210,10 +180,10 @@ const LiquidityViewHeader = memo(({ marketId }: { marketId?: string }) => {
|
||||
|
||||
{formatNumberPercentage(percentage, 2)}
|
||||
</HeaderStat>
|
||||
<HeaderStat heading={t('Market ID')}>
|
||||
<HeaderStat heading={t('Market ID')} testId="liquidity-market-id">
|
||||
<div className="break-word">{marketId}</div>
|
||||
</HeaderStat>
|
||||
<HeaderStat heading={t('Learn more')}>
|
||||
<HeaderStat heading={t('Learn more')} testId="liquidity-learn-more">
|
||||
{DocsLinks ? (
|
||||
<ExternalLink href={DocsLinks.LIQUIDITY}>
|
||||
{t('Providing liquidity')}
|
||||
|
||||
@@ -6,25 +6,32 @@ import { MemoryRouter } from 'react-router-dom';
|
||||
import type { MockedResponse } from '@apollo/client/testing';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import type {
|
||||
MarketCandlesQuery,
|
||||
MarketCandlesQueryVariables,
|
||||
MarketDataUpdateFieldsFragment,
|
||||
MarketDataUpdateSubscription,
|
||||
} from '@vegaprotocol/markets';
|
||||
import { MarketCandlesDocument } from '@vegaprotocol/markets';
|
||||
import { MarketDataUpdateDocument } from '@vegaprotocol/markets';
|
||||
import {
|
||||
AuctionTrigger,
|
||||
Interval,
|
||||
MarketState,
|
||||
MarketTradingMode,
|
||||
} from '@vegaprotocol/types';
|
||||
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
|
||||
import { subDays } from 'date-fns';
|
||||
|
||||
describe('MarketSelectorItem', () => {
|
||||
const yesterday = new Date();
|
||||
yesterday.setHours(yesterday.getHours() - 20);
|
||||
const market = createMarketFragment({
|
||||
id: 'market-0',
|
||||
decimalPlaces: 2,
|
||||
// @ts-ignore fragment doesn't contain candles
|
||||
candles: [
|
||||
{ close: '5', volume: '50' },
|
||||
{ close: '10', volume: '50' },
|
||||
{ close: '5', volume: '50', periodStart: yesterday.toISOString() },
|
||||
{ close: '10', volume: '50', periodStart: yesterday.toISOString() },
|
||||
],
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
@@ -36,6 +43,7 @@ describe('MarketSelectorItem', () => {
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const marketData: MarketDataUpdateFieldsFragment = {
|
||||
__typename: 'ObservableMarketData',
|
||||
marketId: market.id,
|
||||
@@ -63,26 +71,32 @@ describe('MarketSelectorItem', () => {
|
||||
trigger: AuctionTrigger.AUCTION_TRIGGER_UNSPECIFIED,
|
||||
priceMonitoringBounds: null,
|
||||
};
|
||||
const mock: MockedResponse<MarketDataUpdateSubscription> = {
|
||||
request: {
|
||||
query: MarketDataUpdateDocument,
|
||||
variables: {
|
||||
marketId: market.id,
|
||||
},
|
||||
|
||||
const candles = [
|
||||
{
|
||||
open: '5',
|
||||
close: '5',
|
||||
high: '5',
|
||||
low: '5',
|
||||
volume: '50',
|
||||
periodStart: yesterday.toISOString(),
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
marketsData: [marketData],
|
||||
},
|
||||
{
|
||||
open: '10',
|
||||
close: '10',
|
||||
high: '10',
|
||||
low: '10',
|
||||
volume: '50',
|
||||
periodStart: yesterday.toISOString(),
|
||||
},
|
||||
};
|
||||
];
|
||||
|
||||
const mockOnSelect = jest.fn();
|
||||
|
||||
const renderJsx = () => {
|
||||
const renderJsx = (mocks: MockedResponse[]) => {
|
||||
return render(
|
||||
<MemoryRouter>
|
||||
<MockedProvider mocks={[mock]}>
|
||||
<MockedProvider mocks={mocks}>
|
||||
<MarketSelectorItem
|
||||
market={market}
|
||||
currentMarketId={market.id}
|
||||
@@ -94,11 +108,66 @@ describe('MarketSelectorItem', () => {
|
||||
);
|
||||
};
|
||||
|
||||
let dateSpy: jest.SpyInstance;
|
||||
const ts = 1685577600000; // 2023-06-01
|
||||
|
||||
beforeAll(() => {
|
||||
dateSpy = jest.spyOn(Date, 'now').mockImplementation(() => ts);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
dateSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('renders market information', async () => {
|
||||
const symbol =
|
||||
market.tradableInstrument.instrument.product.settlementAsset.symbol;
|
||||
|
||||
renderJsx();
|
||||
const mock: MockedResponse<MarketDataUpdateSubscription> = {
|
||||
request: {
|
||||
query: MarketDataUpdateDocument,
|
||||
variables: {
|
||||
marketId: market.id,
|
||||
},
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
marketsData: [marketData],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const since = subDays(Date.now(), 5).toISOString();
|
||||
const variables: MarketCandlesQueryVariables = {
|
||||
marketId: market.id,
|
||||
interval: Interval.INTERVAL_I1H,
|
||||
since,
|
||||
};
|
||||
const mockCandles: MockedResponse<MarketCandlesQuery> = {
|
||||
request: {
|
||||
query: MarketCandlesDocument,
|
||||
variables,
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
marketsConnection: {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
candlesConnection: {
|
||||
edges: candles.map((c) => ({
|
||||
node: c,
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
renderJsx([mock, mockCandles]);
|
||||
|
||||
const link = screen.getByRole('link');
|
||||
// link renders and is styled
|
||||
@@ -106,18 +175,17 @@ describe('MarketSelectorItem', () => {
|
||||
|
||||
expect(link).toHaveClass('ring-1');
|
||||
|
||||
expect(screen.getByTitle('24h vol')).toHaveTextContent('100');
|
||||
expect(screen.getByTitle('24h vol')).toHaveTextContent('0.00');
|
||||
expect(screen.getByTitle(symbol)).toHaveTextContent('-');
|
||||
|
||||
// candles are loaded immediately
|
||||
expect(screen.getByTestId('market-item-change')).toHaveTextContent(
|
||||
'+100.00%'
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTitle('24h vol')).toHaveTextContent('100');
|
||||
expect(screen.getByTitle(symbol)).toHaveTextContent(
|
||||
addDecimalsFormatNumber(marketData.markPrice, market.decimalPlaces)
|
||||
);
|
||||
expect(screen.getByTestId('market-item-change')).toHaveTextContent(
|
||||
'+100.00%'
|
||||
);
|
||||
});
|
||||
|
||||
await userEvent.click(link);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { CSSProperties } from 'react';
|
||||
import type { CSSProperties, ReactNode } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from '@vegaprotocol/utils';
|
||||
import type { MarketMaybeWithDataAndCandles } from '@vegaprotocol/markets';
|
||||
import { calcCandleVolume } from '@vegaprotocol/markets';
|
||||
import { useCandles } from '@vegaprotocol/markets';
|
||||
import { useMarketDataUpdateSubscription } from '@vegaprotocol/markets';
|
||||
import { Sparkline } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
@@ -80,8 +81,9 @@ const MarketData = ({ market }: { market: MarketMaybeWithDataAndCandles }) => {
|
||||
: '';
|
||||
|
||||
const instrument = market.tradableInstrument.instrument;
|
||||
const { oneDayCandles } = useCandles({ marketId: market.id });
|
||||
|
||||
const vol = market.candles ? calcCandleVolume(market.candles) : '0';
|
||||
const vol = oneDayCandles ? calcCandleVolume(oneDayCandles) : '0';
|
||||
const volume =
|
||||
vol && vol !== '0'
|
||||
? addDecimalsFormatNumber(vol, market.positionDecimalPlaces)
|
||||
@@ -111,20 +113,20 @@ const MarketData = ({ market }: { market: MarketMaybeWithDataAndCandles }) => {
|
||||
value={price}
|
||||
label={instrument.product.settlementAsset.symbol}
|
||||
/>
|
||||
<div className="relative">
|
||||
{market.candles && (
|
||||
<PriceChange candles={market.candles.map((c) => c.close)} />
|
||||
<div className="relative text-xs p-1">
|
||||
{oneDayCandles && (
|
||||
<PriceChange candles={oneDayCandles.map((c) => c.close)} />
|
||||
)}
|
||||
|
||||
<div
|
||||
// absolute so height is not larger than price change value
|
||||
className="absolute right-0 bottom-0 w-[120px]"
|
||||
>
|
||||
{market.candles && (
|
||||
{oneDayCandles && (
|
||||
<Sparkline
|
||||
width={120}
|
||||
height={20}
|
||||
data={market.candles.filter(Boolean).map((c) => Number(c.close))}
|
||||
data={oneDayCandles.map((c) => Number(c.close))}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -133,7 +135,13 @@ const MarketData = ({ market }: { market: MarketMaybeWithDataAndCandles }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const DataRow = ({ value, label }: { value: string; label: string }) => {
|
||||
const DataRow = ({
|
||||
value,
|
||||
label,
|
||||
}: {
|
||||
value: string | ReactNode;
|
||||
label: string;
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
className="text-ellipsis whitespace-nowrap overflow-hidden leading-tight"
|
||||
|
||||
@@ -296,7 +296,7 @@ const MainGrid = memo(
|
||||
</TradeGridChild>
|
||||
</ResizableGridPanel>
|
||||
<ResizableGridPanel
|
||||
preferredSize={sizesMiddle[2] || 430}
|
||||
preferredSize={sizesMiddle[2] || 300}
|
||||
minSize={200}
|
||||
>
|
||||
<TradeGridChild>
|
||||
|
||||
@@ -53,7 +53,7 @@ export const useMarketSelectorList = ({
|
||||
});
|
||||
|
||||
if (sort === Sort.None) {
|
||||
// Sort by market state primarilly and AtoZ secondarilly
|
||||
// Sort by market state primarily and AtoZ secondarily
|
||||
return orderBy(
|
||||
markets,
|
||||
[
|
||||
|
||||
@@ -27,7 +27,6 @@ import type { ColDef } from 'ag-grid-community';
|
||||
import { SettlementDateCell } from './settlement-date-cell';
|
||||
import { SettlementPriceCell } from './settlement-price-cell';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
type SettlementAsset =
|
||||
MarketMaybeWithData['tradableInstrument']['instrument']['product']['settlementAsset'];
|
||||
@@ -55,7 +54,6 @@ export const Closed = () => {
|
||||
const { pubKey } = useVegaWallet();
|
||||
const {
|
||||
data: marketData,
|
||||
loading,
|
||||
error,
|
||||
reload,
|
||||
} = useDataProvider({
|
||||
@@ -117,21 +115,20 @@ export const Closed = () => {
|
||||
});
|
||||
return (
|
||||
<div className="h-full relative">
|
||||
<ClosedMarketsDataGrid rowData={rowData} />
|
||||
<div className="pointer-events-none absolute inset-0">
|
||||
<AsyncRenderer
|
||||
loading={loading}
|
||||
error={error}
|
||||
data={marketData}
|
||||
noDataMessage={t('No markets')}
|
||||
reload={reload}
|
||||
/>
|
||||
</div>
|
||||
<ClosedMarketsDataGrid rowData={rowData} error={error} reload={reload} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ClosedMarketsDataGrid = ({ rowData }: { rowData: Row[] }) => {
|
||||
const ClosedMarketsDataGrid = ({
|
||||
rowData,
|
||||
error,
|
||||
reload,
|
||||
}: {
|
||||
rowData: Row[];
|
||||
error: Error | undefined;
|
||||
reload: () => void;
|
||||
}) => {
|
||||
const openAssetDialog = useAssetDetailsDialogStore((store) => store.open);
|
||||
const colDefs = useMemo(() => {
|
||||
const cols: ColDef[] = [
|
||||
@@ -315,7 +312,7 @@ const ClosedMarketsDataGrid = ({ rowData }: { rowData: Row[] }) => {
|
||||
resizable: true,
|
||||
minWidth: 100,
|
||||
}}
|
||||
overlayNoRowsTemplate="No data"
|
||||
overlayNoRowsTemplate={error ? error.message : t('No markets')}
|
||||
storeKey="closedMarkets"
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AsyncRenderer, Button } from '@vegaprotocol/ui-toolkit';
|
||||
import { Button } from '@vegaprotocol/ui-toolkit';
|
||||
import { useDepositDialog, DepositsTable } from '@vegaprotocol/deposits';
|
||||
import { depositsProvider } from '@vegaprotocol/deposits';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
@@ -11,7 +11,7 @@ import type { AgGridReact } from 'ag-grid-react';
|
||||
export const DepositsContainer = () => {
|
||||
const gridRef = useRef<AgGridReact | null>(null);
|
||||
const { pubKey, isReadOnly } = useVegaWallet();
|
||||
const { data, loading, error, reload } = useDataProvider({
|
||||
const { data, error } = useDataProvider({
|
||||
dataProvider: depositsProvider,
|
||||
variables: { partyId: pubKey || '' },
|
||||
skip: !pubKey,
|
||||
@@ -23,21 +23,10 @@ export const DepositsContainer = () => {
|
||||
<div className="h-full relative">
|
||||
<DepositsTable
|
||||
rowData={data || []}
|
||||
suppressLoadingOverlay
|
||||
suppressNoRowsOverlay
|
||||
ref={gridRef}
|
||||
{...bottomPlaceholderProps}
|
||||
overlayNoRowsTemplate={error ? error.message : t('No deposits')}
|
||||
/>
|
||||
<div className="pointer-events-none absolute inset-0">
|
||||
<AsyncRenderer
|
||||
data={data}
|
||||
loading={loading}
|
||||
error={error}
|
||||
noDataCondition={(data) => !(data && data.length)}
|
||||
noDataMessage={t('No deposits')}
|
||||
reload={reload}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{!isReadOnly && (
|
||||
<div className="h-auto flex justify-end px-[11px] py-2 bottom-0 right-3 absolute dark:bg-black/75 bg-white/75 rounded">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AsyncRenderer, Button } from '@vegaprotocol/ui-toolkit';
|
||||
import { Button } from '@vegaprotocol/ui-toolkit';
|
||||
import {
|
||||
withdrawalProvider,
|
||||
useWithdrawalDialog,
|
||||
@@ -11,7 +11,7 @@ import { VegaWalletContainer } from '../../components/vega-wallet-container';
|
||||
|
||||
export const WithdrawalsContainer = () => {
|
||||
const { pubKey, isReadOnly } = useVegaWallet();
|
||||
const { data, loading, error, reload } = useDataProvider({
|
||||
const { data, error } = useDataProvider({
|
||||
dataProvider: withdrawalProvider,
|
||||
variables: { partyId: pubKey || '' },
|
||||
skip: !pubKey,
|
||||
@@ -24,19 +24,8 @@ export const WithdrawalsContainer = () => {
|
||||
<WithdrawalsTable
|
||||
data-testid="withdrawals-history"
|
||||
rowData={data}
|
||||
suppressLoadingOverlay
|
||||
suppressNoRowsOverlay
|
||||
overlayNoRowsTemplate={error ? error.message : t('No withdrawals')}
|
||||
/>
|
||||
<div className="pointer-events-none absolute inset-0">
|
||||
<AsyncRenderer
|
||||
data={data}
|
||||
loading={loading}
|
||||
error={error}
|
||||
noDataCondition={(data) => !(data && data.length)}
|
||||
noDataMessage={t('No withdrawals')}
|
||||
reload={reload}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{!isReadOnly && (
|
||||
<div className="h-auto flex justify-end px-[11px] py-2 bottom-0 right-3 absolute dark:bg-black/75 bg-white/75 rounded">
|
||||
|
||||
@@ -99,7 +99,7 @@ export const MarketLiquiditySupplied = ({
|
||||
AuctionTrigger.AUCTION_TRIGGER_UNABLE_TO_DEPLOY_LP_ORDERS;
|
||||
|
||||
const description = marketId ? (
|
||||
<section>
|
||||
<section data-testid="liquidity-supplied-tooltip">
|
||||
<KeyValueTable>
|
||||
<KeyValueTableRow>
|
||||
<span>{t('Supplied stake')}</span>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { renderHook, act, waitFor } from '@testing-library/react';
|
||||
import { useLocalStorage } from '@vegaprotocol/react-helpers';
|
||||
import { SentryInit, SentryClose } from '@vegaprotocol/utils';
|
||||
import { SentryInit, SentryClose } from '@vegaprotocol/logger';
|
||||
import { STORAGE_KEY, useTelemetryApproval } from './use-telemetry-approval';
|
||||
|
||||
const mockSetValue = jest.fn();
|
||||
const mockRemoveValue = jest.fn();
|
||||
jest.mock('@vegaprotocol/utils');
|
||||
jest.mock('@vegaprotocol/logger');
|
||||
jest.mock('@vegaprotocol/react-helpers', () => ({
|
||||
...jest.requireActual('@vegaprotocol/react-helpers'),
|
||||
useLocalStorage: jest
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useLocalStorage } from '@vegaprotocol/react-helpers';
|
||||
import { useCallback } from 'react';
|
||||
import { SentryInit, SentryClose } from '@vegaprotocol/utils';
|
||||
import { SentryInit, SentryClose } from '@vegaprotocol/logger';
|
||||
import { ENV } from '../config';
|
||||
export const STORAGE_KEY = 'vega_telemetry_approval';
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ENV } from './lib/config/env';
|
||||
import { LocalStorage, SentryInit } from '@vegaprotocol/utils';
|
||||
import { LocalStorage, SentryInit } from '@vegaprotocol/logger';
|
||||
import { STORAGE_KEY } from './lib/hooks/use-telemetry-approval';
|
||||
|
||||
const { dsn, envName } = ENV;
|
||||
|
||||
@@ -3,6 +3,7 @@ server {
|
||||
listen 80;
|
||||
|
||||
location / {
|
||||
add_header 'Cache-Control' 'max-age=60';
|
||||
root /usr/share/nginx/html;
|
||||
index index.html index.htm;
|
||||
try_files $uri $uri/ /index.html;
|
||||
|
||||
@@ -97,7 +97,7 @@ describe('AccountManager', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('splash loading should be displayed', async () => {
|
||||
it('loading should be displayed', async () => {
|
||||
mockedUseDataProvider.mockImplementation((args) => {
|
||||
return {
|
||||
loading: true,
|
||||
@@ -113,15 +113,6 @@ describe('AccountManager', () => {
|
||||
/>
|
||||
);
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByText(
|
||||
(content, element) =>
|
||||
Boolean(
|
||||
element?.className.endsWith('flex items-center justify-center')
|
||||
) && content.startsWith('Loading')
|
||||
)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
expect(await screen.findByText('Loading...')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { useRef, memo, useCallback, useState, useEffect } from 'react';
|
||||
import { useRef, memo, useCallback, useState } from 'react';
|
||||
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useBottomPlaceholder } from '@vegaprotocol/datagrid';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import type { RowDataUpdatedEvent } from 'ag-grid-community';
|
||||
import type { AccountFields } from './accounts-data-provider';
|
||||
import {
|
||||
aggregatedAccountsDataProvider,
|
||||
@@ -71,7 +69,6 @@ export const AccountManager = ({
|
||||
storeKey,
|
||||
}: AccountManagerProps) => {
|
||||
const gridRef = useRef<AgGridReact | null>(null);
|
||||
const [hasData, setHasData] = useState(Boolean(pinnedAsset));
|
||||
const [breakdownAssetId, setBreakdownAssetId] = useState<string>();
|
||||
const update = useCallback(
|
||||
({ data }: { data: AccountFields[] | null }) => {
|
||||
@@ -102,7 +99,7 @@ export const AccountManager = ({
|
||||
},
|
||||
[gridRef, pinnedAsset]
|
||||
);
|
||||
const { data, loading, error, reload } = useDataProvider({
|
||||
const { data, error } = useDataProvider({
|
||||
dataProvider: aggregatedAccountsDataProvider,
|
||||
variables: { partyId },
|
||||
update,
|
||||
@@ -112,45 +109,21 @@ export const AccountManager = ({
|
||||
disabled: noBottomPlaceholder,
|
||||
});
|
||||
|
||||
useEffect(
|
||||
() => setHasData(Boolean(pinnedAsset || data?.length)),
|
||||
[data, pinnedAsset]
|
||||
);
|
||||
|
||||
const onRowDataUpdated = useCallback(
|
||||
(event: RowDataUpdatedEvent) => {
|
||||
setHasData(Boolean(pinnedAsset || event.api?.getModel().getRowCount()));
|
||||
},
|
||||
[pinnedAsset]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="relative h-full">
|
||||
<AccountTable
|
||||
ref={gridRef}
|
||||
rowData={error ? [] : data}
|
||||
rowData={data}
|
||||
onClickAsset={onClickAsset}
|
||||
onClickDeposit={onClickDeposit}
|
||||
onClickWithdraw={onClickWithdraw}
|
||||
onClickBreakdown={setBreakdownAssetId}
|
||||
onRowDataUpdated={onRowDataUpdated}
|
||||
isReadOnly={isReadOnly}
|
||||
suppressLoadingOverlay
|
||||
suppressNoRowsOverlay
|
||||
pinnedAsset={pinnedAsset}
|
||||
storeKey={storeKey}
|
||||
{...bottomPlaceholderProps}
|
||||
overlayNoRowsTemplate={error ? error.message : t('No accounts')}
|
||||
/>
|
||||
<div className="pointer-events-none absolute inset-0">
|
||||
<AsyncRenderer
|
||||
data={data}
|
||||
noDataCondition={() => !hasData}
|
||||
error={error}
|
||||
loading={loading}
|
||||
noDataMessage={pinnedAsset ? ' ' : t('No accounts')}
|
||||
reload={reload}
|
||||
/>
|
||||
</div>
|
||||
<Dialog
|
||||
size="medium"
|
||||
open={Boolean(breakdownAssetId)}
|
||||
|
||||
@@ -13,7 +13,7 @@ import { createClient as createWSClient } from 'graphql-ws';
|
||||
import { onError } from '@apollo/client/link/error';
|
||||
import { RetryLink } from '@apollo/client/link/retry';
|
||||
import ApolloLinkTimeout from 'apollo-link-timeout';
|
||||
import { localLoggerFactory } from '@vegaprotocol/utils';
|
||||
import { localLoggerFactory } from '@vegaprotocol/logger';
|
||||
import { useHeaderStore } from './header-store';
|
||||
|
||||
const isBrowser = typeof window !== 'undefined';
|
||||
|
||||
@@ -28,3 +28,4 @@ export * from '../trades/src/lib/trades.mock';
|
||||
export * from '../withdraws/src/lib/withdrawal.mock';
|
||||
export * from '../proposals/src/lib/protocol-upgrade-proposals/protocol-statistics-proposals.mock';
|
||||
export * from '../proposals/src/lib/protocol-upgrade-proposals/block-statistics.mock';
|
||||
export * from '../liquidity/src/lib/liquidity.mock';
|
||||
|
||||
@@ -7,44 +7,54 @@ declare global {
|
||||
namespace Cypress {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
interface Chainable<Subject> {
|
||||
VegaWalletTopUpRewardsPool(
|
||||
transferStartEpoch: number,
|
||||
transferEndEpoch: number
|
||||
): void;
|
||||
VegaWalletTopUpRewardsPool(): void;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function addVegaWalletTopUpRewardsPool() {
|
||||
Cypress.Commands.add(
|
||||
'VegaWalletTopUpRewardsPool',
|
||||
(transferStartEpoch, transferEndEpoch) => {
|
||||
const vegaWalletUrl = Cypress.env('VEGA_WALLET_URL');
|
||||
const token = Cypress.env('VEGA_WALLET_API_TOKEN');
|
||||
const vegaPubKey = Cypress.env('VEGA_PUBLIC_KEY');
|
||||
const assetAddress =
|
||||
'b4f2726571fbe8e33b442dc92ed2d7f0d810e21835b7371a7915a365f07ccd9b';
|
||||
Cypress.Commands.add('VegaWalletTopUpRewardsPool', () => {
|
||||
let transferStartEpoch = 0;
|
||||
let transferEndEpoch = 0;
|
||||
const vegaWalletUrl = Cypress.env('VEGA_WALLET_URL');
|
||||
const token = Cypress.env('VEGA_WALLET_API_TOKEN');
|
||||
const vegaPubKey = Cypress.env('VEGA_PUBLIC_KEY');
|
||||
const assetAddress =
|
||||
'b4f2726571fbe8e33b442dc92ed2d7f0d810e21835b7371a7915a365f07ccd9b';
|
||||
|
||||
createWalletClient(vegaWalletUrl, token);
|
||||
cy.getByTestId('epoch-countdown')
|
||||
.within(() => {
|
||||
cy.get('h3')
|
||||
.invoke('text')
|
||||
.then((epochText) => {
|
||||
transferStartEpoch = Number(epochText.replace('Epoch', '')) + 5;
|
||||
transferEndEpoch = transferStartEpoch + 100;
|
||||
|
||||
const transactionBody: TransferBody = {
|
||||
transfer: {
|
||||
fromAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
toAccountType: AccountType.ACCOUNT_TYPE_GLOBAL_REWARD,
|
||||
to: '0000000000000000000000000000000000000000000000000000000000000000',
|
||||
asset: assetAddress,
|
||||
amount: '1000000000000000000',
|
||||
recurring: {
|
||||
factor: '1',
|
||||
startEpoch: transferStartEpoch,
|
||||
endEpoch: transferEndEpoch,
|
||||
console.log(transferStartEpoch);
|
||||
console.log(transferEndEpoch);
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
createWalletClient(vegaWalletUrl, token);
|
||||
|
||||
const transactionBody: TransferBody = {
|
||||
transfer: {
|
||||
fromAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
toAccountType: AccountType.ACCOUNT_TYPE_GLOBAL_REWARD,
|
||||
to: '0000000000000000000000000000000000000000000000000000000000000000',
|
||||
asset: assetAddress,
|
||||
amount: '1000000000000000000',
|
||||
recurring: {
|
||||
factor: '1',
|
||||
startEpoch: transferStartEpoch,
|
||||
endEpoch: transferEndEpoch,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
cy.highlight('Topping up rewards pool');
|
||||
cy.highlight('Topping up rewards pool');
|
||||
|
||||
sendVegaTx(vegaPubKey, transactionBody);
|
||||
}
|
||||
);
|
||||
sendVegaTx(vegaPubKey, transactionBody);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -38,7 +38,6 @@ type Data = Item[];
|
||||
type QueryData = {
|
||||
data: Data;
|
||||
pageInfo?: PageInfo;
|
||||
totalCount?: number;
|
||||
};
|
||||
|
||||
type CombinedData = {
|
||||
@@ -115,7 +114,6 @@ const paginatedSubscribe = makeDataProvider<
|
||||
first,
|
||||
append: defaultAppend,
|
||||
getPageInfo: (r) => r?.pageInfo ?? null,
|
||||
getTotalCount: (r) => r?.totalCount,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -373,32 +371,10 @@ describe('data provider', () => {
|
||||
subscription.unsubscribe();
|
||||
});
|
||||
|
||||
it('fills data with nulls if pagination is enabled', async () => {
|
||||
const totalCount = 1000;
|
||||
const data: Item[] = new Array(first).fill(null).map((v, i) => ({
|
||||
cursor: i.toString(),
|
||||
node: {
|
||||
id: i.toString(),
|
||||
},
|
||||
}));
|
||||
const subscription = paginatedSubscribe(callback, client, variables);
|
||||
await resolveQuery({
|
||||
data,
|
||||
totalCount,
|
||||
pageInfo: {
|
||||
hasNextPage: true,
|
||||
},
|
||||
});
|
||||
expect(callback.mock.calls[1][0].data?.length).toBe(totalCount);
|
||||
subscription.unsubscribe();
|
||||
});
|
||||
|
||||
it('loads requested data blocks and inserts data with total count', async () => {
|
||||
const totalCount = 1000;
|
||||
it('loads requested data blocks', async () => {
|
||||
const subscription = paginatedSubscribe(callback, client, variables);
|
||||
await resolveQuery({
|
||||
data: generateData(),
|
||||
totalCount,
|
||||
pageInfo: {
|
||||
hasNextPage: true,
|
||||
endCursor: '100',
|
||||
@@ -407,168 +383,25 @@ describe('data provider', () => {
|
||||
|
||||
// load next page
|
||||
subscription.load && subscription.load();
|
||||
let lastQueryArgs =
|
||||
const lastQueryArgs =
|
||||
clientQuery.mock.calls[clientQuery.mock.calls.length - 1][0];
|
||||
expect(lastQueryArgs?.variables?.pagination).toEqual({
|
||||
expect(lastQueryArgs?.variables?.['pagination']).toEqual({
|
||||
after: '100',
|
||||
first,
|
||||
});
|
||||
await resolveQuery({
|
||||
data: generateData(100),
|
||||
pageInfo: {
|
||||
hasNextPage: true,
|
||||
hasNextPage: false,
|
||||
endCursor: '200',
|
||||
},
|
||||
});
|
||||
|
||||
// load page with skip
|
||||
subscription.load && subscription.load(500, 600);
|
||||
lastQueryArgs =
|
||||
clientQuery.mock.calls[clientQuery.mock.calls.length - 1][0];
|
||||
expect(lastQueryArgs?.variables?.pagination).toEqual({
|
||||
after: '200',
|
||||
first,
|
||||
skip: 300,
|
||||
});
|
||||
await resolveQuery({
|
||||
data: generateData(500),
|
||||
pageInfo: {
|
||||
hasNextPage: true,
|
||||
endCursor: '600',
|
||||
},
|
||||
});
|
||||
|
||||
// load in the gap
|
||||
subscription.load && subscription.load(400, 500);
|
||||
lastQueryArgs =
|
||||
clientQuery.mock.calls[clientQuery.mock.calls.length - 1][0];
|
||||
expect(lastQueryArgs?.variables?.pagination).toEqual({
|
||||
after: '200',
|
||||
first,
|
||||
skip: 200,
|
||||
});
|
||||
await resolveQuery({
|
||||
data: generateData(400),
|
||||
pageInfo: {
|
||||
hasNextPage: true,
|
||||
endCursor: '500',
|
||||
},
|
||||
});
|
||||
|
||||
// load page after last block
|
||||
subscription.load && subscription.load(700, 800);
|
||||
lastQueryArgs =
|
||||
clientQuery.mock.calls[clientQuery.mock.calls.length - 1][0];
|
||||
expect(lastQueryArgs?.variables?.pagination).toEqual({
|
||||
after: '600',
|
||||
first,
|
||||
skip: 100,
|
||||
});
|
||||
await resolveQuery({
|
||||
data: generateData(700),
|
||||
pageInfo: {
|
||||
hasNextPage: true,
|
||||
endCursor: '800',
|
||||
},
|
||||
});
|
||||
|
||||
// load last page shorter than expected
|
||||
subscription.load && subscription.load(950, 1050);
|
||||
lastQueryArgs =
|
||||
clientQuery.mock.calls[clientQuery.mock.calls.length - 1][0];
|
||||
expect(lastQueryArgs?.variables?.pagination).toEqual({
|
||||
after: '800',
|
||||
first,
|
||||
skip: 150,
|
||||
});
|
||||
await resolveQuery({
|
||||
data: generateData(950, 20),
|
||||
pageInfo: {
|
||||
hasNextPage: false,
|
||||
endCursor: '970',
|
||||
},
|
||||
});
|
||||
let lastCallbackArgs = callback.mock.calls[callback.mock.calls.length - 1];
|
||||
expect(lastCallbackArgs[0].totalCount).toBe(970);
|
||||
|
||||
// load next page when pageInfo.hasNextPage === false
|
||||
const clientQueryCallsLength = clientQuery.mock.calls.length;
|
||||
subscription.load && subscription.load();
|
||||
expect(clientQuery.mock.calls.length).toBe(clientQueryCallsLength);
|
||||
|
||||
// load last page longer than expected
|
||||
subscription.load && subscription.load(960, 1000);
|
||||
lastQueryArgs =
|
||||
clientQuery.mock.calls[clientQuery.mock.calls.length - 1][0];
|
||||
expect(lastQueryArgs?.variables?.pagination).toEqual({
|
||||
after: '960',
|
||||
first,
|
||||
});
|
||||
await resolveQuery({
|
||||
data: generateData(960, 40),
|
||||
pageInfo: {
|
||||
hasNextPage: true,
|
||||
endCursor: '1000',
|
||||
},
|
||||
});
|
||||
lastCallbackArgs = callback.mock.calls[callback.mock.calls.length - 1];
|
||||
expect(lastCallbackArgs[0].totalCount).toBe(1000);
|
||||
|
||||
subscription.unsubscribe();
|
||||
});
|
||||
|
||||
it('loads requested data blocks and inserts data without totalCount', async () => {
|
||||
const totalCount = undefined;
|
||||
const subscription = paginatedSubscribe(callback, client, variables);
|
||||
await resolveQuery({
|
||||
data: generateData(),
|
||||
totalCount,
|
||||
pageInfo: {
|
||||
hasNextPage: true,
|
||||
endCursor: '100',
|
||||
},
|
||||
});
|
||||
let lastCallbackArgs = callback.mock.calls[callback.mock.calls.length - 1];
|
||||
expect(lastCallbackArgs[0].totalCount).toBe(undefined);
|
||||
|
||||
// load next page
|
||||
subscription.load && subscription.load();
|
||||
await resolveQuery({
|
||||
data: generateData(100),
|
||||
pageInfo: {
|
||||
hasNextPage: true,
|
||||
endCursor: '200',
|
||||
},
|
||||
});
|
||||
lastCallbackArgs = callback.mock.calls[callback.mock.calls.length - 1];
|
||||
expect(lastCallbackArgs[0].totalCount).toBe(undefined);
|
||||
|
||||
// load last page
|
||||
subscription.load && subscription.load();
|
||||
await resolveQuery({
|
||||
data: generateData(200, 50),
|
||||
pageInfo: {
|
||||
hasNextPage: false,
|
||||
endCursor: '250',
|
||||
},
|
||||
});
|
||||
lastCallbackArgs = callback.mock.calls[callback.mock.calls.length - 1];
|
||||
expect(lastCallbackArgs[0].totalCount).toBe(250);
|
||||
subscription.unsubscribe();
|
||||
});
|
||||
|
||||
it('sets total count when first page has no next page', async () => {
|
||||
const subscription = paginatedSubscribe(callback, client, variables);
|
||||
await resolveQuery({
|
||||
data: generateData(),
|
||||
pageInfo: {
|
||||
hasNextPage: false,
|
||||
endCursor: '100',
|
||||
},
|
||||
});
|
||||
const lastCallbackArgs =
|
||||
callback.mock.calls[callback.mock.calls.length - 1];
|
||||
expect(lastCallbackArgs[0].totalCount).toBe(100);
|
||||
subscription.unsubscribe();
|
||||
});
|
||||
|
||||
@@ -752,7 +585,7 @@ describe('derived data provider', () => {
|
||||
subscription.load && subscription.load();
|
||||
const lastQueryArgs =
|
||||
clientQuery.mock.calls[clientQuery.mock.calls.length - 1][0];
|
||||
expect(lastQueryArgs?.variables?.pagination).toEqual({
|
||||
expect(lastQueryArgs?.variables?.['pagination']).toEqual({
|
||||
after: '100',
|
||||
first,
|
||||
});
|
||||
|
||||
@@ -27,7 +27,6 @@ export interface UpdateCallback<Data, Delta> {
|
||||
loading: boolean;
|
||||
loaded: boolean;
|
||||
pageInfo: PageInfo | null;
|
||||
totalCount?: number;
|
||||
}
|
||||
): void;
|
||||
}
|
||||
@@ -40,9 +39,7 @@ export interface Reload {
|
||||
(forceReset?: boolean): void;
|
||||
}
|
||||
|
||||
type Pagination = Schema.Pagination & {
|
||||
skip?: number;
|
||||
};
|
||||
type Pagination = Schema.Pagination;
|
||||
|
||||
export interface PageInfo {
|
||||
startCursor?: string;
|
||||
@@ -83,12 +80,8 @@ export interface Append<Data> {
|
||||
data: Data | null,
|
||||
insertionData: Data | null,
|
||||
insertionPageInfo: PageInfo | null,
|
||||
pagination?: Pagination,
|
||||
totalCount?: number
|
||||
): {
|
||||
data: Data | null;
|
||||
totalCount?: number;
|
||||
};
|
||||
pagination?: Pagination
|
||||
): Data | null;
|
||||
}
|
||||
|
||||
interface GetData<QueryData, Data, Variables> {
|
||||
@@ -99,10 +92,6 @@ interface GetPageInfo<QueryData> {
|
||||
(queryData: QueryData): PageInfo | null;
|
||||
}
|
||||
|
||||
interface GetTotalCount<QueryData> {
|
||||
(queryData: QueryData): number | undefined;
|
||||
}
|
||||
|
||||
interface GetDelta<SubscriptionData, Delta, Variables> {
|
||||
(
|
||||
subscriptionData: SubscriptionData,
|
||||
@@ -119,44 +108,32 @@ export interface Edge<T extends Node> extends Cursor {
|
||||
node: T;
|
||||
}
|
||||
|
||||
export function defaultAppend<Data>(
|
||||
data: Data | null,
|
||||
insertionData: Data | null,
|
||||
export function defaultAppend<T extends Cursor>(
|
||||
data: T[] | null,
|
||||
insertionData: T[] | null,
|
||||
insertionPageInfo: PageInfo | null,
|
||||
pagination?: Pagination,
|
||||
totalCount?: number
|
||||
pagination?: Pagination
|
||||
) {
|
||||
if (data && insertionData && insertionPageInfo) {
|
||||
if (!(data instanceof Array) || !(insertionData instanceof Array)) {
|
||||
throw new Error(
|
||||
'data needs to be instance of Edge[] when using pagination'
|
||||
'data needs to be instance of Array[] when using pagination'
|
||||
);
|
||||
}
|
||||
if (pagination?.after) {
|
||||
if (data[data.length - 1].cursor === pagination?.after) {
|
||||
return [...data, ...insertionData];
|
||||
}
|
||||
const cursors = data.map((item) => item && item.cursor);
|
||||
const startIndex = cursors.lastIndexOf(pagination.after);
|
||||
if (startIndex !== -1) {
|
||||
const start = startIndex + 1 + (pagination.skip ?? 0);
|
||||
const end = start + insertionData.length;
|
||||
let updatedData = [
|
||||
...data.slice(0, start),
|
||||
...insertionData,
|
||||
...data.slice(end),
|
||||
];
|
||||
if (!insertionPageInfo.hasNextPage && end !== (totalCount ?? 0)) {
|
||||
// adjust totalCount if last page is shorter or longer than expected
|
||||
totalCount = end;
|
||||
updatedData = updatedData.slice(0, end);
|
||||
}
|
||||
return {
|
||||
data: updatedData,
|
||||
// increase totalCount if last page is longer than expected
|
||||
totalCount: totalCount && Math.max(updatedData.length, totalCount),
|
||||
};
|
||||
const start = startIndex + 1;
|
||||
const updatedData = [...data.slice(0, start), ...insertionData];
|
||||
return updatedData;
|
||||
}
|
||||
}
|
||||
}
|
||||
return { data, totalCount };
|
||||
return data;
|
||||
}
|
||||
|
||||
interface DataProviderParams<
|
||||
@@ -175,7 +152,6 @@ interface DataProviderParams<
|
||||
getDelta?: GetDelta<SubscriptionData, Delta, Variables>;
|
||||
pagination?: {
|
||||
getPageInfo: GetPageInfo<QueryData>;
|
||||
getTotalCount?: GetTotalCount<QueryData>;
|
||||
append: Append<Data>;
|
||||
first: number;
|
||||
};
|
||||
@@ -245,7 +221,6 @@ function makeDataProviderInternal<
|
||||
let client: ApolloClient<object>;
|
||||
let subscription: Subscription[] | undefined;
|
||||
let pageInfo: PageInfo | null = null;
|
||||
let totalCount: number | undefined;
|
||||
|
||||
// notify single callback about current state, delta is passes optionally only if notify was invoked onNext
|
||||
const notify = (
|
||||
@@ -258,7 +233,6 @@ function makeDataProviderInternal<
|
||||
loading,
|
||||
loaded,
|
||||
pageInfo,
|
||||
totalCount,
|
||||
...updateData,
|
||||
});
|
||||
};
|
||||
@@ -301,59 +275,41 @@ function makeDataProviderInternal<
|
||||
}
|
||||
});
|
||||
|
||||
const load = async (start?: number) => {
|
||||
const load = async () => {
|
||||
if (!pagination) {
|
||||
return Promise.reject();
|
||||
}
|
||||
if (!pageInfo?.hasNextPage) {
|
||||
return null;
|
||||
}
|
||||
const paginationVariables: Pagination = {
|
||||
first: pagination.first,
|
||||
after: pageInfo?.endCursor,
|
||||
};
|
||||
if (start !== undefined && data instanceof Array) {
|
||||
if (!start) {
|
||||
paginationVariables.after = undefined;
|
||||
} else if (data && data[start - 1]) {
|
||||
paginationVariables.after = (data[start - 1] as Cursor).cursor;
|
||||
} else {
|
||||
let skip = 1;
|
||||
while (!data[start - 1 - skip] && skip <= start) {
|
||||
skip += 1;
|
||||
}
|
||||
paginationVariables.skip = skip;
|
||||
if (skip === start) {
|
||||
paginationVariables.after = undefined;
|
||||
} else {
|
||||
paginationVariables.after = (data[start - 1 - skip] as Cursor).cursor;
|
||||
}
|
||||
if (data) {
|
||||
const endCursor = (data as Cursor[])[(data as Cursor[]).length - 1]
|
||||
.cursor;
|
||||
if (endCursor) {
|
||||
paginationVariables.after = endCursor;
|
||||
}
|
||||
} else if (!pageInfo?.hasNextPage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const res = await call(paginationVariables);
|
||||
|
||||
const insertionData = getData(res.data, variables);
|
||||
const insertionPageInfo = pagination.getPageInfo(res.data);
|
||||
({ data, totalCount } = pagination.append(
|
||||
data = pagination.append(
|
||||
data,
|
||||
insertionData,
|
||||
insertionPageInfo,
|
||||
paginationVariables,
|
||||
totalCount
|
||||
));
|
||||
paginationVariables
|
||||
);
|
||||
pageInfo = insertionPageInfo;
|
||||
totalCount =
|
||||
(pagination.getTotalCount && pagination.getTotalCount(res.data)) ??
|
||||
totalCount;
|
||||
notifyAll({ insertionData, isInsert: true });
|
||||
return insertionData;
|
||||
};
|
||||
|
||||
const setData = (updatedData: Data | null) => {
|
||||
data = updatedData;
|
||||
if (totalCount !== undefined && data instanceof Array) {
|
||||
totalCount = data.length;
|
||||
}
|
||||
};
|
||||
|
||||
const subscriptionSubscribe = () => {
|
||||
@@ -400,16 +356,6 @@ function makeDataProviderInternal<
|
||||
);
|
||||
}
|
||||
pageInfo = pagination.getPageInfo(res.data);
|
||||
if (pageInfo && !pageInfo.hasNextPage) {
|
||||
totalCount = data.length;
|
||||
} else {
|
||||
totalCount =
|
||||
pagination.getTotalCount && pagination.getTotalCount(res.data);
|
||||
}
|
||||
|
||||
if (data && totalCount && data.length < totalCount) {
|
||||
data.push(...new Array(totalCount - data.length).fill(null));
|
||||
}
|
||||
}
|
||||
// if there was some updates received from subscription during initial query loading apply them on just received data
|
||||
if (update && data && updateQueue && updateQueue.length > 0) {
|
||||
@@ -417,9 +363,6 @@ function makeDataProviderInternal<
|
||||
const delta = updateQueue.shift();
|
||||
if (delta) {
|
||||
setData(update(data, delta, reload, variables));
|
||||
if (totalCount !== undefined && data instanceof Array) {
|
||||
totalCount = data.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -590,7 +533,7 @@ const memoize = <
|
||||
* @param update Update<Data, Delta> function that will be executed on each onNext, it should update data base on delta, it can reload data provider
|
||||
* @param getData transforms received query data to format that will be stored in data provider
|
||||
* @param getDelta transforms delta data to format that will be stored in data provider
|
||||
* @param pagination pagination related functions { getPageInfo, getTotalCount, append, first }
|
||||
* @param pagination pagination related functions { getPageInfo, append, first }
|
||||
* @returns Subscribe<Data, Delta> subscribe function
|
||||
* @example
|
||||
* const marketMidPriceProvider = makeDataProvider<QueryData, Data, SubscriptionData, Delta>({
|
||||
|
||||
@@ -12,23 +12,13 @@ export interface useDataProviderParams<
|
||||
Variables extends OperationVariables | undefined = undefined
|
||||
> {
|
||||
dataProvider: Subscribe<Data, Delta, Variables>;
|
||||
update?: ({
|
||||
delta,
|
||||
data,
|
||||
totalCount,
|
||||
}: {
|
||||
delta?: Delta;
|
||||
data: Data | null;
|
||||
totalCount?: number;
|
||||
}) => boolean;
|
||||
update?: ({ delta, data }: { delta?: Delta; data: Data | null }) => boolean;
|
||||
insert?: ({
|
||||
insertionData,
|
||||
data,
|
||||
totalCount,
|
||||
}: {
|
||||
insertionData?: Data | null;
|
||||
data: Data | null;
|
||||
totalCount?: number;
|
||||
}) => boolean;
|
||||
variables: Variables;
|
||||
skipUpdates?: boolean;
|
||||
@@ -56,7 +46,6 @@ export const useDataProvider = <
|
||||
}: useDataProviderParams<Data, Delta, Variables>) => {
|
||||
const client = useApolloClient();
|
||||
const [data, setData] = useState<Data | null>(null);
|
||||
const [totalCount, setTotalCount] = useState<number>();
|
||||
const [loading, setLoading] = useState<boolean>(!skip);
|
||||
const [error, setError] = useState<Error | undefined>(undefined);
|
||||
const flushRef = useRef<(() => void) | undefined>(undefined);
|
||||
@@ -101,7 +90,6 @@ export const useDataProvider = <
|
||||
error,
|
||||
loading,
|
||||
insertionData,
|
||||
totalCount,
|
||||
isInsert,
|
||||
isUpdate,
|
||||
loaded,
|
||||
@@ -116,19 +104,18 @@ export const useDataProvider = <
|
||||
(skipUpdatesRef.current ||
|
||||
(!skipUpdatesRef.current &&
|
||||
updateRef.current &&
|
||||
updateRef.current({ delta, data, totalCount })))
|
||||
updateRef.current({ delta, data })))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
isInsert &&
|
||||
insertRef.current &&
|
||||
insertRef.current({ insertionData, data, totalCount })
|
||||
insertRef.current({ insertionData, data })
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
setTotalCount(totalCount);
|
||||
setData(data);
|
||||
if (!loading && !isUpdate && updateRef.current) {
|
||||
updateRef.current({ data });
|
||||
@@ -150,7 +137,6 @@ export const useDataProvider = <
|
||||
useEffect(() => {
|
||||
setData(null);
|
||||
setError(undefined);
|
||||
setTotalCount(undefined);
|
||||
if (updateRef.current) {
|
||||
updateRef.current({ data: null });
|
||||
}
|
||||
@@ -184,7 +170,6 @@ export const useDataProvider = <
|
||||
flush,
|
||||
reload,
|
||||
load,
|
||||
totalCount,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { AgGridReactProps, AgReactUiProps } from 'ag-grid-react';
|
||||
import { AgGridReact } from 'ag-grid-react';
|
||||
import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useColumnSizes } from './use-column-sizes';
|
||||
import classNames from 'classnames';
|
||||
|
||||
@@ -23,6 +24,8 @@ export const AgGridThemed = ({
|
||||
rowHeight: 22,
|
||||
headerHeight: 22,
|
||||
enableCellTextSelection: true,
|
||||
overlayLoadingTemplate: t('Loading...'),
|
||||
overlayNoRowsTemplate: t('No data'),
|
||||
};
|
||||
|
||||
const wrapperClasses = classNames('vega-ag-grid', {
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { forwardRef } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { getDecimalSeparator, isNumeric } from '@vegaprotocol/utils';
|
||||
|
||||
interface NumericCellProps {
|
||||
value: number | bigint | null | undefined;
|
||||
valueFormatted: string;
|
||||
testId?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -12,7 +14,7 @@ interface NumericCellProps {
|
||||
* use, right aligned, monospace and decimals deemphasised
|
||||
*/
|
||||
export const NumericCell = forwardRef<HTMLSpanElement, NumericCellProps>(
|
||||
({ value, valueFormatted, testId }, ref) => {
|
||||
({ value, valueFormatted, testId, className }, ref) => {
|
||||
if (!isNumeric(value)) {
|
||||
return (
|
||||
<span ref={ref} data-testid={testId}>
|
||||
@@ -29,7 +31,10 @@ export const NumericCell = forwardRef<HTMLSpanElement, NumericCellProps>(
|
||||
return (
|
||||
<span
|
||||
ref={ref}
|
||||
className="font-mono relative text-black dark:text-white whitespace-nowrap overflow-hidden text-ellipsis text-right rtl-dir"
|
||||
className={classNames(
|
||||
'font-mono relative text-black dark:text-white whitespace-nowrap overflow-hidden text-ellipsis text-right rtl-dir',
|
||||
className
|
||||
)}
|
||||
data-testid={testId}
|
||||
title={valueFormatted}
|
||||
>
|
||||
|
||||
@@ -16,7 +16,7 @@ export const OrderTypeCell = ({
|
||||
data: order,
|
||||
onClick,
|
||||
}: OrderTypeCellProps) => {
|
||||
const id = order ? order.market.id : '';
|
||||
const id = order?.market?.id ?? '';
|
||||
|
||||
const label = useMemo(() => {
|
||||
if (!order) {
|
||||
|
||||
@@ -6,11 +6,15 @@ export interface IPriceCellProps {
|
||||
valueFormatted: string;
|
||||
testId?: string;
|
||||
onClick?: (price?: string | number) => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const PriceCell = memo(
|
||||
forwardRef<HTMLSpanElement, IPriceCellProps>(
|
||||
({ value, valueFormatted, testId, onClick }: IPriceCellProps, ref) => {
|
||||
(
|
||||
{ value, valueFormatted, testId, onClick, className }: IPriceCellProps,
|
||||
ref
|
||||
) => {
|
||||
if (!isNumeric(value)) {
|
||||
return (
|
||||
<span data-testid="price" ref={ref}>
|
||||
@@ -27,6 +31,7 @@ export const PriceCell = memo(
|
||||
value={value}
|
||||
valueFormatted={valueFormatted}
|
||||
testId={testId || 'price'}
|
||||
className={className}
|
||||
/>
|
||||
</button>
|
||||
) : (
|
||||
|
||||
@@ -31,6 +31,7 @@ export const MarginWarning = ({ margin, balance, asset }: Props) => {
|
||||
text: t(`Deposit ${asset.symbol}`),
|
||||
action: () => openDepositDialog(asset.id),
|
||||
dataTestId: 'deal-ticket-deposit-dialog-button',
|
||||
size: 'sm',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -21,10 +21,14 @@ export const ZeroBalanceError = ({
|
||||
testId="dealticket-error-message-zero-balance"
|
||||
message={
|
||||
<>
|
||||
You need {asset.symbol} in your wallet to trade in this market.
|
||||
{t(
|
||||
'You need %s in your wallet to trade in this market. ',
|
||||
asset.symbol
|
||||
)}
|
||||
{onClickCollateral && (
|
||||
<>
|
||||
See all your <Link onClick={onClickCollateral}>collateral</Link>.
|
||||
{t('See all your')}{' '}
|
||||
<Link onClick={onClickCollateral}>collateral</Link>.
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
@@ -33,7 +37,7 @@ export const ZeroBalanceError = ({
|
||||
text: t(`Make a deposit`),
|
||||
action: () => openDepositDialog(asset.id),
|
||||
dataTestId: 'deal-ticket-deposit-dialog-button',
|
||||
size: 'md',
|
||||
size: 'sm',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,25 +1,15 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import type { ButtonVariant } from '@vegaprotocol/ui-toolkit';
|
||||
import { Button } from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
|
||||
interface Props {
|
||||
disabled: boolean;
|
||||
variant: ButtonVariant;
|
||||
}
|
||||
|
||||
export const DealTicketButton = ({ disabled, variant }: Props) => {
|
||||
const { pubKey, isReadOnly } = useVegaWallet();
|
||||
const isDisabled = !pubKey || isReadOnly || disabled;
|
||||
export const DealTicketButton = ({ variant }: Props) => {
|
||||
return (
|
||||
<div className="mb-2">
|
||||
<Button
|
||||
variant={variant}
|
||||
fill
|
||||
type="submit"
|
||||
disabled={isDisabled}
|
||||
data-testid="place-order"
|
||||
>
|
||||
<Button variant={variant} fill type="submit" data-testid="place-order">
|
||||
{t('Place order')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -33,24 +33,35 @@ export const DealTicketFeeDetails = (props: FeeDetails) => {
|
||||
const details = getFeeDetailsValues(props);
|
||||
return (
|
||||
<div>
|
||||
{details.map(({ label, value, labelDescription, symbol, indent }) => (
|
||||
<div
|
||||
key={typeof label === 'string' ? label : 'value-dropdown'}
|
||||
className={classnames(
|
||||
'text-xs mt-2 flex justify-between items-center gap-4 flex-wrap',
|
||||
{ 'ml-2': indent }
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
<Tooltip description={labelDescription}>
|
||||
<div>{label}</div>
|
||||
{details.map(
|
||||
({
|
||||
label,
|
||||
value,
|
||||
labelDescription,
|
||||
symbol,
|
||||
indent,
|
||||
formattedValue,
|
||||
}) => (
|
||||
<div
|
||||
key={typeof label === 'string' ? label : 'value-dropdown'}
|
||||
className={classnames(
|
||||
'text-xs mt-2 flex justify-between items-center gap-4 flex-wrap',
|
||||
{ 'ml-2': indent }
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
<Tooltip description={labelDescription}>
|
||||
<div>{label}</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Tooltip description={`${value ?? '-'} ${symbol || ''}`}>
|
||||
<div className="text-neutral-500 dark:text-neutral-300">{`${
|
||||
formattedValue ?? '-'
|
||||
} ${symbol || ''}`}</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="text-neutral-500 dark:text-neutral-300">{`${
|
||||
value ?? '-'
|
||||
} ${symbol || ''}`}</div>
|
||||
</div>
|
||||
))}
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -42,6 +42,9 @@ describe('DealTicket', () => {
|
||||
it('should display ticket defaults', () => {
|
||||
const { container } = render(generateJsx());
|
||||
|
||||
// place order button should always be enabled
|
||||
expect(screen.getByTestId('place-order')).toBeEnabled();
|
||||
|
||||
// Assert defaults are used
|
||||
expect(
|
||||
screen.getByTestId(`order-type-${Schema.OrderType.TYPE_MARKET}`)
|
||||
|
||||
@@ -146,7 +146,7 @@ export const DealTicket = ({
|
||||
});
|
||||
const openVolume = useOpenVolume(pubKey, market.id) ?? '0';
|
||||
const orders = activeOrders
|
||||
? activeOrders.map<OrderInfo>(({ node: order }) => ({
|
||||
? activeOrders.map<OrderInfo>((order) => ({
|
||||
isMarketOrder: order.type === OrderType.TYPE_MARKET,
|
||||
price: order.price,
|
||||
remaining: order.remaining,
|
||||
@@ -217,6 +217,8 @@ export const DealTicket = ({
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// No error found above clear the error in case it was active on a previous render
|
||||
clearErrors('summary');
|
||||
}, [
|
||||
marketState,
|
||||
@@ -480,7 +482,6 @@ export const DealTicket = ({
|
||||
onClickCollateral={onClickCollateral}
|
||||
/>
|
||||
<DealTicketButton
|
||||
disabled={Object.keys(errors).length >= 1 || isReadOnly}
|
||||
variant={
|
||||
order.side === Schema.Side.SIDE_BUY ? 'ternary' : 'secondary'
|
||||
}
|
||||
@@ -562,7 +563,7 @@ const SummaryMessage = memo(
|
||||
text: t('Connect wallet'),
|
||||
action: openVegaWalletDialog,
|
||||
dataTestId: 'order-connect-wallet',
|
||||
size: 'md',
|
||||
size: 'sm',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import { formatRange, formatValue } from './use-fee-deal-ticket-details';
|
||||
|
||||
describe('useFeeDealTicketDetails', () => {
|
||||
it.each([
|
||||
{ v: 123000, d: 5, o: '1.23' },
|
||||
{ v: 123000, d: 3, o: '123.00' },
|
||||
{ v: 123000, d: 1, o: '12,300.0' },
|
||||
{ v: 123001000, d: 2, o: '1,230,010.00' },
|
||||
{ v: 123001, d: 2, o: '1,230.01' },
|
||||
{
|
||||
v: '123456789123456789',
|
||||
d: 10,
|
||||
o: '12,345,678.91234568',
|
||||
},
|
||||
])('formats values correctly', ({ v, d, o }) => {
|
||||
expect(formatValue(v, d)).toStrictEqual(o);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ v: 123000, d: 5, o: '1.23', q: '0.1' },
|
||||
{ v: 123000, d: 3, o: '123.00', q: '0.1' },
|
||||
{ v: 123000, d: 1, o: '12,300.00', q: '0.1' },
|
||||
{ v: 123001000, d: 2, o: '1,230,010.00', q: '0.1' },
|
||||
{ v: 123001, d: 2, o: '1,230', q: '100' },
|
||||
{ v: 123001, d: 2, o: '1,230.01', q: '0.1' },
|
||||
{
|
||||
v: '123456789123456789',
|
||||
d: 10,
|
||||
o: '12,345,678.9123457',
|
||||
q: '0.00003846',
|
||||
},
|
||||
])(
|
||||
'formats with formatValue with quantum given number correctly',
|
||||
({ v, d, o, q }) => {
|
||||
expect(formatValue(v.toString(), d, q)).toStrictEqual(o);
|
||||
}
|
||||
);
|
||||
|
||||
it.each([
|
||||
{ min: 123000, max: 12300011111, d: 5, o: '1.23 - 123,000.111', q: '0.1' },
|
||||
{
|
||||
min: 123000,
|
||||
max: 12300011111,
|
||||
d: 3,
|
||||
o: '123.00 - 12,300,011.111',
|
||||
q: '0.1',
|
||||
},
|
||||
{
|
||||
min: 123000,
|
||||
max: 12300011111,
|
||||
d: 1,
|
||||
o: '12,300.00 - 1,230,001,111.10',
|
||||
q: '0.1',
|
||||
},
|
||||
{
|
||||
min: 123001000,
|
||||
max: 12300011111,
|
||||
d: 2,
|
||||
o: '1,230,010 - 123,000,111',
|
||||
q: '100',
|
||||
},
|
||||
])(
|
||||
'formats with formatValue with quantum given number correctly',
|
||||
({ min, max, d, o, q }) => {
|
||||
expect(formatRange(min, max, d, q)).toStrictEqual(o);
|
||||
}
|
||||
);
|
||||
});
|
||||
@@ -1,5 +1,9 @@
|
||||
import { FeesBreakdown } from '@vegaprotocol/markets';
|
||||
import { addDecimalsFormatNumber, isNumeric } from '@vegaprotocol/utils';
|
||||
import {
|
||||
addDecimalsFormatNumber,
|
||||
addDecimalsFormatNumberQuantum,
|
||||
isNumeric,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import type { Market } from '@vegaprotocol/markets';
|
||||
@@ -52,21 +56,25 @@ export interface FeeDetails {
|
||||
}
|
||||
|
||||
const emptyValue = '-';
|
||||
const formatValue = (
|
||||
|
||||
export const formatValue = (
|
||||
value: string | number | null | undefined,
|
||||
formatDecimals: number
|
||||
formatDecimals: number,
|
||||
quantum?: string
|
||||
): string => {
|
||||
return isNumeric(value)
|
||||
? addDecimalsFormatNumber(value, formatDecimals)
|
||||
: emptyValue;
|
||||
if (!isNumeric(value)) return emptyValue;
|
||||
if (!quantum) return addDecimalsFormatNumber(value, formatDecimals);
|
||||
return addDecimalsFormatNumberQuantum(value, formatDecimals, quantum);
|
||||
};
|
||||
const formatRange = (
|
||||
|
||||
export const formatRange = (
|
||||
min: string | number | null | undefined,
|
||||
max: string | number | null | undefined,
|
||||
formatDecimals: number
|
||||
formatDecimals: number,
|
||||
quantum?: string
|
||||
) => {
|
||||
const minFormatted = formatValue(min, formatDecimals);
|
||||
const maxFormatted = formatValue(max, formatDecimals);
|
||||
const minFormatted = formatValue(min, formatDecimals, quantum);
|
||||
const maxFormatted = formatValue(max, formatDecimals, quantum);
|
||||
if (minFormatted !== maxFormatted) {
|
||||
return `${minFormatted} - ${maxFormatted}`;
|
||||
}
|
||||
@@ -93,9 +101,12 @@ export const getFeeDetailsValues = ({
|
||||
BigInt(generalAccountBalance || '0') + BigInt(marginAccountBalance || '0');
|
||||
const assetDecimals =
|
||||
market.tradableInstrument.instrument.product.settlementAsset.decimals;
|
||||
const quantum =
|
||||
market.tradableInstrument.instrument.product.settlementAsset.quantum;
|
||||
const details: {
|
||||
label: string;
|
||||
value?: string | null;
|
||||
formattedValue?: string | null;
|
||||
symbol: string;
|
||||
indent?: boolean;
|
||||
labelDescription?: React.ReactNode;
|
||||
@@ -103,6 +114,7 @@ export const getFeeDetailsValues = ({
|
||||
{
|
||||
label: t('Notional'),
|
||||
value: formatValue(notionalSize, assetDecimals),
|
||||
formattedValue: formatValue(notionalSize, assetDecimals, quantum),
|
||||
symbol: assetSymbol,
|
||||
labelDescription: NOTIONAL_SIZE_TOOLTIP_TEXT(assetSymbol),
|
||||
},
|
||||
@@ -111,6 +123,9 @@ export const getFeeDetailsValues = ({
|
||||
value:
|
||||
feeEstimate?.totalFeeAmount &&
|
||||
`~${formatValue(feeEstimate?.totalFeeAmount, assetDecimals)}`,
|
||||
formattedValue:
|
||||
feeEstimate?.totalFeeAmount &&
|
||||
`~${formatValue(feeEstimate?.totalFeeAmount, assetDecimals, quantum)}`,
|
||||
labelDescription: (
|
||||
<>
|
||||
<span>
|
||||
@@ -154,6 +169,12 @@ export const getFeeDetailsValues = ({
|
||||
}
|
||||
details.push({
|
||||
label: t('Margin required'),
|
||||
formattedValue: formatRange(
|
||||
marginRequiredBestCase,
|
||||
marginRequiredWorstCase,
|
||||
assetDecimals,
|
||||
quantum
|
||||
),
|
||||
value: formatRange(
|
||||
marginRequiredBestCase,
|
||||
marginRequiredWorstCase,
|
||||
@@ -172,12 +193,13 @@ export const getFeeDetailsValues = ({
|
||||
details.push({
|
||||
indent: true,
|
||||
label: t('Total margin available'),
|
||||
formattedValue: formatValue(totalMarginAvailable, assetDecimals, quantum),
|
||||
value: formatValue(totalMarginAvailable, assetDecimals),
|
||||
symbol: assetSymbol,
|
||||
labelDescription: TOTAL_MARGIN_AVAILABLE(
|
||||
formatValue(generalAccountBalance, assetDecimals),
|
||||
formatValue(marginAccountBalance, assetDecimals),
|
||||
formatValue(currentMaintenanceMargin, assetDecimals),
|
||||
formatValue(generalAccountBalance, assetDecimals, quantum),
|
||||
formatValue(marginAccountBalance, assetDecimals, quantum),
|
||||
formatValue(currentMaintenanceMargin, assetDecimals, quantum),
|
||||
assetSymbol
|
||||
),
|
||||
});
|
||||
@@ -203,6 +225,16 @@ export const getFeeDetailsValues = ({
|
||||
: '0',
|
||||
assetDecimals
|
||||
),
|
||||
formattedValue: formatRange(
|
||||
deductionFromCollateralBestCase > 0
|
||||
? deductionFromCollateralBestCase.toString()
|
||||
: '0',
|
||||
deductionFromCollateralWorstCase > 0
|
||||
? deductionFromCollateralWorstCase.toString()
|
||||
: '0',
|
||||
assetDecimals,
|
||||
quantum
|
||||
),
|
||||
symbol: assetSymbol,
|
||||
labelDescription: DEDUCTION_FROM_COLLATERAL_TOOLTIP_TEXT(assetSymbol),
|
||||
});
|
||||
@@ -214,6 +246,12 @@ export const getFeeDetailsValues = ({
|
||||
marginEstimate?.worstCase.initialLevel,
|
||||
assetDecimals
|
||||
),
|
||||
formattedValue: formatRange(
|
||||
marginEstimate?.bestCase.initialLevel,
|
||||
marginEstimate?.worstCase.initialLevel,
|
||||
assetDecimals,
|
||||
quantum
|
||||
),
|
||||
symbol: assetSymbol,
|
||||
labelDescription: EST_TOTAL_MARGIN_TOOLTIP_TEXT,
|
||||
});
|
||||
@@ -223,9 +261,11 @@ export const getFeeDetailsValues = ({
|
||||
value: formatValue(marginAccountBalance, assetDecimals),
|
||||
symbol: assetSymbol,
|
||||
labelDescription: MARGIN_ACCOUNT_TOOLTIP_TEXT,
|
||||
formattedValue: formatValue(marginAccountBalance, assetDecimals, quantum),
|
||||
});
|
||||
|
||||
let liquidationPriceEstimate = emptyValue;
|
||||
let liquidationPriceEstimateFormatted;
|
||||
|
||||
if (liquidationEstimate) {
|
||||
const liquidationEstimateBestCaseIncludingBuyOrders = BigInt(
|
||||
@@ -262,11 +302,24 @@ export const getFeeDetailsValues = ({
|
||||
).toString(),
|
||||
assetDecimals
|
||||
);
|
||||
liquidationPriceEstimateFormatted = formatRange(
|
||||
(liquidationEstimateBestCase < liquidationEstimateWorstCase
|
||||
? liquidationEstimateBestCase
|
||||
: liquidationEstimateWorstCase
|
||||
).toString(),
|
||||
(liquidationEstimateBestCase > liquidationEstimateWorstCase
|
||||
? liquidationEstimateBestCase
|
||||
: liquidationEstimateWorstCase
|
||||
).toString(),
|
||||
assetDecimals,
|
||||
quantum
|
||||
);
|
||||
}
|
||||
|
||||
details.push({
|
||||
label: t('Liquidation price estimate'),
|
||||
value: liquidationPriceEstimate,
|
||||
formattedValue: liquidationPriceEstimateFormatted,
|
||||
symbol: assetSymbol,
|
||||
labelDescription: LIQUIDATION_PRICE_ESTIMATE_TOOLTIP_TEXT,
|
||||
});
|
||||
|
||||
@@ -32,6 +32,7 @@ export function generateMarket(override?: PartialDeep<Market>): Market {
|
||||
symbol: 'tDAI',
|
||||
name: 'tDAI',
|
||||
decimals: 5,
|
||||
quantum: '1',
|
||||
__typename: 'Asset',
|
||||
},
|
||||
dataSourceSpecForTradingTermination: {
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { MarketState, MarketStateMapping } from '@vegaprotocol/types';
|
||||
|
||||
export const validateMarketState = (state: Schema.MarketState) => {
|
||||
export const validateMarketState = (state: MarketState) => {
|
||||
if (
|
||||
[
|
||||
Schema.MarketState.STATE_SETTLED,
|
||||
Schema.MarketState.STATE_REJECTED,
|
||||
Schema.MarketState.STATE_TRADING_TERMINATED,
|
||||
Schema.MarketState.STATE_CANCELLED,
|
||||
Schema.MarketState.STATE_CLOSED,
|
||||
MarketState.STATE_SETTLED,
|
||||
MarketState.STATE_REJECTED,
|
||||
MarketState.STATE_TRADING_TERMINATED,
|
||||
MarketState.STATE_CANCELLED,
|
||||
MarketState.STATE_CLOSED,
|
||||
].includes(state)
|
||||
) {
|
||||
return t(
|
||||
@@ -16,7 +16,7 @@ export const validateMarketState = (state: Schema.MarketState) => {
|
||||
);
|
||||
}
|
||||
|
||||
if (state === Schema.MarketState.STATE_PROPOSED) {
|
||||
if (state === MarketState.STATE_PROPOSED) {
|
||||
return t(
|
||||
`This market is ${marketTranslations(
|
||||
state
|
||||
@@ -27,11 +27,11 @@ export const validateMarketState = (state: Schema.MarketState) => {
|
||||
return true;
|
||||
};
|
||||
|
||||
const marketTranslations = (marketState: Schema.MarketState) => {
|
||||
const marketTranslations = (marketState: MarketState) => {
|
||||
switch (marketState) {
|
||||
case Schema.MarketState.STATE_TRADING_TERMINATED:
|
||||
case MarketState.STATE_TRADING_TERMINATED:
|
||||
return t('terminated');
|
||||
default:
|
||||
return t(Schema.MarketStateMapping[marketState]).toLowerCase();
|
||||
return t(MarketStateMapping[marketState]).toLowerCase();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { MarketTradingMode } from '@vegaprotocol/types';
|
||||
|
||||
export const validateMarketTradingMode = (
|
||||
marketTradingMode: Schema.MarketTradingMode
|
||||
marketTradingMode: MarketTradingMode
|
||||
) => {
|
||||
if (marketTradingMode === Schema.MarketTradingMode.TRADING_MODE_NO_TRADING) {
|
||||
if (marketTradingMode === MarketTradingMode.TRADING_MODE_NO_TRADING) {
|
||||
return t('Trading terminated');
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,8 @@ import {
|
||||
useIsExemptDepositor,
|
||||
} from './use-get-deposit-maximum';
|
||||
import { useGetDepositedAmount } from './use-get-deposited-amount';
|
||||
import { isAssetTypeERC20, localLoggerFactory } from '@vegaprotocol/utils';
|
||||
import { isAssetTypeERC20 } from '@vegaprotocol/utils';
|
||||
import { localLoggerFactory } from '@vegaprotocol/logger';
|
||||
import { useAccountBalance } from '@vegaprotocol/accounts';
|
||||
import type { Asset } from '@vegaprotocol/assets';
|
||||
import { useWeb3React } from '@web3-react/core';
|
||||
|
||||
@@ -4,7 +4,8 @@ import { useCallback } from 'react';
|
||||
import { useEthereumConfig } from '@vegaprotocol/web3';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import type { Asset } from '@vegaprotocol/assets';
|
||||
import { addDecimal, localLoggerFactory } from '@vegaprotocol/utils';
|
||||
import { addDecimal } from '@vegaprotocol/utils';
|
||||
import { localLoggerFactory } from '@vegaprotocol/logger';
|
||||
|
||||
export const useGetAllowance = (
|
||||
contract: Token | null,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useCallback } from 'react';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import type { Asset } from '@vegaprotocol/assets';
|
||||
import { addDecimal, localLoggerFactory } from '@vegaprotocol/utils';
|
||||
import { addDecimal } from '@vegaprotocol/utils';
|
||||
import { localLoggerFactory } from '@vegaprotocol/logger';
|
||||
import type { CollateralBridge } from '@vegaprotocol/smart-contracts';
|
||||
|
||||
export const useGetDepositMaximum = (
|
||||
|
||||
@@ -3,7 +3,8 @@ import { ethers } from 'ethers';
|
||||
import { useEthereumConfig } from '@vegaprotocol/web3';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import type { Asset } from '@vegaprotocol/assets';
|
||||
import { addDecimal, localLoggerFactory } from '@vegaprotocol/utils';
|
||||
import { addDecimal } from '@vegaprotocol/utils';
|
||||
import { localLoggerFactory } from '@vegaprotocol/logger';
|
||||
import { useWeb3React } from '@web3-react/core';
|
||||
|
||||
export const useGetDepositedAmount = (asset: Asset | undefined) => {
|
||||
|
||||
@@ -13,6 +13,24 @@ import { Networks } from '../../';
|
||||
jest.mock('../../hooks/use-environment');
|
||||
|
||||
describe('Network switcher', () => {
|
||||
const { location } = window;
|
||||
let hrefSetSpy: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
hrefSetSpy = jest.fn();
|
||||
// @ts-ignore can't set location as optional
|
||||
delete window.location;
|
||||
window.location = {} as Location;
|
||||
Object.defineProperty(window.location, 'href', {
|
||||
// @ts-ignore set cannot take SpyInstance
|
||||
set: hrefSetSpy,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.location = location;
|
||||
});
|
||||
|
||||
it.each`
|
||||
network | label
|
||||
${Networks.CUSTOM} | ${envTriggerMapping[Networks.CUSTOM]}
|
||||
@@ -49,21 +67,29 @@ describe('Network switcher', () => {
|
||||
render(<NetworkSwitcher />);
|
||||
|
||||
await userEvent.click(screen.getByRole('button'));
|
||||
let links = screen.getAllByRole('link');
|
||||
|
||||
expect(links[0]).toHaveTextContent(envNameMapping[Networks.MAINNET]);
|
||||
expect(links[1]).toHaveTextContent(envNameMapping[Networks.TESTNET]);
|
||||
expect(links[0]).not.toHaveTextContent(t('current'));
|
||||
expect(links[1]).not.toHaveTextContent(t('current'));
|
||||
expect(links[0]).not.toHaveTextContent(t('not available'));
|
||||
expect(links[1]).not.toHaveTextContent(t('not available'));
|
||||
expect(links[2]).toHaveTextContent(t('Propose a network parameter change'));
|
||||
|
||||
const menuitems = screen.getAllByRole('menuitem');
|
||||
|
||||
expect(menuitems[0]).toHaveTextContent(envNameMapping[Networks.MAINNET]);
|
||||
expect(menuitems[1]).toHaveTextContent(envNameMapping[Networks.TESTNET]);
|
||||
expect(menuitems[0]).not.toHaveTextContent(t('current'));
|
||||
expect(menuitems[1]).not.toHaveTextContent(t('current'));
|
||||
expect(menuitems[0]).not.toHaveTextContent(t('not available'));
|
||||
expect(menuitems[1]).not.toHaveTextContent(t('not available'));
|
||||
expect(menuitems[2]).toHaveTextContent(t('Advanced'));
|
||||
expect(menuitems[0]).toHaveTextContent('Advanced');
|
||||
|
||||
const links = screen.getAllByRole('link');
|
||||
await userEvent.click(links[0]);
|
||||
expect(hrefSetSpy).toHaveBeenCalledWith(mainnetUrl);
|
||||
|
||||
expect(links[0]).toHaveAttribute('href', mainnetUrl);
|
||||
expect(links[1]).toHaveAttribute('href', testnetUrl);
|
||||
// re open dropdown as clicking an item will close it
|
||||
await userEvent.click(screen.getByRole('button'));
|
||||
links = screen.getAllByRole('link');
|
||||
|
||||
await userEvent.click(links[1]);
|
||||
expect(hrefSetSpy).toHaveBeenCalledWith(testnetUrl);
|
||||
});
|
||||
|
||||
it('displays the correct selected network on the default dropdown view', async () => {
|
||||
@@ -82,10 +108,10 @@ describe('Network switcher', () => {
|
||||
|
||||
await userEvent.click(screen.getByRole('button'));
|
||||
|
||||
const menuitems = screen.getAllByRole('menuitem');
|
||||
const links = screen.getAllByRole('link');
|
||||
|
||||
expect(menuitems[0]).toHaveTextContent(envNameMapping[Networks.MAINNET]);
|
||||
expect(menuitems[0]).toHaveTextContent(t('current'));
|
||||
expect(links[0]).toHaveTextContent(envNameMapping[Networks.MAINNET]);
|
||||
expect(links[0]).toHaveTextContent(t('current'));
|
||||
});
|
||||
|
||||
it('displays the correct selected network on the default dropdown view when it does not have an associated url', async () => {
|
||||
@@ -103,10 +129,10 @@ describe('Network switcher', () => {
|
||||
|
||||
await userEvent.click(screen.getByRole('button'));
|
||||
|
||||
const menuitems = screen.getAllByRole('menuitem');
|
||||
const links = screen.getAllByRole('link');
|
||||
|
||||
expect(menuitems[0]).toHaveTextContent(envNameMapping[Networks.MAINNET]);
|
||||
expect(menuitems[0]).toHaveTextContent(t('current'));
|
||||
expect(links[0]).toHaveTextContent(envNameMapping[Networks.MAINNET]);
|
||||
expect(links[0]).toHaveTextContent(t('current'));
|
||||
});
|
||||
|
||||
it('displays the correct state for a network without url on the default dropdown view', async () => {
|
||||
@@ -123,44 +149,59 @@ describe('Network switcher', () => {
|
||||
render(<NetworkSwitcher />);
|
||||
|
||||
await userEvent.click(screen.getByRole('button'));
|
||||
const links = screen.getAllByRole('link');
|
||||
|
||||
const menuitems = screen.getAllByRole('menuitem');
|
||||
|
||||
expect(menuitems[0]).toHaveTextContent(envNameMapping[Networks.MAINNET]);
|
||||
expect(menuitems[0]).toHaveTextContent(t('not available'));
|
||||
expect(links[0]).toHaveTextContent(envNameMapping[Networks.MAINNET]);
|
||||
expect(links[0]).toHaveTextContent(t('not available'));
|
||||
});
|
||||
|
||||
it('displays the advanced view in the correct state', async () => {
|
||||
const VEGA_NETWORKS: Record<Networks, string | undefined> = {
|
||||
[Networks.CUSTOM]: undefined,
|
||||
[Networks.MAINNET]: 'https://main.net',
|
||||
[Networks.TESTNET]: 'https://test.net',
|
||||
[Networks.VALIDATOR_TESTNET]: 'https://validator-test.net',
|
||||
[Networks.DEVNET]: 'https://dev.net',
|
||||
[Networks.STAGNET1]: 'https://stag1.net',
|
||||
};
|
||||
// @ts-ignore Typescript doesn't know about this module being mocked
|
||||
useEnvironment.mockImplementation(() => ({
|
||||
VEGA_ENV: Networks.DEVNET,
|
||||
VEGA_NETWORKS,
|
||||
}));
|
||||
it.each([Networks.MAINNET, Networks.TESTNET, Networks.DEVNET])(
|
||||
'displays the advanced view in the correct state',
|
||||
async (network) => {
|
||||
const VEGA_NETWORKS: Record<Networks, string | undefined> = {
|
||||
[Networks.CUSTOM]: undefined,
|
||||
[Networks.MAINNET]: 'https://main.net',
|
||||
[Networks.TESTNET]: 'https://test.net',
|
||||
[Networks.VALIDATOR_TESTNET]: 'https://validator-test.net',
|
||||
[Networks.DEVNET]: 'https://dev.net',
|
||||
[Networks.STAGNET1]: 'https://stag1.net',
|
||||
};
|
||||
// @ts-ignore Typescript doesn't know about this module being mocked
|
||||
useEnvironment.mockImplementation(() => ({
|
||||
VEGA_ENV: Networks.DEVNET,
|
||||
VEGA_NETWORKS,
|
||||
}));
|
||||
|
||||
render(<NetworkSwitcher />);
|
||||
render(<NetworkSwitcher />);
|
||||
|
||||
await userEvent.click(screen.getByRole('button'));
|
||||
await userEvent.click(
|
||||
screen.getByRole('menuitem', { name: t('Advanced') })
|
||||
);
|
||||
await userEvent.click(screen.getByTestId('network-switcher'));
|
||||
|
||||
[Networks.MAINNET, Networks.TESTNET, Networks.DEVNET].forEach((network) => {
|
||||
expect(
|
||||
screen.getByRole('link', { name: envNameMapping[network] })
|
||||
).toHaveAttribute('href', VEGA_NETWORKS[network]);
|
||||
expect(
|
||||
screen.getByText(envDescriptionMapping[network])
|
||||
await screen.findByRole('menuitem', { name: t('Advanced') })
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
await userEvent.click(
|
||||
screen.getByRole('menuitem', { name: t('Advanced') })
|
||||
);
|
||||
|
||||
expect(
|
||||
await screen.findByText(envDescriptionMapping[network])
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole('link', {
|
||||
name: new RegExp(`^${envNameMapping[network]}`),
|
||||
})
|
||||
).toBeInTheDocument();
|
||||
|
||||
await userEvent.click(
|
||||
screen.getByRole('link', {
|
||||
name: new RegExp(`^${envNameMapping[network]}`),
|
||||
})
|
||||
);
|
||||
|
||||
expect(hrefSetSpy).toHaveBeenCalledWith(VEGA_NETWORKS[network]);
|
||||
}
|
||||
);
|
||||
|
||||
it('labels the selected network in the advanced view', async () => {
|
||||
const selectedNetwork = Networks.DEVNET;
|
||||
@@ -188,7 +229,7 @@ describe('Network switcher', () => {
|
||||
const label = screen.getByText(`(${t('current')})`);
|
||||
|
||||
expect(label).toBeInTheDocument();
|
||||
expect(label.parentNode?.firstElementChild).toHaveTextContent(
|
||||
expect(label.parentNode?.parentNode?.firstElementChild).toHaveTextContent(
|
||||
envNameMapping[selectedNetwork]
|
||||
);
|
||||
});
|
||||
@@ -217,7 +258,7 @@ describe('Network switcher', () => {
|
||||
const label = screen.getByText('(not available)');
|
||||
|
||||
expect(label).toBeInTheDocument();
|
||||
expect(label.parentNode?.firstElementChild).toHaveTextContent(
|
||||
expect(label.parentNode?.parentNode?.firstElementChild).toHaveTextContent(
|
||||
envNameMapping[Networks.MAINNET]
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useState, useCallback, useRef } from 'react';
|
||||
import { useState, useCallback } from 'react';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import {
|
||||
Link,
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
@@ -99,7 +98,6 @@ export const NetworkSwitcher = ({
|
||||
},
|
||||
[setOpen, setAdvancedView]
|
||||
);
|
||||
const menuRef = useRef<HTMLButtonElement | null>(null);
|
||||
|
||||
const current = currentNetwork || VEGA_ENV;
|
||||
|
||||
@@ -110,7 +108,6 @@ export const NetworkSwitcher = ({
|
||||
trigger={
|
||||
<DropdownMenuTrigger
|
||||
data-testid="network-switcher"
|
||||
ref={menuRef}
|
||||
className={classNames(
|
||||
'flex justify-between items-center text-sm text-vega-dark-600 dark:text-vega-light-600 py-1 px-2 rounded border border-vega-dark-200 whitespace-nowrap dark:hover:bg-vega-dark-500 hover:bg-vega-light-500',
|
||||
className
|
||||
@@ -123,10 +120,7 @@ export const NetworkSwitcher = ({
|
||||
</DropdownMenuTrigger>
|
||||
}
|
||||
>
|
||||
<DropdownMenuContent
|
||||
align="start"
|
||||
style={{ minWidth: `${menuRef.current?.offsetWidth || 290}px` }}
|
||||
>
|
||||
<DropdownMenuContent align="start">
|
||||
{!isAdvancedView && (
|
||||
<>
|
||||
{standardNetworkKeys.map((key) => (
|
||||
@@ -134,14 +128,16 @@ export const NetworkSwitcher = ({
|
||||
key={key}
|
||||
data-testid="network-item"
|
||||
disabled={!VEGA_NETWORKS[key]}
|
||||
role="link"
|
||||
onClick={() =>
|
||||
(window.location.href = VEGA_NETWORKS[key] || '')
|
||||
}
|
||||
>
|
||||
<a href={VEGA_NETWORKS[key]}>
|
||||
{envNameMapping[key]}
|
||||
<NetworkLabel
|
||||
isCurrent={current === key}
|
||||
isAvailable={!!VEGA_NETWORKS[key]}
|
||||
/>
|
||||
</a>
|
||||
{envNameMapping[key]}
|
||||
<NetworkLabel
|
||||
isCurrent={current === key}
|
||||
isAvailable={!!VEGA_NETWORKS[key]}
|
||||
/>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
<DropdownMenuItem
|
||||
@@ -159,10 +155,17 @@ export const NetworkSwitcher = ({
|
||||
{isAdvancedView && (
|
||||
<>
|
||||
{advancedNetworkKeys.map((key) => (
|
||||
<DropdownMenuItem key={key} data-testid="network-item-advanced">
|
||||
<DropdownMenuItem
|
||||
key={key}
|
||||
data-testid="network-item-advanced"
|
||||
role="link"
|
||||
onClick={() =>
|
||||
(window.location.href = VEGA_NETWORKS[key] || '')
|
||||
}
|
||||
>
|
||||
<div className="w-full flex justify-between gap-2">
|
||||
<div>
|
||||
<Link href={VEGA_NETWORKS[key]}>{envNameMapping[key]}</Link>
|
||||
{envNameMapping[key]}
|
||||
<NetworkLabel
|
||||
isCurrent={current === key}
|
||||
isAvailable={!!VEGA_NETWORKS[key]}
|
||||
|
||||
@@ -72,6 +72,7 @@ export const DocsLinks = VEGA_DOCS_URL
|
||||
POSITION_RESOLUTION: `${VEGA_DOCS_URL}/concepts/trading-on-vega/market-protections#position-resolution`,
|
||||
LIQUIDITY: `${VEGA_DOCS_URL}/concepts/liquidity/provision`,
|
||||
WITHDRAWAL_LIMITS: `${VEGA_DOCS_URL}/concepts/assets/deposits-withdrawals#withdrawal-limits`,
|
||||
VALIDATOR_SCORES_REWARDS: `${VEGA_DOCS_URL}/concepts/vega-chain/validator-scores-and-rewards`,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { localLoggerFactory } from '@vegaprotocol/utils';
|
||||
import { localLoggerFactory } from '@vegaprotocol/logger';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import z from 'zod';
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
export * from './lib/fills-container';
|
||||
export * from './lib/use-fills-list';
|
||||
export * from './lib/fills-data-provider';
|
||||
export * from './lib/__generated__/Fills';
|
||||
|
||||
@@ -48,28 +48,32 @@ query Fills($filter: TradesFilter, $pagination: Pagination) {
|
||||
}
|
||||
}
|
||||
|
||||
fragment FillUpdateFields on TradeUpdate {
|
||||
id
|
||||
marketId
|
||||
buyOrder
|
||||
sellOrder
|
||||
buyerId
|
||||
sellerId
|
||||
aggressor
|
||||
price
|
||||
size
|
||||
createdAt
|
||||
type
|
||||
buyerFee {
|
||||
makerFee
|
||||
infrastructureFee
|
||||
liquidityFee
|
||||
}
|
||||
sellerFee {
|
||||
makerFee
|
||||
infrastructureFee
|
||||
liquidityFee
|
||||
}
|
||||
}
|
||||
|
||||
subscription FillsEvent($filter: TradesSubscriptionFilter!) {
|
||||
tradesStream(filter: $filter) {
|
||||
id
|
||||
marketId
|
||||
buyOrder
|
||||
sellOrder
|
||||
buyerId
|
||||
sellerId
|
||||
aggressor
|
||||
price
|
||||
size
|
||||
createdAt
|
||||
type
|
||||
buyerFee {
|
||||
makerFee
|
||||
infrastructureFee
|
||||
liquidityFee
|
||||
}
|
||||
sellerFee {
|
||||
makerFee
|
||||
infrastructureFee
|
||||
liquidityFee
|
||||
}
|
||||
...FillUpdateFields
|
||||
}
|
||||
}
|
||||
|
||||
+29
-22
@@ -15,6 +15,8 @@ export type FillsQueryVariables = Types.Exact<{
|
||||
|
||||
export type FillsQuery = { __typename?: 'Query', trades?: { __typename?: 'TradeConnection', edges: Array<{ __typename?: 'TradeEdge', cursor: string, node: { __typename?: 'Trade', id: string, createdAt: any, price: string, size: string, buyOrder: string, sellOrder: string, aggressor: Types.Side, market: { __typename?: 'Market', id: string }, buyer: { __typename?: 'Party', id: string }, seller: { __typename?: 'Party', id: string }, buyerFee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string }, sellerFee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string } } }>, pageInfo: { __typename?: 'PageInfo', startCursor: string, endCursor: string, hasNextPage: boolean, hasPreviousPage: boolean } } | null };
|
||||
|
||||
export type FillUpdateFieldsFragment = { __typename?: 'TradeUpdate', id: string, marketId: string, buyOrder: string, sellOrder: string, buyerId: string, sellerId: string, aggressor: Types.Side, price: string, size: string, createdAt: any, type: Types.TradeType, buyerFee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string }, sellerFee: { __typename?: 'TradeFee', makerFee: string, infrastructureFee: string, liquidityFee: string } };
|
||||
|
||||
export type FillsEventSubscriptionVariables = Types.Exact<{
|
||||
filter: Types.TradesSubscriptionFilter;
|
||||
}>;
|
||||
@@ -60,6 +62,31 @@ export const FillEdgeFragmentDoc = gql`
|
||||
cursor
|
||||
}
|
||||
${FillFieldsFragmentDoc}`;
|
||||
export const FillUpdateFieldsFragmentDoc = gql`
|
||||
fragment FillUpdateFields on TradeUpdate {
|
||||
id
|
||||
marketId
|
||||
buyOrder
|
||||
sellOrder
|
||||
buyerId
|
||||
sellerId
|
||||
aggressor
|
||||
price
|
||||
size
|
||||
createdAt
|
||||
type
|
||||
buyerFee {
|
||||
makerFee
|
||||
infrastructureFee
|
||||
liquidityFee
|
||||
}
|
||||
sellerFee {
|
||||
makerFee
|
||||
infrastructureFee
|
||||
liquidityFee
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const FillsDocument = gql`
|
||||
query Fills($filter: TradesFilter, $pagination: Pagination) {
|
||||
trades(filter: $filter, pagination: $pagination) {
|
||||
@@ -107,30 +134,10 @@ export type FillsQueryResult = Apollo.QueryResult<FillsQuery, FillsQueryVariable
|
||||
export const FillsEventDocument = gql`
|
||||
subscription FillsEvent($filter: TradesSubscriptionFilter!) {
|
||||
tradesStream(filter: $filter) {
|
||||
id
|
||||
marketId
|
||||
buyOrder
|
||||
sellOrder
|
||||
buyerId
|
||||
sellerId
|
||||
aggressor
|
||||
price
|
||||
size
|
||||
createdAt
|
||||
type
|
||||
buyerFee {
|
||||
makerFee
|
||||
infrastructureFee
|
||||
liquidityFee
|
||||
}
|
||||
sellerFee {
|
||||
makerFee
|
||||
infrastructureFee
|
||||
liquidityFee
|
||||
}
|
||||
...FillUpdateFields
|
||||
}
|
||||
}
|
||||
`;
|
||||
${FillUpdateFieldsFragmentDoc}`;
|
||||
|
||||
/**
|
||||
* __useFillsEventSubscription__
|
||||
|
||||
@@ -1,74 +1,34 @@
|
||||
import produce from 'immer';
|
||||
import orderBy from 'lodash/orderBy';
|
||||
import {} from '@vegaprotocol/utils';
|
||||
import type { PageInfo, Edge } from '@vegaprotocol/data-provider';
|
||||
import type { PageInfo, Cursor } from '@vegaprotocol/data-provider';
|
||||
import {
|
||||
makeDataProvider,
|
||||
makeDerivedDataProvider,
|
||||
defaultAppend as append,
|
||||
paginatedCombineDelta as combineDelta,
|
||||
paginatedCombineInsertionData as combineInsertionData,
|
||||
} from '@vegaprotocol/data-provider';
|
||||
import type { Market } from '@vegaprotocol/markets';
|
||||
import { marketsProvider } from '@vegaprotocol/markets';
|
||||
import { marketsMapProvider } from '@vegaprotocol/markets';
|
||||
import { FillsDocument, FillsEventDocument } from './__generated__/Fills';
|
||||
import type {
|
||||
FillsQuery,
|
||||
FillsQueryVariables,
|
||||
FillFieldsFragment,
|
||||
FillEdgeFragment,
|
||||
FillsEventSubscription,
|
||||
FillUpdateFieldsFragment,
|
||||
FillsEventSubscriptionVariables,
|
||||
} from './__generated__/Fills';
|
||||
|
||||
const update = (
|
||||
data: FillEdgeFragment[] | null,
|
||||
delta: FillsEventSubscription['tradesStream']
|
||||
) => {
|
||||
return produce(data, (draft) => {
|
||||
orderBy(delta, 'createdAt').forEach((node) => {
|
||||
if (draft === null) {
|
||||
return;
|
||||
}
|
||||
const index = draft.findIndex((edge) => edge?.node.id === node.id);
|
||||
if (index !== -1) {
|
||||
if (draft[index]?.node) {
|
||||
Object.assign(draft[index]?.node as FillFieldsFragment, node);
|
||||
}
|
||||
} else {
|
||||
const firstNode = draft[0]?.node;
|
||||
if (
|
||||
(firstNode && node.createdAt >= firstNode.createdAt) ||
|
||||
!firstNode
|
||||
) {
|
||||
const { buyerId, sellerId, marketId, ...trade } = node;
|
||||
draft.unshift({
|
||||
node: {
|
||||
...trade,
|
||||
__typename: 'Trade',
|
||||
market: {
|
||||
__typename: 'Market',
|
||||
id: marketId,
|
||||
},
|
||||
buyer: { id: buyerId, __typename: 'Party' },
|
||||
seller: { id: buyerId, __typename: 'Party' },
|
||||
},
|
||||
cursor: '',
|
||||
__typename: 'TradeEdge',
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export type Trade = Omit<FillFieldsFragment, 'market'> & {
|
||||
market?: Market;
|
||||
isLastPlaceholder?: boolean;
|
||||
};
|
||||
export type TradeEdge = Edge<Trade>;
|
||||
|
||||
const getData = (responseData: FillsQuery | null): FillEdgeFragment[] =>
|
||||
responseData?.trades?.edges || [];
|
||||
const getData = (
|
||||
responseData: FillsQuery | null
|
||||
): (FillFieldsFragment & Cursor)[] =>
|
||||
responseData?.trades?.edges.map<FillFieldsFragment & Cursor>((edge) => ({
|
||||
...edge.node,
|
||||
cursor: edge.cursor,
|
||||
})) || [];
|
||||
|
||||
const getPageInfo = (responseData: FillsQuery | null): PageInfo | null =>
|
||||
responseData?.trades?.pageInfo || null;
|
||||
@@ -76,16 +36,65 @@ const getPageInfo = (responseData: FillsQuery | null): PageInfo | null =>
|
||||
const getDelta = (subscriptionData: FillsEventSubscription) =>
|
||||
subscriptionData.tradesStream || [];
|
||||
|
||||
const mapFillUpdateToFill = (
|
||||
fillUpdate: FillUpdateFieldsFragment
|
||||
): FillFieldsFragment => {
|
||||
const { buyerId, sellerId, marketId, ...fill } = fillUpdate;
|
||||
return {
|
||||
...fill,
|
||||
__typename: 'Trade',
|
||||
market: {
|
||||
__typename: 'Market',
|
||||
id: marketId,
|
||||
},
|
||||
buyer: { id: buyerId, __typename: 'Party' },
|
||||
seller: { id: buyerId, __typename: 'Party' },
|
||||
};
|
||||
};
|
||||
|
||||
const mapFillUpdateToFillWithMarket =
|
||||
(markets: Record<string, Market>) =>
|
||||
(fillUpdate: FillUpdateFieldsFragment): Trade => {
|
||||
const { market, ...fill } = mapFillUpdateToFill(fillUpdate);
|
||||
return {
|
||||
...fill,
|
||||
market: markets[market.id],
|
||||
};
|
||||
};
|
||||
|
||||
const update = <T extends Omit<FillFieldsFragment, 'market'> & Cursor>(
|
||||
data: T[] | null,
|
||||
delta: ReturnType<typeof getDelta>,
|
||||
variables: FillsQueryVariables,
|
||||
mapDeltaToData: (delta: FillUpdateFieldsFragment) => T
|
||||
): T[] => {
|
||||
const updatedData = data ? [...data] : ([] as T[]);
|
||||
orderBy(delta, 'createdAt', 'desc').forEach((fillUpdate) => {
|
||||
const index = data?.findIndex((fill) => fill.id === fillUpdate.id) ?? -1;
|
||||
if (index !== -1) {
|
||||
updatedData[index] = {
|
||||
...updatedData[index],
|
||||
...mapDeltaToData(fillUpdate),
|
||||
};
|
||||
} else if (!data?.length || fillUpdate.createdAt >= data[0].createdAt) {
|
||||
updatedData.unshift(mapDeltaToData(fillUpdate));
|
||||
}
|
||||
});
|
||||
return updatedData;
|
||||
};
|
||||
|
||||
export const fillsProvider = makeDataProvider<
|
||||
Parameters<typeof getData>['0'],
|
||||
ReturnType<typeof getData>,
|
||||
Parameters<typeof getDelta>['0'],
|
||||
ReturnType<typeof getDelta>,
|
||||
FillsQueryVariables
|
||||
FillsQueryVariables,
|
||||
FillsEventSubscriptionVariables
|
||||
>({
|
||||
query: FillsDocument,
|
||||
subscriptionQuery: FillsEventDocument,
|
||||
update,
|
||||
update: (data, delta, reload, variables) =>
|
||||
update(data, delta, variables, mapFillUpdateToFill),
|
||||
getData,
|
||||
getDelta,
|
||||
pagination: {
|
||||
@@ -93,30 +102,41 @@ export const fillsProvider = makeDataProvider<
|
||||
append,
|
||||
first: 100,
|
||||
},
|
||||
getSubscriptionVariables: ({ filter }) => {
|
||||
const variables: FillsEventSubscriptionVariables = { filter: {} };
|
||||
if (filter) {
|
||||
variables.filter = {
|
||||
partyIds: filter.partyIds,
|
||||
marketIds: filter.marketIds,
|
||||
};
|
||||
}
|
||||
return variables;
|
||||
},
|
||||
});
|
||||
|
||||
export const fillsWithMarketProvider = makeDerivedDataProvider<
|
||||
(TradeEdge | null)[],
|
||||
Trade[],
|
||||
never,
|
||||
FillsQueryVariables
|
||||
>(
|
||||
[
|
||||
fillsProvider,
|
||||
(callback, client) => marketsProvider(callback, client, undefined),
|
||||
(callback, client) => marketsMapProvider(callback, client, undefined),
|
||||
],
|
||||
(partsData): (TradeEdge | null)[] =>
|
||||
(partsData[0] as ReturnType<typeof getData>)?.map(
|
||||
(edge) =>
|
||||
edge && {
|
||||
cursor: edge.cursor,
|
||||
node: {
|
||||
...edge.node,
|
||||
market: (partsData[1] as Market[]).find(
|
||||
(market) => market.id === edge.node.market.id
|
||||
),
|
||||
},
|
||||
}
|
||||
) || null,
|
||||
combineDelta<Trade, ReturnType<typeof getDelta>['0']>,
|
||||
combineInsertionData<Trade>
|
||||
(partsData, variables, prevData, parts): Trade[] | null => {
|
||||
if (prevData && parts[0].isUpdate) {
|
||||
return update(
|
||||
prevData,
|
||||
parts[0].delta as ReturnType<typeof getDelta>,
|
||||
variables,
|
||||
mapFillUpdateToFillWithMarket(partsData[1] as Record<string, Market>)
|
||||
);
|
||||
}
|
||||
return ((partsData[0] as ReturnType<typeof getData>) || []).map(
|
||||
(trade) => ({
|
||||
...trade,
|
||||
market: (partsData[1] as Record<string, Market>)[trade.market.id],
|
||||
})
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||
import { useRef } from 'react';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { FillsTable } from './fills-table';
|
||||
import type { BodyScrollEvent, BodyScrollEndEvent } from 'ag-grid-community';
|
||||
import { useFillsList } from './use-fills-list';
|
||||
import { useBottomPlaceholder } from '@vegaprotocol/datagrid';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import type * as Schema from '@vegaprotocol/types';
|
||||
import { fillsWithMarketProvider } from './fills-data-provider';
|
||||
|
||||
interface FillsManagerProps {
|
||||
partyId: string;
|
||||
@@ -21,81 +21,36 @@ export const FillsManager = ({
|
||||
storeKey,
|
||||
}: FillsManagerProps) => {
|
||||
const gridRef = useRef<AgGridReact | null>(null);
|
||||
const scrolledToTop = useRef(true);
|
||||
const {
|
||||
data,
|
||||
error,
|
||||
loading,
|
||||
addNewRows,
|
||||
getRows,
|
||||
reload,
|
||||
makeBottomPlaceholders,
|
||||
} = useFillsList({
|
||||
partyId,
|
||||
marketId,
|
||||
const filter: Schema.TradesFilter | Schema.TradesSubscriptionFilter = {
|
||||
partyIds: [partyId],
|
||||
};
|
||||
if (marketId) {
|
||||
filter.marketIds = [marketId];
|
||||
}
|
||||
const { data, error } = useDataProvider({
|
||||
dataProvider: fillsWithMarketProvider,
|
||||
update: ({ data }) => {
|
||||
if (data?.length && gridRef.current?.api) {
|
||||
gridRef.current?.api.setRowData(data);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
variables: { filter },
|
||||
});
|
||||
const bottomPlaceholderProps = useBottomPlaceholder({
|
||||
gridRef,
|
||||
scrolledToTop,
|
||||
});
|
||||
|
||||
const checkBottomPlaceholder = useCallback(() => {
|
||||
const rowCont = gridRef.current?.api?.getModel().getRowCount() ?? 0;
|
||||
const lastRowIndex = gridRef.current?.api?.getLastDisplayedRow();
|
||||
if (lastRowIndex && rowCont - 1 === lastRowIndex) {
|
||||
const lastrow = gridRef.current?.api.getDisplayedRowAtIndex(lastRowIndex);
|
||||
lastrow?.setRowHeight(50);
|
||||
makeBottomPlaceholders(lastrow?.data);
|
||||
gridRef.current?.api.onRowHeightChanged();
|
||||
gridRef.current?.api.refreshInfiniteCache();
|
||||
}
|
||||
}, [makeBottomPlaceholders]);
|
||||
|
||||
const onBodyScrollEnd = useCallback(
|
||||
(event: BodyScrollEndEvent) => {
|
||||
if (event.top === 0) {
|
||||
addNewRows();
|
||||
}
|
||||
checkBottomPlaceholder();
|
||||
},
|
||||
[addNewRows, checkBottomPlaceholder]
|
||||
);
|
||||
|
||||
const onBodyScroll = useCallback((event: BodyScrollEvent) => {
|
||||
scrolledToTop.current = event.top <= 0;
|
||||
}, []);
|
||||
|
||||
const { isFullWidthRow, fullWidthCellRenderer, rowClassRules, getRowHeight } =
|
||||
useBottomPlaceholder({
|
||||
gridRef,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="h-full relative">
|
||||
<FillsTable
|
||||
ref={gridRef}
|
||||
partyId={partyId}
|
||||
rowModelType="infinite"
|
||||
datasource={{ getRows }}
|
||||
onBodyScrollEnd={onBodyScrollEnd}
|
||||
onBodyScroll={onBodyScroll}
|
||||
onMarketClick={onMarketClick}
|
||||
suppressLoadingOverlay
|
||||
suppressNoRowsOverlay
|
||||
isFullWidthRow={isFullWidthRow}
|
||||
fullWidthCellRenderer={fullWidthCellRenderer}
|
||||
rowClassRules={rowClassRules}
|
||||
getRowHeight={getRowHeight}
|
||||
storeKey={storeKey}
|
||||
/>
|
||||
<div className="pointer-events-none absolute inset-0">
|
||||
<AsyncRenderer
|
||||
loading={loading}
|
||||
error={error}
|
||||
data={data}
|
||||
noDataMessage={t('No fills')}
|
||||
noDataCondition={(data) => !(data && data.length)}
|
||||
reload={reload}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<FillsTable
|
||||
ref={gridRef}
|
||||
rowData={data}
|
||||
partyId={partyId}
|
||||
onMarketClick={onMarketClick}
|
||||
storeKey={storeKey}
|
||||
{...bottomPlaceholderProps}
|
||||
overlayNoRowsTemplate={error ? error.message : t('No fills')}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user