StoryBook for React #3 복잡한 컴포넌트 만들기(데코레이터, 단위 테스트의 단점)

한마디: 아래와 같이 코드 위치와 정돈된 상태로 보여주려면 개행을 할 수 없어서 불편하다..

components/TaskList.tsx

import React from 'react';
import Task, { TaskProp } from './Task';
type TaskListProps = {
  tasks: TaskProp[];
  loading: string;
  onPinTask: (id: number) => void;
  onArchiveTask: (id: number) => void;
};
export default function TaskList({ tasks, loading, onPinTask, onArchiveTask }: TaskListProps) {
  const events = {
    onPinTask,
    onArchiveTask,
  };
  if (loading) {
    return <div>loading</div>;
  }
  if (tasks.length === 0) {
    return <div>아무것도 없음</div>;
  }
  return (
    <div className='list-items'>
      {tasks.map(task => {
        return <Task id={task.id} task={task} {...events} />;
      })}
    </div>
  );
}

components/TaskList.stories.js

데코레이터를 사용해서 임의의 레퍼를 제공했다. 스토리를 감싸준다. 이렇게 스타일을 줄 때도 사용할 수 있지만 context나 redux를 사용할 때 상태를 전달하기 위한 용도로도 사용한다.

import React from 'react';
import TaskList from './TaskList';
import * as TaskStories from './Task.stories';
export default {
  component: TaskList,
  title: 'TaskList',
  decorators: [story => <div style={{ paddingLeft: '20px' }}>{story()}</div>],
};
const Template = args => <TaskList {...args} />;
export const Default = Template.bind({});
Default.args = {
  tasks: [
    { ...TaskStories.Default.args.task, id: 1, title: 'Task1' },
    { ...TaskStories.Default.args.task, id: 2, title: 'Task2' },
    { ...TaskStories.Default.args.task, id: 3, title: 'Task3' },
    { ...TaskStories.Default.args.task, id: 4, title: 'Task4' },
    { ...TaskStories.Default.args.task, id: 5, title: 'Task5' },
  ],
};
export const WithPinnedTasks = Template.bind({});
WithPinnedTasks.args = {
  tasks: [...Default.args.tasks.slice(0, 5), { id: 6, title: '6 pinned', state: 'TASK_PINNED' }],
};
export const Loading = Template.bind({});
Loading.args = {
  tasks: [],
  loading: true,
};
export const Empty = Template.blnd({});
Empty.args = {
  ...Loading.args,
  loading: false,
};

TaskList를 좀 더 자세하게 구현하면 아래와 같이 할 수 있다.

TaskList.tsx

import React from 'react';
import Task, { TaskProp } from './Task';
export type TaskListProps = {
  tasks: TaskProp[];
  loading: boolean;
  onPinTask: (id: number) => void;
  onArchiveTask: (id: number) => void;
};
export default function TaskList({ tasks, loading, onPinTask, onArchiveTask }: TaskListProps) {
  const events = {
    onPinTask,
    onArchiveTask,
  };
  const LoadingRow = (
    <div className='loading-item'>
      <span className='glow-checkbox' />
      <span className='glow-text'>
        <span>Loading</span> <span>cool</span> <span>state</span>
      </span>
    </div>
  );
  if (loading) {
    return (
      <div className='list-items'>
        {LoadingRow}
        {LoadingRow}
        {LoadingRow}
        {LoadingRow}
        {LoadingRow}
        {LoadingRow}
      </div>
    );
  }
  if (tasks.length === 0) {
    return (
      <div className='list-items'>
        <div className='wrapper-message'>
          <span className='icon-check' />
          <div className='title-message'>You have no tasks</div>
          <div className='subtitle-message'>Sit back and relax</div>
        </div>
      </div>
    );
  }
  const taskInOrder = [
    ...tasks.filter(task => task.state === 'TASK_PINNED'),
    ...tasks.filter(task => task.state !== 'TASK_PINNED'),
  ];
  return (
    <div className='list-items'>
      {taskInOrder.map(task => {
        return <Task id={task.id} task={task} {...events} />;
      })}
    </div>
  );
}

Jest를 이용한 단위 테스트를 진행 해볼수도 있다.

기능상의 문제를 검토해보기에는 좋지만 뒤에서 알아볼 시각적 회귀 테스트(?)라고 직역되어있는 테스트를 해보자. 단위테스트의 경우 업데이트가 되었을 때 추적이 어려워지기도 한다. 테스트 코드를 계속 업데이트를 해줘야 한다.
👏 하지만 스토리북에서 얘기하고 있는 시각적 회귀 테스트의 경우 변경 전 후 바뀐 부분을 시각적으로 보여주기 때문에 굉장히 유용할 수 있다.

좋은 웹페이지 즐겨찾기