使用 react-testing-library 测试材料 ui popper 时如何修复“TypeError:document.createRange 不是函数”错误?

Posted

技术标签:

【中文标题】使用 react-testing-library 测试材料 ui popper 时如何修复“TypeError:document.createRange 不是函数”错误?【英文标题】:How to fix `TypeError: document.createRange is not a function` error while testing material ui popper with react-testing-library? 【发布时间】:2020-06-05 13:25:03 【问题描述】:

我有一个 Material-ui 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>
    );


测试

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();

);

当我运行测试时,我收到以下错误 - 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();

我该如何进行这项工作?

【问题讨论】:

【参考方案1】:

参考this github issue,我发现我们可以通过将以下代码放入测试设置文件来修复错误。

  (global as any).document.createRange = () => (
    setStart: () => ,
    setEnd: () => ,
    commonAncestorContainer: 
      nodeName: 'BODY',
      ownerDocument: document,
    ,
  );

【讨论】:

你知道怎么打吗?我在打字稿中收到此警告:Type ' nodeName: string; ownerDocument:文档; ' 缺少“Node”类型的以下属性:baseURI、childNodes、firstChild、isConnected 和另外 45 个 @jean182 我和你有同样的问题。你找到解决办法了吗? @jean182 您可以使用global as any 来修复类型问题。我更新了答案。 @kylejw2 嗨,我更新了答案以修复类型问题。你可以使用global as any【参考方案2】:

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

// __mocks__/popper.js.js

import PopperJs from 'popper.js';

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

        return 
            update: () => ,
            destroy: () => ,
            scheduleUpdate: () => 
        ;
    

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

【讨论】:

这是一件好事,但是使用 Enzyme 而不是 React-Testing-Library,我不必模拟 Popper.js 来超过测试。你知道 Enzyme 与 React-Testing-Library 的区别吗? 我正在使用 jest 和酵素,这对我有用。 ***.com/a/60616862/2060913

以上是关于使用 react-testing-library 测试材料 ui popper 时如何修复“TypeError:document.createRange 不是函数”错误?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 React-Testing-Library 测试 mapDispatchToProps?

如何使用 Jest 和 react-testing-library 测试 useRef?

react-testing-library - 屏幕与渲染查询

如何使用 react-testing-library 测试由其他组件组成的组件?

使用 react-testing-library 时如何测试组件是不是使用正确的道具呈现?

使用 react-testing-library 时找不到带有文本的元素:“myText”错误