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주차 과제 Step 1 #30

Open
wants to merge 6 commits into
base: anheejeong
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module.exports = {
browser: true,
es2021: true,
node: true,
jest: true,
},
extends: [
'plugin:@typescript-eslint/recommended',
Expand Down
7 changes: 7 additions & 0 deletions craco.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,11 @@ module.exports = {
'@': path.resolve(__dirname, 'src'),
},
},
jest: {
configure: {
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
},
},
},
};
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"start:mock": "cross-env REACT_APP_RUN_MSW=true npm run start",
"start": "craco start",
"build": "craco build",
"test": "craco test",
"test": "craco test --transformIgnorePatterns \"node_modules/(?!axios)/\"",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
},
Expand Down Expand Up @@ -72,16 +72,18 @@
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-simple-import-sort": "^12.0.0",
"eslint-plugin-storybook": "^0.8.0",
"jest": "^29.7.0",
"msw": "^1.3.3",
"prettier": "^3.2.5",
"prop-types": "^15.8.1",
"react-scripts": "5.0.1",
"storybook": "^7.6.17",
"ts-jest": "^29.2.3",
"tsconfig-paths-webpack-plugin": "^4.1.0",
"typescript": "^4.9.5",
"webpack": "^5.90.3"
},
"overrides": {
"react-refresh": "0.11.0"
}
}
}
21 changes: 21 additions & 0 deletions src/components/features/Goods/Detail/Header.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { render, screen, waitFor } from '@testing-library/react';

import { GoodsDetailHeader } from './Header';

