#3: useState로 관리하던 상태를 ContextAPI로 변경 + 기능추가

Song-Minhyung·2023년 5월 18일
0
post-thumbnail

목차

  1. useState를 사용해 ToDo-List TDD 적용해보기
  2. Context API(TodoProvider) Test 해보기
  3. useState로 관리하던 상태를 ContextAPI로 변경 + 기능추가 👈 NOW
  4. ToDo App에 삭제, 토글, 수정 기능 추가하기
  5. Context API를 Recoil로 변경하기 (recoil test)

➕ 기능추가

이번에는 아이템의 토글, 변경 기능을 추가해줄 예정이다.
기능을 위해 TodoContext에 해당 함수를 추가한 테스트부터 진행해본다.

TodoContext Test 작성

TestComponent

TestComponent는 TodoContext가 toggleTodo, changeTodo를 추가로 리턴해주고
아이템을 눌렀을 때 toggleTodo를 호출, 변경을 눌렀을 때 changeTodo를 호출하면 된다.

const TestComponent = () => {
  const {Todos, addTodo, deleteTodo, toggleTodo, changeTodo} = useTodoContext();

  return (
    <>
      <button data-testid="addTodo"
        onClick={() => addTodo(newTodoValue)}
        > add Todo Button </button>

      {Todos.map(todo => (
        <div data-testid="todoItem" key={todo.id}
          onClick={() => toggleTodo(todo.id)} 
          >
          {`${todo.id} ${todo.done}`}
          {todo.value}

          <div data-testid="deleteTodo"
            onClick={() => deleteTodo(todo.id)}
            > delete Todo Button</div>

          <div data-testid="changeTodo"
            onClick={() => changeTodo(todo.id, changedTodoValue)}
            > change Todo Button</div>
        </div>
      ))}
      </>
  );
}

it("deleteTodo")

간단하게 삭제버튼 클릭시 첫번째 요소가 없어지는지 확인하면 된다.

it("addTodo 확인, newTodoValue 추가 되는지", async () => {
  setup();

  const addButton = screen.getByTestId("addTodo")
  await userEvent.click(addButton);

  const newTodo = screen.getByText(new RegExp(newTodoValue));
  expect(newTodo).toBeInTheDocument();
});

it("toggle")

toggle은 버튼 한번 눌렀을 땐 ${todo.id}: true가 있는지 확인해보면 되고,
두번 눌렀을 땐 ${todo.id}: false가 있는지 확인해보면 된다.
todo의 상태중 done이 제대로 변경이 됐는지 확인해주는 테스트 코드다.

it("item의 done: 한번 클릭시 true, 두번 클릭시 false 되는지 확인", async () => {
  setup();
  const trueReg = new RegExp(`${mockTodos[0].id } true`);
  const falseReg = new RegExp(`${mockTodos[0].id }`);

  const firstItem = screen.getAllByTestId("todoItem")[0];
  await userEvent.click(firstItem);

  let isDoneTrue = screen.getByText(trueReg);
  expect(isDoneTrue).toBeInTheDocument();

  await userEvent.click(firstItem);
  const isDoneFalse = screen.getByText(falseReg);
  expect(isDoneFalse).toBeInTheDocument();
});

it("changeTodo")

changeTodo도 toggle과 비슷하게 변경전에 존재하는지, 변경후 바뀌었는지를 확인해준다.

it("changeTodo 확인, 첫번째 todo 변경해서 제대로 변경되는지", async () => {
  setup();

  const firstChangeButton = screen.getAllByTestId("changeTodo")[0];
  await userEvent.click(firstChangeButton);

  const changedTodo = screen.getByText(new RegExp(changedTodoValue));
  expect(changedTodo).toBeInTheDocument();
});

it("changeModify")

changeModify 역시 toggle과 같다.

