fix(governance): better vote error handling (#4579)
This commit is contained in:
parent
546b710093
commit
5b0bd69710
@ -55,7 +55,7 @@ export const Proposal = ({
|
|||||||
mostRecentlyEnactedAssociatedMarketProposal,
|
mostRecentlyEnactedAssociatedMarketProposal,
|
||||||
}: ProposalProps) => {
|
}: ProposalProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { submit, Dialog, finalizedVote } = useVoteSubmit();
|
const { submit, Dialog, finalizedVote, transaction } = useVoteSubmit();
|
||||||
const { voteState, voteDatetime } = useUserVote(proposal?.id, finalizedVote);
|
const { voteState, voteDatetime } = useUserVote(proposal?.id, finalizedVote);
|
||||||
|
|
||||||
if (!proposal) {
|
if (!proposal) {
|
||||||
@ -215,6 +215,7 @@ export const Proposal = ({
|
|||||||
}
|
}
|
||||||
submit={submit}
|
submit={submit}
|
||||||
dialog={Dialog}
|
dialog={Dialog}
|
||||||
|
transaction={transaction}
|
||||||
voteState={voteState}
|
voteState={voteState}
|
||||||
voteDatetime={voteDatetime}
|
voteDatetime={voteDatetime}
|
||||||
/>
|
/>
|
||||||
|
@ -0,0 +1,110 @@
|
|||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import { VoteTransactionDialog } from './vote-transaction-dialog';
|
||||||
|
import { VoteState } from './use-user-vote';
|
||||||
|
import { VegaTxStatus } from '@vegaprotocol/wallet';
|
||||||
|
|
||||||
|
describe('VoteTransactionDialog', () => {
|
||||||
|
const mockTransactionDialog = jest.fn(({ title, content }) => (
|
||||||
|
<div>
|
||||||
|
<div>{title}</div>
|
||||||
|
<div>{content?.Complete}</div>
|
||||||
|
</div>
|
||||||
|
));
|
||||||
|
|
||||||
|
it('renders without crashing', () => {
|
||||||
|
render(
|
||||||
|
<VoteTransactionDialog
|
||||||
|
voteState={VoteState.Yes}
|
||||||
|
transaction={null}
|
||||||
|
TransactionDialog={mockTransactionDialog}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByTestId('vote-transaction-dialog')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders with txRequested title when voteState is Requested', () => {
|
||||||
|
render(
|
||||||
|
<VoteTransactionDialog
|
||||||
|
voteState={VoteState.Requested}
|
||||||
|
transaction={null}
|
||||||
|
TransactionDialog={mockTransactionDialog}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText('txRequested')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders with votePending title when voteState is Pending', () => {
|
||||||
|
render(
|
||||||
|
<VoteTransactionDialog
|
||||||
|
voteState={VoteState.Pending}
|
||||||
|
transaction={null}
|
||||||
|
TransactionDialog={mockTransactionDialog}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText('votePending')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders with no title when voteState is neither Requested nor Pending', () => {
|
||||||
|
render(
|
||||||
|
<VoteTransactionDialog
|
||||||
|
voteState={VoteState.Yes} // or any other state other than Requested or Pending
|
||||||
|
transaction={null}
|
||||||
|
TransactionDialog={mockTransactionDialog}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.queryByText('txRequested')).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('votePending')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders custom error message when voteState is Failed and error message exists', () => {
|
||||||
|
render(
|
||||||
|
<VoteTransactionDialog
|
||||||
|
voteState={VoteState.Failed}
|
||||||
|
transaction={{
|
||||||
|
error: { message: 'Custom error test message', name: 'blah' },
|
||||||
|
txHash: null,
|
||||||
|
signature: null,
|
||||||
|
status: VegaTxStatus.Error,
|
||||||
|
dialogOpen: false,
|
||||||
|
}}
|
||||||
|
TransactionDialog={mockTransactionDialog}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText('Custom error test message')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders default error message when voteState is failed and no error message exists on the tx', () => {
|
||||||
|
render(
|
||||||
|
<VoteTransactionDialog
|
||||||
|
voteState={VoteState.Failed}
|
||||||
|
transaction={{
|
||||||
|
error: null,
|
||||||
|
txHash: null,
|
||||||
|
signature: null,
|
||||||
|
status: VegaTxStatus.Error,
|
||||||
|
dialogOpen: false,
|
||||||
|
}}
|
||||||
|
TransactionDialog={mockTransactionDialog}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText('voteError')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders default ui (i.e. not error) when not in a failed state', () => {
|
||||||
|
render(
|
||||||
|
<VoteTransactionDialog
|
||||||
|
voteState={VoteState.Yes}
|
||||||
|
transaction={null}
|
||||||
|
TransactionDialog={mockTransactionDialog}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.queryByText('voteError')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
@ -1,4 +1,4 @@
|
|||||||
import { render, screen, fireEvent } from '@testing-library/react';
|
import { fireEvent, render, screen } from '@testing-library/react';
|
||||||
import BigNumber from 'bignumber.js';
|
import BigNumber from 'bignumber.js';
|
||||||
import { VoteButtons } from './vote-buttons';
|
import { VoteButtons } from './vote-buttons';
|
||||||
import { VoteState } from './use-user-vote';
|
import { VoteState } from './use-user-vote';
|
||||||
@ -24,6 +24,7 @@ describe('Vote buttons', () => {
|
|||||||
currentStakeAvailable={new BigNumber(1)}
|
currentStakeAvailable={new BigNumber(1)}
|
||||||
dialog={() => <div>Blah</div>}
|
dialog={() => <div>Blah</div>}
|
||||||
submit={() => Promise.resolve()}
|
submit={() => Promise.resolve()}
|
||||||
|
transaction={null}
|
||||||
/>
|
/>
|
||||||
</VegaWalletContext.Provider>
|
</VegaWalletContext.Provider>
|
||||||
</MockedProvider>
|
</MockedProvider>
|
||||||
@ -47,6 +48,7 @@ describe('Vote buttons', () => {
|
|||||||
currentStakeAvailable={new BigNumber(1)}
|
currentStakeAvailable={new BigNumber(1)}
|
||||||
dialog={() => <div>Blah</div>}
|
dialog={() => <div>Blah</div>}
|
||||||
submit={() => Promise.resolve()}
|
submit={() => Promise.resolve()}
|
||||||
|
transaction={null}
|
||||||
/>
|
/>
|
||||||
</VegaWalletContext.Provider>
|
</VegaWalletContext.Provider>
|
||||||
</MockedProvider>
|
</MockedProvider>
|
||||||
@ -81,6 +83,7 @@ describe('Vote buttons', () => {
|
|||||||
currentStakeAvailable={new BigNumber(1)}
|
currentStakeAvailable={new BigNumber(1)}
|
||||||
dialog={() => <div>Blah</div>}
|
dialog={() => <div>Blah</div>}
|
||||||
submit={() => Promise.resolve()}
|
submit={() => Promise.resolve()}
|
||||||
|
transaction={null}
|
||||||
/>
|
/>
|
||||||
</VegaWalletContext.Provider>
|
</VegaWalletContext.Provider>
|
||||||
</MockedProvider>
|
</MockedProvider>
|
||||||
@ -105,6 +108,7 @@ describe('Vote buttons', () => {
|
|||||||
currentStakeAvailable={new BigNumber(0)}
|
currentStakeAvailable={new BigNumber(0)}
|
||||||
dialog={() => <div>Blah</div>}
|
dialog={() => <div>Blah</div>}
|
||||||
submit={() => Promise.resolve()}
|
submit={() => Promise.resolve()}
|
||||||
|
transaction={null}
|
||||||
/>
|
/>
|
||||||
</VegaWalletContext.Provider>
|
</VegaWalletContext.Provider>
|
||||||
</MockedProvider>
|
</MockedProvider>
|
||||||
@ -132,6 +136,7 @@ describe('Vote buttons', () => {
|
|||||||
currentStakeAvailable={new BigNumber(1)}
|
currentStakeAvailable={new BigNumber(1)}
|
||||||
dialog={() => <div>Blah</div>}
|
dialog={() => <div>Blah</div>}
|
||||||
submit={() => Promise.resolve()}
|
submit={() => Promise.resolve()}
|
||||||
|
transaction={null}
|
||||||
/>
|
/>
|
||||||
</VegaWalletContext.Provider>
|
</VegaWalletContext.Provider>
|
||||||
</MockedProvider>
|
</MockedProvider>
|
||||||
@ -159,6 +164,7 @@ describe('Vote buttons', () => {
|
|||||||
currentStakeAvailable={new BigNumber(10)}
|
currentStakeAvailable={new BigNumber(10)}
|
||||||
dialog={() => <div>Blah</div>}
|
dialog={() => <div>Blah</div>}
|
||||||
submit={() => Promise.resolve()}
|
submit={() => Promise.resolve()}
|
||||||
|
transaction={null}
|
||||||
/>
|
/>
|
||||||
</VegaWalletContext.Provider>
|
</VegaWalletContext.Provider>
|
||||||
</MockedProvider>
|
</MockedProvider>
|
||||||
@ -183,6 +189,7 @@ describe('Vote buttons', () => {
|
|||||||
currentStakeAvailable={new BigNumber(10)}
|
currentStakeAvailable={new BigNumber(10)}
|
||||||
dialog={() => <div>Blah</div>}
|
dialog={() => <div>Blah</div>}
|
||||||
submit={() => Promise.resolve()}
|
submit={() => Promise.resolve()}
|
||||||
|
transaction={null}
|
||||||
/>
|
/>
|
||||||
</VegaWalletContext.Provider>
|
</VegaWalletContext.Provider>
|
||||||
</MockedProvider>
|
</MockedProvider>
|
||||||
|
@ -17,7 +17,7 @@ import { VoteState } from './use-user-vote';
|
|||||||
import { ProposalMinRequirements, ProposalUserAction } from '../shared';
|
import { ProposalMinRequirements, ProposalUserAction } from '../shared';
|
||||||
import { VoteTransactionDialog } from './vote-transaction-dialog';
|
import { VoteTransactionDialog } from './vote-transaction-dialog';
|
||||||
import { useVoteButtonsQuery } from './__generated__/Stake';
|
import { useVoteButtonsQuery } from './__generated__/Stake';
|
||||||
import type { DialogProps } from '@vegaprotocol/wallet';
|
import type { DialogProps, VegaTxState } from '@vegaprotocol/wallet';
|
||||||
|
|
||||||
interface VoteButtonsContainerProps {
|
interface VoteButtonsContainerProps {
|
||||||
voteState: VoteState | null;
|
voteState: VoteState | null;
|
||||||
@ -27,6 +27,7 @@ interface VoteButtonsContainerProps {
|
|||||||
minVoterBalance: string | null | undefined;
|
minVoterBalance: string | null | undefined;
|
||||||
spamProtectionMinTokens: string | null | undefined;
|
spamProtectionMinTokens: string | null | undefined;
|
||||||
submit: (voteValue: VoteValue, proposalId: string | null) => Promise<void>;
|
submit: (voteValue: VoteValue, proposalId: string | null) => Promise<void>;
|
||||||
|
transaction: VegaTxState | null;
|
||||||
dialog: (props: DialogProps) => JSX.Element;
|
dialog: (props: DialogProps) => JSX.Element;
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
@ -67,6 +68,7 @@ export const VoteButtons = ({
|
|||||||
minVoterBalance,
|
minVoterBalance,
|
||||||
spamProtectionMinTokens,
|
spamProtectionMinTokens,
|
||||||
submit,
|
submit,
|
||||||
|
transaction,
|
||||||
dialog: Dialog,
|
dialog: Dialog,
|
||||||
}: VoteButtonsProps) => {
|
}: VoteButtonsProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -208,7 +210,11 @@ export const VoteButtons = ({
|
|||||||
</p>
|
</p>
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
<VoteTransactionDialog voteState={voteState} TransactionDialog={Dialog} />
|
<VoteTransactionDialog
|
||||||
|
voteState={voteState}
|
||||||
|
transaction={transaction}
|
||||||
|
TransactionDialog={Dialog}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -12,7 +12,7 @@ import { VoteButtonsContainer } from './vote-buttons';
|
|||||||
import { SubHeading } from '../../../../components/heading';
|
import { SubHeading } from '../../../../components/heading';
|
||||||
import { ProposalType } from '../proposal/proposal';
|
import { ProposalType } from '../proposal/proposal';
|
||||||
import type { VoteValue } from '@vegaprotocol/types';
|
import type { VoteValue } from '@vegaprotocol/types';
|
||||||
import type { DialogProps } from '@vegaprotocol/wallet';
|
import type { DialogProps, VegaTxState } from '@vegaprotocol/wallet';
|
||||||
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
|
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
|
||||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||||
import type { VoteState } from './use-user-vote';
|
import type { VoteState } from './use-user-vote';
|
||||||
@ -22,6 +22,7 @@ interface VoteDetailsProps {
|
|||||||
minVoterBalance: string | null | undefined;
|
minVoterBalance: string | null | undefined;
|
||||||
spamProtectionMinTokens: string | null | undefined;
|
spamProtectionMinTokens: string | null | undefined;
|
||||||
proposalType: ProposalType | null;
|
proposalType: ProposalType | null;
|
||||||
|
transaction: VegaTxState | null;
|
||||||
submit: (voteValue: VoteValue, proposalId: string | null) => Promise<void>;
|
submit: (voteValue: VoteValue, proposalId: string | null) => Promise<void>;
|
||||||
dialog: (props: DialogProps) => JSX.Element;
|
dialog: (props: DialogProps) => JSX.Element;
|
||||||
voteState: VoteState | null;
|
voteState: VoteState | null;
|
||||||
@ -34,6 +35,7 @@ export const VoteDetails = ({
|
|||||||
spamProtectionMinTokens,
|
spamProtectionMinTokens,
|
||||||
proposalType,
|
proposalType,
|
||||||
submit,
|
submit,
|
||||||
|
transaction,
|
||||||
dialog,
|
dialog,
|
||||||
voteState,
|
voteState,
|
||||||
voteDatetime,
|
voteDatetime,
|
||||||
@ -228,6 +230,7 @@ export const VoteDetails = ({
|
|||||||
spamProtectionMinTokens={spamProtectionMinTokens}
|
spamProtectionMinTokens={spamProtectionMinTokens}
|
||||||
className="flex"
|
className="flex"
|
||||||
submit={submit}
|
submit={submit}
|
||||||
|
transaction={transaction}
|
||||||
dialog={dialog}
|
dialog={dialog}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { t } from '@vegaprotocol/i18n';
|
import { t } from '@vegaprotocol/i18n';
|
||||||
import { VoteState } from './use-user-vote';
|
import { VoteState } from './use-user-vote';
|
||||||
import type { DialogProps } from '@vegaprotocol/wallet';
|
import type { DialogProps, VegaTxState } from '@vegaprotocol/wallet';
|
||||||
|
|
||||||
interface VoteTransactionDialogProps {
|
interface VoteTransactionDialogProps {
|
||||||
voteState: VoteState;
|
voteState: VoteState;
|
||||||
|
transaction: VegaTxState | null;
|
||||||
TransactionDialog: (props: DialogProps) => JSX.Element;
|
TransactionDialog: (props: DialogProps) => JSX.Element;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -20,12 +21,15 @@ const dialogTitle = (voteState: VoteState): string | undefined => {
|
|||||||
|
|
||||||
export const VoteTransactionDialog = ({
|
export const VoteTransactionDialog = ({
|
||||||
voteState,
|
voteState,
|
||||||
|
transaction,
|
||||||
TransactionDialog,
|
TransactionDialog,
|
||||||
}: VoteTransactionDialogProps) => {
|
}: VoteTransactionDialogProps) => {
|
||||||
// Render a custom message if the voting fails otherwise
|
// Render a custom message if the voting fails otherwise
|
||||||
// pass undefined so that the default vega transaction dialog UI gets used
|
// pass undefined so that the default vega transaction dialog UI gets used
|
||||||
const customMessage =
|
const customMessage =
|
||||||
voteState === VoteState.Failed ? <p>{t('voteError')}</p> : undefined;
|
voteState === VoteState.Failed ? (
|
||||||
|
<p>{transaction?.error?.message || t('voteError')}</p>
|
||||||
|
) : undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-testid="vote-transaction-dialog">
|
<div data-testid="vote-transaction-dialog">
|
||||||
|
Loading…
Reference in New Issue
Block a user