import { userEvent, fireEvent, render, screen, waitFor } from '../../test-utils';
import { AmountInput } from './AmountInput';
describe('AmountInput', () => {
// TODO: "allows to delete the thousands separator and the preceding digit" test is flaky
jest.retryTimes(3, { logErrorsBeforeRetry: true });
it('formats the passed in value', () => {
const onChange = jest.fn();
const { rerender } = render(
,
);
const input = screen.getByRole('textbox');
expect(input).toHaveValue('123,456.78');
expect(onChange).not.toHaveBeenCalled();
rerender();
expect(input).toHaveValue('4');
expect(onChange).not.toHaveBeenCalled();
rerender();
expect(input).toHaveValue('');
expect(onChange).not.toHaveBeenCalled();
});
it('formats the value while the user is typing', async () => {
const onChange = jest.fn();
render();
expect(onChange).not.toHaveBeenCalled();
const input = screen.getByRole('textbox');
await userEvent.type(input, '123');
expect(input).toHaveValue('123');
expect(onChange).toHaveBeenLastCalledWith(123);
await userEvent.type(input, '4');
expect(input).toHaveValue('1,234');
expect(onChange).toHaveBeenLastCalledWith(1234);
onChange.mockClear();
expect(onChange).not.toHaveBeenCalled();
await userEvent.type(input, '.');
expect(input).toHaveValue('1,234.');
expect(onChange).not.toHaveBeenCalled();
await userEvent.type(input, '5');
expect(input).toHaveValue('1,234.5');
expect(onChange).toHaveBeenLastCalledWith(1234.5);
await userEvent.type(input, '6');
expect(input).toHaveValue('1,234.56');
expect(onChange).toHaveBeenLastCalledWith(1234.56);
await userEvent.type(input, '{backspace}');
expect(input).toHaveValue('1,234.5');
expect(onChange).toHaveBeenLastCalledWith(1234.5);
onChange.mockClear();
fireEvent.blur(input);
expect(input).toHaveValue('1,234.50');
expect(onChange).not.toHaveBeenCalled();
});
it('allows to delete the thousands separator and the preceding digit', async () => {
const onChange = jest.fn();
render();
const input = screen.getByRole('textbox');
await userEvent.type(input, '1234');
expect(input).toHaveValue('1,234');
expect(onChange).toHaveBeenLastCalledWith(1234);
await userEvent.type(input, '{backspace}', {
initialSelectionStart: 2,
});
expect(input).toHaveValue('234');
expect(onChange).toHaveBeenLastCalledWith(234);
await waitFor(() => {
expect(input.selectionStart).toBe(0);
});
});
it('allows to paste poorly formatted values', async () => {
const onChange = jest.fn();
render();
const input = screen.getByRole('textbox');
await userEvent.click(input);
await userEvent.paste('1234.5678');
expect(input).toHaveValue('1,234.56');
expect(onChange).toHaveBeenLastCalledWith(1234.56);
});
it('allows to paste values', async () => {
const onChange = jest.fn();
render();
const input = screen.getByRole('textbox');
await userEvent.click(input);
await userEvent.paste('1000');
expect(input).toHaveValue('1,000');
expect(onChange).toHaveBeenLastCalledWith(1000);
});
it('only allows specific characters to be entered', async () => {
const onChange = jest.fn();
render();
const input = screen.getByRole('textbox');
await userEvent.type(input, 'abc');
expect(input).toHaveValue('');
expect(onChange).not.toHaveBeenCalled();
await userEvent.type(input, '123');
expect(input).toHaveValue('123');
expect(onChange).toHaveBeenLastCalledWith(123);
await userEvent.type(input, 'def');
expect(input).toHaveValue('123');
expect(onChange).toHaveBeenLastCalledWith(123);
await userEvent.type(input, '{backspace}');
expect(input).toHaveValue('12');
expect(onChange).toHaveBeenLastCalledWith(12);
});
it('does not allow to enter too many decimals', async () => {
const onChange = jest.fn();
render();
const input = screen.getByRole('textbox');
await userEvent.type(input, '123.45');
expect(input).toHaveValue('123.45');
expect(onChange).toHaveBeenLastCalledWith(123.45);
onChange.mockClear();
await userEvent.type(input, '6');
expect(input).toHaveValue('123.45');
expect(onChange).not.toHaveBeenCalled();
});
it('does not allow to enter too many decimal separators', async () => {
const onChange = jest.fn();
render();
const input = screen.getByRole('textbox');
await userEvent.type(input, '123.');
expect(input).toHaveValue('123.');
expect(onChange).toHaveBeenLastCalledWith(123);
onChange.mockClear();
await userEvent.type(input, '.');
expect(input).toHaveValue('123.');
expect(onChange).not.toHaveBeenCalled();
await userEvent.type(input, '45');
expect(input).toHaveValue('123.45');
expect(onChange).toHaveBeenLastCalledWith(123.45);
onChange.mockClear();
await userEvent.type(input, '.');
expect(input).toHaveValue('123.45');
expect(onChange).not.toHaveBeenCalled();
});
it('maintains cursor position after formatting', async () => {
const onChange = jest.fn();
render();
const input = screen.getByRole('textbox');
await userEvent.type(input, '1234567');
expect(input).toHaveValue('1,234,567');
expect(onChange).toHaveBeenLastCalledWith(1234567);
expect(input.selectionStart).toBe(9);
await userEvent.type(input, '9', {
initialSelectionStart: 4, // Place the cursor between "3" and "4"
});
expect(input).toHaveValue('12,394,567');
expect(onChange).toHaveBeenLastCalledWith(12394567);
await waitFor(() => {
expect(input.selectionStart).toBe(5);
});
});
it('adds decimal placeholders when the user stars typing decimals', async () => {
const onChange = jest.fn();
const { container } = render();
const input = screen.getByRole('textbox');
await userEvent.type(input, '1234.');
expect(input).toHaveValue('1,234.');
expect(getInputAndAddonContents(container)).toBe('1,234.00');
await userEvent.type(input, '5');
expect(input).toHaveValue('1,234.5');
expect(getInputAndAddonContents(container)).toBe('1,234.50');
await userEvent.type(input, '6');
expect(input).toHaveValue('1,234.56');
expect(getInputAndAddonContents(container)).toBe('1,234.56');
});
it('adds decimal placeholders when the user stars typing decimals in Spansh', async () => {
const onChange = jest.fn();
const { container } = render(, {
locale: 'es',
});
const input = screen.getByRole('textbox');
await userEvent.type(input, '1234,');
expect(input).toHaveValue('1234,');
expect(getInputAndAddonContents(container)).toBe('1234,00');
await userEvent.type(input, '5');
expect(input).toHaveValue('1234,5');
expect(getInputAndAddonContents(container)).toBe('1234,50');
await userEvent.type(input, '6');
expect(input).toHaveValue('1234,56');
expect(getInputAndAddonContents(container)).toBe('1234,56');
});
it('adds decimal placeholders when the input is blurred', async () => {
const onChange = jest.fn();
const { container } = render();
const input = screen.getByRole('textbox');
await userEvent.type(input, '1234');
expect(input).toHaveValue('1,234');
fireEvent.blur(input);
expect(getInputAndAddonContents(container)).toBe('1,234.00');
});
it('adds decimal placeholders when the input is blurred in Spanish', async () => {
const onChange = jest.fn();
const { container } = render(, {
locale: 'es',
});
const input = screen.getByRole('textbox');
await userEvent.type(input, '1234');
expect(input).toHaveValue('1234');
fireEvent.blur(input);
expect(getInputAndAddonContents(container)).toBe('1234,00');
});
it('does not allow to enter an amount greater than the maximum', async () => {
const maxAmount = Number.MAX_SAFE_INTEGER - 1;
const onChange = jest.fn();
render();
const input = screen.getByRole('textbox');
await userEvent.type(input, String(maxAmount));
expect(input).toHaveValue('9,007,199,254,740,990');
expect(onChange).toHaveBeenLastCalledWith(maxAmount);
onChange.mockClear();
await userEvent.type(input, '1');
expect(input).toHaveValue('9,007,199,254,740,990');
expect(onChange).not.toHaveBeenCalled();
});
});
const getInputAndAddonContents = (container: HTMLElement) => {
const input = screen.getByRole('textbox');
return `${input.value}${container.textContent}`;
};