feat: stop order form validation unit tests

This commit is contained in:
Bartłomiej Głownia
2023-07-28 16:17:42 +02:00
parent 53e82100a8
commit 98eaa43cf4
2 changed files with 241 additions and 60 deletions
@@ -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