test('productId에 맞는 상품 정보 렌더링 확인', async () => {
render(
<QueryClientProvider client={new QueryClient()}> // QueryClientProvider로 감싸서 렌더링
<GoodsDetailHeader productId="3245119" /> // productId를 3245119로 설정하여 전달
</QueryClientProvider>,
);
await waitFor(() => { // 비동기 대기
expect( // 화면에 특정 텍스트가 표시되는지 검증
screen.getByText('[단독각인] 피렌체 1221 에디션 오드코롱 50ml (13종 택1)'), // 텍스트 노드를 검색
).toBeInTheDocument(); // 해당 텍스트가 DOM에 존재하는지 확인
expect(screen.getByText('145000원')).toBeInTheDocument(); // 상품 가격 확인
expect( // 문구 확인
screen.getByText('카톡 친구가 아니어도 선물 코드로 선물 할 수 있어요!'),
).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// CountOptionItem.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { CountOptionItem } from './CountOptionItem';

describe('옵션 디테일 선택 확인', () => {
it('상품 이름과 수량 렌더링 확인', () => {
render(
<CountOptionItem name="[단독각인] 피렌체 1221 에디션 오드코롱 50ml (13종 택1)" value="1" onChange={() => { }} />
);

expect(screen.getByText('[단독각인] 피렌체 1221 에디션 오드코롱 50ml (13종 택1)')).toBeInTheDocument();
expect(screen.getByDisplayValue('1')).toBeInTheDocument();
});

it('input value 변경 시 onChange 호출 확인', () => {
const handleChange = jest.fn();
render(
<CountOptionItem name="[단독각인] 피렌체 1221 에디션 오드코롱 50ml (13종 택1)" value="1" onChange={handleChange} />
);

const input = screen.getByRole('textbox');
fireEvent.change(input, { target: { value: '2' } });

expect(handleChange).toHaveBeenCalledWith('2');
});

it('수량 추가 및 감소 확인', () => {
const handleChange = jest.fn();
render(
<CountOptionItem name="[단독각인] 피렌체 1221 에디션 오드코롱 50ml (13종 택1)" value="1" onChange={handleChange} />
);

const incrementButton = screen.getByRole('button', { name: /수량 1개 추가/i });
const decrementButton = screen.getByRole('button', { name: /수량 1개 감소/i });

fireEvent.click(incrementButton);
expect(handleChange).toHaveBeenCalledWith('2');

fireEvent.click(decrementButton);
expect(handleChange).toHaveBeenCalledWith('1');
});
});
68 changes: 68 additions & 0 deletions src/components/features/Goods/Detail/OptionSection.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { render, screen, fireEvent } from '@testing-library/react';
import { OptionSection } from './OptionSection';
import { useGetProductDetail } from '@/api/hooks/useGetProductDetail';
import { useGetProductOptions } from '@/api/hooks/useGetProductOptions';
import { useAuth } from '@/provider/Auth';
import { BrowserRouter as Router } from 'react-router-dom';

// 모킹(Mocking) : 테스트 환경에서 실제 훅을 사용하지 않고, 필요한 데이터를 반환하도록 설정
jest.mock('@/api/hooks/useGetProductDetail');
jest.mock('@/api/hooks/useGetProductOptions');
jest.mock('@/provider/Auth');

const mockUseGetProductDetail = useGetProductDetail as jest.Mock;
const mockUseGetProductOptions = useGetProductOptions as jest.Mock;
const mockUseAuth = useAuth as jest.Mock;

describe('옵션 선택 확인', () => { // 테스트를 그룹화
beforeEach(() => { // beforeEach는 각 테스트가 실행되기 전에 실행, 모킹된 훅이 반환하는 데이터를 설정
mockUseGetProductDetail.mockReturnValue({
data: { price: 12000 },
});
mockUseGetProductOptions.mockReturnValue({
data: [{ name: '[단독각인] 피렌체 1221 에디션 오드코롱 50ml (13종 택1)' }],
});
mockUseAuth.mockReturnValue(true); // 로그인 상태로 가정
});

it('productDetail과 productOptions의 렌더링 확인', () => {
render(
<Router>
<OptionSection productId="3245119" />
</Router>
);

expect(screen.getByText('총 결제 금액 12000원')).toBeInTheDocument();
expect(screen.getByText('[단독각인] 피렌체 1221 에디션 오드코롱 50ml (13종 택1)')).toBeInTheDocument();
});

it('count 변경 시 totalPrice 변경 확인', async () => {
render(
<Router>
<OptionSection productId="3245119" />
</Router>
);

const input = screen.getByRole('textbox');
fireEvent.change(input, { target: { value: '2' } });

expect(screen.getByText('총 결제 금액')).toHaveTextContent('24000원');
});

it('비로그인시, 버튼 클릭 후 alert 및 navigate 확인', async () => {
mockUseAuth.mockReturnValue(false); // 비로그인 상태로 가정

render(
<Router>
<OptionSection productId="3245119" />
</Router>
);

const button = screen.getByRole('button', { name: /나에게 선물하기/i });
fireEvent.click(button);

expect(window.confirm).toHaveBeenCalledWith(
'로그인이 필요한 메뉴입니다.\n로그인 페이지로 이동하시겠습니까?'
);
});
});
140 changes: 140 additions & 0 deletions src/components/features/Order/OrderForm/OrderForm.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { OrderForm } from '.';
import { OrderHistory } from '@/types';

jest.mock('./OrderInfo', () => ({
OrderFormOrderInfo: jest.fn(() => <div>Order Info Component</div>),
}));
jest.mock('./MessageCard', () => ({
OrderFormMessageCard: jest.fn(() => <div>Message Card Component</div>),
}));
jest.mock('./GoodsInfo', () => ({
GoodsInfo: jest.fn(() => <div>Goods Info Component</div>),
}));

describe('OrderForm Integration Test', () => {
const orderHistory: OrderHistory = {
id: 3245119,
count: 1,
};

const renderOrderForm = () => {
render(<OrderForm orderHistory={orderHistory} />);
};

beforeEach(() => {
jest.spyOn(window, 'alert').mockImplementation(() => { });
});

afterEach(() => {
jest.clearAllMocks();
});

it('현금영수증 Checkbox가 false인 경우 현금영수증 종류, 현금영수증 번호 field가 비활성화 되어있는지 확인', () => {
renderOrderForm();

const cashReceiptCheckbox = screen.getByLabelText('현금영수증 신청');
const cashReceiptType = screen.getByRole('combobox');
const cashReceiptNumber = screen.getByPlaceholderText('(-없이) 숫자만 입력해주세요.');

expect(cashReceiptCheckbox).not.toBeChecked();
expect(cashReceiptType).toBeDisabled();
expect(cashReceiptNumber).toBeDisabled();
});

it('현금영수증 Checkbox가 true인 경우 현금영수증 종류, 번호 field에 값이 입력 되어야 하는지 확인', () => {
renderOrderForm();

const cashReceiptCheckbox = screen.getByLabelText('현금영수증 신청');
fireEvent.click(cashReceiptCheckbox);

const cashReceiptType = screen.getByRole('combobox');
const cashReceiptNumber = screen.getByPlaceholderText('(-없이) 숫자만 입력해주세요.');

expect(cashReceiptCheckbox).toBeChecked();
expect(cashReceiptType).not.toBeDisabled();
expect(cashReceiptNumber).not.toBeDisabled();
});

it('form의 validation 로직이 정상 동작하는지 확인', async () => {
renderOrderForm();

const submitButton = screen.getByRole('button', { name: /제출/i });

// Submit the form with empty fields to check validation errors
fireEvent.click(submitButton);

await screen.findByText('메시지를 입력해주세요.');

// Fill the message field with an invalid value
const messageField = screen.getByLabelText('메시지');
fireEvent.change(messageField, { target: { value: 'a'.repeat(101) } });

fireEvent.click(submitButton);
await screen.findByText('메시지는 100자 이내로 입력해주세요.');

// Fill the form correctly and submit
fireEvent.change(messageField, { target: { value: '유효한 메시지' } });

const cashReceiptCheckbox = screen.getByLabelText('현금영수증 발급');
fireEvent.click(cashReceiptCheckbox);
const cashReceiptNumber = screen.getByLabelText('현금영수증 번호');
fireEvent.change(cashReceiptNumber, { target: { value: '1234567890' } });

fireEvent.click(submitButton);
await screen.findByText('주문이 완료되었습니다.');
});

it('현금영수증 번호에 숫자가 아닌 값 입력 시 에러 메세지 alert 확인', async () => {
renderOrderForm();

fireEvent.click(screen.getByLabelText('현금영수증 신청'));
fireEvent.change(screen.getByPlaceholderText('(-없이) 숫자만 입력해주세요.'), {
target: { value: '숫자' },
});

fireEvent.submit(screen.getByRole('form'));

await waitFor(() => {
expect(window.alert).toHaveBeenCalledWith('현금영수증 번호는 숫자로만 입력해주세요.');
});
});

test('현금영수증 번호를 미입력시 에러 메세지 alert 확인F', async () => {
renderOrderForm();

const cashReceiptCheckbox = screen.getByLabelText('현금영수증 신청') as HTMLInputElement;

fireEvent.click(cashReceiptCheckbox);

fireEvent.submit(screen.getByRole('form'));

await waitFor(() => {
expect(window.alert).toHaveBeenCalledWith('현금영수증 번호를 입력해주세요.');
});
});

it('메시지가 100자를 초과 시 에러 메세지 확인', async () => {
renderOrderForm();

fireEvent.change(screen.getByPlaceholderText('선물과 함께 보낼 메시지를 적어보세요'), {
target: { value: '메세지'.repeat(35) },
});

fireEvent.submit(screen.getByRole('form'));

await waitFor(() => {
expect(window.alert).toHaveBeenCalledWith('메시지는 100자 이내로 입력해주세요.');
});
});

it('메시지를 미입력시 에러 메세지 alert.', async () => {
renderOrderForm();

fireEvent.submit(screen.getByRole('form'));

await waitFor(() => {
expect(window.alert).toHaveBeenCalledWith('메시지를 입력해주세요.');
});
});
});
Loading