fix(trading): order stopped misleading message (#4455)

This commit is contained in:
m.ray 2023-08-02 13:52:07 +03:00 committed by GitHub
parent 45283161aa
commit fde49e5446
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 230 additions and 213 deletions

View File

@ -7,123 +7,125 @@ import {
} from './utils'; } from './utils';
import * as Types from '@vegaprotocol/types'; import * as Types from '@vegaprotocol/types';
describe('getOrderToastTitle', () => { describe('WithdrawalsTable', () => {
it('should return the correct title', () => { describe('getOrderToastTitle', () => {
expect(getOrderToastTitle(Types.OrderStatus.STATUS_ACTIVE)).toBe( it('should return the correct title', () => {
'Order submitted' expect(getOrderToastTitle(Types.OrderStatus.STATUS_ACTIVE)).toBe(
); 'Order submitted'
expect(getOrderToastTitle(Types.OrderStatus.STATUS_FILLED)).toBe( );
'Order filled' expect(getOrderToastTitle(Types.OrderStatus.STATUS_FILLED)).toBe(
); 'Order filled'
expect(getOrderToastTitle(Types.OrderStatus.STATUS_PARTIALLY_FILLED)).toBe( );
'Order partially filled' expect(
); getOrderToastTitle(Types.OrderStatus.STATUS_PARTIALLY_FILLED)
expect(getOrderToastTitle(Types.OrderStatus.STATUS_PARKED)).toBe( ).toBe('Order partially filled');
'Order parked' expect(getOrderToastTitle(Types.OrderStatus.STATUS_PARKED)).toBe(
); 'Order parked'
expect(getOrderToastTitle(Types.OrderStatus.STATUS_STOPPED)).toBe( );
'Order stopped' expect(getOrderToastTitle(Types.OrderStatus.STATUS_STOPPED)).toBe(
); 'Order stopped'
expect(getOrderToastTitle(Types.OrderStatus.STATUS_CANCELLED)).toBe( );
'Order cancelled' expect(getOrderToastTitle(Types.OrderStatus.STATUS_CANCELLED)).toBe(
); 'Order cancelled'
expect(getOrderToastTitle(Types.OrderStatus.STATUS_EXPIRED)).toBe( );
'Order expired' expect(getOrderToastTitle(Types.OrderStatus.STATUS_EXPIRED)).toBe(
); 'Order expired'
expect(getOrderToastTitle(Types.OrderStatus.STATUS_REJECTED)).toBe( );
'Order rejected' expect(getOrderToastTitle(Types.OrderStatus.STATUS_REJECTED)).toBe(
); 'Order rejected'
expect(getOrderToastTitle(undefined)).toBe(undefined); );
}); expect(getOrderToastTitle(undefined)).toBe(undefined);
}); });
describe('getOrderToastIntent', () => {
it('should return the correct intent', () => {
expect(getOrderToastIntent(Types.OrderStatus.STATUS_PARKED)).toBe(
Intent.Warning
);
expect(getOrderToastIntent(Types.OrderStatus.STATUS_EXPIRED)).toBe(
Intent.Warning
);
expect(getOrderToastIntent(Types.OrderStatus.STATUS_PARTIALLY_FILLED)).toBe(
Intent.Warning
);
expect(getOrderToastIntent(Types.OrderStatus.STATUS_REJECTED)).toBe(
Intent.Danger
);
expect(getOrderToastIntent(Types.OrderStatus.STATUS_STOPPED)).toBe(
Intent.Danger
);
expect(getOrderToastIntent(Types.OrderStatus.STATUS_FILLED)).toBe(
Intent.Success
);
expect(getOrderToastIntent(Types.OrderStatus.STATUS_ACTIVE)).toBe(
Intent.Success
);
expect(getOrderToastIntent(Types.OrderStatus.STATUS_CANCELLED)).toBe(
Intent.Success
);
expect(getOrderToastIntent(undefined)).toBe(undefined);
});
});
describe('getRejectionReason', () => {
it('should return the correct rejection reason for insufficient asset balance', () => {
expect(
getRejectionReason({
rejectionReason:
Types.OrderRejectionReason.ORDER_ERROR_INSUFFICIENT_ASSET_BALANCE,
status: Types.OrderStatus.STATUS_REJECTED,
id: '',
createdAt: undefined,
size: '',
price: '',
timeInForce: Types.OrderTimeInForce.TIME_IN_FORCE_FOK,
side: Types.Side.SIDE_BUY,
marketId: '',
})
).toBe('Insufficient asset balance');
}); });
it('should return the correct rejection reason when order is stopped', () => { describe('getOrderToastIntent', () => {
expect( it('should return the correct intent', () => {
getRejectionReason({ expect(getOrderToastIntent(Types.OrderStatus.STATUS_PARKED)).toBe(
rejectionReason: null, Intent.Warning
status: Types.OrderStatus.STATUS_STOPPED, );
id: '', expect(getOrderToastIntent(Types.OrderStatus.STATUS_EXPIRED)).toBe(
createdAt: undefined, Intent.Warning
size: '', );
price: '', expect(
timeInForce: Types.OrderTimeInForce.TIME_IN_FORCE_FOK, getOrderToastIntent(Types.OrderStatus.STATUS_PARTIALLY_FILLED)
side: Types.Side.SIDE_BUY, ).toBe(Intent.Warning);
marketId: '', expect(getOrderToastIntent(Types.OrderStatus.STATUS_REJECTED)).toBe(
}) Intent.Danger
).toBe( );
'Your Fill or Kill (FOK) order was not filled and it has been stopped' expect(getOrderToastIntent(Types.OrderStatus.STATUS_STOPPED)).toBe(
); Intent.Warning
);
expect(getOrderToastIntent(Types.OrderStatus.STATUS_FILLED)).toBe(
Intent.Success
);
expect(getOrderToastIntent(Types.OrderStatus.STATUS_ACTIVE)).toBe(
Intent.Success
);
expect(getOrderToastIntent(Types.OrderStatus.STATUS_CANCELLED)).toBe(
Intent.Success
);
expect(getOrderToastIntent(undefined)).toBe(undefined);
});
}); });
});
describe('timeInForceLabel', () => { describe('getRejectionReason', () => {
it('should return the correct label for time in force', () => { it('should return the correct rejection reason for insufficient asset balance', () => {
expect(timeInForceLabel(Types.OrderTimeInForce.TIME_IN_FORCE_FOK)).toBe( expect(
`Fill or Kill (FOK)` getRejectionReason({
); rejectionReason:
expect(timeInForceLabel(Types.OrderTimeInForce.TIME_IN_FORCE_GTC)).toBe( Types.OrderRejectionReason.ORDER_ERROR_INSUFFICIENT_ASSET_BALANCE,
`Good 'til Cancelled (GTC)` status: Types.OrderStatus.STATUS_REJECTED,
); id: '',
expect(timeInForceLabel(Types.OrderTimeInForce.TIME_IN_FORCE_IOC)).toBe( createdAt: undefined,
`Immediate or Cancel (IOC)` size: '',
); price: '',
expect(timeInForceLabel(Types.OrderTimeInForce.TIME_IN_FORCE_GTT)).toBe( timeInForce: Types.OrderTimeInForce.TIME_IN_FORCE_FOK,
`Good 'til Time (GTT)` side: Types.Side.SIDE_BUY,
); marketId: '',
expect(timeInForceLabel(Types.OrderTimeInForce.TIME_IN_FORCE_GFA)).toBe( })
`Good for Auction (GFA)` ).toBe('Insufficient asset balance');
); });
expect(timeInForceLabel(Types.OrderTimeInForce.TIME_IN_FORCE_GFN)).toBe(
`Good for Normal (GFN)` it('should return the correct rejection reason when order is stopped', () => {
); expect(
expect(timeInForceLabel('')).toBe(''); getRejectionReason({
rejectionReason: null,
status: Types.OrderStatus.STATUS_STOPPED,
id: '',
createdAt: undefined,
size: '',
price: '',
timeInForce: Types.OrderTimeInForce.TIME_IN_FORCE_FOK,
side: Types.Side.SIDE_BUY,
marketId: '',
})
).toBe(
'Your Fill or Kill (FOK) order was not filled and it has been stopped'
);
});
});
describe('timeInForceLabel', () => {
it('should return the correct label for time in force', () => {
expect(timeInForceLabel(Types.OrderTimeInForce.TIME_IN_FORCE_FOK)).toBe(
`Fill or Kill (FOK)`
);
expect(timeInForceLabel(Types.OrderTimeInForce.TIME_IN_FORCE_GTC)).toBe(
`Good 'til Cancelled (GTC)`
);
expect(timeInForceLabel(Types.OrderTimeInForce.TIME_IN_FORCE_IOC)).toBe(
`Immediate or Cancel (IOC)`
);
expect(timeInForceLabel(Types.OrderTimeInForce.TIME_IN_FORCE_GTT)).toBe(
`Good 'til Time (GTT)`
);
expect(timeInForceLabel(Types.OrderTimeInForce.TIME_IN_FORCE_GFA)).toBe(
`Good for Auction (GFA)`
);
expect(timeInForceLabel(Types.OrderTimeInForce.TIME_IN_FORCE_GFN)).toBe(
`Good for Normal (GFN)`
);
expect(timeInForceLabel('')).toBe('');
});
}); });
}); });

