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 (