How to test properly React Dropzone onDrop method
Asked Answered
D

4

13

I'm testing React Dropzone and I need to check the onDrop function. This function has two parameters (acceptedFiles and rejectedFiles). I'm mocking the files like this:

let image = {
  name: 'cat.jpg',
  size: 1000,
  type: 'image/jpeg'
};

Then in my test, I do that:

it('should call handleOnDrop with more than 5 acceptedFiles', () => {
    const wrapper = mount(mockComponent());

    for (let index = 0; index < 5; index++) {
      images.push(image);
    }

    wrapper.find(Dropzone).simulate('drop', { dataTransfer: { files: images } });

    expect(setUserNotificationsSpy).toHaveBeenCalledTimes(1);
});

This is my onDrop function:

const handleOnDrop = (acceptedFiles, rejectedFiles) => {
    if (rejectedFiles && rejectedFiles.length) {
      checkMaxFile(rejectedFiles, maxSize) && setUserNotifications('error_big_image');
    }

    acceptedFiles && acceptedFiles.length <= maxFiles ? onDrop(acceptedFiles) : setUserNotifications('more_than_5');
};

The expected result would be that handleOnDrop returns acceptedFiles but returns rejectedFiles and I don't know why.

Mime type it's ok and also size.

That's the function from react-dropzone:

  fileAccepted(file) {
      // Firefox versions prior to 53 return a bogus MIME type for every file drag, so dragovers with
      // that MIME type will always be accepted
      return file.type === 'application/x-moz-file' || accepts(file, this.props.accept);
  }

Thanks.

Devaluate answered 14/6, 2017 at 7:47 Comment(0)
A
9

When passing

let image = {
  name: 'cat.jpg',
  size: 1000,
  type: 'image/jpeg'
};

Into

wrapper.find(Dropzone).simulate('drop', { dataTransfer: { files: images } });

It will think image is undefined or null. The way I was able to fix this is

//Create a non-null file
const fileContents = "file contents";
const file = new Blob([fileContents], { type: "text/plain" });

 wrapper.find(Dropzone).simulate("drop", { dataTransfer: { files: [file] } });

This of course is how you would do it for a plain text file. For different types of images you will want to specify the image type instead of doing "text/plain"

Ambo answered 30/1, 2018 at 14:21 Comment(0)
A
8

I ran into this issue while I was using the useDropzone hook. Using

wrapper.find(...).simulate('drop', ...);

did not work for me.

Instead, I simulated change on the input field. This fit my use case for unit testing the component. I don't care about testing the specific drop functionality of the component since that is out of the scope for unit testing my component. Assuming that react-dropzone functions as it should, I just need to test that my component handles the file drop event properly, which I can still test by interacting with the input field instead. And it has the nice side effect of being more generic, in case I swap out dropzone libraries in the future.

wrapper.find('input').simulate('change', {
  target: { files },
  preventDefault: () => {},
  persist: () => {},
});

And I define my files like this:

const createFile = (name, size, type) => ({
  name,
  path: name,
  size,
  type,
});

const files = [
  createFile('foo.png', 200, 'image/png'),
  createFile('bar.jpg', 200, 'image/jpeg'),
];

Again, it fit my use case to just create mocked up file objects like this, instead of using native File. You can add more properties (e.g. lastModifiedDate) if you need to, but I didn't.

If, for some reason, you feel you need to create proper File instances, you can do that as well:

const createFile = (name, size, type) => {
  // the first arg, [], is the file content
  // it's irrelevant, so I left it blank
  // you can fill it like ['foobar'] or [name] if you want to
  const file = new File([], name, { type });
  Reflect.defineProperty(file, 'size', {
    get() {
      return size;
    }
  });
  return file;
};

I had some issues going down this route in my testing due to the path property not being set. Checking the equality of native File objects is quite the hassle as well. The serialized file objects would end up being {}, which is obviously not useful. I'm sure you can make it work, but IMO, avoid the native File object if you can. There wasn't a benefit to using them in my tests.

Altercate answered 8/8, 2019 at 22:2 Comment(4)
was wondering how were u able to simulate your input onChange here? im facing the same issueVinificator
@Vinificator the first code snippet in my answer shows thatAltercate
yea i know that, but whenever i add ie. mount( <Dropzone onDrop={mockValidate}>) and use .find to location the onDrop, and try to simulate it, it doesnt seem to get called when toHaveBeenCalledTimes(1)Vinificator
It's been a while since I've had access to the codebase for these tests, so I don't have anything in front of me that I can use to try and help you further. You may want to ask a new question on here and provide code snippets. Feel free to link me the question if you do create one. Larger code snippets might mean I can see an issue.Altercate
T
3

I am using the react testing library and this is what worked for me properly , i had attached the test id to the div that took the getRootProps from the useDropZone hook,it does not necessarily have to be a div it can be any container element

function dispatchEvt(node: any, type: any, data: any) {
  const event = new Event(type, { bubbles: true });
  Object.assign(event, data);
  fireEvent(node, event);
}
async function flushPromises(rerender: any, ui: any) {
  await act(() => wait(() => rerender(ui)));
}
  const onDropMock = jest.fn();

 it("The on Drop is called on dragging a file ", async () => {
    const file = new File([JSON.stringify({ ping: true })], "fileName.csv", { type: "text/csv" });
    const data = mockData([file]);
    const ui = <MyComponent onDrop={onDropMock} />;
    const { container, rerender } = render(ui);
    const input = screen.getByTestId("testId-for-div");

    dispatchEvt(input, "drop", data);
    await flushPromises(rerender, ui);

    expect(onDropMock).toHaveBeenCalled();
  });

I found all this info in the official docs here

Thoughtless answered 14/5, 2021 at 1:11 Comment(0)
I
2

If anyone is experiencing any further issues after creating stubbed files, I found also supplying a types field in the dataTransfer option to bypass some checks that react-dropzone uses . I'm using RTL so ymmv.

const fakeVideo = new File(['muhaahahah'], 'hello.mp4', { type: 'video/mp4' });

fireEvent.drop(getByTestId('test-input'), {
   dataTransfer: { files: [fakeVideo], types: ['Files'] }
});
Intendment answered 16/3, 2021 at 22:26 Comment(1)
This is not trigeering the onDrop functionThoughtless

© 2022 - 2024 — McMap. All rights reserved.