Skip to content

Commit

Permalink
Merge pull request #16 from ekabolotina/feature/add-textarea-component
Browse files Browse the repository at this point in the history
Add `Textarea` component
  • Loading branch information
ekabolotina authored Sep 24, 2022
2 parents 608bfc7 + ac2a35d commit b252670
Show file tree
Hide file tree
Showing 5 changed files with 175 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ from `@alfalab/core-components`, but with one required prop `name`. Available co
* RadioGroup
* Select
* Switch
* Textarea

Consider the example:
```tsx
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
"Select/",
"Switch/",
"InputAutocomplete/",
"Textarea/",
"hooks/"
]
}
135 changes: 135 additions & 0 deletions src/Textarea/index.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import React, { createRef } from 'react';
import userEvent from '@testing-library/user-event';
import { Textarea as CoreComponentsTextarea } from '@alfalab/core-components/textarea';
import { FormikProps } from 'formik';
import { renderWithFormik, render, screen } from 'test-utils';
import { Textarea } from '.';

type Values = {
field: string;
};

it('should render original component', () => {
render(
<div data-testid="originalComponent">
<CoreComponentsTextarea name="field" />
</div>,
);
renderWithFormik<Values>(
<div data-testid="bindingComponent">
<Textarea name="field" />
</div>,
{ initialValues: { field: '' } },
);

const originalComponent = screen.getByTestId('originalComponent');
const bindingComponent = screen.getByTestId('bindingComponent');

expect(originalComponent.innerHTML).toBe(bindingComponent.innerHTML);
});

it('should render value from formik context', () => {
renderWithFormik<Values>(<Textarea name="field" data-testid="input" />, {
initialValues: { field: 'valueFromFormikContext' },
});

expect(screen.getByTestId('input')).toHaveValue('valueFromFormikContext');
});

it('should update value inside formik context', async () => {
const formikRef = createRef<FormikProps<Values>>();

renderWithFormik<Values>(<Textarea name="field" data-testid="input" />, {
initialValues: { field: '' },
innerRef: formikRef,
});
await userEvent.type(screen.getByTestId('input'), 'typedValue');

expect(formikRef.current?.values.field).toBe('typedValue');
});

it('should update `touched` state inside formik context', async () => {
const formikRef = createRef<FormikProps<Values>>();

renderWithFormik<Values>(<Textarea name="field" data-testid="input" />, {
initialValues: { field: '' },
innerRef: formikRef,
});

await userEvent.click(screen.getByTestId('input'));
await userEvent.click(document.body);

expect(formikRef.current?.touched.field).toBe(true);
});

it('should not render error from formik context if not touched', () => {
renderWithFormik<Values>(<Textarea name="field" />, {
initialValues: { field: '' },
initialErrors: { field: 'Error text' },
});

expect(screen.queryByText('Error text')).not.toBeInTheDocument();
});

it('should not render error from formik context if `error={false}` provided', () => {
renderWithFormik<Values>(<Textarea name="field" error={false} />, {
initialValues: { field: '' },
initialErrors: { field: 'Error text' },
initialTouched: { field: true },
});

expect(screen.queryByText('Error text')).not.toBeInTheDocument();
});

it('should render provided error instead of the one from formik context', () => {
renderWithFormik<Values>(<Textarea name="field" error="Custom error" />, {
initialValues: { field: '' },
initialErrors: { field: 'Error text' },
initialTouched: { field: true },
});

expect(screen.queryByText('Error text')).not.toBeInTheDocument();
expect(screen.queryByText('Custom error')).toBeInTheDocument();
});

it('should render error from formik context if touched', () => {
renderWithFormik<Values>(<Textarea name="field" />, {
initialValues: { field: '' },
initialErrors: { field: 'Error text' },
initialTouched: { field: true },
});

expect(screen.queryByText('Error text')).toBeInTheDocument();
});

it('should call provided `onChange` callback', async () => {
const onChangeCallback = jest.fn();

renderWithFormik<Values>(
<Textarea name="field" data-testid="input" onChange={onChangeCallback} />,
{
initialValues: { field: '' },
},
);
await userEvent.type(screen.getByTestId('input'), 'typedValue');

expect(onChangeCallback).toHaveBeenLastCalledWith(
expect.objectContaining({ target: expect.objectContaining({ value: 'typedValue' }) }),
{ value: 'typedValue' },
);
});

it('should call provided `onBlur` callback', async () => {
const onBlurCallback = jest.fn();

renderWithFormik<Values>(
<Textarea name="field" data-testid="input" onBlur={onBlurCallback} />,
{
initialValues: { field: '' },
},
);
await userEvent.click(screen.getByTestId('input'));
await userEvent.click(document.body);

expect(onBlurCallback).toBeCalled();
});
37 changes: 37 additions & 0 deletions src/Textarea/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React, { FC } from 'react';
import { SetRequired } from 'type-fest';
import {
Textarea as CoreComponentsTextarea,
TextareaProps as CoreComponentsTextareaProps,
} from '@alfalab/core-components/textarea';
import { useField } from 'formik';
import { useFieldBlurState } from '../hooks/useFieldBlurState';
import { useFieldOkState } from '../hooks/useFieldOkState';

export type TextareaProps = SetRequired<CoreComponentsTextareaProps, 'name'>;

export const Textarea: FC<TextareaProps> = (props) => {
// TODO: Maybe use `useInputFieldState` hook
const { name, onChange, ...restProps } = props;
const blurState = useFieldBlurState(props);
const { error } = useFieldOkState(props);
const [field, , form] = useField(name);

const handleChange: TextareaProps['onChange'] = (event, payload) => {
form.setValue(payload.value);

if (onChange) {
onChange(event, payload);
}
};

return (
<CoreComponentsTextarea
{...restProps}
{...field}
{...blurState}
error={error}
onChange={handleChange}
/>
);
};
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export * from './PhoneInput';
export * from './RadioGroup';
export * from './Select';
export * from './Switch';
export * from './Textarea';

0 comments on commit b252670

Please sign in to comment.