diff --git a/src/app/mocks/handler/feed.ts b/src/app/mocks/handler/feed.ts index d583e85..2a72386 100644 --- a/src/app/mocks/handler/feed.ts +++ b/src/app/mocks/handler/feed.ts @@ -187,6 +187,6 @@ export const feedHandlers = [ hiddens[formattedFeedId] = true; } - return createHttpSuccessResponse({}); + return createHttpSuccessResponse({ isReported: true }); }), ]; diff --git a/src/features/feed-bookmark/api/useBookmarks.tsx b/src/features/feed-bookmark/api/useBookmarks.tsx index e18ac4e..72c88f1 100644 --- a/src/features/feed-bookmark/api/useBookmarks.tsx +++ b/src/features/feed-bookmark/api/useBookmarks.tsx @@ -10,7 +10,11 @@ import { updateBookmarkStatusInFeeds } from '../lib'; export const useBookmarks = (feedId: number, isBookmarked: boolean) => { const queryClient = useQueryClient(); - const { mutate: handleBookmarkFeed, isPending } = useMutation({ + const { + data, + mutate: handleBookmarkFeed, + isPending, + } = useMutation({ mutationFn: () => isBookmarked ? requestUnbookmarkFeed(feedId) @@ -51,5 +55,5 @@ export const useBookmarks = (feedId: number, isBookmarked: boolean) => { }, }); - return { handleBookmarkFeed, isPending }; + return { data, handleBookmarkFeed, isPending }; }; diff --git a/src/features/feed-bookmark/test/useBookmarks.test.tsx b/src/features/feed-bookmark/test/useBookmarks.test.tsx deleted file mode 100644 index 7da5b97..0000000 --- a/src/features/feed-bookmark/test/useBookmarks.test.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { renderHook, act, waitFor } from '@testing-library/react'; -import { expect, test, vi } from 'vitest'; - -import * as bookmarkModule from '@/shared/axios'; -import { createQueryClientWrapper } from '@/shared/tests'; - -import { useBookmarks } from '../api'; - -test('북마크 상태가 아닐 때, 북마크 버튼을 클릭하면 북마크 요청이 발생한다.', async () => { - // given - // requestBookmarkFeed 함수를 스파이한다. - const spy = vi.spyOn(bookmarkModule, 'requestBookmarkFeed'); - const { result } = renderHook(() => useBookmarks(1, false), { - wrapper: createQueryClientWrapper(), - }); - - // requestBookmarkFeed가 호출되지 않았는지 확인 - await waitFor(() => expect(spy).not.toHaveBeenCalled()); - - // when - // 좋아요 버튼 클릭 - await act(async () => result.current.handleBookmarkFeed()); - - // then - // requestBookmarkFeed가 호출되었는지 확인 - await waitFor(() => expect(spy).toHaveBeenCalled()); -}); - -test('북마크 상태일 때, 북마크 버튼을 클릭하면 북마크 취소 요청이 발생한다.', async () => { - // given - const spy = vi.spyOn(bookmarkModule, 'requestUnbookmarkFeed'); - const { result } = renderHook(() => useBookmarks(1, true), { - wrapper: createQueryClientWrapper(), - }); - - await waitFor(() => expect(spy).not.toHaveBeenCalled()); - - // when - await act(async () => result.current.handleBookmarkFeed()); - - // then - await waitFor(() => expect(spy).toHaveBeenCalled()); -}); diff --git a/src/features/feed-bookmark/test/useBookmarks.unit.test.tsx b/src/features/feed-bookmark/test/useBookmarks.unit.test.tsx new file mode 100644 index 0000000..48a17a2 --- /dev/null +++ b/src/features/feed-bookmark/test/useBookmarks.unit.test.tsx @@ -0,0 +1,44 @@ +import { renderHook, act, waitFor } from '@testing-library/react'; +import { describe, expect, it } from 'vitest'; + +import { createQueryClientWrapper } from '@/shared/tests'; + +import { useBookmarks } from '../api'; + +describe('북마크 기능 테스트', () => { + it('북마크 상태가 아닐 때, 북마크 버튼을 클릭하면 북마크 된다', async () => { + // given + const { result } = renderHook(() => useBookmarks(1, false), { + wrapper: createQueryClientWrapper(), + }); + + // when + act(() => result.current.handleBookmarkFeed()); + + // then + await waitFor(() => { + const { + data: { isBookmarked }, + } = result.current.data; + expect(isBookmarked).toBeTruthy(); + }); + }); + + it('북마크 상태일 때, 북마크 버튼을 클릭하면 북마크가 취소된다', async () => { + // given + const { result } = renderHook(() => useBookmarks(1, true), { + wrapper: createQueryClientWrapper(), + }); + + // when + act(() => result.current.handleBookmarkFeed()); + + // then + await waitFor(() => { + const { + data: { isBookmarked }, + } = result.current.data; + expect(isBookmarked).toBeFalsy(); + }); + }); +}); diff --git a/src/features/feed-hides/api/useHideCancle.tsx b/src/features/feed-hides/api/useHideCancle.tsx index 38044fc..bd4cc32 100644 --- a/src/features/feed-hides/api/useHideCancle.tsx +++ b/src/features/feed-hides/api/useHideCancle.tsx @@ -10,10 +10,14 @@ async function requestHideCancelFeed(feedId: number) { } export const useHideCancel = (feedId: number) => { - const { mutate: hideCancelFeed, isPending } = useMutation({ + const { + data, + mutate: hideCancelFeed, + isPending, + } = useMutation({ mutationFn: () => requestHideCancelFeed(feedId), onMutate: () => cancleHiddenFeed(feedId), }); - return { hideCancelFeed, isPending }; + return { data, hideCancelFeed, isPending }; }; diff --git a/src/features/feed-hides/api/useHides.tsx b/src/features/feed-hides/api/useHides.tsx index 65267cb..8ea3e79 100644 --- a/src/features/feed-hides/api/useHides.tsx +++ b/src/features/feed-hides/api/useHides.tsx @@ -10,10 +10,14 @@ async function requestHideFeed(feedId: number) { } export const useHides = (feedId: number) => { - const { mutate: hideFeed, isPending } = useMutation({ + const { + data, + mutate: hideFeed, + isPending, + } = useMutation({ mutationFn: () => requestHideFeed(feedId), onMutate: () => addHiddenFeed(feedId, 'hidden'), }); - return { hideFeed, isPending }; + return { data, hideFeed, isPending }; }; diff --git a/src/features/feed-hides/test/useHide.unit.test.tsx b/src/features/feed-hides/test/useHide.unit.test.tsx new file mode 100644 index 0000000..10e1a4e --- /dev/null +++ b/src/features/feed-hides/test/useHide.unit.test.tsx @@ -0,0 +1,46 @@ +import { renderHook, act, waitFor } from '@testing-library/react'; +import { describe, expect, it } from 'vitest'; + +import { createQueryClientWrapper } from '@/shared/tests'; + +import { useHideCancel, useHides } from '../api'; + +describe('숨기기 기능 테스트', () => { + it('숨기기 상태가 아닐 때, 숨기기 버튼을 클릭하면 게시물이 숨겨진다.', async () => { + // given + const { result } = renderHook(() => useHides(1), { + wrapper: createQueryClientWrapper(), + }); + + // when + act(() => result.current.hideFeed()); + + // then + await waitFor(() => { + const { + data: { isHidden }, + } = result.current.data; + + expect(isHidden).toBeTruthy(); + }); + }); + + it('숨기기 상태일 때, 취소 버튼을 클릭하면 숨기기가 취소된다.', async () => { + // given + const { result } = renderHook(() => useHideCancel(1), { + wrapper: createQueryClientWrapper(), + }); + + // when + act(() => result.current.hideCancelFeed()); + + // then + await waitFor(() => { + const { + data: { isHidden }, + } = result.current.data; + + expect(isHidden).toBeFalsy(); + }); + }); +}); diff --git a/src/features/feed-main-like/api/useLikes.tsx b/src/features/feed-main-like/api/useLikes.tsx index e0d4e2d..5dd373c 100644 --- a/src/features/feed-main-like/api/useLikes.tsx +++ b/src/features/feed-main-like/api/useLikes.tsx @@ -10,7 +10,11 @@ import { updateLikeStatusInFeeds } from '../lib'; export const useLikes = (feedId: number, isLiked: boolean) => { const queryClient = useQueryClient(); - const { mutate: handleLikeFeed, isPending } = useMutation({ + const { + data, + mutate: handleLikeFeed, + isPending, + } = useMutation({ mutationFn: () => isLiked ? requestUnlikeFeed(feedId) : requestLikeFeed(feedId), // mutate가 호출되면 ✨낙관적 업데이트를 위해 onMutate를 실행 @@ -53,5 +57,5 @@ export const useLikes = (feedId: number, isLiked: boolean) => { }, }); - return { handleLikeFeed, isPending }; + return { data, handleLikeFeed, isPending }; }; diff --git a/src/features/feed-main-like/test/useLikes.test.tsx b/src/features/feed-main-like/test/useLikes.test.tsx deleted file mode 100644 index 73ce59d..0000000 --- a/src/features/feed-main-like/test/useLikes.test.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { renderHook, act, waitFor } from '@testing-library/react'; -import { expect, test, vi } from 'vitest'; - -import * as likeModule from '@/shared/axios/like'; -import { createQueryClientWrapper } from '@/shared/tests'; - -import { useLikes } from '../api'; - -test('좋아요 상태가 아닐 때, 좋아요 버튼을 클릭하면 좋아요 요청이 발생한다.', async () => { - // given - // requestLikeFeed 함수를 스파이한다. - const spy = vi.spyOn(likeModule, 'requestLikeFeed'); - const { result } = renderHook(() => useLikes(1, false), { - wrapper: createQueryClientWrapper(), - }); - - // requestLikeFeed가 호출되지 않았는지 확인 - await waitFor(() => expect(spy).not.toHaveBeenCalled()); - - // when - // 좋아요 버튼 클릭 - await act(async () => result.current.handleLikeFeed()); - - // then - // requestLikeFeed가 호출되었는지 확인 - await waitFor(() => expect(spy).toHaveBeenCalled()); -}); - -test('좋아요 상태일 때, 좋아요 버튼을 클릭하면 좋아요 취소 요청이 발생한다.', async () => { - // given - const spy = vi.spyOn(likeModule, 'requestUnlikeFeed'); - const { result } = renderHook(() => useLikes(1, true), { - wrapper: createQueryClientWrapper(), - }); - - await waitFor(() => expect(spy).not.toHaveBeenCalled()); - - // when - await act(async () => result.current.handleLikeFeed()); - - // then - await waitFor(() => expect(spy).toHaveBeenCalled()); -}); diff --git a/src/features/feed-main-like/test/useLikes.unit.test.tsx b/src/features/feed-main-like/test/useLikes.unit.test.tsx new file mode 100644 index 0000000..9b556e0 --- /dev/null +++ b/src/features/feed-main-like/test/useLikes.unit.test.tsx @@ -0,0 +1,46 @@ +import { renderHook, act, waitFor } from '@testing-library/react'; +import { describe, expect, it } from 'vitest'; + +import { createQueryClientWrapper } from '@/shared/tests'; + +import { useLikes } from '../api'; + +describe('좋아요 기능 테스트', () => { + it('좋아요 상태가 아닐 때, 좋아요 버튼을 클릭하면 좋아요 상태가 변경된다.', async () => { + // given + const { result } = renderHook(() => useLikes(1, false), { + wrapper: createQueryClientWrapper(), + }); + + // when + act(() => result.current.handleLikeFeed()); + + // then + await waitFor(() => { + const { + data: { isLiked }, + } = result.current.data; + + expect(isLiked).toBeTruthy(); + }); + }); + + it('좋아요 상태일 때, 좋아요 버튼을 클릭하면 좋아요가 취소된다.', async () => { + // given + const { result } = renderHook(() => useLikes(1, true), { + wrapper: createQueryClientWrapper(), + }); + + // when + act(() => result.current.handleLikeFeed()); + + // then + await waitFor(() => { + const { + data: { isLiked }, + } = result.current.data; + + expect(isLiked).toBeFalsy(); + }); + }); +}); diff --git a/src/features/feed-reports/api/useSubmitReports.tsx b/src/features/feed-reports/api/useSubmitReports.tsx index c67ab45..abd88b9 100644 --- a/src/features/feed-reports/api/useSubmitReports.tsx +++ b/src/features/feed-reports/api/useSubmitReports.tsx @@ -13,7 +13,11 @@ async function requestFeedReports(feedId: number, body: FeedReportForm) { } export const useSubmitReports = (feedId: number) => { - const { mutate: reportFeed, isPending } = useMutation({ + const { + data, + mutate: reportFeed, + isPending, + } = useMutation({ mutationKey: ['feed-report'], mutationFn: (body: FeedReportForm) => requestFeedReports(feedId, body), onError: (_, body) => saveFeedReportForm(feedId, body), @@ -29,5 +33,5 @@ export const useSubmitReports = (feedId: number) => { }, }); - return { reportFeed, isPending }; + return { data, reportFeed, isPending }; }; diff --git a/src/features/feed-reports/test/FeedReports.integration.test.tsx b/src/features/feed-reports/test/FeedReports.integration.test.tsx new file mode 100644 index 0000000..19b05d8 --- /dev/null +++ b/src/features/feed-reports/test/FeedReports.integration.test.tsx @@ -0,0 +1,44 @@ +import { expect, test, vi } from 'vitest'; + +import { fireEvent, render, screen } from '@/shared/tests'; +import { Toast } from '@/shared/toast'; + +import { FeedReportsForm } from '../ui'; + +vi.mock('@/shared/ui/modal/ModalOverlay', () => ({ + ModalOverlay: ({ children }: { children: JSX.Element }) => ( +
{children}
+ ), +})); + +vi.mock('@/features/feed-reports/model', () => ({ + useReportForm: () => ({ + content: '', + isBlind: false, + isDisabledReportForm: false, + createReportBody: () => ({ + category: '상업적/홍보성', + content: '', + isBlind: false, + }), + }), +})); + +test('신고하기 버튼을 클릭하면, 신고 접수 토스트 메시지가 표시된다', async () => { + // given + render( + <> + {}} /> + + , + ); + + const reportBtn = screen.getByRole('button', { name: '신고하기' }); + + // when + fireEvent.click(reportBtn); + + // then + const reportAcceptMsg = await screen.findByText('신고가 접수되었어요'); + expect(reportAcceptMsg).toBeInTheDocument(); +}); diff --git a/src/features/feed-reports/test/useSubmitReports.unit.test.tsx b/src/features/feed-reports/test/useSubmitReports.unit.test.tsx new file mode 100644 index 0000000..d6df516 --- /dev/null +++ b/src/features/feed-reports/test/useSubmitReports.unit.test.tsx @@ -0,0 +1,30 @@ +import { act, renderHook, waitFor } from '@testing-library/react'; +import { expect, test } from 'vitest'; + +import { createQueryClientWrapper } from '@/shared/tests'; + +import { useSubmitReports } from '../api'; + +test('신고 기능 테스트', async () => { + // given + const { result } = renderHook(() => useSubmitReports(1), { + wrapper: createQueryClientWrapper(), + }); + const body = { + category: '상업적/홍보성', + content: '', + isBlind: false, + }; + + // when + act(() => result.current.reportFeed(body)); + + // then + await waitFor(() => { + const { + data: { isReported }, + } = result.current.data; + + expect(isReported).toBeTruthy(); + }); +}); diff --git a/src/setupTest.ts b/src/setupTest.ts index 0aaa4f0..e1bea44 100644 --- a/src/setupTest.ts +++ b/src/setupTest.ts @@ -4,7 +4,11 @@ import * as matchers from '@testing-library/jest-dom/matchers'; import { setupServer } from 'msw/node'; import { beforeAll, afterAll, afterEach, expect } from 'vitest'; +import { bookmarkHandlers } from './app/mocks/handler/bookmark'; +import { commentHandlers } from './app/mocks/handler/comment'; +import { feedHandlers } from './app/mocks/handler/feed'; import { followHandler } from './app/mocks/handler/follow'; +import { feedHidesHandlers } from './app/mocks/handler/hide'; import { likeHandlers } from './app/mocks/handler/like'; import { searchHandler } from './app/mocks/handler/search'; import { userHandler } from './app/mocks/handler/user'; @@ -12,12 +16,16 @@ import { userHandler } from './app/mocks/handler/user'; expect.extend(matchers); export const server = setupServer( + ...commentHandlers, + ...feedHandlers, + ...feedHidesHandlers, + ...bookmarkHandlers, ...likeHandlers, ...followHandler, ...searchHandler, ...userHandler, ); -beforeAll(() => server.listen({ onUnhandledRequest: 'error' })); +beforeAll(() => server.listen({ onUnhandledRequest: 'bypass' })); afterAll(() => server.close()); afterEach(() => server.resetHandlers()); diff --git a/src/shared/tests/setup.tsx b/src/shared/tests/setup.tsx index 09449d0..9f8a1b3 100644 --- a/src/shared/tests/setup.tsx +++ b/src/shared/tests/setup.tsx @@ -1,24 +1,13 @@ -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { QueryClientProvider } from '@tanstack/react-query'; import { render as testRender } from '@testing-library/react'; import { PropsWithChildren, ReactElement } from 'react'; import { MemoryRouter } from 'react-router-dom'; -const generateQueryClient = () => { - const queryClientOptions = { - defaultOptions: { - queries: { - retry: false, - }, - }, - }; - - return new QueryClient(queryClientOptions); -}; +import { queryClient } from '../react-query'; // hooks를 사용하는 컴포넌트를 테스트할 때 필요한 wrapper를 생성하는 함수 // from https://tkdodo.eu/blog/testing-react-query#for-custom-hooks export const createQueryClientWrapper = () => { - const queryClient = generateQueryClient(); return ({ children }: PropsWithChildren) => ( {children} ); @@ -27,8 +16,6 @@ export const createQueryClientWrapper = () => { // custom render 함수 // reference: https://testing-library.com/docs/react-testing-library/setup#custom-render const customRender = (children: ReactElement, baseEntries?: string[]) => { - const queryClient = generateQueryClient(); - return testRender( {children} diff --git a/src/widgets/feed-main-list/test/FeedMainList.test.tsx b/src/widgets/feed-main-list/test/FeedMainList.integration.test.tsx similarity index 100% rename from src/widgets/feed-main-list/test/FeedMainList.test.tsx rename to src/widgets/feed-main-list/test/FeedMainList.integration.test.tsx diff --git a/src/widgets/feed-main-list/test/FeedMainList.error.test.tsx b/src/widgets/feed-main-list/test/FeedMainList.intergration.error.test.tsx similarity index 100% rename from src/widgets/feed-main-list/test/FeedMainList.error.test.tsx rename to src/widgets/feed-main-list/test/FeedMainList.intergration.error.test.tsx diff --git a/src/widgets/feed-main-list/test/useInfinityFeeds.unit.test.tsx b/src/widgets/feed-main-list/test/useInfinityFeeds.unit.test.tsx new file mode 100644 index 0000000..eedb277 --- /dev/null +++ b/src/widgets/feed-main-list/test/useInfinityFeeds.unit.test.tsx @@ -0,0 +1,18 @@ +import { renderHook, waitFor } from '@testing-library/react'; +import { expect, test } from 'vitest'; + +import { createQueryClientWrapper } from '@/shared/tests'; + +import { useInfinityFeeds } from '../api'; + +test('무한 스크롤 테스트', async () => { + const { result } = renderHook(() => useInfinityFeeds(), { + wrapper: createQueryClientWrapper(), + }); + + await waitFor(() => expect(result.current.feeds?.pageParams).toEqual([1])); + + result.current.fetchNextFeeds(); + + await waitFor(() => expect(result.current.feeds?.pageParams).toEqual([1, 2])); +});