TypeError: Cannot read property 'scrollIntoView' of null - react. jest enzyme

Viewed 24172

Using react 16.3.1, jest 16.3.1, enzyme 3.3.0.

Within my React Class component I have created a react ref which I use to ensure that when the component is mounted the browser is at the top of the page.

class PageView extends React.Component {

  constructor(props) {
    super(props);
    this.topOfPageRef = React.createRef();
  }

  componentDidMount() {
    ReactDOM.findDOMNode(this.topOfPageRef.current).scrollIntoView();
  }

  render(){
    const { x } = this.props;

    return (
      <React.Fragment>
        <div className="main-wrapper" ref={this.topOfPageRef}>
         Top
        </div>
        )}
      </React.Fragment>
    );
  }
}

This all works perfectly within the browser but fails in my enzyme test.

My test is simple, it just tries to render the component.

  it('should render component correctly', () => {
    const props = {
      ...defaultProps,
    };
    const wrapper = mount(<PageView {...props} />);
    expect(wrapper).toMatchSnapshot();
  });

TypeError: Cannot read property 'scrollIntoView' of null

I have tried both shallow and mount methods and whilst the element found is not null, it appears to be a react instance of HTMLDivElement which is missing the scrollIntoView method.

3 Answers

Error message clarification

Using mount like in the sample code above gives this error:

TypeError: _reactDom2.default.findDOMNode(...).scrollIntoView is not a function

Using shallow gives the error listed above:

TypeError: Cannot read property 'scrollIntoView' of null


shallow

Issue

shallow does not do DOM rendering so there will never be a DOM node on which to call scrollIntoView().

Solution

Any code that does DOM manipulation needs to be tested using the full DOM rendering provided by mount.


mount

"The default environment in Jest is a browser-like environment through jsdom".

"jsdom is a pure-JavaScript implementation of many web standards...[that] emulate[s] enough of a subset of a web browser to be useful for testing".

Issue

jsdom implements much of the browser environment but it does not implement everything. Of particular note for this question is that it does not implement scrollIntoView since jsdom does not do layout and would therefore not be able to provide an accurate implementation.

Because jsdom does not implement scrollIntoView it will be undefined on elements provided by jsdom.

Solution

The recommended approach from this Google dev is to add the following line to your test code:

Element.prototype.scrollIntoView = () => {};

That line will add a noop implementation of scrollIntoView to the jsdom-provided Element.

For your test you could take it a step further and set scrollIntoView to a spy to make sure it is called:

it('should render component correctly', () => {
  const props = {
    ...defaultProps,
  };
  Element.prototype.scrollIntoView = jest.fn();  // set scrollIntoView to a spy
  const wrapper = mount(<PageView {...props} />);
  expect(wrapper).toMatchSnapshot();
  expect(Element.prototype.scrollIntoView).toHaveBeenCalled();  // PASSES
});

Also, Antonio is correct that you shouldn't need to use ReactDOM.findDOMNode(), you should be able to use this.topOfPageRef.current directly:

componentDidMount() {
  this.topOfPageRef.current.scrollIntoView();
}

The error you get is because ReactDOM.findDOMNode is giving you back null.

As the React doc says:

When a component renders to null or false, findDOMNode returns null

also

In most cases, you can attach a ref to the DOM node and avoid using findDOMNode at all.

You should not use React.findDOMNode

componentDidMount() {
   ReactDOM.findDOMNode(this.topOfPageRef.current).scrollIntoView();
}

but:

componentDidMount() {
   this.topOfPageRef.current.scrollIntoView();
}

Hope it will help.

You can set in window

  it('should call paginator component', () => {
      window.HTMLElement.prototype.scrollIntoView = jest.fn();
      // arrange
      // act
      // assertion
  })
  
Related