Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

강원대 FE_허윤수 5주차 과제 STEP3 #43

Open
wants to merge 62 commits into
base: sugoring
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
7142c9b
docs: 전체 기능요구사항 정리
Jul 22, 2024
4f5ac5d
docs: step0 기능요구사항 구현
Jul 22, 2024
82bf4ce
Merge pull request #1 from sugoring/step0
sugoring Jul 22, 2024
98f0be9
docs: step1 구현 요구사항 정리
Jul 25, 2024
e13b0c0
feat: MSW를 사용한 상세 및 옵션 API 모킹 핸들러 추가
Jul 25, 2024
c1bc343
feat: 제품 옵션 API 모킹 핸들러 추가
Jul 25, 2024
799ed8a
feat: 제품 상세 정보 API 모킹 핸들러 추가
Jul 25, 2024
cb54939
chore: ESLint 룰 적용 및 설정 추가
Jul 25, 2024
a75ce66
refactor: useGetCategorys 대신 categories.mock.ts를 사용하여 카테고리 데이터 모킹
Jul 25, 2024
8d4c1e4
refactor: useGetProducts에서 pageParam 사용 제거
Jul 25, 2024
0c390c8
fix: 제품 상세 정보 API 호출에서 핸들러 경로와 요청 경로 일치 문제 해결
Jul 25, 2024
394ca8e
feat: 상품 상세 정보 및 옵션 조회 API 모킹 데이터 개선
Jul 25, 2024
8468844
fix: 상품 상세 헤더 컴포넌트 null 체크 추가
Jul 25, 2024
68c6c14
fix: handle undefined price and add error handling in OptionSection c…
Jul 25, 2024
f8c5bdf
fix: handle null detail and remove unused error variable in GoodsInfo…
Jul 25, 2024
ea21d7a
fix: handle 'detail' possibly being null
Jul 25, 2024
b73df39
feat: Implement product options data fetching and mocking
Jul 25, 2024
d879506
ix: 상품 옵션 계산 로직 수정 및 타입 오류 해결
Jul 25, 2024
2b1263b
feat: 제품 상세 정보 조회 기능 구현 및 MSW 핸들러 추가
Jul 25, 2024
40e33f7
feat: 제품 상세 정보 API 모킹 및 useSuspenseQuery 훅 추가
Jul 25, 2024
d4845d6
docs: step1 msw에 대한 브랜치 전환
Jul 25, 2024
c71e5d9
Merge pull request #4 from sugoring/step0
sugoring Jul 25, 2024
2ac29c5
test: useGetProducts 테스트 추가
Jul 25, 2024
a02b848
test: getProductDetail 함수에 대한 단위 테스트 추가
Jul 25, 2024
b5c2014
feat: 현금영수증 입력 컴포넌트(CashReceiptFields) 단위 테스트 추가
Jul 25, 2024
3960572
feat(components/order): 메시지 카드 입력 컴포넌트(MessageCardFields) 단위 테스트 추가
Jul 25, 2024
b278fd5
docs: 단위 테스트 구현 목록
Jul 25, 2024
33d8b9a
test: useGetCategories 훅에 대한 통합 테스트 추가
Jul 25, 2024
4842d82
test: useGetProducts 훅에 대한 통합 테스트 추가
Jul 25, 2024
91ed3a4
test: useGetProductDetail 훅 통합 테스트 구현
Jul 25, 2024
8c30ec4
test: useGetProductOptions 훅 통합 테스트 구현
Jul 25, 2024
c288458
docs: mock 통합 테스트 구현
Jul 25, 2024
1ae0bd3
feat: 현금영수증 입력 컴포넌트 통합 테스트 추가
Jul 25, 2024
9826bdb
test: 메시지 카드 입력 컴포넌트 통합 테스트 추가
Jul 25, 2024
bef9089
Merge pull request #5 from sugoring/step1-test
sugoring Jul 25, 2024
ec2703b
docs: step2 구현 목록 정리
Jul 25, 2024
da347d0
fix: Remove req
Jul 26, 2024
18366d0
feat: 로그인 기능 추가
Jul 26, 2024
689ef92
feat(login): 회원가입 버튼 UI 구현
Jul 26, 2024
526e094
feat(router): 회원가입 페이지 라우팅 설정 및 컴포넌트 추가
Jul 26, 2024
22925be
feat(signup): 회원가입 UI 구현
Jul 26, 2024
90cbb38
feat(api): 회원가입 요청을 처리하는 useRegister 훅 생성
Jul 26, 2024
786b62d
fest: 타입스크립트 환경에서 MSW 초기화 코드 리팩터링
Jul 26, 2024
6764475
feat(auth): implement login functionality and always return success r…
Jul 26, 2024
eec996c
feat(mock): 상품 옵션 API 모의 응답 개선
Jul 26, 2024
e11b867
fix: lint 룰 적용
Jul 26, 2024
1ee72e4
Merge pull request #6 from sugoring/step2-login
sugoring Jul 26, 2024
160d544
docs: step2 기능 요구사항 정리
Jul 26, 2024
557e8cd
feat: HeartIcon 컴포넌트 추가
Jul 26, 2024
195cfbe
feat: OptionSection에 위시리스트 추가 버튼 구현
Jul 26, 2024
8c4cc81
feat: WishList 컴포넌트를 만들어 UI를 구현
Jul 26, 2024
621e7fb
feat: API 호출을 위한 훅을 만들고, 이를 사용하여 위시 목록을 관리함
Jul 26, 2024
fa973c3
feat: 마이 페이지 위시 목록 기능 구현
Jul 26, 2024
ee2e5d2
refactor: React Query 훅 사용 방식 업데이트
Jul 26, 2024
0a61b6e
refactor: 위시리스트 모의 API에 정렬 기능 추가
Jul 26, 2024
121e0ce
feat: MyAccount 컴포넌트에 위시리스트 기능 통합
Jul 26, 2024
608baa3
style: 카드에 그림자, 호버 시 확대 효과 등을 추가
Jul 26, 2024
c93d957
refactor: 파일명 리팩토링
Jul 26, 2024
7170b28
refactor: lint 룰 적용
Jul 26, 2024
dfb1649
feat:useLogin 훅 테스트 환경
Jul 26, 2024
ee5c133
Merge pull request #7 from sugoring/step2-wish
sugoring Jul 26, 2024
54b6753
docs: step3 답변
Jul 26, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 84 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,84 @@
# 카카오 테크 캠퍼스 - 프론트엔드 카카오 선물하기 편
# React Product msw

