Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DropZone] Allow duplicate files to be uploaded consecutively/ removed and added again #782

Merged
merged 1 commit into from
Jan 16, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions UNRELEASED.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Use [the changelog guidelines](https://git.io/polaris-changelog-guidelines) to f
### Bug fixes

- Ensure disabled `Button` components with a `url` prop output valid HTML ([#773](https://github.com/Shopify/polaris-react/pull/773))
- Fixed `DropZone` which was unable to add a duplicate file back to back or add a file again once removed [#782](https://github.com/Shopify/polaris-react/pull/782). Thank you [@jzsplk](https://github.com/jzsplk) for the contribution [#425](https://github.com/Shopify/polaris-react/issues/425) and [@vladucu](https://github.com/vladucu) for the clear example.

### Documentation

Expand Down
1 change: 1 addition & 0 deletions src/components/DropZone/DropZone.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,7 @@ export class DropZone extends React.Component<CombinedProps, State> {
if (onDropRejected && rejectedFiles.length) {
onDropRejected(rejectedFiles);
}
(event.target as HTMLInputElement).value = '';
}

@autobind
Expand Down
177 changes: 101 additions & 76 deletions src/components/DropZone/tests/DropZone.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,87 +5,73 @@ import {Label, Labelled, DisplayText, Caption} from 'components';
import {mountWithAppProvider} from 'test-utilities';
import DropZone from '../DropZone';

const files = [
{
name: 'jpeg file',
type: 'image/jpeg',
},
{
name: 'svg file',
type: 'image/svg',
},
];
const duplicateFiles = [
{
name: 'jpegs files',
type: 'image/jpeg',
},
{
name: 'svg file',
type: 'image/svg',
},
];
const acceptedFiles = [files[0]];
const rejectedFiles = [files[1]];
const origGetBoundingClientRect = Element.prototype.getBoundingClientRect;
const widths = {
small: 99,
medium: 159,
large: 299,
extraLarge: 1024,
};

describe('<DropZone />', () => {
let spy: jest.Mock;
let files: {}[];
let acceptedFiles: {}[];
let rejectedFiles: {}[];
let createEvent: any;
let setBoundingClientRect: any;
let origGetBoundingClientRect: any;
const widths = {
small: 99,
medium: 159,
large: 299,
extraLarge: 1024,
};

const fireEvent = (eventType: string, element: any) => {
spy.mockReset();
const event = createEvent(eventType);
element.getDOMNode().dispatchEvent(event);
};

const triggerDragEnter = (element: ReactWrapper<any, any>) => {
const event = createEvent('dragenter');
element.getDOMNode().dispatchEvent(event);
clock.tick(50);
element.update();
};

beforeEach(() => {
spy = jest.fn();
clock.mock();
files = [
{
name: 'jpeg file',
type: 'image/jpeg',
},
{
name: 'svg file',
type: 'image/svg',
},
];
acceptedFiles = [files[0]];
rejectedFiles = [files[1]];
createEvent = (name: string) => {
const evt = new CustomEvent(name);
Object.defineProperty(evt, 'dataTransfer', {
enumerable: true,
value: {files},
});
return evt;
};
origGetBoundingClientRect = Element.prototype.getBoundingClientRect;
setBoundingClientRect = (size: keyof typeof widths) => {
Element.prototype.getBoundingClientRect = jest.fn(() => {
return {
width: widths[size],
height: 100,
top: 0,
left: 0,
bottom: 0,
right: 0,
};
});
};
});

afterEach(() => {
clock.restore();
});

afterAll(() => {
Element.prototype.getBoundingClientRect = origGetBoundingClientRect;
});

it('calls the onDrop callback when a drop event is fired', () => {
const dropZone = mountWithAppProvider(<DropZone onDrop={spy} />);
const event = createEvent('drop');
const event = createEvent('drop', files);
dropZone.getDOMNode().dispatchEvent(event);
expect(spy).toBeCalledWith(files, files, []);
});

it('calls the onDrop callback when a drop event is fired on document twice when a duplicate file is added consecutively', () => {
const dropZone = mountWithAppProvider(<DropZone onDrop={spy} />);
const event1 = createEvent('drop', files);
dropZone.getDOMNode().dispatchEvent(event1);
expect(spy).toBeCalledWith(files, files, []);

const event2 = createEvent('drop', duplicateFiles);
dropZone.getDOMNode().dispatchEvent(event2);
expect(spy).toBeCalledWith(duplicateFiles, duplicateFiles, []);
});

it('calls the onDrop callback when a drop event is fired on document', () => {
mountWithAppProvider(<DropZone dropOnPage onDrop={spy} />);
const event = createEvent('drop');
const event = createEvent('drop', files);
document.dispatchEvent(event);
expect(spy).toBeCalledWith(files, files, []);
});
Expand All @@ -94,7 +80,7 @@ describe('<DropZone />', () => {
const dropZone = mountWithAppProvider(
<DropZone onDrop={spy} accept="image/jpeg" />,
);
const event = createEvent('drop');
const event = createEvent('drop', files);
dropZone.getDOMNode().dispatchEvent(event);
expect(spy).toBeCalledWith(files, acceptedFiles, rejectedFiles);
});
Expand All @@ -103,7 +89,7 @@ describe('<DropZone />', () => {
const dropZone = mountWithAppProvider(
<DropZone onDropAccepted={spy} accept="image/jpeg" />,
);
const event = createEvent('drop');
const event = createEvent('drop', files);
dropZone.getDOMNode().dispatchEvent(event);
expect(spy).toBeCalledWith(acceptedFiles);
});
Expand All @@ -112,28 +98,28 @@ describe('<DropZone />', () => {
const dropZone = mountWithAppProvider(
<DropZone onDropRejected={spy} accept="image/jpeg" />,
);
const event = createEvent('drop');
const event = createEvent('drop', files);
dropZone.getDOMNode().dispatchEvent(event);
expect(spy).toBeCalledWith(rejectedFiles);
});

it('calls the onDragEnter callback when a dragEnter event is fired', () => {
const dropZone = mountWithAppProvider(<DropZone onDragEnter={spy} />);
const event = createEvent('dragenter');
const event = createEvent('dragenter', files);
dropZone.getDOMNode().dispatchEvent(event);
expect(spy).toBeCalled();
});

it('calls the onDragOver callback when a dragOver event is fired', () => {
const dropZone = mountWithAppProvider(<DropZone onDragOver={spy} />);
const event = createEvent('dragover');
const event = createEvent('dragover', files);
dropZone.getDOMNode().dispatchEvent(event);
expect(spy).toBeCalled();
});

it('calls the onDragLeave callback when a dragLeave event is fired', () => {
const dropZone = mountWithAppProvider(<DropZone onDragLeave={spy} />);
const event = createEvent('dragleave');
const event = createEvent('dragleave', files);
dropZone.getDOMNode().dispatchEvent(event);
expect(spy).toBeCalled();
});
Expand All @@ -145,7 +131,7 @@ describe('<DropZone />', () => {
const dropZone = mountWithAppProvider(
<DropZone onDrop={spy} customValidator={customValidator} />,
);
const event = createEvent('drop');
const event = createEvent('drop', files);
dropZone.getDOMNode().dispatchEvent(event);
expect(spy).toBeCalledWith(files, acceptedFiles, rejectedFiles);
});
Expand All @@ -162,13 +148,13 @@ describe('<DropZone />', () => {
onDragOver={spy}
/>,
);
fireEvent('drop', dropZone);
fireEvent('drop', dropZone, spy);
expect(spy).not.toBeCalled();
fireEvent('dragenter', dropZone);
fireEvent('dragenter', dropZone, spy);
expect(spy).not.toBeCalled();
fireEvent('dragleave', dropZone);
fireEvent('dragleave', dropZone, spy);
expect(spy).not.toBeCalled();
fireEvent('dragover', dropZone);
fireEvent('dragover', dropZone, spy);
expect(spy).not.toBeCalled();
});

Expand All @@ -185,17 +171,17 @@ describe('<DropZone />', () => {
);

// Initial event to populate zone with data (should succeed)
fireEvent('drop', dropZone);
fireEvent('drop', dropZone, spy);
expect(spy).toBeCalledWith(files, acceptedFiles, rejectedFiles);

// All events should now be ignored
fireEvent('drop', dropZone);
fireEvent('drop', dropZone, spy);
expect(spy).not.toBeCalled();
fireEvent('dragenter', dropZone);
fireEvent('dragenter', dropZone, spy);
expect(spy).not.toBeCalled();
fireEvent('dragleave', dropZone);
fireEvent('dragleave', dropZone, spy);
expect(spy).not.toBeCalled();
fireEvent('dragover', dropZone);
fireEvent('dragover', dropZone, spy);
expect(spy).not.toBeCalled();
});

Expand Down Expand Up @@ -334,3 +320,42 @@ describe('<DropZone />', () => {
});
});
});

function createEvent(name: string, files: any) {
const evt = new CustomEvent(name);
Object.defineProperty(evt, 'dataTransfer', {
enumerable: true,
value: {files},
});
return evt;
}

function setBoundingClientRect(size: keyof typeof widths) {
Element.prototype.getBoundingClientRect = jest.fn(() => {
return {
width: widths[size],
height: 100,
top: 0,
left: 0,
bottom: 0,
right: 0,
};
});
}

function triggerDragEnter(element: ReactWrapper<any, any>) {
const event = createEvent('dragenter', files);
element.getDOMNode().dispatchEvent(event);
clock.tick(50);
element.update();
}

function fireEvent(
eventType: string,
element: ReactWrapper<any, any>,
spy: jest.Mock,
) {
spy.mockReset();
const event = createEvent(eventType, files);
element.getDOMNode().dispatchEvent(event);
}