无法使React转发的引用在测试环境中工作

时间:2019-12-12 18:17:30

标签: reactjs jestjs react-testing-library react-forwardref

我有一个要测试一些转发引用的问题。我可以使这些转发的引用在我的浏览器中正常工作,但是在测试环境中,我看到了不同的行为。

您可以在https://github.com/jasonfb/jest-playground-2

处浏览我完整的复制应用程序

快速概述:

// App.js

import React from 'react';
import './App.css';

import AbcContainer from './abc_container'

function App() {
  return (
    <div className="App">
      <AbcContainer />
    </div>
  );
}
export default App;

// abc_container.js

import React from 'react'
import DefThing from './def_thing'
import styled from 'styled-components'

const StyledAbcContainer = styled.div`
  display: block;
  width: 80%;
  height: 80%;
  margin-left: auto;
  margin-right: auto;
  border: solid 1px red;
  padding: 15px;
`

class AbcContainer extends React.Component {
  constructor(props) {
    super()
    this.def_thing_ref = React.createRef()
    this.state = {
      visible: false,
      message: "wait for it..."
    }
  }

  componentDidMount() {
    setTimeout(() => {
      console.log("this.def_thing_ref= ", this.def_thing_ref)
      let rect = this.def_thing_ref.current.getBoundingClientRect()
      console.log("the obejct's rect is ", rect)
      this.setState({visible: true,
          message: "the position of the box is " + rect.x + "," + rect.y +
          "I know this because the ref for this.def_thing_ref.current is populated"
      })
    }, 2500)
  }


  render() {
    const {visible, message} = this.state

    return (
      <StyledAbcContainer data-testid="abc-container">
        StyledAbcContainer
        <DefThing ref={this.def_thing_ref} visible={visible}/>

        {message}
      </StyledAbcContainer>
    )
  }

}
export default AbcContainer

// def_thing.js

import React, {Component} from 'react'
import styled from "styled-components";

const StyledDefThing = styled.div`
  position: relative;
  height: 110px; 
  width: 136px;
  border: solid 1px green;
  display: block;
`
class DefThing extends React.Component {
  render () {
    const {selfRef, visible} = this.props
    return (
      <StyledDefThing ref={selfRef} style={{'visibility': visible ? 'visible' : 'hidden'}}>
        StyledDefThing
      </StyledDefThing>
    )
  }
}
export default React.forwardRef((props, ref) => <DefThing {...props} selfRef={ref}  />)

除此之外,这是基本的create-react-app,具有一些依赖关系

// package.json

{
  "name": "jest-playground-2",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@testing-library/dom": "^6.10.1",
    "@testing-library/jest-dom": "^4.2.4",
    "@testing-library/react": "^9.3.3",
    "@testing-library/user-event": "^7.1.2",
    "react": "^16.12.0",
    "react-dom": "^16.12.0",
    "react-scripts": "3.3.0",
    "styled-components": "^4.4.1"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  "jest": {
    "coverageReporters": [
      "html",
      "text"
    ]
  }
}

您可以在https://github.com/jasonfb/jest-playground-2

处浏览我完整的复制应用程序

在开发中,在我的浏览器中,转发的ref起作用。 2.5秒(任意)后,AbcContainer中的代码通过使用ref来获取DefThing的位置,并在其上获取'.getBoundingClientRect()`,如下所示:

this.def_thing_ref.current.getBoundingClientRect()