## 개요

본 저장소는 5주차 과제 (2024-07-22 ~ 2024-07-16)를 위한 로그인 및 위시목록 구현을 담고 있습니다. 상세한 학습 내용은 [Notion 노트](https://www.notion.so/TIL-FE-25dbeb894e884b889eca0fa3e4e13904)에서 확인할 수 있습니다.

---

## 0단계 - 기본 코드 준비

- [x] 기본 코드 준비

---

## 1단계 - Form 부분 테스트 코드 작성하기

### 테스트 기반 환경 구축

- [x] Jest 테스트 환경 설정
- [x] React Testing Library 테스트 환경 설정

### MSW를 사용하여 Mock API 설정

- [x] 상세 API 엔드포인트 추가
- [x] 옵션 API 엔드포인트 추가

### 단위 테스트 작성

- CashReceiptFields 컴포넌트
- [x] 렌더링 테스트: 현금영수증 관련 입력 컴포넌트(체크박스, 셀렉트, input)가 화면에 정상적으로 표시되는지 확인
- [x] 사용자 상호작용 테스트: 체크박스 클릭, 셀렉트 옵션 선택, input 값 입력 등 사용자 입력에 대한 테스트
- MessageCardFields 컴포넌트
- [x] 렌더링 테스트: 메시지 카드 입력 textarea가 화면에 정상적으로 표시되는지 확인
- [x] 사용자 입력 테스트: textarea에 메시지 입력 후 값이 제대로 반영되는지 확인

### 통합 테스트 작성

- 상품 상세 페이지
- [x] useGetCategorys.test
- [x] useGetProducts.test
- [x] useGetProductDetail.test
- [x] useGetProductOptions.test

- 결제하기 페이지
- 현금영수증
- [x] Checkbox 상태에 따른 필드 활성화/비활성화 테스트
- [x] Checkbox가 `true`인 경우 필드 값 입력 테스트
- Form
- [x] 필수 입력 필드 검사
- [x] 입력 값 형식 검사

---

## 2단계 - 로그인 및 회원가입 / 위시 리스트

- 계정 관리
- [x] 로그인 기능 구현
- [x] 회원가입 버튼 UI 구현: 로그인 화면 하단에 회원가입 버튼 배치
- [x] 회원가입 버튼 로직 구현: 버튼 클릭 시 회원가입 페이지로 이동
- [x] 회원가입 UI 구현: 로그인 UI 참고 및 사용
- [x] 회원가입 로직 구현: 회원가입 성공 시 로그인 페이지로 이동 및 성공 메시지 표시

- 상품 상세 페이지
- [x] 위시 등록 버튼 UI 구현
- [x] 위시 등록 버튼 로직 구현: 위시 등록 성공 시 "위시 등록 완료" Alert 메시지 표시

- 마이 페이지
- [x] 위시 목록 리스트 UI 구현: Chakra UI 컴포넌트 활용
- [x] 위시 목록 API 활용: 선물하기 API 노션의 response 데이터 활용
- [x] 위시 목록 리스트 로직 구현: 위시 삭제 성공 시 해당 항목 리스트에서 제거

---

## 3단계 - 질문의 답변을 README에 작성

- **질문 1**: Test code를 작성해보면서 좋았던 점과 아쉬웠던 점에 대해 말해주세요.
- 테스트 코드 작성은 처음에는 막막한 과정이었다. 하지만 404 에러, 모듈을 찾을 수 없는 에러 등 예상치 못한 문제를 파악할 수 있었다. 생성형 AI와 문서를 참고하여 문제를 해결했지만, 스스로 해결했다기보다는 따라 했다는 느낌이 강했다. 테스트 결과 또한 방대하고 복잡하여 해석하기 어려웠다. 이러한 경험은 아쉬움과 답답함을 남겼다. 그러나 이번 경험을 통해 Jest 도구와 기술을 접할 수 있었다는 점은 긍정적이었다. 문제를 파악할 수 있었지만, 아직 스스로 부족하여 충분히 활용하지 못한 점이 아쉬웠다.

- **질문 2**: 스스로 생각했을 때 좋은 컴포넌트란 무엇인지 본인만의 기준을 세우고 설명해 주세요.
- 먼저 `재사용성`을 꼽을 수 있다. 컴포넌트를 재사용할 수 있도록 분리하는 것이 좋다고 생각한다. 카테캠을 하면서 스토리북을 알게 되었는데, 이를 통해 다양한 프로젝트에서 동일한 컴포넌트를 옮겨서 사용할 수 있었고, 통일된 기능과 UI를 구현할 수 있었다. 처음에는 생소하게 느껴졌지만, 한번 알고 나니 다른 문서를 봐도 이해하고 사용할 수 있었다.
- 또한, 나는 아직 함수의 흐름을 이해하는 데 부족하다고 생각해서, 내가 이해할 수 있는 범위의 작은 단위로 쪼개서 구현하는 것을 목표로 하고 있다. 이 점에 대해서는 너무 잘게 쪼갰다는 피드백을 받기도 했지만, 너무 광범위한 역할을 가진 컴포넌트와 스스로 이해할 수 있는 기능을 가진 컴포넌트 사이를 절충해야 한다고 본다. 따라서, 좋은 컴포넌트는 `가독성`이 좋아야 한다고 생각한다. 이를 위해 페이지와 각 페이지의 기능별로 나누고, API와 타입을 기능별로 폴더를 구분하는 것이 좋다고 생각한다.

- **질문 3**: 스스로 생각했을 때 공통 컴포넌트를 만들 때 가장 중요한 요소 2개를 선택하고 이유와 함께 설명해주세요.
- 위 질문에 어느 정도 답변을 작성한 것 같다. `가독성`과 `재사용성`이다. 가독성은 다른 개발자나 내가 다시 사용할 때 쉽게 이해할 수 있도록 해준다. 재사용성을 위해서는 다른 개발자나 내가 다시 사용해도 편리하도록 가독성이 좋아야 한다. 또한, 재사용성을 확보하기 위해서는 전체 프로젝트의 구조를 파악하고 필요한 기능들을 정리하여 구현해야 한다.
92 changes: 75 additions & 17 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 9 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
"build": "craco build",
"test": "craco test",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
"build-storybook": "storybook build",
"lint": "eslint \"./src/**/*.{ts,tsx,js,jsx}\"",
"lint:fix": "eslint --fix \"./src/**/*.{ts,tsx,js,jsx}\"",
"prettier": "prettier --write \"./src/**/*.{ts,tsx,js,jsx}\""
},
"browserslist": {
"production": [
Expand All @@ -27,15 +30,15 @@
"@chakra-ui/react": "^2.8.2",
"@emotion/react": "^11.11.3",
"@emotion/styled": "^11.11.0",
"@tanstack/react-query": "^5.24.1",
"axios": "^1.6.7",
"axios": "^1.7.2",
"framer-motion": "^11.0.6",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-error-boundary": "^4.0.12",
"react-hook-form": "^7.50.1",
"react-intersection-observer": "^9.8.1",
"react-router-dom": "^6.22.1"
"react-router-dom": "^6.22.1",
"zod": "^3.23.8"
},
"devDependencies": {
"@craco/craco": "^7.1.0",
Expand All @@ -49,8 +52,10 @@
"@storybook/react": "^7.6.17",
"@storybook/react-webpack5": "^7.6.17",
"@storybook/test": "^7.6.17",
"@tanstack/react-query": "^5.51.11",
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/react-hooks": "^8.0.1",
"@testing-library/user-event": "^13.5.0",
"@types/jest": "^27.5.2",
"@types/node": "^16.18.82",
Expand Down
15 changes: 15 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,26 @@ import { queryClient } from './api/instance';
import { AuthProvider } from './provider/Auth';
import { Routes } from './routes';

const initializeMocks = async () => {
if (process.env.NODE_ENV === 'development') {
const { worker } = await import('./mocks/browser');
worker.start().then(() => {
console.log('Mock Service Worker is running');
});
}
};

initializeMocks();

const App = () => {
return (
// ChakraProvider wraps the app to provide Chakra UI components and theme
<ChakraProvider>
{/* QueryClientProvider provides React Query context for managing server state */}
<QueryClientProvider client={queryClient}>
{/* AuthProvider provides authentication context to the app */}
<AuthProvider>
{/* Routes component handles the routing of the application */}
<Routes />
</AuthProvider>
</QueryClientProvider>
Expand Down
19 changes: 19 additions & 0 deletions src/api/hooks/useGetCategorys.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { renderHook, waitFor } from '@testing-library/react';

import { CATEGORIES_RESPONSE_DATA } from '../mocks/categories.mock';
import { useGetCategories } from './useGetCategorys';
describe('useGetCategories', () => {
it('should fetch categories correctly', async () => {
const queryClient = new QueryClient();

const wrapper = ({ children }: { children: React.ReactNode }) => (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
);
const { result } = renderHook(() => useGetCategories(), { wrapper });

await waitFor(() => expect(result.current.isSuccess).toBe(true));

expect(result.current.data).toEqual(CATEGORIES_RESPONSE_DATA);
});
});
11 changes: 7 additions & 4 deletions src/api/hooks/useGetCategorys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,21 @@ import { useQuery } from '@tanstack/react-query';

import type { CategoryData } from '@/types';

import { BASE_URL, fetchInstance } from '../instance';
import { CATEGORIES_RESPONSE_DATA, getCategoriesPath } from '../mocks/categories.mock';

// 모킹 데이터 타입 정의
export type CategoryResponseData = CategoryData[];

export const getCategoriesPath = () => `${BASE_URL}/api/categories`;
// React Query에서 사용할 쿼리 키
const categoriesQueryKey = [getCategoriesPath()];

// 모킹 데이터를 반환하는 함수
export const getCategories = async () => {
const response = await fetchInstance.get<CategoryResponseData>(getCategoriesPath());
return response.data;
// 실제 API 호출 대신 모킹 데이터를 반환합니다.
return CATEGORIES_RESPONSE_DATA;
};

// useGetCategories 훅
export const useGetCategories = () =>
useQuery({
queryKey: categoriesQueryKey,
Expand Down
39 changes: 39 additions & 0 deletions src/api/hooks/useGetLogin.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { act, renderHook } from '@testing-library/react-hooks';
import type { ReactNode } from 'react'; // ReactNode 타입 import

import { useLogin } from './useGetLogin';

// QueryClient 생성
const queryClient = new QueryClient();

// QueryClientProvider로 useLogin 훅을 감싼 Wrapper 컴포넌트 생성
const wrapper = ({ children }: { children: ReactNode }) => (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
);

describe('useLogin Hook', () => {
beforeEach(() => {
global.fetch = jest.fn(() =>
Promise.resolve({
ok: true,
json: () => Promise.resolve({ email: '[email protected]', token: 'mockToken' }),
}),
) as jest.Mock;
});
it('should successfully login with valid credentials', async () => {
const { result, waitForNextUpdate } = renderHook(() => useLogin(), { wrapper });

// 로그인 시도
act(() => {
result.current.mutate({ email: '[email protected]', password: 'password123' });
});

// useMutation의 상태가 'success'로 바뀔 때까지 기다림
await waitForNextUpdate();

// 결과 확인
expect(result.current.isSuccess).toBe(true);
expect(result.current.data).toEqual({ email: '[email protected]', token: 'mockToken' });
});
});
Loading