简体   繁体   中英

How to test react-dropzone with Jest and react-testing-library?

I want to test onDrop method from react-dropzone library in React component. I am using Jest, React Testing Library. I'm creating mock file and I'm trying to drop this files in input, but in console.log files are still equal to an empty array. Do you have any ideas?

package.json

"typescript": "^3.9.7",
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.0.4",
"@types/jest": "^26.0.13",
"jest": "^26.4.2",
"ts-jest": "^26.3.0",
"react-router-dom": "^5.1.2",
"react-dropzone": "^10.1.10",
"@types/react-dropzone": "4.2.0",

ModalImportFile.tsx

import React, { FC, useState } from "react";
import { Box, Button, Dialog, DialogContent, DialogTitle, Grid } from "@material-ui/core";
import { useDropzone } from "react-dropzone";
import AttachFileIcon from "@material-ui/icons/AttachFile";
import DeleteIcon from "@material-ui/icons/Delete";

interface Props {
    isOpen: boolean;
}

interface Events {
    onClose: () => void;
}

const ModalImportFile: FC<Props & Events> = props => {
    const { isOpen } = props as Props;
    const { onClose } = props as Events;

    const [files, setFiles] = useState<Array<File>>([]);

    const { getRootProps, getInputProps, open } = useDropzone({
        onDrop: (acceptedFiles: []) => {
            setFiles(
                acceptedFiles.map((file: File) =>
                    Object.assign(file, {
                        preview: URL.createObjectURL(file),
                    }),
                ),
            );
        },
        noClick: true,
        noKeyboard: true,
    });

    const getDragZoneContent = () => {
        if (files && files.length > 0)
            return (
                <Box border={1} borderRadius={5} borderColor={"#cecece"} p={2} mb={2}>
                    <Grid container alignItems="center" justify="space-between">
                        <Box color="text.primary">{files[0].name}</Box>
                        <Box ml={1} color="text.secondary">
                            <Button
                                startIcon={<DeleteIcon color="error" />}
                                onClick={() => {
                                    setFiles([]);
                                }}
                            />
                        </Box>
                    </Grid>
                </Box>
            );
        return (
            <Box border={1} borderRadius={5} borderColor={"#cecece"} p={2} mb={2} style={{ borderStyle: "dashed" }}>
                <Grid container alignItems="center">
                    <Box mr={1} color="text.secondary">
                        <AttachFileIcon />
                    </Box>
                    <Box color="text.secondary">
                        <Box onClick={open} component="span" marginLeft="5px">
                            Download
                        </Box>
                    </Box>
                </Grid>
            </Box>
        );
    };

    const closeHandler = () => {
        onClose();
        setFiles([]);
    };

    return (
        <Dialog open={isOpen} onClose={closeHandler}>
            <Box width={520}>
                <DialogTitle>Import</DialogTitle>
                <DialogContent>
                    <div data-testid="container" className="container">
                        <div data-testid="dropzone" {...getRootProps({ className: "dropzone" })}>
                            <input data-testid="drop-input" {...getInputProps()} />
                            {getDragZoneContent()}
                        </div>
                    </div>
                </DialogContent>
            </Box>
        </Dialog>
    );
};

export default ModalImportFile;

ModalImportFile.test.tsx

import React from "react";
import { render, screen, fireEvent } from "@testing-library/react";
import ModalImportFile from "../../components/task/elements/ModalImportFile";

const props = {
    isOpen: true,
    onClose: jest.fn(),
};

beforeEach(() => jest.clearAllMocks());

describe("<ModalImportFile/>", () => {
    it("should drop", async () => {
        render(<ModalImportFile {...props} />);

        const file = new File([JSON.stringify({ ping: true })], "ping.json", { type: "application/json" });
        const data = mockData([file]);

        function dispatchEvt(node: any, type: any, data: any) {
            const event = new Event(type, { bubbles: true });
            Object.assign(event, data);
            fireEvent(node, event);
        }

        function mockData(files: Array<File>) {
            return {
                dataTransfer: {
                    files,
                    items: files.map(file => ({
                        kind: "file",
                        type: file.type,
                        getAsFile: () => file,
                    })),
                    types: ["Files"],
                },
            };
        }
        const inputEl = screen.getByTestId("drop-input");
        dispatchEvt(inputEl, "dragenter", data);
    });
}

With the rokki`s answer ( https://stackoverflow.com/a/64643985/9405587 ), I rewrote the test component for easier understanding.

ModalImportFile.test.tsx

import React from "react";
import { render, screen, fireEvent } from "@testing-library/react";
import ModalImportFile from "../../components/task/elements/ModalImportFile";

const props = {
    isOpen: true,
    onClose: jest.fn(),
};

beforeEach(() => jest.clearAllMocks());

describe("<ModalImportFile/>", () => {
    it("should drop", async () => {
        render(<ModalImportFile {...props} />);
        window.URL.createObjectURL = jest.fn().mockImplementation(() => "url");
        const inputEl = screen.getByTestId("drop-input");
        const file = new File(["file"], "ping.json", {
            type: "application/json",
        });
        Object.defineProperty(inputEl, "files", {
            value: [file],
        });
        fireEvent.drop(inputEl);
        expect(await screen.findByText("ping.json")).toBeInTheDocument();
}

How about changing fireEvent(node, event); to fireEvent.drop(node, event); .

References: https://jestjs.io/docs/jest-object#jestrequireactualmodulename

requireActual

Returns the actual module instead of a mock, bypassing all checks on whether the module should receive a mock implementation or not.


let dropCallback = null;
let onDragEnterCallback = null;
let onDragLeaveCallback = null;

jest.mock('react-dropzone', () => ({
  ...jest.requireActual('react-dropzone'),
  useDropzone: options => {
    dropCallback = options.onDrop;
    onDragEnterCallback = options.onDragEnter;
    onDragLeaveCallback = options.onDragLeave;

    return {
      acceptedFiles: [{
          path: 'sample4.png'
        },
        {
          path: 'sample3.png'
        }
      ],
      fileRejections: [{
        file: {
          path: 'FileSelector.docx'
        },
        errors: [{
          code: 'file-invalid-type',
          message: 'File type must be image/*'
        }]
      }],
      getRootProps: jest.fn(),
      getInputProps: jest.fn(),
      open: jest.fn()
    };
  }
}));


it('Should get on drop Function with parameter', async() => {
  const accepted = [{
      path: 'sample4.png'
    },
    {
      path: 'sample3.png'
    },
    {
      path: 'sample2.png'
    }
  ];
  const rejected = [{
    file: {
      path: 'FileSelector.docx'
    },
    errors: [{
      code: 'file-invalid-type',
      message: 'File type must be image/*'
    }]
  }];

  const event = {
    bubbles: true,
    cancelable: false,
    currentTarget: null,
    defaultPrevented: true,
    eventPhase: 3,
    isDefaultPrevented: () => {},
    isPropagationStopped: () => {},
    isTrusted: true,
    target: {
      files: {
        '0': {
          path: 'FileSelector.docx'
        },
        '1': {
          path: 'sample4.png'
        },
        '2': {
          path: 'sample3.png'
        },
        '3': {
          path: 'sample2.png'
        }
      }
    },
    timeStamp: 1854316.299999997,
    type: 'change'
  };
  dropCallback(accepted, rejected, event);
  onDragEnterCallback();
  onDragLeaveCallback();
  expect(handleFiles).toHaveBeenCalledTimes(1);
});

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM