From 0a81ce2f0d0017db2256fc71bd317d26aee7f0b5 Mon Sep 17 00:00:00 2001 From: Gregory <41027510+refactor256@users.noreply.github.com> Date: Sat, 11 Feb 2023 20:22:45 +0400 Subject: [PATCH] fix(TextInput): no unexpected autocomplete when label prop present (#509) --- src/components/TextInput/TextInput.tsx | 10 +- .../__tests__/TextInput.input.test.tsx | 167 ++++++++++++++++++ .../TextInput/__tests__/TextInput.test.tsx | 119 ------------- .../__tests__/TextInput.textarea.test.tsx | 63 +++++++ 4 files changed, 237 insertions(+), 122 deletions(-) create mode 100644 src/components/TextInput/__tests__/TextInput.input.test.tsx delete mode 100644 src/components/TextInput/__tests__/TextInput.test.tsx create mode 100644 src/components/TextInput/__tests__/TextInput.textarea.test.tsx diff --git a/src/components/TextInput/TextInput.tsx b/src/components/TextInput/TextInput.tsx index dbf8b1106..49c0d1ddf 100644 --- a/src/components/TextInput/TextInput.tsx +++ b/src/components/TextInput/TextInput.tsx @@ -62,14 +62,18 @@ export const TextInput = React.forwardRef(funct const [uncontrolledValue, setUncontrolledValue] = React.useState(defaultValue ?? ''); const innerControlRef = React.useRef(null); const labelRef = React.useRef(null); - const innerId = useUniqId(); - const id = label ? originalId || innerId : originalId; const [hasVerticalScrollbar, setHasVerticalScrollbar] = React.useState(false); const isControlled = value !== undefined; const inputValue = isControlled ? value : uncontrolledValue; const isLabelVisible = !multiline && Boolean(label); + const innerId = useUniqId(); + const id = isLabelVisible ? originalId || innerId : originalId; + + const isAutoCompleteOff = + isLabelVisible && !originalId && !name && typeof autoComplete === 'undefined'; + const handleRef = useForkRef(props.controlRef, innerControlRef); const labelSize = useElementSize(isLabelVisible ? labelRef : null, size); @@ -141,7 +145,7 @@ export const TextInput = React.forwardRef(funct onUpdate(newValue); } }, - autoComplete: prepareAutoComplete(autoComplete), + autoComplete: isAutoCompleteOff ? 'off' : prepareAutoComplete(autoComplete), controlProps, }; diff --git a/src/components/TextInput/__tests__/TextInput.input.test.tsx b/src/components/TextInput/__tests__/TextInput.input.test.tsx new file mode 100644 index 000000000..1ab297e59 --- /dev/null +++ b/src/components/TextInput/__tests__/TextInput.input.test.tsx @@ -0,0 +1,167 @@ +import React from 'react'; +import userEvent from '@testing-library/user-event'; +import {render, screen, fireEvent} from '@testing-library/react'; +import {TextInput} from '../TextInput'; + +describe('TextInput input', () => { + describe('without label prop', () => { + describe('basic', () => { + test('render input by default', () => { + render(); + const input = screen.getByRole('textbox'); + + expect(input).toBeVisible(); + expect(input.tagName.toLowerCase()).toBe('input'); + }); + + test('render error message with error prop', () => { + const {container} = render(); + + // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access + expect(container.querySelector('.yc-text-input__error')).toBeInTheDocument(); + expect(screen.getByText('Some Error')).toBeVisible(); + }); + + test('do not show error without error prop', () => { + const {container} = render(); + + // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access + expect(container.querySelector('.yc-text-input__error')).not.toBeInTheDocument(); + }); + + test('render clear button with hasClear prop', () => { + render(); + + expect(screen.getByRole('button', {name: 'Clear input value'})).toBeInTheDocument(); + }); + + test('do not render clear button without hasClear prop', () => { + render(); + + expect( + screen.queryByRole('button', {name: 'Clear input value'}), + ).not.toBeInTheDocument(); + }); + + test('call onChange when input changes value', () => { + const onChangeFn = jest.fn(); + + render(); + fireEvent.change(screen.getByRole('textbox'), {target: {value: '1'}}); + + expect(onChangeFn).toBeCalled(); + }); + + test('call onUpdate with certain value when input changes value', () => { + const onUpdateFn = jest.fn(); + const value = 'some'; + + render(); + fireEvent.change(screen.getByRole('textbox'), {target: {value}}); + + expect(onUpdateFn).toBeCalledWith(value); + }); + + test('call onChange when click to clean button', async () => { + const onChangeFn = jest.fn(); + const user = userEvent.setup(); + render(); + const clear = screen.getByRole('button', {name: 'Clear input value'}); + + if (clear) { + await user.click(clear); + } + + expect(onChangeFn).toBeCalled(); + }); + + test('call onUpdate with emply value when click to clean button', async () => { + const onUpdateFn = jest.fn(); + const user = userEvent.setup(); + render(); + const clear = screen.getByRole('button', {name: 'Clear input value'}); + + if (clear) { + await user.click(clear); + } + + expect(onUpdateFn).toBeCalledWith(''); + }); + }); + + describe('autocomplete', () => { + test('render no autocomplete attribute when no autoComplete, no id, no name props', () => { + render(); + const input = screen.getByRole('textbox'); + + expect(input.getAttribute('autocomplete')).toBeNull(); + }); + + test('render autocomplete=on attribute with autoComplete prop', () => { + render(); + const input = screen.getByRole('textbox'); + + expect(input.getAttribute('autocomplete')).toBe('on'); + }); + + test('render autocomplete=off attribute with autoComplete=false prop', () => { + render(); + const input = screen.getByRole('textbox'); + + expect(input.getAttribute('autocomplete')).toBe('off'); + }); + }); + }); + + describe('with label prop', () => { + describe('basic', () => { + test('render input with label', () => { + const {container} = render(); + + // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access + const label = container.querySelector('.yc-text-input__label'); + + expect(label).toBeInTheDocument(); + expect(label?.tagName.toLowerCase()).toBe('label'); + expect(screen.getByText('Label:')).toBeVisible(); + }); + }); + + describe('autocomplete', () => { + test('render autocomplete=off attribute when no autoComplete, no id, no name props', () => { + render(); + const input = screen.getByRole('textbox'); + + expect(input.getAttribute('autocomplete')).toBe('off'); + }); + + test('render no autocomplete attribute when no autoComplete prop, but id prop set', () => { + render(); + const input = screen.getByRole('textbox'); + + expect(input.getAttribute('autocomplete')).toBeNull(); + }); + + test('render no autocomplete attribute when no autoComplete prop, but name prop set', () => { + render(); + const input = screen.getByRole('textbox'); + + expect(input.getAttribute('autocomplete')).toBeNull(); + }); + + test('render autocomplete=on attribute when autoComplete prop "on"', () => { + render(); + const input = screen.getByRole('textbox'); + + expect(input.getAttribute('autocomplete')).toBe('on'); + }); + + test('render autocomplete=off attribute when autoComplete prop "off"', () => { + render(); + const input = screen.getByRole('textbox'); + + expect(input.getAttribute('autocomplete')).toBe('off'); + }); + }); + }); +}); diff --git a/src/components/TextInput/__tests__/TextInput.test.tsx b/src/components/TextInput/__tests__/TextInput.test.tsx deleted file mode 100644 index b10602118..000000000 --- a/src/components/TextInput/__tests__/TextInput.test.tsx +++ /dev/null @@ -1,119 +0,0 @@ -import React from 'react'; -import userEvent from '@testing-library/user-event'; -import {render, screen, fireEvent} from '@testing-library/react'; -import {TextInput} from '../TextInput'; - -describe('TextInput', () => { - test('render input by default', () => { - render(); - const input = screen.getByRole('textbox'); - - expect(input).toBeVisible(); - expect(input.tagName.toLowerCase()).toBe('input'); - }); - - test('render textarea with multiline prop', () => { - render(); - const input = screen.getByRole('textbox'); - - expect(input).toBeVisible(); - expect(input.tagName.toLowerCase()).toBe('textarea'); - }); - - test('render input with label', () => { - const {container} = render(); - - // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access - const label = container.querySelector('.yc-text-input__label'); - - expect(label).toBeInTheDocument(); - expect(label?.tagName.toLowerCase()).toBe('label'); - expect(screen.getByText('Label:')).toBeVisible(); - }); - - test('render error message with error prop', () => { - const {container} = render(); - - // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access - expect(container.querySelector('.yc-text-input__error')).toBeInTheDocument(); - expect(screen.getByText('Some Error')).toBeVisible(); - }); - - test('do not show error without error prop', () => { - const {container} = render(); - - // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access - expect(container.querySelector('.yc-text-input__error')).not.toBeInTheDocument(); - }); - - test('render clear button with hasClear prop', () => { - render(); - - expect(screen.getByRole('button', {name: 'Clear input value'})).toBeInTheDocument(); - }); - - test('do not render clear button without hasClear prop', () => { - render(); - - expect(screen.queryByRole('button', {name: 'Clear input value'})).not.toBeInTheDocument(); - }); - - test('call onChange when input changes value', () => { - const onChangeFn = jest.fn(); - - render(); - fireEvent.change(screen.getByRole('textbox'), {target: {value: '1'}}); - - expect(onChangeFn).toBeCalled(); - }); - - test('call onUpdate with certain value when input changes value', () => { - const onUpdateFn = jest.fn(); - const value = 'some'; - - render(); - fireEvent.change(screen.getByRole('textbox'), {target: {value}}); - - expect(onUpdateFn).toBeCalledWith(value); - }); - - test('call onChange when click to clean button', async () => { - const onChangeFn = jest.fn(); - const user = userEvent.setup(); - render(); - const clear = screen.getByRole('button', {name: 'Clear input value'}); - - if (clear) { - await user.click(clear); - } - - expect(onChangeFn).toBeCalled(); - }); - - test('call onUpdate with emply value when click to clean button', async () => { - const onUpdateFn = jest.fn(); - const user = userEvent.setup(); - render(); - const clear = screen.getByRole('button', {name: 'Clear input value'}); - - if (clear) { - await user.click(clear); - } - - expect(onUpdateFn).toBeCalledWith(''); - }); - - test('render autocomplete=on attribute with autoComplete prop', () => { - render(); - const input = screen.getByRole('textbox'); - - expect(input.getAttribute('autocomplete')).toBe('on'); - }); - - test('render autocomplete=off attribute with autoComplete=false prop', () => { - render(); - const input = screen.getByRole('textbox'); - - expect(input.getAttribute('autocomplete')).toBe('off'); - }); -}); diff --git a/src/components/TextInput/__tests__/TextInput.textarea.test.tsx b/src/components/TextInput/__tests__/TextInput.textarea.test.tsx new file mode 100644 index 000000000..e8bdbbd7b --- /dev/null +++ b/src/components/TextInput/__tests__/TextInput.textarea.test.tsx @@ -0,0 +1,63 @@ +import React from 'react'; +import {render, screen} from '@testing-library/react'; +import {TextInput} from '../TextInput'; + +describe('TextInput textarea', () => { + describe('without label prop', () => { + describe('basic', () => { + test('render textarea with multiline prop', () => { + render(); + const input = screen.getByRole('textbox'); + + expect(input).toBeVisible(); + expect(input.tagName.toLowerCase()).toBe('textarea'); + }); + }); + + describe('autocomplete', () => { + test('render no autocomplete attribute when no autoComplete, no id, no name props', () => { + render(); + const input = screen.getByRole('textbox'); + + expect(input.getAttribute('autocomplete')).toBeNull(); + }); + }); + }); + + describe('with label prop', () => { + describe('basic', () => { + test('render textarea without label', () => { + const {container} = render(); + + // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access + const label = container.querySelector('.yc-text-input__label'); + + expect(label).toBeNull(); + expect(screen.queryByText('Label:')).toBeNull(); + }); + }); + + describe('autocomplete', () => { + test('render no autocomplete attribute when no autoComplete, no id, no name props', () => { + render(); + const input = screen.getByRole('textbox'); + + expect(input.getAttribute('autocomplete')).toBeNull(); + }); + + test('render no autocomplete attribute when no autoComplete prop, but id prop set', () => { + render(); + const input = screen.getByRole('textbox'); + + expect(input.getAttribute('autocomplete')).toBeNull(); + }); + + test('render no autocomplete attribute when no autoComplete prop, but name prop set', () => { + render(); + const input = screen.getByRole('textbox'); + + expect(input.getAttribute('autocomplete')).toBeNull(); + }); + }); + }); +});