Skip to content

Commit

Permalink
feat(core-components-attach): add Attach component (#235)
Browse files Browse the repository at this point in the history
* feat(core-components-attach): add Attach component

#227

* chore(core-components-attach): remove unused import in story

#227

* style(core-components-attach): change progressbar color

#235

* fix(core-components-attach): fix ie click on label

#227

* chore(core-components-attach): add status

* test(core-components-attach): add util test

* feat(core-components-attach): add aria-label and focus to clear button

* feat(core-components-attach): change useFocus to KeyboardFocusable

Co-authored-by: Кононенко Артем Игоревич <AIKononenko@alfabank.ru>
Co-authored-by: Alex Yatsenko <reme3d2y@gmail.com>
  • Loading branch information
3 people committed Aug 28, 2020
1 parent f57fb14 commit 71aa41c
Show file tree
Hide file tree
Showing 13 changed files with 778 additions and 3 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"dependencies": {
"@alfalab/hooks": "^0.8.0",
"@alfalab/icons-classic": "^1.7.0",
"@alfalab/utils": "^0.3.0",
"@popperjs/core": "^2.3.3",
"alfa-ui-primitives": "^2.31.0",
"compute-scroll-into-view": "^1.0.13",
Expand Down Expand Up @@ -79,6 +80,7 @@
"@storybook/preset-create-react-app": "^2.1.1",
"@storybook/preset-typescript": "^1.2.0",
"@storybook/react": "^5.3.8",
"@testing-library/dom": "^7.22.2",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.2.1",
"@testing-library/user-event": "^12.0.2",
Expand Down
26 changes: 26 additions & 0 deletions packages/attach/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "@alfalab/core-components-attach",
"version": "1.0.0",
"description": "",
"keywords": [],
"license": "ISC",
"main": "dist/index.js",
"files": [
"dist"
],
"publishConfig": {
"access": "public"
},
"peerDependencies": {
"classnames": "^2.2.6",
"react": "^16.9.0",
"react-merge-refs": "^1.1.0"
},
"dependencies": {
"@alfalab/core-components-button": "^1.3.6",
"@alfalab/core-components-keyboard-focusable": "^1.0.0",
"@alfalab/core-components-progress-bar": "^1.0.2",
"@alfalab/icons-classic": "^1.7.0",
"@alfalab/utils": "^0.3.0"
}
}
43 changes: 43 additions & 0 deletions packages/attach/src/Component.stories.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Meta, Story, Props, Title } from '@storybook/addon-docs/blocks';
import { text, select, boolean, number } from '@storybook/addon-knobs';
import { Status } from 'storybook/blocks/status';

import { Attach } from './Component';
import { name, version } from '../package.json';


<Meta
title='Компоненты|Attach'
component={Attach}
parameters={{ 'theme-switcher': { enabled: true } }}
/>

<Title>
Attach ({name}@{version})
</Title>

<Status>
Candidate Recommendation
</Status>

```tsx
import { Attach } from '@alfalab/core-components-attach';
```

## Описание

Компонент прикрепления файлов.

<Story name='Песочница'>
<Attach
size={select('size', ['xs', 's', 'm', 'l'], 's')}
buttonContent={text('buttonContent', undefined)}
disabled={boolean('disabled', false)}
maxFilenameLength={number('maxFilenameLength', undefined)}
multiple={boolean('multiple', false)}
noFileText={text('noFileText', undefined)}
progressBarPercent={number('progressBarPercent', undefined)}
/>
</Story>

<Props of={Attach} />
226 changes: 226 additions & 0 deletions packages/attach/src/Component.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
import React from 'react';
import { render, fireEvent } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

import { Attach } from './index';

describe('Attach', () => {
describe('Snapshots tests', () => {
it('should match snapshot', () => {
const { container } = render(<Attach />);

expect(container).toMatchSnapshot();
});
});

it('should forward ref to input', () => {
const ref = jest.fn();
const dataTestId = 'test-id';
const { getByTestId } = render(<Attach ref={ref} dataTestId={dataTestId} />);

expect(ref.mock.calls).toEqual([[getByTestId(dataTestId)]]);
});

it('should set `data-test-id` attribute to input', () => {
const dataTestId = 'test-id';
const { getByTestId } = render(<Attach dataTestId={dataTestId} />);

expect(getByTestId(dataTestId).tagName).toBe('INPUT');
});

it('should set `noFileText`', () => {
const noFileText = 'empty attach';
const { container } = render(<Attach noFileText={noFileText} />);

expect(container.textContent).toContain(noFileText);
});

describe('Classes tests', () => {
it('should set `className` class', () => {
const className = 'test-class';
const { container } = render(<Attach className={className} />);

expect(container.firstElementChild).toHaveClass(className);
});

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

expect(container.firstElementChild).toHaveClass('disabled');
expect(getByTestId(dataTestId)).toHaveAttribute('disabled');
});
});

