Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FE] useError, useToast 테스트코드 작성 #387

Merged
merged 16 commits into from
Aug 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
e316891
style: errorBody -> errorInfo로 이름 변경
pakxe Aug 16, 2024
dda91e8
test: jest에서 svg파일을 못읽으므로 이를 모킹해 오류가 발생하지 않도록 대처
pakxe Aug 16, 2024
a3438d0
chore: coverage에서 errorProvider가 보이도록 ignore에서 제거
pakxe Aug 16, 2024
dafaa91
feat: 불필요하게 낭비되는 상태인 hasError를 제거하고 errorMessage는 클라이언트에서 보여지는 에러메세지이…
pakxe Aug 16, 2024
8fd839a
refactor: ErrorProvider에서 useError를 파일로 분리
pakxe Aug 16, 2024
399f100
test: 전역 에러 상태를 위한 useError 훅의 테스트 코드 작성
pakxe Aug 16, 2024
792e458
chore: 파일 위치 변경으로 인한 import 경로 수정
pakxe Aug 16, 2024
7588615
chore: toBeInTheDocument 를 사용하기 위한 라이브러리 import
pakxe Aug 16, 2024
23c38d4
chore: toBeInTheDocument 를 사용하기 위한 라이브러리 설치
pakxe Aug 16, 2024
3320688
chore: Property 'toBeInTheDocument' does not exist on type 'JestMatch…
pakxe Aug 16, 2024
c6193f1
refactor: ToastProvider에서 useToast분리
pakxe Aug 16, 2024
cf9072d
test: 핸들링 가능한 에러발생 시 토스트를 띄우고, 핸들링 불가능한 에러 발생 시 토스트를 띄우지 않는지 테스트 코드 작성
pakxe Aug 16, 2024
e57509a
feat: Toast 컴포넌트에 토스트 엘리먼트 식별을 위한 id='toast' 추가
pakxe Aug 16, 2024
7ecd473
chore: 파일을 hooks내부로 이동
pakxe Aug 16, 2024
422925d
chore: 파일 이동으로 인한 import 경로 수정
pakxe Aug 16, 2024
bd26428
chore: components폴더 내부는 커버리지에 포함되지 않도록 ignore에 추가
pakxe Aug 16, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion client/jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const config: Config = {
'<rootDir>/src/request/',
'<rootDir>/src/constants/',
'<rootDir>/src/errors/',
'<rootDir>/src/ErrorProvider.tsx',
'<rootDir>/src/components/',
],

