user3019647
user3019647

Reputation: 153

React Tests fails when i use document.getElementById (Jest+Enzyme)

I am working on a React form and have an onSubmit function to it. I only added below lines in onSubmit function.

const id = this.getErrorPositionById();
    const errorPosition = document.getElementById(id).offsetTop; //CANNOT READ PROPERTY 'offsetTop' of null
    window.scrollTo({
      top: errorPosition,
      behavior: "smooth"
    });

And this is the onSubmit function.

public getErrorPositionById = () => {
    const error = this.state.errors;
    return Object.keys(error).find(id => error[id] != null);
  };


public onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    const id = this.getErrorPositionById();
    const errorPosition = document.getElementById(id).offsetTop; 
    window.scrollTo({
      top: errorPosition,
      behavior: "smooth"
    });
    if (
      !this.state.isValid ||
      (!this.props.allowMultipleSubmits && this.state.isSubmitted)
    ) {
      return;
    }
    Promise.all(this.validateFields())
      .then((validationErrors: IValidationErrors[]) => {
        this.setError(Object.assign({}, ...validationErrors), () => {
          this.isFormValid() ? this.setSubmitted(e) : this.scrollFormToView();
        });
      })
      .then(() => {
        const newErrors = this.state.errors;
        this.setState({ errors: { ...newErrors, ...this.props.apiErrors } });
      });
  };

Here is the test case

  beforeEach(() => {
    jest.clearAllMocks();
    formFields = jest.fn();
    onSubmit = jest.fn();
    onValidate = jest.fn();
    validate = jest.fn();
    mockPreventDefault = jest.fn();
    mockEvent = jest.fn(() => ({ preventDefault: mockPreventDefault }));
    mockValidateAllFields = jest.fn(() => Promise);
    mockChildFieldComponent = { validate };
    instance = formWrapper.instance();
  });


it("should not reValidate if form has been submitted already", () => {
    instance.validateFields = mockValidateAllFields;
    instance.setSubmitted();
    expect(instance.state.isSubmitted).toBe(true);
    instance.onSubmit(mockEvent());
    expect(mockValidateAllFields).toHaveBeenCalledTimes(0);
  });

The test case fails with error

TypeError: Cannot read property 'offsetTop' of null

on below line

const errorPosition = document.getElementById(id).offsetTop; 

Can someone please help me understand how to eliminate this error.

Upvotes: 1

Views: 2095

Answers (1)

Lin Du
Lin Du

Reputation: 102447

You should make a stub for document.getElementById(id). For simple, I remove your business logic from the component.

E.g.

index.tsx:

import React, { Component } from 'react';

class SomeComponent extends Component {
  state = {
    errors: {
      '#selector': {},
    },
  };

  public getErrorPositionById = () => {
    const error = this.state.errors;
    return Object.keys(error).find((id) => error[id] != null);
  };

  public onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    const id = this.getErrorPositionById() as string;
    const errorPosition = document.getElementById(id)!.offsetTop;
    window.scrollTo({
      top: errorPosition,
      behavior: 'smooth',
    });
  };

  public render() {
    return (
      <div>
        <form onSubmit={this.onSubmit}></form>
      </div>
    );
  }
}

export default SomeComponent;

index.spec.tsx:

import React from 'react';
import { shallow } from 'enzyme';
import SomeComponent from './';

describe('SomeComponent', () => {
  afterEach(() => {
    jest.resetAllMocks();
  });
  it('should handle submit correctly', async () => {
    const mElement = { offsetTop: 123 };
    document.getElementById = jest.fn().mockReturnValueOnce(mElement);
    window.scrollTo = jest.fn();
    const wrapper = shallow(<SomeComponent></SomeComponent>);
    const mEvent = { preventDefault: jest.fn() };
    wrapper.find('form').simulate('submit', mEvent);
    expect(mEvent.preventDefault).toHaveBeenCalledTimes(1);
    expect(document.getElementById).toBeCalledWith('#selector');
    expect(window.scrollTo).toBeCalledWith({ top: 123, behavior: 'smooth' });
  });
});

Unit test result with coverage report:

 PASS  src/stackoverflow/53352420/index.spec.tsx (12.859s)
  SomeComponent
    ✓ should handle submit correctly (20ms)

-----------|----------|----------|----------|----------|-------------------|
File       |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
-----------|----------|----------|----------|----------|-------------------|
All files  |      100 |      100 |      100 |      100 |                   |
 index.tsx |      100 |      100 |      100 |      100 |                   |
-----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        14.751s

Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/53352420

Upvotes: 4

Related Questions