describe('Callbacks tests', () => {
it('should call `onChange` prop', async () => {
const cb = jest.fn();
const dataTestId = 'test-id';
const { getByTestId } = render(<Attach onChange={cb} dataTestId={dataTestId} />);

const input = getByTestId(dataTestId) as HTMLInputElement;

fireEvent.change(input, {
target: {
files: [
{
name: 'test.txt',
type: 'application/text',
},
],
},
});

expect(cb).toBeCalledTimes(1);
expect(input.files && input.files.length).toEqual(1);
});

it('should not call `onChange` prop if disabled', async () => {
const cb = jest.fn();
const dataTestId = 'test-id';
const { getByTestId } = render(
<Attach onChange={cb} dataTestId={dataTestId} disabled={true} />,
);

const input = getByTestId(dataTestId) as HTMLInputElement;

await userEvent.upload(input, {
name: 'test.txt',
type: 'application/text',
} as File);

expect(cb).not.toBeCalled();
expect(input.files && input.files.length).toEqual(0);
});
});

describe('Files tests', () => {
it('should render selected file name if one file selected', () => {
const dataTestId = 'test-id';
const { container, getByTestId } = render(<Attach dataTestId={dataTestId} />);

const input = getByTestId(dataTestId) as HTMLInputElement;

fireEvent.change(input, {
target: {
files: [
{
name: 'test.txt',
type: 'application/text',
},
],
},
});

expect(container.textContent).toContain('test.txt');
});

it('should render selected files count if several files selected', () => {
const dataTestId = 'test-id';
const { container, getByTestId } = render(
<Attach multiple={true} dataTestId={dataTestId} />,
);

const input = getByTestId(dataTestId) as HTMLInputElement;

fireEvent.change(input, {
target: {
files: [
{
name: 'test1.txt',
type: 'application/text',
},
{
name: 'test2.txt',
type: 'application/text',
},
{
name: 'test3.txt',
type: 'application/text',
},
],
},
});

expect(container.textContent).toContain('3 файла');
});

it('should render "no file" and clear input value after clear button was clicked', () => {
const dataTestId = 'test-id';
const { container, getByTestId } = render(<Attach dataTestId={dataTestId} />);

const input = getByTestId(dataTestId) as HTMLInputElement;

fireEvent.change(input, {
target: {
files: [
{
name: 'test.txt',
type: 'application/text',
},
],
},
});

expect(container.textContent).toContain('test.txt');

const clearButton = container.querySelector('.clear');

if (clearButton) {
fireEvent.click(clearButton);
}

expect(container.textContent).toContain('Нет файла');
expect(input.value).toBeFalsy();
});

it('should truncate the filename if the maxFilenameLength prop is passed', () => {
const dataTestId = 'test-id';
const { container, getByTestId } = render(
<Attach maxFilenameLength={30} dataTestId={dataTestId} />,
);

const input = getByTestId(dataTestId) as HTMLInputElement;

fireEvent.change(input, {
target: {
files: [
{
name: 'so_long_filename_it_definitely_has_more_than_30_symbols.txt',
type: 'application/text',
},
],
},
});

expect(container.textContent).toContain('so_long_filena…30_symbols.txt');

const clearButton = container.querySelector('.clear');

if (clearButton) {
fireEvent.click(clearButton);
}

fireEvent.change(input, {
target: {
files: [
{
name: 'it_has_just_26_symbols.txt',
type: 'application/text',
},
],
},
});

expect(container.textContent).toContain('it_has_just_26_symbols.txt');
});
});

it('should unmount without errors', () => {
const { unmount } = render(<Attach />);

expect(unmount).not.toThrowError();
});
});

0 comments on commit 71aa41c

Please sign in to comment.