verbose: true,
Expand All @@ -34,6 +34,7 @@ const config: Config = {
'^@types/(.*)$': '<rootDir>/src/types/$1',
'^@errors/(.*)$': '<rootDir>/src/errors/$1',
'^@mocks/(.*)$': '<rootDir>/src/mocks/$1',
'\\.svg$': '<rootDir>/src/mocks/svg.ts',
},
testEnvironmentOptions: {
customExportConditions: [''],
Expand Down
1 change: 1 addition & 0 deletions client/jest.setup.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {server} from './src/mocks/server';
import * as router from 'react-router';
import '@testing-library/jest-dom'; // toBeInTheDocument를 인식하기 위해 @testing-library/jest-dom/extend-expect추가

beforeAll(() => {
server.listen();
Expand Down
11 changes: 11 additions & 0 deletions client/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"@types/react": "^18.3.3",
"@types/react-copy-to-clipboard": "^5.0.7",
"@types/react-dom": "^18.3.0",
"@types/testing-library__jest-dom": "^6.0.0",
"@typescript-eslint/eslint-plugin": "^7.16.0",
"@typescript-eslint/parser": "^7.16.0",
"cypress": "^13.13.2",
Expand Down
4 changes: 2 additions & 2 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import {Outlet} from 'react-router-dom';
import {HDesignProvider} from 'haengdong-design';
import {Global} from '@emotion/react';

import {ToastProvider} from '@components/Toast/ToastProvider';
import {ToastProvider} from '@hooks/useToast/ToastProvider';

import {GlobalStyle} from './GlobalStyle';
// import toast from 'react-simple-toasts';
import {ErrorProvider} from './ErrorProvider';
import {ErrorProvider} from './hooks/useError/ErrorProvider';
import UnhandledErrorBoundary from './UnhandledErrorBoundary';

const App: React.FC = () => {
Expand Down
83 changes: 0 additions & 83 deletions client/src/ErrorProvider.tsx

This file was deleted.

12 changes: 6 additions & 6 deletions client/src/apis/fetcher.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {ServerError} from 'ErrorProvider';
import {ErrorInfo} from '@hooks/useError/ErrorProvider';

import objectToQueryString from '@utils/objectToQueryString';

Expand Down Expand Up @@ -96,23 +96,23 @@ const errorHandler = async ({url, options, body}: ErrorHandlerProps) => {
const response: Response = await fetch(url, options);

if (!response.ok) {
const serverErrorBody: ServerError = await response.json();
const serverErrorInfo: ErrorInfo = await response.json();

throw new FetchError({
status: response.status,
requestBody: body,
endpoint: response.url,
errorBody: serverErrorBody,
name: serverErrorBody.errorCode,
message: serverErrorBody.message || '',
errorInfo: serverErrorInfo,
name: serverErrorInfo.errorCode,
message: serverErrorInfo.message || '',
method: options.method,
});
}

return response;
} catch (error) {
if (error instanceof Error) {
throw error;
throw error; // 그대로 FetchError || Error 인스턴스를 던집니다.
}

throw new Error(UNKNOWN_ERROR);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import type {MemberAction, MemberType} from 'types/serviceType';

import {BottomSheet, Flex, Input, Text, IconButton, FixedButton, Icon} from 'haengdong-design';

import {useToast} from '@components/Toast/ToastProvider';
import useDeleteMemberAction from '@hooks/useDeleteMemberAction/useDeleteMemberAction';
import {useToast} from '@hooks/useToast/useToast';

import {bottomSheetHeaderStyle, bottomSheetStyle, inputGroupStyle} from './DeleteMemberActionModal.style';

Expand Down
2 changes: 1 addition & 1 deletion client/src/components/Toast/Toast.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ const Toast = ({
};

return createPortal(
<div css={toastMarginStyle({...styleProps})} {...htmlProps} onClick={handleClickToClose}>
<div css={toastMarginStyle({...styleProps})} {...htmlProps} onClick={handleClickToClose} id="toast">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

지금 당장은 토스트가 한 개만 띄워주어서 크게 상관은 없는데 여러 개를 띄워줄 경우 id보다는 class로 짓는 것이 더 안전해보입니다~

<div css={toastStyle(isVisible)}>
<Flex justifyContent="spaceBetween" alignItems="center">
<Flex alignItems="center" gap="0.5rem">
Expand Down
8 changes: 4 additions & 4 deletions client/src/errors/FetchError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@ class FetchError extends Error {
requestBody;
status;
endpoint;
errorBody;
errorInfo;
method;

constructor({requestBody, status, endpoint, errorBody, method, name, message}: FetchErrorType) {
super(errorBody.errorCode);
constructor({requestBody, status, endpoint, errorInfo, method, name, message}: FetchErrorType) {
super(errorInfo.errorCode);

this.requestBody = requestBody;
this.status = status;
this.endpoint = endpoint;
this.errorBody = errorBody;
this.errorInfo = errorInfo;
this.method = method;
this.name = name;
this.message = message;
Expand Down
20 changes: 11 additions & 9 deletions client/src/hooks/useAuth/useAuth.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ import {renderHook, waitFor} from '@testing-library/react';
import {act} from 'react';
import {MemoryRouter} from 'react-router-dom';

import {useError} from '@hooks/useError/useError';

import {PASSWORD_LENGTH} from '@constants/password';

import {VALID_PASSWORD_FOR_TEST, VALID_TOKEN_FOR_TEST} from '@mocks/validValueForTest';

import {ErrorProvider, useError} from '../../ErrorProvider';
import {ErrorProvider} from '../useError/ErrorProvider';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hooks 사용하면 좋을 것 같아효!


import useAuth from './useAuth';

Expand Down Expand Up @@ -34,7 +36,7 @@ describe('useAuth', () => {
});

await waitFor(() => {
expect(result.current.errorResult.error?.errorCode).toBe('TOKEN_NOT_FOUND');
expect(result.current.errorResult.errorInfo?.errorCode).toBe('TOKEN_NOT_FOUND');
});
});

Expand All @@ -48,7 +50,7 @@ describe('useAuth', () => {
});

await waitFor(() => {
expect(result.current.errorResult.error).toBe(null);
expect(result.current.errorResult.errorInfo).toBe(null);
});
});

Expand All @@ -62,7 +64,7 @@ describe('useAuth', () => {
});

await waitFor(() => {
expect(result.current.errorResult.error?.errorCode).toBe('TOKEN_INVALID');
expect(result.current.errorResult.errorInfo?.errorCode).toBe('TOKEN_INVALID');
});
});

Expand All @@ -76,7 +78,7 @@ describe('useAuth', () => {
});

await waitFor(() => {
expect(result.current.errorResult.error?.errorCode).toBe('TOKEN_EXPIRED');
expect(result.current.errorResult.errorInfo?.errorCode).toBe('TOKEN_EXPIRED');
});
});

Expand All @@ -90,7 +92,7 @@ describe('useAuth', () => {
});

await waitFor(() => {
expect(result.current.errorResult.error?.errorCode).toBe('FORBIDDEN');
expect(result.current.errorResult.errorInfo?.errorCode).toBe('FORBIDDEN');
});
});
});
Expand All @@ -104,7 +106,7 @@ describe('useAuth', () => {
});

