0
votes

I have functions called changeStatus and deleteItem in component TodoItem.js

When a button (with the className of btn-info) is clicked, the changeStatus function is called and it changes the todo item's "complete" state to opposite (default is false, so it should turn to true).

When a button (with the className of btn-danger) is clicked, the deleteItem function is called and it removes the item from the todos array.


The state for todo item is in App.js from where the todos array and setTodos function are passed down to TodoItemList which renders the TodoItem component and passes the todos and setTodos down to TodoItem.

import React, { useState } from "react";
import AddItemBar from "./components/AddItemBar";
import TodoItemList from "./components/TodoItemList";
import "./App.css";
import { v4 as uuidv4 } from "uuid";

const App = () => {
  // Initial state for todos
  const [todos, setTodos] = useState([
    { id: uuidv4(), name: "Learn React", complete: false },
    { id: uuidv4(), name: "Graduate from school", complete: false },
    { id: uuidv4(), name: "Get a cool web development job", complete: false },
    { id: uuidv4(), name: "Learn more new technologies", complete: false },
    { id: uuidv4(), name: "Enjoy working life", complete: false }
  ]);

  return (
    <div className="container appbox">
      <TodoItemList todos={todos} setTodos={setTodos} />
      <AddItemBar todos={todos} setTodos={setTodos} />
    </div>
  );
};

export default App;

TodoItemList just loops through the todos array and renders a TodoItem for each array item, passing the individual todo, todos array and setTodos function to TodoItem:

import React from "react";
import TodoItem from "./TodoItem";

const TodoItemList = ({ todos, setTodos }) => {
  var items = [];

  // Create todo item list
  todos.forEach(todo => {
    items.push(
      <TodoItem key={todo.id} todo={todo} todos={todos} setTodos={setTodos} />
    );
  });

  return items;
};

export default TodoItemList;

TodoItem.js component:

import React from "react";

const TodoItem = ({ todo, todos, setTodos }) => {
  // Todo item color and text changes based on todo.complete state
  var color = todo.complete === true ? "lightgreen" : "bisque";
  var text = todo.complete === true ? "Complete" : "Incomplete";

  // Change completed status for clicked item
  const changeStatus = id => {
    var todoItems = todos.slice();
    for (let i = 0; i < todos.length; i++) {
      if (todoItems[i].id === id) {
        var newComplete = !todoItems[i].complete;
        todoItems[i].complete = newComplete;
      }
    }

    setTodos(todoItems);
  };

  // Remove todo item
  const deleteItem = id => {
    const newTodos = [...todos];

    var removeIndex = newTodos
      .map(todo => {
        return todo.id;
      })
      .indexOf(id);

    newTodos.splice(removeIndex, 1);

    setTodos(newTodos);
  };

  return (
    <div
      className="row mt-2 mr-1 ml-1 todoItem"
      style={{ backgroundColor: color }}
    >
      <h5 className="col-sm text-center">{todo.name}</h5>
      <button
        className="btn btn-info col-sm-3"
        onClick={() => changeStatus(todo.id)}
      >
        {text}
      </button>
      <button
        className="btn btn-danger col-sm-2"
        onClick={() => deleteItem(todo.id)}
      >
        Delete
      </button>
    </div>
  );
};

export default TodoItem;


Now how do I test the side-effects of these functions in the TodoItem?

Here's the TodoItem.test.js file so far, it only tests the UI (all of them pass), but not the state updates:

import React from "react";
import TodoItem from "./TodoItem";
import { v4 as uuidv4 } from "uuid";
import { shallow } from "enzyme";

describe("<TodoItem />", () => {
  it("should render without problems", () => {
    const todo = { id: uuidv4(), name: "Learn React", complete: false };
    shallow(<TodoItem todo={todo} />);
  });

  it("shallow wrapper instance should be null", () => {
    const todo = { id: uuidv4(), name: "Learn React", complete: false };
    const wrapper = shallow(<TodoItem todo={todo} />);
    const instance = wrapper.instance();

    expect(instance).toEqual(null);
  });

  it("should have todo text", () => {
    const todo = { id: uuidv4(), name: "Learn React", complete: false };
    const wrapper = shallow(<TodoItem todo={todo} />);
    expect(wrapper.find("h5").length).toEqual(1);
  });

  it("should have a complete button", () => {
    const todo = { id: uuidv4(), name: "Learn React", complete: false };
    const wrapper = shallow(<TodoItem todo={todo} />);
    expect(wrapper.find("button.btn-info").length).toEqual(1);
  });

  it("should have a delete button", () => {
    const todo = { id: uuidv4(), name: "Learn React", complete: false };
    const wrapper = shallow(<TodoItem todo={todo} />);
    expect(wrapper.find("button.btn-danger").length).toEqual(1);
  });
});

I've read that it would be harmful to test the functions themselves directly,
so I want to just test the side-effects which are:

  • changeStatus: test if the todo.complete changes from false to true
  • deleteItem: test if the item is deleted from todos array
1

1 Answers

0
votes

OK, so after searching Google for a while, I found out that currently it is impossible to test function component's state with Jest/Enzyme.

For example, the state() testing function which Enzyme provides is only for class components...