diff --git a/src/app/message/page.test.tsx b/src/app/message/page.test.tsx new file mode 100644 index 00000000..f2c53c8f --- /dev/null +++ b/src/app/message/page.test.tsx @@ -0,0 +1,43 @@ +import { render, screen } from '@testing-library/react'; + +import { ModalProvider } from '@/components/ui'; +import { useInfiniteScroll } from '@/hooks/use-group/use-group-infinite-list'; + +import FollowingPage from './page'; + +jest.mock('@/hooks/use-group/use-group-infinite-list', () => ({ + useInfiniteScroll: jest.fn(), +})); + +jest.mock('next/navigation', () => ({ + useSearchParams: () => ({ + get: () => 'following', + }), +})); + +jest.mock('js-cookie', () => ({ + get: () => '1', +})); + +describe('FollowingPage 테스트', () => { + beforeEach(() => { + (useInfiniteScroll as jest.Mock).mockReturnValue({ + items: [], + error: null, + fetchNextPage: jest.fn(), + hasNextPage: false, + isFetchingNextPage: false, + completedMessage: '', + }); + }); + + test('팔로잉이 없을 경우 FollowingNone을 보여준다', async () => { + render( + + + , + ); + + expect(await screen.findByText('아직 팔로우 한 사람이 없어요.')).toBeInTheDocument(); + }); +}); diff --git a/src/components/pages/message/message-following-modal/index.test.tsx b/src/components/pages/message/message-following-modal/index.test.tsx new file mode 100644 index 00000000..8ffa4ade --- /dev/null +++ b/src/components/pages/message/message-following-modal/index.test.tsx @@ -0,0 +1,79 @@ +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'; + +import { ModalProvider } from '@/components/ui'; +import { useAddFollowers } from '@/hooks/use-follower'; + +import { FollowingModal } from '.'; + +// Mock 설정 +jest.mock('@/hooks/use-follower'); + +const createQueryClient = () => + new QueryClient({ + defaultOptions: { + queries: { retry: false }, + mutations: { retry: false }, + }, + }); + +const renderWithQueryClient = async (component: React.ReactElement) => { + const testQueryClient = createQueryClient(); + let renderResult; + + await act(async () => { + renderResult = render( + + {component} + , + ); + }); + + return renderResult; +}; + +describe('FollowingModal 테스트', () => { + const mockMutate = jest.fn(); + const mockUserId = 123; + + beforeEach(() => { + jest.clearAllMocks(); + + // 기본 mock 설정 + (useAddFollowers as jest.Mock).mockReturnValue({ + mutate: mockMutate, + }); + }); + + test('FollowingModal 렌더링 테스트', async () => { + await renderWithQueryClient(); + + expect(screen.getByText('팔로우 할 닉네임을 입력하세요')).toBeInTheDocument(); + expect(screen.getByPlaceholderText('nickname')).toBeInTheDocument(); + }); + + test('닉네임 입력이 정상적으로 동작한다', async () => { + await renderWithQueryClient(); + + const input = screen.getByPlaceholderText('nickname'); + fireEvent.change(input, { target: { value: 'test' } }); + + expect(input).toHaveValue('test'); + }); + + test('Enter 키 입력 시 폼이 제출된다', async () => { + mockMutate.mockImplementation((_data, options) => { + options?.onSuccess?.(); + }); + + await renderWithQueryClient(); + + const input = screen.getByRole('textbox'); + fireEvent.change(input, { target: { value: 'test' } }); + fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' }); + + await waitFor(() => { + expect(mockMutate).toHaveBeenCalledWith({ followNickname: 'test' }, expect.any(Object)); + }); + }); +}); diff --git a/src/components/pages/message/message-following-modal/index.tsx b/src/components/pages/message/message-following-modal/index.tsx new file mode 100644 index 00000000..cf906a9c --- /dev/null +++ b/src/components/pages/message/message-following-modal/index.tsx @@ -0,0 +1,82 @@ +import { useState } from 'react'; + +import { useForm } from '@tanstack/react-form'; + +import { Icon } from '@/components/icon'; +import { Button, Input, ModalContent, ModalTitle, useModal } from '@/components/ui'; +import { useAddFollowers } from '@/hooks/use-follower'; + +export const FollowingModal = ({ userId }: { userId: number }) => { + const { close } = useModal(); + const [errorMessage, setErrorMessage] = useState(null); + const { mutate: addFollower } = useAddFollowers({ userId }); + const form = useForm({ + defaultValues: { + nickname: '', + }, + onSubmit: ({ value }) => { + const { nickname } = value; + setErrorMessage(null); + + addFollower( + { + followNickname: nickname, + }, + { + onSuccess: () => { + close(); + }, + onError: () => { + setErrorMessage('존재하지 않는 유저입니다.'); + }, + }, + ); + }, + }); + return ( + + 팔로우 할 닉네임을 입력하세요 +
{ + e.preventDefault(); + form.handleSubmit(); + }} + > +
+ ( + + } + placeholder='nickname' + value={field.state.value} + onChange={(e) => { + field.handleChange(e.target.value); + setErrorMessage(null); + }} + onKeyDown={(e) => { + if (e.key === 'Enter') { + e.preventDefault(); + form.handleSubmit(); + } + }} + /> + )} + name='nickname' + > +
+ {errorMessage &&

{errorMessage}

} +
+ + +
+
+
+ ); +}; diff --git a/src/components/pages/message/message-following-search/index.test.tsx b/src/components/pages/message/message-following-search/index.test.tsx index 74f09216..598af25c 100644 --- a/src/components/pages/message/message-following-search/index.test.tsx +++ b/src/components/pages/message/message-following-search/index.test.tsx @@ -1,5 +1,5 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { fireEvent, render, screen } from '@testing-library/react'; +import { act, fireEvent, render, screen } from '@testing-library/react'; import { ModalProvider } from '@/components/ui'; @@ -8,18 +8,15 @@ import { FollowingSearch } from '.'; const createQueryClient = () => new QueryClient({ defaultOptions: { - queries: { - retry: false, - }, - mutations: { - retry: false, - }, + queries: { retry: false }, + mutations: { retry: false }, }, }); -describe('Following Search 테스트', () => { +const renderComponent = async () => { const queryClient = createQueryClient(); - test('Following Search 렌더링 테스트', () => { + + await act(async () => { render( @@ -27,20 +24,19 @@ describe('Following Search 테스트', () => { , ); + }); +}; +describe('FollowingSearch', () => { + test('렌더링된다', async () => { + await renderComponent(); expect(screen.getByText('팔로우 추가')).toBeInTheDocument(); }); - test('팔로우 추가 클릭 시 모달 생성', () => { - render( - - - - - , - ); + test('클릭 시 FollowingModal이 열린다', async () => { + await renderComponent(); - expect(screen.queryByText('팔로우 할 닉네임을 입력하세요')).toBeNull(); + expect(screen.queryByText('팔로우 할 닉네임을 입력하세요')).not.toBeInTheDocument(); fireEvent.click(screen.getByText('팔로우 추가')); diff --git a/src/components/pages/message/message-following-search/index.tsx b/src/components/pages/message/message-following-search/index.tsx index 22b10221..ec4b84ad 100644 --- a/src/components/pages/message/message-following-search/index.tsx +++ b/src/components/pages/message/message-following-search/index.tsx @@ -1,84 +1,16 @@ 'use client'; -import { useState } from 'react'; import { Icon } from '@/components/icon'; -import { Button, Input, ModalContent, ModalTitle, useModal } from '@/components/ui'; -import { useAddFollowers } from '@/hooks/use-follower'; +import { useModal } from '@/components/ui'; -const FollowerModal = ({ userId }: { userId: number }) => { - const { close } = useModal(); - const [nickname, setNickname] = useState(''); - const [errorMessage, setErrorMessage] = useState(''); - const { mutate: addFollower } = useAddFollowers({ userId }); - - const handleConfirm = () => { - if (!nickname.trim()) { - setErrorMessage('닉네임을 입력해주세요.'); - return; - } - - setErrorMessage(''); // 에러 메세지 초기화. - - addFollower( - { followNickname: nickname }, - { - onSuccess: () => { - close(); - }, - onError: () => { - setErrorMessage('존재하지 않는 유저입니다.'); - }, - }, - ); - }; - - const handleChange = (e: React.ChangeEvent) => { - const value = e.target.value; - setNickname(value); - if (errorMessage) { - setErrorMessage(''); - } - }; - - // 모달 모양 바뀌면 적용하기! - return ( - - 팔로우 할 닉네임을 입력하세요 -
- - } - placeholder='nickname' - value={nickname} - onChange={handleChange} - onKeyDown={(e) => { - if (e.key === 'Enter') { - handleConfirm(); - } - }} - /> - {errorMessage &&

{errorMessage}

} -
-
- - -
-
- ); -}; +import { FollowingModal } from '../message-following-modal'; export const FollowingSearch = ({ userId }: { userId: number }) => { const { open } = useModal(); return (
open()} + onClick={() => open()} >