From e3966037300916e7e0ded30dec830124de1361c0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=EC=9C=A0=EC=A0=9C=ED=98=B8?=
<45571631+ludacirs@users.noreply.github.com>
Date: Tue, 19 Sep 2023 22:37:55 +0900
Subject: [PATCH] =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=9D=B4?=
=?UTF-8?q?=EB=8F=99=EC=97=90=20=EC=82=AC=EC=9A=A9=EB=90=98=EB=8A=94=20pat?=
=?UTF-8?q?h=EB=93=A4=EC=9D=84=20=EB=A6=AC=ED=8C=A9=ED=84=B0=EB=A7=81?=
=?UTF-8?q?=ED=95=A9=EB=8B=88=EB=8B=A4.=20(#131)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* refcator: builder pattern을 이용한 `PathBuilder` 구현
path를 일관되게 얻을 수 있는 클래스 구현
* feat: PathBuilder 테스트 코드 추가
---
apps/realworld/src/app/login/page.tsx | 21 ++----
.../ui/article-preview/article-preview.tsx | 3 +-
.../ui/read-more-button/read-more-button.tsx | 3 +-
.../ui/induce-sign-in/induce-sign-in.tsx | 5 +-
.../ui/article-tag-list/article-tag-list.tsx | 3 +-
.../article-list-pagination.tsx | 3 +-
.../realworld/src/shared/utils/routes.test.ts | 63 ++++++++++++++++
apps/realworld/src/shared/utils/routes.ts | 74 +++++++++++++++++++
apps/realworld/src/widgets/gnb/ui/gnb/gnb.tsx | 9 ++-
9 files changed, 159 insertions(+), 25 deletions(-)
create mode 100644 apps/realworld/src/shared/utils/routes.test.ts
create mode 100644 apps/realworld/src/shared/utils/routes.ts
diff --git a/apps/realworld/src/app/login/page.tsx b/apps/realworld/src/app/login/page.tsx
index 7ace16ee..f0ed9aae 100644
--- a/apps/realworld/src/app/login/page.tsx
+++ b/apps/realworld/src/app/login/page.tsx
@@ -1,4 +1,5 @@
-import React from "react";
+import { PathBuilder } from '@/shared/utils/routes';
+import React from 'react';
const Login = () => {
return (
@@ -9,7 +10,7 @@ const Login = () => {
diff --git a/apps/realworld/src/entities/article/ui/article-preview/article-preview.tsx b/apps/realworld/src/entities/article/ui/article-preview/article-preview.tsx
index e44a298c..6a03f428 100644
--- a/apps/realworld/src/entities/article/ui/article-preview/article-preview.tsx
+++ b/apps/realworld/src/entities/article/ui/article-preview/article-preview.tsx
@@ -1,3 +1,4 @@
+import { PathBuilder } from '@/shared/utils/routes';
import Link from 'next/link';
import React from 'react';
@@ -9,7 +10,7 @@ interface ArticlePreviewProps {
const ArticlePreview = ({ description, title, slug }: ArticlePreviewProps) => {
return (
-
+
{title}
{description}
diff --git a/apps/realworld/src/entities/article/ui/read-more-button/read-more-button.tsx b/apps/realworld/src/entities/article/ui/read-more-button/read-more-button.tsx
index 1c64e5fb..deac9da0 100644
--- a/apps/realworld/src/entities/article/ui/read-more-button/read-more-button.tsx
+++ b/apps/realworld/src/entities/article/ui/read-more-button/read-more-button.tsx
@@ -1,3 +1,4 @@
+import { PathBuilder } from '@/shared/utils/routes';
import Link from 'next/link';
import React from 'react';
@@ -7,7 +8,7 @@ interface ReadMoreButtonProps {
const ReadMoreButton = ({ slug }: ReadMoreButtonProps) => {
return (
-
+
Read More..
);
diff --git a/apps/realworld/src/entities/comment/ui/induce-sign-in/induce-sign-in.tsx b/apps/realworld/src/entities/comment/ui/induce-sign-in/induce-sign-in.tsx
index 32368d35..68bf7f27 100644
--- a/apps/realworld/src/entities/comment/ui/induce-sign-in/induce-sign-in.tsx
+++ b/apps/realworld/src/entities/comment/ui/induce-sign-in/induce-sign-in.tsx
@@ -1,14 +1,15 @@
+import { PathBuilder } from '@/shared/utils/routes';
import Link from 'next/link';
import React from 'react';
const InduceSignIn = () => {
return (
-
+
Sign in
{' '}
or{` `}
-
+
sign up
{' '}
to add comments on this article.
diff --git a/apps/realworld/src/entities/tag/ui/article-tag-list/article-tag-list.tsx b/apps/realworld/src/entities/tag/ui/article-tag-list/article-tag-list.tsx
index f452e457..68b491fb 100644
--- a/apps/realworld/src/entities/tag/ui/article-tag-list/article-tag-list.tsx
+++ b/apps/realworld/src/entities/tag/ui/article-tag-list/article-tag-list.tsx
@@ -1,3 +1,4 @@
+import { PathBuilder } from '@/shared/utils/routes';
import { Tag } from '@packages/ui';
import Link from 'next/link';
import React from 'react';
@@ -9,7 +10,7 @@ interface ArticleTagListProps {
const ArticleTagList = ({ tagList, slug }: ArticleTagListProps) => {
if (slug) {
return (
-
+
{tagList.map(label => (
))}
diff --git a/apps/realworld/src/features/article/ui/article-list-pagination/article-list-pagination.tsx b/apps/realworld/src/features/article/ui/article-list-pagination/article-list-pagination.tsx
index 2a4db979..902fd6c0 100644
--- a/apps/realworld/src/features/article/ui/article-list-pagination/article-list-pagination.tsx
+++ b/apps/realworld/src/features/article/ui/article-list-pagination/article-list-pagination.tsx
@@ -1,6 +1,7 @@
'use client';
import { generatePageList, getItemActivation } from '@/entities/article/api/page';
import { getItemIndex } from '@/shared/utils/array';
+import { PathBuilder } from '@/shared/utils/routes';
import { useRouter } from 'next/navigation';
import React from 'react';
@@ -12,7 +13,7 @@ interface ArticleListPaginationProps {
const ArticleListPagination = ({ currentPage = 1, lastPage }: ArticleListPaginationProps) => {
const { push } = useRouter();
const handleChangePage = (page: number) => {
- push(`/?page=${page}`);
+ push(PathBuilder.buildHome().addPage(page).getPath());
};
const pageList = generatePageList(lastPage);
diff --git a/apps/realworld/src/shared/utils/routes.test.ts b/apps/realworld/src/shared/utils/routes.test.ts
new file mode 100644
index 00000000..6197b849
--- /dev/null
+++ b/apps/realworld/src/shared/utils/routes.test.ts
@@ -0,0 +1,63 @@
+import { PathBuilder } from './routes';
+
+const context = describe;
+
+describe('PathBuilder에서', () => {
+ context('home path를 빌드할 때', () => {
+ it('/ 경로가 생성된다.', () => {
+ const homePath = PathBuilder.buildHome().getPath();
+
+ expect(homePath).toEqual('/');
+ });
+
+ it('addPage로 경로가 생성된다.', () => {
+ const page = 2;
+ const homePathWithPageParam = PathBuilder.buildHome().addPage(page).getPath();
+
+ expect(homePathWithPageParam).toEqual(`/?page=${page}`);
+ });
+ });
+
+ context('register path를 빌드할 때 ', () => {
+ it('/register 경로가 생성된다.', () => {
+ const buildedPath = PathBuilder.buildRegister().getPath();
+
+ expect(buildedPath).toEqual('/register');
+ });
+ });
+
+ context('login path를 빌드할 때 ', () => {
+ it('/login 경로가 생성된다.', () => {
+ const buildedPath = PathBuilder.buildLogin().getPath();
+
+ expect(buildedPath).toEqual('/login');
+ });
+ });
+
+ context('article path를 빌드할 때', () => {
+ it('addSlug로 경로가 생성된다.', () => {
+ const slug = 'this is slug';
+ const path = PathBuilder.buildArticle().addSlug(slug).getPath();
+
+ expect(path).toEqual(`/article/${slug}`);
+ });
+
+ it('addSlug가 호출되지 않으면 에러가 발생한다.', () => {
+ try {
+ PathBuilder.buildArticle().getPath();
+ expect(true).toBe(false);
+ } catch (e) {
+ expect(e).toBeInstanceOf(Error);
+ }
+ });
+
+ it('addSlug가 호출되지 않으면 에러가 발생한다.', () => {
+ try {
+ PathBuilder.buildArticle().addSlug('1').addSlug('2').getPath();
+ expect(true).toBe(false);
+ } catch (e) {
+ expect(e).toBeInstanceOf(Error);
+ }
+ });
+ });
+});
diff --git a/apps/realworld/src/shared/utils/routes.ts b/apps/realworld/src/shared/utils/routes.ts
new file mode 100644
index 00000000..be6dbde3
--- /dev/null
+++ b/apps/realworld/src/shared/utils/routes.ts
@@ -0,0 +1,74 @@
+export class PathBuilder {
+ private path: string;
+
+ private constructor(path: string) {
+ this.path = path;
+ }
+
+ static buildHome(): HomePathBuilder {
+ return new HomePathBuilder('/');
+ }
+
+ static buildRegister(): PathBuilder {
+ return new PathBuilder('/register');
+ }
+
+ static buildLogin(): PathBuilder {
+ return new PathBuilder('/login');
+ }
+
+ static buildArticle(): ArticlePathBuilder {
+ return new ArticlePathBuilder('/article');
+ }
+ getPath(): string {
+ return this.path;
+ }
+}
+
+class HomePathBuilder {
+ private path: string;
+ private params: string;
+ constructor(path: '/') {
+ this.path = path;
+ this.params = '?';
+ }
+
+ private getParamsString() {
+ if (this.params === '?') {
+ return '';
+ }
+ return this.params;
+ }
+
+ addPage(page: number) {
+ this.params += `page=${page}`;
+ return this;
+ }
+
+ getPath() {
+ return this.path + this.getParamsString();
+ }
+}
+
+class ArticlePathBuilder {
+ private path: string;
+ private count: number;
+
+ constructor(path: '/article') {
+ this.path = path;
+ this.count = 0;
+ }
+
+ addSlug(slug: string): ArticlePathBuilder {
+ this.path += `/${slug}`;
+ this.count += 1;
+ return this;
+ }
+
+ getPath(): string {
+ if (this.count !== 1) {
+ throw new Error('article path must have slug or only once');
+ }
+ return this.path;
+ }
+}
diff --git a/apps/realworld/src/widgets/gnb/ui/gnb/gnb.tsx b/apps/realworld/src/widgets/gnb/ui/gnb/gnb.tsx
index e3a76dcd..639b9e04 100644
--- a/apps/realworld/src/widgets/gnb/ui/gnb/gnb.tsx
+++ b/apps/realworld/src/widgets/gnb/ui/gnb/gnb.tsx
@@ -1,6 +1,7 @@
'use client';
import { responsiveWidth } from '@/shared/css/responsive-width';
+import { PathBuilder } from '@/shared/utils/routes';
import Link from 'next/link';
import React from 'react';
@@ -8,22 +9,22 @@ const GNB = () => {
return (