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}`; };