await waitFor(() => {
expect(result.current.errorResult.error).toBe(null);
expect(result.current.errorResult.errorInfo).toBe(null);
});
});

Expand All @@ -116,7 +118,7 @@ describe('useAuth', () => {
});

await waitFor(() => {
expect(result.current.errorResult.error?.errorCode).toBe('EVENT_PASSWORD_FORMAT_INVALID');
expect(result.current.errorResult.errorInfo?.errorCode).toBe('EVENT_PASSWORD_FORMAT_INVALID');
});
});

Expand All @@ -128,7 +130,7 @@ describe('useAuth', () => {
});

await waitFor(() => {
expect(result.current.errorResult.error?.errorCode).toBe('PASSWORD_INVALID');
expect(result.current.errorResult.errorInfo?.errorCode).toBe('PASSWORD_INVALID');
});
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {BillStep, MemberAction, MemberStep} from 'types/serviceType';

import stepListJson from '../../mocks/stepList.json';
import StepListProvider, {useStepList} from '../useStepList/useStepList';
import {ErrorProvider} from '../../ErrorProvider';
import {ErrorProvider} from '../useError/ErrorProvider';
import invalidMemberStepListJson from '../../mocks/invalidMemberStepList.json';

import useDeleteMemberAction from './useDeleteMemberAction';
Expand Down
69 changes: 69 additions & 0 deletions client/src/hooks/useError/ErrorProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import {createContext, useState, useEffect, ReactNode} from 'react';

import {SERVER_ERROR_MESSAGES} from '@constants/errorMessage';

// 에러 컨텍스트 생성
export interface ErrorContextType {
clientErrorMessage: string;
setErrorInfo: (error: ErrorInfo) => void;
clearError: (ms?: number) => void;
errorInfo: ErrorInfo | null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

에러가 없으면 null 좋아요~~

}

export const ErrorContext = createContext<ErrorContextType | undefined>(undefined);

// 에러 컨텍스트를 제공하는 프로바이더 컴포넌트
interface ErrorProviderProps {
children: ReactNode;
callback?: (message: string) => void;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 콜백은 에러메시지를 받아서 어떤 것을 처리해주는 함수인가요!?

}

export type ErrorInfo = {
errorCode: string;
message: string;
};

export const ErrorProvider = ({children, callback}: ErrorProviderProps) => {
const [clientErrorMessage, setClientErrorMessage] = useState('');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기도 errorMessage가 단순 빈 문자열이 아니라 string | null로 에러가 없을 때는 null로 처리해주는 것이 어떨지 제안드립니다!

const [errorInfo, setErrorState] = useState<ErrorInfo | null>(null);

useEffect(() => {
if (errorInfo) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

스타일이지만 errorInfo가 null일 때는 early return을 해주는 것은 어떨까요?

if (isUnhandledError(errorInfo.errorCode)) {
// 에러바운더리로 보내기

throw errorInfo;
}

const message = SERVER_ERROR_MESSAGES[errorInfo.errorCode];
setClientErrorMessage(message);
// callback(message);
}
}, [errorInfo, callback]);

const setErrorInfo = (error: ErrorInfo) => {
setClientErrorMessage('');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기서 새로운 error를 setting 해줄 때 client error message를 빈 문자열로 해주는 이유가 궁금해요!

setErrorState(error);
};

const clearError = (ms: number = 0) => {
if (errorInfo === null) return;

setTimeout(() => {
setClientErrorMessage('');
setErrorState(null);
}, ms);
};

return (
<ErrorContext.Provider value={{errorInfo, clientErrorMessage, setErrorInfo, clearError}}>
{children}
</ErrorContext.Provider>
);
};

const isUnhandledError = (errorCode: string) => {
if (errorCode === 'INTERNAL_SERVER_ERROR') return true;

return SERVER_ERROR_MESSAGES[errorCode] === undefined;
};
Loading
Loading