Skip to content
Merged
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
2 changes: 1 addition & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const createJestConfig = nextJest({

const customJestConfig = {
coverageProvider: 'v8',
testEnvironment: 'jsdom',
testEnvironment: 'jest-fixed-jsdom',
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"axios": "^1.13.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"jest-fixed-jsdom": "^0.0.11",
"motion": "^12.23.24",
"next": "16.0.7",
"react": "19.2.1",
Expand Down Expand Up @@ -89,7 +90,7 @@
"jest": "^30.2.0",
"jest-environment-jsdom": "^30.2.0",
"lint-staged": "^16.2.4",
"msw": "^2.11.5",
"msw": "^2.11.2",
"orval": "^7.13.2",
"prettier": "^3.6.2",
"prettier-plugin-tailwindcss": "^0.7.0",
Expand Down
76 changes: 52 additions & 24 deletions pnpm-lock.yaml

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

108 changes: 108 additions & 0 deletions src/app/(user)/profile/[userId]/page.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { act, render, screen } from '@testing-library/react';
import { http, HttpResponse } from 'msw';

import { formatISO } from '@/lib/formatDateTime';
import { server } from '@/mock/server';
import { createMockSuccessResponse } from '@/mock/service/common/common-mock';
import { mockUserItems } from '@/mock/service/user/users-mock';

import ProfilePage from './page';

const createTestQueryClient = () =>
new QueryClient({
defaultOptions: {
queries: {
retry: false, // 테스트에서 재시도 비활성화
gcTime: Infinity, // Jest 환경에서 카비지 컬렉션을 위한 타이머 생성 방지
},
},
});

const renderWithQueryClient = async (component: React.ReactElement) => {
// 각 테스트마다 새로운 QueryClient 생성하여 독립적인 상태 유지 (useState 없이)
const testQueryClient = createTestQueryClient();
let renderResult;
await act(async () => {
renderResult = render(
<QueryClientProvider client={testQueryClient}>{component}</QueryClientProvider>,
);
});

return renderResult;
};

describe('프로필 페이지 테스트', () => {
beforeAll(() => {
server.listen();
});
afterEach(() => {
server.resetHandlers();
});
afterAll(() => server.close());

test('프로필 정보가 올바르게 표시되는지 테스트', async () => {
const id = 2;
const user = mockUserItems.find((item) => item.userId === id)!;

await renderWithQueryClient(<ProfilePage params={Promise.resolve({ userId: String(id) })} />);

await screen.findByRole('img', { name: '프로필 이미지' });

expect(screen.getByRole('heading', { level: 1 })).toHaveTextContent(user.nickName);
expect(screen.getByText(user.profileMessage)).toBeInTheDocument();

expect(screen.getByText('팔로워')).toBeInTheDocument();
expect(screen.getByText(user.followersCnt.toLocaleString())).toBeInTheDocument();
expect(screen.getByText('팔로잉')).toBeInTheDocument();
expect(screen.getByText(user.followeesCnt.toLocaleString())).toBeInTheDocument();

expect(screen.getByText('MBTI')).toBeInTheDocument();
expect(screen.getByText(user.mbti)).toBeInTheDocument();
expect(screen.getByText('가입 일자')).toBeInTheDocument();
expect(screen.getByText(formatISO(user.createdAt))).toBeInTheDocument();
expect(screen.getByText('모임 참여')).toBeInTheDocument();
expect(screen.getByText(`${user.groupJoinedCnt}회`));
expect(screen.getByText('모임 생성')).toBeInTheDocument();
expect(screen.getByText(`${user.groupCreatedCnt}회`));
});

test('팔로우 중이 아니면 팔로우 하기 버튼이 보이는지 테스트', async () => {
server.use(
http.get('*/users/2', () => {
return HttpResponse.json(
createMockSuccessResponse({
...mockUserItems[1],
isFollowing: false,
}),
);
}),
);

await renderWithQueryClient(<ProfilePage params={Promise.resolve({ userId: '2' })} />);

await screen.findByRole('img', { name: '프로필 이미지' });

expect(screen.getByRole('button', { name: '팔로우 하기' })).toBeInTheDocument();
expect(screen.queryByRole('button', { name: '팔로우 취소' })).not.toBeInTheDocument();
});

test('팔로우 중이면 팔로우 취소 버튼이 보이는지 테스트', async () => {
server.use(
http.get('*/users/2', () => {
return HttpResponse.json(
createMockSuccessResponse({
...mockUserItems[1],
isFollowing: true,
}),
);
}),
);
await renderWithQueryClient(<ProfilePage params={Promise.resolve({ userId: '2' })} />);

await screen.findByRole('img', { name: '프로필 이미지' });

expect(screen.getByRole('button', { name: '팔로우 취소' })).toBeInTheDocument();
expect(screen.queryByRole('button', { name: '팔로우 하기' })).not.toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Link from 'next/link';

import { Icon } from '@/components/icon';
import { formatTimeAgo } from '@/lib/format-time-ago';
import { formatTimeAgo } from '@/lib/formatDateTime';
import { Notification, NotificationType } from '@/types/service/notification';

interface Props {
Expand Down
4 changes: 2 additions & 2 deletions src/components/pages/user/profile/profile-card/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ export const ProfileCard = ({ user }: Props) => {
return (
<div className='flex-col-center mb-6'>
<div className='relative mb-3 size-24 overflow-hidden rounded-full'>
<Image alt='image' fill objectFit='cover' src={profileImage} />
<Image className='object-cover' alt='프로필 이미지' fill src={profileImage} />
</div>
<h2 className='text-text-xl-bold text-gray-900'>{nickName}</h2>
<h1 className='text-text-xl-bold text-gray-900'>{nickName}</h1>
<p className='text-text-sm-medium text-gray-600'>{profileMessage}</p>
</div>
);
Expand Down
Loading