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] refactor: 리뷰 그룹 생성 API 요청 함수 및 MSW 핸들러, 테스트 추가 #420

Merged
merged 9 commits into from
Aug 19, 2024
2 changes: 1 addition & 1 deletion frontend/src/apis/endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const endPoint = {
gettingDataToWriteReview: (reviewRequestCode: string) =>
`${REVIEW_WRITING_API_URL}/${REVIEW_WRITING_API_PARAMS.queryString.write}?${REVIEW_WRITING_API_PARAMS.queryString.reviewRequestCode}=${reviewRequestCode}`,
gettingReviewList: `${process.env.API_BASE_URL}/${VERSION2}/reviews`,
postingDataForURL: `${process.env.API_BASE_URL}/${VERSION2}/groups`,
postingDataForReviewRequestCode: `${process.env.API_BASE_URL}/${VERSION2}/groups`,
gettingPasswordValidation: (reviewRequestCode: string) =>
`${REVIEW_PASSWORD_API_URL}?${REVIEW_PASSWORD_API_PARAMS.queryString.reviewRequestCode}=${reviewRequestCode}`,
gettingReviewGroupData: (reviewRequestCode: string) =>
Expand Down
10 changes: 5 additions & 5 deletions frontend/src/apis/group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,19 @@ import { PasswordResponse, ReviewGroupData } from '@/types';
import createApiErrorMessage from './apiErrorMessageCreator';
import endPoint from './endpoints';

//리뷰 그룹 생성
export interface DataForURL {
export interface DataForReviewRequestCode {
revieweeName: string;
projectName: string;
groupAccessCode: string;
}

export const postDataForURLApi = async (dataForURL: DataForURL) => {
const response = await fetch(endPoint.postingDataForURL, {
export const postDataForReviewRequestCodeApi = async (dataForReviewRequestCode: DataForReviewRequestCode) => {
const response = await fetch(endPoint.postingDataForReviewRequestCode, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(dataForURL),
body: JSON.stringify(dataForReviewRequestCode),
});

if (!response.ok) {
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/constants/queryKeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export const REVIEW_QUERY_KEYS = {
};

export const GROUP_QUERY_KEY = {
dataForURL: 'dataForURL',
dataForReviewRequestCode: 'dataForReviewRequestCode',
password: 'password',
reviewGroupData: 'reviewGroupData',
};
2 changes: 2 additions & 0 deletions frontend/src/constants/routes.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
// TODO: ROUTES -> ROUTE 및 상수 인덱스에 추가하기
export const ROUTES = {
home: '/',
reviewList: 'user/review-list',
reviewWriting: 'user/review-writing',
reviewWritingComplete: 'user/review-writing-complete',
detailedReview: 'user/detailed-review',
reviewDashboard: 'user/reviewDashboard',
};
19 changes: 13 additions & 6 deletions frontend/src/mocks/handlers/group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,26 @@ import { API_ERROR_MESSAGE } from '@/constants';
import { PasswordResponse } from '@/types';

import {
CREATED_GROUP_DATA,
CREATED_REVIEW_REQUEST_CODE,
REVIEW_GROUP_DATA,
VALID_REVIEW_GROUP_REVIEW_REQUEST_CODE,
VALIDATED_PASSWORD,
} from '../mockData/group';

// NOTE: URL 생성 정상 응답
const postDataForUrl = () => {
return http.post(endPoint.postingDataForURL, async () => {
return HttpResponse.json(CREATED_GROUP_DATA, { status: 200 });
// NOTE: reviewRequestCode 생성 정상 응답
const postDataForReviewRequestCode = () => {
return http.post(endPoint.postingDataForReviewRequestCode, async () => {
return HttpResponse.json(CREATED_REVIEW_REQUEST_CODE, { status: 200 });
});
};

// NOTE: reviewRequestCode 생성 에러 응답
// const postDataForReviewRequestCode = () => {
// return http.post(endPoint.postingDataForReviewRequestCode, async () => {
// return HttpResponse.json({ error: '서버 에러 테스트' }, { status: 500 });
// });
// };

const getPassWordValidation = () => {
return http.get(new RegExp(`^${REVIEW_PASSWORD_API_URL}`), async ({ request }) => {
const url = new URL(request.url);
Expand Down Expand Up @@ -67,6 +74,6 @@ const getReviewGroupData = () => {
return HttpResponse.json({ error: '잘못된 리뷰 그룹 데이터 요청' }, { status: 404 });
});
};
const groupHandler = [postDataForUrl(), getReviewGroupData(), getPassWordValidation()];
const groupHandler = [postDataForReviewRequestCode(), getReviewGroupData(), getPassWordValidation()];

export default groupHandler;
11 changes: 1 addition & 10 deletions frontend/src/mocks/mockData/group.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,11 @@
import { ReviewGroupData } from '@/types';

export const CREATED_GROUP_DATA = {
export const CREATED_REVIEW_REQUEST_CODE = {
reviewRequestCode: 'mocked-reviewRequestCode',
groupAccessCode: 'mocked-groupAccessCode',
};

export const VALIDATED_PASSWORD = '1234';

export const INVALID_GROUP_ACCESS_CODE = {
type: 'about:blank',
title: 'Bad Request',
status: 400,
detail: '올바르지 않은 확인 코드입니다.',
instance: '/reviews',
};

/**
* 리뷰 연결 페이지에서 사용하는 리뷰 그룹 정보
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ import Checkbox from '@/components/common/Checkbox';
import { CopyTextButton } from '../index';

import * as S from './styles';
interface ReviewURLModalProps {
reviewURL: string;
interface ReviewDashboardURLModalProps {
reviewDashboardURL: string;
closeModal: () => void;
}

const ReviewURLModal = ({ reviewURL, closeModal }: ReviewURLModalProps) => {
const ReviewURLModal = ({ reviewDashboardURL, closeModal }: ReviewDashboardURLModalProps) => {
const [isChecked, setIsChecked] = useState(false);

const handleCheckboxClick = () => {
Expand All @@ -28,24 +28,24 @@ const ReviewURLModal = ({ reviewURL, closeModal }: ReviewURLModalProps) => {
handleClose={null}
isClosableOnBackground={false}
>
<S.ReviewURLModal>
<S.ReviewDashboardURLModal>
<S.ModalTitle>아래 요청 URL을 확인해주세요</S.ModalTitle>
<S.ReviewURLModalItem>
<S.ReviewDashboardURLModalItem>
<S.DataName>리뷰 요청 URL</S.DataName>
<S.Data>{reviewURL}</S.Data>
<CopyTextButton targetText={reviewURL} alt="리뷰 URL 복사하기"></CopyTextButton>
</S.ReviewURLModalItem>
<S.Data>{reviewDashboardURL}</S.Data>
<CopyTextButton targetText={reviewDashboardURL} alt="리뷰 대시보드 페이지 링크 복사하기"></CopyTextButton>
</S.ReviewDashboardURLModalItem>
<S.CheckContainer>
<Checkbox
id="is-confirmed-checkbox"
isChecked={isChecked}
handleChange={handleCheckboxClick}
$style={{ width: '2.3rem', height: '2.3rem' }}
/>
<S.CheckMessage>URL을 저장해두었어요!</S.CheckMessage>
<S.CheckMessage>링크를 저장해두었어요!</S.CheckMessage>
</S.CheckContainer>
<S.WarningMessage>* 창이 닫히면 URL을 다시 확인할 수 없어요!</S.WarningMessage>
</S.ReviewURLModal>
<S.WarningMessage>* 창이 닫히면 링크를 다시 확인할 수 없어요!</S.WarningMessage>
</S.ReviewDashboardURLModal>
</AlertModal>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import styled from '@emotion/styled';

export const ReviewURLModal = styled.div`
export const ReviewDashboardURLModal = styled.div`
display: flex;
flex-direction: column;
width: 52rem;
Expand All @@ -13,7 +13,7 @@ export const ModalTitle = styled.p`
margin-bottom: 4.5rem;
`;

export const ReviewURLModalItem = styled.div`
export const ReviewDashboardURLModalItem = styled.div`
display: flex;
gap: 1.8rem;
align-items: center;
Expand Down
31 changes: 19 additions & 12 deletions frontend/src/pages/HomePage/components/URLGeneratorForm/index.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { useEffect, useState } from 'react';

import { DataForURL } from '@/apis/group';
import { DataForReviewRequestCode } from '@/apis/group';
import { Button, Input, EyeButton } from '@/components';
import { ROUTES } from '@/constants/routes';
import { useEyeButton } from '@/hooks';
import useModals from '@/hooks/useModals';
import { debounce } from '@/utils/debounce';

import usePostDataForURL from '../../queries/usePostDataForURL';
import usePostDataForReviewRequestCode from '../../queries/usePostDataForReviewRequestCode';
import {
isValidReviewGroupDataInput,
isWithinLengthRange,
Expand All @@ -16,7 +17,7 @@ import {
MIN_PASSWORD_INPUT,
isValidPasswordInput,
} from '../../utils/validateInput';
import { FormLayout, ReviewURLModal } from '../index';
import { FormLayout, ReviewDashboardURLModal } from '../index';

import * as S from './styles';

Expand All @@ -34,14 +35,17 @@ const MODAL_KEYS = {
const URLGeneratorForm = () => {
const [revieweeName, setRevieweeName] = useState('');
const [projectName, setProjectName] = useState('');
// NOTE: 이 password는 groupAccessCode로 사용됨.
// groupAccessCode로 통일하기로 했지만 이미 이 페이지에서는 pwd로 작업한 게 많아서 놔두고
// API 요청 함수와 리액트 쿼리 코드에서는 groupAccessCode: password로 전달합니다
const [password, setPassword] = useState('');
const [reviewURL, setReviewURL] = useState('');
const [reviewDashboardURL, setReviewDashboardURL] = useState('');

const [revieweeNameErrorMessage, setRevieweeNameErrorMessage] = useState('');
const [projectNameErrorMessage, setProjectNameErrorMessage] = useState('');
const [passwordErrorMessage, setPasswordErrorMessage] = useState('');

const mutation = usePostDataForURL();
const mutation = usePostDataForReviewRequestCode();
const { isOff, handleEyeButtonToggle } = useEyeButton();
const { isOpen, openModal, closeModal } = useModals();

Expand All @@ -51,12 +55,12 @@ const URLGeneratorForm = () => {
isValidPasswordInput(password);

const postDataForURL = () => {
const dataForURL: DataForURL = { revieweeName, projectName };
const dataForReviewRequestCode: DataForReviewRequestCode = { revieweeName, projectName, groupAccessCode: password };

mutation.mutate(dataForURL, {
mutation.mutate(dataForReviewRequestCode, {
onSuccess: (data) => {
const completeURL = getCompleteURL(data.reviewRequestCode);
setReviewURL(completeURL);
const completeReviewDashboardURL = getCompleteReviewDashboardURL(data.reviewRequestCode);
setReviewDashboardURL(completeReviewDashboardURL);

resetInputs();
},
Expand All @@ -69,8 +73,8 @@ const URLGeneratorForm = () => {
setPassword('');
};

const getCompleteURL = (reviewRequestCode: string) => {
return `${window.location.origin}/user/review-writing/${reviewRequestCode}`;
const getCompleteReviewDashboardURL = (reviewRequestCode: string) => {
return `${window.location.origin}/${ROUTES.reviewDashboard}/${reviewRequestCode}`;
};

const handleNameInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
Expand Down Expand Up @@ -170,7 +174,10 @@ const URLGeneratorForm = () => {
리뷰 링크 생성하기
</Button>
{isOpen(MODAL_KEYS.confirm) && (
<ReviewURLModal reviewURL={reviewURL} closeModal={() => closeModal(MODAL_KEYS.confirm)} />
<ReviewDashboardURLModal
reviewDashboardURL={reviewDashboardURL}
closeModal={() => closeModal(MODAL_KEYS.confirm)}
/>
)}
</FormLayout>
</S.URLGeneratorForm>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/pages/HomePage/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export { default as FormBody } from './FormBody';
export { default as FormLayout } from './FormLayout';
export { default as ReviewURLModal } from './ReviewURLModal';
export { default as ReviewDashboardURLModal } from './ReviewDashboardURLModal';
export { default as URLGeneratorForm } from './URLGeneratorForm';
export { default as CopyTextButton } from './CopyTextButton';
export { default as ReviewMeOverview } from './ReviewMeOverview';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { renderHook, act, waitFor } from '@testing-library/react';

import QueryClientWrapper from '@/queryTestSetup/QueryClientWrapper';

import { CREATED_REVIEW_REQUEST_CODE } from '../../../mocks/mockData/group';
Copy link
Contributor

Choose a reason for hiding this comment

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

경로에 절대 경로 사용하면 어떨까요?

Suggested change
import { CREATED_REVIEW_REQUEST_CODE } from '../../../mocks/mockData/group';
import { CREATED_REVIEW_REQUEST_CODE } from '@/mocks/mockData/group';


import usePostDataForReviewRequestCode from './usePostDataForReviewRequestCode';

describe('usePostDataForReviewRequestCode', () => {
it('ReviewRequestCode를 발급받을 수 있다.', async () => {
// given
const dataForReviewRequestCode = {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
const dataForReviewRequestCode = {
const DATA_FOR_REVIEW_REQUEST_CODE= {

상수로 바꿔도 좋을 것 같아요.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

이 변수는 해당 테스트에서만 사용돼서 일단 별도로 상수화하지는 않았습니다!

revieweeName: 'ollie',
projectName: 'review-me',
groupAccessCode: '1234',
};

const { result } = renderHook(() => usePostDataForReviewRequestCode(), { wrapper: QueryClientWrapper });

// when
act(() => {
result.current.mutate(dataForReviewRequestCode);
});

await waitFor(() => expect(result.current.isSuccess).toBe(true));

// then
expect(result.current.data).toEqual(CREATED_REVIEW_REQUEST_CODE);
});
});
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';

import { DataForURL, postDataForURLApi } from '@/apis/group';
import { DataForReviewRequestCode, postDataForReviewRequestCodeApi } from '@/apis/group';
import { GROUP_QUERY_KEY } from '@/constants';

const usePostDataForURL = () => {
const usePostDataForReviewRequestCode = () => {
const queryClient = useQueryClient();

const { mutate, isSuccess, data } = useMutation({
mutationFn: (dataForURL: DataForURL) => postDataForURLApi(dataForURL),
mutationFn: (dataForReviewRequestCode: DataForReviewRequestCode) =>
postDataForReviewRequestCodeApi(dataForReviewRequestCode),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: [GROUP_QUERY_KEY.dataForURL] });
queryClient.invalidateQueries({ queryKey: [GROUP_QUERY_KEY.dataForReviewRequestCode] });
},
onError: (error) => {
console.error(error.message);
Expand All @@ -23,4 +24,4 @@ const usePostDataForURL = () => {
};
};

export default usePostDataForURL;
export default usePostDataForReviewRequestCode;
28 changes: 0 additions & 28 deletions frontend/src/pages/HomePage/queries/usePostDataForURL.test.tsx

This file was deleted.