diff --git a/src/components/pages/auth/login/login-form/index.test.tsx b/src/components/pages/auth/login/login-form/index.test.tsx new file mode 100644 index 00000000..731e0470 --- /dev/null +++ b/src/components/pages/auth/login/login-form/index.test.tsx @@ -0,0 +1,84 @@ +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import { LoginForm } from './index'; + +const handleLoginMock = jest.fn(); +const clearLoginErrorMock = jest.fn(); + +jest.mock('@/hooks/use-auth', () => ({ + useLogin: () => ({ + handleLogin: handleLoginMock, + loginError: null, + clearLoginError: clearLoginErrorMock, + }), +})); + +type MinimalField = { + state: { value: string }; + handleChange: (value: string) => void; +}; + +type FieldProps = { field: MinimalField }; + +jest.mock('@/components/pages/auth/fields', () => ({ + EmailField: ({ field }: FieldProps) => ( + field.handleChange(e.target.value)} + /> + ), + PasswordField: ({ field }: FieldProps) => ( + field.handleChange(e.target.value)} + /> + ), +})); + +jest.mock('../../auth-button', () => ({ + AuthSubmitButton: () => , +})); + +type LoginSnsButtonProps = React.PropsWithChildren<{ onClick: () => void }>; + +jest.mock('../login-sns-button', () => ({ + LoginSnsButton: ({ onClick, children }: LoginSnsButtonProps) => ( + + ), +})); + +describe('LoginForm', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('입력 후 submit 시 handleLogin이 payload로 호출된다', async () => { + const user = userEvent.setup(); + render(); + + await user.type(screen.getByLabelText('이메일'), 'ok@wego.com'); + await user.type(screen.getByLabelText('비밀번호'), 'pw'); + + await user.click(screen.getByRole('button', { name: '로그인하기' })); + + expect(handleLoginMock).toHaveBeenCalledTimes(1); + expect(handleLoginMock.mock.calls[0][0]).toEqual({ + email: 'ok@wego.com', + password: 'pw', + }); + expect(handleLoginMock.mock.calls[0][1]).toBeTruthy(); + }); + + it('email/password 변경 시 clearLoginError가 호출된다', async () => { + const user = userEvent.setup(); + render(); + + await user.type(screen.getByLabelText('이메일'), 'a'); + expect(clearLoginErrorMock).toHaveBeenCalled(); + }); +}); diff --git a/src/hooks/use-auth/use-auth-login/index.ts b/src/hooks/use-auth/use-auth-login/index.ts index 0d5f2d0f..45165489 100644 --- a/src/hooks/use-auth/use-auth-login/index.ts +++ b/src/hooks/use-auth/use-auth-login/index.ts @@ -1,6 +1,6 @@ 'use client'; -import { useRouter, useSearchParams } from 'next/navigation'; +import { useSearchParams } from 'next/navigation'; import { useCallback, useState } from 'react'; @@ -24,22 +24,8 @@ const getLoginErrorMessage = (problem: CommonErrorResponse) => { return '로그인에 실패했습니다.'; }; -// 📜 proxy 설정 후 삭제 -// const isCommonErrorResponse = (e: unknown): e is CommonErrorResponse => { -// if (!e || typeof e !== 'object') return false; - -// const obj = e as Record; -// return ( -// typeof obj.status === 'number' && -// typeof obj.detail === 'string' && -// typeof obj.errorCode === 'string' && -// typeof obj.instance === 'string' -// ); -// }; - export const useLogin = () => { const searchParams = useSearchParams(); - const router = useRouter(); const [loginError, setLoginError] = useState(null); const clearLoginError = useCallback(() => setLoginError(null), []); @@ -56,16 +42,8 @@ export const useLogin = () => { setIsAuthenticated(true); const nextPath = normalizePath(searchParams.get('path')); - // window.location.replace(nextPath); - - router.replace(nextPath); + window.location.replace(nextPath); } catch (error) { - // if (isCommonErrorResponse(error)) { - // console.error('[LOGIN ERROR]', error.errorCode, error.detail); - // setLoginError(getLoginErrorMessage(error)); - // return; - // } - if (axios.isAxiosError(error)) { const axiosError = error as AxiosError; const problem = axiosError.response?.data; @@ -78,7 +56,7 @@ export const useLogin = () => { } console.error(error); - setLoginError('알 수 없는 오류가 발생했습니다.'); + setLoginError('로그인에 실패했습니다. 다시 시도해주세요.'); } }; diff --git a/src/hooks/use-auth/use-auth-logout/index.ts b/src/hooks/use-auth/use-auth-logout/index.ts index 71d20b67..905fcd78 100644 --- a/src/hooks/use-auth/use-auth-logout/index.ts +++ b/src/hooks/use-auth/use-auth-logout/index.ts @@ -1,7 +1,5 @@ 'use client'; -import { useRouter } from 'next/navigation'; - import { useQueryClient } from '@tanstack/react-query'; import { API } from '@/api'; @@ -9,7 +7,6 @@ import { userKeys } from '@/lib/query-key/query-key-user'; import { useAuth } from '@/providers'; export const useLogout = () => { - const router = useRouter(); const queryClient = useQueryClient(); const { setIsAuthenticated } = useAuth(); @@ -24,8 +21,7 @@ export const useLogout = () => { queryClient.removeQueries({ queryKey: userKeys.all }); setIsAuthenticated(false); - - router.push('/'); + window.location.replace('/'); } }; diff --git a/src/hooks/use-auth/use-auth-signup/index.ts b/src/hooks/use-auth/use-auth-signup/index.ts index 859f3445..6afbf42c 100644 --- a/src/hooks/use-auth/use-auth-signup/index.ts +++ b/src/hooks/use-auth/use-auth-signup/index.ts @@ -11,9 +11,7 @@ export const useSignup = () => { const handleSignup = async (payload: SignupRequest, formApi: { reset: () => void }) => { try { - const result = await API.authService.signup(payload); - // 📜 추후 삭제 - console.log('signup success:', result); + await API.authService.signup(payload); formApi.reset(); router.push('/login'); @@ -27,7 +25,7 @@ export const useSignup = () => { alert(problem.detail || '회원가입에 실패했습니다.'); } else { console.error(error); - alert('알 수 없는 오류가 발생했습니다.'); + alert('회원가입에 실패했습니다. 잠시 후에 다시 시도해주세요.'); } } }; diff --git a/src/hooks/use-auth/use-auth-withdraw/index.ts b/src/hooks/use-auth/use-auth-withdraw/index.ts index 78f3a689..a0891d31 100644 --- a/src/hooks/use-auth/use-auth-withdraw/index.ts +++ b/src/hooks/use-auth/use-auth-withdraw/index.ts @@ -12,7 +12,7 @@ export const useWithdraw = () => { } catch (error) { // 📜 에러 UI 결정나면 변경 console.error('[WITHDRAW ERROR]', error); - alert('회원탈퇴에 실패했습니다.'); + alert('회원탈퇴에 실패했습니다. 잠시 후에 다시 시도해주세요.'); } };