diff --git a/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap b/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap index 17a592d4524c..5f46882d9bcc 100644 --- a/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap +++ b/packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap @@ -2495,7 +2495,6 @@ Map { "defaultProps": Object { "onDelete": [Function], "status": "uploading", - "uuid": "id1", }, "propTypes": Object { "errorBody": Object { @@ -2527,7 +2526,6 @@ Map { "type": "oneOf", }, "uuid": Object { - "isRequired": true, "type": "string", }, }, diff --git a/packages/react/src/components/FileUploader/FileUploader-test.js b/packages/react/src/components/FileUploader/FileUploader-test.js deleted file mode 100644 index 367dc88e217a..000000000000 --- a/packages/react/src/components/FileUploader/FileUploader-test.js +++ /dev/null @@ -1,356 +0,0 @@ -/** - * Copyright IBM Corp. 2016, 2018 - * - * This source code is licensed under the Apache-2.0 license found in the - * LICENSE file in the root directory of this source tree. - */ - -import React from 'react'; -import { settings } from 'carbon-components'; -import { Close16, CheckmarkFilled16 } from '@carbon/icons-react'; -import { mount, shallow } from 'enzyme'; -import FileUploader, { FileUploaderButton, Filename } from './FileUploader'; -import FileUploaderDropContainer from './FileUploaderDropContainer'; -import FileUploaderItem from './FileUploaderItem'; -import FileUploaderSkeleton from '../FileUploader/FileUploader.Skeleton'; -import Loading from '../Loading'; - -const { prefix } = settings; - -describe('Filename', () => { - describe('renders as expected', () => { - const icons = [Loading, Close16, CheckmarkFilled16]; - const statuses = ['uploading', 'edit', 'complete']; - statuses.forEach((status, i) => { - const wrapper = mount( - - ); - - it('renders upload status icon as expected', () => { - expect(wrapper).toMatchSnapshot(); - expect(wrapper.find(icons[i]).length).toBe(1); - }); - }); - }); - - describe('Check that functions passed in as props are called', () => { - let wrapper; - let mockProps; - - beforeEach(() => { - mockProps = { - onClick: jest.fn(), - onKeyDown: jest.fn(), - status: 'complete', - }; - wrapper = mount(); - }); - - it('should call onClick', () => { - wrapper.simulate('click'); - expect(mockProps.onClick).toBeCalled(); - }); - - it('should call onKeyDown', () => { - wrapper.simulate('keydown'); - expect(mockProps.onKeyDown).toBeCalled(); - }); - }); - - describe('click on edit icon (close--solid)', () => { - it('should have a click event', () => { - const mountWrapper = mount( - - ); - const onClick = jest.fn(); - mountWrapper.setProps({ onClick, status: 'edit' }); - mountWrapper.find(Close16).simulate('click'); - expect(onClick).toBeCalled(); - }); - }); -}); - -describe('FileUploaderItem', () => { - const mountWrapper = mount( - - ); - - describe('click on edit icon (close--solid)', () => { - it('should have a click event', () => { - const onDelete = jest.fn(); - mountWrapper.setProps({ onDelete, status: 'edit' }); - mountWrapper.find(Close16).simulate('click'); - expect(onDelete).toBeCalled(); - }); - }); -}); - -describe('FileUploaderButton', () => { - const button = ; - const mountWrapper = mount(button); - - describe('Renders as expected with default props', () => { - it('renders with expected className', () => { - expect(mountWrapper.find('label').hasClass(`${prefix}--btn`)).toBe(true); - }); - - it('renders with given className', () => { - expect(mountWrapper.hasClass('extra-class')).toBe(true); - }); - - it('renders with default labelText prop', () => { - expect(mountWrapper.props().labelText).toEqual('Add file'); - }); - - it('renders with default buttonKind prop', () => { - expect(mountWrapper.props().buttonKind).toEqual('primary'); - }); - - it('renders with expected button className', () => { - expect(mountWrapper.find(`.${prefix}--btn--primary`).exists()).toBe(true); - }); - - it('renders with default multiple prop', () => { - expect(mountWrapper.props().multiple).toEqual(false); - }); - - it('renders with default disableLabelChanges prop', () => { - expect(mountWrapper.props().disableLabelChanges).toEqual(false); - }); - - it('renders with default accept prop', () => { - expect(mountWrapper.props().accept).toEqual([]); - }); - - it('renders with default disabled prop', () => { - expect(mountWrapper.props().disabled).toBe(false); - }); - - it('disables file upload input', () => { - const wrapper = shallow(button); - wrapper.setProps({ disabled: true }); - expect(wrapper.find('input').prop('disabled')).toEqual(true); - }); - - it('does have default role', () => { - expect(mountWrapper.props().role).toBeTruthy(); - }); - - it('resets the input value onClick', () => { - const input = mountWrapper.find(`.${prefix}--visually-hidden`); - input.instance().value = ''; - const evt = { target: { value: input.instance().value } }; - input.simulate('click', evt); - - expect(evt.target.value).toEqual(null); - }); - }); - - describe('Unique id props', () => { - it('each FileUploaderButton should have a unique ID', () => { - const mountedButtons = mount( -
- - -
- ); - const firstButton = mountedButtons.find(FileUploaderButton).at(0); - const lastButton = mountedButtons.find(FileUploaderButton).at(1); - const isEqual = firstButton === lastButton; - expect(isEqual).toBe(false); - }); - }); - - describe('Update labelText', () => { - it('should have equal state and props', () => { - expect( - shallow().state().labelText - ).toEqual('foo'); - }); - - it('should change the label text upon change in props', () => { - mountWrapper.setProps({ labelText: 'foo' }); - mountWrapper.setState({ labelText: 'foo' }); - mountWrapper.setProps({ labelText: 'bar' }); - expect(mountWrapper.state().labelText).toEqual('bar'); - }); - - it('should avoid change the label text upon setting props, unless there the value actually changes', () => { - mountWrapper.setProps({ labelText: 'foo' }); - mountWrapper.setState({ labelText: 'bar' }); - mountWrapper.setProps({ labelText: 'foo' }); - expect(mountWrapper.state().labelText).toEqual('bar'); - }); - }); -}); - -describe('FileUploader', () => { - const fileUploader = ; - const mountWrapper = mount(fileUploader); - - describe('Renders as expected with defaults', () => { - it('should render with default className', () => { - expect(mountWrapper.children().hasClass(`${prefix}--form-item`)).toEqual( - true - ); - }); - - it('should render with given className', () => { - expect(mountWrapper.hasClass('extra-class')).toEqual(true); - }); - - it('renders with FileUploaderButton with disableLabelChanges set to true', () => { - expect( - mountWrapper.find('FileUploaderButton').props().disableLabelChanges - ).toEqual(true); - }); - it('renders input with hidden prop', () => { - expect(mountWrapper.find('input').props().className).toEqual( - `${prefix}--visually-hidden` - ); - }); - it(`renders with empty div.${prefix}--file-container by default`, () => { - expect(mountWrapper.find(`div.${prefix}--file-container`).text()).toEqual( - '' - ); - }); - it('clears all uploaded files when the clearFiles method is called', () => { - const mountUploadedWrapper = mount(fileUploader); - mountUploadedWrapper.setState({ - filenames: ['examplefile.jpg'], - filenameStatus: 'complete', - }); - - // Test to make sure that the Filename is rendered - expect(mountUploadedWrapper.find(Filename)).toHaveLength(1); - - // Test to make sure it was properly removed - mountUploadedWrapper.instance().clearFiles(); - expect(mountUploadedWrapper.update().find(Filename)).toHaveLength(0); - }); - }); - - describe('Update filenameStatus', () => { - it('should have equal state and props', () => { - expect( - shallow().state() - .filenameStatus - ).toEqual('uploading'); - }); - - it('should change the label text upon change in props', () => { - mountWrapper.setProps({ filenameStatus: 'uploading' }); - mountWrapper.setState({ filenameStatus: 'uploading' }); - mountWrapper.setProps({ filenameStatus: 'edit' }); - expect(mountWrapper.state().filenameStatus).toEqual('edit'); - }); - - it('should avoid change the label text upon setting props, unless there the value actually changes', () => { - mountWrapper.setProps({ filenameStatus: 'uploading' }); - mountWrapper.setState({ filenameStatus: 'edit' }); - mountWrapper.setProps({ filenameStatus: 'uploading' }); - expect(mountWrapper.state().filenameStatus).toEqual('edit'); - }); - }); -}); - -describe('FileUploaderDropContainer', () => { - let onAddFiles; - let dropContainer; - let mountWrapper; - - beforeEach(() => { - onAddFiles = jest.fn(); - dropContainer = ( - - ); - mountWrapper = mount(dropContainer); - }); - - describe('Renders as expected with default props', () => { - it('renders with given className', () => { - expect(mountWrapper.hasClass('extra-class')).toBe(true); - }); - - it('renders with default labelText prop', () => { - expect(mountWrapper.props().labelText).toEqual('Add file'); - }); - - it('renders with default multiple prop', () => { - expect(mountWrapper.props().multiple).toEqual(false); - }); - - it('renders with default accept prop', () => { - expect(mountWrapper.props().accept).toEqual([]); - }); - - it('disables file upload input', () => { - const wrapper = shallow(dropContainer); - wrapper.setProps({ disabled: true }); - expect(wrapper.find('input').prop('disabled')).toEqual(true); - }); - - it('does not have default role', () => { - expect(mountWrapper.props().role).not.toBeTruthy(); - }); - - it('resets the input value onClick', () => { - const input = mountWrapper.find(`.${prefix}--file-input`); - input.instance().value = ''; - const evt = { target: { value: input.instance().value } }; - input.simulate('click', evt); - - expect(evt.target.value).toEqual(null); - }); - - it('should call `onAddFiles` when a file is selected', () => { - const fileFoo = new File(['foo'], 'foo.txt', { type: 'text/plain' }); - const fileBar = new File(['bar'], 'bar.txt', { type: 'text/plain' }); - const mockFiles = [fileFoo, fileBar]; - const input = mountWrapper.find(`.${prefix}--file-input`); - const evt = { target: { files: mockFiles } }; - input.simulate('change', evt); - expect(onAddFiles).toHaveBeenCalledTimes(1); - expect(onAddFiles).toHaveBeenCalledWith( - expect.objectContaining({ - target: { - files: [fileFoo, fileBar], - }, - }), - { addedFiles: [fileFoo, fileBar] } - ); - }); - }); - - describe('Unique id props', () => { - it('each FileUploaderDropContainer should have a unique ID', () => { - const mountedDropContainers = mount( -
- - -
- ); - const firstDropContainer = mountedDropContainers - .find(FileUploaderDropContainer) - .at(0); - const lastDropContainer = mountedDropContainers - .find(FileUploaderDropContainer) - .at(1); - const isEqual = firstDropContainer === lastDropContainer; - expect(isEqual).toBe(false); - }); - }); -}); - -describe('FileUploaderSkeleton', () => { - describe('Renders as expected', () => { - const wrapper = shallow(); - - it('Has the expected classes', () => { - expect(wrapper.hasClass(`${prefix}--form-item`)).toEqual(true); - }); - }); -}); diff --git a/packages/react/src/components/FileUploader/FileUploaderItem.js b/packages/react/src/components/FileUploader/FileUploaderItem.js index c74aba9b3aae..835f8771258d 100644 --- a/packages/react/src/components/FileUploader/FileUploaderItem.js +++ b/packages/react/src/components/FileUploader/FileUploaderItem.js @@ -5,17 +5,17 @@ * LICENSE file in the root directory of this source tree. */ -import React from 'react'; -import PropTypes from 'prop-types'; import { settings } from 'carbon-components'; -import classNames from 'classnames'; -import { Filename } from './FileUploader'; +import cx from 'classnames'; +import PropTypes from 'prop-types'; +import React, { useRef } from 'react'; +import { Filename } from './'; import { keys, matches } from '../../internal/keyboard'; import uid from '../../tools/uniqueId'; const { prefix } = settings; -export default function FileUploaderItem({ +function FileUploaderItem({ uuid, name, status, @@ -27,7 +27,8 @@ export default function FileUploaderItem({ size, ...other }) { - const classes = classNames(`${prefix}--file__selected-file`, { + const { current: id } = useRef(uuid || uid()); + const classes = cx(`${prefix}--file__selected-file`, { [`${prefix}--file__selected-file--invalid`]: invalid, [`${prefix}--file__selected-file--field`]: size === 'field', [`${prefix}--file__selected-file--sm`]: size === 'small', @@ -43,13 +44,13 @@ export default function FileUploaderItem({ onKeyDown={evt => { if (matches(evt, [keys.Enter, keys.Space])) { if (status === 'edit') { - onDelete(evt, { uuid }); + onDelete(evt, { uuid: id }); } } }} onClick={evt => { if (status === 'edit') { - onDelete(evt, { uuid }); + onDelete(evt, { uuid: id }); } }} /> @@ -74,7 +75,7 @@ FileUploaderItem.propTypes = { /** * Unique identifier for the file object */ - uuid: PropTypes.string.isRequired, + uuid: PropTypes.string, /** * Name of the uploaded file @@ -114,7 +115,8 @@ FileUploaderItem.propTypes = { }; FileUploaderItem.defaultProps = { - uuid: uid(), status: 'uploading', onDelete: () => {}, }; + +export default FileUploaderItem; diff --git a/packages/react/src/components/FileUploader/__snapshots__/FileUploader-test.js.snap b/packages/react/src/components/FileUploader/__snapshots__/FileUploader-test.js.snap deleted file mode 100644 index 1eb5d68d8aaf..000000000000 --- a/packages/react/src/components/FileUploader/__snapshots__/FileUploader-test.js.snap +++ /dev/null @@ -1,155 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Filename renders as expected renders upload status icon as expected 1`] = ` - - -
- - - - Upload complete - - - - -
-
-
-`; - -exports[`Filename renders as expected renders upload status icon as expected 2`] = ` - - - - - - - Upload complete - - - - - -`; - -exports[`Filename renders as expected renders upload status icon as expected 3`] = ` - - - - - - - - Upload complete - - - - - -`; diff --git a/packages/react/src/components/FileUploader/__tests__/FileUploader-test.js b/packages/react/src/components/FileUploader/__tests__/FileUploader-test.js new file mode 100644 index 000000000000..131f71513391 --- /dev/null +++ b/packages/react/src/components/FileUploader/__tests__/FileUploader-test.js @@ -0,0 +1,82 @@ +/** + * Copyright IBM Corp. 2016, 2018 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { getByText } from '@carbon/test-utils/dom'; +import { render, cleanup } from '@carbon/test-utils/react'; +import React from 'react'; +import FileUploader from '../'; +import { uploadFiles } from '../test-helpers'; + +describe('FileUploader', () => { + afterEach(cleanup); + + describe('automated accessibility tests', () => { + it.skip('should have no axe violations', async () => { + const { container } = render(); + await expect(container).toHaveNoAxeViolations(); + }); + + it.skip('should have no DAP violations', async () => { + const { container } = render(); + await expect(container).toHaveNoDAPViolations('FileUploader'); + }); + }); + + it('should support a custom class name on the root element', () => { + const { container } = render(); + expect(container.firstChild.classList.contains('test')).toBe(true); + }); + + it('should not update the label by default when selecting files', () => { + const { container } = render(); + const input = container.querySelector('input'); + const label = getByText(container, 'upload'); + + expect(label).toBeInstanceOf(HTMLElement); + uploadFiles(input, [new File(['test'], 'test.png', { type: 'image/png' })]); + expect(getByText(container, 'upload')).toBeInstanceOf(HTMLElement); + }); + + it('should clear all uploaded files when `clearFiles` is called on a ref', () => { + const ref = React.createRef(); + const { container } = render(); + const input = container.querySelector('input'); + + const filename = 'test.png'; + uploadFiles(input, [new File(['test'], filename, { type: 'image/png' })]); + + expect(getByText(container, filename)).toBeInstanceOf(HTMLElement); + ref.current.clearFiles(); + expect(getByText(container, filename)).not.toBeInstanceOf(HTMLElement); + }); + + it('should synchronize the filename status state when its prop changes', () => { + const container = document.createElement('div'); + const description = 'test'; + render( + , + { + container, + } + ); + + const input = container.querySelector('input'); + uploadFiles(input, [new File(['test'], 'test.png', { type: 'image/png' })]); + + const edit = getByText(container, description); + + render( + , + { + container, + } + ); + + const complete = getByText(container, description); + expect(edit.parentNode).not.toEqual(complete.parentNode); + }); +}); diff --git a/packages/react/src/components/FileUploader/__tests__/FileUploaderButton-test.js b/packages/react/src/components/FileUploader/__tests__/FileUploaderButton-test.js new file mode 100644 index 000000000000..5f0519340cc9 --- /dev/null +++ b/packages/react/src/components/FileUploader/__tests__/FileUploaderButton-test.js @@ -0,0 +1,147 @@ +/** + * Copyright IBM Corp. 2016, 2018 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { render, cleanup } from '@carbon/test-utils/react'; +import { getByText } from '@carbon/test-utils/dom'; +import React from 'react'; +import { Simulate } from 'react-dom/test-utils'; +import { FileUploaderButton } from '../'; +import { uploadFiles } from '../test-helpers'; + +describe('FileUploaderButton', () => { + afterEach(cleanup); + + describe('automated accessibility tests', () => { + it('should have no axe violations', async () => { + const { container } = render(); + await expect(container).toHaveNoAxeViolations(); + }); + }); + + it('should support a custom class name on the root element', () => { + const { container } = render(); + expect(container.firstChild.classList.contains('test')).toBe(true); + }); + + it('should call `onClick` if the label is clicked', () => { + const onClick = jest.fn(); + const { container } = render( + + ); + const label = getByText(container, 'test'); + Simulate.click(label); + expect(onClick).toHaveBeenCalledTimes(1); + }); + + it('should call `onChange` if the value of the input changes', () => { + const onChange = jest.fn(); + const { container } = render( + + ); + const input = container.querySelector('input'); + const file = new File(['test'], 'test.png', { type: 'image/png' }); + uploadFiles(input, file); + expect(onChange).toHaveBeenCalledTimes(1); + }); + + it('should not support multiple files by default', () => { + const { container } = render(); + const input = container.querySelector('input'); + expect(input.getAttribute('multiple')).toBeFalsy(); + }); + + it('should have a unique id', () => { + const { container } = render( + <> + + + + ); + const inputs = container.querySelectorAll('input'); + expect(inputs[0].getAttribute('id')).not.toBe(inputs[1].getAttribute('id')); + }); + + it('should reset the input value when the label is clicked', () => { + const { container } = render( + + ); + const input = container.querySelector('input'); + + const filename = 'test.png'; + const file = new File(['test'], filename, { type: 'image/png' }); + uploadFiles(input, [file]); + + expect(input.files.length).toBe(1); + Simulate.click(input); + expect(input.files.length).toBe(0); + }); + + it('should update the label text if it receives a new value from props', () => { + const container = document.createElement('div'); + render(, { container }); + expect(getByText(container, 'test')).toBeInstanceOf(HTMLElement); + + render(, { container }); + expect(getByText(container, 'tester')).toBeInstanceOf(HTMLElement); + }); + + describe('FileUploaderButton label', () => { + it('should update the label when a file is selected', () => { + const { container } = render( + + ); + const input = container.querySelector('input'); + const label = getByText(container, 'test'); + expect(label).toBeInstanceOf(HTMLElement); + + const filename = 'test.png'; + const file = new File(['test'], filename, { type: 'image/png' }); + uploadFiles(input, [file]); + + expect(getByText(container, filename)).toBeInstanceOf(HTMLElement); + }); + + it('should update the label when multiple files are selected', () => { + const { container } = render( + + ); + const input = container.querySelector('input'); + const label = getByText(container, 'test'); + expect(label).toBeInstanceOf(HTMLElement); + + const files = [ + new File(['test-1'], 'test-1.png', { type: 'image/png' }), + new File(['test-2'], 'test-1.png', { type: 'image/png' }), + new File(['test-3'], 'test-1.png', { type: 'image/png' }), + ]; + + uploadFiles(input, files); + expect(getByText(container, `${files.length} files`)).toBeInstanceOf( + HTMLElement + ); + }); + + it('should not update the label when files are selected but `disableLabelChanges` is false', () => { + const { container } = render( + + ); + const input = container.querySelector('input'); + const label = getByText(container, 'test'); + expect(label).toBeInstanceOf(HTMLElement); + + const filename = 'test.png'; + const file = new File(['test'], filename, { type: 'image/png' }); + uploadFiles(input, [file]); + + expect(getByText(container, 'test')).toBeInstanceOf(HTMLElement); + }); + }); +}); diff --git a/packages/react/src/components/FileUploader/__tests__/FileUploaderDropContainer-test.js b/packages/react/src/components/FileUploader/__tests__/FileUploaderDropContainer-test.js new file mode 100644 index 000000000000..caccd59ed9b7 --- /dev/null +++ b/packages/react/src/components/FileUploader/__tests__/FileUploaderDropContainer-test.js @@ -0,0 +1,85 @@ +/** + * Copyright IBM Corp. 2016, 2018 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { getByText } from '@carbon/test-utils/dom'; +import { render, cleanup } from '@carbon/test-utils/react'; +import React from 'react'; +import { Simulate } from 'react-dom/test-utils'; +import { FileUploaderDropContainer } from '../'; +import { uploadFiles } from '../test-helpers'; + +describe('FileUploaderDropContainer', () => { + afterEach(cleanup); + + it('should support a custom class name on the drop area', () => { + const { container } = render( + + ); + const dropArea = container.querySelector('[role="button"]'); + expect(dropArea.classList.contains('test')).toBe(true); + }); + + it('should have a unique id each time it is used', () => { + const { container } = render( + <> + + + + ); + const inputs = container.querySelectorAll('input'); + expect(inputs[0].getAttribute('id')).not.toBe(inputs[1].getAttribute('id')); + }); + + it('should render with the default labelText prop', () => { + const { container } = render(); + const label = getByText(container, 'Add file'); + expect(label).toBeInstanceOf(HTMLElement); + }); + + it('should render with multiple set to false by default', () => { + const { container } = render(); + const input = container.querySelector('input'); + expect(input.getAttribute('multiple')).toBeFalsy(); + }); + + it('should reset the value of the input when the drop area is clicked', () => { + const { container } = render( + + ); + const input = container.querySelector('input'); + + uploadFiles(input, [ + new File(['content'], 'test.png', { type: 'image/png' }), + ]); + expect(input.files.length).toBe(1); + Simulate.click(input); + expect(input.files.length).toBe(0); + }); + + it('should call `onAddFiles` when a file is selected', () => { + const onAddFiles = jest.fn(); + const { container } = render( + + ); + const input = container.querySelector('input'); + const files = [ + new File(['foo'], 'foo.txt', { type: 'text/plain' }), + new File(['bar'], 'bar.txt', { type: 'text/plain' }), + ]; + + uploadFiles(input, files); + expect(onAddFiles).toHaveBeenCalledTimes(1); + expect(onAddFiles).toHaveBeenCalledWith( + expect.objectContaining({ + target: { + files, + }, + }), + { addedFiles: files } + ); + }); +}); diff --git a/packages/react/src/components/FileUploader/__tests__/FileUploaderItem-test.js b/packages/react/src/components/FileUploader/__tests__/FileUploaderItem-test.js new file mode 100644 index 000000000000..ba8f2a116c22 --- /dev/null +++ b/packages/react/src/components/FileUploader/__tests__/FileUploaderItem-test.js @@ -0,0 +1,77 @@ +/** + * Copyright IBM Corp. 2016, 2018 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { render, cleanup } from '@carbon/test-utils/react'; +import { getByText } from '@carbon/test-utils/dom'; +import React from 'react'; +import { Simulate } from 'react-dom/test-utils'; +import { FileUploaderItem } from '../'; +import { keys } from '../../../internal/keyboard'; + +const statuses = ['uploading', 'edit', 'complete']; + +describe('FileUploaderItem', () => { + afterEach(cleanup); + + describe('automated accessibility tests', () => { + it.each(statuses)( + 'should have no axe violations with status %s', + async status => { + const { container } = render( + + ); + await expect(container).toHaveNoAxeViolations(); + } + ); + }); + + it('should support calling `onDelete` if the user interacts with the filename during editing', () => { + const onDelete = jest.fn(); + const description = 'test-description'; + const edit = render( + + ); + + let removeFile = getByText(edit.container, description); + Simulate.click(removeFile); + expect(onDelete).toHaveBeenCalledTimes(1); + + Simulate.keyDown(removeFile, keys.Enter); + expect(onDelete).toHaveBeenCalledTimes(2); + + Simulate.keyDown(removeFile, keys.Space); + expect(onDelete).toHaveBeenCalledTimes(3); + + onDelete.mockReset(); + + const uploading = render( + + ); + removeFile = getByText(uploading.container, description); + + Simulate.click(removeFile); + expect(onDelete).not.toHaveBeenCalled(); + + Simulate.keyDown(removeFile, keys.Enter); + expect(onDelete).not.toHaveBeenCalled(); + + Simulate.keyDown(removeFile, keys.Space); + expect(onDelete).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/react/src/components/FileUploader/__tests__/FileUploaderSkeleton-test.js b/packages/react/src/components/FileUploader/__tests__/FileUploaderSkeleton-test.js new file mode 100644 index 000000000000..ca69eee2090e --- /dev/null +++ b/packages/react/src/components/FileUploader/__tests__/FileUploaderSkeleton-test.js @@ -0,0 +1,34 @@ +/** + * Copyright IBM Corp. 2016, 2018 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { render, cleanup } from '@carbon/test-utils/react'; +import React from 'react'; +import { FileUploaderSkeleton } from '../'; + +describe('FileUploaderSkeleton', () => { + afterEach(cleanup); + + describe('automated accessibility testing', () => { + it('should have no axe violations', async () => { + const { container } = render(); + await expect(container).toHaveNoAxeViolations(); + }); + + it('should have no DAP violations', async () => { + const { container } = render(); + await expect(container).toHaveNoDAPViolations('FileUploaderSkeleton'); + }); + }); + + it('should accept a custom className prop on the root node', () => { + const className = 'test'; + const { container } = render( + + ); + expect(container.firstChild.classList.contains(className)).toBe(true); + }); +}); diff --git a/packages/react/src/components/FileUploader/__tests__/Filename-test.js b/packages/react/src/components/FileUploader/__tests__/Filename-test.js new file mode 100644 index 000000000000..14db70c2f5bd --- /dev/null +++ b/packages/react/src/components/FileUploader/__tests__/Filename-test.js @@ -0,0 +1,80 @@ +/** + * Copyright IBM Corp. 2016, 2018 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { getByText } from '@carbon/test-utils/dom'; +import { render, cleanup } from '@carbon/test-utils/react'; +import React from 'react'; +import { Simulate } from 'react-dom/test-utils'; +import { Filename } from '../'; + +const statuses = ['uploading', 'edit', 'complete']; + +describe('Filename', () => { + afterEach(cleanup); + + describe('automated accessibility tests', () => { + it.each(statuses)( + 'should have no axe violations with status %s', + async status => { + const { container } = render( + + ); + await expect(container).toHaveNoAxeViolations(); + } + ); + + it.each(statuses)( + 'should have no DAP violations with status %s', + async status => { + const { container } = render( + + ); + await expect(container).toHaveNoDAPViolations(`Filename-${status}`); + } + ); + }); + + it('should support events on interactive icons when `edit` or `complete` is the status', () => { + const onClick = jest.fn(); + const { container: edit } = render( + + ); + + Simulate.click(edit.querySelector(`[aria-label="test description"]`)); + expect(onClick).toHaveBeenCalledTimes(1); + + onClick.mockReset(); + + const { container: complete } = render( + + ); + + Simulate.click(complete.querySelector(`[aria-label="test description"]`)); + expect(onClick).toHaveBeenCalledTimes(1); + + const { container: uploading } = render( + + ); + + onClick.mockReset(); + + Simulate.click(getByText(uploading, 'test description')); + expect(onClick).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/react/src/components/FileUploader/index.js b/packages/react/src/components/FileUploader/index.js index 3099453e1fce..b912e878ccb2 100644 --- a/packages/react/src/components/FileUploader/index.js +++ b/packages/react/src/components/FileUploader/index.js @@ -5,8 +5,9 @@ * LICENSE file in the root directory of this source tree. */ -export * from './FileUploader.Skeleton'; -export * from './FileUploader'; -export FileUploaderItem from './FileUploaderItem'; -export FileUploaderDropContainer from './FileUploaderDropContainer'; -export default from './FileUploader'; +import FileUploader, { Filename, FileUploaderButton } from './FileUploader'; +export { default as FileUploaderSkeleton } from './FileUploader.Skeleton'; +export { default as FileUploaderItem } from './FileUploaderItem'; +export { default as FileUploaderDropContainer } from './FileUploaderDropContainer'; +export { Filename, FileUploaderButton }; +export default FileUploader; diff --git a/packages/react/src/components/FileUploader/test-helpers.js b/packages/react/src/components/FileUploader/test-helpers.js new file mode 100644 index 000000000000..430ea55db7fd --- /dev/null +++ b/packages/react/src/components/FileUploader/test-helpers.js @@ -0,0 +1,44 @@ +/** + * Copyright IBM Corp. 2016, 2018 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { Simulate } from 'react-dom/test-utils'; + +/** + * A helper with standardizing behavior around selecting and clearing files with + * an input with type="file". + * + * Based on comments on this discussion over in react-testing-library: + * https://github.com/testing-library/react-testing-library/issues/93#issuecomment-392126991 + * + * @param {HTMLInputElement} input + * @param {Array} [files] + */ +export function uploadFiles(input, files = []) { + // Define the 'files' property on the input with the given files + Object.defineProperty(input, 'files', { + writable: true, + value: files, + }); + + // When we update the value of the empty, if it is falsy we clear the input + // files to mirror browser behavior + Object.defineProperty(input, 'value', { + set(newValue) { + if (!newValue) { + input.files.length = 0; + } + return newValue; + }, + }); + + // Simulate the change event with the given options + Simulate.change(input, { + target: { + files, + }, + }); +}