Skip to content

Commit

Permalink
Merge pull request #5 from jch1223/create-custom-hooks
Browse files Browse the repository at this point in the history
Create custom hooks
  • Loading branch information
jch1223 authored Jan 16, 2025
2 parents 785f149 + 69f7815 commit 6b11121
Show file tree
Hide file tree
Showing 35 changed files with 646 additions and 220 deletions.
291 changes: 279 additions & 12 deletions src/advanced/__tests__/advanced.test.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { fireEvent, render, screen, within } from '@testing-library/react';
import { useState } from 'react';
import { describe, expect, test } from 'vitest';
import { fireEvent, render, renderHook, screen, within } from '@testing-library/react';
import { act, useState } from 'react';
import { beforeEach, describe, expect, test } from 'vitest';

import { useForm, useLocalStorage } from '@/refactoring/hooks';
import { AdminPage } from '@/refactoring/pages/Admin/AdminPage';
import { useCouponForm } from '@/refactoring/pages/Admin/CouponManagement/components/CouponAddForm/hooks/useCouponForm';
import { useProductForm } from '@/refactoring/pages/Admin/ProductManagement/components/ProductEditor/ProductUpdateForm/hooks/useProductForm';
import { CartPage } from '@/refactoring/pages/Cart/CartPage';
import type { Coupon, Product } from '@/types';
import { useCartLocalStorage } from '@/refactoring/pages/Cart/hooks/useCartLocalStorage';
import type { Coupon, Discount, Product } from '@/types';

