import React from 'react'; import 'jest-styled-components'; import 'regenerator-runtime/runtime'; import { fireEvent, render, waitFor } from '@testing-library/react'; import { getByText, screen } from '@testing-library/dom'; import { axe } from 'jest-axe'; import 'jest-axe/extend-expect'; import { Search } from 'grommet-icons'; import { createPortal, expectPortal } from '../../../utils/portal'; import { Grommet } from '../../Grommet'; import { TextInput } from '..'; import { Keyboard } from '../../Keyboard'; import { Text } from '../../Text'; describe('TextInput', () => { beforeEach(createPortal); test('should not have accessibility violations', async () => { const { container } = render( , ); const results = await axe(container); expect(container.firstChild).toMatchSnapshot(); expect(results).toHaveNoViolations(); }); test('basic', () => { const { container } = render(); expect(container.firstChild).toMatchSnapshot(); }); test('a11yTitle or aria-label', () => { const { container, getByLabelText } = render( , ); expect(getByLabelText('aria-test')).toBeTruthy(); expect(getByLabelText('aria-test-2')).toBeTruthy(); expect(container.firstChild).toMatchSnapshot(); }); test('disabled', () => { const { container } = render(); expect(container.firstChild).toMatchSnapshot(); }); test('icon', () => { const { container } = render(} name="item" />); expect(container.firstChild).toMatchSnapshot(); }); test('icon reverse', () => { const { container } = render( } reverse name="item" />, ); expect(container.firstChild).toMatchSnapshot(); }); test('suggestions', (done) => { const onChange = jest.fn(); const onFocus = jest.fn(); const { getByTestId, container } = render( , ); expect(container.firstChild).toMatchSnapshot(); fireEvent.focus(getByTestId('test-input')); fireEvent.change(getByTestId('test-input'), { target: { value: ' ' } }); setTimeout(() => { expectPortal('text-input-drop__item').toMatchSnapshot(); expect(onChange).toBeCalled(); expect(onFocus).toBeCalled(); fireEvent( document, new MouseEvent('mousedown', { bubbles: true, cancelable: true }), ); expect(document.getElementById('text-input-drop__item')).toBeNull(); done(); }, 50); }); test('complex suggestions', (done) => { const { getByTestId, container } = render( , ); expect(container.firstChild).toMatchSnapshot(); fireEvent.focus(getByTestId('test-input')); fireEvent.change(getByTestId('test-input'), { target: { value: ' ' } }); setTimeout(() => { expectPortal('text-input-drop__item').toMatchSnapshot(); fireEvent( document, new MouseEvent('mousedown', { bubbles: true, cancelable: true }), ); expect(document.getElementById('text-input-drop__item')).toBeNull(); done(); }, 50); }); test('close suggestion drop', (done) => { const { getByTestId, container } = render( , ); expect(container.firstChild).toMatchSnapshot(); fireEvent.focus(getByTestId('test-input')); fireEvent.change(getByTestId('test-input'), { target: { value: ' ' } }); setTimeout(() => { expectPortal('text-input-drop__item').toMatchSnapshot(); fireEvent.keyDown(getByTestId('test-input'), { key: 'Esc', keyCode: 27, which: 27, }); setTimeout(() => { expect(document.getElementById('text-input-drop__item')).toBeNull(); expect(container.firstChild).toMatchSnapshot(); done(); }, 50); }, 50); }); test('let escape events propagage if there are no suggestions', (done) => { const callback = jest.fn(); const { getByTestId } = render( , ); fireEvent.change(getByTestId('test-input'), { target: { value: ' ' } }); setTimeout(() => { fireEvent.keyDown(getByTestId('test-input'), { key: 'Esc', keyCode: 27, which: 27, }); expect(callback).toBeCalled(); done(); }, 50); }); test('calls onSuggestionsOpen', (done) => { const onSuggestionsOpen = jest.fn(); const { getByTestId } = render( , ); fireEvent.focus(getByTestId('test-input')); setTimeout(() => { expectPortal('text-input-drop__item').toMatchSnapshot(); expect(onSuggestionsOpen).toBeCalled(); done(); }, 50); }); test('calls onSuggestionsClose', (done) => { const onSuggestionsClose = jest.fn(); const { getByTestId, container } = render( , ); expect(container.firstChild).toMatchSnapshot(); fireEvent.focus(getByTestId('test-input')); setTimeout(() => { expectPortal('text-input-drop__item').toMatchSnapshot(); fireEvent.keyDown(getByTestId('test-input'), { key: 'Esc', keyCode: 27, which: 27, }); setTimeout(() => { expect(document.getElementById('text-input-drop__item')).toBeNull(); expect(onSuggestionsClose).toBeCalled(); expect(container.firstChild).toMatchSnapshot(); done(); }, 50); }, 50); }); test('select suggestion', (done) => { const onSelect = jest.fn(); const { getByTestId, container } = render( , ); expect(container.firstChild).toMatchSnapshot(); fireEvent.focus(getByTestId('test-input')); fireEvent.change(getByTestId('test-input'), { target: { value: ' ' } }); setTimeout(() => { expectPortal('text-input-drop__item').toMatchSnapshot(); // Casting a custom to a primitive by erasing type with unknown. fireEvent.click(getByText(document as unknown as HTMLElement, 'test1')); expect(container.firstChild).toMatchSnapshot(); expect(document.getElementById('text-input-drop__item')).toBeNull(); expect(onSelect).toBeCalledWith( expect.objectContaining({ suggestion: 'test1' }), ); done(); }, 50); }); test('select a suggestion with onSelect', () => { const onSelect = jest.fn(); const { getByTestId, container } = render( , ); expect(container.firstChild).toMatchSnapshot(); const input = getByTestId('test-input'); // pressing enter here nothing will happen fireEvent.keyDown(input, { keyCode: 13 }); // enter fireEvent.keyDown(input, { keyCode: 40 }); // down fireEvent.keyDown(input, { keyCode: 40 }); // down fireEvent.keyDown(input, { keyCode: 38 }); // up fireEvent.keyDown(input, { keyCode: 13 }); // enter expect(onSelect).toBeCalledWith( expect.objectContaining({ suggestion: 'test', }), ); }); test('auto-select 2nd suggestion with defaultSuggestion', () => { const onSelect = jest.fn(); const suggestions = ['test1', 'test2']; const defaultSuggestionIndex = 1; const { getByTestId } = render( , ); const input = getByTestId('test-input'); // open drop - second should be automatically highlighted fireEvent.keyDown(input, { keyCode: 40 }); // down // pressing enter here will select the second suggestion fireEvent.keyDown(input, { keyCode: 13 }); // enter expect(onSelect).toBeCalledWith( expect.objectContaining({ suggestion: suggestions[defaultSuggestionIndex], }), ); }); test('auto-select 1st suggestion via typing with defaultSuggestion', () => { const onSelect = jest.fn(); const suggestions = ['nodefault1', 'default', 'nodefault2']; const defaultSuggestionIndex = 1; const { getByTestId } = render( , ); const input = getByTestId('test-input'); // Set focus so drop opens and we track activeSuggestionIndex fireEvent.focus(input); // Fire a change event so that onChange is triggered. fireEvent.change(input, { target: { value: 'ma' } }); // Each time we type, the active suggestion should reset to the suggestion // matching the entered text, or the default suggestion index if no // suggestion matches. Now, when we hit enter, there's no match yet, so // the default suggestion should be selected. fireEvent.keyDown(input, { keyCode: 13 }); // enter expect(onSelect).toBeCalledWith( expect.objectContaining({ suggestion: 'default', }), ); }); test('do not select any suggestion without defaultSuggestion', () => { const onSelect = jest.fn(); const { getByTestId } = render( , ); const input = getByTestId('test-input'); // open drop fireEvent.keyDown(input, { keyCode: 40 }); // down // pressing enter here closes drop but doesn't select fireEvent.keyDown(input, { keyCode: 13 }); // enter // if no suggestion had been selected, don't call onSelect expect(onSelect).not.toBeCalled(); // open drop fireEvent.keyDown(input, { keyCode: 40 }); // down // highlight first fireEvent.keyDown(input, { keyCode: 40 }); // down // highlight second fireEvent.keyDown(input, { keyCode: 40 }); // down // select highlighted fireEvent.keyDown(input, { keyCode: 13 }); // enter expect(onSelect).toBeCalledWith( expect.objectContaining({ suggestion: 'test2', }), ); }); test('select a suggestion with onSuggestionSelect', () => { const onSuggestionSelect = jest.fn(); const { getByTestId, container } = render( , ); expect(container.firstChild).toMatchSnapshot(); const input = getByTestId('test-input'); // pressing enter here nothing will happen fireEvent.keyDown(input, { keyCode: 13 }); // enter fireEvent.keyDown(input, { keyCode: 40 }); // down fireEvent.keyDown(input, { keyCode: 40 }); // down fireEvent.keyDown(input, { keyCode: 38 }); // up fireEvent.keyDown(input, { keyCode: 13 }); // enter expect(onSuggestionSelect).toBeCalledWith( expect.objectContaining({ suggestion: 'test', }), ); }); test('select with onSuggestionSelect when onSelect is present', () => { const onSelect = jest.fn(); const onSuggestionSelect = jest.fn(); const { getByTestId, container } = render( , ); expect(container.firstChild).toMatchSnapshot(); const input = getByTestId('test-input'); // pressing enter here nothing will happen fireEvent.keyDown(input, { keyCode: 13 }); // enter fireEvent.keyDown(input, { keyCode: 40 }); // down fireEvent.keyDown(input, { keyCode: 40 }); // down fireEvent.keyDown(input, { keyCode: 38 }); // up fireEvent.keyDown(input, { keyCode: 13 }); // enter expect(onSuggestionSelect).toBeCalledWith( expect.objectContaining({ suggestion: 'test', }), ); }); test('handles next and previous without suggestion', () => { const onSelect = jest.fn(); const { getByTestId, container } = render( , ); expect(container.firstChild).toMatchSnapshot(); const input = getByTestId('test-input'); fireEvent.keyDown(input, { keyCode: 40 }); fireEvent.keyDown(input, { keyCode: 40 }); fireEvent.keyDown(input, { keyCode: 38 }); fireEvent.keyDown(input, { keyCode: 13 }); // enter expect(onSelect).not.toBeCalled(); expect(container.firstChild).toMatchSnapshot(); }); ['small', 'medium', 'large'].forEach((dropHeight) => { test(`${dropHeight} drop height`, (done) => { const { getByTestId } = render( , ); fireEvent.focus(getByTestId('test-input')); setTimeout(() => { expectPortal('text-input-drop__item').toMatchSnapshot(); done(); }, 50); }); }); test('should return focus to input on select', async () => { const onSelect = jest.fn(); const { getByPlaceholderText } = render( , ); const input = getByPlaceholderText('Type to search...'); expect(document.activeElement).not.toEqual(input); fireEvent.focus(input); expect(document.activeElement).not.toEqual(input); const selection = await waitFor(() => screen.getByText('option1')); fireEvent.click(selection); expect(document.activeElement).toEqual(input); }); test('should return focus to ref on select', async () => { const inputRef = React.createRef(); const onSelect = jest.fn(); const { getByPlaceholderText } = render( , ); const input = getByPlaceholderText('Type to search...'); expect(document.activeElement).not.toEqual(input); fireEvent.focus(input); expect(document.activeElement).not.toEqual(input); const selection = await waitFor(() => screen.getByText('option2')); fireEvent.click(selection); expect(document.activeElement).toEqual(input); }); test('should not have padding when plain="full"', async () => { const { container } = render( , ); expect(container.firstChild).toMatchSnapshot(); }); test('should have padding when plain', async () => { const { container } = render( , ); expect(container.firstChild).toMatchSnapshot(); }); test('should show non-string placeholder', () => { const { container } = render( placeholder text} /> , ); const placeholder = screen.getByText('placeholder text'); expect(placeholder).toBeTruthy(); expect(container.firstChild).toMatchSnapshot(); }); test('should hide non-string placeholder when having a value', () => { const { container } = render( placeholder text} value="test" /> , ); const placeholder = screen.queryByText('placeholder text'); expect(placeholder).toBeNull(); expect(container.firstChild).toMatchSnapshot(); }); test(`should only show default placeholder when placeholder is a string`, () => { const { container, getByTestId } = render( , ); const placeholder = screen.queryByText('placeholder text'); fireEvent.change(getByTestId('placeholder'), { target: { value: 'something' }, }); expect(placeholder).toBeNull(); expect(container.firstChild).toMatchSnapshot(); // after value is removed, only one placeholder should be present // nothing from styled placeholder should appear since placeholder // is a string fireEvent.change(getByTestId('placeholder'), { target: { value: '' } }); expect(container.firstChild).toMatchSnapshot(); }); test('textAlign end', () => { const { container } = render( , ); expect(container.firstChild).toMatchSnapshot(); }); test('custom theme input font size', () => { const { container } = render( , ); expect(container.firstChild).toMatchSnapshot(); }); test('renders size', () => { const { container } = render( , ); expect(container.children).toMatchSnapshot(); }); test('width', () => { const { container } = render( , ); expect(container.firstChild).toMatchSnapshot(); }); test('matches icon size to size prop when theme.icon.matchSize is true', () => { const theme = { icon: { matchSize: true, }, }; const { asFragment } = render( } /> } /> } /> , ); expect(asFragment()).toMatchSnapshot(); }); });