typescript 如何利用Jest和React测试库对React滴区进行测试?

oyxsuwqo  于 2023-02-25  发布在  TypeScript
关注(0)|答案(4)|浏览(150)

我想从React组件中的react-dropzone库测试onDrop方法。我正在使用Jest,React测试库。我正在创建模拟文件,并尝试在输入中删除此文件,但在console.log文件中仍然等于空数组。您有什么想法吗?

    • 包. 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",
    • 模态导入文件. 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;
    • 模态导入文件. 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);
    });
}
f87krz0w

f87krz0w1#

使用rokki's答案(https://stackoverflow.com/a/64643985/9405587),我重写了测试组件,以便更容易理解。

模态导入文件.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();
}
dwthyt8l

dwthyt8l2#

fireEvent(node, event);改成fireEvent.drop(node, event);怎么样?

vuv7lop3

vuv7lop33#

参考文献:www.example.comhttps://jestjs.io/docs/jest-object#jestrequireactualmodulename
requireActual
返回实际的模块而不是模拟模块,跳过对模块是否应该接收模拟实现的所有检查。

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);
});
ttcibm8c

ttcibm8c4#

虽然接受的答案确实触发了事件onDrop,但这还不足以让我使用useDropzone()进行测试,因为钩子的状态(如acceptedFiles)没有更新。
我发现this代码片段使用userEvent.upload(<input>, <files>)将文件上传到嵌套的<input>。我将在这里粘贴相关代码,以防链接丢失。

应用程序测试tsx

test("upload multiple files", () => {
  const files = [
    new File(["hello"], "hello.geojson", { type: "application/json" }),
    new File(["there"], "hello2.geojson", { type: "application/json" })
  ];

  const { getByTestId } = render(<App />);
  const input = getByTestId("dropzone") as HTMLInputElement;
  userEvent.upload(input, files);

  expect(input.files).toHaveLength(2);
  expect(input.files[0]).toStrictEqual(files[0]);
  expect(input.files[1]).toStrictEqual(files[1]);
});

应用程序tsx

export default function App() {
  
  const {
    acceptedFiles,
    isDragActive,
    isDragAccept,
    isDragReject,
    getRootProps,
    getInputProps
  } = useDropzone({ accept: ".geojson, .geotiff, .tiff" });

  useEffect(() => console.log(acceptedFiles), [acceptedFiles]);

  return (
    <section>
      <div {...getRootProps()}>
        <input data-testid="dropzone" {...getInputProps()} />
        <p>Drag 'n' drop some files here, or click to select files</p>
      </div>
    </section>
  );
}

注意,设置为data-testid="dropzone"的元素是<input>,而不是<div>,这是userEvent.upload能够充分执行上载所必需的。

相关问题