javascriptreactjstypescriptreact-reduxparent-node

React TypeError: Cannot read property 'parentNode' of null after document.getElementByID JEST TESTING


I have setup a couple Jest tests but they just are not working and returning this error.

    TypeError: Cannot read property 'parentNode' of null
     44 |     console.log(document.getElementById("body"));
     45 |     var title = document.getElementById("modalTitle"),
   > 46 |       parentTitle = title.parentNode,

When I run the program though it all works properly, no errors, it's just fine. I added some simulated changes to input some fake values into a journal to check if that would help, but it didn't.

So here is the test code.

DisplayJournal.spec.tsx

import * as React from "react";
import { shallow } from "enzyme";
import { DisplayJournal } from "./DisplayJournal";
import AddJournal from "./AddJournal";

let mock: any = jest.fn();

const DisplayJournal_Mock = shallow(
  <DisplayJournal
    selectedJournal={{
      id: 1,
      title: "hello",
      notes: "frog",
      reference: "Test",
      date: "12"
    }}
    deselectJournal={mock}
    onClickEditJournal={mock}
    match={mock}
    location={mock}
    //@ts-ignorecls
    history={{ push: mock }}
  />
);

test("DisplayJournal.Render() does not return null.", () => {
  expect(DisplayJournal_Mock.type()).not.toBeNull();
  expect(DisplayJournal_Mock.type()).not.toEqual(null);
  expect(DisplayJournal_Mock.get(0)).not.toBeNull();
  expect(DisplayJournal_Mock.get(0)).not.toEqual(null);
});

test("DisplayJournal.tsx contains information", () => {
  expect(DisplayJournal_Mock.find(".modalBody")).not.toBeNull();
  expect(DisplayJournal_Mock.find("#refPlaceholder")).not.toBeNull();
  expect(DisplayJournal_Mock.find(".editButton")).not.toBeNull();
});

test("Checking onClick for edit button can be reached", () => {
  const jestFunc = jest.fn();
  const AddJournal_Mock = shallow(
    <AddJournal handleSubmit={() => this._handleSubmit} />
  );

  AddJournal_Mock.find("#value").simulate("change", {
    target: jestFunc,
    value: "testVal",
    preventDefault: jestFunc
  });
  AddJournal_Mock.find("#notes").simulate("change", {
    target: jestFunc,
    value: "testNotes",

    preventDefault: jestFunc
  });
  AddJournal_Mock.find("#value").simulate("change", {
    target: jestFunc,
    value: "testName",
    preventDefault: jestFunc
  });
  AddJournal_Mock.find("#button").simulate("click", {
    preventDefault: jestFunc
  });

  const eventProps = {
    preventDefault: jestFunc
  };
  const button = DisplayJournal_Mock.find("button").at(0);
  button.simulate("click", eventProps);
  expect(jestFunc).toBeCalled();
});

And now here is the file that is being tested against and I will BlockQuote the line that is having the issue.

import * as React from "react";
import { Journal } from "../Models";
import Modal from "../../../global/components/modal/Modal";
import * as css from "../css/journal.scss";
import { connect } from "react-redux";
import { hideJournal, editJournal } from "../Actions";
import { Route, RouteComponentProps, withRouter } from "react-router-dom";
import { getNames } from "../Selectors";
import { State } from "../Reducers";

interface Props extends RouteComponentProps {
  selectedJournal: Journal;
  deselectJournal: Function;
  onClickEditJournal: Function;
}
interface States {
  title: string;
  body: string;
  name: string;
  date: string;
  names: string[];
}

var edited = false;

export class DisplayJournal extends React.Component<Props, States> {
  constructor(props: Props) {
    super(props);
    this.state = {
      title: "",
      body: "",
      name: "",
      date: "",
      names: []
    };
  }

  _dismiss(e: React.MouseEvent): void {
    e.stopPropagation();
    e.preventDefault();
  }

