使用 react-testing-library 测试材料 ui popper 时如何修复 `TypeError: document.createRange is not a function` 错误?

sru*_*thi 16 typescript reactjs material-ui react-testing-library

我有一个材料用户界面TextField,它在焦点上打开一个Popper. 我正在尝试使用 react-testing-library 测试这种行为。

组件

import ClickAwayListener from '@material-ui/core/ClickAwayListener';
import Grow from '@material-ui/core/Grow';
import Paper from '@material-ui/core/Paper';
import Popper from '@material-ui/core/Popper';
import TextField from '@material-ui/core/TextField';
import React from 'react';
import { ItemType } from './types';

export type TextFieldDropDownProps = {
    items: ItemType,
};

export function TextFieldDropDown(props: TextFieldDropDownProps) {
    const [searchTerm, setSearchTerm] = React.useState('');
    const [anchorEl, setAnchorEl] = React.useState(null);

    const handleSearchTermChange = (event: any) => {
        setSearchTerm(event.target.value);
    };

    const onFoucs = (event: any) => {
        setAnchorEl(event.currentTarget);
    };

    const handleClose = (event: any) => {
        setAnchorEl(null);
    };

    const popperTrans = ({ TransitionProps }: any) => {
        return (
          <Grow
            {...TransitionProps}
            style={{ transformOrigin: '0 0 0' }}
          >
            <Paper>
              <ul />
            </Paper>
          </Grow>
        );
    };

    const open = Boolean(anchorEl);
    return (
        <ClickAwayListener onClickAway={handleClose}>
            <div>
                <TextField
                    onChange={handleSearchTermChange}
                    onFocus={onFoucs}
                    value={searchTerm}
                    label='Search'
                />
                <Popper
                    open={open}
                    anchorEl={anchorEl}
                    transition={true}
                    disablePortal={true}
                    placement='bottom-start'
                    style={{zIndex: 999, minWidth: '100%'}}
                >
                    {popperTrans}
                </Popper>
            </div>
        </ClickAwayListener>
    );
}

Run Code Online (Sandbox Code Playgroud)

测试

import { fireEvent, render, wait } from '@testing-library/react';
import { getTestData } from 'test-data';
import React from 'react';
import { TextFieldDropDown } from './TextFieldDropDown';

test('that on focus on input field, the component shows a dropdown', async () => {
    // Set up test data
    const items: any = getTestData();

    // Render component
    const props = { items };
    const { getByRole, queryByRole } = render(<TextFieldDropDown {...props} />, {});
    await wait();

    expect(queryByRole('list')).toBeNull();

    // Fire event
    const placeSelectInputField = getByRole('textbox') as HTMLInputElement;
    fireEvent.focus(placeSelectInputField);

    // Verify that dropdown is shown
    expect(queryByRole('list')).toBeInTheDocument();

});

Run Code Online (Sandbox Code Playgroud)

当我运行测试时,我收到以下错误 - TypeError: document.createRange is not a function.

      The above error occurred in the <div> component:
          in div (created by ForwardRef(Portal))
          in ForwardRef(Portal) (created by ForwardRef(Popper))
          in ForwardRef(Popper) (created by TextFieldDropDown)
          in div (created by ForwardRef(ClickAwayListener))
          in ForwardRef(ClickAwayListener) (created by TextFieldDropDown)
          in TextFieldDropDown
          in Provider (created by AllTheProviders)
          in AllTheProviders

      Consider adding an error boundary to your tree to customize error handling behavior.

      The above error occurred in the <ForwardRef(Popper)> component:
          in ForwardRef(Popper) (created by TextFieldDropDown)
          in div (created by ForwardRef(ClickAwayListener))
          in ForwardRef(ClickAwayListener) (created by TextFieldDropDown)
          in TextFieldDropDown
          in Provider (created by AllTheProviders)
          in AllTheProviders

      Consider adding an error boundary to your tree to customize error handling behavior.


  ? that on focus on input field, the component shows a dropdown

    TypeError: document.createRange is not a function

      46 |     // Fire event
      47 |     const TextFieldComponent = getByRole('textbox') as HTMLInputElement;
    > 48 |     fireEvent.focus(TextFieldComponent);
         |               ^
      49 |
      50 |     // Verify that dropdown is shown
      51 |     expect(queryByRole('list')).toBeInTheDocument();
Run Code Online (Sandbox Code Playgroud)

我如何使这项工作?

sru*_*thi 21

参考这个 github 问题,我发现我们可以用下面的代码修复这个错误,放在测试设置文件中。

  (global as any).document.createRange = () => ({
    setStart: () => {},
    setEnd: () => {},
    commonAncestorContainer: {
      nodeName: 'BODY',
      ownerDocument: document,
    },
  });
Run Code Online (Sandbox Code Playgroud)

  • 你知道怎么打字吗?我在打字稿中收到此警告:Type '{ nodeName: string; 所有者文档:文档;}' 缺少类型“Node”中的以下属性:baseURI、childNodes、firstChild、isConnected 以及其他 45 个属性 (6认同)

小智 5

问题在于,document.createRange当没有 DOM API 可供调用时,PopperJS 的底层实现会调用该函数。解决方案是模拟 PopperJS。

// __mocks__/popper.js.js

import PopperJs from 'popper.js';

export default class Popper {
    constructor() {
        this.placements = PopperJs.placements;

        return {
            update: () => {},
            destroy: () => {},
            scheduleUpdate: () => {}
        };
    }
}
Run Code Online (Sandbox Code Playgroud)

jest 将/__mocks__自动选择目录中的任何模拟,因此只需添加此文件即可解决您的问题