한마디: 아래와 같이 코드 위치와 정돈된 상태로 보여주려면 개행을 할 수 없어서 불편하다..
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.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> ); }
기능상의 문제를 검토해보기에는 좋지만 뒤에서 알아볼 시각적 회귀 테스트(?)라고 직역되어있는 테스트를 해보자. 단위테스트의 경우 업데이트가 되었을 때 추적이 어려워지기도 한다. 테스트 코드를 계속 업데이트를 해줘야 한다.
👏 하지만 스토리북에서 얘기하고 있는 시각적 회귀 테스트의 경우 변경 전 후 바뀐 부분을 시각적으로 보여주기 때문에 굉장히 유용할 수 있다.