From e77080bc05cf9a0dd629bbf94b6323564e5c0a4d Mon Sep 17 00:00:00 2001 From: Jeong Daseul <98886223+goodaseul@users.noreply.github.com> Date: Wed, 14 Jan 2026 21:04:24 +0900 Subject: [PATCH 1/3] =?UTF-8?q?=F0=9F=A7=AA=20Test=20:=20=EB=AA=A9?= =?UTF-8?q?=ED=91=9C=EC=83=81=EC=84=B8=20=EC=82=AD=EC=A0=9C=EC=8B=9C=20?= =?UTF-8?q?=EB=A7=81=ED=81=AC=20=EC=9D=B4=EB=8F=99=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/unit/goals/goals.test.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/unit/goals/goals.test.tsx b/tests/unit/goals/goals.test.tsx index 784c3db..1001e99 100644 --- a/tests/unit/goals/goals.test.tsx +++ b/tests/unit/goals/goals.test.tsx @@ -3,13 +3,15 @@ import GoalHeader from "@/app/(protected)/goals/[goalId]/_components/GoalHeader" import GoalSection from "@/app/(protected)/goals/[goalId]/_components/GoalSection"; import { calcProgress } from "@/app/(protected)/goals/[goalId]/_utils/calcProgress"; import { EMPTY_MESSAGES } from "@/constants/messages"; -import { render, screen } from "@testing-library/react"; +import { render, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { renderWithQueryClient } from "tests/test-utils"; const push = jest.fn(); +const replace = jest.fn(); + jest.mock("next/navigation", () => ({ - useRouter: () => ({ push }), + useRouter: () => ({ push, replace }), useParams: () => ({ goalId: "1" }), })); @@ -97,6 +99,10 @@ describe("목표 영역", () => { expect(screen.getByText("정말 삭제하시겠어요?")).toBeInTheDocument(); await user.click(screen.getByText("확인")); + expect(replace).toHaveBeenCalledWith( + expect.stringContaining("dashboard"), + ); + expect(deleteGoal).toHaveBeenCalled(); }); }); From aa29f6653e46ad926d6b5e7875a220498d4d2541 Mon Sep 17 00:00:00 2001 From: Jeong Daseul <98886223+goodaseul@users.noreply.github.com> Date: Wed, 14 Jan 2026 21:39:30 +0900 Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=A7=AA=20Test=20:=20=EA=B3=B5?= =?UTF-8?q?=ED=86=B5=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=A6=AC?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/components/list/ListItem.test.tsx | 33 ++++++++ tests/components/list/ListItemButton.test.tsx | 22 +++++ tests/components/list/ListItemRow.test.tsx | 80 +++++++++++++++++++ 3 files changed, 135 insertions(+) create mode 100644 tests/components/list/ListItem.test.tsx create mode 100644 tests/components/list/ListItemButton.test.tsx create mode 100644 tests/components/list/ListItemRow.test.tsx diff --git a/tests/components/list/ListItem.test.tsx b/tests/components/list/ListItem.test.tsx new file mode 100644 index 0000000..3f70138 --- /dev/null +++ b/tests/components/list/ListItem.test.tsx @@ -0,0 +1,33 @@ +import { render, screen } from "@testing-library/react"; +import ListItem from "@/components/common/list/list-item/ListItem"; + +jest.mock("@/components/common/list/list-item/ListItemRow", () => + jest.fn(() =>
  • ), +); + +describe("ListItem 컴포넌트", () => { + it("items 개수만큼 ListItemRow를 렌더링한다", () => { + render( + , + ); + + expect(screen.getAllByTestId("list-item-row")).toHaveLength(2); + }); + + it("items가 비어 있어도 에러 없이 렌더링된다", () => { + render( + , + ); + + expect(screen.queryAllByTestId("list-item-row")).toHaveLength(0); + }); +}); diff --git a/tests/components/list/ListItemButton.test.tsx b/tests/components/list/ListItemButton.test.tsx new file mode 100644 index 0000000..3e91f1a --- /dev/null +++ b/tests/components/list/ListItemButton.test.tsx @@ -0,0 +1,22 @@ +import ListItemButton from "@/components/common/list/list-button/ListItemButton"; +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; + +describe("ListItemButton", () => { + it("버튼을 클릭하면 onClick이 호출된다", async () => { + const user = userEvent.setup(); + const handleClick = jest.fn(); + + render( + icon} + ariaLabel="테스트 버튼" + onClick={handleClick} + />, + ); + + await user.click(screen.getByRole("button", { name: "테스트 버튼" })); + + expect(handleClick).toHaveBeenCalledTimes(1); + }); +}); diff --git a/tests/components/list/ListItemRow.test.tsx b/tests/components/list/ListItemRow.test.tsx new file mode 100644 index 0000000..abe39f8 --- /dev/null +++ b/tests/components/list/ListItemRow.test.tsx @@ -0,0 +1,80 @@ +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import ListItemRow from "@/components/common/list/list-item/ListItemRow"; +import { ListActionType } from "@/components/common/list/list-item-actions/types"; + +jest.mock("@/components/common/checkbox/Checkbox", () => { + const MockListToggleChecked = ({ + checked, + onToggleChecked, + }: { + checked: boolean; + onToggleChecked: (checked: boolean) => void; + }) => ( + onToggleChecked(e.target.checked)} + /> + ); + + MockListToggleChecked.displayName = "MockListToggleChecked"; + + return MockListToggleChecked; +}); + +jest.mock("@/components/common/list/list-item-actions/ListItemActions", () => { + const MockListItemActions = ({ + actions = [], + }: { + actions?: ListActionType[]; + }) => ( +
    + {actions.map((action) => action.type).join(",")} +
    + ); + + MockListItemActions.displayName = "MockListItemActions"; + + return MockListItemActions; +}); +describe("ListItemRow 컴포넌트", () => { + it("체크박스를 클릭하면 onToggleChecked가 호출된다", async () => { + const user = userEvent.setup(); + const onToggleChecked = jest.fn(); + + render( + , + ); + + await user.click(screen.getByTestId("checkbox")); + + expect(onToggleChecked).toHaveBeenCalledWith(1, true); + }); + + it("item에 link, file, note가 있으면 actions가 생성된다", () => { + render( + , + ); + + expect(screen.getByTestId("actions")).toHaveTextContent( + "link,file,note,more", + ); + }); +}); From 21cd033cdf0f1e09176cc04dd413e8ce2f732450 Mon Sep 17 00:00:00 2001 From: Jeong Daseul <98886223+goodaseul@users.noreply.github.com> Date: Wed, 14 Jan 2026 22:38:54 +0900 Subject: [PATCH 3/3] =?UTF-8?q?=F0=9F=A7=AA=20Test=20:=20=EB=8C=80?= =?UTF-8?q?=EC=8B=9C=EB=B3=B4=EB=93=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/unit/dashboard/dashboard.test.tsx | 119 ++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 tests/unit/dashboard/dashboard.test.tsx diff --git a/tests/unit/dashboard/dashboard.test.tsx b/tests/unit/dashboard/dashboard.test.tsx new file mode 100644 index 0000000..2e4da51 --- /dev/null +++ b/tests/unit/dashboard/dashboard.test.tsx @@ -0,0 +1,119 @@ +import { screen } from "@testing-library/react"; +import ProgressContent from "@/app/(protected)/dashboard/_components/todos/progress/ProgressContent"; +import RecentTodosContent from "@/app/(protected)/dashboard/_components/todos/recent/RecentTodosContent"; +import GoalList from "@/app/(protected)/dashboard/_components/goal/GoalList"; +import { useProgressTodosSuspense } from "@/hooks/queries/todos/useProgressTodosSuspense"; +import { useTodosSuspense } from "@/hooks/queries/todos/useTodosSuspense"; +import { useGoalsInfiniteQuery } from "@/hooks/queries/goals/useGoalsInfiniteQuery"; +import { renderWithQueryClient } from "tests/test-utils"; + +jest.mock("@/app/(protected)/_components/AsyncBoundary", () => { + const MockAsyncBoundary = ({ children }: { children: React.ReactNode }) => ( + <>{children} + ); + MockAsyncBoundary.displayName = "MockAsyncBoundary"; + return MockAsyncBoundary; +}); + +jest.mock("@/hooks/queries/todos/useTodosSuspense", () => ({ + useTodosSuspense: jest.fn(), +})); + +jest.mock("@/hooks/queries/todos/useProgressTodosSuspense", () => ({ + useProgressTodosSuspense: jest.fn(), +})); + +jest.mock("@/hooks/queries/goals/useGoalsInfiniteQuery", () => ({ + useGoalsInfiniteQuery: jest.fn(), +})); + +jest.mock("@/hooks/queries/todos/useToggleTodo", () => ({ + useToggleTodo: () => ({ + handleToggle: jest.fn(), + }), +})); + +jest.mock("@/components/common/list/list-item-actions/ListItemActions", () => { + const MockListItemActions = () => null; + MockListItemActions.displayName = "MockListItemActions"; + return MockListItemActions; +}); + +afterEach(() => { + jest.clearAllMocks(); +}); + +describe("대시보드 - 최근 등록한 할 일", () => { + it("최근 4개만 보여준다", () => { + (useTodosSuspense as jest.Mock).mockReturnValue([ + { id: 1, label: "todo1", checked: false }, + { id: 2, label: "todo2", checked: false }, + { id: 3, label: "todo3", checked: false }, + { id: 4, label: "todo4", checked: false }, + { id: 5, label: "todo5", checked: false }, + ]); + + renderWithQueryClient(); + + expect(screen.getByText("todo5")).toBeInTheDocument(); + expect(screen.getByText("todo4")).toBeInTheDocument(); + expect(screen.getByText("todo3")).toBeInTheDocument(); + expect(screen.getByText("todo2")).toBeInTheDocument(); + expect(screen.queryByText("todo1")).not.toBeInTheDocument(); + }); + + it("할 일이 없으면 안내 문구를 보여준다", () => { + (useTodosSuspense as jest.Mock).mockReturnValue([]); + + renderWithQueryClient(); + + expect( + screen.getByText("최근에 등록한 할 일이 없어요"), + ).toBeInTheDocument(); + }); +}); + +describe("대시보드 - 진행도", () => { + it("할 일들의 진행도를 보여준다", () => { + (useProgressTodosSuspense as jest.Mock).mockReturnValue({ + progress: 80, + }); + + renderWithQueryClient(); + + expect(screen.getByText(/80/)).toBeInTheDocument(); + }); + + it("progress가 없으면 0%를 보여준다", () => { + (useProgressTodosSuspense as jest.Mock).mockReturnValue({}); + + renderWithQueryClient(); + + expect(screen.getByText(/0/)).toBeInTheDocument(); + }); +}); + +describe("대시보드 - 목표별 할 일", () => { + it("목표가 잘 나온다", () => { + (useGoalsInfiniteQuery as jest.Mock).mockReturnValue({ + data: { + pages: [ + { + goals: [ + { id: 1, title: "운동 목표", todos: [] }, + { id: 2, title: "공부 목표", todos: [] }, + ], + }, + ], + }, + hasNextPage: false, + fetchNextPage: jest.fn(), + isFetchingNextPage: false, + }); + + renderWithQueryClient(); + + expect(screen.getByText("운동 목표")).toBeInTheDocument(); + expect(screen.getByText("공부 목표")).toBeInTheDocument(); + }); +});