Skip to content

Commit

Permalink
Merge branch 'develop' into fe/test/210-review-list-page-api-test
Browse files Browse the repository at this point in the history
  • Loading branch information
soosoo22 authored Aug 6, 2024
2 parents 94f7530 + 36ebd7a commit aa92f02
Show file tree
Hide file tree
Showing 15 changed files with 725 additions and 818 deletions.
2 changes: 2 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"dev": "webpack-dev-server --mode=development --open --hot --progress",
"start": "webpack serve --open --config webpack.config.js",
"build": "webpack --config webpack.config.js",
"serve": "http-server ./dist",
"lint:styles": "stylelint \"src/**/styles.ts\" --fix",
"test": "jest"
},
Expand Down Expand Up @@ -50,6 +51,7 @@
"eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-react-refresh": "^0.4.8",
"html-webpack-plugin": "^5.6.0",
"http-server": "^14.1.1",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"jest-fixed-jsdom": "^0.0.2",
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/apis/endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ export const DETAILED_REVIEW_API_PARAMS = {
},
};

export const DETAILED_REVIEW_API_URL = `${process.env.API_BASE_URL}/${DETAILED_REVIEW_API_PARAMS.resource}`;

export const REVIEW_WRITING_API_PARAMS = {
queryString: {
reviewRequestCode: 'reviewRequestCode',
Expand All @@ -14,7 +16,7 @@ export const REVIEW_WRITING_API_PARAMS = {
const endPoint = {
postingReview: `${process.env.API_BASE_URL}/reviews`,
gettingDetailedReview: (reviewId: number, memberId: number) =>
`${process.env.API_BASE_URL}/${DETAILED_REVIEW_API_PARAMS.resource}/${reviewId}?${DETAILED_REVIEW_API_PARAMS.queryString.memberId}=${memberId}`,
`${DETAILED_REVIEW_API_URL}/${reviewId}?${DETAILED_REVIEW_API_PARAMS.queryString.memberId}=${memberId}`,
gettingDataToWriteReview: (reviewRequestCode: string) =>
`${process.env.API_BASE_URL}/reviews/write?${REVIEW_WRITING_API_PARAMS.queryString.reviewRequestCode}=${reviewRequestCode}`,
gettingReviewList: `${process.env.API_BASE_URL}/${DETAILED_REVIEW_API_PARAMS.resource}`,
Expand Down
1 change: 1 addition & 0 deletions frontend/src/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from './page';
export * from './errorMessage';
export * from './review';
export * from './queryKeys';
export * from './system';
4 changes: 4 additions & 0 deletions frontend/src/constants/system.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const DEV_ENVIRONMENT = {
port: '3000',
hostname: 'localhost',
};
2 changes: 2 additions & 0 deletions frontend/src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ export { default as useSidebar } from './useSidebar';
export { default as useModalClose } from './useModalClose';
export { default as useGroupAccessCode } from './useGroupAccessCode';
export { default as useGetReviewList } from './useGetReviewList';
export { default as useGetDetailedReview } from './review/useGetDetailedReview';
export { default as useSearchParamAndQuery } from './useSearchParamAndQuery';
33 changes: 33 additions & 0 deletions frontend/src/hooks/review/useGetDetailedReview/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { useSuspenseQuery } from '@tanstack/react-query';

import { getDetailedReviewApi } from '@/apis/review';
import { REVIEW_QUERY_KEYS } from '@/constants';
import { DetailReviewData } from '@/types';

interface UseGetDetailedReviewProps {
reviewId: number;
memberId: number;
groupAccessCode: string;
}

interface FetchDetailedReviewParams {
reviewId: number;
memberId: number;
groupAccessCode: string;
}

const useGetDetailedReview = ({ reviewId, memberId, groupAccessCode }: UseGetDetailedReviewProps) => {
const fetchDetailedReview = async ({ reviewId, memberId, groupAccessCode }: FetchDetailedReviewParams) => {
const result = await getDetailedReviewApi({ reviewId, memberId, groupAccessCode });
return result;
};

const result = useSuspenseQuery<DetailReviewData>({
queryKey: [REVIEW_QUERY_KEYS.detailedReview, reviewId, memberId],
queryFn: () => fetchDetailedReview({ reviewId, memberId, groupAccessCode }),
});

return result;
};

export default useGetDetailedReview;
26 changes: 26 additions & 0 deletions frontend/src/hooks/review/useGetDetailedReview/test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { renderHook, waitFor } from '@testing-library/react';

import { DETAILED_PAGE_MOCK_API_SETTING_VALUES } from '@/mocks/mockData/detailedReviewMockData';
import QueryClientWrapper from '@/queryTestSetup/QueryClientWrapper';

import useGetDetailedReview from '.';
// 아래의 테스트는 로그인이 유효하다는 가정하에서 진행

describe('리뷰 상세페이지 데이터 요청 테스트', () => {
const GROUND_ACCESS_CODE = '1234';

it('유효힌 id,memberId 사용해야 라뷰 상세 페이지 데이터를 불러온다.', async () => {
const { reviewId, memberId } = DETAILED_PAGE_MOCK_API_SETTING_VALUES;

const { result } = renderHook(
() => useGetDetailedReview({ reviewId, memberId, groupAccessCode: GROUND_ACCESS_CODE }),
{ wrapper: QueryClientWrapper },
);

await waitFor(() => {
expect(result.current.status).toBe('success');
});

expect(result.current.data).toBeDefined();
});
});
22 changes: 22 additions & 0 deletions frontend/src/hooks/useSearchParamAndQuery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { useLocation, useParams } from 'react-router';

interface UseSearchParamAndQueryProps {
paramKey: string;
queryStringKey?: string;
}
/**
* url에서 원하는 param, queryString의 값을 가져온다.
* @param paramKey: 가져오고 싶은 param의 key
* @param queryStringKey: 가져오고 싶은 queryString의 key (옵셔널)
*/
const useSearchParamAndQuery = ({ paramKey, queryStringKey }: UseSearchParamAndQueryProps) => {
const location = useLocation();
const queryParams = new URLSearchParams(location.search);

return {
param: useParams()[`${paramKey}`],
queryString: queryStringKey ? queryParams.get(queryStringKey) : null,
};
};

export default useSearchParamAndQuery;
39 changes: 26 additions & 13 deletions frontend/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { RecoilRoot } from 'recoil';

import App from '@/App';

import { DEV_ENVIRONMENT } from './constants';
import DetailedReviewPage from './pages/DetailedReviewPage';
import ErrorPage from './pages/ErrorPage';
import LandingPage from './pages/LandingPage';
Expand Down Expand Up @@ -47,7 +48,7 @@ const router = createBrowserRouter([
element: <ReviewListPage />,
},
{
path: 'user/detailed-review/:id',
path: 'user/detailed-review/:reviewId',
element: <DetailedReviewPage />,
},
{
Expand All @@ -60,15 +61,27 @@ const router = createBrowserRouter([

const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);

root.render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<ThemeProvider theme={theme}>
<Global styles={globalStyles} />
<RecoilRoot>
<RouterProvider router={router} />
</RecoilRoot>
</ThemeProvider>
</QueryClientProvider>
</React.StrictMode>,
);
async function enableMocking() {
const { hostname, port } = DEV_ENVIRONMENT;
const isDev = window?.location.hostname === hostname && window.location.port === port;

if (isDev) {
const { worker } = await import('./mocks/browser');
return worker.start();
}
}

enableMocking().then(() => {
root.render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<ThemeProvider theme={theme}>
<Global styles={globalStyles} />
<RecoilRoot>
<RouterProvider router={router} />
</RecoilRoot>
</ThemeProvider>
</QueryClientProvider>
</React.StrictMode>,
);
});
32 changes: 15 additions & 17 deletions frontend/src/mocks/handlers/review.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { http, HttpResponse } from 'msw';

import endPoint from '@/apis/endpoints';
import endPoint, { DETAILED_REVIEW_API_PARAMS, DETAILED_REVIEW_API_URL } from '@/apis/endpoints';

import {
DETAILED_REVIEW_MOCK_DATA,
DETAILED_PAGE_MOCK_API_SETTING_VALUES,
DETAILED_PAGE_ERROR_API_VALUES,
} from '../mockData/detailedReviewMockData';
import { REVIEW_LIST } from '../mockData/reviewListMockData';
import { DETAILED_REVIEW_MOCK_DATA, DETAILED_PAGE_MOCK_API_SETTING_VALUES } from '../mockData/detailedReviewMockData';
import { REVIEW_WRITING_DATA } from '../mockData/reviewWritingData';

export const PAGE = {
Expand All @@ -18,23 +19,20 @@ export const PAGE = {
};

const getDetailedReview = () =>
http.get(
endPoint.gettingDetailedReview(
DETAILED_PAGE_MOCK_API_SETTING_VALUES.reviewId,
DETAILED_PAGE_MOCK_API_SETTING_VALUES.memberId,
),
async () => {
http.get(new RegExp(`^${DETAILED_REVIEW_API_URL}/\\d+$`), async ({ request }) => {
//요청 url에서 reviewId, memberId 추출
const url = new URL(request.url);
const urlReviewId = url.pathname.replace(`/${DETAILED_REVIEW_API_PARAMS.resource}/`, '');
const urlMemberId = url.searchParams.get(DETAILED_REVIEW_API_PARAMS.queryString.memberId);

const { reviewId, memberId } = DETAILED_PAGE_MOCK_API_SETTING_VALUES;
// 유효한 reviewId, memberId일 경우에만 데이터 반환
if (Number(urlReviewId) == reviewId && Number(urlMemberId) === memberId) {
return HttpResponse.json(DETAILED_REVIEW_MOCK_DATA);
},
);

const getWrongDetailReview = () =>
http.get(
endPoint.gettingDetailedReview(DETAILED_PAGE_ERROR_API_VALUES.reviewId, DETAILED_PAGE_ERROR_API_VALUES.memberId),
async () => {
return HttpResponse.json({ error: '잘못된 상세리뷰 요청' }, { status: 404 });
},
);
}

return HttpResponse.json({ error: '잘못된 상세리뷰 요청' }, { status: 404 });
});

const getDataToWriteReview = () =>
http.get(endPoint.gettingDataToWriteReview(10), async ({ request }) => {
Expand Down
5 changes: 0 additions & 5 deletions frontend/src/mocks/mockData/detailedReviewMockData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,6 @@ export const DETAILED_PAGE_MOCK_API_SETTING_VALUES = {
memberId: 2,
};

export const DETAILED_PAGE_ERROR_API_VALUES = {
reviewId: 0,
memberId: 0,
};

const ANSWER =
'림순의 바람은 그윽한 산들바람처럼 잔잔하게 흘러갔습니다. \n 눈부신 햇살이 그의 어깨를 감싸며, 푸른 하늘 아래 펼쳐진 들판을 바라보았습니다.\n 그의 마음은 자연의 아름다움 속에서 평온을 찾았고, 그 순간마다 삶의 소중함을 느꼈습니다.\n 그는 늘 그러한 순간들을 기억하며, 미래의 나날들을 기대했습니다. \n 바람은 여전히 그를 감싸며, 그의 마음 속 깊은 곳에 있는 꿈과 희망을 불러일으켰습니다.\n 림순은 미소 지으며 앞으로 나아갔습니다.림순의 바람은 그윽한 산들바람처럼 잔잔하게 흘러갔습니다. \n 눈부신 햇살이 그의 어깨를 감싸며, 푸른 하늘 아래 펼쳐진 들판을 바라보았습니다.\n 그의 마음은 자연의 아름다움 속에서 평온을 찾았고, 그 순간마다 삶의 소중함을 느꼈습니다.\n 그는 늘 그러한 순간들을 기억하며, 미래의 나날들을 기대했습니다. 림순의 바람은 그윽한 산들바람처럼 잔잔하게 흘러갔습니다. \n 눈부신 햇살이 그의 어깨를 감싸며, 푸른 하늘 아래 펼쳐진 들판을 바라보았습니다.\n 그의 마음은 자연의 아름다움 속에서 평온을 찾았고, 그 순간마다 삶의 소중함을 느꼈습니다.\n 그는 늘 그러한 순간들을 기억하며, 미래의 나날들을 기대했습니다. \n 바람은 여전히 그를 감싸며, 그의 마음 속 깊은 곳에 있는 꿈과 희망을 불러일으켰습니다.\n 림순은 미소 지으며 앞으로 나아갔습니다.림순의 바람은 그윽한 산들바람처럼 잔잔하게 흘러갔습니다. \n 눈부신 햇살이 그의 어깨를 감싸며, 푸른 하늘 아래 펼쳐진 들판을 바라보았습니다.\n 그의 마음은 자연의 아름다움 속에서 평온을 찾았고, 그 순간마다 삶의 소중함을 느꼈습니다.\n 그는 늘 그러한 순간들을 기억하며, 미래의 나날들을 기대했습니다. ';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,36 +1,24 @@
import { useSuspenseQuery } from '@tanstack/react-query';
import { useLocation, useParams } from 'react-router';

import { DETAILED_REVIEW_API_PARAMS } from '@/apis/endpoints';
import { getDetailedReviewApi } from '@/apis/review';
import { LoginRedirectModal } from '@/components';
import { REVIEW_QUERY_KEYS } from '@/constants';
import { useGetDetailedReview, useSearchParamAndQuery } from '@/hooks';
import { ReviewDescription, ReviewSection, KeywordSection } from '@/pages/DetailedReviewPage/components';
import { DetailReviewData } from '@/types';

import * as S from './styles';

interface DetailedReviewPageContentsProps {
groupAccessCode: string;
}
const DetailedReviewPageContents = ({ groupAccessCode }: DetailedReviewPageContentsProps) => {
const { id } = useParams<{ id: string }>();
const location = useLocation();
const queryParams = new URLSearchParams(location.search);
const memberId = queryParams.get(DETAILED_REVIEW_API_PARAMS.queryString.memberId);

const fetchDetailedReview = async (reviewId: number, memberId: number, groupAccessCode: string) => {
const result = await getDetailedReviewApi({ reviewId, memberId, groupAccessCode });
return result;
};
const { param: reviewId, queryString: memberId } = useSearchParamAndQuery({
paramKey: 'reviewId',
queryStringKey: DETAILED_REVIEW_API_PARAMS.queryString.memberId,
});

const { data: detailedReview } = useSuspenseQuery<DetailReviewData>({
queryKey: [REVIEW_QUERY_KEYS.detailedReview, id, memberId],
queryFn: () => fetchDetailedReview(Number(id), Number(memberId), groupAccessCode),
const { data: detailedReview } = useGetDetailedReview({
reviewId: Number(reviewId),
memberId: Number(memberId),
groupAccessCode,
});

if (!detailedReview) throw new Error(' 상세보기 리뷰 데이터를 가져올 수 없어요.');
if (!groupAccessCode) return <LoginRedirectModal />;
// TODO: 리뷰 공개/비공개 토글 버튼 기능
return (
<S.DetailedReviewPageContents>
Expand Down
14 changes: 5 additions & 9 deletions frontend/src/pages/DetailedReviewPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,12 @@ import { DetailedReviewPageContents } from './components';
const DetailedReviewPage = () => {
const { groupAccessCode } = useGroupAccessCode();

if (!groupAccessCode) return <LoginRedirectModal />;

return (
<>
{groupAccessCode ? (
<ErrorSuspenseContainer>
<DetailedReviewPageContents groupAccessCode={groupAccessCode} />
</ErrorSuspenseContainer>
) : (
<LoginRedirectModal />
)}
</>
<ErrorSuspenseContainer>
<DetailedReviewPageContents groupAccessCode={groupAccessCode} />
</ErrorSuspenseContainer>
);
};

Expand Down
11 changes: 11 additions & 0 deletions frontend/src/queryTestSetup/QueryClientWrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

import { EssentialPropsWithChildren } from '@/types';

const queryClient = new QueryClient();

const QueryClientWrapper = ({ children }: EssentialPropsWithChildren) => (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
);

export default QueryClientWrapper;
Loading

0 comments on commit aa92f02

Please sign in to comment.