const mockProducts: Product[] = [
{
Expand Down Expand Up @@ -72,7 +76,7 @@ const TestAdminPage = () => {
};

describe('advanced > ', () => {
describe.only('시나리오 테스트 > ', () => {
describe('시나리오 테스트 > ', () => {
test('장바구니 페이지 테스트 > ', async () => {
render(<CartPage products={mockProducts} coupons={mockCoupons} />);
const product1 = screen.getByTestId('product-p1');
Expand Down Expand Up @@ -136,15 +140,15 @@ describe('advanced > ', () => {

// 10. 쿠폰 적용하기
const couponSelect = screen.getByRole('combobox');
fireEvent.change(couponSelect, { target: { value: '1' } }); // 10% 할인 쿠폰 선택
fireEvent.change(couponSelect, { target: { value: mockCoupons[1].code } }); // 10% 할인 쿠폰 선택

// 11. 할인율 계산
expect(screen.getByText('상품 금액: 700,000원')).toBeInTheDocument();
expect(screen.getByText('할인 금액: 169,000원')).toBeInTheDocument();
expect(screen.getByText('최종 결제 금액: 531,000원')).toBeInTheDocument();

// 12. 다른 할인 쿠폰 적용하기
fireEvent.change(couponSelect, { target: { value: '0' } }); // 5000원 할인 쿠폰
fireEvent.change(couponSelect, { target: { value: mockCoupons[0].code } }); // 5000원 할인 쿠폰
expect(screen.getByText('상품 금액: 700,000원')).toBeInTheDocument();
expect(screen.getByText('할인 금액: 115,000원')).toBeInTheDocument();
expect(screen.getByText('최종 결제 금액: 585,000원')).toBeInTheDocument();
Expand Down Expand Up @@ -219,13 +223,276 @@ describe('advanced > ', () => {
});
});

describe('자유롭게 작성해보세요.', () => {
test('새로운 유틸 함수를 만든 후에 테스트 코드를 작성해서 실행해보세요', () => {
expect(true).toBe(false);
describe('useForm > ', () => {
test('초기 상태로 주어진 값으로 초기화되어야 합니다', () => {
const initialState = { name: 'John', age: 30 };
const { result } = renderHook(() => useForm(initialState));

expect(result.current.value).toEqual(initialState);
});

test('값을 올바르게 업데이트해야 합니다', () => {
const initialState = { name: 'John', age: 30 };
const { result } = renderHook(() => useForm(initialState));

act(() => {
result.current.updateValue('name', 'Doe');
});

expect(result.current.value).toEqual({ name: 'Doe', age: 30 });
});

test('init이 호출되면 초기 상태로 리셋되어야 합니다', () => {
const initialState = { name: 'John', age: 30 };
const { result } = renderHook(() => useForm(initialState));

act(() => {
result.current.updateValue('name', 'Doe');
result.current.init();
});

expect(result.current.value).toEqual(initialState);
});
});

describe('useCouponForm > ', () => {
test('기본 값으로 초기화되어야 한다', () => {
const { result } = renderHook(() => useCouponForm());

expect(result.current.editingCoupon).toEqual({
name: '',
code: '',
discountType: 'amount',
discountValue: 0
});
});

test('이름이 올바르게 업데이트되어야 한다', () => {
const { result } = renderHook(() => useCouponForm());

act(() => {
result.current.updateName('New Coupon Name');
});

expect(result.current.editingCoupon.name).toBe('New Coupon Name');
});

test('코드가 올바르게 업데이트되어야 한다', () => {
const { result } = renderHook(() => useCouponForm());

act(() => {
result.current.updateCode('NEWCODE123');
});

expect(result.current.editingCoupon.code).toBe('NEWCODE123');
});

test('할인 유형이 올바르게 업데이트되어야 한다', () => {
const { result } = renderHook(() => useCouponForm());

act(() => {
result.current.updateDiscountType('percentage');
});

expect(result.current.editingCoupon.discountType).toBe('percentage');
});

test('할인 값이 올바르게 업데이트되어야 한다', () => {
const { result } = renderHook(() => useCouponForm());

act(() => {
result.current.updateDiscountValue(50);
});

expect(result.current.editingCoupon.discountValue).toBe(50);
});
});

describe('useProductForm > ', () => {
const initialProduct: Product = {
id: '1',
name: 'Test Product',
price: 100,
stock: 10,
discounts: []
};

test('초기 제품 데이터로 올바르게 초기화되어야 한다', () => {
const { result } = renderHook(() => useProductForm({ initProduct: initialProduct }));
expect(result.current.editingProduct).toEqual(initialProduct);
});

test('제품 이름을 업데이트할 수 있어야 한다', () => {
const { result } = renderHook(() => useProductForm({ initProduct: initialProduct }));
act(() => {
result.current.updateName('Updated Product');
});
expect(result.current.editingProduct.name).toBe('Updated Product');
});

test('제품 가격을 업데이트할 수 있어야 한다', () => {
const { result } = renderHook(() => useProductForm({ initProduct: initialProduct }));
act(() => {
result.current.updatePrice(200);
});
expect(result.current.editingProduct.price).toBe(200);
});

test('제품 재고를 업데이트할 수 있어야 한다', () => {
const { result } = renderHook(() => useProductForm({ initProduct: initialProduct }));
act(() => {
result.current.updateStock(20);
});
expect(result.current.editingProduct.stock).toBe(20);
});

test('제품 할인 정보를 업데이트할 수 있어야 한다', () => {
const newDiscounts: Discount[] = [{ quantity: 10, rate: 0.1 }];
const { result } = renderHook(() => useProductForm({ initProduct: initialProduct }));
act(() => {
result.current.updateDiscounts(newDiscounts);
});
expect(result.current.editingProduct.discounts).toEqual(newDiscounts);
});
});

describe('useLocalStorage > ', () => {
const key = 'testKey';
const initialValue = 'initialValue';

beforeEach(() => {
window.localStorage.clear();
});

test('값이 저장되어 있지 않으면 초기 값을 반환해야 합니다.', () => {
const { result } = renderHook(() => useLocalStorage<string>({ key, initialValue }));

expect(result.current[0]).toBe(initialValue);
});

test('값을 저장하고 검색할 수 있어야 합니다.', () => {
const { result } = renderHook(() => useLocalStorage<string>({ key, initialValue }));

act(() => {
result.current[1]('newValue');
});

expect(result.current[0]).toBe('newValue');
expect(localStorage.getItem(key)).toBe(JSON.stringify('newValue'));
});

test('localStorage에서 저장되어 있다면, 저장된 값을 가지고 와야 합니다.', () => {
localStorage.setItem(key, JSON.stringify('storedValue'));

const { result } = renderHook(() => useLocalStorage<string>({ key, initialValue }));

expect(result.current[0]).toBe('storedValue');
});

test('JSON 파싱 오류를 잘 처리해야 합니다.', () => {
localStorage.setItem(key, '{');

const { result } = renderHook(() => useLocalStorage<string>({ key, initialValue }));

expect(result.current[0]).toBe(initialValue);
});
});
});

describe('useCartLocalStorage > ', () => {
const sampleProduct: Product = { id: '1', name: 'Sample Product', price: 100, stock: 10, discounts: [] };
const sampleCoupon: Coupon = {
name: '10% 할인 쿠폰',
code: 'DISCOUNT10',
discountType: 'percentage',
discountValue: 10
};

beforeEach(() => {
window.localStorage.clear();
});

test('상품을 장바구니에 추가할 수 있어야 합니다.', () => {
const { result } = renderHook(() => useCartLocalStorage());

act(() => {
result.current.addToCart(sampleProduct);
});

expect(result.current.cart).toHaveLength(1);
expect(result.current.cart[0].product).toEqual(sampleProduct);
});

test('이미 존재하는 상품의 수량이 증가해야 합니다.', () => {
const { result } = renderHook(() => useCartLocalStorage());

act(() => {
result.current.addToCart(sampleProduct);
});

act(() => {
result.current.addToCart(sampleProduct);
});

expect(result.current.cart[0].quantity).toBe(2);
});

test('장바구니에서 상품을 제거할 수 있어야 합니다.', () => {
const { result } = renderHook(() => useCartLocalStorage());

act(() => {
result.current.addToCart(sampleProduct);
});

expect(result.current.cart).toHaveLength(1);

act(() => {
result.current.removeFromCart(sampleProduct.id);
});

expect(result.current.cart).toHaveLength(0);
});

test('상품의 수량을 업데이트할 수 있어야 합니다.', () => {
const { result } = renderHook(() => useCartLocalStorage());

act(() => {
result.current.addToCart(sampleProduct);
});

act(() => {
result.current.updateQuantity(sampleProduct.id, 5);
});

expect(result.current.cart[0].quantity).toBe(5);
});

test('쿠폰을 적용할 수 있어야 합니다.', () => {
const { result } = renderHook(() => useCartLocalStorage());

act(() => {
result.current.applyCoupon(sampleCoupon);
});

expect(result.current.selectedCoupon).toEqual(sampleCoupon);
});

test('총액이 올바르게 계산되어야 합니다.', () => {
const { result } = renderHook(() => useCartLocalStorage());

act(() => {
result.current.addToCart(sampleProduct);
});

act(() => {
result.current.applyCoupon(sampleCoupon);
});

test('새로운 hook 함수르 만든 후에 테스트 코드를 작성해서 실행해보세요', () => {
expect(true).toBe(false);
const total = result.current.calculateTotal();
expect(total).toEqual({
totalAfterDiscount: 90,
totalBeforeDiscount: 100,
totalDiscount: 10
});
});
});
15 changes: 12 additions & 3 deletions src/basic/__tests__/basic.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,15 +139,15 @@ describe('basic > ', () => {

// 10. 쿠폰 적용하기
const couponSelect = screen.getByRole('combobox');
fireEvent.change(couponSelect, { target: { value: '1' } }); // 10% 할인 쿠폰 선택
fireEvent.change(couponSelect, { target: { value: mockCoupons[1].code } }); // 10% 할인 쿠폰 선택

// 11. 할인율 계산
expect(screen.getByText('상품 금액: 700,000원')).toBeInTheDocument();
expect(screen.getByText('할인 금액: 169,000원')).toBeInTheDocument();
expect(screen.getByText('최종 결제 금액: 531,000원')).toBeInTheDocument();

// 12. 다른 할인 쿠폰 적용하기
fireEvent.change(couponSelect, { target: { value: '0' } }); // 5000원 할인 쿠폰
fireEvent.change(couponSelect, { target: { value: mockCoupons[0].code } }); // 5000원 할인 쿠폰
expect(screen.getByText('상품 금액: 700,000원')).toBeInTheDocument();
expect(screen.getByText('할인 금액: 115,000원')).toBeInTheDocument();
expect(screen.getByText('최종 결제 금액: 585,000원')).toBeInTheDocument();
Expand Down Expand Up @@ -453,11 +453,14 @@ describe('basic > ', () => {
expect(result.current.cart).toHaveLength(0);
});

test('제품 수량을 업데이트해야 합니다', () => {
test('제품 수량을 업데이트해야 합니다', async () => {
const { result } = renderHook(() => useCart());

act(() => {
result.current.addToCart(testProduct);
});

act(() => {
result.current.updateQuantity(testProduct.id, 5);
});

Expand All @@ -479,7 +482,13 @@ describe('basic > ', () => {

act(() => {
result.current.addToCart(testProduct);
});

act(() => {
result.current.updateQuantity(testProduct.id, 2);
});

act(() => {
result.current.applyCoupon(testCoupon);
});

Expand Down
Empty file.
Empty file.
Empty file.
Empty file.
2 changes: 2 additions & 0 deletions src/refactoring/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
export * from './useCoupon';
export * from './useProduct';
export * from './useForm';
export * from './useLocalstorage';
15 changes: 15 additions & 0 deletions src/refactoring/hooks/useForm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { useState } from 'react';

export const useForm = <T>(initialState: T) => {
const [value, setValue] = useState<T>(initialState);

const init = () => {
setValue(initialState);
};

const updateValue = (key: keyof T, value: T[keyof T]) => {
setValue(prev => ({ ...prev, [key]: value }));
};

return { value, updateValue, init };
};
Loading

0 comments on commit 6b11121

Please sign in to comment.