(请参阅abc_container.js #componentDidMount)

在我的浏览器中,它可以正常工作,并且已转发引用的current已填充

enter image description here

但是,在运行规格时,我无法使其正常工作。这是我的规格

// abc_container.test.js

import React from 'react';
import { render, wait, waitForElement } from '@testing-library/react';
import App from './App';
import AbcContainer from "./abc_container";



test('it is able to render', () => {
  const {getByTestId} = render(<AbcContainer/>)
  const abcContainterElement = getByTestId('abc-container')
  expect(abcContainterElement).toBeInTheDocument()

})


test('after 1 second, the ref is created', async () => {
  const {getByText, getByTestId} = render(<AbcContainer/>)
  const abcContainerElement = getByTestId('abc-container')
  await wait(() => getByText(/the position of the box/i))
  const words = getByText(/the position of the box/i)
  expect(words).toBeInTheDocument()
})

// App.test.js

import React from 'react';
import { render } from '@testing-library/react';
import App from './App';

test('renders learn react link', () => {
  const { getByText } = render(<App />);
  const styledDefThingElement = getByText(/StyledDefThing/i);
  expect(styledDefThingElement).toBeInTheDocument();
});

// def_thing.test.js

import React from 'react';
import { render } from '@testing-library/react';
import App from './App';

import DefThing from './def_thing'

test('renders a box and expects a ref', () => {

  const fakeRef = React.createRef()
  const { getByText } = render(<DefThing ref={fakeRef}/>);

});

不幸的是,我在abc_container中遇到此失败,这证明了问题:

FAIL  src/abc_container.test.js (7.19s)
  ● Console

    console.log src/abc_container.js:32
      this.def_thing_ref=  { current: null }
    console.error node_modules/@testing-library/react/dist/act-compat.js:52
      Error: Uncaught [TypeError: Cannot read property 'getBoundingClientRect' of null]
          at reportException (/Users/jason/Work/LEARNING/React_JS/jest-playground-2/node_modules/jest-environment-jsdom/node_modules/jsdom/lib/jsdom/living/helpers/runtime-script-errors.js:66:24)
          at Timeout.callback [as _onTimeout] (/Users/jason/Work/LEARNING/React_JS/jest-playground-2/node_modules/jest-environment-jsdom/node_modules/jsdom/lib/jsdom/browser/Window.js:680:7)
          at listOnTimeout (timers.js:327:15)
          at processTimers (timers.js:271:5) TypeError: Cannot read property 'getBoundingClientRect' of null
          at setTimeout (/Users/jason/Work/LEARNING/React_JS/jest-playground-2/src/abc_container.js:33:45)
          at Timeout.callback [as _onTimeout] (/Users/jason/Work/LEARNING/React_JS/jest-playground-2/node_modules/jest-environment-jsdom/node_modules/jsdom/lib/jsdom/browser/Window.js:678:19)
          at listOnTimeout (timers.js:327:15)
          at processTimers (timers.js:271:5)

  ● after 1 second, the ref is created

如您所见,this.def_thing_ref返回的{ current: null }不应返回。在实际的浏览器中,它返回{current: div.sc-bVVdaja.hwwsLL},指示引用正确无误

似乎在使“转发的引用”在测试设置中工作时缺少一些东西

更新12/13/2019:

我在这方面取得了一些进展。我认为我的测试文件中没有包含ReactDOM。我的新测试文件看起来像

// def_thing.test.js

import React from 'react';

import ReactDOM from 'react-dom';
import { render } from '@testing-library/react';
import { getByTestId, getByText } from '@testing-library/dom'

import App from './App';

import DefThing from './def_thing'

test('renders a box and expects a ref', () => {
  const div = document.createElement('div');

  const fakeRef = React.createRef()
  const { getByText } = render(<DefThing ref={fakeRef}/>, div);

});

/// abc_container.test.js

import React from 'react';
import ReactDOM from 'react-dom';
import { render, wait, waitForElement } from '@testing-library/react';
import { getByTestId, getByText } from '@testing-library/dom'

import App from './App';
import AbcContainer from "./abc_container";



test('it is able to render',  () => {
  const div = document.createElement('div');

  const {getByTestId} = render(<AbcContainer/>, div)
  const abcContainterElement = getByTestId('abc-container')
  expect(abcContainterElement).toBeInTheDocument()
})



test('after 1 second, the ref is created', async () => {
  const div = document.createElement('div');

  const {getByText, getByTestId} = render(<AbcContainer/>, div)

  const abcContainerElement = getByTestId('abc-container')
  await wait(() => getByText(/the position of the box is 169.5,134/i))
  const words = getByText(/the position of the box is 169.5,134/i)
  expect(words).toBeInTheDocument()
})

通过修改测试文件,我可以成功地将“转发的引用”显示为

但是,当我调用getBoundingRectRect()

请提取存储库并使用yarn test --watchAll --coverage

运行规范

更新2019-12-13 @ 13-23EST:

基本上现在发生的是,我认为这已被归结为setTimeout的问题

由于某种原因,我看不到,当我从componentDidMount中立即检查对象时,我看到正确填充了引用(现在是def_ref和abc_ref):

def_thing_ref:
     { current:
        HTMLDivElement {

/// abc_container.js

  componentDidMount() {
    console.log("componentDidMount... this= ", this) // when examined this way, the def_thing_ref and abc_ref are coming out correctly populated

    if (this.def_thing_ref.current) {
      let rect = this.def_thing_ref.current.getBoundingClientRect()
      console.log("the obejct's rect is ", rect)

      this.setState({
        visible: true,
        message: "the position of the box is " + rect.x + "," + rect.y +
          "....I know this because the ref for this.def_thing_ref.current is populated"
      })
    } else {
      console.log(".....***** no current ref object********************")
    }

但是,如果我将其包装在setTimeout中,则参照似乎仍然是参照,但它们以{current: null}的形式出现

当修改为位于setTimeout之内时,代码如下:

// abc_container.js

   componentDidMount() {
    setTimeout(() => {
      console.log("TIMEOUT componentDidMount... this= ", this) 
      if (this.def_thing_ref.current) {
        let rect = this.def_thing_ref.current.getBoundingClientRect()
        console.log("the obejct's rect is ", rect)
        this.setState({
          visible: true,
          message: "the position of the box is " + rect.x + "," + rect.y +
            "....I know this because the ref for this.def_thing_ref.current is populated"
        })
      } else {
        console.log(".....***** no current ref object********************")
      }
    }, 2500)
  }

在这里它产生:

     TIMEOUT componentDidMount... this=  AbcContainer {
 //
        def_thing_ref: { current: null },
        abc_ref: { current: null },

如您所见,经过2.5秒的超时检查后,Refs对象似乎恢复(?)为{current:null}

0 个答案:

没有答案