From a6e239ded7affcea539c7824c1f1f3920391359b Mon Sep 17 00:00:00 2001 From: jjinheeWorld Date: Sat, 17 Jan 2026 07:29:16 +0900 Subject: [PATCH 1/4] =?UTF-8?q?=F0=9F=A7=AA=20Test:=20NoteList=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=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/components/notes/NoteList.test.tsx | 115 +++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 tests/components/notes/NoteList.test.tsx diff --git a/tests/components/notes/NoteList.test.tsx b/tests/components/notes/NoteList.test.tsx new file mode 100644 index 0000000..1a9c7a9 --- /dev/null +++ b/tests/components/notes/NoteList.test.tsx @@ -0,0 +1,115 @@ +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import NoteList from "@/app/(protected)/notes/_components/NoteList"; +import { Note } from "@/hooks/queries/notes"; + +describe("NoteList", () => { + const mockNotes: Note[] = [ + { + id: 1, + title: "첫 번째 노트", + todo: { id: 1, title: "할 일 1", done: false }, + updatedAt: "2024-01-15T10:00:00Z", + }, + { + id: 2, + title: "두 번째 노트", + todo: { id: 2, title: "할 일 2", done: true }, + updatedAt: "2024-01-16T10:00:00Z", + }, + ]; + + const mockOnEditNote = jest.fn(); + const mockOnDeleteNote = jest.fn(); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe("렌더링", () => { + it("노트 목록이 화면에 렌더링된다", () => { + render( + , + ); + + expect(screen.getByText("첫 번째 노트")).toBeInTheDocument(); + expect(screen.getByText("두 번째 노트")).toBeInTheDocument(); + }); + + it("노트가 최신순으로 정렬된다", () => { + render( + , + ); + + const articles = screen.getAllByRole("article"); + expect(articles[0]).toHaveTextContent("두 번째 노트"); + expect(articles[1]).toHaveTextContent("첫 번째 노트"); + }); + + it("노트 클릭 시 상세 페이지로 이동하는 링크가 있다", () => { + render( + , + ); + + const links = screen.getAllByRole("link"); + expect(links[0]).toHaveAttribute("href", "/notes/2"); + expect(links[1]).toHaveAttribute("href", "/notes/1"); + }); + }); + + describe("사용자 인터랙션", () => { + it("수정 버튼 클릭 시 onEditNote가 호출된다", async () => { + const user = userEvent.setup(); + + render( + , + ); + + const dropdownButtons = screen.getAllByLabelText("노트 옵션 메뉴"); + await user.click(dropdownButtons[0]); + + const editButton = screen.getByText("수정하기"); + await user.click(editButton); + + expect(mockOnEditNote).toHaveBeenCalledWith(2); + expect(mockOnEditNote).toHaveBeenCalledTimes(1); + }); + + it("삭제 버튼 클릭 시 onDeleteNote가 호출된다", async () => { + const user = userEvent.setup(); + + render( + , + ); + + const dropdownButtons = screen.getAllByLabelText("노트 옵션 메뉴"); + await user.click(dropdownButtons[0]); + + const deleteButton = screen.getByText("삭제하기"); + await user.click(deleteButton); + + expect(mockOnDeleteNote).toHaveBeenCalledWith(2); + expect(mockOnDeleteNote).toHaveBeenCalledTimes(1); + }); + }); +}); From e0db900a44f5b291daa1e848c45df6a008be74c1 Mon Sep 17 00:00:00 2001 From: jjinheeWorld Date: Sat, 17 Jan 2026 18:53:42 +0900 Subject: [PATCH 2/4] =?UTF-8?q?=F0=9F=A7=AA=20Test=20:=20NoteListContainer?= =?UTF-8?q?=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../notes/NoteListContainer.test.tsx | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 tests/components/notes/NoteListContainer.test.tsx diff --git a/tests/components/notes/NoteListContainer.test.tsx b/tests/components/notes/NoteListContainer.test.tsx new file mode 100644 index 0000000..f0d2706 --- /dev/null +++ b/tests/components/notes/NoteListContainer.test.tsx @@ -0,0 +1,73 @@ +import { render, screen } from "@testing-library/react"; +import NoteListContainer from "@/app/(protected)/notes/_components/NoteListContainer"; +import { useNotesQuery, useDeleteNoteMutation } from "@/hooks/queries/notes"; + +jest.mock("@/hooks/queries/notes"); + +jest.mock("next/navigation", () => ({ + useRouter: () => ({ + push: jest.fn(), + }), +})); + +const mockUseNotesQuery = useNotesQuery as jest.MockedFunction< + typeof useNotesQuery +>; + +const mockUseDeleteNoteMutation = useDeleteNoteMutation as jest.MockedFunction< + typeof useDeleteNoteMutation +>; + +describe("NoteListContainer", () => { + const goalId = 1; + + beforeEach(() => { + jest.clearAllMocks(); + + mockUseDeleteNoteMutation.mockReturnValue({ + mutate: jest.fn(), + } as unknown as ReturnType); + }); + + describe("노트가 없을 때", () => { + it("노트가 없으면 EmptyState를 렌더링된다", () => { + mockUseNotesQuery.mockReturnValue({ + data: { + totalCount: 0, + nextCursor: null, + goal: { id: goalId, title: "목표 1" }, + notes: [], + }, + } as unknown as ReturnType); + + render(); + + expect(screen.getByText("아직 등록된 노트가 없어요")).toBeInTheDocument(); + }); + }); + + describe("노트가 있을 때", () => { + it("GoalBanner와 NoteList를 렌더링한다", () => { + mockUseNotesQuery.mockReturnValue({ + data: { + totalCount: 1, + nextCursor: null, + goal: { id: goalId, title: "목표 1" }, + notes: [ + { + id: 1, + title: "첫 번째 노트", + todo: { id: 1, title: "할 일 1", done: false }, + updatedAt: "2026-01-17T10:00:00Z", + }, + ], + }, + } as unknown as ReturnType); + + render(); + + expect(screen.getByText("목표 1")).toBeInTheDocument(); + expect(screen.getByText("첫 번째 노트")).toBeInTheDocument(); + }); + }); +}); From e713f3cb035416b6c777ab6d222f288b32882095 Mon Sep 17 00:00:00 2001 From: jjinheeWorld Date: Sun, 18 Jan 2026 11:13:05 +0900 Subject: [PATCH 3/4] =?UTF-8?q?=F0=9F=A7=AA=20Test:=20NoteListContainer=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../notes/NoteListContainer.test.tsx | 25 +++++++------------ 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/tests/components/notes/NoteListContainer.test.tsx b/tests/components/notes/NoteListContainer.test.tsx index f0d2706..a228f90 100644 --- a/tests/components/notes/NoteListContainer.test.tsx +++ b/tests/components/notes/NoteListContainer.test.tsx @@ -1,6 +1,7 @@ -import { render, screen } from "@testing-library/react"; -import NoteListContainer from "@/app/(protected)/notes/_components/NoteListContainer"; +import { screen } from "@testing-library/react"; import { useNotesQuery, useDeleteNoteMutation } from "@/hooks/queries/notes"; +import NoteListContainer from "@/app/(protected)/notes/_components/NoteListContainer"; +import { renderWithQueryClient } from "tests/test-utils"; jest.mock("@/hooks/queries/notes"); @@ -10,28 +11,20 @@ jest.mock("next/navigation", () => ({ }), })); -const mockUseNotesQuery = useNotesQuery as jest.MockedFunction< - typeof useNotesQuery ->; - -const mockUseDeleteNoteMutation = useDeleteNoteMutation as jest.MockedFunction< - typeof useDeleteNoteMutation ->; - describe("NoteListContainer", () => { const goalId = 1; beforeEach(() => { jest.clearAllMocks(); - mockUseDeleteNoteMutation.mockReturnValue({ + jest.mocked(useDeleteNoteMutation).mockReturnValue({ mutate: jest.fn(), } as unknown as ReturnType); }); describe("노트가 없을 때", () => { - it("노트가 없으면 EmptyState를 렌더링된다", () => { - mockUseNotesQuery.mockReturnValue({ + it("노트가 없으면 EmptyState를 렌더링한다", () => { + jest.mocked(useNotesQuery).mockReturnValue({ data: { totalCount: 0, nextCursor: null, @@ -40,7 +33,7 @@ describe("NoteListContainer", () => { }, } as unknown as ReturnType); - render(); + renderWithQueryClient(); expect(screen.getByText("아직 등록된 노트가 없어요")).toBeInTheDocument(); }); @@ -48,7 +41,7 @@ describe("NoteListContainer", () => { describe("노트가 있을 때", () => { it("GoalBanner와 NoteList를 렌더링한다", () => { - mockUseNotesQuery.mockReturnValue({ + jest.mocked(useNotesQuery).mockReturnValue({ data: { totalCount: 1, nextCursor: null, @@ -64,7 +57,7 @@ describe("NoteListContainer", () => { }, } as unknown as ReturnType); - render(); + renderWithQueryClient(); expect(screen.getByText("목표 1")).toBeInTheDocument(); expect(screen.getByText("첫 번째 노트")).toBeInTheDocument(); From f5b3d6d2c7f70214877fff60a9c16ef0ac175455 Mon Sep 17 00:00:00 2001 From: jjinheeWorld Date: Sun, 18 Jan 2026 11:19:08 +0900 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=A7=AA=20Test=20:=20NoteList=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/components/notes/NoteList.test.tsx | 54 ++++++++---------------- 1 file changed, 17 insertions(+), 37 deletions(-) diff --git a/tests/components/notes/NoteList.test.tsx b/tests/components/notes/NoteList.test.tsx index 1a9c7a9..ff2b988 100644 --- a/tests/components/notes/NoteList.test.tsx +++ b/tests/components/notes/NoteList.test.tsx @@ -9,45 +9,43 @@ describe("NoteList", () => { id: 1, title: "첫 번째 노트", todo: { id: 1, title: "할 일 1", done: false }, - updatedAt: "2024-01-15T10:00:00Z", + updatedAt: "2026-01-15T10:00:00Z", }, { id: 2, title: "두 번째 노트", todo: { id: 2, title: "할 일 2", done: true }, - updatedAt: "2024-01-16T10:00:00Z", + updatedAt: "2026-01-16T10:00:00Z", }, ]; const mockOnEditNote = jest.fn(); const mockOnDeleteNote = jest.fn(); + const renderNoteList = () => { + return render( + , + ); + }; + beforeEach(() => { jest.clearAllMocks(); }); describe("렌더링", () => { it("노트 목록이 화면에 렌더링된다", () => { - render( - , - ); + renderNoteList(); expect(screen.getByText("첫 번째 노트")).toBeInTheDocument(); expect(screen.getByText("두 번째 노트")).toBeInTheDocument(); }); it("노트가 최신순으로 정렬된다", () => { - render( - , - ); + renderNoteList(); const articles = screen.getAllByRole("article"); expect(articles[0]).toHaveTextContent("두 번째 노트"); @@ -55,13 +53,7 @@ describe("NoteList", () => { }); it("노트 클릭 시 상세 페이지로 이동하는 링크가 있다", () => { - render( - , - ); + renderNoteList(); const links = screen.getAllByRole("link"); expect(links[0]).toHaveAttribute("href", "/notes/2"); @@ -73,13 +65,7 @@ describe("NoteList", () => { it("수정 버튼 클릭 시 onEditNote가 호출된다", async () => { const user = userEvent.setup(); - render( - , - ); + renderNoteList(); const dropdownButtons = screen.getAllByLabelText("노트 옵션 메뉴"); await user.click(dropdownButtons[0]); @@ -94,13 +80,7 @@ describe("NoteList", () => { it("삭제 버튼 클릭 시 onDeleteNote가 호출된다", async () => { const user = userEvent.setup(); - render( - , - ); + renderNoteList(); const dropdownButtons = screen.getAllByLabelText("노트 옵션 메뉴"); await user.click(dropdownButtons[0]);