it("changeModify확인, 첫번째 todo의 modifyMode 제대로 변경되는지확인", async () => {
  setup();
  const trueReg = new RegExp(`modify: ${mockTodos[0].id } true`);
  const falseReg = new RegExp(`modify: ${mockTodos[0].id } false`);

  const firstChangeButton = screen.getAllByTestId("changeModify")[0];

  // 최초에는 modifyMode false
  let isModifyModeFalse = screen.getByText(falseReg);
  expect(isModifyModeFalse).toBeInTheDocument();

  await userEvent.click(firstChangeButton);

  // 한번 누르면 modifyMode true
  let isModifyModeTrue = screen.getByText(trueReg);
  expect(isModifyModeTrue).toBeInTheDocument();
});

TodoContext 수정

위에서 작성한 테스트 코드 기반으로 테스트가 통과할 수 있도록 빠르게 코드를 작성해본다.
그리고 toggle 하기 위해 Todo의 상태에 done: boolean도 추가해준다!
물론 목업에도 해당 속성과 함수들을 추가해준다.

//TodoContext.tsx
...
export interface TodoContextState {
  ...
  toggleTodo: (id: number) => void;
  changeTodo: (id: number, value: string) => void;
}
...
const TodoProvider = (...) => {
  ...
  const toggleTodo = (id: number) => {
    setState(prev => 
      prev.map( todo => 
        todo.id === id 
        ? {...todo, done: !todo.done}
        : todo
      )
    );
  }

  const changeTodo = (id: number, value: string) => {
    setState(prev => 
      prev.map( todo => 
        todo.id === id 
        ? {...todo, value}
        : todo
      )
    );
  }

  return (
    <TodoContext.Provider value={value ?? {Todos, addTodo, deleteTodo, toggleTodo, changeTodo}}>
      {children}
    </TodoContext.Provider>
  );
}

TodoContext 수정 완료

//TodoContext.tsx
import { createContext, ReactNode, useRef, useState } from "react";

export interface Todo {
  id: number;
  value: string;
  done: boolean;
}
export interface TodoContextState {
  Todos: Todo[],
  addTodo: (value: string) => void;
  deleteTodo: (id: number) => void;
  toggleTodo: (id: number) => void;
  changeTodo: (id: number, value: string) => void;
}

const TodoContext = createContext<TodoContextState | null>(null);

const TodoProvider = ({children, value, init = []}: {children: ReactNode, value?: TodoContextState, init?: Todo[]}) => {
  const [Todos, setState] = useState<Todo[]>(init ?? value?.Todos);
  const nextId = useRef(0);

  const addTodo = (value: string) => {
    setState(prev => [...prev, {id: nextId.current++, value, done: false}]);
  }

  const deleteTodo = (id: number) => {
    setState(prev => prev.filter(todo => todo.id !== id));
  }

  const toggleTodo = (id: number) => {
    setState(prev => 
      prev.map( todo => 
        todo.id === id 
        ? {...todo, done: !todo.done}
        : todo
      )
    );
  }

  const changeTodo = (id: number, value: string) => {
    setState(prev => 
      prev.map( todo => 
        todo.id === id 
        ? {...todo, value}
        : todo
      )
    );
  }

  return (
    <TodoContext.Provider value={value ?? {Todos, addTodo, deleteTodo, toggleTodo, changeTodo}}>
      {children}
    </TodoContext.Provider>
  );
}

export {TodoContext, TodoProvider};

이번에 TodoContext 수정을 할땐 테스트코드 작성 후 -> 코드를 작성했다.
하지만 아직 테스트 코드 작성이 미숙해서 그런지 잘못 작성해버렸다.

결국 마지막에 테스트코드를 수정해버려서 결국 또 처음으로 돌아가버렸다!!

테스트 코드 작성법이 익숙해져서 작성시간이 줄어들면 TDD는 그때부터 진가를 발휘할것같다.

다음 글에서는 이제 실제로 컴포넌트들에서 돌아가는 테스트 코드를 작성할 것이다.
토글, 수정, 삭제는 아마 TodoItem 컴포넌트의 테스트 코드만 많이 수정해주면 될것같다.

profile
기록하는 블로그

0개의 댓글