feat: stop order form validation unit tests
This commit is contained in:
@@ -28,6 +28,37 @@ function generateJsx(pubKey: string | null = 'pubKey', isReadOnly = false) {
|
||||
);
|
||||
}
|
||||
|
||||
const submitButton = 'place-order';
|
||||
const sizeInput = 'order-size';
|
||||
const priceInput = 'order-price';
|
||||
const triggerPriceInput = 'triggerPrice';
|
||||
const triggerTrailingPercentOffsetInput = 'triggerTrailingPercentOffset';
|
||||
|
||||
const orderTypeLimit = 'order-type-StopLimit';
|
||||
const orderTypeMarket = 'order-type-StopMarket';
|
||||
|
||||
const orderSideBuy = 'order-side-SIDE_BUY';
|
||||
const orderSideSell = 'order-side-SIDE_SELL';
|
||||
|
||||
const triggerDirectionRisesAbove = 'triggerDirection-risesAbove';
|
||||
// const triggerDirectionFallsBelow = 'triggerDirection-fallsBelow';
|
||||
|
||||
const expiryStrategySubmit = 'expiryStrategy-submit';
|
||||
const expiryStrategyCancel = 'expiryStrategy-cancel';
|
||||
|
||||
const triggerTypePrice = 'triggerType-price';
|
||||
const triggerTypeTrailingPercentOffset = 'triggerType-trailingPercentOffset';
|
||||
|
||||
const expire = 'expire';
|
||||
const datePicker = 'date-picker-field';
|
||||
const timeInForce = 'order-tif';
|
||||
|
||||
const sizeErrorMessage = 'stop-order-error-message-size';
|
||||
const priceErrorMessage = 'stop-order-error-message-price';
|
||||
const triggerPriceErrorMessage = 'stop-order-error-message-trigger-price';
|
||||
const triggerTrailingPercentOffsetErrorMessage =
|
||||
'stop-order-error-message-trigger-trailing-percent-offset';
|
||||
|
||||
describe('StopOrder', () => {
|
||||
beforeEach(() => {
|
||||
localStorage.clear();
|
||||
@@ -41,35 +72,33 @@ describe('StopOrder', () => {
|
||||
it('should display ticket defaults', async () => {
|
||||
render(generateJsx());
|
||||
// place order button should always be enabled
|
||||
expect(screen.getByTestId('place-order')).toBeEnabled();
|
||||
expect(screen.getByTestId(submitButton)).toBeEnabled();
|
||||
|
||||
// Assert defaults are used
|
||||
expect(screen.getByTestId('order-type-StopLimit').dataset.state).toEqual(
|
||||
'checked'
|
||||
);
|
||||
expect(screen.getByTestId(orderTypeLimit).dataset.state).toEqual('checked');
|
||||
|
||||
expect(
|
||||
screen.queryByTestId('order-side-SIDE_BUY')?.querySelector('input')
|
||||
screen.queryByTestId(orderSideBuy)?.querySelector('input')
|
||||
).toBeChecked();
|
||||
expect(
|
||||
screen.queryByTestId('order-side-SIDE_SELL')?.querySelector('input')
|
||||
screen.queryByTestId(orderSideSell)?.querySelector('input')
|
||||
).not.toBeChecked();
|
||||
expect(screen.getByTestId('order-size')).toHaveDisplayValue('0');
|
||||
expect(screen.getByTestId('order-tif')).toHaveValue(
|
||||
expect(screen.getByTestId(sizeInput)).toHaveDisplayValue('0');
|
||||
expect(screen.getByTestId(timeInForce)).toHaveValue(
|
||||
Schema.OrderTimeInForce.TIME_IN_FORCE_FOK
|
||||
);
|
||||
expect(
|
||||
screen.getByTestId('triggerDirection-risesAbove').dataset.state
|
||||
screen.getByTestId(triggerDirectionRisesAbove).dataset.state
|
||||
).toEqual('checked');
|
||||
expect(screen.getByTestId('triggerType-price').dataset.state).toEqual(
|
||||
expect(screen.getByTestId(triggerTypePrice).dataset.state).toEqual(
|
||||
'checked'
|
||||
);
|
||||
expect(screen.getByTestId('expire').dataset.state).toEqual('unchecked');
|
||||
expect(screen.getByTestId(expire).dataset.state).toEqual('unchecked');
|
||||
act(() => {
|
||||
screen.getByTestId('expire').click();
|
||||
screen.getByTestId(expire).click();
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('expiryStrategy-submit').dataset.state).toEqual(
|
||||
expect(screen.getByTestId(expiryStrategySubmit).dataset.state).toEqual(
|
||||
'checked'
|
||||
);
|
||||
});
|
||||
@@ -77,8 +106,8 @@ describe('StopOrder', () => {
|
||||
|
||||
it('should display trigger price as price for market type order', async () => {
|
||||
render(generateJsx());
|
||||
screen.getByTestId('order-type-StopMarket').click();
|
||||
await userEvent.type(screen.getByTestId('triggerPrice'), '10');
|
||||
screen.getByTestId(orderTypeMarket).click();
|
||||
await userEvent.type(screen.getByTestId(triggerPriceInput), '10');
|
||||
expect(screen.getByTestId('price')).toHaveTextContent('10.0');
|
||||
});
|
||||
|
||||
@@ -102,38 +131,178 @@ describe('StopOrder', () => {
|
||||
|
||||
render(generateJsx());
|
||||
// Assert correct defaults are used from store
|
||||
expect(screen.queryByTestId('order-type-StopLimit')).toBeChecked();
|
||||
expect(screen.queryByTestId(orderTypeLimit)).toBeChecked();
|
||||
expect(
|
||||
screen.queryByTestId('order-side-SIDE_SELL')?.querySelector('input')
|
||||
screen.queryByTestId(orderSideSell)?.querySelector('input')
|
||||
).toBeChecked();
|
||||
expect(
|
||||
screen.queryByTestId('order-side-SIDE_BUY')?.querySelector('input')
|
||||
screen.queryByTestId(orderSideBuy)?.querySelector('input')
|
||||
).not.toBeChecked();
|
||||
expect(screen.getByTestId('order-size')).toHaveDisplayValue(
|
||||
expect(screen.getByTestId(sizeInput)).toHaveDisplayValue(
|
||||
values.size as string
|
||||
);
|
||||
expect(screen.getByTestId('order-tif')).toHaveValue(values.timeInForce);
|
||||
expect(screen.getByTestId('order-price')).toHaveDisplayValue(
|
||||
expect(screen.getByTestId(priceInput)).toHaveDisplayValue(
|
||||
values.price as string
|
||||
);
|
||||
expect(screen.getByTestId('expire').dataset.state).toEqual('checked');
|
||||
expect(screen.getByTestId('expiryStrategy-cancel').dataset.state).toEqual(
|
||||
expect(screen.getByTestId(expiryStrategyCancel).dataset.state).toEqual(
|
||||
'checked'
|
||||
);
|
||||
expect(screen.getByTestId('date-picker-field')).toHaveDisplayValue(
|
||||
expect(screen.getByTestId(datePicker)).toHaveDisplayValue(
|
||||
values.expiresAt as string
|
||||
);
|
||||
});
|
||||
|
||||
it('shows no wallet warning and do not submit', async () => {
|
||||
it('shows no wallet warning and do not submit if no wallet connected', async () => {
|
||||
render(generateJsx(null));
|
||||
await userEvent.type(screen.getByTestId('order-size'), '1');
|
||||
await userEvent.type(screen.getByTestId('order-price'), '1');
|
||||
await userEvent.type(screen.getByTestId('triggerPrice'), '1');
|
||||
await userEvent.click(screen.getByTestId('place-order'));
|
||||
await userEvent.type(screen.getByTestId(sizeInput), '1');
|
||||
await userEvent.type(screen.getByTestId(priceInput), '1');
|
||||
await userEvent.type(screen.getByTestId(triggerPriceInput), '1');
|
||||
await userEvent.click(screen.getByTestId(submitButton));
|
||||
expect(submit).not.toBeCalled();
|
||||
expect(
|
||||
screen.getByTestId('deal-ticket-connect-wallet')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calls submit if form is valid', async () => {
|
||||
render(generateJsx());
|
||||
await userEvent.type(screen.getByTestId(sizeInput), '1');
|
||||
await userEvent.type(screen.getByTestId(priceInput), '1');
|
||||
await userEvent.type(screen.getByTestId(triggerPriceInput), '1');
|
||||
await userEvent.click(screen.getByTestId(submitButton));
|
||||
expect(submit).toBeCalled();
|
||||
});
|
||||
|
||||
it('validates size field', async () => {
|
||||
render(generateJsx());
|
||||
|
||||
await userEvent.click(screen.getByTestId(submitButton));
|
||||
|
||||
// default value should be invalid
|
||||
expect(screen.getByTestId(sizeErrorMessage)).toBeInTheDocument();
|
||||
// to small value should be invalid
|
||||
await userEvent.type(screen.getByTestId(sizeInput), '0.01');
|
||||
expect(screen.getByTestId(sizeErrorMessage)).toBeInTheDocument();
|
||||
|
||||
// clear and fill using valid value
|
||||
await userEvent.clear(screen.getByTestId(sizeInput));
|
||||
await userEvent.type(screen.getByTestId(sizeInput), '0.1');
|
||||
expect(screen.queryByTestId(sizeErrorMessage)).toBeNull();
|
||||
});
|
||||
|
||||
it('validates price field', async () => {
|
||||
render(generateJsx());
|
||||
|
||||
await userEvent.click(screen.getByTestId(submitButton));
|
||||
// price error message should not show if size has error
|
||||
expect(screen.queryByTestId(priceErrorMessage)).toBeNull();
|
||||
await userEvent.type(screen.getByTestId(sizeInput), '0.1');
|
||||
expect(screen.getByTestId(priceErrorMessage)).toBeInTheDocument();
|
||||
await userEvent.type(screen.getByTestId(priceInput), '0.001');
|
||||
expect(screen.getByTestId(priceErrorMessage)).toBeInTheDocument();
|
||||
|
||||
// switch to market order type error should disappear
|
||||
await userEvent.click(screen.getByTestId(orderTypeMarket));
|
||||
expect(screen.queryByTestId(priceErrorMessage)).toBeNull();
|
||||
|
||||
// switch back to limit type
|
||||
await userEvent.click(screen.getByTestId(orderTypeLimit));
|
||||
expect(screen.getByTestId(priceErrorMessage)).toBeInTheDocument();
|
||||
|
||||
// to small value should be invalid
|
||||
await userEvent.type(screen.getByTestId(priceInput), '0.001');
|
||||
expect(screen.getByTestId(priceErrorMessage)).toBeInTheDocument();
|
||||
|
||||
// clear and fill using valid value
|
||||
await userEvent.clear(screen.getByTestId(priceInput));
|
||||
await userEvent.type(screen.getByTestId(priceInput), '0.01');
|
||||
expect(screen.queryByTestId(priceErrorMessage)).toBeNull();
|
||||
});
|
||||
|
||||
it('validates trigger price field', async () => {
|
||||
render(generateJsx());
|
||||
|
||||
await userEvent.click(screen.getByTestId(submitButton));
|
||||
expect(screen.getByTestId(triggerPriceErrorMessage)).toBeInTheDocument();
|
||||
|
||||
// switch to trailing percentage offset trigger type
|
||||
await userEvent.click(screen.getByTestId(triggerTypeTrailingPercentOffset));
|
||||
expect(screen.queryByTestId(triggerPriceErrorMessage)).toBeNull();
|
||||
|
||||
// switch back to price trigger type
|
||||
await userEvent.click(screen.getByTestId(triggerTypePrice));
|
||||
expect(screen.getByTestId(triggerPriceErrorMessage)).toBeInTheDocument();
|
||||
|
||||
// to small value should be invalid
|
||||
await userEvent.type(screen.getByTestId(triggerPriceInput), '0.001');
|
||||
expect(screen.getByTestId(triggerPriceErrorMessage)).toBeInTheDocument();
|
||||
|
||||
// clear and fill using valid value
|
||||
await userEvent.clear(screen.getByTestId(triggerPriceInput));
|
||||
await userEvent.type(screen.getByTestId(triggerPriceInput), '0.01');
|
||||
expect(screen.queryByTestId(triggerPriceErrorMessage)).toBeNull();
|
||||
});
|
||||
|
||||
it('validates trigger trailing percentage offset field', async () => {
|
||||
render(generateJsx());
|
||||
|
||||
// should not show error with default form values
|
||||
await userEvent.click(screen.getByTestId(submitButton));
|
||||
expect(
|
||||
screen.queryByTestId(triggerTrailingPercentOffsetErrorMessage)
|
||||
).toBeNull();
|
||||
|
||||
// switch to trailing percentage offset trigger type
|
||||
await userEvent.click(screen.getByTestId(triggerTypeTrailingPercentOffset));
|
||||
expect(
|
||||
screen.getByTestId(triggerTrailingPercentOffsetErrorMessage)
|
||||
).toBeInTheDocument();
|
||||
|
||||
// to small value should be invalid
|
||||
await userEvent.type(
|
||||
screen.getByTestId(triggerTrailingPercentOffsetInput),
|
||||
'0.09'
|
||||
);
|
||||
expect(
|
||||
screen.getByTestId(triggerTrailingPercentOffsetErrorMessage)
|
||||
).toBeInTheDocument();
|
||||
|
||||
// clear and fill using valid value
|
||||
await userEvent.clear(
|
||||
screen.getByTestId(triggerTrailingPercentOffsetInput)
|
||||
);
|
||||
await userEvent.type(
|
||||
screen.getByTestId(triggerTrailingPercentOffsetInput),
|
||||
'0.1'
|
||||
);
|
||||
expect(
|
||||
screen.queryByTestId(triggerTrailingPercentOffsetErrorMessage)
|
||||
).toBeNull();
|
||||
|
||||
// to big value should be invalid
|
||||
await userEvent.clear(
|
||||
screen.getByTestId(triggerTrailingPercentOffsetInput)
|
||||
);
|
||||
await userEvent.type(
|
||||
screen.getByTestId(triggerTrailingPercentOffsetInput),
|
||||
'99.91'
|
||||
);
|
||||
expect(
|
||||
screen.getByTestId(triggerTrailingPercentOffsetErrorMessage)
|
||||
).toBeInTheDocument();
|
||||
|
||||
// clear and fill using valid value
|
||||
await userEvent.clear(
|
||||
screen.getByTestId(triggerTrailingPercentOffsetInput)
|
||||
);
|
||||
await userEvent.type(
|
||||
screen.getByTestId(triggerTrailingPercentOffsetInput),
|
||||
'99.9'
|
||||
);
|
||||
expect(
|
||||
screen.queryByTestId(triggerTrailingPercentOffsetErrorMessage)
|
||||
).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -196,9 +196,6 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (type === DealTicketType.StopMarket) {
|
||||
clearErrors('price');
|
||||
}
|
||||
setValue(
|
||||
'type',
|
||||
type === DealTicketType.StopLimit
|
||||
@@ -277,6 +274,7 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||
<Input
|
||||
data-testid="triggerPrice"
|
||||
type="number"
|
||||
step={priceStep}
|
||||
appendElement={asset.symbol}
|
||||
value={value || ''}
|
||||
{...props}
|
||||
@@ -318,13 +316,16 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||
),
|
||||
}}
|
||||
render={({ field }) => {
|
||||
const { value, ...props } = field;
|
||||
return (
|
||||
<div className="mb-2">
|
||||
<Input
|
||||
type="number"
|
||||
step={trailingPercentOffsetStep}
|
||||
appendElement="%"
|
||||
{...field}
|
||||
data-testid="triggerTrailingPercentOffset"
|
||||
value={value || ''}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -378,18 +379,22 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||
},
|
||||
validate: validateAmount(sizeStep, 'Size'),
|
||||
}}
|
||||
render={({ field }) => (
|
||||
<Input
|
||||
id="input-order-size-market"
|
||||
className="w-full"
|
||||
type="number"
|
||||
step={sizeStep}
|
||||
min={sizeStep}
|
||||
onWheel={(e) => e.currentTarget.blur()}
|
||||
data-testid="order-size"
|
||||
{...field}
|
||||
/>
|
||||
)}
|
||||
render={({ field }) => {
|
||||
const { value, ...props } = field;
|
||||
return (
|
||||
<Input
|
||||
id="order-size"
|
||||
className="w-full"
|
||||
type="number"
|
||||
step={sizeStep}
|
||||
min={sizeStep}
|
||||
onWheel={(e) => e.currentTarget.blur()}
|
||||
data-testid="order-size"
|
||||
value={value || ''}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</FormGroup>
|
||||
<div className="pt-7 leading-10">@</div>
|
||||
@@ -405,6 +410,7 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||
name="price"
|
||||
control={control}
|
||||
rules={{
|
||||
deps: 'type',
|
||||
required: t('You need provide a price'),
|
||||
min: {
|
||||
value: priceStep,
|
||||
@@ -412,17 +418,21 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||
},
|
||||
validate: validateAmount(priceStep, 'Price'),
|
||||
}}
|
||||
render={({ field }) => (
|
||||
<Input
|
||||
id="input-price-quote"
|
||||
className="w-full"
|
||||
type="number"
|
||||
step={priceStep}
|
||||
data-testid="order-price"
|
||||
onWheel={(e) => e.currentTarget.blur()}
|
||||
{...field}
|
||||
/>
|
||||
)}
|
||||
render={({ field }) => {
|
||||
const { value, ...props } = field;
|
||||
return (
|
||||
<Input
|
||||
id="input-price-quote"
|
||||
className="w-full"
|
||||
type="number"
|
||||
step={priceStep}
|
||||
data-testid="order-price"
|
||||
onWheel={(e) => e.currentTarget.blur()}
|
||||
value={value || ''}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</FormGroup>
|
||||
) : (
|
||||
@@ -438,16 +448,18 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
|
||||
</div>
|
||||
</div>
|
||||
{errors.size && (
|
||||
<InputError testId="deal-ticket-error-message-size-limit">
|
||||
<InputError testId="stop-order-error-message-size">
|
||||
{errors.size.message}
|
||||
</InputError>
|
||||
)}
|
||||
|
||||
{!errors.size && errors.price && (
|
||||
<InputError testId="deal-ticket-error-message-price-limit">
|
||||
{errors.price.message}
|
||||
</InputError>
|
||||
)}
|
||||
{!errors.size &&
|
||||
errors.price &&
|
||||
type === Schema.OrderType.TYPE_LIMIT && (
|
||||
<InputError testId="stop-order-error-message-price">
|
||||
{errors.price.message}
|
||||
</InputError>
|
||||
)}
|
||||
</div>
|
||||
<div className="mb-2">
|
||||
<FormGroup
|
||||
|
||||
Reference in New Issue
Block a user