  handleClick = g => {
    console.log(document.getElementById("body"));
    var title = document.getElementById("modalTitle"),
      parentTitle = title.parentNode,
      titleInput = document.createElement("input");
    var body = document.getElementById("body"),
      parentBody = body.parentNode,
      bodyInput = document.createElement("input");
    var name = document.getElementById("name"),
      parentName = name.parentNode,
      nameInput = document.createElement("input");
    var date = document.getElementById("date");
    var currentDate = new Date();
    var day = currentDate.getDate();
    var month = currentDate.getMonth() + 1;
    var year = currentDate.getFullYear();
    var now = day + "/" + month + "/" + year;

    titleInput.id = titleInput.name = "title";
    bodyInput.id = bodyInput.name = "body";
    nameInput.id = nameInput.name = "name";

    titleInput.type = "text";
    bodyInput.type = "text";
    nameInput.type = "text";

    titleInput.value = this.state.title;
    bodyInput.value = this.state.body;
    nameInput.value = this.state.name;

    date.innerText = now;

    this.setState({ date: now });

    parentTitle.replaceChild(titleInput, title);
    parentBody.replaceChild(bodyInput, body);
    parentName.replaceChild(nameInput, name);

    titleInput.addEventListener("blur", this.onBlurEdit, false);
    bodyInput.addEventListener("blur", this.onBlurEdit, false);
    nameInput.addEventListener("blur", this.onBlurEdit, false);
  };

  onBlurEdit = e => {
    if (e.target.name === "title") {
      let titleVal = e.target.value;
      this.setState({ title: titleVal });
    } else if (e.target.name === "body") {
      let bodyVal = e.target.value;
      this.setState({ body: bodyVal });
    } else if (e.target.name === "name") {
      let nameVal = e.target.value;
      this.setState({ name: nameVal });
    }

    edited = true;

    //@ts-ignore
    let id = this.props.match.params.id;
    let title = this.state.title;
    let body = this.state.body;
    let name = this.state.name;
    let date = this.state.date;

    this.props.onClickEditJournal(id, title, body, name, date);
  };

  render() {
    const { selectedJournal } = this.props;
    const Button = () => (
      <Route
        render={({ history }) => (
          <span
            className={css.viewJournalCloseButton}
            title="Close the modal dialog"
            onClick={() => {
              this.props.history.push("/journal");
            }}
          >
            X
          </span>
        )}
      />
    );

    if (selectedJournal == null) return null;

    if (edited == true) {
      selectedJournal.title = this.state.title;
      selectedJournal.notes = this.state.body;
      selectedJournal.reference = this.state.name;
      selectedJournal.date = this.state.date;
    }

    return (
      <Modal title={selectedJournal.title}>
        <Button />
        <div className={css.modalBody}>
          <div>
            <div className={css.displayNotes}>
              <div id="body" className={css.notesSpan}>
                {selectedJournal.notes}
              </div>
            </div>
            <div className={css.row2}>
              <div className={css.displayTogether}>
                <div className={css.referenceSpan}>
                  <span id="refPlaceholder">Written by:</span>
                  <span id="name">{selectedJournal.reference}</span>
                </div>
              </div>
              <div className={css.displayTogether}>
                <div className={css.dateSpan}>
                  Date created:
                  <span id="date">{selectedJournal.date}</span>
                </div>
              </div>
            </div>
            <button className={css.editButton} onClick={this.handleClick}>
              Edit
            </button>
          </div>
        </div>
      </Modal>
    );
  }
}

const mapStateToProps = (state: State) => ({
  names: getNames(state)
});
const mapDispatchToProps = {
  _dismiss: hideJournal,
  onClickEditJournal: editJournal
};

export default connect<any, any, any, any>(
  mapStateToProps,
  mapDispatchToProps
)(withRouter(DisplayJournal));

Solution

  • This issue was that modalTitle was part of the modal component. The way that I fixed this in the JEST testing was by changing shallow to mount I was also able to use shallow(AddJournal_Mock).dive();