Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to test properly React Dropzone onDrop method

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.

like image 313
Albert Olivé Avatar asked Jun 14 '17 07:06

Albert Olivé


3 Answers

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

like image 144
rookieCoder Avatar answered Nov 12 '22 20:11

rookieCoder


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"

like image 9
Kevin Hyer Avatar answered Nov 12 '22 19:11

Kevin Hyer


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.

like image 8
Michael Yaworski Avatar answered Nov 12 '22 18:11

Michael Yaworski