mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Making FileUpload component accessible via keyboard (#47497)
This commit is contained in:
parent
3e752a0db1
commit
17eca4505c
@ -11,9 +11,6 @@ exports[`no enzyme tests`] = {
|
||||
"packages/grafana-ui/src/components/ColorPicker/NamedColorsPalette.test.tsx:3311646309": [
|
||||
[0, 31, 13, "RegExp match", "2409514259"]
|
||||
],
|
||||
"packages/grafana-ui/src/components/FileUpload/FileUpload.test.tsx:4145620610": [
|
||||
[0, 19, 13, "RegExp match", "2409514259"]
|
||||
],
|
||||
"packages/grafana-ui/src/components/FormField/FormField.test.tsx:3429087660": [
|
||||
[0, 19, 13, "RegExp match", "2409514259"]
|
||||
],
|
||||
|
@ -333,4 +333,8 @@ export const Components = {
|
||||
orgsTable: 'data-testid-user-orgs-table',
|
||||
sessionsTable: 'data-testid-user-sessions-table',
|
||||
},
|
||||
FileUpload: {
|
||||
inputField: 'data-testid-file-upload-input-field',
|
||||
fileNameSpan: 'data-testid-file-upload-file-name',
|
||||
},
|
||||
};
|
||||
|
@ -1,33 +1,44 @@
|
||||
import { shallow } from 'enzyme';
|
||||
import { render, waitFor, fireEvent, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
|
||||
import { FileUpload } from './FileUpload';
|
||||
|
||||
describe('FileUpload', () => {
|
||||
it('should render upload button with default text and no file name', () => {
|
||||
const wrapper = shallow(<FileUpload onFileUpload={() => {}} />);
|
||||
expect(wrapper.findWhere((comp) => comp.text() === 'Upload file').exists()).toBeTruthy();
|
||||
expect(wrapper.find({ 'aria-label': 'File name' }).exists()).toBeFalsy();
|
||||
render(<FileUpload onFileUpload={() => {}} />);
|
||||
expect(screen.getByText('Upload file')).toBeInTheDocument();
|
||||
expect(screen.queryByLabelText('File name')).toBeNull();
|
||||
});
|
||||
|
||||
it("should trim uploaded file's name", () => {
|
||||
const wrapper = shallow(<FileUpload onFileUpload={() => {}} />);
|
||||
it('should display uploaded file name', async () => {
|
||||
const testFileName = 'grafana.png';
|
||||
const file = new File(['(⌐□_□)'], testFileName, { type: 'image/png' });
|
||||
const onFileUpload = jest.fn();
|
||||
const { getByTestId } = render(<FileUpload onFileUpload={onFileUpload} />);
|
||||
let uploader = getByTestId(selectors.components.FileUpload.inputField);
|
||||
await waitFor(() =>
|
||||
fireEvent.change(uploader, {
|
||||
target: { files: [file] },
|
||||
})
|
||||
);
|
||||
let uploaderLabel = getByTestId(selectors.components.FileUpload.fileNameSpan);
|
||||
expect(uploaderLabel).toHaveTextContent(testFileName);
|
||||
});
|
||||
|
||||
wrapper.find('input').simulate('change', {
|
||||
currentTarget: {
|
||||
files: [{ name: 'longFileName.something.png' }],
|
||||
},
|
||||
});
|
||||
expect(wrapper.find({ 'aria-label': 'File name' }).exists()).toBeTruthy();
|
||||
// Trim file name longer than 16 chars
|
||||
expect(wrapper.find({ 'aria-label': 'File name' }).text()).toEqual('longFileName.som....png');
|
||||
|
||||
// Keep the name below the length limit intact
|
||||
wrapper.find('input').simulate('change', {
|
||||
currentTarget: {
|
||||
files: [{ name: 'longFileName.png' }],
|
||||
},
|
||||
});
|
||||
expect(wrapper.find({ 'aria-label': 'File name' }).text()).toEqual('longFileName.png');
|
||||
it("should trim uploaded file's name", async () => {
|
||||
const testFileName = 'longFileName.something.png';
|
||||
const file = new File(['(⌐□_□)'], testFileName, { type: 'image/png' });
|
||||
const onFileUpload = jest.fn();
|
||||
const { getByTestId } = render(<FileUpload onFileUpload={onFileUpload} />);
|
||||
let uploader = getByTestId(selectors.components.FileUpload.inputField);
|
||||
await waitFor(() =>
|
||||
fireEvent.change(uploader, {
|
||||
target: { files: [file] },
|
||||
})
|
||||
);
|
||||
let uploaderLabel = getByTestId(selectors.components.FileUpload.fileNameSpan);
|
||||
expect(uploaderLabel).toHaveTextContent('longFileName.som....png');
|
||||
});
|
||||
});
|
||||
|
@ -2,8 +2,10 @@ import { css, cx } from '@emotion/css';
|
||||
import React, { FC, FormEvent, useCallback, useState } from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
|
||||
import { stylesFactory, useTheme2 } from '../../themes';
|
||||
import { useStyles2 } from '../../themes';
|
||||
import { getFocusStyles } from '../../themes/mixins';
|
||||
import { ComponentSize } from '../../types/size';
|
||||
import { trimFileName } from '../../utils/file';
|
||||
import { getButtonStyles } from '../Button';
|
||||
@ -27,8 +29,7 @@ export const FileUpload: FC<Props> = ({
|
||||
accept = '*',
|
||||
size = 'md',
|
||||
}) => {
|
||||
const theme = useTheme2();
|
||||
const style = getStyles(theme, size);
|
||||
const style = useStyles2(getStyles(size));
|
||||
const [fileName, setFileName] = useState('');
|
||||
|
||||
const onChange = useCallback(
|
||||
@ -44,20 +45,26 @@ export const FileUpload: FC<Props> = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<label className={cx(style.button, className)}>
|
||||
<input
|
||||
type="file"
|
||||
id="fileUpload"
|
||||
className={style.fileUpload}
|
||||
onChange={onChange}
|
||||
multiple={false}
|
||||
accept={accept}
|
||||
data-testid={selectors.components.FileUpload.inputField}
|
||||
/>
|
||||
<label className={cx(style.labelWrapper, className)}>
|
||||
<Icon name="upload" className={style.icon} />
|
||||
{children}
|
||||
<input
|
||||
type="file"
|
||||
id="fileUpload"
|
||||
className={style.fileUpload}
|
||||
onChange={onChange}
|
||||
multiple={false}
|
||||
accept={accept}
|
||||
/>
|
||||
</label>
|
||||
|
||||
{fileName && (
|
||||
<span aria-label="File name" className={style.fileName}>
|
||||
<span
|
||||
aria-label="File name"
|
||||
className={style.fileName}
|
||||
data-testid={selectors.components.FileUpload.fileNameSpan}
|
||||
>
|
||||
{trimFileName(fileName)}
|
||||
</span>
|
||||
)}
|
||||
@ -65,16 +72,25 @@ export const FileUpload: FC<Props> = ({
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = stylesFactory((theme: GrafanaTheme2, size: ComponentSize) => {
|
||||
const getStyles = (size: ComponentSize) => (theme: GrafanaTheme2) => {
|
||||
const buttonStyles = getButtonStyles({ theme, variant: 'primary', size, iconOnly: false });
|
||||
const focusStyle = getFocusStyles(theme);
|
||||
|
||||
return {
|
||||
fileUpload: css`
|
||||
display: none;
|
||||
`,
|
||||
button: buttonStyles.button,
|
||||
fileUpload: css({
|
||||
height: '0.1px',
|
||||
opacity: '0',
|
||||
overflow: 'hidden',
|
||||
position: 'absolute',
|
||||
width: '0.1px',
|
||||
zIndex: -1,
|
||||
'&:focus + label': focusStyle,
|
||||
'&:focus-visible + label': focusStyle,
|
||||
}),
|
||||
labelWrapper: buttonStyles.button,
|
||||
icon: buttonStyles.icon,
|
||||
fileName: css`
|
||||
margin-left: ${theme.spacing(0.5)};
|
||||
`,
|
||||
fileName: css({
|
||||
marginLeft: theme.spacing(0.5),
|
||||
}),
|
||||
};
|
||||
});
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user