스토리북
UI 컴포넌트를 독립적으로 개발하고 문서화 할 수 있는 환경을 제공하는도구
UI 컴포넌트에 story(UI 컴포넌트의 state를 의미)를 부여해서 렌더링을 테스트 한다. (ex. button - disabled, enabled, primary 등)
스토리북은 단순히 프론트엔드 개발자가 UI 컴포넌트를 테스트 하는 용도 뿐만 아니라, 디자인 시스템 구축에 중요한 역할을 한다.
개발한 컴포넌트가 디자인 가이드에 맞는지, 스타일이 일관성 있는지, 재사용성 및 문서화와 더불어 프론트엔드 - 프론트엔드, 프론트엔드 - 디자이너 간의 협업과 의사소통이 수월해질 수 있도록 도와준다.
설치
npx storybook@latest init
공식 문서 참고 (yarn은 yarn berry를 사용해야 에러가 없는데 저는 어떻게 썼던 기억이 있네요)
npm을 써서 그런 건지 매우 힘겹게 깔렸다.
스토리북을 실행하려면 npm run storybook을 입력해달라고 한다.
// package.json
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
설치 후 package.json을 보면 스토리북에서 알아서 명령어를 생성해준 걸 알 수 있다.
스토리북은 Button, Header, Page와 같이 예시를 준다.
아토믹 디자인 패턴을 안다면 그와 유사하다고 보면 된다.
이벤트나 primay 여부, label, backgroundColor 등을 여기서 테스트 해 볼 수 있다.
스토리북을 설치하면 자동으로 폴더들이 생긴다.
// .storybook/main.ts
import type { StorybookConfig } from "@storybook/react-vite";
const config: StorybookConfig = {
// 스토리북에 사용할 .mdx, .stories 파일의 위치
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
// 적용할 addon
addons: [
"@storybook/addon-onboarding",
"@storybook/addon-links",
"@storybook/addon-essentials",
"@chromatic-com/storybook",
"@storybook/addon-interactions",
],
// 프레임워크 종류
framework: {
name: "@storybook/react-vite",
options: {},
},
};
export default config;
스토리 경로와 addon을 추가할 수 있다.
// .storybook/preview.ts
import type { Preview } from "@storybook/react";
const preview: Preview = {
parameters: {
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
},
};
export default preview;
스토리북 프리뷰 화면에 대한 설정을 적용할 수 있다.
이 부분은 실제로 사용해보지 않아서 정확히 모르겠다. (확인 필요)
사용하기
회원가입 페이지에 정확한 정보를 입력하면 비활성화 되어 있던 회원가입 버튼이 활성화 되게 하려고 한다.
비활성화 -> 활성화 될 때 디자인을 스토리북을 사용해 확인해보자.
// stories/Button.tsx
import React from 'react';
import './button.css';
interface ButtonProps {
/**
* Is this the principal call to action on the page?
*/
primary?: boolean;
/**
* What background color to use
*/
backgroundColor?: string;
/**
* How large should the button be?
*/
size?: 'small' | 'medium' | 'large';
/**
* Button contents
*/
label: string;
/**
* Optional click handler
*/
onClick?: () => void;
disabled?: boolean;
}
/**
* Primary UI component for user interaction
*/
export const Button = ({
primary = false,
disabled = true,
size = 'medium',
backgroundColor,
label,
...props
}: ButtonProps) => {
const mode = primary
? 'storybook-button--primary'
: 'storybook-button--secondary';
return (
<button
type="button"
className={['storybook-button', `storybook-button--${size}`, mode].join(
' '
)}
style={{ backgroundColor }}
{...props}
disabled={disabled}
>
{label}
</button>
);
};
disabled?: boolean과 disabled = true, disabled={disabled}를 추가해줬다.
이 비활성화는 옵셔널한데, 처음에는 true가 되어 있어야 한다. (입력해야만 활성화가 되어야 하니까)
// src/stories/button.css
.storybook-button {
font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-weight: 700;
border: 0;
border-radius: 3em;
cursor: pointer;
display: inline-block;
line-height: 1;
}
.storybook-button--primary {
color: white;
background-color: var(--primary);
}
.storybook-button--secondary {
color: #333;
background-color: var(--secondary);
box-shadow: rgba(0, 0, 0, 0.15) 0px 0px 0px 1px inset;
}
.storybook-button:disabled {
background-color: var(--mono-100);
color: var(--mono-200);
}
--primary나 --secondary, --mono-100의 경우 예제 파일의 App.css에 적혀 있어서 바로 사용했다.
disabled에 대한 값의 boolean 값이 생겼고 그에 따른 CSS가 바뀌는 걸 볼 수 있다.
참고로 스토리북은 수정하고 나면 한번 내렸다가 올려줘야 반영이 된다고 한다. (번거로움 이슈)
border-radius를 빼먹었는데 이 값도 4px 적용이 되었다고 봅시다.
이런 페이지의 회원가입 버튼이 활성화 되게 하려고 한다.
아까 만든 Button 컴포넌트를 해당 페이지의 SignupButton 대신 사용해보자.
// SignupPage.tsx
<SignupButton
disabled={!email || !password || password != confirmPassword}
onClick={() => handleSignup({ username: email, password })}
>
회원가입
</SignupButton>
// SignupPage.tsx
<Button
disabled={!email || !password || password != confirmPassword}
onClick={() => handleSignup({ username: email, password })}
label="회원가입"
/>
위에서 아래 코드로 바꿔준다.
단순히 Button만 import하면 원래 stories/Button.tsx에 있는 children에서 에러가 난다.
label을 반드시 전달해줘야 하기 때문이라서 label prop에 label을 적어준다.
잘 들어갔는데 primary 색상이 아닌 다른 색이 들어가 있다.
위 코드에 primary={true}도 추가해주자.
그런데 위에 원래 SignUpButton을 적용했을 때를 보면 해당 버튼이 살짝 떠 있는데 이것은 그렇지 않다.
SignUpButton에는 margin-bottom이 적용되어 있어서 그렇다.
// SignupPage.tsx
const SignupButton = styled.button`
width: 100%;
padding: 16px;
border-radius: 4px;
background-color: ${props => (props.disabled ? '#f1f1f1' : '#1d2745')};
color: ${props => (props.disabled ? '#bebebe' : '#ffffff')};
margin-bottom: 24px;
`;
우리가 사용하는 stories/Button.tsx는 공용 컴포넌트인데, 위 페이지에서만 margin-bottom을 쓰려고 한다.
이 경우에는 공용 컴포넌트를 손대기 보다 selector를 사용해 주면 된다.
const Wrapper = styled.div`
${ColumnSpaceBetween}
height: 100%;
background-color: #ffffff;
padding: 0 16px;
> button {
margin-bottom: 24px;
}
`;
Button 컴포넌트는 Wrapper에 감싸져 있기 때문에 이렇게 선택해주면 된다.
disabled일 때도 잘 되는 것을 확인할 수 있다.
스토리북 배포
스토리북을 배포해서 디자이너와 효율적으로 협업을 해보자.
공식 문서대로 따라가면 되는데, 그냥 package.json에 적혀 있는 명령어를 실행해주면 된다.
npm run build-storybook
storybook-static이라는 폴더가 생성된다.
npx http-server ./storybook-static
명령어를 입력해준다.
http-server를 설치해야 한다면 설치해주면 된다.
여러 가지 설정들이 나오는데 Available on의 링크를 클릭하면 스토리북을 사용할 수 있게 된다.
build 폴더의 위치만 바꿔서 AWS 같이 다른 곳에 올려주면 된다.
웹 접근성 테스트
스토리북은 다양한 기능을 제공하는데 그 중 하나가 웹 접근성에 대한 테스트다.
26% of adults in the US have at least one disability.
When you improve accessibility, it has an outsized impact on your current and future customers.
It’s also a legal requirement.
The most accurate way to check accessibility is manually on real devices.
But that requires specialized expertise and a lot of time, both of which are scarce on frontend teams.
That's why many companies now use a combination of automated and manual testing. Automation catches common accessibility issues with low effort from developers.
Manual QA is reserved for trickier problems that require human attention.
There are plenty of resources that dive deep into accessibility principles, so we won't get into that here.
Instead, we'll focus on how to automate accessibility testing with Storybook.
It's a pragmatic approach to finding and fixing most issues you're likely to encounter.
미국 성인의 26%는 최소 한 가지 이상의 장애를 가지고 있습니다.
접근성을 개선하면 현재와 미래의 고객에게 큰 영향을 미칩니다.
접근성은 법적 요건이기도 합니다.
접근성을 가장 정확하게 확인하는 방법은 실제 디바이스에서 수동으로 확인하는 것입니다.
그러나 이를 위해서는 전문 지식과 많은 시간이 필요하며, 프론트엔드 팀에는 이 두 가지가 모두 부족합니다.
그래서 현재 많은 기업이 자동 테스트와 수동 테스트를 함께 사용합니다.
자동화는 개발자가 적은 노력으로 일반적인 접근성 문제를 찾아냅니다.
수동 QA는 사람의 주의가 필요한 까다로운 문제를 위한 것입니다.
접근성 원칙에 대해 자세히 설명하는 리소스가 많이 있으므로 여기서는 다루지 않겠습니다.
대신 스토리북으로 접근성 테스트를 자동화하는 방법에 중점을 두겠습니다.
이는 발생할 수 있는 대부분의 문제를 찾아서 해결할 수 있는 실용적인 접근 방식입니다.
맞는 말이다!
To install the addon, run: yarn add --dev @storybook/addon-a11y.
Then, add '@storybook/addon-a11y' to the addons array in your .storybook/main.js:
// .storybook/main.js
/** @type { import('@storybook/react-vite').StorybookConfig } */
const config = {
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
staticDirs: ['../public'],
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-interactions',
'@storybook/addon-a11y',
],
framework: {
name: '@storybook/react-vite',
options: {},
},
};
export default config;
하라는대로 해주고 npm run storybook을 해주면 Accessibility가 추가된 걸 알 수 있다.
참고 강의
'⚛️ React > 📜 Test Code' 카테고리의 다른 글
[Test Code] 프론트엔드 TDD (0) | 2024.07.16 |
---|---|
[Test Code] cypress cloud / aws amplify에서 테스트 진행하기 (0) | 2024.07.16 |
[Test Code] Cypress로 본격적인 테스트 코드 작성하기(fixture, recoil mocking) (0) | 2024.07.02 |
[Test Code] Cypress로 테스트 하기 (0) | 2024.07.02 |
[Test Code] react-query 공식 문서를 따라 테스트 코드를 하면 안되는 이유 + nock으로 mocking 하기 (0) | 2024.06.26 |