-
Notifications
You must be signed in to change notification settings - Fork 45
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(core-components-attach): add Attach component (#235)
* 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
1 parent
f57fb14
commit 71aa41c
Showing
13 changed files
with
778 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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} /> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
}); | ||
}); |
Oops, something went wrong.