Compare commits
43
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab27b52845 | ||
|
|
9084ea0afb | ||
|
|
437a3ab495 | ||
|
|
2491c1c118 | ||
|
|
75d9166260 | ||
|
|
07175d2854 | ||
|
|
1b8f5d0952 | ||
|
|
53f8bb148b | ||
|
|
238d9596fb | ||
|
|
67c178c56e | ||
|
|
6f817d2fad | ||
|
|
50013bdf95 | ||
|
|
8e91a6db3d | ||
|
|
ceea736c82 | ||
|
|
9629eb21ac | ||
|
|
24c9d9aed7 | ||
|
|
af746f05aa | ||
|
|
74661c4ccd | ||
|
|
a42b359f11 | ||
|
|
8f78626627 | ||
|
|
becd62996c | ||
|
|
e565b3887c | ||
|
|
0165063ad5 | ||
|
|
b8b0bf7586 | ||
|
|
b029857830 | ||
|
|
a914e67061 | ||
|
|
4e9117aca9 | ||
|
|
bda7430484 | ||
|
|
a90f631a3e | ||
|
|
a196c96156 | ||
|
|
576c3db59e | ||
|
|
2ecb518d39 | ||
|
|
4cbee21cc5 | ||
|
|
2c2fab1da1 | ||
|
|
7f95ba577e | ||
|
|
2f7e53e282 | ||
|
|
568dcd5305 | ||
|
|
9605b0e7fa | ||
|
|
0776332ba7 | ||
|
|
f780013846 | ||
|
|
73f37c2477 | ||
|
|
607ad06971 | ||
|
|
7aee2a3a7b |
@@ -34,7 +34,7 @@ jobs:
|
||||
${{ runner.os }}-cache-node-modules-
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
@@ -57,7 +57,7 @@ jobs:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
# https://stackoverflow.com/questions/61010294/how-to-cache-yarn-packages-in-github-actions
|
||||
@@ -96,7 +96,7 @@ jobs:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: yarn
|
||||
@@ -128,7 +128,7 @@ jobs:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: yarn
|
||||
@@ -182,7 +182,7 @@ jobs:
|
||||
run: |
|
||||
if [[ "${{ github.base_ref }}" == "develop" ]]; then
|
||||
echo "e2e-needed=true" >> $GITHUB_OUTPUT
|
||||
elif [[ "${{ github.base_ref }}" == "main" ]]; then
|
||||
elif [[ "${{ github.base_ref }}" == "main" ]]; then
|
||||
echo "e2e-needed=true" >> $GITHUB_OUTPUT
|
||||
elif [[ "${{ github.event_name }}" == "push" && "${{ github.ref_name }}" == *"release/"* ]]; then
|
||||
echo "e2e-needed=true" >> $GITHUB_OUTPUT
|
||||
|
||||
@@ -32,9 +32,9 @@ jobs:
|
||||
# cache node modules
|
||||
#----------------------------------------------
|
||||
- name: setup node
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '16'
|
||||
node-version-file: '.nvmrc'
|
||||
cache: yarn
|
||||
|
||||
- name: Cache node modules
|
||||
|
||||
@@ -18,11 +18,11 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Use Node.js 16
|
||||
- name: Use Node.js 20
|
||||
id: Node
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 16.15.1
|
||||
node-version-file: '.nvmrc'
|
||||
|
||||
- name: Run Cypress tests
|
||||
uses: cypress-io/github-action@v4
|
||||
|
||||
@@ -57,6 +57,14 @@ jobs:
|
||||
path: './frontend-monorepo'
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: './frontend-monorepo/.nvmrc'
|
||||
# https://stackoverflow.com/questions/61010294/how-to-cache-yarn-packages-in-github-actions
|
||||
cache: yarn
|
||||
cache-dependency-path: './frontend-monorepo/yarn.lock'
|
||||
|
||||
# Restore node_modules from cache if possible
|
||||
- name: Restore node_modules from cache
|
||||
id: cache-node-modules
|
||||
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
# https://stackoverflow.com/questions/61010294/how-to-cache-yarn-packages-in-github-actions
|
||||
|
||||
@@ -18,13 +18,13 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 16
|
||||
node-version-file: '.nvmrc'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
rm package.json
|
||||
rm package.json
|
||||
npm install --no-save @commitlint/cli @commitlint/config-conventional @commitlint/config-nx-scopes nx
|
||||
|
||||
- name: Check PR title
|
||||
|
||||
@@ -93,7 +93,7 @@ jobs:
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
# https://stackoverflow.com/questions/61010294/how-to-cache-yarn-packages-in-github-actions
|
||||
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
# https://stackoverflow.com/questions/61010294/how-to-cache-yarn-packages-in-github-actions
|
||||
|
||||
@@ -52,3 +52,5 @@ cypress.env.json
|
||||
/apps/**/cypress/reports/
|
||||
/apps/**/cypress/downloads/
|
||||
/apps/**/fixtures/wallet/node**
|
||||
|
||||
.nx/cache
|
||||
@@ -8,3 +8,5 @@ __generated___
|
||||
apps/static/src/assets/devnet-tranches.json
|
||||
apps/static/src/assets/mainnet-tranches.json
|
||||
apps/static/src/assets/testnet-tranches.json
|
||||
|
||||
/.nx/cache
|
||||
@@ -1,3 +1,4 @@
|
||||
{
|
||||
"plugins": ["prettier-plugin-tailwindcss"],
|
||||
"singleQuote": true
|
||||
}
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
* @vegaprotocol/frontend
|
||||
apps/ @vegaprotocol/frontend-qa
|
||||
libs/ @vegaprotocol/frontend-qa
|
||||
*.graphql @vegaprotocol/core
|
||||
|
||||
@@ -136,7 +136,7 @@ The [`docker`](./docker) subfolder has some docker configurations for easily set
|
||||
Using multistage dockerfile dist is compiled using [node](https://hub.docker.com/_/node) image and later packed to nginx as in [dist build](#dist-build). The multistage builds ensures consistent CPU architecture and build toolchains are used so that the result will be identical.
|
||||
|
||||
```bash
|
||||
docker build --build-arg APP=[YOUR APP] --build-arg NODE_VERSION=16.5.1 --build-arg ENV_NAME=mainnet -t [TAG] -f docker/node-inside-docker.Dockerfile .
|
||||
docker build --build-arg APP=[YOUR APP] --build-arg NODE_VERSION=20.9.1 --build-arg ENV_NAME=mainnet -t [TAG] -f docker/node-inside-docker.Dockerfile .
|
||||
```
|
||||
|
||||
### Computing ipfs-hash of the build
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
"rules": {
|
||||
"cypress/unsafe-to-chain-command": 0
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/linter:eslint",
|
||||
"executor": "@nx/eslint:lint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
"options": {
|
||||
"lintFilePatterns": ["apps/explorer-e2e/**/*.{js,ts}"]
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/linter:eslint",
|
||||
"executor": "@nx/eslint:lint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
"options": {
|
||||
"lintFilePatterns": ["apps/explorer/**/*.{ts,tsx,js,jsx}"]
|
||||
@@ -63,14 +63,7 @@
|
||||
"executor": "@nx/jest:jest",
|
||||
"outputs": ["{workspaceRoot}/coverage/apps/explorer"],
|
||||
"options": {
|
||||
"jestConfig": "apps/explorer/jest.config.ts",
|
||||
"passWithNoTests": true
|
||||
},
|
||||
"configurations": {
|
||||
"ci": {
|
||||
"ci": true,
|
||||
"codeCoverage": true
|
||||
}
|
||||
"jestConfig": "apps/explorer/jest.config.ts"
|
||||
}
|
||||
},
|
||||
"generate-types": {
|
||||
|
||||
@@ -91,6 +91,6 @@ describe('Market link component', () => {
|
||||
};
|
||||
|
||||
const res = render(renderComponent('123', [mock]));
|
||||
expect(await res.findByText('123')).toBeInTheDocument();
|
||||
expect(await res.findByTitle('123')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,7 +17,7 @@ function renderAmendOrderDetails(
|
||||
mocks: MockedResponse[]
|
||||
) {
|
||||
return render(
|
||||
<MockedProvider mocks={mocks} addTypename={false}>
|
||||
<MockedProvider mocks={mocks}>
|
||||
<MemoryRouter>
|
||||
<AmendOrderDetails version={version} id={id} amend={amend} />
|
||||
</MemoryRouter>
|
||||
@@ -44,18 +44,18 @@ function renderExistingAmend(
|
||||
orderByID: {
|
||||
__typename: 'Order',
|
||||
id: '123',
|
||||
type: 'GTT',
|
||||
type: Schema.OrderType.TYPE_LIMIT,
|
||||
status: Schema.OrderStatus.STATUS_ACTIVE,
|
||||
version: version,
|
||||
version: version ? version.toString() : '',
|
||||
createdAt: '123',
|
||||
updatedAt: '456',
|
||||
expiresAt: '789',
|
||||
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
|
||||
price: '200',
|
||||
side: 'BUY',
|
||||
side: Schema.Side.SIDE_BUY,
|
||||
peggedOrder: null,
|
||||
remaining: '99',
|
||||
rejectionReason: 'rejection',
|
||||
rejectionReason: Schema.OrderRejectionReason.ORDER_ERROR_NOT_FOUND,
|
||||
reference: '123',
|
||||
size: '100',
|
||||
party: {
|
||||
@@ -65,18 +65,15 @@ function renderExistingAmend(
|
||||
market: {
|
||||
__typename: 'Market',
|
||||
id: '789',
|
||||
state: 'STATUS_ACTIVE',
|
||||
state: Schema.MarketState.STATE_ACTIVE,
|
||||
positionDecimalPlaces: 2,
|
||||
decimalPlaces: '5',
|
||||
decimalPlaces: 0,
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
name: 'test',
|
||||
product: {
|
||||
__typename: 'Future',
|
||||
quoteName: '123',
|
||||
settlementAsset: {
|
||||
decimals: 8,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -97,18 +94,18 @@ function renderExistingAmend(
|
||||
orderByID: {
|
||||
__typename: 'Order',
|
||||
id: '123',
|
||||
type: 'GTT',
|
||||
type: Schema.OrderType.TYPE_LIMIT,
|
||||
status: Schema.OrderStatus.STATUS_ACTIVE,
|
||||
version: 100,
|
||||
version: '100',
|
||||
createdAt: '123',
|
||||
updatedAt: '456',
|
||||
expiresAt: '789',
|
||||
timeInForce: Schema.OrderTimeInForce.TIME_IN_FORCE_GTC,
|
||||
peggedOrder: null,
|
||||
price: '200',
|
||||
side: 'BUY',
|
||||
side: Schema.Side.SIDE_BUY,
|
||||
remaining: '99',
|
||||
rejectionReason: 'rejection',
|
||||
rejectionReason: Schema.OrderRejectionReason.ORDER_ERROR_NOT_FOUND,
|
||||
reference: '123',
|
||||
size: '200',
|
||||
party: {
|
||||
@@ -117,19 +114,16 @@ function renderExistingAmend(
|
||||
},
|
||||
market: {
|
||||
__typename: 'Market',
|
||||
id: 'amend-to-order-latest-version',
|
||||
state: 'STATUS_ACTIVE',
|
||||
id: '8888',
|
||||
state: Schema.MarketState.STATE_ACTIVE,
|
||||
positionDecimalPlaces: 2,
|
||||
decimalPlaces: '5',
|
||||
decimalPlaces: 0,
|
||||
tradableInstrument: {
|
||||
instrument: {
|
||||
name: 'amend-to-order-latest-version-test',
|
||||
product: {
|
||||
__typename: 'Future',
|
||||
quoteName: '123',
|
||||
settlementAsset: {
|
||||
decimals: 8,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -142,13 +136,13 @@ function renderExistingAmend(
|
||||
request: {
|
||||
query: ExplorerMarketDocument,
|
||||
variables: {
|
||||
id: '789',
|
||||
id: '8888',
|
||||
},
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
market: {
|
||||
id: '789',
|
||||
id: '8888',
|
||||
decimalPlaces: 5,
|
||||
positionDecimalPlaces: 2,
|
||||
state: 'irrelevant-test-data',
|
||||
@@ -225,12 +219,10 @@ describe('Amend order details', () => {
|
||||
|
||||
it('Fetches latest version when version is not specified', async () => {
|
||||
const amend: Amend = {
|
||||
price: '-7879',
|
||||
price: '123',
|
||||
};
|
||||
|
||||
const res = renderExistingAmend('123', undefined, amend);
|
||||
expect(
|
||||
await res.findByText('amend-to-order-latest-version')
|
||||
).toBeInTheDocument();
|
||||
expect(await res.findByText('test-label')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -46,10 +46,10 @@ const AmendOrderDetails = ({ id, version, amend }: AmendOrderDetailsProps) => {
|
||||
<div className={wrapperClasses}>
|
||||
<div className="mb-12 lg:mb-0">
|
||||
<div className="relative block rounded-lg px-3 py-6 md:px-6 lg:-mr-7">
|
||||
<h2 className="text-3xl font-bold mb-4 display-5">
|
||||
<h2 className="display-5 mb-4 text-3xl font-bold">
|
||||
{t('Order not found')}
|
||||
</h2>
|
||||
<p className="text-gray-500 mb-12">
|
||||
<p className="mb-12 text-gray-500">
|
||||
{t('No order created from this transaction')}
|
||||
</p>
|
||||
</div>
|
||||
@@ -68,24 +68,24 @@ const AmendOrderDetails = ({ id, version, amend }: AmendOrderDetailsProps) => {
|
||||
<div className={wrapperClasses}>
|
||||
<div className="mb-12 lg:mb-0">
|
||||
<div className="relative block px-3 py-6 md:px-6 lg:-mr-7">
|
||||
<h2 className="text-3xl font-bold mb-4 display-5">
|
||||
<h2 className="display-5 mb-4 text-3xl font-bold">
|
||||
{t('Edits to ')}
|
||||
{sideText[o.side]}
|
||||
{t(' order')}
|
||||
</h2>
|
||||
<p className="text-gray-500 mb-4">
|
||||
<div className="mb-4 text-gray-500">
|
||||
In <MarketLink id={o.market.id} />, updated at{' '}
|
||||
<Time date={o.updatedAt} />.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-4 gap-x-6">
|
||||
<div className="grid gap-x-6 md:grid-cols-4">
|
||||
{amend.sizeDelta && amend.sizeDelta !== '0' ? (
|
||||
<div className="mb-12 md:mb-0">
|
||||
<h2 className="text-2xl font-bold text-dark mb-4">
|
||||
<h2 className="text-dark mb-4 text-2xl font-bold">
|
||||
{t('New size')}
|
||||
</h2>
|
||||
<h5
|
||||
className={`text-lg font-medium text-gray-500 mb-0 capitalize ${getSideDeltaColour(
|
||||
className={`mb-0 text-lg font-medium capitalize text-gray-500 ${getSideDeltaColour(
|
||||
amend.sizeDelta
|
||||
)}`}
|
||||
>
|
||||
@@ -96,10 +96,10 @@ const AmendOrderDetails = ({ id, version, amend }: AmendOrderDetailsProps) => {
|
||||
|
||||
{amend.price && amend.price !== '0' ? (
|
||||
<div className="">
|
||||
<h2 className="text-2xl font-bold text-dark mb-4">
|
||||
<h2 className="text-dark mb-4 text-2xl font-bold">
|
||||
{t('New price')}
|
||||
</h2>
|
||||
<h5 className="text-lg font-medium text-gray-500 mb-0">
|
||||
<h5 className="mb-0 text-lg font-medium text-gray-500">
|
||||
<PriceInMarket price={amend.price} marketId={o.market.id} />
|
||||
</h5>
|
||||
</div>
|
||||
@@ -108,10 +108,10 @@ const AmendOrderDetails = ({ id, version, amend }: AmendOrderDetailsProps) => {
|
||||
{amend.peggedReference &&
|
||||
amend.peggedReference !== 'PEGGED_REFERENCE_UNSPECIFIED' ? (
|
||||
<div className="">
|
||||
<h2 className="text-2xl font-bold text-dark mb-4">
|
||||
<h2 className="text-dark mb-4 text-2xl font-bold">
|
||||
{t('New reference')}
|
||||
</h2>
|
||||
<h5 className="text-lg font-medium text-gray-500 mb-0">
|
||||
<h5 className="mb-0 text-lg font-medium text-gray-500">
|
||||
{peggedReference[amend.peggedReference]}
|
||||
</h5>
|
||||
</div>
|
||||
@@ -119,10 +119,10 @@ const AmendOrderDetails = ({ id, version, amend }: AmendOrderDetailsProps) => {
|
||||
|
||||
{amend.peggedOffset ? (
|
||||
<div className="">
|
||||
<h2 className="text-2xl font-bold text-dark mb-4">
|
||||
<h2 className="text-dark mb-4 text-2xl font-bold">
|
||||
{t('New offset')}
|
||||
</h2>
|
||||
<h5 className="text-lg font-medium text-gray-500 mb-0">
|
||||
<h5 className="mb-0 text-lg font-medium text-gray-500">
|
||||
{amend.peggedOffset}
|
||||
</h5>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,11 @@
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"types": ["node"]
|
||||
"types": [
|
||||
"node",
|
||||
"@nx/react/typings/cssmodule.d.ts",
|
||||
"@nx/react/typings/image.d.ts"
|
||||
]
|
||||
},
|
||||
"files": [
|
||||
"../../node_modules/@nx/react/typings/cssmodule.d.ts",
|
||||
|
||||
@@ -3,7 +3,13 @@
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"module": "commonjs",
|
||||
"types": ["jest", "node", "@testing-library/jest-dom"]
|
||||
"types": [
|
||||
"jest",
|
||||
"node",
|
||||
"@testing-library/jest-dom",
|
||||
"@nx/react/typings/cssmodule.d.ts",
|
||||
"@nx/react/typings/image.d.ts"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"**/*.test.ts",
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
"rules": {
|
||||
"cypress/unsafe-to-chain-command": 0
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/linter:eslint",
|
||||
"executor": "@nx/eslint:lint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
"options": {
|
||||
"lintFilePatterns": ["apps/governance-e2e/**/*.{js,ts}"]
|
||||
|
||||
@@ -61,6 +61,7 @@ const marketProposalType = 'proposal-type';
|
||||
describe(
|
||||
'Governance flow for proposal details',
|
||||
{ tags: '@slow' },
|
||||
// @ts-ignore clash between jest and cypress
|
||||
function () {
|
||||
before('connect wallets and set approval limit', function () {
|
||||
cy.visit('/');
|
||||
@@ -78,6 +79,7 @@ describe(
|
||||
getProposalInformationFromTable('ID').invoke('text').as('parentMarketId');
|
||||
});
|
||||
|
||||
// @ts-ignore clash between jest and cypress
|
||||
beforeEach('visit proposals tab', function () {
|
||||
cy.clearLocalStorage();
|
||||
turnTelemetryOff();
|
||||
@@ -323,6 +325,7 @@ describe(
|
||||
});
|
||||
});
|
||||
cy.VegaWalletSubmitProposal(
|
||||
// @ts-ignore this is any
|
||||
createSuccessorMarketProposalTxBody(this.parentMarketId)
|
||||
);
|
||||
navigateTo(navigation.proposals);
|
||||
@@ -334,6 +337,7 @@ describe(
|
||||
cy.getByTestId(proposalTermsToggle).click();
|
||||
cy.get('.language-json').within(() => {
|
||||
cy.get('.hljs-attr').should('contain.text', 'parentMarketId');
|
||||
// @ts-ignore this is any
|
||||
cy.get('.hljs-string').should('contain.text', this.parentMarketId);
|
||||
cy.get('.hljs-attr').should('contain.text', 'insurancePoolFraction');
|
||||
cy.get('.hljs-string').should('contain.text', '0.75');
|
||||
@@ -352,6 +356,7 @@ describe(
|
||||
validateProposalDetailsDiff(
|
||||
'Parent Market ID',
|
||||
proposalChangeType.ADDED,
|
||||
// @ts-ignore this is any
|
||||
this.parentMarketId
|
||||
);
|
||||
validateProposalDetailsDiff(
|
||||
@@ -450,6 +455,7 @@ describe(
|
||||
const closingTimestamp = createTenDigitUnixTimeStampForSpecifiedDays(2);
|
||||
submitUniqueRawProposal({
|
||||
proposalBody: proposalPath,
|
||||
// @ts-ignore this is any
|
||||
updateMarketId: this.parentMarketId,
|
||||
enactmentTimestamp: enactmentTimestamp,
|
||||
closingTimestamp: closingTimestamp,
|
||||
@@ -466,6 +472,7 @@ describe(
|
||||
cy.getByTestId('proposal-update-market-state').within(() => {
|
||||
getProposalInformationFromTable('Market ID')
|
||||
.invoke('text')
|
||||
// @ts-ignore this is any
|
||||
.and('eq', this.parentMarketId);
|
||||
});
|
||||
});
|
||||
@@ -476,6 +483,7 @@ describe(
|
||||
const closingTimestamp = createTenDigitUnixTimeStampForSpecifiedDays(2);
|
||||
submitUniqueRawProposal({
|
||||
proposalBody: proposalPath,
|
||||
// @ts-ignore this is any
|
||||
updateMarketId: this.parentMarketId,
|
||||
enactmentTimestamp: enactmentTimestamp,
|
||||
closingTimestamp: closingTimestamp,
|
||||
@@ -489,6 +497,7 @@ describe(
|
||||
cy.getByTestId('proposal-update-market-state').within(() => {
|
||||
getProposalInformationFromTable('Market ID')
|
||||
.invoke('text')
|
||||
// @ts-ignore this is any
|
||||
.and('eq', this.parentMarketId);
|
||||
});
|
||||
});
|
||||
@@ -499,6 +508,7 @@ describe(
|
||||
const closingTimestamp = createTenDigitUnixTimeStampForSpecifiedDays(2);
|
||||
submitUniqueRawProposal({
|
||||
proposalBody: proposalPath,
|
||||
// @ts-ignore this is any
|
||||
updateMarketId: this.parentMarketId,
|
||||
enactmentTimestamp: enactmentTimestamp,
|
||||
closingTimestamp: closingTimestamp,
|
||||
@@ -518,6 +528,7 @@ describe(
|
||||
cy.getByTestId('proposal-update-market-state').within(() => {
|
||||
getProposalInformationFromTable('Market ID')
|
||||
.invoke('text')
|
||||
// @ts-ignore this is any
|
||||
.and('eq', this.parentMarketId);
|
||||
getProposalDetailsValue('Termination Price').should(
|
||||
'contain.text',
|
||||
|
||||
@@ -37,6 +37,7 @@ context(
|
||||
vegaWalletSetSpecifiedApprovalAmount('1000');
|
||||
});
|
||||
|
||||
// @ts-ignore clash between jest and cypress
|
||||
beforeEach('visit proposals', function () {
|
||||
cy.clearLocalStorage();
|
||||
turnTelemetryOff();
|
||||
|
||||
@@ -78,6 +78,7 @@ context(
|
||||
vegaWalletSetSpecifiedApprovalAmount('1000');
|
||||
});
|
||||
|
||||
// @ts-ignore clash between jest and cypress
|
||||
beforeEach('visit governance tab', function () {
|
||||
cy.clearLocalStorage();
|
||||
turnTelemetryOff();
|
||||
@@ -301,6 +302,7 @@ context(
|
||||
cy.getByTestId(voteButtons).should('not.exist');
|
||||
cy.getByTestId('min-proposal-requirements').should(
|
||||
'have.text',
|
||||
// @ts-ignore this is any
|
||||
`You must have at least ${this.minVoterBalance} VEGA associated to vote on this proposal`
|
||||
);
|
||||
});
|
||||
|
||||
@@ -70,6 +70,7 @@ context(
|
||||
vegaWalletSetSpecifiedApprovalAmount('1000');
|
||||
});
|
||||
|
||||
// @ts-ignore clash between jest and cypress
|
||||
beforeEach('visit governance tab', function () {
|
||||
cy.clearLocalStorage();
|
||||
turnTelemetryOff();
|
||||
@@ -216,6 +217,7 @@ context(
|
||||
// 3003-PMAN-001
|
||||
it(
|
||||
'Able to submit valid new market proposal',
|
||||
// @ts-ignore clash between jest and cypress
|
||||
{ tags: '@smoke' },
|
||||
function () {
|
||||
const proposalTitle = 'Test new market proposal';
|
||||
@@ -631,6 +633,7 @@ context(
|
||||
);
|
||||
cy.fixture('/proposals/successor-market').then((newMarketProposal) => {
|
||||
newMarketProposal.changes.successor.parentMarketId =
|
||||
// @ts-ignore clash between jest and cypress
|
||||
this.parentMarketId;
|
||||
const newMarketPayload = JSON.stringify(newMarketProposal);
|
||||
cy.getByTestId(newProposalTerms).type(newMarketPayload, {
|
||||
@@ -658,6 +661,7 @@ context(
|
||||
.should('have.text', 'Successor market to: TEST.24h')
|
||||
.find('a')
|
||||
.should('have.attr', 'href')
|
||||
// @ts-ignore clash between jest and cypress
|
||||
.and('contain', this.parentMarketId);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -33,12 +33,14 @@ const voteMajorityNotMet = 'token-majority-not-met';
|
||||
const voteMajorityMet = 'token-majority-met';
|
||||
const votesForPercentage = 'votes-for-percentage';
|
||||
|
||||
// @ts-ignore clash between jest and cypress
|
||||
describe('Governance flow for proposal list', { tags: '@slow' }, function () {
|
||||
before('connect wallets and set approval limit', function () {
|
||||
vegaWalletSetSpecifiedApprovalAmount('1000');
|
||||
cy.visit('/');
|
||||
});
|
||||
|
||||
// @ts-ignore clash between jest and cypress
|
||||
beforeEach('visit proposals tab', function () {
|
||||
cy.clearLocalStorage();
|
||||
turnTelemetryOff();
|
||||
@@ -106,6 +108,7 @@ describe('Governance flow for proposal list', { tags: '@slow' }, function () {
|
||||
it('Newly created proposals list - shows open proposals in an open state', function () {
|
||||
// 3001-VOTE-004
|
||||
// 3001-VOTE-035
|
||||
// @ts-ignore clash between jest and cypress
|
||||
createRawProposal(this.minProposerBalance);
|
||||
cy.get<testFreeformProposal>('@rawProposal').then((rawProposal) => {
|
||||
getProposalFromTitle(rawProposal.rationale.title).within(() => {
|
||||
|
||||
@@ -64,6 +64,7 @@ context(
|
||||
|
||||
describe('Eth wallet - contains VEGA tokens', function () {
|
||||
beforeEach(
|
||||
// @ts-ignore clash between jest and cypress
|
||||
'teardown wallet & drill into a specific validator',
|
||||
function () {
|
||||
cy.clearLocalStorage();
|
||||
@@ -237,6 +238,7 @@ context(
|
||||
// 1002-STKE-041 1002-STKE-053
|
||||
it(
|
||||
'Able to remove part of a stake against a validator',
|
||||
// @ts-ignore clash between jest and cypress
|
||||
{ tags: '@smoke' },
|
||||
function () {
|
||||
ensureSpecifiedUnstakedTokensAreAssociated('4');
|
||||
|
||||
@@ -52,6 +52,7 @@ context(
|
||||
|
||||
describe('Eth wallet - contains VEGA tokens', function () {
|
||||
beforeEach(
|
||||
// @ts-ignore clash between jest and cypress
|
||||
'teardown wallet & drill into a specific validator',
|
||||
function () {
|
||||
cy.clearLocalStorage();
|
||||
@@ -67,6 +68,7 @@ context(
|
||||
|
||||
it(
|
||||
'Able to associate tokens - from wallet',
|
||||
// @ts-ignore clash between jest and cypress
|
||||
{ tags: '@smoke' },
|
||||
function () {
|
||||
//1004-ASSO-003
|
||||
|
||||
@@ -48,6 +48,7 @@ context(
|
||||
depositAsset(usdcEthAddress, '1000', 5);
|
||||
});
|
||||
|
||||
// @ts-ignore clash between jest and cypress
|
||||
beforeEach('Navigate to withdrawal page', function () {
|
||||
cy.clearLocalStorage();
|
||||
turnTelemetryOff();
|
||||
@@ -101,6 +102,7 @@ context(
|
||||
// eslint-disable-next-line
|
||||
it.skip(
|
||||
'Able to withdraw asset: -eth wallet connected -withdraw funds button',
|
||||
// @ts-ignore clash between jest and cypress
|
||||
{ tags: '@smoke' },
|
||||
function () {
|
||||
// fill in withdrawal form
|
||||
|
||||
@@ -25,6 +25,7 @@ context('View functionality with public key', { tags: '@smoke' }, function () {
|
||||
);
|
||||
});
|
||||
|
||||
// @ts-ignore clash between jest and cypress
|
||||
beforeEach('visit home page', function () {
|
||||
cy.clearLocalStorage();
|
||||
turnTelemetryOff();
|
||||
|
||||
@@ -52,6 +52,7 @@ context(
|
||||
.invoke('text')
|
||||
.then(($newPageNumber) => {
|
||||
const newPageNumber = Number($newPageNumber.slice(5));
|
||||
// @ts-ignore clash between jest and cypress
|
||||
expect(newPageNumber).to.be.greaterThan(currentPageNumber);
|
||||
cy.getByTestId('goto-previous-page').click();
|
||||
cy.getByTestId('page-info').should(
|
||||
|
||||
@@ -44,6 +44,7 @@ context('Validators Page - verify elements on page', function () {
|
||||
cy.mockChainId();
|
||||
});
|
||||
|
||||
// @ts-ignore clash between jest and cypress
|
||||
describe('with wallets disconnected', { tags: '@smoke' }, function () {
|
||||
it('Should have validators tab highlighted', function () {
|
||||
verifyTabHighlighted(navigation.validators);
|
||||
@@ -76,6 +77,7 @@ context('Validators Page - verify elements on page', function () {
|
||||
describe(
|
||||
'Should be able to see validator list from the staking page',
|
||||
{ tags: '@regression' },
|
||||
// @ts-ignore clash between jest and cypress
|
||||
function () {
|
||||
// 1002-STKE-050
|
||||
it('Should be able to see validator names', function () {
|
||||
@@ -180,6 +182,7 @@ context('Validators Page - verify elements on page', function () {
|
||||
describe(
|
||||
'Should be able to see static information about a validator',
|
||||
{ tags: '@smoke' },
|
||||
// @ts-ignore clash between jest and cypress
|
||||
function () {
|
||||
before('connect wallets and click on validator', function () {
|
||||
cy.mockChainId();
|
||||
|
||||
@@ -198,6 +198,7 @@ context(
|
||||
});
|
||||
})
|
||||
.then(function () {
|
||||
// @ts-ignore clash between jest and cypress
|
||||
expect(parseFloat(this.value).toFixed(1)).to.equal(
|
||||
(Math.round((this.locked + this.unlocked) * 100) / 100).toFixed(
|
||||
1
|
||||
@@ -270,6 +271,7 @@ context(
|
||||
});
|
||||
})
|
||||
.then(function () {
|
||||
// @ts-ignore clash between jest and cypress
|
||||
expect(this.value).to.equal(this.locked + this.unlocked);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -105,6 +105,7 @@ context(
|
||||
// 0002-WCON-008
|
||||
it(
|
||||
'should have truncated account number visible',
|
||||
// @ts-ignore clash between jest and cypress
|
||||
{ tags: '@smoke' },
|
||||
function () {
|
||||
cy.get(walletContainer).within(() => {
|
||||
@@ -125,6 +126,7 @@ context(
|
||||
|
||||
it(
|
||||
'should have Vega Associated currency value visible',
|
||||
// @ts-ignore clash between jest and cypress
|
||||
{ tags: '@smoke' },
|
||||
function () {
|
||||
cy.get(walletContainer).within(() => {
|
||||
@@ -135,14 +137,19 @@ context(
|
||||
}
|
||||
);
|
||||
|
||||
it('should have Unstaked value visible', { tags: '@smoke' }, function () {
|
||||
cy.get(walletContainer).within(() => {
|
||||
cy.get(vegaUnstaked)
|
||||
.should('be.visible')
|
||||
.invoke('text')
|
||||
.and('not.be.empty');
|
||||
});
|
||||
});
|
||||
it(
|
||||
'should have Unstaked value visible',
|
||||
// @ts-ignore clash between jest and cypress
|
||||
{ tags: '@smoke' },
|
||||
function () {
|
||||
cy.get(walletContainer).within(() => {
|
||||
cy.get(vegaUnstaked)
|
||||
.should('be.visible')
|
||||
.invoke('text')
|
||||
.and('not.be.empty');
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
it('should have Governance button visible', function () {
|
||||
cy.get(walletContainer).within(() => {
|
||||
@@ -299,6 +306,7 @@ context(
|
||||
.siblings(txTimeout)
|
||||
.should((elementAmount) => {
|
||||
const displayedAmount = parseFloat(elementAmount.text());
|
||||
// @ts-ignore clash between jest and cypress
|
||||
expect(displayedAmount).be.gte(expectedAmount);
|
||||
});
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/linter:eslint",
|
||||
"executor": "@nx/eslint:lint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
"options": {
|
||||
"lintFilePatterns": ["apps/governance/**/*.{ts,tsx,js,jsx}"]
|
||||
@@ -66,14 +66,7 @@
|
||||
"executor": "@nx/jest:jest",
|
||||
"outputs": ["{workspaceRoot}/coverage/apps/governance"],
|
||||
"options": {
|
||||
"jestConfig": "apps/governance/jest.config.ts",
|
||||
"passWithNoTests": true
|
||||
},
|
||||
"configurations": {
|
||||
"ci": {
|
||||
"ci": true,
|
||||
"codeCoverage": true
|
||||
}
|
||||
"jestConfig": "apps/governance/jest.config.ts"
|
||||
}
|
||||
},
|
||||
"build-netlify": {
|
||||
|
||||
@@ -2,7 +2,11 @@
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"types": ["node"]
|
||||
"types": [
|
||||
"node",
|
||||
"@nx/react/typings/cssmodule.d.ts",
|
||||
"@nx/react/typings/image.d.ts"
|
||||
]
|
||||
},
|
||||
"files": [
|
||||
"../../node_modules/@nx/react/typings/cssmodule.d.ts",
|
||||
|
||||
@@ -3,7 +3,13 @@
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"module": "commonjs",
|
||||
"types": ["jest", "node", "@testing-library/jest-dom"]
|
||||
"types": [
|
||||
"jest",
|
||||
"node",
|
||||
"@testing-library/jest-dom",
|
||||
"@nx/react/typings/cssmodule.d.ts",
|
||||
"@nx/react/typings/image.d.ts"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"**/*.test.ts",
|
||||
|
||||
@@ -3,20 +3,20 @@
|
||||
|
||||
# Netlify Environment Variables
|
||||
# https://www.netlify.com/docs/continuous-deployment/#environment-variables
|
||||
NX_VERSION=$npm_package_version
|
||||
NX_REPOSITORY_URL=$REPOSITORY_URL
|
||||
NX_BRANCH=$BRANCH
|
||||
NX_PULL_REQUEST=$PULL_REQUEST
|
||||
NX_HEAD=$HEAD
|
||||
NX_COMMIT_REF=$COMMIT_REF
|
||||
NX_CONTEXT=$CONTEXT
|
||||
NX_REVIEW_ID=$REVIEW_ID
|
||||
NX_INCOMING_HOOK_TITLE=$INCOMING_HOOK_TITLE
|
||||
NX_INCOMING_HOOK_URL=$INCOMING_HOOK_URL
|
||||
NX_INCOMING_HOOK_BODY=$INCOMING_HOOK_BODY
|
||||
NX_URL=$URL
|
||||
NX_DEPLOY_URL=$DEPLOY_URL
|
||||
NX_DEPLOY_PRIME_URL=$DEPLOY_PRIME_URL
|
||||
NX_VERSION=\$npm_package_version
|
||||
NX_REPOSITORY_URL=\$REPOSITORY_URL
|
||||
NX_BRANCH=\$BRANCH
|
||||
NX_PULL_REQUEST=\$PULL_REQUEST
|
||||
NX_HEAD=\$HEAD
|
||||
NX_COMMIT_REF=\$COMMIT_REF
|
||||
NX_CONTEXT=\$CONTEXT
|
||||
NX_REVIEW_ID=\$REVIEW_ID
|
||||
NX_INCOMING_HOOK_TITLE=\$INCOMING_HOOK_TITLE
|
||||
NX_INCOMING_HOOK_URL=\$INCOMING_HOOK_URL
|
||||
NX_INCOMING_HOOK_BODY=\$INCOMING_HOOK_BODY
|
||||
NX_URL=\$URL
|
||||
NX_DEPLOY_URL=\$DEPLOY_URL
|
||||
NX_DEPLOY_PRIME_URL=\$DEPLOY_PRIME_URL
|
||||
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/fairground/vegawallet-fairground.toml
|
||||
NX_VEGA_ENV = 'TESTNET'
|
||||
NX_VEGA_URL="https://api.n07.testnet.vega.xyz/graphql"
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/linter:eslint",
|
||||
"executor": "@nx/eslint:lint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
"options": {
|
||||
"lintFilePatterns": [
|
||||
@@ -79,14 +79,7 @@
|
||||
"{workspaceRoot}/coverage/apps/liquidity-provision-dashboard"
|
||||
],
|
||||
"options": {
|
||||
"jestConfig": "apps/liquidity-provision-dashboard/jest.config.ts",
|
||||
"passWithNoTests": true
|
||||
},
|
||||
"configurations": {
|
||||
"ci": {
|
||||
"ci": true,
|
||||
"codeCoverage": true
|
||||
}
|
||||
"jestConfig": "apps/liquidity-provision-dashboard/jest.config.ts"
|
||||
}
|
||||
},
|
||||
"build-netlify": {
|
||||
|
||||
@@ -2,7 +2,11 @@
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"types": ["node"]
|
||||
"types": [
|
||||
"node",
|
||||
"@nx/react/typings/cssmodule.d.ts",
|
||||
"@nx/react/typings/image.d.ts"
|
||||
]
|
||||
},
|
||||
"files": [
|
||||
"../../node_modules/@nx/react/typings/cssmodule.d.ts",
|
||||
|
||||
@@ -3,7 +3,13 @@
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"module": "commonjs",
|
||||
"types": ["jest", "node", "@testing-library/jest-dom"],
|
||||
"types": [
|
||||
"jest",
|
||||
"node",
|
||||
"@testing-library/jest-dom",
|
||||
"@nx/react/typings/cssmodule.d.ts",
|
||||
"@nx/react/typings/image.d.ts"
|
||||
],
|
||||
"jsx": "react",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/linter:eslint",
|
||||
"executor": "@nx/eslint:lint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
"options": {
|
||||
"lintFilePatterns": ["apps/multisig-signer/**/*.{ts,tsx,js,jsx}"]
|
||||
@@ -63,14 +63,7 @@
|
||||
"executor": "@nx/jest:jest",
|
||||
"outputs": ["{workspaceRoot}/coverage/apps/multisig-signer"],
|
||||
"options": {
|
||||
"jestConfig": "apps/multisig-signer/jest.config.ts",
|
||||
"passWithNoTests": true
|
||||
},
|
||||
"configurations": {
|
||||
"ci": {
|
||||
"ci": true,
|
||||
"codeCoverage": true
|
||||
}
|
||||
"jestConfig": "apps/multisig-signer/jest.config.ts"
|
||||
}
|
||||
},
|
||||
"build-netlify": {
|
||||
|
||||
@@ -2,7 +2,11 @@
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"types": ["node"]
|
||||
"types": [
|
||||
"node",
|
||||
"@nx/react/typings/cssmodule.d.ts",
|
||||
"@nx/react/typings/image.d.ts"
|
||||
]
|
||||
},
|
||||
"files": [
|
||||
"../../node_modules/@nx/react/typings/cssmodule.d.ts",
|
||||
|
||||
@@ -3,7 +3,13 @@
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"module": "commonjs",
|
||||
"types": ["jest", "node", "@testing-library/jest-dom"]
|
||||
"types": [
|
||||
"jest",
|
||||
"node",
|
||||
"@testing-library/jest-dom",
|
||||
"@nx/react/typings/cssmodule.d.ts",
|
||||
"@nx/react/typings/image.d.ts"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"**/*.test.ts",
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
"rules": {
|
||||
"cypress/unsafe-to-chain-command": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["src/plugins/index.js"],
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/linter:eslint",
|
||||
"executor": "@nx/eslint:lint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
"options": {
|
||||
"lintFilePatterns": ["apps/trading-e2e/**/*.{js,ts}"]
|
||||
|
||||
@@ -26,7 +26,7 @@ describe('ethereum wallet', { tags: '@smoke', testIsolation: true }, () => {
|
||||
cy.getByTestId('tab-deposits').should('not.be.empty');
|
||||
});
|
||||
|
||||
it('should see QR code modal for WalletConnect', () => {
|
||||
it.skip('should see QR code modal for WalletConnect', () => {
|
||||
// 0004-EWAL-003
|
||||
|
||||
cy.getByTestId('Deposits').click();
|
||||
@@ -35,7 +35,7 @@ describe('ethereum wallet', { tags: '@smoke', testIsolation: true }, () => {
|
||||
cy.getByTestId('web3-connector-list').should('exist');
|
||||
cy.getByTestId('web3-connector-WalletConnect').click();
|
||||
// testing if exists rather than visible because of the long loading time
|
||||
cy.get('w3m-modal').should('exist');
|
||||
cy.get('#w3m-modal').should('exist');
|
||||
});
|
||||
|
||||
it('able to disconnect eth wallet', () => {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"next",
|
||||
"next/core-web-vitals"
|
||||
],
|
||||
"ignorePatterns": ["!**/*", "__generated__", ".next"],
|
||||
"ignorePatterns": ["!**/*", "__generated__", ".next", "setup-tests.ts"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
||||
|
||||
@@ -36,6 +36,22 @@ query Fees(
|
||||
}
|
||||
}
|
||||
}
|
||||
referrer: referralSets(referrer: $partyId) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
referrer
|
||||
}
|
||||
}
|
||||
}
|
||||
referee: referralSets(referee: $partyId) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
referrer
|
||||
}
|
||||
}
|
||||
}
|
||||
referralSetReferees(referee: $partyId) {
|
||||
edges {
|
||||
node {
|
||||
|
||||
+17
-1
@@ -15,7 +15,7 @@ export type FeesQueryVariables = Types.Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type FeesQuery = { __typename?: 'Query', epoch: { __typename?: 'Epoch', id: string }, volumeDiscountStats: { __typename?: 'VolumeDiscountStatsConnection', edges: Array<{ __typename?: 'VolumeDiscountStatsEdge', node: { __typename?: 'VolumeDiscountStats', atEpoch: number, discountFactor: string, runningVolume: string } } | null> }, referralSetReferees: { __typename?: 'ReferralSetRefereeConnection', edges: Array<{ __typename?: 'ReferralSetRefereeEdge', node: { __typename?: 'ReferralSetReferee', atEpoch: number } } | null> }, referralSetStats: { __typename?: 'ReferralSetStatsConnection', edges: Array<{ __typename?: 'ReferralSetStatsEdge', node: { __typename?: 'ReferralSetStats', atEpoch: number, discountFactor: string, referralSetRunningNotionalTakerVolume: string } } | null> } };
|
||||
export type FeesQuery = { __typename?: 'Query', epoch: { __typename?: 'Epoch', id: string }, volumeDiscountStats: { __typename?: 'VolumeDiscountStatsConnection', edges: Array<{ __typename?: 'VolumeDiscountStatsEdge', node: { __typename?: 'VolumeDiscountStats', atEpoch: number, discountFactor: string, runningVolume: string } } | null> }, referrer: { __typename?: 'ReferralSetConnection', edges: Array<{ __typename?: 'ReferralSetEdge', node: { __typename?: 'ReferralSet', id: string, referrer: string } } | null> }, referee: { __typename?: 'ReferralSetConnection', edges: Array<{ __typename?: 'ReferralSetEdge', node: { __typename?: 'ReferralSet', id: string, referrer: string } } | null> }, referralSetReferees: { __typename?: 'ReferralSetRefereeConnection', edges: Array<{ __typename?: 'ReferralSetRefereeEdge', node: { __typename?: 'ReferralSetReferee', atEpoch: number } } | null> }, referralSetStats: { __typename?: 'ReferralSetStatsConnection', edges: Array<{ __typename?: 'ReferralSetStatsEdge', node: { __typename?: 'ReferralSetStats', atEpoch: number, discountFactor: string, referralSetRunningNotionalTakerVolume: string } } | null> } };
|
||||
|
||||
|
||||
export const DiscountProgramsDocument = gql`
|
||||
@@ -81,6 +81,22 @@ export const FeesDocument = gql`
|
||||
}
|
||||
}
|
||||
}
|
||||
referrer: referralSets(referrer: $partyId) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
referrer
|
||||
}
|
||||
}
|
||||
}
|
||||
referee: referralSets(referee: $partyId) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
referrer
|
||||
}
|
||||
}
|
||||
}
|
||||
referralSetReferees(referee: $partyId) {
|
||||
edges {
|
||||
node {
|
||||
|
||||
@@ -17,6 +17,14 @@ import { useReferralStats } from './use-referral-stats';
|
||||
import { formatPercentage, getAdjustedFee } from './utils';
|
||||
import { Table, Td, Th, THead, Tr } from './table';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { Links } from '../../lib/links';
|
||||
import { Link } from 'react-router-dom';
|
||||
import {
|
||||
Tooltip,
|
||||
VegaIcon,
|
||||
VegaIconNames,
|
||||
truncateMiddle,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
export const FeesContainer = () => {
|
||||
const { pubKey } = useVegaWallet();
|
||||
@@ -56,16 +64,25 @@ export const FeesContainer = () => {
|
||||
referralTierIndex,
|
||||
referralTiers,
|
||||
epochsInSet,
|
||||
code,
|
||||
isReferrer,
|
||||
} = useReferralStats(
|
||||
feesData?.referralSetStats,
|
||||
feesData?.referralSetReferees,
|
||||
programData?.currentReferralProgram,
|
||||
feesData?.epoch
|
||||
feesData?.epoch,
|
||||
feesData?.referrer,
|
||||
feesData?.referee
|
||||
);
|
||||
|
||||
const loading = paramsLoading || feesLoading || programLoading;
|
||||
const isConnected = Boolean(pubKey);
|
||||
|
||||
const isReferralProgramRunning = Boolean(programData?.currentReferralProgram);
|
||||
const isVolumeDiscountProgramRunning = Boolean(
|
||||
programData?.currentVolumeDiscountProgram
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="grid auto-rows-min grid-cols-4 gap-3">
|
||||
{isConnected && (
|
||||
@@ -90,6 +107,8 @@ export const FeesContainer = () => {
|
||||
<TotalDiscount
|
||||
referralDiscount={referralDiscount}
|
||||
volumeDiscount={volumeDiscount}
|
||||
isReferralProgramRunning={isReferralProgramRunning}
|
||||
isVolumeDiscountProgramRunning={isVolumeDiscountProgramRunning}
|
||||
/>
|
||||
</FeeCard>
|
||||
<FeeCard
|
||||
@@ -97,23 +116,37 @@ export const FeesContainer = () => {
|
||||
className="sm:col-span-2"
|
||||
loading={loading}
|
||||
>
|
||||
<CurrentVolume
|
||||
tiers={volumeTiers}
|
||||
tierIndex={volumeTierIndex}
|
||||
windowLengthVolume={volumeInWindow}
|
||||
windowLength={volumeDiscountWindowLength}
|
||||
/>
|
||||
{isVolumeDiscountProgramRunning ? (
|
||||
<CurrentVolume
|
||||
tiers={volumeTiers}
|
||||
tierIndex={volumeTierIndex}
|
||||
windowLengthVolume={volumeInWindow}
|
||||
windowLength={volumeDiscountWindowLength}
|
||||
/>
|
||||
) : (
|
||||
<p className="pt-3 text-sm text-muted">
|
||||
{t('No volume discount program active')}
|
||||
</p>
|
||||
)}
|
||||
</FeeCard>
|
||||
<FeeCard
|
||||
title={t('Referral benefits')}
|
||||
className="sm:col-span-2"
|
||||
loading={loading}
|
||||
>
|
||||
<ReferralBenefits
|
||||
setRunningNotionalTakerVolume={referralVolumeInWindow}
|
||||
epochsInSet={epochsInSet}
|
||||
epochs={referralDiscountWindowLength}
|
||||
/>
|
||||
{isReferrer ? (
|
||||
<ReferrerInfo code={code} />
|
||||
) : isReferralProgramRunning ? (
|
||||
<ReferralBenefits
|
||||
setRunningNotionalTakerVolume={referralVolumeInWindow}
|
||||
epochsInSet={epochsInSet}
|
||||
epochs={referralDiscountWindowLength}
|
||||
/>
|
||||
) : (
|
||||
<p className="pt-3 text-sm text-muted">
|
||||
{t('No referral program active')}
|
||||
</p>
|
||||
)}
|
||||
</FeeCard>
|
||||
</>
|
||||
)}
|
||||
@@ -142,7 +175,7 @@ export const FeesContainer = () => {
|
||||
/>
|
||||
</FeeCard>
|
||||
<FeeCard
|
||||
title={t('Liquidity fees')}
|
||||
title={t('Fees by market')}
|
||||
className="lg:col-span-full"
|
||||
loading={marketsLoading}
|
||||
>
|
||||
@@ -325,26 +358,64 @@ const ReferralBenefits = ({
|
||||
const TotalDiscount = ({
|
||||
referralDiscount,
|
||||
volumeDiscount,
|
||||
isReferralProgramRunning,
|
||||
isVolumeDiscountProgramRunning,
|
||||
}: {
|
||||
referralDiscount: number;
|
||||
volumeDiscount: number;
|
||||
isReferralProgramRunning: boolean;
|
||||
isVolumeDiscountProgramRunning: boolean;
|
||||
}) => {
|
||||
const totalDiscount = 1 - (1 - volumeDiscount) * (1 - referralDiscount);
|
||||
const totalDiscountDescription = t(
|
||||
'The total discount is calculated according to the following formula: '
|
||||
);
|
||||
const formula = (
|
||||
<span className="italic">
|
||||
1 - (1 - d<sub>volume</sub>) ⋇ (1 - d<sub>referral</sub>)
|
||||
</span>
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Stat
|
||||
value={formatPercentage(referralDiscount + volumeDiscount) + '%'}
|
||||
description={
|
||||
<>
|
||||
{totalDiscountDescription}
|
||||
{formula}
|
||||
</>
|
||||
}
|
||||
value={formatPercentage(totalDiscount) + '%'}
|
||||
highlight={true}
|
||||
/>
|
||||
<table className="w-full mt-0.5 text-xs text-muted">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th className="font-normal text-left">{t('Volume discount')}</th>
|
||||
<td className="text-right">{formatPercentage(volumeDiscount)}%</td>
|
||||
<td className="text-right">
|
||||
{formatPercentage(volumeDiscount)}%
|
||||
{!isVolumeDiscountProgramRunning && (
|
||||
<Tooltip description={t('No active volume discount programme')}>
|
||||
<span className="cursor-help">
|
||||
{' '}
|
||||
<VegaIcon name={VegaIconNames.INFO} size={12} />
|
||||
</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th className="font-normal text-left ">{t('Referral discount')}</th>
|
||||
<td className="text-right">
|
||||
{formatPercentage(referralDiscount)}%
|
||||
{!isReferralProgramRunning && (
|
||||
<Tooltip description={t('No active referral programme')}>
|
||||
<span className="cursor-help">
|
||||
{' '}
|
||||
<VegaIcon name={VegaIconNames.INFO} size={12} />
|
||||
</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -491,3 +562,31 @@ const YourTier = () => {
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
const ReferrerInfo = ({ code }: { code?: string }) => (
|
||||
<div className="pt-3 text-sm text-vega-clight-200 dark:vega-cdark-200">
|
||||
<p className="mb-1">
|
||||
{t('Connected key is owner of the referral set')}
|
||||
{code && (
|
||||
<>
|
||||
{' '}
|
||||
<span className="text-transparent bg-rainbow bg-clip-text">
|
||||
{truncateMiddle(code)}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
{'. '}
|
||||
{t('As owner, it is eligible for commission not fee discounts.')}
|
||||
</p>
|
||||
<p>
|
||||
{t('See')}{' '}
|
||||
<Link
|
||||
className="underline text-black dark:text-white"
|
||||
to={Links.REFERRALS()}
|
||||
>
|
||||
{t('Referrals')}
|
||||
</Link>{' '}
|
||||
{t('for more information.')}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,23 +1,31 @@
|
||||
import { Tooltip } from '@vegaprotocol/ui-toolkit';
|
||||
import classNames from 'classnames';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export const Stat = ({
|
||||
value,
|
||||
text,
|
||||
highlight,
|
||||
description,
|
||||
}: {
|
||||
value: string | number;
|
||||
text?: string;
|
||||
highlight?: boolean;
|
||||
description?: ReactNode;
|
||||
}) => {
|
||||
const val = (
|
||||
<span
|
||||
className={classNames('inline-block text-3xl leading-none', {
|
||||
'text-transparent bg-rainbow bg-clip-text': highlight,
|
||||
'cursor-help': description,
|
||||
})}
|
||||
>
|
||||
{value}
|
||||
</span>
|
||||
);
|
||||
return (
|
||||
<p className="pt-3 leading-none first:pt-6">
|
||||
<span
|
||||
className={classNames('inline-block text-3xl leading-none', {
|
||||
'text-transparent bg-rainbow bg-clip-text': highlight,
|
||||
})}
|
||||
>
|
||||
{value}
|
||||
</span>
|
||||
{description ? <Tooltip description={description}>{val}</Tooltip> : val}
|
||||
{text && (
|
||||
<small className="block mt-0.5 text-xs text-muted">{text}</small>
|
||||
)}
|
||||
|
||||
@@ -73,6 +73,8 @@ describe('useReferralStats', () => {
|
||||
referralTierIndex: -1,
|
||||
referralTiers: [],
|
||||
epochsInSet: 0,
|
||||
code: undefined,
|
||||
isReferrer: false,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -93,6 +95,8 @@ describe('useReferralStats', () => {
|
||||
referralTierIndex: 1,
|
||||
referralTiers: program.benefitTiers,
|
||||
epochsInSet: Number(epoch.id) - set.atEpoch,
|
||||
code: undefined,
|
||||
isReferrer: false,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -2,12 +2,15 @@ import compact from 'lodash/compact';
|
||||
import maxBy from 'lodash/maxBy';
|
||||
import { getReferralBenefitTier } from './utils';
|
||||
import type { DiscountProgramsQuery, FeesQuery } from './__generated__/Fees';
|
||||
import { first } from 'lodash';
|
||||
|
||||
export const useReferralStats = (
|
||||
setStats?: FeesQuery['referralSetStats'],
|
||||
setReferees?: FeesQuery['referralSetReferees'],
|
||||
program?: DiscountProgramsQuery['currentReferralProgram'],
|
||||
epoch?: FeesQuery['epoch']
|
||||
epoch?: FeesQuery['epoch'],
|
||||
setIfReferrer?: FeesQuery['referrer'],
|
||||
setIfReferee?: FeesQuery['referee']
|
||||
) => {
|
||||
const referralTiers = program?.benefitTiers || [];
|
||||
|
||||
@@ -18,9 +21,18 @@ export const useReferralStats = (
|
||||
referralTierIndex: -1,
|
||||
referralTiers,
|
||||
epochsInSet: 0,
|
||||
code: undefined,
|
||||
isReferrer: false,
|
||||
};
|
||||
}
|
||||
|
||||
const setIfReferrerData = first(
|
||||
compact(setIfReferrer?.edges).map((e) => e.node)
|
||||
);
|
||||
const setIfRefereeData = first(
|
||||
compact(setIfReferee?.edges).map((e) => e.node)
|
||||
);
|
||||
|
||||
const referralSetsStats = compact(setStats.edges).map((e) => e.node);
|
||||
const referralSets = compact(setReferees.edges).map((e) => e.node);
|
||||
|
||||
@@ -48,5 +60,7 @@ export const useReferralStats = (
|
||||
referralTierIndex,
|
||||
referralTiers,
|
||||
epochsInSet,
|
||||
code: (setIfReferrerData || setIfRefereeData)?.id,
|
||||
isReferrer: Boolean(setIfReferrerData),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -21,7 +21,7 @@ describe('getAdjustedFee', () => {
|
||||
new BigNumber(referralDiscount),
|
||||
];
|
||||
|
||||
// 1 - 0.5 - 0.5
|
||||
// 1 - 0.5 = 0.5
|
||||
const v = new BigNumber(1).minus(new BigNumber(volumeDiscount));
|
||||
|
||||
// 1 - 0.5 = 0.5
|
||||
@@ -34,13 +34,15 @@ describe('getAdjustedFee', () => {
|
||||
// 0.1 + 0.1 + 0.1 = 0.3
|
||||
const totalFees = fees.reduce((sum, x) => sum.plus(x), new BigNumber(0));
|
||||
|
||||
// 0.3 * 0.75 = 0.225
|
||||
const expected = new BigNumber(totalFees).times(factor).toNumber();
|
||||
// (1 - 0.3) * 0.75 = 0.525
|
||||
const expected = new BigNumber(totalFees)
|
||||
.times(new BigNumber(1).minus(factor))
|
||||
.toNumber();
|
||||
|
||||
expect(getAdjustedFee(fees, discounts)).toBe(expected);
|
||||
});
|
||||
|
||||
it('combines discount factors multiplicativly', () => {
|
||||
it('combines discount factors multiplicatively', () => {
|
||||
const volumeDiscount = 0.4;
|
||||
const referralDiscount = 0.1;
|
||||
|
||||
@@ -67,7 +69,9 @@ describe('getAdjustedFee', () => {
|
||||
// summed fees
|
||||
const totalFees = fees.reduce((sum, x) => sum.plus(x), new BigNumber(0));
|
||||
|
||||
const expected = new BigNumber(totalFees).times(factor).toNumber();
|
||||
const expected = new BigNumber(totalFees)
|
||||
.times(new BigNumber(1).minus(factor))
|
||||
.toNumber();
|
||||
|
||||
expect(getAdjustedFee(fees, discounts)).toBe(expected);
|
||||
});
|
||||
|
||||
@@ -12,7 +12,9 @@ export const formatPercentage = (num: number) => {
|
||||
const pct = new BigNumber(num).times(100);
|
||||
const dps = pct.decimalPlaces();
|
||||
const formatter = new Intl.NumberFormat(getUserLocale(), {
|
||||
minimumFractionDigits: dps || 0,
|
||||
// set to 0 in order to remove the "trailing zeroes" for numbers such as:
|
||||
// 0.123456789 -non-zero-min-> 12.3456800% -zero-min-> 12.34568%
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: dps || 0,
|
||||
});
|
||||
return formatter.format(parseFloat(pct.toFixed(5)));
|
||||
@@ -101,5 +103,7 @@ export const getAdjustedFee = (fees: BigNumber[], discounts: BigNumber[]) => {
|
||||
|
||||
const totalFactor = new BigNumber(1).minus(combinedFactors);
|
||||
|
||||
return totalFee.times(BigNumber.max(0, totalFactor)).toNumber();
|
||||
return totalFee
|
||||
.times(new BigNumber(1).minus(BigNumber.max(0, totalFactor)))
|
||||
.toNumber();
|
||||
};
|
||||
|
||||
@@ -60,7 +60,7 @@ function AppBody({ Component }: AppProps) {
|
||||
const location = useLocation();
|
||||
const { VEGA_ENV } = useEnvironment();
|
||||
const gridClasses = classNames(
|
||||
'h-full relative z-0 grid',
|
||||
'grid relative h-full z-0',
|
||||
'grid-rows-[repeat(3,min-content),minmax(0,1fr)]'
|
||||
);
|
||||
return (
|
||||
|
||||
@@ -41,18 +41,11 @@
|
||||
"executor": "@nx/jest:jest",
|
||||
"outputs": ["{workspaceRoot}/coverage/apps/trading"],
|
||||
"options": {
|
||||
"jestConfig": "apps/trading/jest.config.ts",
|
||||
"passWithNoTests": true
|
||||
},
|
||||
"configurations": {
|
||||
"ci": {
|
||||
"ci": true,
|
||||
"codeCoverage": true
|
||||
}
|
||||
"jestConfig": "apps/trading/jest.config.ts"
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/linter:eslint",
|
||||
"executor": "@nx/eslint:lint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
"options": {
|
||||
"lintFilePatterns": ["apps/trading/**/*.{ts,tsx,js,jsx}"]
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#!/bin/bash -e
|
||||
yarn --pure-lockfile
|
||||
app=${1:-trading}
|
||||
envCmd="envCmd="yarn env-cmd -f ./apps/${app}/.env.${2:-mainnet}"
|
||||
envCmd="envCmd="yarn -f ./apps/${app}/.env.${2:-mainnet}"
|
||||
yarn install
|
||||
if [ "${app}" = "trading" ]; then
|
||||
$envCmd yarn nx export trading
|
||||
DIST_LOCATION=dist/apps/trading/exported
|
||||
DIST_LOCATION=dist/apps/trading/exported/
|
||||
else
|
||||
$envCmd yarn nx build ${app}
|
||||
DIST_LOCATION=dist/apps/${app}
|
||||
|
||||
+8
-1
@@ -1,3 +1,10 @@
|
||||
const nxPreset = require('@nx/jest/preset').default;
|
||||
|
||||
module.exports = { ...nxPreset };
|
||||
module.exports = {
|
||||
...nxPreset,
|
||||
moduleNameMapper: {
|
||||
...nxPreset.moduleNameMapper,
|
||||
// this mapping fixes jest breaking if anything tries to import d3 due to esm exports
|
||||
'^d3-(.*)$': 'd3-$1/dist/d3-$1',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"emitDecoratorMetadata": true,
|
||||
"outDir": ""
|
||||
},
|
||||
"files": [
|
||||
"../../../node_modules/@nx/react/typings/styled-jsx.d.ts",
|
||||
"../../../node_modules/@nx/react/typings/cssmodule.d.ts",
|
||||
"../../../node_modules/@nx/react/typings/image.d.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"../**/*.spec.ts",
|
||||
"../**/*.spec.js",
|
||||
"../**/*.spec.tsx",
|
||||
"../**/*.spec.jsx",
|
||||
"jest.config.ts"
|
||||
],
|
||||
"include": ["../src/**/*", "*.js"]
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
"tags": [],
|
||||
"targets": {
|
||||
"lint": {
|
||||
"executor": "@nx/linter:eslint",
|
||||
"executor": "@nx/eslint:lint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
"options": {
|
||||
"lintFilePatterns": ["libs/accounts/**/*.{ts,tsx,js,jsx}"]
|
||||
@@ -16,14 +16,7 @@
|
||||
"executor": "@nx/jest:jest",
|
||||
"outputs": ["{workspaceRoot}/coverage/libs/accounts"],
|
||||
"options": {
|
||||
"jestConfig": "libs/accounts/jest.config.ts",
|
||||
"passWithNoTests": true
|
||||
},
|
||||
"configurations": {
|
||||
"ci": {
|
||||
"ci": true,
|
||||
"codeCoverage": true
|
||||
}
|
||||
"jestConfig": "libs/accounts/jest.config.ts"
|
||||
}
|
||||
},
|
||||
"storybook": {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import sortBy from 'lodash/sortBy';
|
||||
import * as Schema from '@vegaprotocol/types';
|
||||
import { truncateByChars } from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import {
|
||||
NetworkParams,
|
||||
useNetworkParam,
|
||||
useNetworkParams,
|
||||
} from '@vegaprotocol/network-parameters';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import type { Transfer } from '@vegaprotocol/wallet';
|
||||
@@ -21,7 +22,11 @@ export const ALLOWED_ACCOUNTS = [
|
||||
|
||||
export const TransferContainer = ({ assetId }: { assetId?: string }) => {
|
||||
const { pubKey, pubKeys } = useVegaWallet();
|
||||
const { param } = useNetworkParam(NetworkParams.transfer_fee_factor);
|
||||
const { params } = useNetworkParams([
|
||||
NetworkParams.transfer_fee_factor,
|
||||
NetworkParams.transfer_minTransferQuantumMultiple,
|
||||
]);
|
||||
|
||||
const { data } = useDataProvider({
|
||||
dataProvider: accountsDataProvider,
|
||||
variables: { partyId: pubKey || '' },
|
||||
@@ -40,6 +45,7 @@ export const TransferContainer = ({ assetId }: { assetId?: string }) => {
|
||||
const accounts = data
|
||||
? data.filter((account) => ALLOWED_ACCOUNTS.includes(account.type))
|
||||
: [];
|
||||
const sortedAccounts = sortBy(accounts, (a) => a.asset.symbol.toLowerCase());
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -59,9 +65,10 @@ export const TransferContainer = ({ assetId }: { assetId?: string }) => {
|
||||
pubKey={pubKey}
|
||||
pubKeys={pubKeys ? pubKeys?.map((pk) => pk.publicKey) : null}
|
||||
assetId={assetId}
|
||||
feeFactor={param}
|
||||
feeFactor={params.transfer_fee_factor}
|
||||
minQuantumMultiple={params.transfer_minTransferQuantumMultiple}
|
||||
submitTransfer={transfer}
|
||||
accounts={accounts}
|
||||
accounts={sortedAccounts}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -9,18 +9,10 @@ import {
|
||||
} from './transfer-form';
|
||||
import { AccountType } from '@vegaprotocol/types';
|
||||
import { removeDecimal } from '@vegaprotocol/utils';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
|
||||
describe('TransferForm', () => {
|
||||
const renderComponent = (props: TransferFormProps) => {
|
||||
return render(
|
||||
// Wrap with mock provider as the form will make queries to fetch the selected
|
||||
// toVegaKey accounts. We don't test this for now but we need to wrap so that
|
||||
// the component has access to the client
|
||||
<MockedProvider>
|
||||
<TransferForm {...props} />
|
||||
</MockedProvider>
|
||||
);
|
||||
return render(<TransferForm {...props} />);
|
||||
};
|
||||
|
||||
const submit = async () => {
|
||||
@@ -54,6 +46,7 @@ describe('TransferForm', () => {
|
||||
symbol: '€',
|
||||
name: 'EUR',
|
||||
decimals: 2,
|
||||
quantum: '1',
|
||||
};
|
||||
const props = {
|
||||
pubKey,
|
||||
@@ -72,12 +65,31 @@ describe('TransferForm', () => {
|
||||
{
|
||||
type: AccountType.ACCOUNT_TYPE_VESTED_REWARDS,
|
||||
asset,
|
||||
balance: '100000',
|
||||
balance: '10000',
|
||||
},
|
||||
],
|
||||
minQuantumMultiple: '1',
|
||||
};
|
||||
|
||||
it('form tooltips correctly displayed', async () => {
|
||||
it.each([
|
||||
{
|
||||
targetText: 'Include transfer fee',
|
||||
tooltipText:
|
||||
'The fee will be taken from the amount you are transferring.',
|
||||
},
|
||||
{
|
||||
targetText: 'Transfer fee',
|
||||
tooltipText: /transfer\.fee\.factor/,
|
||||
},
|
||||
{
|
||||
targetText: 'Amount to be transferred',
|
||||
tooltipText: /without the fee/,
|
||||
},
|
||||
{
|
||||
targetText: 'Total amount (with fee)',
|
||||
tooltipText: /total amount taken from your account/,
|
||||
},
|
||||
])('Tooltip for "$targetText" shows', async (o) => {
|
||||
// 1003-TRAN-015
|
||||
// 1003-TRAN-016
|
||||
// 1003-TRAN-017
|
||||
@@ -98,32 +110,9 @@ describe('TransferForm', () => {
|
||||
await userEvent.type(amountInput, amount);
|
||||
expect(amountInput).toHaveValue(amount);
|
||||
|
||||
const includeTransferLabel = screen.getByText('Include transfer fee');
|
||||
await userEvent.hover(includeTransferLabel);
|
||||
expect(await screen.findByRole('tooltip')).toHaveTextContent(
|
||||
'The fee will be taken from the amount you are transferring.'
|
||||
);
|
||||
await userEvent.unhover(screen.getByText('Include transfer fee'));
|
||||
|
||||
const transferFee = screen.getByText('Transfer fee');
|
||||
await userEvent.hover(transferFee);
|
||||
expect(await screen.findByRole('tooltip')).toHaveTextContent(
|
||||
/transfer.fee.factor/
|
||||
);
|
||||
await userEvent.unhover(transferFee);
|
||||
|
||||
const amountToBeTransferred = screen.getByText('Amount to be transferred');
|
||||
await userEvent.hover(amountToBeTransferred);
|
||||
expect(await screen.findByRole('tooltip')).toHaveTextContent(
|
||||
/without the fee/
|
||||
);
|
||||
await userEvent.unhover(amountToBeTransferred);
|
||||
|
||||
const totalAmountWithFee = screen.getByText('Total amount (with fee)');
|
||||
await userEvent.hover(totalAmountWithFee);
|
||||
expect(await screen.findByRole('tooltip')).toHaveTextContent(
|
||||
/total amount taken from your account/
|
||||
);
|
||||
const label = screen.getByText(o.targetText);
|
||||
await userEvent.hover(label);
|
||||
expect(await screen.findByRole('tooltip')).toHaveTextContent(o.tooltipText);
|
||||
});
|
||||
|
||||
it('validates a manually entered address', async () => {
|
||||
@@ -132,7 +121,7 @@ describe('TransferForm', () => {
|
||||
// 1003-TRAN-004
|
||||
renderComponent(props);
|
||||
await submit();
|
||||
expect(await screen.findAllByText('Required')).toHaveLength(3); // pubkey is set as default value
|
||||
expect(await screen.findAllByText('Required')).toHaveLength(2); // pubkey is set as default value
|
||||
const toggle = screen.getByText('Enter manually');
|
||||
await userEvent.click(toggle);
|
||||
// has switched to input
|
||||
@@ -145,12 +134,12 @@ describe('TransferForm', () => {
|
||||
screen.getByLabelText('To Vega key'),
|
||||
'invalid-address'
|
||||
);
|
||||
expect(screen.getAllByTestId('input-error-text')[0]).toHaveTextContent(
|
||||
expect(screen.getAllByTestId('input-error-text')[1]).toHaveTextContent(
|
||||
'Invalid Vega key'
|
||||
);
|
||||
});
|
||||
|
||||
it('validates fields and submits', async () => {
|
||||
it('sends transfer from general accounts', async () => {
|
||||
// 1003-TRAN-002
|
||||
// 1003-TRAN-003
|
||||
// 1002-WITH-010
|
||||
@@ -168,7 +157,7 @@ describe('TransferForm', () => {
|
||||
]);
|
||||
|
||||
await submit();
|
||||
expect(await screen.findAllByText('Required')).toHaveLength(3); // pubkey is set as default value
|
||||
expect(await screen.findAllByText('Required')).toHaveLength(2); // pubkey is set as default value
|
||||
|
||||
// Select a pubkey
|
||||
await userEvent.selectOptions(
|
||||
@@ -181,15 +170,20 @@ describe('TransferForm', () => {
|
||||
|
||||
await userEvent.selectOptions(
|
||||
screen.getByLabelText('From account'),
|
||||
AccountType.ACCOUNT_TYPE_VESTED_REWARDS
|
||||
`${AccountType.ACCOUNT_TYPE_GENERAL}-${asset.id}`
|
||||
);
|
||||
|
||||
const amountInput = screen.getByLabelText('Amount');
|
||||
|
||||
// Test use max button
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Use max' }));
|
||||
expect(amountInput).toHaveValue('1000');
|
||||
|
||||
// Test amount validation
|
||||
await userEvent.type(amountInput, '0.00000001');
|
||||
await userEvent.clear(amountInput);
|
||||
await userEvent.type(amountInput, '0.001'); // Below quantum multiple amount
|
||||
expect(
|
||||
await screen.findByText('Value is below minimum')
|
||||
await screen.findByText(/Amount below minimum requirement/)
|
||||
).toBeInTheDocument();
|
||||
|
||||
await userEvent.clear(amountInput);
|
||||
@@ -210,7 +204,7 @@ describe('TransferForm', () => {
|
||||
await waitFor(() => {
|
||||
expect(props.submitTransfer).toHaveBeenCalledTimes(1);
|
||||
expect(props.submitTransfer).toHaveBeenCalledWith({
|
||||
fromAccountType: AccountType.ACCOUNT_TYPE_VESTED_REWARDS,
|
||||
fromAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
toAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
to: props.pubKeys[1],
|
||||
asset: asset.id,
|
||||
@@ -220,6 +214,83 @@ describe('TransferForm', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('sends transfer from vested accounts', async () => {
|
||||
const mockSubmit = jest.fn();
|
||||
renderComponent({
|
||||
...props,
|
||||
submitTransfer: mockSubmit,
|
||||
minQuantumMultiple: '100000',
|
||||
});
|
||||
|
||||
// check current pubkey not shown
|
||||
const keySelect: HTMLSelectElement = screen.getByLabelText('To Vega key');
|
||||
const pubKeyOptions = ['', pubKey, props.pubKeys[1]];
|
||||
expect(keySelect.children).toHaveLength(pubKeyOptions.length);
|
||||
expect(Array.from(keySelect.options).map((o) => o.value)).toEqual(
|
||||
pubKeyOptions
|
||||
);
|
||||
|
||||
await submit();
|
||||
expect(await screen.findAllByText('Required')).toHaveLength(2); // pubkey set as default value
|
||||
|
||||
// Select a pubkey
|
||||
await userEvent.selectOptions(
|
||||
screen.getByLabelText('To Vega key'),
|
||||
props.pubKeys[1] // Use not current pubkey so we can check it switches to current pubkey later
|
||||
);
|
||||
|
||||
// Select asset
|
||||
await selectAsset(asset);
|
||||
|
||||
await userEvent.selectOptions(
|
||||
screen.getByLabelText('From account'),
|
||||
`${AccountType.ACCOUNT_TYPE_VESTED_REWARDS}-${asset.id}`
|
||||
);
|
||||
|
||||
// Check switch back to connected key
|
||||
expect(screen.getByLabelText('To Vega key')).toHaveValue(props.pubKey);
|
||||
|
||||
const amountInput = screen.getByLabelText('Amount');
|
||||
|
||||
const checkbox = screen.getByTestId('include-transfer-fee');
|
||||
expect(checkbox).not.toBeChecked();
|
||||
|
||||
await userEvent.clear(amountInput);
|
||||
await userEvent.type(amountInput, '50');
|
||||
|
||||
expect(await screen.findByText(/Use max to bypass/)).toBeInTheDocument();
|
||||
|
||||
// Test use max button
|
||||
await userEvent.click(screen.getByRole('button', { name: 'Use max' }));
|
||||
expect(amountInput).toHaveValue('100');
|
||||
|
||||
// If transfering from a vested account 'include fees' checkbox should
|
||||
// be disabled and fees should be 0
|
||||
expect(checkbox).not.toBeChecked();
|
||||
expect(checkbox).toBeDisabled();
|
||||
const expectedFee = '0';
|
||||
const total = new BigNumber(amount).plus(expectedFee).toFixed();
|
||||
|
||||
expect(screen.getByTestId('transfer-fee')).toHaveTextContent(expectedFee);
|
||||
expect(screen.getByTestId('transfer-amount')).toHaveTextContent(amount);
|
||||
expect(screen.getByTestId('total-transfer-fee')).toHaveTextContent(total);
|
||||
|
||||
await submit();
|
||||
|
||||
await waitFor(() => {
|
||||
// 1003-TRAN-023
|
||||
expect(mockSubmit).toHaveBeenCalledTimes(1);
|
||||
expect(mockSubmit).toHaveBeenCalledWith({
|
||||
fromAccountType: AccountType.ACCOUNT_TYPE_VESTED_REWARDS,
|
||||
toAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
to: props.pubKey,
|
||||
asset: asset.id,
|
||||
amount: removeDecimal(amount, asset.decimals),
|
||||
oneOff: {},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('IncludeFeesCheckbox', () => {
|
||||
it('validates fields and submits when checkbox is checked', async () => {
|
||||
const mockSubmit = jest.fn();
|
||||
@@ -234,7 +305,7 @@ describe('TransferForm', () => {
|
||||
);
|
||||
|
||||
await submit();
|
||||
expect(await screen.findAllByText('Required')).toHaveLength(3); // pubkey set as default value
|
||||
expect(await screen.findAllByText('Required')).toHaveLength(2); // pubkey set as default value
|
||||
|
||||
// Select a pubkey
|
||||
await userEvent.selectOptions(
|
||||
@@ -247,7 +318,7 @@ describe('TransferForm', () => {
|
||||
|
||||
await userEvent.selectOptions(
|
||||
screen.getByLabelText('From account'),
|
||||
AccountType.ACCOUNT_TYPE_VESTED_REWARDS
|
||||
`${AccountType.ACCOUNT_TYPE_GENERAL}-${asset.id}`
|
||||
);
|
||||
|
||||
const amountInput = screen.getByLabelText('Amount');
|
||||
@@ -281,7 +352,7 @@ describe('TransferForm', () => {
|
||||
// 1003-TRAN-023
|
||||
expect(mockSubmit).toHaveBeenCalledTimes(1);
|
||||
expect(mockSubmit).toHaveBeenCalledWith({
|
||||
fromAccountType: AccountType.ACCOUNT_TYPE_VESTED_REWARDS,
|
||||
fromAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
toAccountType: AccountType.ACCOUNT_TYPE_GENERAL,
|
||||
to: props.pubKeys[1],
|
||||
asset: asset.id,
|
||||
@@ -303,7 +374,7 @@ describe('TransferForm', () => {
|
||||
);
|
||||
|
||||
await submit();
|
||||
expect(await screen.findAllByText('Required')).toHaveLength(3); // pubkey set as default value
|
||||
expect(await screen.findAllByText('Required')).toHaveLength(2); // pubkey set as default value
|
||||
|
||||
// Select a pubkey
|
||||
await userEvent.selectOptions(
|
||||
@@ -314,6 +385,11 @@ describe('TransferForm', () => {
|
||||
// Select asset
|
||||
await selectAsset(asset);
|
||||
|
||||
await userEvent.selectOptions(
|
||||
screen.getByLabelText('From account'),
|
||||
`${AccountType.ACCOUNT_TYPE_GENERAL}-${asset.id}`
|
||||
);
|
||||
|
||||
const amountInput = screen.getByLabelText('Amount');
|
||||
const checkbox = screen.getByTestId('include-transfer-fee');
|
||||
expect(checkbox).not.toBeChecked();
|
||||
@@ -333,26 +409,28 @@ describe('TransferForm', () => {
|
||||
|
||||
describe('AddressField', () => {
|
||||
const props = {
|
||||
mode: 'select' as const,
|
||||
select: <div>select</div>,
|
||||
input: <div>input</div>,
|
||||
onChange: jest.fn(),
|
||||
};
|
||||
|
||||
it('toggles content and calls onChange', async () => {
|
||||
it('renders correct content by mode prop and calls onChange', async () => {
|
||||
const mockOnChange = jest.fn();
|
||||
render(<AddressField {...props} onChange={mockOnChange} />);
|
||||
const { rerender } = render(
|
||||
<AddressField {...props} onChange={mockOnChange} />
|
||||
);
|
||||
|
||||
// select should be shown by default
|
||||
expect(screen.getByText('select')).toBeInTheDocument();
|
||||
expect(screen.queryByText('input')).not.toBeInTheDocument();
|
||||
await userEvent.click(screen.getByText('Enter manually'));
|
||||
expect(mockOnChange).toHaveBeenCalled();
|
||||
|
||||
rerender(<AddressField {...props} mode="input" />);
|
||||
|
||||
expect(screen.queryByText('select')).not.toBeInTheDocument();
|
||||
expect(screen.getByText('input')).toBeInTheDocument();
|
||||
expect(mockOnChange).toHaveBeenCalledTimes(1);
|
||||
await userEvent.click(screen.getByText('Select from wallet'));
|
||||
expect(screen.getByText('select')).toBeInTheDocument();
|
||||
expect(screen.queryByText('input')).not.toBeInTheDocument();
|
||||
expect(mockOnChange).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import sortBy from 'lodash/sortBy';
|
||||
import {
|
||||
minSafe,
|
||||
maxSafe,
|
||||
required,
|
||||
vegaPublicKey,
|
||||
addDecimal,
|
||||
formatNumber,
|
||||
addDecimalsFormatNumber,
|
||||
toBigNum,
|
||||
} from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import {
|
||||
@@ -27,14 +27,20 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import { AssetOption, Balance } from '@vegaprotocol/assets';
|
||||
import { AccountType, AccountTypeMapping } from '@vegaprotocol/types';
|
||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||
import { accountsDataProvider } from './accounts-data-provider';
|
||||
|
||||
interface FormFields {
|
||||
toVegaKey: string;
|
||||
asset: string;
|
||||
asset: string; // This is used to simply filter the from account list, the fromAccount type should be used in the tx
|
||||
amount: string;
|
||||
fromAccount: AccountType;
|
||||
fromAccount: string; // AccountType-AssetId
|
||||
}
|
||||
|
||||
interface Asset {
|
||||
id: string;
|
||||
symbol: string;
|
||||
name: string;
|
||||
decimals: number;
|
||||
quantum: string;
|
||||
}
|
||||
|
||||
export interface TransferFormProps {
|
||||
@@ -43,10 +49,11 @@ export interface TransferFormProps {
|
||||
accounts: Array<{
|
||||
type: AccountType;
|
||||
balance: string;
|
||||
asset: { id: string; symbol: string; name: string; decimals: number };
|
||||
asset: Asset;
|
||||
}>;
|
||||
assetId?: string;
|
||||
feeFactor: string | null;
|
||||
minQuantumMultiple: string | null;
|
||||
submitTransfer: (transfer: Transfer) => void;
|
||||
}
|
||||
|
||||
@@ -57,6 +64,7 @@ export const TransferForm = ({
|
||||
feeFactor,
|
||||
submitTransfer,
|
||||
accounts,
|
||||
minQuantumMultiple,
|
||||
}: TransferFormProps) => {
|
||||
const {
|
||||
control,
|
||||
@@ -72,6 +80,8 @@ export const TransferForm = ({
|
||||
},
|
||||
});
|
||||
|
||||
const [toVegaKeyMode, setToVegaKeyMode] = useState<ToVegaKeyMode>('select');
|
||||
|
||||
const assets = sortBy(
|
||||
accounts
|
||||
.filter(
|
||||
@@ -99,48 +109,29 @@ export const TransferForm = ({
|
||||
...account.asset,
|
||||
balance: addDecimal(account.balance, account.asset.decimals),
|
||||
})),
|
||||
'name'
|
||||
(a) => a.symbol.toLowerCase()
|
||||
);
|
||||
|
||||
const selectedPubKey = watch('toVegaKey');
|
||||
const amount = watch('amount');
|
||||
const fromAccount = watch('fromAccount');
|
||||
const assetId = watch('asset');
|
||||
const selectedAssetId = watch('asset');
|
||||
|
||||
const asset = assets.find((a) => a.id === assetId);
|
||||
|
||||
const { data: toAccounts } = useDataProvider({
|
||||
dataProvider: accountsDataProvider,
|
||||
variables: {
|
||||
partyId: selectedPubKey,
|
||||
},
|
||||
skip: !selectedPubKey,
|
||||
});
|
||||
// Convert the account type (Type-AssetId) into separate values
|
||||
const [accountType, accountAssetId] = fromAccount
|
||||
? parseFromAccount(fromAccount)
|
||||
: [undefined, undefined];
|
||||
const fromVested = accountType === AccountType.ACCOUNT_TYPE_VESTED_REWARDS;
|
||||
const asset = assets.find((a) => a.id === accountAssetId);
|
||||
|
||||
const account = accounts.find(
|
||||
(a) => a.asset.id === assetId && a.type === fromAccount
|
||||
(a) => a.asset.id === accountAssetId && a.type === accountType
|
||||
);
|
||||
const accountBalance =
|
||||
account && addDecimal(account.balance, account.asset.decimals);
|
||||
|
||||
// The general account of the selected pubkey. You can only transfer
|
||||
// to general accounts, either when redeeming vested rewards or just
|
||||
// during normal general -> general transfers
|
||||
const toGeneralAccount =
|
||||
toAccounts &&
|
||||
toAccounts.find((a) => {
|
||||
return (
|
||||
a.asset.id === assetId && a.type === AccountType.ACCOUNT_TYPE_GENERAL
|
||||
);
|
||||
});
|
||||
|
||||
const [includeFee, setIncludeFee] = useState(false);
|
||||
|
||||
// Min viable amount given asset decimals EG for WEI 0.000000000000000001
|
||||
const min = asset
|
||||
? new BigNumber(addDecimal('1', asset.decimals))
|
||||
: new BigNumber(0);
|
||||
|
||||
// Max amount given selected asset and from account
|
||||
const max = accountBalance ? new BigNumber(accountBalance) : new BigNumber(0);
|
||||
|
||||
@@ -164,16 +155,21 @@ export const TransferForm = ({
|
||||
|
||||
const onSubmit = useCallback(
|
||||
(fields: FormFields) => {
|
||||
if (!asset) {
|
||||
throw new Error('Submitted transfer with no asset selected');
|
||||
}
|
||||
if (!transferAmount) {
|
||||
throw new Error('Submitted transfer with no amount selected');
|
||||
}
|
||||
|
||||
const [type, assetId] = parseFromAccount(fields.fromAccount);
|
||||
const asset = assets.find((a) => a.id === assetId);
|
||||
|
||||
if (!asset) {
|
||||
throw new Error('Submitted transfer with no asset selected');
|
||||
}
|
||||
|
||||
const transfer = normalizeTransfer(
|
||||
fields.toVegaKey,
|
||||
transferAmount,
|
||||
fields.fromAccount,
|
||||
type,
|
||||
AccountType.ACCOUNT_TYPE_GENERAL, // field is readonly in the form
|
||||
{
|
||||
id: asset.id,
|
||||
@@ -182,7 +178,7 @@ export const TransferForm = ({
|
||||
);
|
||||
submitTransfer(transfer);
|
||||
},
|
||||
[asset, submitTransfer, transferAmount]
|
||||
[submitTransfer, transferAmount, assets]
|
||||
);
|
||||
|
||||
// reset for placeholder workaround https://github.com/radix-ui/primitives/issues/1569
|
||||
@@ -198,55 +194,10 @@ export const TransferForm = ({
|
||||
className="text-sm"
|
||||
data-testid="transfer-form"
|
||||
>
|
||||
<TradingFormGroup label="To Vega key" labelFor="toVegaKey">
|
||||
<AddressField
|
||||
onChange={() => setValue('toVegaKey', '')}
|
||||
select={
|
||||
<TradingSelect {...register('toVegaKey')} id="toVegaKey">
|
||||
<option value="" disabled={true}>
|
||||
{t('Please select')}
|
||||
</option>
|
||||
{pubKeys?.map((pk) => {
|
||||
const text = pk === pubKey ? t('Current key: ') + pk : pk;
|
||||
|
||||
return (
|
||||
<option key={pk} value={pk}>
|
||||
{text}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</TradingSelect>
|
||||
}
|
||||
input={
|
||||
<TradingInput
|
||||
// eslint-disable-next-line jsx-a11y/no-autofocus
|
||||
autoFocus={true} // focus input immediately after is shown
|
||||
id="toVegaKey"
|
||||
type="text"
|
||||
{...register('toVegaKey', {
|
||||
validate: {
|
||||
required,
|
||||
vegaPublicKey,
|
||||
},
|
||||
})}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{errors.toVegaKey?.message && (
|
||||
<TradingInputError forInput="toVegaKey">
|
||||
{errors.toVegaKey.message}
|
||||
</TradingInputError>
|
||||
)}
|
||||
</TradingFormGroup>
|
||||
<TradingFormGroup label={t('Asset')} labelFor="asset">
|
||||
<Controller
|
||||
control={control}
|
||||
name="asset"
|
||||
rules={{
|
||||
validate: {
|
||||
required,
|
||||
},
|
||||
}}
|
||||
render={({ field }) => (
|
||||
<TradingRichSelect
|
||||
data-testid="select-asset"
|
||||
@@ -254,6 +205,7 @@ export const TransferForm = ({
|
||||
name={field.name}
|
||||
onValueChange={(value) => {
|
||||
field.onChange(value);
|
||||
setValue('fromAccount', '');
|
||||
}}
|
||||
placeholder={t('Please select an asset')}
|
||||
value={field.value}
|
||||
@@ -280,10 +232,10 @@ export const TransferForm = ({
|
||||
)}
|
||||
</TradingFormGroup>
|
||||
<TradingFormGroup label={t('From account')} labelFor="fromAccount">
|
||||
<TradingSelect
|
||||
id="fromAccount"
|
||||
defaultValue=""
|
||||
{...register('fromAccount', {
|
||||
<Controller
|
||||
control={control}
|
||||
name="fromAccount"
|
||||
rules={{
|
||||
validate: {
|
||||
required,
|
||||
sameAccount: (value) => {
|
||||
@@ -298,50 +250,106 @@ export const TransferForm = ({
|
||||
return true;
|
||||
},
|
||||
},
|
||||
})}
|
||||
>
|
||||
<option value="" disabled={true}>
|
||||
{t('Please select')}
|
||||
</option>
|
||||
{accounts
|
||||
.filter((a) => {
|
||||
if (!assetId) return true;
|
||||
return assetId === a.asset.id;
|
||||
})
|
||||
.map((a) => {
|
||||
return (
|
||||
<option value={a.type} key={`${a.type}-${a.asset.id}`}>
|
||||
{AccountTypeMapping[a.type]} (
|
||||
{addDecimalsFormatNumber(a.balance, a.asset.decimals)}{' '}
|
||||
{a.asset.symbol})
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</TradingSelect>
|
||||
}}
|
||||
render={({ field }) => (
|
||||
<TradingSelect
|
||||
id="fromAccount"
|
||||
defaultValue=""
|
||||
{...field}
|
||||
onChange={(e) => {
|
||||
field.onChange(e);
|
||||
|
||||
const [type] = parseFromAccount(e.target.value);
|
||||
|
||||
// Enforce that if transferring from a vested rewards account it must go to
|
||||
// the current connected general account
|
||||
if (
|
||||
type === AccountType.ACCOUNT_TYPE_VESTED_REWARDS &&
|
||||
pubKey
|
||||
) {
|
||||
setValue('toVegaKey', pubKey);
|
||||
setToVegaKeyMode('select');
|
||||
setIncludeFee(false);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<option value="" disabled={true}>
|
||||
{t('Please select')}
|
||||
</option>
|
||||
{accounts
|
||||
.filter((a) => {
|
||||
if (!selectedAssetId) return true;
|
||||
return selectedAssetId === a.asset.id;
|
||||
})
|
||||
.map((a) => {
|
||||
const id = `${a.type}-${a.asset.id}`;
|
||||
return (
|
||||
<option value={id} key={id}>
|
||||
{AccountTypeMapping[a.type]} (
|
||||
{addDecimalsFormatNumber(a.balance, a.asset.decimals)}{' '}
|
||||
{a.asset.symbol})
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</TradingSelect>
|
||||
)}
|
||||
/>
|
||||
{errors.fromAccount?.message && (
|
||||
<TradingInputError forInput="fromAccount">
|
||||
{errors.fromAccount.message}
|
||||
</TradingInputError>
|
||||
)}
|
||||
</TradingFormGroup>
|
||||
<TradingFormGroup label={t('To account')} labelFor="toAccount">
|
||||
<TradingSelect
|
||||
id="toAccount"
|
||||
defaultValue={AccountType.ACCOUNT_TYPE_GENERAL}
|
||||
>
|
||||
<option value={AccountType.ACCOUNT_TYPE_GENERAL}>
|
||||
{toGeneralAccount
|
||||
? `${
|
||||
AccountTypeMapping[AccountType.ACCOUNT_TYPE_GENERAL]
|
||||
} (${addDecimalsFormatNumber(
|
||||
toGeneralAccount.balance,
|
||||
toGeneralAccount.asset.decimals
|
||||
)} ${toGeneralAccount.asset.symbol})`
|
||||
: `${AccountTypeMapping[AccountType.ACCOUNT_TYPE_GENERAL]} ${
|
||||
asset ? `(0 ${asset.symbol})` : ''
|
||||
}`}
|
||||
</option>
|
||||
</TradingSelect>
|
||||
<TradingFormGroup label="To Vega key" labelFor="toVegaKey">
|
||||
<AddressField
|
||||
onChange={() => {
|
||||
setValue('toVegaKey', '');
|
||||
setToVegaKeyMode((curr) => (curr === 'input' ? 'select' : 'input'));
|
||||
}}
|
||||
mode={toVegaKeyMode}
|
||||
select={
|
||||
<TradingSelect
|
||||
{...register('toVegaKey')}
|
||||
disabled={fromVested}
|
||||
id="toVegaKey"
|
||||
>
|
||||
<option value="" disabled={true}>
|
||||
{t('Please select')}
|
||||
</option>
|
||||
{pubKeys?.map((pk) => {
|
||||
const text = pk === pubKey ? t('Current key: ') + pk : pk;
|
||||
|
||||
return (
|
||||
<option key={pk} value={pk}>
|
||||
{text}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</TradingSelect>
|
||||
}
|
||||
input={
|
||||
fromVested ? null : (
|
||||
<TradingInput
|
||||
// eslint-disable-next-line jsx-a11y/no-autofocus
|
||||
autoFocus={true} // focus input immediately after is shown
|
||||
id="toVegaKey"
|
||||
type="text"
|
||||
disabled={fromVested}
|
||||
{...register('toVegaKey', {
|
||||
validate: {
|
||||
required,
|
||||
vegaPublicKey,
|
||||
},
|
||||
})}
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
{errors.toVegaKey?.message && (
|
||||
<TradingInputError forInput="toVegaKey">
|
||||
{errors.toVegaKey.message}
|
||||
</TradingInputError>
|
||||
)}
|
||||
</TradingFormGroup>
|
||||
<TradingFormGroup label="Amount" labelFor="amount">
|
||||
<TradingInput
|
||||
@@ -353,7 +361,43 @@ export const TransferForm = ({
|
||||
{...register('amount', {
|
||||
validate: {
|
||||
required,
|
||||
minSafe: (value) => minSafe(new BigNumber(min))(value),
|
||||
minSafe: (v) => {
|
||||
if (!asset || !minQuantumMultiple) return true;
|
||||
|
||||
const value = new BigNumber(v);
|
||||
|
||||
if (value.isZero()) {
|
||||
return t('Amount cannot be 0');
|
||||
}
|
||||
|
||||
const minByQuantumMultiple = toBigNum(
|
||||
minQuantumMultiple,
|
||||
asset.decimals
|
||||
);
|
||||
|
||||
if (fromVested) {
|
||||
// special conditions which let you bypass min transfer rules set by quantum multiple
|
||||
if (value.isGreaterThanOrEqualTo(max)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (value.isLessThan(minByQuantumMultiple)) {
|
||||
return t(
|
||||
'Amount below minimum requirements for partial transfer. Use max to bypass'
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
if (value.isLessThan(minByQuantumMultiple)) {
|
||||
return t(
|
||||
'Amount below minimum requirement set by transfer.minTransferQuantumMultiple'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
maxSafe: (v) => {
|
||||
const value = new BigNumber(v);
|
||||
if (value.isGreaterThan(max)) {
|
||||
@@ -369,7 +413,9 @@ export const TransferForm = ({
|
||||
type="button"
|
||||
className="absolute top-0 right-0 ml-auto text-xs underline"
|
||||
onClick={() =>
|
||||
setValue('amount', parseFloat(accountBalance).toString())
|
||||
setValue('amount', parseFloat(accountBalance).toString(), {
|
||||
shouldValidate: true,
|
||||
})
|
||||
}
|
||||
>
|
||||
{t('Use max')}
|
||||
@@ -390,10 +436,10 @@ export const TransferForm = ({
|
||||
<div>
|
||||
<TradingCheckbox
|
||||
name="include-transfer-fee"
|
||||
disabled={!transferAmount}
|
||||
disabled={!transferAmount || fromVested}
|
||||
label={t('Include transfer fee')}
|
||||
checked={includeFee}
|
||||
onCheckedChange={() => setIncludeFee(!includeFee)}
|
||||
onCheckedChange={() => setIncludeFee((x) => !x)}
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
@@ -403,7 +449,7 @@ export const TransferForm = ({
|
||||
amount={transferAmount}
|
||||
transferAmount={transferAmount}
|
||||
feeFactor={feeFactor}
|
||||
fee={fee}
|
||||
fee={fromVested ? '0' : fee}
|
||||
decimals={asset?.decimals}
|
||||
/>
|
||||
)}
|
||||
@@ -485,32 +531,38 @@ export const TransferFee = ({
|
||||
);
|
||||
};
|
||||
|
||||
type ToVegaKeyMode = 'input' | 'select';
|
||||
|
||||
interface AddressInputProps {
|
||||
select: ReactNode;
|
||||
input: ReactNode;
|
||||
mode: ToVegaKeyMode;
|
||||
onChange: () => void;
|
||||
}
|
||||
|
||||
export const AddressField = ({
|
||||
select,
|
||||
input,
|
||||
mode,
|
||||
onChange,
|
||||
}: AddressInputProps) => {
|
||||
const [isInput, setIsInput] = useState(false);
|
||||
|
||||
const isInput = mode === 'input';
|
||||
return (
|
||||
<>
|
||||
{isInput ? input : select}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setIsInput((curr) => !curr);
|
||||
onChange();
|
||||
}}
|
||||
className="absolute top-0 right-0 ml-auto text-xs underline"
|
||||
>
|
||||
{isInput ? t('Select from wallet') : t('Enter manually')}
|
||||
</button>
|
||||
{select && input && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onChange}
|
||||
className="absolute top-0 right-0 ml-auto text-xs underline"
|
||||
>
|
||||
{isInput ? t('Select from wallet') : t('Enter manually')}
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const parseFromAccount = (fromAccountStr: string) => {
|
||||
return fromAccountStr.split('-') as [AccountType, string];
|
||||
};
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"path": "./tsconfig.spec.json"
|
||||
},
|
||||
{
|
||||
"path": "./.storybook/tsconfig.json"
|
||||
"path": "./tsconfig.storybook.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"emitDecoratorMetadata": true,
|
||||
"outDir": ""
|
||||
},
|
||||
"files": [
|
||||
"../../node_modules/@nx/react/typings/styled-jsx.d.ts",
|
||||
"../../node_modules/@nx/react/typings/cssmodule.d.ts",
|
||||
"../../node_modules/@nx/react/typings/image.d.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.spec.js",
|
||||
"src/**/*.spec.tsx",
|
||||
"src/**/*.spec.jsx",
|
||||
"jest.config.ts"
|
||||
],
|
||||
"include": ["src/**/*", ".storybook/*.js"]
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"emitDecoratorMetadata": true,
|
||||
"outDir": ""
|
||||
},
|
||||
"files": [
|
||||
"../../../node_modules/@nx/react/typings/styled-jsx.d.ts",
|
||||
"../../../node_modules/@nx/react/typings/cssmodule.d.ts",
|
||||
"../../../node_modules/@nx/react/typings/image.d.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"../**/*.spec.ts",
|
||||
"../**/*.spec.js",
|
||||
"../**/*.spec.tsx",
|
||||
"../**/*.spec.jsx",
|
||||
"jest.config.ts"
|
||||
],
|
||||
"include": ["../src/**/*", "*.js"]
|
||||
}
|
||||
@@ -32,7 +32,7 @@
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/linter:eslint",
|
||||
"executor": "@nx/eslint:lint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
"options": {
|
||||
"lintFilePatterns": ["libs/announcements/**/*.{ts,tsx,js,jsx}"]
|
||||
@@ -42,14 +42,7 @@
|
||||
"executor": "@nx/jest:jest",
|
||||
"outputs": ["{workspaceRoot}/coverage/libs/announcements"],
|
||||
"options": {
|
||||
"jestConfig": "libs/announcements/jest.config.ts",
|
||||
"passWithNoTests": true
|
||||
},
|
||||
"configurations": {
|
||||
"ci": {
|
||||
"ci": true,
|
||||
"codeCoverage": true
|
||||
}
|
||||
"jestConfig": "libs/announcements/jest.config.ts"
|
||||
}
|
||||
},
|
||||
"storybook": {
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"path": "./tsconfig.spec.json"
|
||||
},
|
||||
{
|
||||
"path": "./.storybook/tsconfig.json"
|
||||
"path": "./tsconfig.storybook.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"emitDecoratorMetadata": true,
|
||||
"outDir": ""
|
||||
},
|
||||
"files": [
|
||||
"../../node_modules/@nx/react/typings/styled-jsx.d.ts",
|
||||
"../../node_modules/@nx/react/typings/cssmodule.d.ts",
|
||||
"../../node_modules/@nx/react/typings/image.d.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.spec.js",
|
||||
"src/**/*.spec.tsx",
|
||||
"src/**/*.spec.jsx",
|
||||
"jest.config.ts"
|
||||
],
|
||||
"include": ["src/**/*", ".storybook/*.js"]
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
"projectType": "library",
|
||||
"targets": {
|
||||
"lint": {
|
||||
"executor": "@nx/linter:eslint",
|
||||
"executor": "@nx/eslint:lint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
"options": {
|
||||
"lintFilePatterns": ["libs/apollo-client/**/*.ts"]
|
||||
@@ -15,14 +15,7 @@
|
||||
"executor": "@nx/jest:jest",
|
||||
"outputs": ["{workspaceRoot}/coverage/libs/apollo-client"],
|
||||
"options": {
|
||||
"jestConfig": "libs/apollo-client/jest.config.ts",
|
||||
"passWithNoTests": true
|
||||
},
|
||||
"configurations": {
|
||||
"ci": {
|
||||
"ci": true,
|
||||
"codeCoverage": true
|
||||
}
|
||||
"jestConfig": "libs/apollo-client/jest.config.ts"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"tags": [],
|
||||
"targets": {
|
||||
"lint": {
|
||||
"executor": "@nx/linter:eslint",
|
||||
"executor": "@nx/eslint:lint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
"options": {
|
||||
"lintFilePatterns": ["libs/assets/**/*.{ts,tsx,js,jsx}"]
|
||||
@@ -16,14 +16,7 @@
|
||||
"executor": "@nx/jest:jest",
|
||||
"outputs": ["{workspaceRoot}/coverage/libs/assets"],
|
||||
"options": {
|
||||
"jestConfig": "libs/assets/jest.config.ts",
|
||||
"passWithNoTests": true
|
||||
},
|
||||
"configurations": {
|
||||
"ci": {
|
||||
"ci": true,
|
||||
"codeCoverage": true
|
||||
}
|
||||
"jestConfig": "libs/assets/jest.config.ts"
|
||||
}
|
||||
},
|
||||
"build-spec": {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"tags": [],
|
||||
"targets": {
|
||||
"lint": {
|
||||
"executor": "@nx/linter:eslint",
|
||||
"executor": "@nx/eslint:lint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
"options": {
|
||||
"lintFilePatterns": ["libs/candles-chart/**/*.{ts,tsx,js,jsx}"]
|
||||
@@ -16,14 +16,7 @@
|
||||
"executor": "@nx/jest:jest",
|
||||
"outputs": ["{workspaceRoot}/coverage/libs/candles-chart"],
|
||||
"options": {
|
||||
"jestConfig": "libs/candles-chart/jest.config.ts",
|
||||
"passWithNoTests": true
|
||||
},
|
||||
"configurations": {
|
||||
"ci": {
|
||||
"ci": true,
|
||||
"codeCoverage": true
|
||||
}
|
||||
"jestConfig": "libs/candles-chart/jest.config.ts"
|
||||
}
|
||||
},
|
||||
"build-spec": {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"projectType": "library",
|
||||
"targets": {
|
||||
"lint": {
|
||||
"executor": "@nx/linter:eslint",
|
||||
"executor": "@nx/eslint:lint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
"options": {
|
||||
"lintFilePatterns": ["libs/cypress/**/*.ts"]
|
||||
@@ -15,14 +15,7 @@
|
||||
"executor": "@nx/jest:jest",
|
||||
"outputs": ["{workspaceRoot}/coverage/libs/cypress"],
|
||||
"options": {
|
||||
"jestConfig": "libs/cypress/jest.config.ts",
|
||||
"passWithNoTests": true
|
||||
},
|
||||
"configurations": {
|
||||
"ci": {
|
||||
"ci": true,
|
||||
"codeCoverage": true
|
||||
}
|
||||
"jestConfig": "libs/cypress/jest.config.ts"
|
||||
}
|
||||
},
|
||||
"build-spec": {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"tags": [],
|
||||
"targets": {
|
||||
"lint": {
|
||||
"executor": "@nx/linter:eslint",
|
||||
"executor": "@nx/eslint:lint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
"options": {
|
||||
"lintFilePatterns": ["libs/data-provider/**/*.{ts,tsx,js,jsx}"]
|
||||
@@ -16,14 +16,7 @@
|
||||
"executor": "@nx/jest:jest",
|
||||
"outputs": ["{workspaceRoot}/coverage/libs/data-provider"],
|
||||
"options": {
|
||||
"jestConfig": "libs/data-provider/jest.config.ts",
|
||||
"passWithNoTests": true
|
||||
},
|
||||
"configurations": {
|
||||
"ci": {
|
||||
"ci": true,
|
||||
"codeCoverage": true
|
||||
}
|
||||
"jestConfig": "libs/data-provider/jest.config.ts"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"tags": [],
|
||||
"targets": {
|
||||
"lint": {
|
||||
"executor": "@nx/linter:eslint",
|
||||
"executor": "@nx/eslint:lint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
"options": {
|
||||
"lintFilePatterns": ["libs/datagrid/**/*.{ts,tsx,js,jsx}"]
|
||||
@@ -16,14 +16,7 @@
|
||||
"executor": "@nx/jest:jest",
|
||||
"outputs": ["{workspaceRoot}/coverage/libs/datagrid"],
|
||||
"options": {
|
||||
"jestConfig": "libs/datagrid/jest.config.ts",
|
||||
"passWithNoTests": true
|
||||
},
|
||||
"configurations": {
|
||||
"ci": {
|
||||
"ci": true,
|
||||
"codeCoverage": true
|
||||
}
|
||||
"jestConfig": "libs/datagrid/jest.config.ts"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"tags": [],
|
||||
"targets": {
|
||||
"lint": {
|
||||
"executor": "@nx/linter:eslint",
|
||||
"executor": "@nx/eslint:lint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
"options": {
|
||||
"lintFilePatterns": ["libs/deal-ticket/**/*.{ts,tsx,js,jsx}"]
|
||||
@@ -16,14 +16,7 @@
|
||||
"executor": "@nx/jest:jest",
|
||||
"outputs": ["{workspaceRoot}/coverage/libs/deal-ticket"],
|
||||
"options": {
|
||||
"jestConfig": "libs/deal-ticket/jest.config.ts",
|
||||
"passWithNoTests": true
|
||||
},
|
||||
"configurations": {
|
||||
"ci": {
|
||||
"ci": true,
|
||||
"codeCoverage": true
|
||||
}
|
||||
"jestConfig": "libs/deal-ticket/jest.config.ts"
|
||||
}
|
||||
},
|
||||
"build-spec": {
|
||||
|
||||
@@ -685,7 +685,7 @@ export const DealTicket = ({
|
||||
subLabel={`${formatValue(
|
||||
normalizedOrder.size,
|
||||
market.positionDecimalPlaces
|
||||
)} ${baseQuote} @ ${
|
||||
)} ${baseQuote || ''} @ ${
|
||||
type === Schema.OrderType.TYPE_MARKET
|
||||
? 'market'
|
||||
: `${formatValue(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import type { Validate } from 'react-hook-form';
|
||||
|
||||
export const validateExpiration: Validate<string | undefined> = (
|
||||
export const validateExpiration: Validate<string | undefined, object> = (
|
||||
value?: string
|
||||
) => {
|
||||
const now = new Date();
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"tags": [],
|
||||
"targets": {
|
||||
"lint": {
|
||||
"executor": "@nx/linter:eslint",
|
||||
"executor": "@nx/eslint:lint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
"options": {
|
||||
"lintFilePatterns": ["libs/deposits/**/*.{ts,tsx,js,jsx}"]
|
||||
@@ -16,14 +16,7 @@
|
||||
"executor": "@nx/jest:jest",
|
||||
"outputs": ["{workspaceRoot}/coverage/libs/deposits"],
|
||||
"options": {
|
||||
"jestConfig": "libs/deposits/jest.config.ts",
|
||||
"passWithNoTests": true
|
||||
},
|
||||
"configurations": {
|
||||
"ci": {
|
||||
"ci": true,
|
||||
"codeCoverage": true
|
||||
}
|
||||
"jestConfig": "libs/deposits/jest.config.ts"
|
||||
}
|
||||
},
|
||||
"build-spec": {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"tags": [],
|
||||
"targets": {
|
||||
"lint": {
|
||||
"executor": "@nx/linter:eslint",
|
||||
"executor": "@nx/eslint:lint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
"options": {
|
||||
"lintFilePatterns": ["libs/environment/**/*.{ts,tsx,js,jsx}"]
|
||||
@@ -16,14 +16,7 @@
|
||||
"executor": "@nx/jest:jest",
|
||||
"outputs": ["{workspaceRoot}/coverage/libs/environment"],
|
||||
"options": {
|
||||
"jestConfig": "libs/environment/jest.config.ts",
|
||||
"passWithNoTests": true
|
||||
},
|
||||
"configurations": {
|
||||
"ci": {
|
||||
"ci": true,
|
||||
"codeCoverage": true
|
||||
}
|
||||
"jestConfig": "libs/environment/jest.config.ts"
|
||||
}
|
||||
},
|
||||
"build-spec": {
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import type { MockedResponse } from '@apollo/react-testing';
|
||||
import { MockedProvider } from '@apollo/react-testing';
|
||||
import { act, render, screen, waitFor } from '@testing-library/react';
|
||||
import {
|
||||
act,
|
||||
render,
|
||||
renderHook,
|
||||
screen,
|
||||
waitFor,
|
||||
} from '@testing-library/react';
|
||||
import { RadioGroup } from '@vegaprotocol/ui-toolkit';
|
||||
import type {
|
||||
NodeCheckTimeUpdateSubscription,
|
||||
@@ -11,30 +17,33 @@ import {
|
||||
NodeCheckTimeUpdateDocument,
|
||||
} from '../../utils/__generated__/NodeCheck';
|
||||
import type { RowDataProps } from './row-data';
|
||||
import { POLL_INTERVAL } from './row-data';
|
||||
import {
|
||||
POLL_INTERVAL,
|
||||
Result,
|
||||
SUBSCRIPTION_TIMEOUT,
|
||||
useNodeBasicStatus,
|
||||
useNodeSubscriptionStatus,
|
||||
useResponseTime,
|
||||
} from './row-data';
|
||||
import { BLOCK_THRESHOLD, RowData } from './row-data';
|
||||
import type { HeaderEntry } from '@vegaprotocol/apollo-client';
|
||||
import { useHeaderStore } from '@vegaprotocol/apollo-client';
|
||||
import { CUSTOM_NODE_KEY } from '../../types';
|
||||
|
||||
jest.mock('@vegaprotocol/apollo-client', () => ({
|
||||
useHeaderStore: jest.fn().mockReturnValue({}),
|
||||
}));
|
||||
|
||||
const statsQueryMock: MockedResponse<NodeCheckQuery> = {
|
||||
const mockStatsQuery = (
|
||||
blockHeight = '1234'
|
||||
): MockedResponse<NodeCheckQuery> => ({
|
||||
request: {
|
||||
query: NodeCheckDocument,
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
statistics: {
|
||||
blockHeight: '1234', // the actual value used in the component is the value from the header store
|
||||
blockHeight,
|
||||
vegaTime: new Date().toISOString(),
|
||||
chainId: 'test-chain-id',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const subMock: MockedResponse<NodeCheckTimeUpdateSubscription> = {
|
||||
request: {
|
||||
@@ -59,18 +68,6 @@ global.performance.getEntriesByName = jest.fn().mockReturnValue([
|
||||
},
|
||||
]);
|
||||
|
||||
const mockHeaders = (
|
||||
url: string,
|
||||
headers: Partial<HeaderEntry> = {
|
||||
blockHeight: 100,
|
||||
timestamp: new Date(),
|
||||
}
|
||||
) => {
|
||||
(useHeaderStore as unknown as jest.Mock).mockReturnValue({
|
||||
[url]: headers,
|
||||
});
|
||||
};
|
||||
|
||||
const renderComponent = (
|
||||
props: RowDataProps,
|
||||
queryMock: MockedResponse<NodeCheckQuery>,
|
||||
@@ -86,6 +83,98 @@ const renderComponent = (
|
||||
);
|
||||
};
|
||||
|
||||
describe('useNodeSubscriptionStatus', () => {
|
||||
beforeAll(() => {
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
afterAll(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
const mockWrapper =
|
||||
(withData = false) =>
|
||||
({ children }: { children: React.ReactNode }) =>
|
||||
(
|
||||
<MockedProvider mocks={withData ? [subMock, subMock, subMock] : []}>
|
||||
{children}
|
||||
</MockedProvider>
|
||||
);
|
||||
it('results initially as loading', async () => {
|
||||
const { result } = renderHook(() => useNodeSubscriptionStatus(), {
|
||||
wrapper: mockWrapper(true),
|
||||
});
|
||||
expect(result.current.status).toBe(Result.Loading);
|
||||
});
|
||||
it('results as successful when data received', async () => {
|
||||
const { result } = renderHook(() => useNodeSubscriptionStatus(), {
|
||||
wrapper: mockWrapper(true),
|
||||
});
|
||||
expect(result.current.status).toBe(Result.Loading);
|
||||
await act(() => {
|
||||
jest.advanceTimersByTime(SUBSCRIPTION_TIMEOUT);
|
||||
});
|
||||
expect(result.current.status).toBe(Result.Successful);
|
||||
});
|
||||
it('result as failed when no data received', async () => {
|
||||
const { result } = renderHook(() => useNodeSubscriptionStatus(), {
|
||||
wrapper: mockWrapper(false),
|
||||
});
|
||||
expect(result.current.status).toBe(Result.Loading);
|
||||
await act(() => {
|
||||
jest.advanceTimersByTime(SUBSCRIPTION_TIMEOUT);
|
||||
});
|
||||
expect(result.current.status).toBe(Result.Failed);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useNodeBasicStatus', () => {
|
||||
const mockWrapper =
|
||||
(withData = false) =>
|
||||
({ children }: { children: React.ReactNode }) =>
|
||||
(
|
||||
<MockedProvider mocks={withData ? [mockStatsQuery('1234')] : []}>
|
||||
{children}
|
||||
</MockedProvider>
|
||||
);
|
||||
it('results initially as loading', async () => {
|
||||
const { result } = renderHook(() => useNodeBasicStatus(), {
|
||||
wrapper: mockWrapper(true),
|
||||
});
|
||||
expect(result.current.status).toBe(Result.Loading);
|
||||
expect(result.current.currentBlockHeight).toBeNaN();
|
||||
});
|
||||
it('results as successful when data received', async () => {
|
||||
const { result } = renderHook(() => useNodeBasicStatus(), {
|
||||
wrapper: mockWrapper(true),
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(result.current.status).toBe(Result.Successful);
|
||||
expect(result.current.currentBlockHeight).toBe(1234);
|
||||
});
|
||||
});
|
||||
it('result as failed when no data received', async () => {
|
||||
const { result } = renderHook(() => useNodeBasicStatus(), {
|
||||
wrapper: mockWrapper(false),
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(result.current.status).toBe(Result.Failed);
|
||||
expect(result.current.currentBlockHeight).toBeNaN();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('useResponseTime', () => {
|
||||
it('returns response time when url is valid', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useResponseTime('https://localhost:1234')
|
||||
);
|
||||
expect(result.current.responseTime).toBe(50);
|
||||
});
|
||||
it('does not return response time when url is invalid', () => {
|
||||
const { result } = renderHook(() => useResponseTime('nope'));
|
||||
expect(result.current.responseTime).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('RowData', () => {
|
||||
const props = {
|
||||
id: '0',
|
||||
@@ -94,9 +183,13 @@ describe('RowData', () => {
|
||||
onBlockHeight: jest.fn(),
|
||||
};
|
||||
|
||||
afterAll(() => {
|
||||
jest.useRealTimers();
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('radio button enabled after stats query successful', async () => {
|
||||
mockHeaders(props.url);
|
||||
render(renderComponent(props, statsQueryMock, subMock));
|
||||
render(renderComponent(props, mockStatsQuery('100'), subMock));
|
||||
|
||||
// radio should be enabled until query resolves
|
||||
expect(
|
||||
@@ -127,8 +220,6 @@ describe('RowData', () => {
|
||||
});
|
||||
|
||||
it('radio button still enabled if query fails', async () => {
|
||||
mockHeaders(props.url, {});
|
||||
|
||||
const failedQueryMock: MockedResponse<NodeCheckQuery> = {
|
||||
request: {
|
||||
query: NodeCheckDocument,
|
||||
@@ -178,12 +269,11 @@ describe('RowData', () => {
|
||||
|
||||
it('highlights rows with a slow block height', async () => {
|
||||
const blockHeight = 100;
|
||||
mockHeaders(props.url, { blockHeight });
|
||||
|
||||
const { rerender } = render(
|
||||
renderComponent(
|
||||
{ ...props, highestBlock: blockHeight + BLOCK_THRESHOLD },
|
||||
statsQueryMock,
|
||||
mockStatsQuery(String(blockHeight)),
|
||||
subMock
|
||||
)
|
||||
);
|
||||
@@ -201,7 +291,7 @@ describe('RowData', () => {
|
||||
rerender(
|
||||
renderComponent(
|
||||
{ ...props, highestBlock: blockHeight + BLOCK_THRESHOLD + 1 },
|
||||
statsQueryMock,
|
||||
mockStatsQuery(String(blockHeight)),
|
||||
subMock
|
||||
)
|
||||
);
|
||||
@@ -216,7 +306,7 @@ describe('RowData', () => {
|
||||
...props,
|
||||
id: CUSTOM_NODE_KEY,
|
||||
},
|
||||
statsQueryMock,
|
||||
mockStatsQuery('1234'),
|
||||
subMock
|
||||
)
|
||||
);
|
||||
@@ -230,16 +320,17 @@ describe('RowData', () => {
|
||||
it('updates highest block after new header received', async () => {
|
||||
const mockOnBlockHeight = jest.fn();
|
||||
const blockHeight = 200;
|
||||
mockHeaders(props.url, { blockHeight });
|
||||
render(
|
||||
renderComponent(
|
||||
{ ...props, onBlockHeight: mockOnBlockHeight },
|
||||
statsQueryMock,
|
||||
mockStatsQuery(String(blockHeight)),
|
||||
subMock
|
||||
)
|
||||
);
|
||||
|
||||
expect(mockOnBlockHeight).toHaveBeenCalledWith(blockHeight);
|
||||
await waitFor(() => {
|
||||
expect(mockOnBlockHeight).toHaveBeenCalledWith(blockHeight);
|
||||
});
|
||||
});
|
||||
|
||||
it('should poll the query unless an errors is returned', async () => {
|
||||
@@ -275,7 +366,6 @@ describe('RowData', () => {
|
||||
};
|
||||
};
|
||||
|
||||
mockHeaders(props.url);
|
||||
const statsQueryMock1 = createStatsQueryMock('1234');
|
||||
const statsQueryMock2 = createStatsQueryMock('1235');
|
||||
const statsQueryMock3 = createFailedStatsQueryMock();
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import type { ApolloError } from '@apollo/client';
|
||||
import { useHeaderStore } from '@vegaprotocol/apollo-client';
|
||||
import { isValidUrl } from '@vegaprotocol/utils';
|
||||
import { t } from '@vegaprotocol/i18n';
|
||||
import { TradingRadio } from '@vegaprotocol/ui-toolkit';
|
||||
@@ -12,6 +10,7 @@ import {
|
||||
import { LayoutCell } from './layout-cell';
|
||||
|
||||
export const POLL_INTERVAL = 1000;
|
||||
export const SUBSCRIPTION_TIMEOUT = 3000;
|
||||
export const BLOCK_THRESHOLD = 3;
|
||||
|
||||
export interface RowDataProps {
|
||||
@@ -21,15 +20,39 @@ export interface RowDataProps {
|
||||
onBlockHeight: (blockHeight: number) => void;
|
||||
}
|
||||
|
||||
export const RowData = ({
|
||||
id,
|
||||
url,
|
||||
highestBlock,
|
||||
onBlockHeight,
|
||||
}: RowDataProps) => {
|
||||
const [subFailed, setSubFailed] = useState(false);
|
||||
const [time, setTime] = useState<number>();
|
||||
// no use of data here as we need the data nodes reference to block height
|
||||
export enum Result {
|
||||
Successful,
|
||||
Failed,
|
||||
Loading,
|
||||
}
|
||||
|
||||
export const useNodeSubscriptionStatus = () => {
|
||||
const [status, setStatus] = useState<Result>(Result.Loading);
|
||||
const { data, error } = useNodeCheckTimeUpdateSubscription();
|
||||
useEffect(() => {
|
||||
if (error) {
|
||||
setStatus(Result.Failed);
|
||||
}
|
||||
if (data?.busEvents && data.busEvents.length > 0) {
|
||||
setStatus(Result.Successful);
|
||||
}
|
||||
// set as failed when no data received after SUBSCRIPTION_TIMEOUT ms
|
||||
const timeout = setTimeout(() => {
|
||||
if (!data || error) {
|
||||
setStatus(Result.Failed);
|
||||
}
|
||||
}, SUBSCRIPTION_TIMEOUT);
|
||||
return () => {
|
||||
clearTimeout(timeout);
|
||||
};
|
||||
}, [data, error]);
|
||||
|
||||
return { status };
|
||||
};
|
||||
|
||||
export const useNodeBasicStatus = () => {
|
||||
const [status, setStatus] = useState<Result>(Result.Loading);
|
||||
|
||||
const { data, error, loading, startPolling, stopPolling } = useNodeCheckQuery(
|
||||
{
|
||||
pollInterval: POLL_INTERVAL,
|
||||
@@ -38,28 +61,7 @@ export const RowData = ({
|
||||
ssr: false,
|
||||
}
|
||||
);
|
||||
const headerStore = useHeaderStore();
|
||||
const headers = headerStore[url];
|
||||
|
||||
const {
|
||||
data: subData,
|
||||
error: subError,
|
||||
loading: subLoading,
|
||||
} = useNodeCheckTimeUpdateSubscription();
|
||||
|
||||
useEffect(() => {
|
||||
const timeout = setTimeout(() => {
|
||||
if (!subData) {
|
||||
setSubFailed(true);
|
||||
}
|
||||
}, 3000);
|
||||
|
||||
return () => {
|
||||
clearTimeout(timeout);
|
||||
};
|
||||
}, [subData]);
|
||||
|
||||
// handle polling
|
||||
useEffect(() => {
|
||||
const handleStartPoll = () => {
|
||||
if (error) return;
|
||||
@@ -83,56 +85,57 @@ export const RowData = ({
|
||||
};
|
||||
}, [startPolling, stopPolling, error]);
|
||||
|
||||
// measure response time
|
||||
const currentBlockHeight = parseInt(
|
||||
data?.statistics.blockHeight || 'NONE',
|
||||
10
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (loading) {
|
||||
setStatus(Result.Loading);
|
||||
return;
|
||||
}
|
||||
if (!error && !isNaN(currentBlockHeight)) {
|
||||
setStatus(Result.Successful);
|
||||
return;
|
||||
}
|
||||
setStatus(Result.Failed);
|
||||
}, [currentBlockHeight, error, loading]);
|
||||
|
||||
return {
|
||||
status,
|
||||
currentBlockHeight,
|
||||
};
|
||||
};
|
||||
|
||||
export const useResponseTime = (url: string, trigger?: unknown) => {
|
||||
const [responseTime, setResponseTime] = useState<number>();
|
||||
useEffect(() => {
|
||||
if (!isValidUrl(url)) return;
|
||||
if (typeof window.performance.getEntriesByName !== 'function') return; // protection for test environment
|
||||
// every time we get data measure response speed
|
||||
const requestUrl = new URL(url);
|
||||
const requests = window.performance.getEntriesByName(requestUrl.href);
|
||||
const { duration } =
|
||||
(requests.length && requests[requests.length - 1]) || {};
|
||||
setTime(duration);
|
||||
}, [url, data]);
|
||||
setResponseTime(duration);
|
||||
}, [url, trigger]);
|
||||
return { responseTime };
|
||||
};
|
||||
|
||||
export const RowData = ({
|
||||
id,
|
||||
url,
|
||||
highestBlock,
|
||||
onBlockHeight,
|
||||
}: RowDataProps) => {
|
||||
const { status: subStatus } = useNodeSubscriptionStatus();
|
||||
const { status, currentBlockHeight } = useNodeBasicStatus();
|
||||
const { responseTime } = useResponseTime(url, currentBlockHeight); // measure response time (ms) every time we get data (block height)
|
||||
useEffect(() => {
|
||||
if (headers?.blockHeight) {
|
||||
onBlockHeight(headers.blockHeight);
|
||||
if (!isNaN(currentBlockHeight)) {
|
||||
onBlockHeight(currentBlockHeight);
|
||||
}
|
||||
}, [headers?.blockHeight, onBlockHeight]);
|
||||
|
||||
const getHasError = () => {
|
||||
// the stats query errored
|
||||
if (error) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// if we are still awaiting a header entry its not an error
|
||||
// we are still waiting for the query to resolve
|
||||
if (!headers) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// highlight this node as 'error' if its more than BLOCK_THRESHOLD blocks behind the most
|
||||
// advanced node
|
||||
if (
|
||||
highestBlock !== null &&
|
||||
headers.blockHeight < highestBlock - BLOCK_THRESHOLD
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const getSubFailed = (
|
||||
subError: ApolloError | undefined,
|
||||
subFailed: boolean
|
||||
) => {
|
||||
if (subError) return true;
|
||||
if (subFailed) return true;
|
||||
return false;
|
||||
};
|
||||
}, [currentBlockHeight, onBlockHeight]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -143,72 +146,58 @@ export const RowData = ({
|
||||
)}
|
||||
<LayoutCell
|
||||
label={t('Response time')}
|
||||
isLoading={!error && loading}
|
||||
hasError={Boolean(error)}
|
||||
isLoading={status === Result.Loading}
|
||||
hasError={status === Result.Failed}
|
||||
dataTestId="response-time-cell"
|
||||
>
|
||||
{getResponseTimeDisplayValue(time, error)}
|
||||
{display(status, formatResponseTime(responseTime))}
|
||||
</LayoutCell>
|
||||
<LayoutCell
|
||||
label={t('Block')}
|
||||
isLoading={loading}
|
||||
hasError={getHasError()}
|
||||
isLoading={status === Result.Loading}
|
||||
hasError={
|
||||
status === Result.Failed ||
|
||||
(highestBlock != null &&
|
||||
!isNaN(currentBlockHeight) &&
|
||||
currentBlockHeight < highestBlock - BLOCK_THRESHOLD)
|
||||
}
|
||||
dataTestId="block-height-cell"
|
||||
>
|
||||
<span
|
||||
data-testid="query-block-height"
|
||||
data-query-block-height={
|
||||
error ? 'failed' : data?.statistics.blockHeight
|
||||
status === Result.Failed ? 'failed' : currentBlockHeight
|
||||
}
|
||||
>
|
||||
{getBlockDisplayValue(headers?.blockHeight, error)}
|
||||
{display(status, currentBlockHeight)}
|
||||
</span>
|
||||
</LayoutCell>
|
||||
<LayoutCell
|
||||
label={t('Subscription')}
|
||||
isLoading={subFailed ? false : subLoading}
|
||||
hasError={getSubFailed(subError, subFailed)}
|
||||
isLoading={subStatus === Result.Loading}
|
||||
hasError={subStatus === Result.Failed}
|
||||
dataTestId="subscription-cell"
|
||||
>
|
||||
{getSubscriptionDisplayValue(subFailed, subData?.busEvents, subError)}
|
||||
{display(subStatus, t('Yes'), t('No'))}
|
||||
</LayoutCell>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const getResponseTimeDisplayValue = (
|
||||
responseTime?: number,
|
||||
error?: ApolloError
|
||||
) => {
|
||||
if (error) {
|
||||
return t('n/a');
|
||||
}
|
||||
if (typeof responseTime === 'number') {
|
||||
return `${Number(responseTime).toFixed(2)}ms`;
|
||||
}
|
||||
return '-';
|
||||
};
|
||||
const formatResponseTime = (time: number | undefined) =>
|
||||
time != null ? `${Number(time).toFixed(2)}ms` : '-';
|
||||
|
||||
const getBlockDisplayValue = (block?: number, error?: ApolloError) => {
|
||||
if (error) {
|
||||
return t('n/a');
|
||||
}
|
||||
if (block) {
|
||||
return block;
|
||||
}
|
||||
return '-';
|
||||
};
|
||||
|
||||
const getSubscriptionDisplayValue = (
|
||||
subFailed: boolean,
|
||||
events?: { id: string }[] | null,
|
||||
error?: ApolloError
|
||||
const display = (
|
||||
status: Result,
|
||||
yes: string | number | undefined,
|
||||
no = t('n/a')
|
||||
) => {
|
||||
if (subFailed || error) {
|
||||
return t('No');
|
||||
switch (status) {
|
||||
case Result.Successful:
|
||||
return yes;
|
||||
case Result.Failed:
|
||||
return no;
|
||||
default:
|
||||
return '-';
|
||||
}
|
||||
if (events?.length) {
|
||||
return t('Yes');
|
||||
}
|
||||
return '-';
|
||||
};
|
||||
|
||||
@@ -22,7 +22,7 @@ export const useNodeHealth = () => {
|
||||
});
|
||||
|
||||
const blockDiff = useMemo(() => {
|
||||
if (!data?.statistics.blockHeight) {
|
||||
if (!data?.statistics?.blockHeight) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ export const useNodeHealth = () => {
|
||||
}
|
||||
|
||||
return Number(data.statistics.blockHeight) - headers.blockHeight;
|
||||
}, [data?.statistics.blockHeight, headers?.blockHeight]);
|
||||
}, [data?.statistics?.blockHeight, headers?.blockHeight]);
|
||||
|
||||
useEffect(() => {
|
||||
if (error) {
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"emitDecoratorMetadata": true,
|
||||
"outDir": ""
|
||||
},
|
||||
"files": [
|
||||
"../../../node_modules/@nx/react/typings/styled-jsx.d.ts",
|
||||
"../../../node_modules/@nx/react/typings/cssmodule.d.ts",
|
||||
"../../../node_modules/@nx/react/typings/image.d.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"../**/*.spec.ts",
|
||||
"../**/*.spec.js",
|
||||
"../**/*.spec.tsx",
|
||||
"../**/*.spec.jsx",
|
||||
"jest.config.ts"
|
||||
],
|
||||
"include": ["../src/**/*", "*.js"]
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
"tags": [],
|
||||
"targets": {
|
||||
"lint": {
|
||||
"executor": "@nx/linter:eslint",
|
||||
"executor": "@nx/eslint:lint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
"options": {
|
||||
"lintFilePatterns": ["libs/fills/**/*.{ts,tsx,js,jsx}"]
|
||||
@@ -16,14 +16,7 @@
|
||||
"executor": "@nx/jest:jest",
|
||||
"outputs": ["{workspaceRoot}/coverage/libs/fills"],
|
||||
"options": {
|
||||
"jestConfig": "libs/fills/jest.config.ts",
|
||||
"passWithNoTests": true
|
||||
},
|
||||
"configurations": {
|
||||
"ci": {
|
||||
"ci": true,
|
||||
"codeCoverage": true
|
||||
}
|
||||
"jestConfig": "libs/fills/jest.config.ts"
|
||||
}
|
||||
},
|
||||
"storybook": {
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"path": "./tsconfig.spec.json"
|
||||
},
|
||||
{
|
||||
"path": "./.storybook/tsconfig.json"
|
||||
"path": "./tsconfig.storybook.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"emitDecoratorMetadata": true,
|
||||
"outDir": ""
|
||||
},
|
||||
"files": [
|
||||
"../../node_modules/@nx/react/typings/styled-jsx.d.ts",
|
||||
"../../node_modules/@nx/react/typings/cssmodule.d.ts",
|
||||
"../../node_modules/@nx/react/typings/image.d.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.spec.js",
|
||||
"src/**/*.spec.tsx",
|
||||
"src/**/*.spec.jsx",
|
||||
"jest.config.ts"
|
||||
],
|
||||
"include": ["src/**/*", ".storybook/*.js"]
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
"tags": [],
|
||||
"targets": {
|
||||
"lint": {
|
||||
"executor": "@nx/linter:eslint",
|
||||
"executor": "@nx/eslint:lint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
"options": {
|
||||
"lintFilePatterns": ["libs/funding-payments/**/*.{ts,tsx,js,jsx}"]
|
||||
@@ -16,14 +16,7 @@
|
||||
"executor": "@nx/jest:jest",
|
||||
"outputs": ["{workspaceRoot}/coverage/libs/funding-payments"],
|
||||
"options": {
|
||||
"jestConfig": "libs/funding-payments/jest.config.ts",
|
||||
"passWithNoTests": true
|
||||
},
|
||||
"configurations": {
|
||||
"ci": {
|
||||
"ci": true,
|
||||
"codeCoverage": true
|
||||
}
|
||||
"jestConfig": "libs/funding-payments/jest.config.ts"
|
||||
}
|
||||
},
|
||||
"build-spec": {
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/linter:eslint",
|
||||
"executor": "@nx/eslint:lint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
"options": {
|
||||
"lintFilePatterns": ["libs/i18n/**/*.ts"]
|
||||
@@ -26,14 +26,7 @@
|
||||
"executor": "@nx/jest:jest",
|
||||
"outputs": ["{workspaceRoot}/coverage/libs/i18n"],
|
||||
"options": {
|
||||
"jestConfig": "libs/i18n/jest.config.ts",
|
||||
"passWithNoTests": true
|
||||
},
|
||||
"configurations": {
|
||||
"ci": {
|
||||
"ci": true,
|
||||
"codeCoverage": true
|
||||
}
|
||||
"jestConfig": "libs/i18n/jest.config.ts"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"projectType": "library",
|
||||
"targets": {
|
||||
"lint": {
|
||||
"executor": "@nx/linter:eslint",
|
||||
"executor": "@nx/eslint:lint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
"options": {
|
||||
"lintFilePatterns": ["libs/ledger/**/*.ts"]
|
||||
@@ -15,14 +15,7 @@
|
||||
"executor": "@nx/jest:jest",
|
||||
"outputs": ["{workspaceRoot}/coverage/libs/ledger"],
|
||||
"options": {
|
||||
"jestConfig": "libs/ledger/jest.config.ts",
|
||||
"passWithNoTests": true
|
||||
},
|
||||
"configurations": {
|
||||
"ci": {
|
||||
"ci": true,
|
||||
"codeCoverage": true
|
||||
}
|
||||
"jestConfig": "libs/ledger/jest.config.ts"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"tags": [],
|
||||
"targets": {
|
||||
"lint": {
|
||||
"executor": "@nx/linter:eslint",
|
||||
"executor": "@nx/eslint:lint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
"options": {
|
||||
"lintFilePatterns": ["libs/liquidity/**/*.{ts,tsx,js,jsx}"]
|
||||
@@ -16,14 +16,7 @@
|
||||
"executor": "@nx/jest:jest",
|
||||
"outputs": ["{workspaceRoot}/coverage/libs/liquidity"],
|
||||
"options": {
|
||||
"jestConfig": "libs/liquidity/jest.config.js",
|
||||
"passWithNoTests": true
|
||||
},
|
||||
"configurations": {
|
||||
"ci": {
|
||||
"ci": true,
|
||||
"codeCoverage": true
|
||||
}
|
||||
"jestConfig": "libs/liquidity/jest.config.js"
|
||||
}
|
||||
},
|
||||
"build-spec": {
|
||||
|
||||
@@ -37,7 +37,7 @@ describe('LiquidityTable', () => {
|
||||
);
|
||||
});
|
||||
|
||||
const headers = await screen.getAllByRole('columnheader');
|
||||
const headers = await screen.findAllByRole('columnheader');
|
||||
|
||||
const headerTexts = headers.map((h) =>
|
||||
h.querySelector('[ref="eText"]')?.textContent?.trim()
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"tags": [],
|
||||
"targets": {
|
||||
"lint": {
|
||||
"executor": "@nx/linter:eslint",
|
||||
"executor": "@nx/eslint:lint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
"options": {
|
||||
"lintFilePatterns": ["libs/logger/**/*.{ts,tsx,js,jsx}"]
|
||||
@@ -16,14 +16,7 @@
|
||||
"executor": "@nx/jest:jest",
|
||||
"outputs": ["{workspaceRoot}/coverage/libs/logger"],
|
||||
"options": {
|
||||
"jestConfig": "libs/logger/jest.config.ts",
|
||||
"passWithNoTests": true
|
||||
},
|
||||
"configurations": {
|
||||
"ci": {
|
||||
"ci": true,
|
||||
"codeCoverage": true
|
||||
}
|
||||
"jestConfig": "libs/logger/jest.config.ts"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"emitDecoratorMetadata": true,
|
||||
"outDir": ""
|
||||
},
|
||||
"files": [
|
||||
"../../../node_modules/@nx/react/typings/styled-jsx.d.ts",
|
||||
"../../../node_modules/@nx/react/typings/cssmodule.d.ts",
|
||||
"../../../node_modules/@nx/react/typings/image.d.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"../**/*.spec.ts",
|
||||
"../**/*.spec.js",
|
||||
"../**/*.spec.tsx",
|
||||
"../**/*.spec.jsx",
|
||||
"jest.config.ts"
|
||||
],
|
||||
"include": ["../src/**/*", "*.js"]
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
"tags": [],
|
||||
"targets": {
|
||||
"lint": {
|
||||
"executor": "@nx/linter:eslint",
|
||||
"executor": "@nx/eslint:lint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
"options": {
|
||||
"lintFilePatterns": ["libs/market-depth/**/*.{ts,tsx,js,jsx}"]
|
||||
@@ -16,14 +16,7 @@
|
||||
"executor": "@nx/jest:jest",
|
||||
"outputs": ["{workspaceRoot}/coverage/libs/market-depth"],
|
||||
"options": {
|
||||
"jestConfig": "libs/market-depth/jest.config.ts",
|
||||
"passWithNoTests": true
|
||||
},
|
||||
"configurations": {
|
||||
"ci": {
|
||||
"ci": true,
|
||||
"codeCoverage": true
|
||||
}
|
||||
"jestConfig": "libs/market-depth/jest.config.ts"
|
||||
}
|
||||
},
|
||||
"storybook": {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user