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`] = `
-
-
-
-
-
-
-
-
-`;
-
-exports[`Filename renders as expected renders upload status icon as expected 2`] = `
-
-
-
-
-
-
-
-`;
-
-exports[`Filename renders as expected renders upload status icon as expected 3`] = `
-
-
-
-
-
-
-
-`;
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,
+ },
+ });
+}