import * as React from 'react' import { fireEvent } from '@testing-library/dom' import userEvent from '@testing-library/user-event' import { COMMON_IME_CONTROL_KEYS } from '~/src/hooks/useKeyboardActionLockerWhileComposing' import { render } from '~/src/utils/test' import { TextField } from './TextField' import { type TextFieldProps, type TextFieldRef } from './TextField.types' describe('TextField', () => { const user = userEvent.setup() let props: TextFieldProps beforeEach(() => { props = {} }) const renderComponent = (optionProps?: TextFieldProps) => render( ) it('TextField have default attribute', () => { const { getByRole } = renderComponent() const input = getByRole('textbox') expect(input).not.toHaveAttribute('disabled') expect(input).not.toHaveAttribute('readOnly') expect(input).not.toHaveAttribute('placeholder') expect(input).not.toHaveAttribute('maxLength') }) it('should have "disabled" attribute when "disabled" props is "true"', () => { const { getByRole } = renderComponent({ disabled: true }) const input = getByRole('textbox') expect(input).toHaveAttribute('disabled') }) it('should have "readOnly" attribute when "readOnly" props is "true"', () => { const { getByRole } = renderComponent({ readOnly: true }) const input = getByRole('textbox') expect(input).toHaveAttribute('readOnly') }) it('should have "placeholder" attribute when "placeholder" props is "true"', () => { const { getByRole } = renderComponent({ placeholder: 'this is placeholder', }) const input = getByRole('textbox') expect(input).toHaveAttribute('placeholder', 'this is placeholder') }) it('should have "maxLength" attribute when "maxLength" props is "true"', () => { const { getByRole } = renderComponent({ maxLength: 5 }) const input = getByRole('textbox') expect(input).toHaveAttribute('maxLength', '5') }) describe('callback should be called', () => { it('onFocus', () => { const onFocus = jest.fn() const { getByRole } = renderComponent({ onFocus }) const input = getByRole('textbox') input.focus() expect(onFocus).toHaveBeenCalled() }) it('onChange', () => { const onChange = jest.fn() const { getByRole } = renderComponent({ onChange }) const input = getByRole('textbox') fireEvent.change(input, { target: { value: 'test' } }) expect(onChange).toHaveBeenCalled() }) it('onKeyDown', () => { const onKeyDown = jest.fn() const { getByRole } = renderComponent({ onKeyDown }) const input = getByRole('textbox') fireEvent.keyDown(input, { key: 'A', code: 'KeyA' }) expect(onKeyDown).toHaveBeenCalled() }) it('onKeyUp', () => { const onKeyUp = jest.fn() const { getByRole } = renderComponent({ onKeyUp }) const input = getByRole('textbox') fireEvent.keyUp(input, { key: 'A', code: 'KeyA' }) expect(onKeyUp).toHaveBeenCalled() }) }) describe('no callback should called when "disabled" or "readOnly" props are "true"', () => { it('onFocus, disabled', () => { const onFocus = jest.fn() const { getByRole } = renderComponent({ onFocus, disabled: true }) const input = getByRole('textbox') input.focus() expect(onFocus).not.toHaveBeenCalled() }) it('onFocus, readOnly', () => { const onFocus = jest.fn() const { getByRole } = renderComponent({ onFocus, readOnly: true }) const input = getByRole('textbox') input.focus() expect(onFocus).not.toHaveBeenCalled() }) it('onChange, disabled', () => { const onChange = jest.fn() const { getByRole } = renderComponent({ onChange, disabled: true }) const input = getByRole('textbox') fireEvent.change(input, { target: { value: 'test' } }) expect(onChange).not.toHaveBeenCalled() }) it('onChange, readOnly', () => { const onChange = jest.fn() const { getByRole } = renderComponent({ onChange, readOnly: true }) const input = getByRole('textbox') fireEvent.change(input, { target: { value: 'test' } }) expect(onChange).not.toHaveBeenCalled() }) it('onKeyDown, disabled', () => { const onKeyDown = jest.fn() const { getByRole } = renderComponent({ onKeyDown, disabled: true }) const input = getByRole('textbox') fireEvent.keyDown(input, { key: 'A', code: 'KeyA' }) expect(onKeyDown).not.toHaveBeenCalled() }) it('onKeyDown, readOnly', () => { const onKeyDown = jest.fn() const { getByRole } = renderComponent({ onKeyDown, readOnly: true }) const input = getByRole('textbox') fireEvent.keyDown(input, { key: 'A', code: 'KeyA' }) expect(onKeyDown).not.toHaveBeenCalled() }) it('onKeyUp, disabled', () => { const onKeyUp = jest.fn() const { getByRole } = renderComponent({ onKeyUp, disabled: true }) const input = getByRole('textbox') fireEvent.keyUp(input, { key: 'A', code: 'KeyA' }) expect(onKeyUp).not.toHaveBeenCalled() }) it('onKeyUp, readOnly', () => { const onKeyUp = jest.fn() const { getByRole } = renderComponent({ onKeyUp, readOnly: true }) const input = getByRole('textbox') fireEvent.keyUp(input, { key: 'A', code: 'KeyA' }) expect(onKeyUp).not.toHaveBeenCalled() }) }) describe('Keyboard event handlers for common ime control keys should not be called while composing', () => { it('onKeyDown', async () => { const onKeyDown = jest.fn() const { getByRole } = renderComponent({ onKeyDown }) const input = getByRole('textbox') COMMON_IME_CONTROL_KEYS.forEach(async (key) => { const isCompositionStartFired = fireEvent.compositionStart(input) fireEvent.keyDown(input, { key, isComposing: isCompositionStartFired }) expect(onKeyDown).not.toHaveBeenCalled() }) }) it('onKeyUp', () => { const onKeyUp = jest.fn() const { getByRole } = renderComponent({ onKeyUp }) const input = getByRole('textbox') COMMON_IME_CONTROL_KEYS.forEach((key) => { const isCompositionStartFired = fireEvent.compositionStart(input) fireEvent.keyUp(input, { key, isComposing: isCompositionStartFired }) expect(onKeyUp).not.toHaveBeenCalled() }) }) }) describe('show remove button only when it is filled and focused/hovered', () => { /** * FIXME: This test is not working properly. * Jest-dom does not support css which is not inline. * @see https://github.com/testing-library/jest-dom/issues/113#issuecomment-496971128 */ // it('disappear when empty & focused/hovered', async () => { // const { getByTestId } = renderComponent({ value: '', allowClear: true }) // const rendered = getByTestId(TEXT_INPUT_TEST_ID) // const input = rendered.getElementsByTagName('input')[0] // await user.hover(input) // input.focus() // const clearButton = within(rendered).queryByTestId(TEXT_INPUT_CLEAR_ICON_TEST_ID) // expect(clearButton).not.toBeVisible() // }) // it('disappear when filled & not focused/hovered', () => { // const { getByTestId } = renderComponent({ value: 'test', allowClear: true, autoFocus: false }) // const rendered = getByTestId(TEXT_INPUT_TEST_ID) // const clearButton = within(rendered).queryByTestId(TEXT_INPUT_CLEAR_ICON_TEST_ID) // expect(clearButton).not.toBeVisible() // }) it('appear when filled & hovered', async () => { const { getByRole, getByLabelText } = renderComponent({ value: 'test', allowClear: true, }) const input = getByRole('textbox') await user.hover(input) const clearButton = getByLabelText('Clear input') expect(clearButton).toBeVisible() }) it('appear when filled & focused', () => { const { getByRole, getByLabelText } = renderComponent({ value: 'test', allowClear: true, }) const input = getByRole('textbox') input.focus() const clearButton = getByLabelText('Clear input') expect(clearButton).toBeVisible() }) it('should focus the clear button when Tab key is pressed', async () => { const { getByRole } = renderComponent({ value: 'test', allowClear: true, }) const input = getByRole('textbox') input.focus() await user.tab() expect(document.activeElement).toHaveClass('CloseIconWrapper') }) it('should clear input value when clear button is clicked', async () => { const onChange = jest.fn() const { getByRole, getByLabelText } = renderComponent({ value: 'test', allowClear: true, onChange, }) const input = getByRole('textbox') as HTMLInputElement const clearButton = getByLabelText('Clear input') input.focus() expect(input.value).toBe('test') await user.click(clearButton) // handleClear dispatches an input event, which React converts to onChange expect(onChange).toHaveBeenCalled() }) it('should not clear input when disabled', async () => { const { getByRole, queryByLabelText } = renderComponent({ value: 'test', allowClear: true, disabled: true, }) const input = getByRole('textbox') as HTMLInputElement const clearButton = queryByLabelText('Clear input') expect(clearButton).not.toBeInTheDocument() expect(input.value).toBe('test') }) it('should not clear input when readOnly', async () => { const { getByRole, queryByLabelText } = renderComponent({ value: 'test', allowClear: true, readOnly: true, }) const input = getByRole('textbox') as HTMLInputElement const clearButton = queryByLabelText('Clear input') expect(clearButton).not.toBeInTheDocument() expect(input.value).toBe('test') }) }) describe('selectAllOnInit', () => { it('should select all text on mount when selectAllOnInit is true', async () => { const { getByRole } = renderComponent({ value: 'test value', selectAllOnInit: true, }) const input = getByRole('textbox') as HTMLInputElement // Wait for setTimeout in the component await new Promise((resolve) => setTimeout(resolve, 10)) expect(input.selectionStart).toBe(0) expect(input.selectionEnd).toBe('test value'.length) }) it('should not select all text on mount when selectAllOnInit is false', () => { const { getByRole } = renderComponent({ value: 'test value', selectAllOnInit: false, }) const input = getByRole('textbox') as HTMLInputElement expect(input.selectionStart).toBe(input.selectionEnd) }) }) describe('selectAllOnFocus', () => { it('should select all text on focus when selectAllOnFocus is true', async () => { const { getByRole } = renderComponent({ value: 'test value', selectAllOnFocus: true, }) const input = getByRole('textbox') as HTMLInputElement input.focus() // Wait for setTimeout in the component await new Promise((resolve) => setTimeout(resolve, 10)) expect(input.selectionStart).toBe(0) expect(input.selectionEnd).toBe('test value'.length) }) it('should not select all text on focus when selectAllOnFocus is false', () => { const { getByRole } = renderComponent({ value: 'test value', selectAllOnFocus: false, }) const input = getByRole('textbox') as HTMLInputElement input.focus() expect(input.selectionStart).toBe(input.selectionEnd) }) }) describe('setSelectionRange with different input types', () => { it('should not set selection range for number type via ref', () => { const ref = React.createRef() render( ) const input = ref.current?.getDOMNode() as HTMLInputElement const setSelectionRangeSpy = jest.spyOn(input, 'setSelectionRange') ref.current?.setSelectionRange(0, 3) expect(setSelectionRangeSpy).not.toHaveBeenCalled() }) it('should not set selection range for email type via ref', () => { const ref = React.createRef() render( ) const input = ref.current?.getDOMNode() as HTMLInputElement const setSelectionRangeSpy = jest.spyOn(input, 'setSelectionRange') ref.current?.setSelectionRange(0, 5) expect(setSelectionRangeSpy).not.toHaveBeenCalled() }) it('should not set selection range for hidden type via ref', () => { const ref = React.createRef() render( ) const input = ref.current?.getDOMNode() as HTMLInputElement const setSelectionRangeSpy = jest.spyOn(input, 'setSelectionRange') ref.current?.setSelectionRange(0, 5) expect(setSelectionRangeSpy).not.toHaveBeenCalled() }) it('should set selection range for text type via ref', () => { const ref = React.createRef() render( ) const input = ref.current?.getDOMNode() as HTMLInputElement input.focus() ref.current?.setSelectionRange(0, 4) expect(input.selectionStart).toBe(0) expect(input.selectionEnd).toBe(4) }) }) })