View File

@ -36,7 +36,7 @@ export const getRejectionReason = (
default: default:
return order.rejectionReason return order.rejectionReason
? t(Schema.OrderRejectionReasonMapping[order.rejectionReason]) ? t(Schema.OrderRejectionReasonMapping[order.rejectionReason])
: null; : '';
} }
}; };
@ -79,9 +79,9 @@ export const getOrderToastIntent = (
case Schema.OrderStatus.STATUS_PARKED: case Schema.OrderStatus.STATUS_PARKED:
case Schema.OrderStatus.STATUS_EXPIRED: case Schema.OrderStatus.STATUS_EXPIRED:
case Schema.OrderStatus.STATUS_PARTIALLY_FILLED: case Schema.OrderStatus.STATUS_PARTIALLY_FILLED:
case Schema.OrderStatus.STATUS_STOPPED:
return Intent.Warning; return Intent.Warning;
case Schema.OrderStatus.STATUS_REJECTED: case Schema.OrderStatus.STATUS_REJECTED:
case Schema.OrderStatus.STATUS_STOPPED:
return Intent.Danger; return Intent.Danger;
case Schema.OrderStatus.STATUS_FILLED: case Schema.OrderStatus.STATUS_FILLED:
case Schema.OrderStatus.STATUS_ACTIVE: case Schema.OrderStatus.STATUS_ACTIVE:

View File

@ -52,6 +52,7 @@ import type { Side } from '@vegaprotocol/types';
import { OrderStatusMapping } from '@vegaprotocol/types'; import { OrderStatusMapping } from '@vegaprotocol/types';
import { Size } from '@vegaprotocol/datagrid'; import { Size } from '@vegaprotocol/datagrid';
import { useWithdrawalApprovalDialog } from './withdrawal-approval-dialog'; import { useWithdrawalApprovalDialog } from './withdrawal-approval-dialog';
import * as Schema from '@vegaprotocol/types';
const intentMap: { [s in VegaTxStatus]: Intent } = { const intentMap: { [s in VegaTxStatus]: Intent } = {
Default: Intent.Primary, Default: Intent.Primary,
@ -508,17 +509,27 @@ const VegaTxCompleteToastsContent = ({ tx }: VegaTxToastContentProps) => {
} }
if (tx.order && tx.order.rejectionReason) { if (tx.order && tx.order.rejectionReason) {
const rejectionReason = const rejectionReason = getRejectionReason(tx.order);
getRejectionReason(tx.order) || tx.order.rejectionReason || '';
return ( return (
<> <>
<ToastHeading>{getOrderToastTitle(tx.order.status)}</ToastHeading> <ToastHeading>{getOrderToastTitle(tx.order.status)}</ToastHeading>
{rejectionReason ? ( {rejectionReason ? (
<p> <p>
{t('Your order has been rejected because: %s', [rejectionReason])} {t('Your order has been %s because: %s', [
tx.order.status === Schema.OrderStatus.STATUS_STOPPED
? 'stopped'
: 'rejected',
rejectionReason,
])}
</p> </p>
) : ( ) : (
<p>{t('Your order has been rejected.')}</p> <p>
{t('Your order has been %s.', [
tx.order.status === Schema.OrderStatus.STATUS_STOPPED
? 'stopped'
: 'rejected',
])}
</p>
)} )}
{tx.txHash && ( {tx.txHash && (
<p className="break-all"> <p className="break-all">

View File

@ -18,109 +18,113 @@ const generateJsx = (props: TypedDataAgGrid<WithdrawalFieldsFragment>) => (
</MockedProvider> </MockedProvider>
); );
describe('renders the correct columns', () => { describe('Withdrawals', () => {
it('incomplete withdrawal', async () => { describe('renders the correct columns', () => {
const withdrawal = generateWithdrawal(); it('incomplete withdrawal', async () => {
await act(async () => { const withdrawal = generateWithdrawal();
render(generateJsx({ rowData: [withdrawal] })); await act(async () => {
render(generateJsx({ rowData: [withdrawal] }));
});
const headers = screen.getAllByRole('columnheader');
expect(headers).toHaveLength(7);
expect(headers.map((h) => h.textContent?.trim())).toEqual([
'Asset',
'Amount',
'Recipient',
'Created',
'Completed',
'Status',
'Transaction',
]);
const cells = screen.getAllByRole('gridcell');
const expectedValues = [
'asset-symbol',
'1.00',
'123456…123456',
getTimeFormat().format(new Date(withdrawal.createdTimestamp as string)),
'-',
'Pending',
'Complete withdrawal',
];
cells.forEach((cell, i) => {
expect(cell).toHaveTextContent(expectedValues[i]);
});
}); });
const headers = screen.getAllByRole('columnheader'); it('completed withdrawal', async () => {
expect(headers).toHaveLength(7); const withdrawal = generateWithdrawal({
expect(headers.map((h) => h.textContent?.trim())).toEqual([ txHash: '0x1234567891011121314',
'Asset', withdrawnTimestamp: '2022-04-21T00:00:00',
'Amount', status: Schema.WithdrawalStatus.STATUS_FINALIZED,
'Recipient', });
'Created',
'Completed',
'Status',
'Transaction',
]);
const cells = screen.getAllByRole('gridcell'); await act(async () => {
const expectedValues = [ render(generateJsx({ rowData: [withdrawal] }));
'asset-symbol', });
'1.00',
'123456…123456', const cells = screen.getAllByRole('gridcell');
getTimeFormat().format(new Date(withdrawal.createdTimestamp as string)), const expectedValues = [
'-', 'asset-symbol',
'Pending', '1.00',
'Complete withdrawal', '123456…123456',
]; getTimeFormat().format(new Date(withdrawal.createdTimestamp as string)),
cells.forEach((cell, i) => { getTimeFormat().format(
expect(cell).toHaveTextContent(expectedValues[i]); new Date(withdrawal.withdrawnTimestamp as string)
),
'Completed',
'0x1234…121314',
];
cells.forEach((cell, i) => {
expect(cell).toHaveTextContent(expectedValues[i]);
});
}); });
}); });
it('completed withdrawal', async () => { describe('StatusCell', () => {
const withdrawal = generateWithdrawal({ let props: { data: WithdrawalFieldsFragment };
txHash: '0x1234567891011121314', let withdrawal: WithdrawalFieldsFragment;
withdrawnTimestamp: '2022-04-21T00:00:00',
status: Schema.WithdrawalStatus.STATUS_FINALIZED, beforeEach(() => {
withdrawal = generateWithdrawal();
props = {
data: withdrawal,
};
}); });
await act(async () => { it('Open', () => {
render(generateJsx({ rowData: [withdrawal] })); props.data.pendingOnForeignChain = false;
props.data.txHash = null;
render(<StatusCell {...props} />);
expect(screen.getByText('Pending')).toBeInTheDocument();
}); });
const cells = screen.getAllByRole('gridcell'); it('Pending', () => {
const expectedValues = [ props.data.pendingOnForeignChain = true;
'asset-symbol', props.data.txHash = '0x123';
'1.00', render(<StatusCell {...props} />);
'123456…123456',
getTimeFormat().format(new Date(withdrawal.createdTimestamp as string)), expect(screen.getByText('Pending')).toBeInTheDocument();
getTimeFormat().format(new Date(withdrawal.withdrawnTimestamp as string)), });
'Completed',
'0x1234…121314', it('Completed', () => {
]; props.data.pendingOnForeignChain = false;
cells.forEach((cell, i) => { props.data.txHash = '0x123';
expect(cell).toHaveTextContent(expectedValues[i]); props.data.status = Schema.WithdrawalStatus.STATUS_FINALIZED;
render(<StatusCell {...props} />);
expect(screen.getByText('Completed')).toBeInTheDocument();
});
it('Rejected', () => {
props.data.pendingOnForeignChain = false;
props.data.txHash = '0x123';
props.data.status = Schema.WithdrawalStatus.STATUS_REJECTED;
render(<StatusCell {...props} />);
expect(screen.getByText('Rejected')).toBeInTheDocument();
}); });
}); });
}); });
describe('StatusCell', () => {
let props: { data: WithdrawalFieldsFragment };
let withdrawal: WithdrawalFieldsFragment;
beforeEach(() => {
withdrawal = generateWithdrawal();
props = {
data: withdrawal,
};
});
it('Open', () => {
props.data.pendingOnForeignChain = false;
props.data.txHash = null;
render(<StatusCell {...props} />);
expect(screen.getByText('Pending')).toBeInTheDocument();
});
it('Pending', () => {
props.data.pendingOnForeignChain = true;
props.data.txHash = '0x123';
render(<StatusCell {...props} />);
expect(screen.getByText('Pending')).toBeInTheDocument();
});
it('Completed', () => {
props.data.pendingOnForeignChain = false;
props.data.txHash = '0x123';
props.data.status = Schema.WithdrawalStatus.STATUS_FINALIZED;
render(<StatusCell {...props} />);
expect(screen.getByText('Completed')).toBeInTheDocument();
});
it('Rejected', () => {
props.data.pendingOnForeignChain = false;
props.data.txHash = '0x123';
props.data.status = Schema.WithdrawalStatus.STATUS_REJECTED;
render(<StatusCell {...props} />);
expect(screen.getByText('Rejected')).toBeInTheDocument();
});
});