From d3667a65bbe372d6e7c2f31d137097fd62ef6dab Mon Sep 17 00:00:00 2001 From: happhee Date: Sat, 15 Jun 2024 19:36:56 +0900 Subject: [PATCH 1/6] docs : move common to shared --- .../ExternalControlOpenDialog/index.tsx | 45 ------------------- 1 file changed, 45 deletions(-) delete mode 100644 src/common/components/ExternalControlOpenDialog/index.tsx diff --git a/src/common/components/ExternalControlOpenDialog/index.tsx b/src/common/components/ExternalControlOpenDialog/index.tsx deleted file mode 100644 index d6182735..00000000 --- a/src/common/components/ExternalControlOpenDialog/index.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import React from "react"; - -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from "@shared/components/ui/dialog" -export interface ExternalControlOpenDialogProps { - isOpen: boolean - setIsOpen: React.Dispatch> - title: React.ReactNode - content: React.ReactNode - footer?: React.ReactNode - description?: string -} - -export default function ExternalControlOpenDialog({ isOpen, setIsOpen, title, content, footer, description }: ExternalControlOpenDialogProps) { - return ( - - - - {title} - { - description && ( - - {description} - - ) - } - - {content} - { - footer && ( - - {footer} - - ) - } - - - ) -} From 10cc318a1f271b6e4a11937e03a185319cf01282 Mon Sep 17 00:00:00 2001 From: happhee Date: Sat, 15 Jun 2024 19:41:08 +0900 Subject: [PATCH 2/6] refactor : props name and interface prefix info --- src/app/problem/layout.tsx | 4 +- .../components/ChoiceFillCircleSvg/index.tsx | 6 ++- .../ExternalControlOpenDialog/index.tsx | 44 +++++++++++++++++++ .../components/CurriculumItem/index.tsx | 24 +++++----- .../components/CurriculumSection/index.tsx | 4 +- src/workbook/types/index.ts | 38 ++++++++-------- 6 files changed, 83 insertions(+), 37 deletions(-) create mode 100644 src/shared/components/ExternalControlOpenDialog/index.tsx diff --git a/src/app/problem/layout.tsx b/src/app/problem/layout.tsx index 641457fe..c43c2ecc 100644 --- a/src/app/problem/layout.tsx +++ b/src/app/problem/layout.tsx @@ -2,10 +2,10 @@ import React, { ReactNode } from "react"; import TopBar from "@common/components/TopBar"; -interface Props { +interface QuizLayoutProps { children: ReactNode; } -export default function QuizLayout({ children }: Props) { +export default function QuizLayout({ children }: QuizLayoutProps) { return (
diff --git a/src/problem/components/ChoiceFillCircleSvg/index.tsx b/src/problem/components/ChoiceFillCircleSvg/index.tsx index 59177501..f0121bfd 100644 --- a/src/problem/components/ChoiceFillCircleSvg/index.tsx +++ b/src/problem/components/ChoiceFillCircleSvg/index.tsx @@ -1,10 +1,12 @@ import React from "react"; -interface Props { +interface ChoiceFillCircleSvgProps { fill: string; } -export default function ChoiceFillCircleSvg({ fill }: Props) { +export default function ChoiceFillCircleSvg({ + fill, +}: ChoiceFillCircleSvgProps) { return ( >; + title: React.ReactNode; + content: React.ReactNode; + footer?: React.ReactNode; + description?: string; +} + +export default function ExternalControlOpenDialog({ + isOpen, + setIsOpen, + title, + content, + footer, + description, +}: ExternalControlOpenDialogProps) { + return ( + + + + {title} + {description && {description}} + + {content} + {footer && ( + + {footer} + + )} + + + ); +} diff --git a/src/workbook/components/CurriculumItem/index.tsx b/src/workbook/components/CurriculumItem/index.tsx index 1d588885..9ad157d3 100644 --- a/src/workbook/components/CurriculumItem/index.tsx +++ b/src/workbook/components/CurriculumItem/index.tsx @@ -1,17 +1,17 @@ -import { ICurriculumItem } from "@workbook/types" +import { CurriculumInfo } from "@workbook/types"; interface CurriculumItemProps { - day: number - item: ICurriculumItem + day: number; + item: CurriculumInfo; } export default function CurriculumItem({ day, item }: CurriculumItemProps) { - return ( -
- Day {day} - - {item.title} - -
- ) -} \ No newline at end of file + return ( +
+ Day {day} + + {item.title} + +
+ ); +} diff --git a/src/workbook/components/CurriculumSection/index.tsx b/src/workbook/components/CurriculumSection/index.tsx index 7635731b..3d4a03ea 100644 --- a/src/workbook/components/CurriculumSection/index.tsx +++ b/src/workbook/components/CurriculumSection/index.tsx @@ -1,9 +1,9 @@ -import { ICurriculumItem } from "@workbook/types"; +import { CurriculumInfo } from "@workbook/types"; import CurriculumItem from "../CurriculumItem"; interface CurriculumSectionProps { - curriculumItems: ICurriculumItem[]; + curriculumItems: CurriculumInfo[]; } export default function CurriculumSection({ diff --git a/src/workbook/types/index.ts b/src/workbook/types/index.ts index 6b24ab2b..7b1f0dba 100644 --- a/src/workbook/types/index.ts +++ b/src/workbook/types/index.ts @@ -1,26 +1,26 @@ -export interface ICurriculumItem { - id: number - title: string +export interface CurriculumInfo { + id: number; + title: string; } export interface Writer { - id: number; - name: string; -}; + id: number; + name: string; +} export interface Article { - id: number; - title: string; -}; + id: number; + title: string; +} export interface WorkbookInfo { - id: number; - name: number; - mainImageUrl: string; - title: string; - description: string; - category: string; - createdAt: string; - writerIds: Writer[]; - articles: Article[]; -}; + id: number; + name: number; + mainImageUrl: string; + title: string; + description: string; + category: string; + createdAt: string; + writerIds: Writer[]; + articles: Article[]; +} From 8775db14103bcfcc3e1edf814dd447acedc16434 Mon Sep 17 00:00:00 2001 From: happhee Date: Sat, 15 Jun 2024 19:59:34 +0900 Subject: [PATCH 3/6] docs : remove query file --- src/problem/remotes/getTagQueryOptions.ts | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 src/problem/remotes/getTagQueryOptions.ts diff --git a/src/problem/remotes/getTagQueryOptions.ts b/src/problem/remotes/getTagQueryOptions.ts deleted file mode 100644 index ee55c0e9..00000000 --- a/src/problem/remotes/getTagQueryOptions.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { UseQueryOptions } from "@tanstack/react-query"; - -import { ApiResponse, axiosRequest } from "@api/api-config"; - -import { apiRoutes } from "@shared/constants/apiRoutes"; - -import { TagInfo } from "@problem/types/tagInfo"; - -const getTags = (): Promise> => { - return axiosRequest("get", apiRoutes.tags); -}; -export const getTagQueryOptions = (): UseQueryOptions< - unknown, - unknown, - TagInfo -> => { - return { - queryKey: ["get-Tags"], - queryFn: () => getTags(), - }; -}; From 62250b851fcbb065268e8a19e5b7b803b9f092de Mon Sep 17 00:00:00 2001 From: happhee Date: Sat, 15 Jun 2024 20:00:17 +0900 Subject: [PATCH 4/6] refactor : createQueryProvider test component --- src/app/workbook/workbookpage.test.tsx | 71 ++++++++++--------- .../AnswerChoiceList.test.tsx | 7 +- src/shared/constants/createQueryProvider.tsx | 11 +++ 3 files changed, 53 insertions(+), 36 deletions(-) create mode 100644 src/shared/constants/createQueryProvider.tsx diff --git a/src/app/workbook/workbookpage.test.tsx b/src/app/workbook/workbookpage.test.tsx index 21bd5863..ffa22b87 100644 --- a/src/app/workbook/workbookpage.test.tsx +++ b/src/app/workbook/workbookpage.test.tsx @@ -1,69 +1,76 @@ /* eslint-disable react/display-name */ -import { ClassAttributes, ImgHTMLAttributes, JSX, ReactNode } from 'react'; +import { ClassAttributes, ImgHTMLAttributes, JSX } from "react"; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import {describe, expect, it, vi } from 'vitest'; +import { describe, expect, it, vi } from "vitest"; -import { useWorkbook } from '@workbook/remotes/getWorkbookQueryOptions'; +import { createQueryProviderWrapper } from "@shared/constants/createQueryProvider"; -import WorkbookPage from './[id]/page'; -import { render, renderHook,screen, waitFor } from '@testing-library/react'; +import { useWorkbook } from "@workbook/remotes/getWorkbookQueryOptions"; -export function createQueryProviderWrapper () { - const queryClient = new QueryClient(); - return ({ children }: { children: ReactNode }) => ( - - {children} - - ); -}; +import WorkbookPage from "./[id]/page"; +import { render, renderHook, screen, waitFor } from "@testing-library/react"; // TBD: 필요하면 vitest.setup.ts 에 빼놓기 -vi.mock('next/navigation', () => ({ +vi.mock("next/navigation", () => ({ useSearchParams: () => ({ get: (key: string) => { - if (key === 'workbookId') return '1'; + if (key === "workbookId") return "1"; return null; }, }), - usePathname: () => '/workbooks/1', + usePathname: () => "/workbooks/1", })); -vi.mock('next/image', () => ({ +vi.mock("next/image", () => ({ __esModule: true, - default: (props: JSX.IntrinsicAttributes & ClassAttributes & ImgHTMLAttributes) => { + default: ( + props: JSX.IntrinsicAttributes & + ClassAttributes & + ImgHTMLAttributes, + ) => { return {props.alt}; }, })); - -describe('워크북 페이지 테스트', () => { - it('workbook page 랜딩 시 react-query 테스트', async () => { +describe("워크북 페이지 테스트", () => { + it("workbook page 랜딩 시 react-query 테스트", async () => { const { result } = renderHook(() => useWorkbook(1), { wrapper: createQueryProviderWrapper(), }); await waitFor(() => { - expect(result.current.data?.title).toBe('재태크, 투자 필수 용어 모음집'); - expect(result.current.data?.mainImageUrl).toBe('/main_img.png'); - expect(result.current.data?.description).toBe('사회 초년생부터, 직장인, 은퇴자까지 모두가 알아야 할 기본적인 재태크, 투자 필수 용어 모음집 입니다.'); - expect(result.current.data?.category).toBe('경제'); - }) + expect(result.current.data?.title).toBe("재태크, 투자 필수 용어 모음집"); + expect(result.current.data?.mainImageUrl).toBe("/main_img.png"); + expect(result.current.data?.description).toBe( + "사회 초년생부터, 직장인, 은퇴자까지 모두가 알아야 할 기본적인 재태크, 투자 필수 용어 모음집 입니다.", + ); + expect(result.current.data?.category).toBe("경제"); + }); }); - it('데이터와 함께 워크북 페이지를 로딩한다', async () => { + it("데이터와 함께 워크북 페이지를 로딩한다", async () => { const queryClient = new QueryClient(); render( - + , ); await waitFor(() => { - expect(screen.getByText('재태크, 투자 필수 용어 모음집')).toBeInTheDocument(); - expect(screen.getByAltText('Workbook landing image')).toHaveAttribute('src', '/main_img.png'); - expect(screen.getByText('사회 초년생부터, 직장인, 은퇴자까지 모두가 알아야 할 기본적인 재태크, 투자 필수 용어 모음집 입니다.')).toBeInTheDocument(); + expect( + screen.getByText("재태크, 투자 필수 용어 모음집"), + ).toBeInTheDocument(); + expect(screen.getByAltText("Workbook landing image")).toHaveAttribute( + "src", + "/main_img.png", + ); + expect( + screen.getByText( + "사회 초년생부터, 직장인, 은퇴자까지 모두가 알아야 할 기본적인 재태크, 투자 필수 용어 모음집 입니다.", + ), + ).toBeInTheDocument(); }); }); diff --git a/src/problem/components/AnswerChoiceList/AnswerChoiceList.test.tsx b/src/problem/components/AnswerChoiceList/AnswerChoiceList.test.tsx index e04dd592..6e336a89 100644 --- a/src/problem/components/AnswerChoiceList/AnswerChoiceList.test.tsx +++ b/src/problem/components/AnswerChoiceList/AnswerChoiceList.test.tsx @@ -3,6 +3,7 @@ import { useQuery } from "@tanstack/react-query"; import { describe, expect, it, vi } from "vitest"; import QueryClientProviders from "@shared/components/queryClientProvider"; +import { createQueryProviderWrapper } from "@shared/constants/createQueryProvider"; import AnswerChoiceList from "."; import { getProblemQueryOptions } from "@problem/remotes/getProblemQueryOptions"; @@ -24,9 +25,7 @@ vi.mock("next/navigation", () => { }), }; }); -const wrapper = ({ children }: React.PropsWithChildren) => { - return {children}; -}; + const renderWithQueryClient = () => { return render( @@ -43,7 +42,7 @@ describe("선택지 불러오고, 버튼 클릭으로 버튼 ui 변경", () => { useQuery({ ...getProblemQueryOptions({ problemId: 1 }), }), - { wrapper }, + { wrapper: createQueryProviderWrapper() }, ); await waitFor(() => result.current.isSuccess); diff --git a/src/shared/constants/createQueryProvider.tsx b/src/shared/constants/createQueryProvider.tsx new file mode 100644 index 00000000..7100037b --- /dev/null +++ b/src/shared/constants/createQueryProvider.tsx @@ -0,0 +1,11 @@ +import { ReactNode } from "react"; + +import { QueryClientProvider } from "@tanstack/react-query"; + +import queryClient from "@api/query-client"; + +export function createQueryProviderWrapper() { + return ({ children }: { children: ReactNode }) => ( + {children} + ); +} From 10ba818ca497e65d3c83edcbe17958983bb37a8d Mon Sep 17 00:00:00 2001 From: happhee Date: Sat, 15 Jun 2024 20:00:53 +0900 Subject: [PATCH 5/6] refactor : feat api constant --- src/workbook/remotes/api.ts | 4 ++++ .../remotes/getWorkbookQueryOptions.ts | 19 ++++++++++++------- 2 files changed, 16 insertions(+), 7 deletions(-) create mode 100644 src/workbook/remotes/api.ts diff --git a/src/workbook/remotes/api.ts b/src/workbook/remotes/api.ts new file mode 100644 index 00000000..8ab9f99f --- /dev/null +++ b/src/workbook/remotes/api.ts @@ -0,0 +1,4 @@ +export const API_ROUTE = {}; +export const QUERY_KEY = { + GET_WORKBOOK: "get-workbook", +}; diff --git a/src/workbook/remotes/getWorkbookQueryOptions.ts b/src/workbook/remotes/getWorkbookQueryOptions.ts index 7a8e715d..b386e7c9 100644 --- a/src/workbook/remotes/getWorkbookQueryOptions.ts +++ b/src/workbook/remotes/getWorkbookQueryOptions.ts @@ -1,4 +1,4 @@ -import { useQuery,UseQueryOptions } from "@tanstack/react-query"; +import { useQuery, UseQueryOptions } from "@tanstack/react-query"; import { ApiResponse, axiosRequest } from "@api/api-config"; @@ -7,6 +7,8 @@ import { apiRoutes } from "@shared/constants/apiRoutes"; import { WorkbookInfo } from "@workbook/types"; import { buildUrl } from "@workbook/utils"; +import { QUERY_KEY } from "./api"; + interface WorkbookParam { workbookId: number; } @@ -22,7 +24,7 @@ export const getWorkbookQueryOptions = ( workbookId: number, ): UseQueryOptions, unknown, WorkbookInfo> => { return { - queryKey: ["get-workbook", workbookId], + queryKey: [QUERY_KEY.GET_WORKBOOK, workbookId], queryFn: () => getWorkbook({ workbookId }), select: (data) => data.data, }; @@ -30,12 +32,15 @@ export const getWorkbookQueryOptions = ( export const useWorkbook = (workbookId: number) => { return useQuery({ - queryKey: ['workbook', workbookId], + queryKey: ["workbook", workbookId], queryFn: async () => { - console.log('Fetching workbook data...'); - const response = await axiosRequest>("get", apiRoutes.workbook.replace(':workbookId', workbookId.toString())); - console.log('Fetched data:', response.data); + console.log("Fetching workbook data..."); + const response = await axiosRequest>( + "get", + apiRoutes.workbook.replace(":workbookId", workbookId.toString()), + ); + console.log("Fetched data:", response.data); return response.data; }, }); -};; +}; From d4a01f524ab13208877b285ca7280b7aa1621219 Mon Sep 17 00:00:00 2001 From: happhee Date: Sat, 15 Jun 2024 20:01:14 +0900 Subject: [PATCH 6/6] refactor : problem api routes component --- src/problem/components/AnswerChoiceButton/index.tsx | 3 ++- src/problem/components/ProblemTitle/index.tsx | 3 ++- src/problem/components/TagList/index.tsx | 3 ++- src/problem/remotes/api.ts | 11 ++++++++--- src/problem/remotes/getProblemQueryOptions.ts | 6 +++--- src/problem/remotes/postProblemAnswerOption.ts | 10 +++------- 6 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/problem/components/AnswerChoiceButton/index.tsx b/src/problem/components/AnswerChoiceButton/index.tsx index fbae4c9a..324f6533 100644 --- a/src/problem/components/AnswerChoiceButton/index.tsx +++ b/src/problem/components/AnswerChoiceButton/index.tsx @@ -8,6 +8,7 @@ import { cn } from "@shared/utils/cn"; import ChoiceFillCircleSvg from "../ChoiceFillCircleSvg"; import { ANSWER_CHOICHE_BUTTON_INFO } from "@problem/constants/problemInfo"; import ProblemContext from "@problem/context/problemContext"; +import { QUERY_KEY } from "@problem/remotes/api"; import { AnswerCheckInfo, AnswerChoiceInfo } from "@problem/types/problemInfo"; interface AnswerChoiceButtonProps extends Pick {} @@ -25,7 +26,7 @@ export default function AnswerChoiceButton({ const problemAnswerInfo = useMutationState({ filters: { - mutationKey: ["get-problem-answer"], + mutationKey: [QUERY_KEY.POST_PROBLEM_ANSWER], }, select: (mutation) => mutation.state.data as AnswerCheckInfo, }); diff --git a/src/problem/components/ProblemTitle/index.tsx b/src/problem/components/ProblemTitle/index.tsx index 6f3fa42b..1691d888 100644 --- a/src/problem/components/ProblemTitle/index.tsx +++ b/src/problem/components/ProblemTitle/index.tsx @@ -7,6 +7,7 @@ import React from "react"; import { useMutationState, useQuery } from "@tanstack/react-query"; import { PROBLEM_TITLE_INFO } from "@problem/constants/problemInfo"; +import { QUERY_KEY } from "@problem/remotes/api"; import { getProblemQueryOptions } from "@problem/remotes/getProblemQueryOptions"; import { AnswerCheckInfo } from "@problem/types/problemInfo"; @@ -18,7 +19,7 @@ export default function ProblemTitle() { }); const problemAnswerInfo = useMutationState({ filters: { - mutationKey: ["get-problem-answer", problemIdNumber], + mutationKey: [QUERY_KEY.POST_PROBLEM_ANSWER, problemIdNumber], }, select: (mutation) => mutation.state.data as AnswerCheckInfo, }); diff --git a/src/problem/components/TagList/index.tsx b/src/problem/components/TagList/index.tsx index bc4fe543..603b37ec 100644 --- a/src/problem/components/TagList/index.tsx +++ b/src/problem/components/TagList/index.tsx @@ -6,10 +6,11 @@ import { ApiResponse } from "@api/api-config"; import queryClient from "@api/query-client"; import Tag from "@common/components/Tag"; +import { QUERY_KEY } from "@problem/remotes/api"; import { PromblemInfo } from "@problem/types/problemInfo"; export default function TagList() { - const problemInfoData = queryClient.getQueryData(["get-Problems-info"]) as + const problemInfoData = queryClient.getQueryData([QUERY_KEY.GET_PROBLEM]) as | ApiResponse | undefined; diff --git a/src/problem/remotes/api.ts b/src/problem/remotes/api.ts index 5083accf..60c94001 100644 --- a/src/problem/remotes/api.ts +++ b/src/problem/remotes/api.ts @@ -1,4 +1,9 @@ -export const PROBLEM_API_ROUTES = { - problems: (articleId: number) => `/articles/${articleId}/problems`, - submitAnswer: (problemId: number) => `/problems/${problemId}`, +export const API_ROUTE = { + PROBLEM: (articleId: number) => `/articles/${articleId}/problems`, + SUBMIT_ANSWER: (problemId: number) => `/problems/${problemId}`, +}; + +export const QUERY_KEY = { + GET_PROBLEM: "get-problem-info", + POST_PROBLEM_ANSWER: "post-problem-answer", }; diff --git a/src/problem/remotes/getProblemQueryOptions.ts b/src/problem/remotes/getProblemQueryOptions.ts index 4fd5e698..6329d923 100644 --- a/src/problem/remotes/getProblemQueryOptions.ts +++ b/src/problem/remotes/getProblemQueryOptions.ts @@ -2,13 +2,13 @@ import { UseQueryOptions } from "@tanstack/react-query"; import { ApiResponse, axiosRequest } from "@api/api-config"; -import { PROBLEM_API_ROUTES } from "./api"; +import { API_ROUTE, QUERY_KEY } from "./api"; import { PromblemInfo } from "@problem/types/problemInfo"; export const getProblemInfo = ({ problemId, }: ProblemInfoParams): Promise> => { - return axiosRequest("get", PROBLEM_API_ROUTES.problems(problemId)); + return axiosRequest("get", API_ROUTE.PROBLEM(problemId)); }; export const getProblemQueryOptions = ({ problemId, @@ -18,7 +18,7 @@ export const getProblemQueryOptions = ({ PromblemInfo > => { return { - queryKey: ["get-Problems-info"], + queryKey: [QUERY_KEY.GET_PROBLEM, problemId], queryFn: () => getProblemInfo({ problemId }), select: (data) => data.data, }; diff --git a/src/problem/remotes/postProblemAnswerOption.ts b/src/problem/remotes/postProblemAnswerOption.ts index feca00ef..e8f5d494 100644 --- a/src/problem/remotes/postProblemAnswerOption.ts +++ b/src/problem/remotes/postProblemAnswerOption.ts @@ -2,18 +2,14 @@ import { UseMutationOptions } from "@tanstack/react-query"; import { ApiResponse, axiosRequest } from "@api/api-config"; -import { PROBLEM_API_ROUTES } from "./api"; +import { API_ROUTE, QUERY_KEY } from "./api"; import { AnswerCheckInfo } from "@problem/types/problemInfo"; export const postProblemAnswer = ( params: ProblemAnswerParams, body: ProblemAnswerBody, ): Promise> => { - return axiosRequest( - "post", - PROBLEM_API_ROUTES.submitAnswer(params.problemId), - body, - ); + return axiosRequest("post", API_ROUTE.SUBMIT_ANSWER(params.problemId), body); }; export const postProblemAnswerMutationOptions = ( params: ProblemAnswerParams, @@ -23,7 +19,7 @@ export const postProblemAnswerMutationOptions = ( ProblemAnswerBody > => { return { - mutationKey: ["get-problem-answer", params.problemId], + mutationKey: [QUERY_KEY.POST_PROBLEM_ANSWER, params.problemId], mutationFn: (body) => postProblemAnswer(params, body), }; };