Skip to content

Commit

Permalink
feat: FileDropzone (internal) (#2929)
Browse files Browse the repository at this point in the history
Co-authored-by: Katie George <[email protected]>
Co-authored-by: Andrei Zhaleznichenka <[email protected]>
  • Loading branch information
3 people authored Oct 28, 2024
1 parent 63b858d commit 041e7e5
Show file tree
Hide file tree
Showing 10 changed files with 227 additions and 160 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,7 @@ exports[`test-utils selectors 1`] = `
"awsui_button-trigger_18eso",
"awsui_button_m5h9f",
"awsui_chart-filter_1px7g",
"awsui_content_1tk3k",
"awsui_control_1wepg",
"awsui_description_1p2cx",
"awsui_description_1wepg",
Expand Down Expand Up @@ -362,6 +363,7 @@ exports[`test-utils selectors 1`] = `
"awsui_root_1kjc7",
"awsui_root_1qprf",
"awsui_root_1t44z",
"awsui_root_1tk3k",
"awsui_root_qwoo0",
"awsui_root_vrgzu",
"awsui_selectable-item_15o6u",
Expand Down
19 changes: 19 additions & 0 deletions src/file-upload/__tests__/file-upload.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { warnOnce } from '@cloudscape-design/component-toolkit/internal';
import '../../__a11y__/to-validate-a11y';
import FileUpload, { FileUploadProps } from '../../../lib/components/file-upload';
import createWrapper from '../../../lib/components/test-utils/dom';
import FileDropzoneWrapper from '../../../lib/components/test-utils/dom/internal/file-dropzone';

import tokenListSelectors from '../../../lib/components/internal/components/token-list/styles.selectors.js';

Expand Down Expand Up @@ -70,6 +71,16 @@ function StatefulFileUpload({ value: initialValue = [], ...rest }: Partial<FileU
return <FileUpload {...defaultProps} {...rest} value={value} onChange={event => setValue(event.detail.value)} />;
}

function createDragEvent(type: string, files = [file1, file2]) {
const event = new CustomEvent(type, { bubbles: true });
(event as any).dataTransfer = {
types: ['Files'],
files: type === 'drop' ? files : [],
items: files.map(() => ({ kind: 'file' })),
};
return event;
}

describe('FileUpload input', () => {
test('`multiple` property is assigned', () => {
expect(render({ multiple: false }).findNativeInput().getElement()).not.toHaveAttribute('multiple');
Expand Down Expand Up @@ -338,6 +349,14 @@ describe('File upload tokens', () => {
});
});

describe('File upload dropzone', () => {
test('dropzone is rendered in component', () => {
const wrapper = render({ multiple: true });
fireEvent(document, createDragEvent('dragover'));
expect(wrapper.findByClassName(FileDropzoneWrapper.rootSelector)!.getElement()).toBeInTheDocument();
});
});

describe('Focusing behavior', () => {
test.each([1, 2])(
`Focus is dispatched to the next token when the token before it is removed, tokenLimit=%s`,
Expand Down
109 changes: 0 additions & 109 deletions src/file-upload/dropzone/index.tsx

This file was deleted.

10 changes: 6 additions & 4 deletions src/file-upload/internal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { ButtonProps } from '../button/interfaces';
import { useFormFieldContext } from '../contexts/form-field';
import { ConstraintText, FormFieldError, FormFieldWarning } from '../form-field/internal';
import { getBaseProps } from '../internal/base-component';
import InternalFileDropzone, { useFilesDragging } from '../internal/components/file-dropzone';
import TokenList from '../internal/components/token-list';
import { fireNonCancelableEvent } from '../internal/events';
import checkControlled from '../internal/hooks/check-controlled';
Expand All @@ -21,7 +22,6 @@ import { useUniqueId } from '../internal/hooks/use-unique-id';
import { joinStrings } from '../internal/utils/strings';
import InternalSpaceBetween from '../space-between/internal';
import { Token } from '../token-group/token';
import { Dropzone, useDropzoneVisible } from './dropzone';
import FileInput from './file-input';
import { FileOption } from './file-option';
import { FileUploadProps } from './interfaces';
Expand Down Expand Up @@ -95,7 +95,7 @@ function InternalFileUpload(
setNextFocusIndex(removeFileIndex);
};

const isDropzoneVisible = useDropzoneVisible(multiple);
const { areFilesDragging } = useFilesDragging();

const showWarning = warningText && !errorText;

Expand Down Expand Up @@ -123,8 +123,10 @@ function InternalFileUpload(
ref={tokenListRef}
>
<InternalBox>
{isDropzoneVisible ? (
<Dropzone onChange={handleFilesChange}>{i18nStrings.dropzoneText(multiple)}</Dropzone>
{areFilesDragging ? (
<InternalFileDropzone onChange={event => handleFilesChange(event.detail.value)}>
{i18nStrings.dropzoneText(multiple)}
</InternalFileDropzone>
) : (
<FileInput
ref={ref}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,15 @@
import React from 'react';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';

import FileUpload, { FileUploadProps } from '../../../lib/components/file-upload';
import { Dropzone, useDropzoneVisible } from '../../../lib/components/file-upload/dropzone';
import Button from '../../../../../lib/components/button';
import InternalFileDropzone, {
FileDropzoneProps,
useFilesDragging,
} from '../../../../../lib/components/internal/components/file-dropzone';
import createWrapper from '../../../../../lib/components/test-utils/dom';
import FileDropzoneWrapper from '../../../../../lib/components/test-utils/dom/internal/file-dropzone';

import selectors from '../../../lib/components/file-upload/dropzone/styles.selectors.js';
import selectors from '../../../../../lib/components/internal/components/file-dropzone/styles.selectors.js';

const file1 = new File([new Blob(['Test content 1'], { type: 'text/plain' })], 'test-file-1.txt', {
type: 'text/plain',
Expand All @@ -17,6 +22,14 @@ const file2 = new File([new Blob(['Test content 2'], { type: 'text/plain' })], '
lastModified: 1590962400000,
});

const onChange = jest.fn();

function renderFileDropzone(props: Partial<FileDropzoneProps>) {
render(<InternalFileDropzone onChange={onChange}>{props.children}</InternalFileDropzone>);
const element = createWrapper().findByClassName(FileDropzoneWrapper.rootSelector)!.getElement();
return new FileDropzoneWrapper(element);
}

function createDragEvent(type: string, files = [file1, file2]) {
const event = new CustomEvent(type, { bubbles: true });
(event as any).dataTransfer = {
Expand All @@ -27,20 +40,11 @@ function createDragEvent(type: string, files = [file1, file2]) {
return event;
}

function TestDropzoneVisible({ multiple = false }) {
const isDropzoneVisible = useDropzoneVisible(multiple);
return <div>{isDropzoneVisible ? 'visible' : 'hidden'}</div>;
function TestDropzoneVisible() {
const { areFilesDragging } = useFilesDragging();
return <div>{areFilesDragging ? 'visible' : 'hidden'}</div>;
}

const i18nStrings: FileUploadProps.I18nStrings = {
uploadButtonText: multiple => (multiple ? 'Choose files' : 'Choose file'),
dropzoneText: multiple => (multiple ? 'Drag files to upload' : 'Drag file to upload'),
removeFileAriaLabel: fileIndex => `Remove file ${fileIndex + 1}`,
errorIconAriaLabel: 'Error',
limitShowFewer: 'Show fewer files',
limitShowMore: 'Show more files',
};

describe('File upload dropzone', () => {
test('Dropzone becomes visible once global dragover event is received', () => {
render(<TestDropzoneVisible />);
Expand All @@ -51,7 +55,7 @@ describe('File upload dropzone', () => {
expect(screen.getByText('visible')).toBeDefined();
});

test('Dropzone shows if multiple files dragged into non-multiple zone', () => {
test('Dropzone shows if multiple files dragged into zone', () => {
render(<TestDropzoneVisible />);
expect(screen.getByText('hidden')).toBeDefined();

Expand All @@ -60,17 +64,8 @@ describe('File upload dropzone', () => {
expect(screen.getByText('visible')).toBeDefined();
});

test('Dropzone shows if multiple files dragged into multiple zone', () => {
render(<TestDropzoneVisible multiple={true} />);
expect(screen.getByText('hidden')).toBeDefined();

fireEvent(document, createDragEvent('dragover', [file1, file2]));

expect(screen.getByText('visible')).toBeDefined();
});

test('Dropzone hides after a delay once global dragleave event is received', async () => {
render(<TestDropzoneVisible multiple={true} />);
render(<TestDropzoneVisible />);

fireEvent(document, createDragEvent('dragover'));

Expand All @@ -84,7 +79,7 @@ describe('File upload dropzone', () => {
});

test('Dropzone hides after a delay once global drop event is received', async () => {
render(<TestDropzoneVisible multiple={true} />);
render(<TestDropzoneVisible />);

fireEvent(document, createDragEvent('dragover'));

Expand All @@ -97,39 +92,42 @@ describe('File upload dropzone', () => {
});
});

test('dropzone is rendered in component', () => {
render(<FileUpload value={[]} i18nStrings={i18nStrings} multiple={true} />);
fireEvent(document, createDragEvent('dragover'));
expect(screen.getByText('Drag files to upload')).toBeDefined();
});

test('dropzone renders provided children', () => {
render(<Dropzone onChange={jest.fn()}>Drop files here</Dropzone>);
renderFileDropzone({ children: 'Drop files here' });
expect(screen.findByText('Drop files here')).toBeDefined();
});

test('dropzone correctly renders buttons as children', () => {
const buttonOnClick = jest.fn();
const wrapper = renderFileDropzone({ children: <Button onClick={buttonOnClick}>test</Button> });

const button = wrapper.findContent().findButton()!.getElement();

button.click();

expect(button).toHaveTextContent('test');
expect(buttonOnClick).toHaveBeenCalledTimes(1);
});

test('dropzone is hovered on dragover and un-hovered on dragleave', () => {
const { container } = render(<Dropzone onChange={jest.fn()}>Drop files here</Dropzone>);
const dropzone = container.querySelector(`.${selectors.dropzone}`)!;
const dropzone = renderFileDropzone({ children: 'Drop files here' }).getElement();

expect(dropzone).not.toHaveClass(selectors['dropzone-hovered']);
expect(dropzone).not.toHaveClass(selectors.hovered);

fireEvent(dropzone, createDragEvent('dragover'));

expect(dropzone).toHaveClass(selectors['dropzone-hovered']);
expect(dropzone).toHaveClass(selectors.hovered);

fireEvent(dropzone, createDragEvent('dragleave'));

expect(dropzone).not.toHaveClass(selectors['dropzone-hovered']);
expect(dropzone).not.toHaveClass(selectors.hovered);
});

test('dropzone fires onChange on drop', () => {
const onChange = jest.fn();
const { container } = render(<Dropzone onChange={onChange}>Drop files here</Dropzone>);
const dropzone = container.querySelector(`.${selectors.dropzone}`)!;
const dropzone = renderFileDropzone({ children: 'Drop files here' }).getElement();

fireEvent(dropzone, createDragEvent('drop'));

expect(onChange).toHaveBeenCalledWith([file1, file2]);
expect(onChange).toHaveBeenCalledWith(expect.objectContaining({ detail: { value: [file1, file2] } }));
});
});
Loading

0 comments on commit 041e7e5

Please sign in to comment.