Feat/1446: Add UpdateAsset proposal form (#1558)
* Feat/1446: Add UpdateAsset proposal form * Feat/1446: Formatting fix * Feat/1446: changes from PR comments * Feat/1446: Fix for mockWalletContext shape and lint fix * Feat/1446: Fix more tests reliant on the updated mockPubkey shape change * Feat/1446: Adding the update asset proposal terms interface into the new location Co-authored-by: Bartłomiej Głownia <bglownia@gmail.com> Co-authored-by: Dexter <dexter.edwards93@gmail.com>
This commit is contained in:
parent
b2619eac7c
commit
46d03826cc
@ -636,6 +636,7 @@
|
||||
"NewMarketProposal": "New market proposal",
|
||||
"UpdateMarketProposal": "Update market proposal",
|
||||
"NewAssetProposal": "New asset proposal",
|
||||
"UpdateAssetProposal": "Update asset proposal",
|
||||
"NewFreeformProposal": "New freeform proposal",
|
||||
"NewRawProposal": "New raw proposal",
|
||||
"MinProposalRequirements": "You must have at least {{value}} VEGA associated to make a proposal",
|
||||
@ -647,6 +648,7 @@
|
||||
"NewMarket": "New market",
|
||||
"UpdateMarket": "Update market",
|
||||
"NewAsset": "New asset",
|
||||
"UpdateAsset": "Update asset",
|
||||
"Freeform": "Freeform",
|
||||
"RawProposal": "Let me choose (raw proposal)",
|
||||
"UseMin": "Use minimum",
|
||||
|
@ -26,7 +26,7 @@ import {
|
||||
lastWeek,
|
||||
nextWeek,
|
||||
} from '../../test-helpers/mocks';
|
||||
import type { ProposalsConnection_proposalsConnection_edges_node as ProposalNode } from '@vegaprotocol/governance';
|
||||
import type { Proposals_proposalsConnection_edges_node as ProposalNode } from '../../proposals/__generated__/Proposals';
|
||||
|
||||
const renderComponent = (
|
||||
proposal: ProposalNode,
|
||||
@ -172,7 +172,7 @@ describe('Proposals list item details', () => {
|
||||
datetime: lastWeek.toString(),
|
||||
party: {
|
||||
__typename: 'Party',
|
||||
id: mockPubkey,
|
||||
id: mockPubkey.publicKey,
|
||||
stakingSummary: {
|
||||
__typename: 'StakingSummary',
|
||||
currentStakeAvailable: '1000',
|
||||
@ -210,7 +210,7 @@ describe('Proposals list item details', () => {
|
||||
datetime: lastWeek.toString(),
|
||||
party: {
|
||||
__typename: 'Party',
|
||||
id: mockPubkey,
|
||||
id: mockPubkey.publicKey,
|
||||
stakingSummary: {
|
||||
__typename: 'StakingSummary',
|
||||
currentStakeAvailable: '1000',
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { ProposeFreeform } from './propose-freeform';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { mockWalletContext } from '../../test-helpers/mocks';
|
||||
@ -81,16 +81,14 @@ describe('Propose Freeform', () => {
|
||||
|
||||
it('should render the title', async () => {
|
||||
renderComponent();
|
||||
await waitFor(() =>
|
||||
expect(screen.getByText('New freeform proposal')).toBeInTheDocument()
|
||||
);
|
||||
expect(
|
||||
await screen.findByText('New freeform proposal')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render the form components', async () => {
|
||||
renderComponent();
|
||||
await waitFor(() =>
|
||||
expect(screen.getByTestId('freeform-proposal-form')).toBeTruthy()
|
||||
);
|
||||
expect(await screen.findByTestId('freeform-proposal-form')).toBeTruthy();
|
||||
expect(screen.getByTestId('min-proposal-requirements')).toBeTruthy();
|
||||
expect(screen.getByTestId('proposal-docs-link')).toBeTruthy();
|
||||
expect(screen.getByTestId('proposal-title')).toBeTruthy();
|
||||
|
@ -27,7 +27,7 @@ export interface FreeformProposalFormFields {
|
||||
proposalReference: string;
|
||||
}
|
||||
|
||||
const docsLink = 'freeform-proposal';
|
||||
const DOCS_LINK = 'freeform-proposal';
|
||||
|
||||
export const ProposeFreeform = () => {
|
||||
const { params, loading, error } = useNetworkParams([
|
||||
@ -76,9 +76,9 @@ export const ProposeFreeform = () => {
|
||||
<p className="text-sm" data-testid="proposal-docs-link">
|
||||
<span className="mr-1">{t('ProposalTermsText')}</span>
|
||||
<Link
|
||||
href={`${VEGA_DOCS_URL}/tutorials/proposals/${docsLink}`}
|
||||
href={`${VEGA_DOCS_URL}/tutorials/proposals/${DOCS_LINK}`}
|
||||
target="_blank"
|
||||
>{`${VEGA_DOCS_URL}/tutorials/proposals/${docsLink}`}</Link>
|
||||
>{`${VEGA_DOCS_URL}/tutorials/proposals/${DOCS_LINK}`}</Link>
|
||||
</p>
|
||||
)}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import { ProposeNetworkParameter } from './propose-network-parameter';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { mockWalletContext } from '../../test-helpers/mocks';
|
||||
@ -81,16 +81,16 @@ describe('Propose Network Parameter', () => {
|
||||
|
||||
it('should render the correct title', async () => {
|
||||
renderComponent();
|
||||
await waitFor(() =>
|
||||
expect(screen.getByText('Update network parameter proposal')).toBeTruthy()
|
||||
);
|
||||
expect(
|
||||
await screen.findByText('Update network parameter proposal')
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should render the form components', async () => {
|
||||
renderComponent();
|
||||
await waitFor(() =>
|
||||
expect(screen.getByTestId('network-parameter-proposal-form')).toBeTruthy()
|
||||
);
|
||||
expect(
|
||||
await screen.findByTestId('network-parameter-proposal-form')
|
||||
).toBeTruthy();
|
||||
expect(screen.getByTestId('min-proposal-requirements')).toBeTruthy();
|
||||
expect(screen.getByTestId('proposal-docs-link')).toBeTruthy();
|
||||
expect(screen.getByTestId('proposal-title')).toBeTruthy();
|
||||
@ -103,15 +103,15 @@ describe('Propose Network Parameter', () => {
|
||||
|
||||
it('should render the network param select element with no initial value', async () => {
|
||||
renderComponent();
|
||||
await waitFor(() =>
|
||||
expect(screen.getByTestId('proposal-parameter-select')).toHaveValue('')
|
||||
expect(await screen.findByTestId('proposal-parameter-select')).toHaveValue(
|
||||
''
|
||||
);
|
||||
});
|
||||
|
||||
it('should render the current param value and a new value input when the network param select element is changed', async () => {
|
||||
renderComponent();
|
||||
await waitFor(() =>
|
||||
expect(screen.getByTestId('proposal-parameter-select')).toHaveValue('')
|
||||
expect(await screen.findByTestId('proposal-parameter-select')).toHaveValue(
|
||||
''
|
||||
);
|
||||
|
||||
fireEvent.change(screen.getByTestId('proposal-parameter-select'), {
|
||||
|
@ -69,7 +69,7 @@ export interface NetworkParameterProposalFormFields {
|
||||
proposalReference: string;
|
||||
}
|
||||
|
||||
const docsLink = '/network-parameter-proposal';
|
||||
const DOCS_LINK = '/network-parameter-proposal';
|
||||
|
||||
export const ProposeNetworkParameter = () => {
|
||||
const [selectedNetworkParam, setSelectedNetworkParam] = useState<
|
||||
@ -141,9 +141,9 @@ export const ProposeNetworkParameter = () => {
|
||||
<p className="text-sm" data-testid="proposal-docs-link">
|
||||
<span className="mr-1">{t('ProposalTermsText')}</span>
|
||||
<Link
|
||||
href={`${VEGA_DOCS_URL}/tutorials/proposals${docsLink}`}
|
||||
href={`${VEGA_DOCS_URL}/tutorials/proposals${DOCS_LINK}`}
|
||||
target="_blank"
|
||||
>{`${VEGA_DOCS_URL}/tutorials/proposals${docsLink}`}</Link>
|
||||
>{`${VEGA_DOCS_URL}/tutorials/proposals${DOCS_LINK}`}</Link>
|
||||
</p>
|
||||
)}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { MemoryRouter as Router } from 'react-router-dom';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
|
||||
import { mockWalletContext } from '../../test-helpers/mocks';
|
||||
@ -81,16 +81,12 @@ describe('Propose New Asset', () => {
|
||||
|
||||
it('should render the title', async () => {
|
||||
renderComponent();
|
||||
await waitFor(() =>
|
||||
expect(screen.getByText('New asset proposal')).toBeTruthy()
|
||||
);
|
||||
expect(await screen.findByText('New asset proposal')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should render the form components', async () => {
|
||||
renderComponent();
|
||||
await waitFor(() =>
|
||||
expect(screen.getByTestId('new-asset-proposal-form')).toBeTruthy()
|
||||
);
|
||||
expect(await screen.findByTestId('new-asset-proposal-form')).toBeTruthy();
|
||||
expect(screen.getByTestId('min-proposal-requirements')).toBeTruthy();
|
||||
expect(screen.getByTestId('proposal-docs-link')).toBeTruthy();
|
||||
expect(screen.getByTestId('proposal-title')).toBeTruthy();
|
||||
|
@ -7,6 +7,7 @@ import {
|
||||
getValidationTimestamp,
|
||||
} from '@vegaprotocol/governance';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { validateJson } from '@vegaprotocol/react-helpers';
|
||||
import {
|
||||
ProposalFormMinRequirements,
|
||||
ProposalFormTitle,
|
||||
@ -32,7 +33,7 @@ export interface NewAssetProposalFormFields {
|
||||
proposalReference: string;
|
||||
}
|
||||
|
||||
const docsLink = '/new-asset-proposal';
|
||||
const DOCS_LINK = '/new-asset-proposal';
|
||||
|
||||
export const ProposeNewAsset = () => {
|
||||
const {
|
||||
@ -100,9 +101,9 @@ export const ProposeNewAsset = () => {
|
||||
<p className="text-sm" data-testid="proposal-docs-link">
|
||||
<span className="mr-1">{t('ProposalTermsText')}</span>
|
||||
<Link
|
||||
href={`${VEGA_DOCS_URL}/tutorials/proposals${docsLink}`}
|
||||
href={`${VEGA_DOCS_URL}/tutorials/proposals${DOCS_LINK}`}
|
||||
target="_blank"
|
||||
>{`${VEGA_DOCS_URL}/tutorials/proposals${docsLink}`}</Link>
|
||||
>{`${VEGA_DOCS_URL}/tutorials/proposals${DOCS_LINK}`}</Link>
|
||||
</p>
|
||||
)}
|
||||
|
||||
@ -141,20 +142,11 @@ export const ProposeNewAsset = () => {
|
||||
<ProposalFormTerms
|
||||
registerField={register('proposalTerms', {
|
||||
required: t('Required'),
|
||||
validate: {
|
||||
validateJson: (value) => {
|
||||
try {
|
||||
JSON.parse(value);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return t('Must be valid JSON');
|
||||
}
|
||||
},
|
||||
},
|
||||
validate: (value) => validateJson(value),
|
||||
})}
|
||||
labelOverride={'Terms.newAsset (JSON format)'}
|
||||
errorMessage={errors?.proposalTerms?.message}
|
||||
customDocLink={docsLink}
|
||||
customDocLink={DOCS_LINK}
|
||||
/>
|
||||
|
||||
<ProposalFormVoteAndEnactmentDeadline
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { ProposeNewMarket } from './propose-new-market';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { mockWalletContext } from '../../test-helpers/mocks';
|
||||
@ -81,9 +81,7 @@ describe('Propose New Market', () => {
|
||||
|
||||
it('should render the form components', async () => {
|
||||
renderComponent();
|
||||
await waitFor(() =>
|
||||
expect(screen.getByTestId('new-market-proposal-form')).toBeTruthy()
|
||||
);
|
||||
expect(await screen.findByTestId('new-market-proposal-form')).toBeTruthy();
|
||||
expect(screen.getByTestId('min-proposal-requirements')).toBeTruthy();
|
||||
expect(screen.getByTestId('proposal-docs-link')).toBeTruthy();
|
||||
expect(screen.getByTestId('proposal-title')).toBeTruthy();
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
getEnactmentTimestamp,
|
||||
} from '@vegaprotocol/governance';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { validateJson } from '@vegaprotocol/react-helpers';
|
||||
import {
|
||||
ProposalFormMinRequirements,
|
||||
ProposalFormTitle,
|
||||
@ -30,7 +31,7 @@ export interface NewMarketProposalFormFields {
|
||||
proposalReference: string;
|
||||
}
|
||||
|
||||
const docsLink = '/new-market-proposal';
|
||||
const DOCS_LINK = '/new-market-proposal';
|
||||
|
||||
export const ProposeNewMarket = () => {
|
||||
const {
|
||||
@ -95,9 +96,9 @@ export const ProposeNewMarket = () => {
|
||||
<p className="text-sm" data-testid="proposal-docs-link">
|
||||
<span className="mr-1">{t('ProposalTermsText')}</span>
|
||||
<Link
|
||||
href={`${VEGA_DOCS_URL}/tutorials/proposals/${docsLink}`}
|
||||
href={`${VEGA_DOCS_URL}/tutorials/proposals${DOCS_LINK}`}
|
||||
target="_blank"
|
||||
>{`${VEGA_DOCS_URL}/tutorials/proposals/${docsLink}`}</Link>
|
||||
>{`${VEGA_DOCS_URL}/tutorials/proposals${DOCS_LINK}`}</Link>
|
||||
</p>
|
||||
)}
|
||||
|
||||
@ -136,20 +137,11 @@ export const ProposeNewMarket = () => {
|
||||
<ProposalFormTerms
|
||||
registerField={register('proposalTerms', {
|
||||
required: t('Required'),
|
||||
validate: {
|
||||
validateJson: (value) => {
|
||||
try {
|
||||
JSON.parse(value);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return t('Must be valid JSON');
|
||||
}
|
||||
},
|
||||
},
|
||||
validate: (value) => validateJson(value),
|
||||
})}
|
||||
labelOverride={'Terms.newMarket (JSON format)'}
|
||||
errorMessage={errors?.proposalTerms?.message}
|
||||
customDocLink={docsLink}
|
||||
customDocLink={DOCS_LINK}
|
||||
/>
|
||||
|
||||
<ProposalFormVoteAndEnactmentDeadline
|
||||
|
@ -24,6 +24,7 @@ describe('Propose', () => {
|
||||
expect(screen.getByText('New market')).toBeTruthy();
|
||||
expect(screen.getByText('Update market')).toBeTruthy();
|
||||
expect(screen.getByText('New asset')).toBeTruthy();
|
||||
expect(screen.getByText('Update asset')).toBeTruthy();
|
||||
expect(screen.getByText('Freeform')).toBeTruthy();
|
||||
expect(screen.getByText('Let me choose (raw proposal)')).toBeTruthy();
|
||||
});
|
||||
|
@ -79,6 +79,16 @@ export const Propose = () => {
|
||||
</Link>
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>
|
||||
<Link
|
||||
className="underline"
|
||||
to={`${Routes.GOVERNANCE}/propose/update-asset`}
|
||||
>
|
||||
{t('UpdateAsset')}
|
||||
</Link>
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>
|
||||
<Link
|
||||
|
@ -9,6 +9,7 @@ import {
|
||||
Link,
|
||||
TextArea,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { validateJson } from '@vegaprotocol/react-helpers';
|
||||
import { useProposalSubmit } from '@vegaprotocol/governance';
|
||||
import {
|
||||
ProposalFormSubmit,
|
||||
@ -74,16 +75,7 @@ export const ProposeRaw = () => {
|
||||
data-testid="proposal-data"
|
||||
{...register('rawProposalData', {
|
||||
required: t('Required'),
|
||||
validate: {
|
||||
validateJson: (value) => {
|
||||
try {
|
||||
JSON.parse(value);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return t('Must be valid JSON');
|
||||
}
|
||||
},
|
||||
},
|
||||
validate: (value) => validateJson(value),
|
||||
})}
|
||||
/>
|
||||
{errors.rawProposalData?.message && (
|
||||
|
@ -0,0 +1,4 @@
|
||||
export {
|
||||
ProposeUpdateAsset,
|
||||
ProposeUpdateAsset as default,
|
||||
} from './propose-update-asset';
|
@ -0,0 +1,102 @@
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { MemoryRouter as Router } from 'react-router-dom';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { VegaWalletContext } from '@vegaprotocol/wallet';
|
||||
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
|
||||
import { mockWalletContext } from '../../test-helpers/mocks';
|
||||
import { ProposeUpdateAsset } from './propose-update-asset';
|
||||
import type { NetworkParamsQuery } from '@vegaprotocol/web3';
|
||||
import type { MockedResponse } from '@apollo/client/testing';
|
||||
import { NETWORK_PARAMETERS_QUERY } from '@vegaprotocol/react-helpers';
|
||||
|
||||
jest.mock('@vegaprotocol/environment', () => ({
|
||||
useEnvironment: () => ({
|
||||
VEGA_DOCS_URL: 'https://docs.vega.xyz',
|
||||
}),
|
||||
}));
|
||||
|
||||
const updateAssetNetworkParamsQueryMock: MockedResponse<NetworkParamsQuery> = {
|
||||
request: {
|
||||
query: NETWORK_PARAMETERS_QUERY,
|
||||
},
|
||||
result: {
|
||||
data: {
|
||||
networkParameters: [
|
||||
{
|
||||
__typename: 'NetworkParameter',
|
||||
key: 'governance.proposal.updateAsset.maxClose',
|
||||
value: '8760h0m0s',
|
||||
},
|
||||
{
|
||||
__typename: 'NetworkParameter',
|
||||
key: 'governance.proposal.updateAsset.maxEnact',
|
||||
value: '8760h0m0s',
|
||||
},
|
||||
{
|
||||
__typename: 'NetworkParameter',
|
||||
key: 'governance.proposal.updateAsset.minClose',
|
||||
value: '1h0m0s',
|
||||
},
|
||||
{
|
||||
__typename: 'NetworkParameter',
|
||||
key: 'governance.proposal.updateAsset.minEnact',
|
||||
value: '2h0m0s',
|
||||
},
|
||||
{
|
||||
__typename: 'NetworkParameter',
|
||||
key: 'governance.proposal.updateAsset.minProposerBalance',
|
||||
value: '1',
|
||||
},
|
||||
{
|
||||
__typename: 'NetworkParameter',
|
||||
key: 'spam.protection.proposal.min.tokens',
|
||||
value: '1000000000000000000',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const renderComponent = () =>
|
||||
render(
|
||||
<Router>
|
||||
<MockedProvider mocks={[updateAssetNetworkParamsQueryMock]}>
|
||||
<AppStateProvider>
|
||||
<VegaWalletContext.Provider value={mockWalletContext}>
|
||||
<ProposeUpdateAsset />
|
||||
</VegaWalletContext.Provider>
|
||||
</AppStateProvider>
|
||||
</MockedProvider>
|
||||
</Router>
|
||||
);
|
||||
|
||||
// Note: form submission is tested in propose-raw.spec.tsx. Reusable form
|
||||
// components are tested in their own directory.
|
||||
|
||||
describe('Propose Update Asset', () => {
|
||||
it('should render successfully', async () => {
|
||||
const { baseElement } = renderComponent();
|
||||
await expect(baseElement).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should render the title', async () => {
|
||||
renderComponent();
|
||||
expect(await screen.findByText('Update asset proposal')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should render the form components', async () => {
|
||||
renderComponent();
|
||||
expect(
|
||||
await screen.findByTestId('update-asset-proposal-form')
|
||||
).toBeTruthy();
|
||||
expect(screen.getByTestId('min-proposal-requirements')).toBeTruthy();
|
||||
expect(screen.getByTestId('proposal-docs-link')).toBeTruthy();
|
||||
expect(screen.getByTestId('proposal-title')).toBeTruthy();
|
||||
expect(screen.getByTestId('proposal-description')).toBeTruthy();
|
||||
expect(screen.getByTestId('proposal-terms')).toBeTruthy();
|
||||
expect(screen.getByTestId('proposal-vote-deadline')).toBeTruthy();
|
||||
expect(screen.getByTestId('proposal-enactment-deadline')).toBeTruthy();
|
||||
expect(screen.getByTestId('proposal-submit')).toBeTruthy();
|
||||
expect(screen.getByTestId('proposal-transaction-dialog')).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,182 @@
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
useProposalSubmit,
|
||||
getClosingTimestamp,
|
||||
getEnactmentTimestamp,
|
||||
} from '@vegaprotocol/governance';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { validateJson } from '@vegaprotocol/react-helpers';
|
||||
import {
|
||||
ProposalFormMinRequirements,
|
||||
ProposalFormTitle,
|
||||
ProposalFormDescription,
|
||||
ProposalFormTerms,
|
||||
ProposalFormSubmit,
|
||||
ProposalFormTransactionDialog,
|
||||
ProposalFormSubheader,
|
||||
ProposalFormVoteAndEnactmentDeadline,
|
||||
} from '../../components/propose';
|
||||
import { AsyncRenderer, Link } from '@vegaprotocol/ui-toolkit';
|
||||
import { Heading } from '../../../../components/heading';
|
||||
import { VegaWalletContainer } from '../../../../components/vega-wallet-container';
|
||||
import { NetworkParams, useNetworkParams } from '@vegaprotocol/react-helpers';
|
||||
|
||||
export interface UpdateAssetProposalFormFields {
|
||||
proposalVoteDeadline: string;
|
||||
proposalEnactmentDeadline: string;
|
||||
proposalTitle: string;
|
||||
proposalDescription: string;
|
||||
proposalTerms: string;
|
||||
proposalReference: string;
|
||||
}
|
||||
|
||||
const DOCS_LINK = '/update-asset-proposal';
|
||||
|
||||
export const ProposeUpdateAsset = () => {
|
||||
const {
|
||||
params,
|
||||
loading: networkParamsLoading,
|
||||
error: networkParamsError,
|
||||
} = useNetworkParams([
|
||||
NetworkParams.governance_proposal_updateAsset_minClose,
|
||||
NetworkParams.governance_proposal_updateAsset_maxClose,
|
||||
NetworkParams.governance_proposal_updateAsset_minEnact,
|
||||
NetworkParams.governance_proposal_updateAsset_maxEnact,
|
||||
NetworkParams.governance_proposal_updateAsset_minProposerBalance,
|
||||
NetworkParams.spam_protection_proposal_min_tokens,
|
||||
]);
|
||||
|
||||
const { VEGA_EXPLORER_URL, VEGA_DOCS_URL } = useEnvironment();
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { isSubmitting, errors },
|
||||
} = useForm<UpdateAssetProposalFormFields>();
|
||||
const { finalizedProposal, submit, Dialog } = useProposalSubmit();
|
||||
|
||||
const onSubmit = async (fields: UpdateAssetProposalFormFields) => {
|
||||
await submit({
|
||||
rationale: {
|
||||
title: fields.proposalTitle,
|
||||
description: fields.proposalDescription,
|
||||
},
|
||||
terms: {
|
||||
updateAsset: {
|
||||
...JSON.parse(fields.proposalTerms),
|
||||
},
|
||||
closingTimestamp: getClosingTimestamp(fields.proposalVoteDeadline),
|
||||
enactmentTimestamp: getEnactmentTimestamp(
|
||||
fields.proposalVoteDeadline,
|
||||
fields.proposalEnactmentDeadline
|
||||
),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<AsyncRenderer
|
||||
loading={networkParamsLoading}
|
||||
error={networkParamsError}
|
||||
data={params}
|
||||
>
|
||||
<Heading title={t('UpdateAssetProposal')} />
|
||||
<VegaWalletContainer>
|
||||
{() => (
|
||||
<>
|
||||
<ProposalFormMinRequirements
|
||||
minProposerBalance={
|
||||
params.governance_proposal_updateAsset_minProposerBalance
|
||||
}
|
||||
spamProtectionMin={params.spam_protection_proposal_min_tokens}
|
||||
/>
|
||||
|
||||
{VEGA_DOCS_URL && (
|
||||
<p className="text-sm" data-testid="proposal-docs-link">
|
||||
<span className="mr-1">{t('ProposalTermsText')}</span>
|
||||
<Link
|
||||
href={`${VEGA_DOCS_URL}/tutorials/proposals${DOCS_LINK}`}
|
||||
target="_blank"
|
||||
>{`${VEGA_DOCS_URL}/tutorials/proposals${DOCS_LINK}`}</Link>
|
||||
</p>
|
||||
)}
|
||||
|
||||
{VEGA_EXPLORER_URL && (
|
||||
<p className="text-sm">
|
||||
{t('MoreAssetsInfo')}{' '}
|
||||
<Link
|
||||
href={`${VEGA_EXPLORER_URL}/assets`}
|
||||
target="_blank"
|
||||
>{`${VEGA_EXPLORER_URL}/assets`}</Link>
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div data-testid="update-asset-proposal-form">
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<ProposalFormSubheader>
|
||||
{t('ProposalRationale')}
|
||||
</ProposalFormSubheader>
|
||||
|
||||
<ProposalFormTitle
|
||||
registerField={register('proposalTitle', {
|
||||
required: t('Required'),
|
||||
})}
|
||||
errorMessage={errors?.proposalTitle?.message}
|
||||
/>
|
||||
|
||||
<ProposalFormDescription
|
||||
registerField={register('proposalDescription', {
|
||||
required: t('Required'),
|
||||
})}
|
||||
errorMessage={errors?.proposalDescription?.message}
|
||||
/>
|
||||
|
||||
<ProposalFormSubheader>
|
||||
{t('UpdateAsset')}
|
||||
</ProposalFormSubheader>
|
||||
|
||||
<ProposalFormTerms
|
||||
registerField={register('proposalTerms', {
|
||||
required: t('Required'),
|
||||
validate: (value) => validateJson(value),
|
||||
})}
|
||||
labelOverride={'Terms.updateAsset (JSON format)'}
|
||||
errorMessage={errors?.proposalTerms?.message}
|
||||
customDocLink={DOCS_LINK}
|
||||
/>
|
||||
|
||||
<ProposalFormVoteAndEnactmentDeadline
|
||||
voteRegister={register('proposalVoteDeadline', {
|
||||
required: t('Required'),
|
||||
})}
|
||||
voteErrorMessage={errors?.proposalVoteDeadline?.message}
|
||||
voteMinClose={params.governance_proposal_updateAsset_minClose}
|
||||
voteMaxClose={params.governance_proposal_updateAsset_maxClose}
|
||||
enactmentRegister={register('proposalEnactmentDeadline', {
|
||||
required: t('Required'),
|
||||
})}
|
||||
enactmentErrorMessage={
|
||||
errors?.proposalEnactmentDeadline?.message
|
||||
}
|
||||
enactmentMinClose={
|
||||
params.governance_proposal_updateAsset_minEnact
|
||||
}
|
||||
enactmentMaxClose={
|
||||
params.governance_proposal_updateAsset_maxEnact
|
||||
}
|
||||
/>
|
||||
|
||||
<ProposalFormSubmit isSubmitting={isSubmitting} />
|
||||
<ProposalFormTransactionDialog
|
||||
finalizedProposal={finalizedProposal}
|
||||
TransactionDialog={Dialog}
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</VegaWalletContainer>
|
||||
</AsyncRenderer>
|
||||
);
|
||||
};
|
@ -178,17 +178,17 @@ describe('Propose Update Market', () => {
|
||||
|
||||
it('should render the select element with no initial value', async () => {
|
||||
renderComponent();
|
||||
await waitFor(() =>
|
||||
expect(screen.getByText('Update market proposal')).toBeInTheDocument()
|
||||
);
|
||||
expect(
|
||||
await screen.findByText('Update market proposal')
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByTestId('proposal-market-select')).toHaveValue('');
|
||||
});
|
||||
|
||||
it('should render the correct market details when the market select is used', async () => {
|
||||
renderComponent();
|
||||
await waitFor(() =>
|
||||
expect(screen.getByText('Update market proposal')).toBeInTheDocument()
|
||||
);
|
||||
expect(
|
||||
await screen.findByText('Update market proposal')
|
||||
).toBeInTheDocument();
|
||||
fireEvent.change(screen.getByTestId('proposal-market-select'), {
|
||||
target: {
|
||||
value:
|
||||
|
@ -8,6 +8,7 @@ import {
|
||||
getEnactmentTimestamp,
|
||||
} from '@vegaprotocol/governance';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import { validateJson } from '@vegaprotocol/react-helpers';
|
||||
import {
|
||||
ProposalFormSubheader,
|
||||
ProposalFormMinRequirements,
|
||||
@ -60,7 +61,7 @@ export interface UpdateMarketProposalFormFields {
|
||||
proposalReference: string;
|
||||
}
|
||||
|
||||
const docsLink = '/update-market-proposal';
|
||||
const DOCS_LINK = '/update-market-proposal';
|
||||
|
||||
export const ProposeUpdateMarket = () => {
|
||||
const {
|
||||
@ -158,9 +159,9 @@ export const ProposeUpdateMarket = () => {
|
||||
<p className="text-sm" data-testid="proposal-docs-link">
|
||||
<span className="mr-1">{t('ProposalTermsText')}</span>
|
||||
<Link
|
||||
href={`${VEGA_DOCS_URL}/tutorials/proposals${docsLink}`}
|
||||
href={`${VEGA_DOCS_URL}/tutorials/proposals${DOCS_LINK}`}
|
||||
target="_blank"
|
||||
>{`${VEGA_DOCS_URL}/tutorials/proposals${docsLink}`}</Link>
|
||||
>{`${VEGA_DOCS_URL}/tutorials/proposals${DOCS_LINK}`}</Link>
|
||||
</p>
|
||||
)}
|
||||
|
||||
@ -255,20 +256,11 @@ export const ProposeUpdateMarket = () => {
|
||||
<ProposalFormTerms
|
||||
registerField={register('proposalTerms', {
|
||||
required: t('Required'),
|
||||
validate: {
|
||||
validateJson: (value) => {
|
||||
try {
|
||||
JSON.parse(value);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return t('Must be valid JSON');
|
||||
}
|
||||
},
|
||||
},
|
||||
validate: (value) => validateJson(value),
|
||||
})}
|
||||
labelOverride={t('ProposeUpdateMarketTerms')}
|
||||
errorMessage={errors?.proposalTerms?.message}
|
||||
customDocLink={docsLink}
|
||||
customDocLink={DOCS_LINK}
|
||||
/>
|
||||
|
||||
<ProposalFormVoteAndEnactmentDeadline
|
||||
|
@ -1,11 +1,15 @@
|
||||
import { NETWORK_PARAMS_QUERY } from '@vegaprotocol/web3';
|
||||
import type { MockedResponse } from '@apollo/client/testing';
|
||||
import type { NetworkParamsQuery } from '@vegaprotocol/web3';
|
||||
import type { PubKey } from '@vegaprotocol/wallet';
|
||||
|
||||
export const mockPubkey = '0x123';
|
||||
export const mockPubkey: PubKey = {
|
||||
publicKey: '0x123',
|
||||
name: 'test key 1',
|
||||
};
|
||||
|
||||
export const mockWalletContext = {
|
||||
pubKey: mockPubkey,
|
||||
pubKey: mockPubkey.publicKey,
|
||||
pubKeys: [mockPubkey],
|
||||
sendTx: jest.fn().mockReturnValue(Promise.resolve(null)),
|
||||
connect: jest.fn(),
|
||||
|
@ -158,6 +158,13 @@ const LazyGovernanceProposeNewAsset = React.lazy(
|
||||
)
|
||||
);
|
||||
|
||||
const LazyGovernanceProposeUpdateAsset = React.lazy(
|
||||
() =>
|
||||
import(
|
||||
/* webpackChunkName: "route-governance-propose-update-asset", webpackPrefetch: true */ './governance/propose/update-asset'
|
||||
)
|
||||
);
|
||||
|
||||
const LazyGovernanceProposeFreeform = React.lazy(
|
||||
() =>
|
||||
import(
|
||||
@ -273,6 +280,10 @@ const routerConfig = [
|
||||
element: <LazyGovernanceProposeUpdateMarket />,
|
||||
},
|
||||
{ path: 'new-asset', element: <LazyGovernanceProposeNewAsset /> },
|
||||
{
|
||||
path: 'update-asset',
|
||||
element: <LazyGovernanceProposeUpdateAsset />,
|
||||
},
|
||||
{ path: 'freeform', element: <LazyGovernanceProposeFreeform /> },
|
||||
{ path: 'raw', element: <LazyGovernanceProposeRaw /> },
|
||||
],
|
||||
|
@ -43,6 +43,14 @@ export const NetworkParams = {
|
||||
governance_proposal_asset_maxClose: 'governance_proposal_asset_maxClose',
|
||||
governance_proposal_asset_minEnact: 'governance_proposal_asset_minEnact',
|
||||
governance_proposal_asset_maxEnact: 'governance_proposal_asset_maxEnact',
|
||||
governance_proposal_updateAsset_minClose:
|
||||
'governance_proposal_updateAsset_minClose',
|
||||
governance_proposal_updateAsset_maxClose:
|
||||
'governance_proposal_updateAsset_maxClose',
|
||||
governance_proposal_updateAsset_minEnact:
|
||||
'governance_proposal_updateAsset_minEnact',
|
||||
governance_proposal_updateAsset_maxEnact:
|
||||
'governance_proposal_updateAsset_maxEnact',
|
||||
governance_proposal_updateNetParam_minClose:
|
||||
'governance_proposal_updateNetParam_minClose',
|
||||
governance_proposal_updateNetParam_maxClose:
|
||||
@ -71,6 +79,8 @@ export const NetworkParams = {
|
||||
'governance_proposal_asset_requiredParticipation',
|
||||
governance_proposal_asset_minProposerBalance:
|
||||
'governance_proposal_asset_minProposerBalance',
|
||||
governance_proposal_updateAsset_minProposerBalance:
|
||||
'governance_proposal_updateAsset_minProposerBalance',
|
||||
governance_proposal_updateNetParam_requiredMajority:
|
||||
'governance_proposal_updateNetParam_requiredMajority',
|
||||
governance_proposal_updateNetParam_requiredParticipation:
|
||||
|
@ -45,3 +45,12 @@ export const suitableForSyntaxHighlighter = (str: string) => {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export const validateJson = (value: string) => {
|
||||
try {
|
||||
JSON.parse(value);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return t('Must be valid JSON');
|
||||
}
|
||||
};
|
||||
|
@ -168,6 +168,21 @@ interface ProposalNewAssetTerms {
|
||||
validationTimestamp: number;
|
||||
}
|
||||
|
||||
interface ProposalUpdateAssetTerms {
|
||||
updateAsset: {
|
||||
assetId: string;
|
||||
changes: {
|
||||
quantum: string;
|
||||
erc20: {
|
||||
withdrawThreshold: string;
|
||||
lifetimeLimit: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
closingTimestamp: number;
|
||||
enactmentTimestamp: number;
|
||||
}
|
||||
|
||||
interface OracleSpecBinding {
|
||||
settlementPriceProperty: string;
|
||||
tradingTerminationProperty: string;
|
||||
@ -227,7 +242,8 @@ export interface ProposalSubmission {
|
||||
| ProposalNewMarketTerms
|
||||
| ProposalUpdateMarketTerms
|
||||
| ProposalNetworkParameterTerms
|
||||
| ProposalNewAssetTerms;
|
||||
| ProposalNewAssetTerms
|
||||
| ProposalUpdateAssetTerms;
|
||||
}
|
||||
|
||||
export interface ProposalSubmissionBody {
|
||||
|
Loading…
Reference in New Issue
Block a user