Не удается заставить React Forwarded refs работать в тестовой среде
У меня проблема, когда я хочу проверить несколько переадресованных ссылок. Я могу заставить эти перенаправленные ссылки нормально работать в моем браузере, но в тестовой среде я вижу другое поведение.
Вы можете просмотреть мое полное приложение для воспроизведения здесь 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-response-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
В разработке, в моем браузере, перенаправленная ссылка работает. Через 2,5 секунды (произвольно) код в AbcContainer выбирает положение DefThing, используя ссылку на него и получая на нем '.getBoundingClientRect()`, например:
this.def_thing_ref.current.getBoundingClientRect()
(см. abc_container.js #componentDidMount)
в моем браузере это работает правильно, и current
переадресованного обращения заполняется
Однако я не могу заставить это работать при запуске спецификаций. Вот мои характеристики
// 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}
что означает, что ссылка работает правильно
Кажется, что-то не хватает для того, чтобы заставить перенаправленные ссылки работать в тестовой настройке
ОБНОВЛЕНИЕ 13.12.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 },
как вы можете видеть, объекты Refs, кажется, возвращаются (?) к { current: null } при исследовании после 2,5-секундного таймаута.