diff --git a/src/app/(auth)/components/LoginForm.test.tsx b/src/app/(auth)/components/LoginForm.test.tsx new file mode 100644 index 0000000..2b292f2 --- /dev/null +++ b/src/app/(auth)/components/LoginForm.test.tsx @@ -0,0 +1,80 @@ +import { render, screen, fireEvent, waitFor } from "@testing-library/react"; +import { vi, Mock } from "vitest"; + +import { useAuth } from "@/hooks/useAuth"; + +import LoginForm from "./LoginForm"; + +// Mock the useAuth hook +vi.mock("@/hooks/useAuth"); + +describe("LoginForm", () => { + const mockLoginMutation = { + mutate: vi.fn(), + isLoading: false, + }; + + beforeEach(() => { + (useAuth as Mock).mockReturnValue({ + loginMutation: mockLoginMutation, + }); + }); + + it("모든 입력 필드와 로그인 버튼이 렌더링되어야 한다.", () => { + render(); + + expect(screen.getByPlaceholderText("이메일")).toBeInTheDocument(); + expect(screen.getByPlaceholderText("비밀번호")).toBeInTheDocument(); + expect(screen.getByRole("button", { name: "로그인" })).toBeInTheDocument(); + }); + + describe("유효성 검사", () => { + it("이메일 및 비밀번호 필드 유효성 검사", async () => { + render(); + fireEvent.blur(screen.getByPlaceholderText("이메일")); + fireEvent.blur(screen.getByPlaceholderText("비밀번호")); + + await waitFor(() => { + expect(screen.getByText("유효한 이메일 주소를 입력해주세요.")).toBeInTheDocument(); + expect(screen.getByText("비밀번호는 최소 8자 이상이어야 합니다.")).toBeInTheDocument(); + }); + }); + + it("이메일과 비밀번호로 로그인합니다.", async () => { + render(); + + fireEvent.change(screen.getByPlaceholderText("이메일"), { + target: { value: "test@example.com" }, + }); + fireEvent.change(screen.getByPlaceholderText("비밀번호"), { + target: { value: "password123" }, + }); + fireEvent.click(screen.getByRole("button", { name: "로그인" })); + + await waitFor(() => { + expect(mockLoginMutation.mutate).toHaveBeenCalledWith({ + email: "test@example.com", + password: "password123", + }); + }); + }); + + it("제출 시 로딩 중인 스피너를 표시합니다.", async () => { + mockLoginMutation.isLoading = true; + render(); + + fireEvent.change(screen.getByPlaceholderText("이메일"), { + target: { value: "test@example.com" }, + }); + fireEvent.change(screen.getByPlaceholderText("비밀번호"), { + target: { value: "password123" }, + }); + + fireEvent.click(screen.getByRole("button", { name: "로그인" })); + + await waitFor(() => { + expect(screen.getByTestId("spinner")).toBeInTheDocument(); + }); + }); + }); +}); diff --git a/src/app/(auth)/components/SignupForm.test.tsx b/src/app/(auth)/components/SignupForm.test.tsx index 1a83c5c..272f84d 100644 --- a/src/app/(auth)/components/SignupForm.test.tsx +++ b/src/app/(auth)/components/SignupForm.test.tsx @@ -2,11 +2,14 @@ import { render, screen, waitFor } from "@testing-library/react"; import { vi, Mock } from "vitest"; import userEvent from "@testing-library/user-event"; -import { useSignup } from "@/hook/useSignup"; +import { useAuth } from "@/hooks/useAuth"; import SignupForm from "./SignupForm"; // Mock modules + +vi.mock("@/hooks/useAuth"); + vi.mock("next/navigation", () => ({ useRouter: () => ({ push: vi.fn(), @@ -25,17 +28,17 @@ vi.mock("react-toastify", () => ({ })); describe("SignupForm", () => { - const mockMutate = vi.fn(); + const mockSignupMutation = { mutate: vi.fn() }; beforeEach(() => { vi.clearAllMocks(); - (useSignup as Mock).mockReturnValue({ - mutate: mockMutate, + (useAuth as Mock).mockReturnValue({ + signupMutation: mockSignupMutation, isLoading: false, }); }); - - describe("렌더링 테스트", () => { + + describe("SignupForm 렌더링 테스트", () => { it("모든 입력 필드와 제출 버튼이 렌더링되어야 한다", () => { render(); @@ -117,7 +120,7 @@ describe("SignupForm", () => { await userEvent.click(submitButton); await waitFor(() => { - expect(mockMutate).toHaveBeenCalledWith( + expect(mockSignupMutation.mutate).toHaveBeenCalledWith( expect.objectContaining({ nickname: validFormData.nickname, email: validFormData.email, @@ -137,7 +140,7 @@ describe("SignupForm", () => { }, }; - mockMutate.mockImplementation((_, options) => { + mockSignupMutation.mutate.mockImplementation((_, options) => { options.onError(mockError); }); diff --git a/src/app/dashboard/goal/[goalId]/page.tsx b/src/app/dashboard/goal/[goalId]/page.tsx index be19ca2..07306c1 100644 --- a/src/app/dashboard/goal/[goalId]/page.tsx +++ b/src/app/dashboard/goal/[goalId]/page.tsx @@ -7,7 +7,7 @@ import { AxiosError } from "axios"; import { deleteGoal, ErrorType, getGoal } from "@/api/goalAPI"; import { getTodos } from "@/api/todoAPI"; -import useModal from "@/hook/useModal"; +import useModal from "@/hooks/useModal"; import { useTodoStore } from "@/store/todoStore"; import CreateNewTodo from "@/components/CreateNewTodo"; import EditGoalTitleModal from "@/components/EditGoalTitleModal"; diff --git a/src/app/dashboard/note/[noteId]/page.tsx b/src/app/dashboard/note/[noteId]/page.tsx index 1e73470..c45d066 100644 --- a/src/app/dashboard/note/[noteId]/page.tsx +++ b/src/app/dashboard/note/[noteId]/page.tsx @@ -4,7 +4,7 @@ import Image from "next/image"; import { ChangeEvent, useEffect, useState } from "react"; import { usePathname, useRouter, useSearchParams } from "next/navigation"; -import useModal from "@/hook/useModal"; +import useModal from "@/hooks/useModal"; import { getNote, patchNotes, postNotes } from "@/api/noteAPI"; import { getTodos } from "@/api/todoAPI"; import UploadLinkModal from "@/components/UploadLinkModal"; diff --git a/src/app/dashboard/todoboard/page.tsx b/src/app/dashboard/todoboard/page.tsx index a8a7542..d183c8d 100644 --- a/src/app/dashboard/todoboard/page.tsx +++ b/src/app/dashboard/todoboard/page.tsx @@ -4,7 +4,7 @@ import { useEffect, useState } from "react"; import { useTodoStore } from "@/store/todoStore"; import CreateNewTodo from "@/components/CreateNewTodo"; -import useModal from "@/hook/useModal"; +import useModal from "@/hooks/useModal"; import { getAllTodos } from "@/api/todoAPI"; import { TodoType } from "@/type"; diff --git a/src/lib/api.ts b/src/lib/api.ts index 8faac82..275ec3a 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -6,9 +6,16 @@ const api = axios.create({ baseURL: process.env.NEXT_PUBLIC_API_URL, }); +// 쿠키에서 특정 키의 값을 가져오는 함수 +const getCookieValue = (key: string) => { + const cookies = document.cookie.split("; "); + const cookie = cookies.find((cookie) => cookie.startsWith(`${key}=`)); + return cookie ? cookie.split("=")[1] : null; +}; + api.interceptors.request.use( (config) => { - const token = document.cookie.replace(/(?:(?:^|.*;\s*)accessToken\s*=\s*([^;]*).*$)|^.*$/, "$1"); + const token = getCookieValue("accessToken"); // getCookieValue 함수를 사용하여 accessToken을 가져옴 if (token) { config.headers.Authorization = `Bearer ${token}`; } @@ -26,6 +33,7 @@ api.interceptors.response.use( if (error.response.status === 401 && !originalRequest._retry) { originalRequest._retry = true; const refreshToken = localStorage.getItem("refreshToken"); + if (refreshToken) { try { const { data } = await axios.post("/auth/tokens", { refreshToken });