diff --git a/src/components/pages/user/profile/profile-edit-modal/index.test.tsx b/src/components/pages/user/profile/profile-edit-modal/index.test.tsx
new file mode 100644
index 00000000..7553a6ec
--- /dev/null
+++ b/src/components/pages/user/profile/profile-edit-modal/index.test.tsx
@@ -0,0 +1,257 @@
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+import { act, render, screen, waitFor } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+
+import { ModalProvider } from '@/components/ui';
+import { server } from '@/mock/server';
+import { mockUserItems } from '@/mock/service/user/user-mock';
+
+import { ProfileEditModal } from '.';
+
+const createTestQueryClient = () =>
+ new QueryClient({
+ defaultOptions: {
+ queries: {
+ retry: false,
+ gcTime: Infinity,
+ },
+ mutations: {
+ retry: false,
+ },
+ },
+ });
+
+const renderWithProviders = async (component: React.ReactElement) => {
+ const testQueryClient = createTestQueryClient();
+ let renderResult;
+
+ await act(async () => {
+ renderResult = render(
+
+ {component}
+ ,
+ );
+ });
+
+ return renderResult;
+};
+
+describe('ProfileEditModal 테스트', () => {
+ const mockUser = mockUserItems[0];
+
+ beforeAll(() => {
+ server.listen();
+ });
+
+ afterEach(() => {
+ server.resetHandlers();
+ });
+
+ afterAll(() => {
+ server.close();
+ });
+
+ describe('렌더링 테스트', () => {
+ test('프로필 수정 모달이 정상적으로 렌더링 된다.', async () => {
+ await renderWithProviders();
+
+ expect(screen.getByRole('heading', { level: 2, name: '프로필 수정' })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: '취소' })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: '수정하기' })).toBeInTheDocument();
+ });
+
+ test('초기값이 user 데이터로 설정된다.', async () => {
+ await renderWithProviders();
+
+ expect(screen.getByDisplayValue(mockUser.nickName)).toBeInTheDocument();
+ expect(screen.getByDisplayValue(mockUser.profileMessage)).toBeInTheDocument();
+ expect(screen.getByDisplayValue(mockUser.mbti)).toBeInTheDocument();
+ });
+ });
+
+ describe('필드 입력 테스트', () => {
+ test('닉네임 입력이 정상적으로 동작한다.', async () => {
+ const user = userEvent.setup();
+ await renderWithProviders();
+
+ const nickNameInput = screen.getByDisplayValue(mockUser.nickName);
+ await user.clear(nickNameInput);
+ await user.type(nickNameInput, '새로운 닉네임');
+
+ expect(nickNameInput).toHaveValue('새로운 닉네임');
+ });
+
+ test('프로필 메시지 입력이 정상적으로 동작한다.', async () => {
+ const user = userEvent.setup();
+ await renderWithProviders();
+
+ const profileMessageInput = screen.getByDisplayValue(mockUser.profileMessage);
+ await user.clear(profileMessageInput);
+ await user.type(profileMessageInput, '새로운 소개글');
+
+ expect(profileMessageInput).toHaveValue('새로운 소개글');
+ });
+
+ test('MBTI 메시지 입력이 정상적으로 동작한다.', async () => {
+ const user = userEvent.setup();
+ await renderWithProviders();
+
+ const mbtiInput = screen.getByDisplayValue(mockUser.mbti);
+ await user.clear(mbtiInput);
+ await user.type(mbtiInput, 'ISTJ');
+
+ expect(mbtiInput).toHaveValue('ISTJ');
+ });
+ });
+
+ describe('유효성 검사 테스트', () => {
+ describe('닉네임 유효성 검사 테스트', () => {
+ test('닉네임이 2글자 미만이면 에러 메시지가 표시된다.', async () => {
+ const user = userEvent.setup();
+ await renderWithProviders();
+
+ const nickNameInput = screen.getByDisplayValue(mockUser.nickName);
+ await user.clear(nickNameInput);
+ await user.type(nickNameInput, 'a');
+
+ await waitFor(() => {
+ expect(screen.getByText('닉네임은 2글자 이상이어야 합니다.')).toBeInTheDocument();
+ });
+ });
+
+ test('닉네임이 20글자를 초과하면 에러 메시지가 표시된다.', async () => {
+ const user = userEvent.setup();
+ await renderWithProviders();
+
+ const nickNameInput = screen.getByDisplayValue(mockUser.nickName);
+ await user.clear(nickNameInput);
+ await user.type(nickNameInput, 'a'.repeat(21));
+
+ await waitFor(() => {
+ expect(screen.getByText('닉네임은 20글자 이하여야 합니다.')).toBeInTheDocument();
+ });
+ });
+ });
+
+ describe('프로필 메시지 유효성 검사 테스트', () => {
+ test('프로필 메시지가 20글자를 초과하면 에러 메시지가 표시된다.', async () => {
+ const user = userEvent.setup();
+ await renderWithProviders();
+
+ const messageInput = screen.getByDisplayValue(mockUser.profileMessage);
+ await user.clear(messageInput);
+ await user.type(messageInput, 'a'.repeat(21));
+
+ await waitFor(() => {
+ expect(screen.getByText('소개글은 20글자까지 작성 가능합니다.')).toBeInTheDocument();
+ });
+ });
+ });
+
+ describe('MBTI 유효성 검사 테스트', () => {
+ test('유효하지 않은 MBTI 입력 시 에러 메시지가 표시된다.', async () => {
+ const user = userEvent.setup();
+ await renderWithProviders();
+
+ const mbtiInput = screen.getByDisplayValue(mockUser.mbti);
+ await user.clear(mbtiInput);
+ await user.type(mbtiInput, 'ab');
+
+ await waitFor(() => {
+ expect(screen.getByText('유효한 MBTI가 아닙니다.')).toBeInTheDocument();
+ });
+ });
+
+ test('대/소문자 관계없이 MBTI 입력이 가능하다.', async () => {
+ const user = userEvent.setup();
+ await renderWithProviders();
+
+ const mbtiInput = screen.getByDisplayValue(mockUser.mbti);
+ await user.clear(mbtiInput);
+ await user.type(mbtiInput, 'IstJ');
+
+ await waitFor(() => {
+ expect(screen.queryByText('유효한 MBTI가 아닙니다.')).not.toBeInTheDocument();
+ });
+ });
+
+ test('유효한 MBTI를 입력 중이라면 Blur 되기 전까지 에러 메시지가 나타나지 않는다.', async () => {
+ const user = userEvent.setup();
+ await renderWithProviders();
+
+ const mbtiInput = screen.getByDisplayValue(mockUser.mbti);
+ await user.clear(mbtiInput);
+ await user.type(mbtiInput, 'is');
+
+ await waitFor(() => {
+ expect(screen.queryByText('유효한 MBTI가 아닙니다.')).not.toBeInTheDocument();
+ });
+ });
+
+ test('MBTI가 4글자가 아닌 상태에서 blur 시 에러 메시지가 표시된다.', async () => {
+ const user = userEvent.setup();
+ await renderWithProviders();
+
+ const mbtiInput = screen.getByDisplayValue(mockUser.mbti);
+ await user.clear(mbtiInput);
+ await user.type(mbtiInput, 'is');
+ await user.tab();
+
+ await waitFor(() => {
+ expect(screen.queryByText('유효한 MBTI가 아닙니다.')).toBeInTheDocument();
+ });
+ });
+
+ test('유효한 MBTI를 입력 후 blur 시 에러 메시지가 표시되지 않는다.', async () => {
+ const user = userEvent.setup();
+ await renderWithProviders();
+
+ const mbtiInput = screen.getByDisplayValue(mockUser.mbti);
+ await user.clear(mbtiInput);
+ await user.type(mbtiInput, 'istj');
+ await user.tab();
+
+ await waitFor(() => {
+ expect(screen.queryByText('유효한 MBTI가 아닙니다.')).not.toBeInTheDocument();
+ });
+ });
+ });
+
+ describe('제출 버튼 상태 테스트', () => {
+ test('유효성 검사 성공 시 제출 버튼이 활성화 된다.', async () => {
+ const user = userEvent.setup();
+ await renderWithProviders();
+
+ const nickNameInput = screen.getByDisplayValue(mockUser.nickName);
+ await user.clear(nickNameInput);
+ await user.type(nickNameInput, '새로운 닉네임');
+
+ const messageInput = screen.getByDisplayValue(mockUser.profileMessage);
+ await user.clear(messageInput);
+ await user.type(messageInput, '새로운 소개글');
+
+ const mbtiInput = screen.getByDisplayValue(mockUser.mbti);
+ await user.clear(mbtiInput);
+ await user.type(mbtiInput, 'ISTJ');
+
+ await waitFor(() => {
+ const submitButton = screen.getByText('수정하기');
+ expect(submitButton).not.toBeDisabled();
+ });
+ });
+ test('유효성 검사 실패 시 제출 버튼이 비활성화 된다.', async () => {
+ const user = userEvent.setup();
+ await renderWithProviders();
+
+ const nickNameInput = screen.getByDisplayValue(mockUser.nickName);
+ await user.clear(nickNameInput);
+ await user.type(nickNameInput, 'a');
+
+ await waitFor(() => {
+ const submitButton = screen.getByText('수정하기');
+ expect(submitButton).toBeDisabled();
+ });
+ });
+ });
+ });
+});
diff --git a/src/lib/schema/mypage.ts b/src/lib/schema/mypage.ts
index c3e245c9..aac29b1e 100644
--- a/src/lib/schema/mypage.ts
+++ b/src/lib/schema/mypage.ts
@@ -24,7 +24,7 @@ export const mbtiOnChangeSchema = z.string().refine(
if (val.length === 4 && !['J', 'P', 'j', 'p'].includes(val[3])) return false;
return true;
},
- { message: '유효한 MBTI가 아닙니다' },
+ { message: '유효한 MBTI가 아닙니다.' },
);
export const mbtiOnBlurSchema = z.string().refine(
@@ -32,5 +32,5 @@ export const mbtiOnBlurSchema = z.string().refine(
if (val === '') return true;
return val.length === 4 && /^[IEie][SNsn][TFtf][JPjp]$/.test(val);
},
- { message: 'MBTI 4글자를 모두 입력해주세요' },
+ { message: '유효한 MBTI가 아닙니다.' },
);