Skip to content

Commit

Permalink
feat(core-components-input): add clear button, some improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
reme3d2y committed Sep 10, 2020
1 parent fdd7179 commit 4f9a453
Show file tree
Hide file tree
Showing 16 changed files with 296 additions and 102 deletions.
21 changes: 17 additions & 4 deletions packages/amount-input/src/Component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export const AmountInput: React.FC<AmountInputProps> = ({
onChange,
onBlur,
onFocus,
onClear,
...restProps
}: AmountInputProps) => {
const [focused, setFocused] = useState(false);
Expand Down Expand Up @@ -151,27 +152,38 @@ export const AmountInput: React.FC<AmountInputProps> = ({
};

const handleInputFocus = useCallback(
(e: React.FocusEvent<HTMLInputElement>) => {
(event: React.FocusEvent<HTMLInputElement>) => {
setFocused(true);

if (onFocus) {
onFocus(e);
onFocus(event);
}
},
[onFocus],
);

const handleInputBlur = useCallback(
(e: React.FocusEvent<HTMLInputElement>) => {
(event: React.FocusEvent<HTMLInputElement>) => {
setFocused(false);

if (onBlur) {
onBlur(e);
onBlur(event);
}
},
[onBlur],
);

const handleClear = useCallback(
(event: React.MouseEvent<HTMLButtonElement>) => {
setInputValue('');

if (onClear) {
onClear(event);
}
},
[onClear],
);

const [majorPart, minorPart] = inputValue.split(',');

return (
Expand Down Expand Up @@ -208,6 +220,7 @@ export const AmountInput: React.FC<AmountInputProps> = ({
onChange={handleChange}
onFocus={handleInputFocus}
onBlur={handleInputBlur}
onClear={handleClear}
dataTestId={dataTestId}
/>
</div>
Expand Down
10 changes: 5 additions & 5 deletions packages/amount-input/src/__snapshots__/Component.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Object {
</div>
<div
class="component s component"
class="component s formControl component"
>
<div
class="inner"
Expand Down Expand Up @@ -95,7 +95,7 @@ Object {
</div>
<div
class="component s component"
class="component s formControl component"
>
<div
class="inner"
Expand Down Expand Up @@ -210,7 +210,7 @@ Object {
</div>
<div
class="component s component"
class="component s formControl component"
>
<div
class="inner"
Expand Down Expand Up @@ -270,7 +270,7 @@ Object {
</div>
<div
class="component s filled component"
class="component s filled formControl component"
>
<div
class="inner"
Expand Down Expand Up @@ -331,7 +331,7 @@ Object {
</div>
<div
class="component s filled component"
class="component s filled formControl component"
>
<div
class="inner"
Expand Down
8 changes: 4 additions & 4 deletions packages/bank-card/src/__snapshots__/Component.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ exports[`BankCard Snapshots tests should match snapshots 1`] = `
</svg>
</div>
<div
class="component s filled hasLabel block hasRightAddons"
class="component s filled hasLabel block hasRightAddons formControl"
>
<div
class="inner"
Expand Down Expand Up @@ -139,7 +139,7 @@ exports[`BankCard Snapshots tests should match snapshots 2`] = `
</svg>
</div>
<div
class="component s filled hasLabel block hasRightAddons"
class="component s filled hasLabel block hasRightAddons formControl"
>
<div
class="inner"
Expand Down Expand Up @@ -245,7 +245,7 @@ exports[`BankCard Snapshots tests should match snapshots 3`] = `
</svg>
</div>
<div
class="component s filled hasLabel block hasRightAddons"
class="component s filled hasLabel block hasRightAddons formControl"
>
<div
class="inner"
Expand Down Expand Up @@ -351,7 +351,7 @@ exports[`BankCard Snapshots tests should match snapshots 4`] = `
</svg>
</div>
<div
class="component s filled hasLabel block hasRightAddons"
class="component s filled hasLabel block hasRightAddons formControl"
>
<div
class="inner"
Expand Down
4 changes: 0 additions & 4 deletions packages/form-control/src/index.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,6 @@
box-sizing: border-box;
}

.component:not(.disabled) .inner {
cursor: pointer;
}

.inputWrapper {
flex-grow: 1;
position: relative;
Expand Down
4 changes: 3 additions & 1 deletion packages/input/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
"react": "^16.9.0"
},
"dependencies": {
"@alfalab/core-components-form-control": "^1.2.1"
"@alfalab/core-components-form-control": "^1.2.1",
"@alfalab/core-components-button": "^1.4.0",
"@alfalab/icons-glyph": "^1.26.0"
}
}
1 change: 1 addition & 0 deletions packages/input/src/Component.stories.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import { Input } from '@alfalab/core-components-input';
'text',
)}
block={boolean('block', false)}
clear={boolean('clear', false)}
size={select('size', ['s', 'm', 'l'], 's')}
disabled={boolean('disabled', false)}
placeholder={text('placeholder', '')}
Expand Down
152 changes: 111 additions & 41 deletions packages/input/src/Component.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ describe('Input', () => {
expect(getByTestId(dataTestId).tagName).toBe('INPUT');
});

it('should set `disabled` atribute', () => {
const dataTestId = 'test-id';
const { getByTestId } = render(<Input disabled={true} dataTestId={dataTestId} />);

expect(getByTestId(dataTestId)).toHaveAttribute('disabled');
});

describe('Classes tests', () => {
it('should set `className` class to root', () => {
const className = 'test-class';
Expand All @@ -58,71 +65,125 @@ describe('Input', () => {
expect(container.getElementsByClassName(className)).toBeTruthy();
});

describe('when component is controlled', () => {
it('should set `filled` class when value passed', () => {
const { container } = render(<Input value='some value' />);
it('should set `hasLabel` class', () => {
const dataTestId = 'test-id';
const { getByTestId } = render(<Input label='label' dataTestId={dataTestId} />);

expect(container.firstElementChild).toHaveClass('filled');
});
expect(getByTestId(dataTestId)).toHaveClass('hasLabel');
});
});

it('should not set `filled` class if the value is empty', () => {
const { container } = render(<Input value='' />);
describe('when component is controlled', () => {
it('should set `filled` class when value passed', () => {
const { container } = render(<Input value='some value' />);

expect(container.firstElementChild).not.toHaveClass('filled');
});
expect(container.firstElementChild).toHaveClass('filled');
});

it('should unset `filled` class if the value becomes empty', () => {
const { container, rerender } = render(<Input value='some value' />);
it('should not set `filled` class if the value is empty', () => {
const { container } = render(<Input value='' />);

rerender(<Input value='' />);
expect(container.firstElementChild).not.toHaveClass('filled');
});

expect(container.firstElementChild).not.toHaveClass('filled');
});
it('should unset `filled` class if the value becomes empty', () => {
const { container, rerender } = render(<Input value='some value' />);

rerender(<Input value='' />);

expect(container.firstElementChild).not.toHaveClass('filled');
});

describe('when component is uncontrolled', () => {
it('should set `filled` class when defaultValue passed', () => {
const { container } = render(<Input defaultValue='some value' />);
it('should show clear button only if input has value', () => {
const cb = jest.fn();
const label = 'очистить';
// toBeVisible не работает
const visibleClass = 'clearButtonVisible';
const { getByLabelText, rerender } = render(<Input onClear={cb} clear={true} />);

expect(container.firstElementChild).toHaveClass('filled');
});
expect(getByLabelText(label)).not.toHaveClass(visibleClass);

it('should not set `filled` class if the value is empty', () => {
const { container } = render(<Input />);
rerender(<Input onClear={cb} clear={true} value='123' />);

expect(container.firstElementChild).not.toHaveClass('filled');
});
expect(getByLabelText(label)).toHaveClass(visibleClass);
});

it('should unset `filled` class if value becomes empty', async () => {
const dataTestId = 'test-id';
const { getByTestId } = render(
<Input defaultValue='some value' dataTestId={dataTestId} />,
);
it('should not actually clear input when clear button clicked', () => {
const dataTestId = 'test-id';
const value = '123';
const { getByTestId, getByLabelText } = render(
<Input clear={true} value={value} dataTestId={dataTestId} />,
);

const input = getByTestId(dataTestId) as HTMLInputElement;
userEvent.click(getByLabelText('очистить'));

input.setSelectionRange(0, input.value.length);
await userEvent.type(input, '{backspace}');
expect(getByTestId(dataTestId)).toHaveValue(value);
});
});

input.blur();
describe('when component is uncontrolled', () => {
it('should set `filled` class when defaultValue passed', () => {
const { container } = render(<Input defaultValue='some value' />);

expect(input.value).toBe('');
expect(input).not.toHaveClass('filled');
});
expect(container.firstElementChild).toHaveClass('filled');
});

it('should set `hasLabel` class', () => {
it('should not set `filled` class if the value is empty', () => {
const { container } = render(<Input />);

expect(container.firstElementChild).not.toHaveClass('filled');
});

it('should unset `filled` class if value becomes empty', async () => {
const dataTestId = 'test-id';
const { getByTestId } = render(<Input label='label' dataTestId={dataTestId} />);
const { getByTestId } = render(
<Input defaultValue='some value' dataTestId={dataTestId} />,
);

expect(getByTestId(dataTestId)).toHaveClass('hasLabel');
const input = getByTestId(dataTestId) as HTMLInputElement;

input.setSelectionRange(0, input.value.length);
await userEvent.type(input, '{backspace}');

input.blur();

expect(input.value).toBe('');
expect(input).not.toHaveClass('filled');
});

it('should show clear button only if input has value', async () => {
const cb = jest.fn();
const dataTestId = 'test-id';
const label = 'очистить';
// toBeVisible не работает
const visibleClass = 'clearButtonVisible';

const { getByLabelText, getByTestId } = render(
<Input onClear={cb} clear={true} dataTestId={dataTestId} />,
);

const input = getByTestId(dataTestId) as HTMLInputElement;

expect(getByLabelText(label)).not.toHaveClass(visibleClass);

await userEvent.type(input, '123');

expect(getByLabelText(label)).toHaveClass(visibleClass);
});

it('should set `disabled` atribute', () => {
it('should clear input when clear button clicked', async () => {
const dataTestId = 'test-id';
const { getByTestId } = render(<Input disabled={true} dataTestId={dataTestId} />);
const { getByTestId, getByLabelText } = render(
<Input clear={true} dataTestId={dataTestId} />,
);

const input = getByTestId(dataTestId) as HTMLInputElement;

await userEvent.type(input, '123');

expect(getByTestId(dataTestId)).toHaveAttribute('disabled');
userEvent.click(getByLabelText('очистить'));

expect(input).toHaveValue('');
});
});

Expand Down Expand Up @@ -174,6 +235,15 @@ describe('Input', () => {

expect(cb).not.toBeCalled();
});

it('should call `onClear` prop when clear button clicked', () => {
const cb = jest.fn();
const { getByLabelText } = render(<Input onClear={cb} clear={true} value='123' />);

userEvent.click(getByLabelText('очистить'));

expect(cb).toBeCalledTimes(1);
});
});

it('should unmount without errors', () => {
Expand Down

0 comments on commit 4f9a453

Please sign in to comment.