Skip to content

Commit

Permalink
페이지 이동에 사용되는 path들을 리팩터링합니다. (#131)
Browse files Browse the repository at this point in the history
* refcator: builder pattern을 이용한 `PathBuilder` 구현

path를 일관되게 얻을 수 있는 클래스 구현

* feat: PathBuilder 테스트 코드 추가
  • Loading branch information
ludacirs authored Sep 19, 2023
1 parent 5fb232a commit e396603
Show file tree
Hide file tree
Showing 9 changed files with 159 additions and 25 deletions.
21 changes: 6 additions & 15 deletions apps/realworld/src/app/login/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from "react";
import { PathBuilder } from '@/shared/utils/routes';
import React from 'react';

const Login = () => {
return (
Expand All @@ -9,7 +10,7 @@ const Login = () => {
<div className="col-md-6 offset-md-3 col-xs-12">
<h1 className="text-xs-center">Sign in</h1>
<p className="text-xs-center">
<a href="/register">Need an account?</a>
<a href={PathBuilder.buildRegister().getPath()}>Need an account?</a>
</p>

<ul className="error-messages">
Expand All @@ -18,22 +19,12 @@ const Login = () => {

<form>
<fieldset className="form-group">
<input
className="form-control form-control-lg"
type="text"
placeholder="Email"
/>
<input className="form-control form-control-lg" type="text" placeholder="Email" />
</fieldset>
<fieldset className="form-group">
<input
className="form-control form-control-lg"
type="password"
placeholder="Password"
/>
<input className="form-control form-control-lg" type="password" placeholder="Password" />
</fieldset>
<button className="btn btn-lg btn-primary pull-xs-right">
Sign in
</button>
<button className="btn btn-lg btn-primary pull-xs-right">Sign in</button>
</form>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { PathBuilder } from '@/shared/utils/routes';
import Link from 'next/link';
import React from 'react';

Expand All @@ -9,7 +10,7 @@ interface ArticlePreviewProps {

const ArticlePreview = ({ description, title, slug }: ArticlePreviewProps) => {
return (
<Link className="flex flex-col gap-4" href={`/article/${slug}`}>
<Link className="flex flex-col gap-4" href={PathBuilder.buildArticle().addSlug(slug).getPath()}>
<p className="font-semibold text-[1.5rem]">{title}</p>
<p className="text-[1rem] text-gray1400">{description}</p>
</Link>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { PathBuilder } from '@/shared/utils/routes';
import Link from 'next/link';
import React from 'react';

Expand All @@ -7,7 +8,7 @@ interface ReadMoreButtonProps {

const ReadMoreButton = ({ slug }: ReadMoreButtonProps) => {
return (
<Link href={`/article/${slug}`} className="text-gray1200 text-[0.8rem] font-light">
<Link href={PathBuilder.buildArticle().addSlug(slug).getPath()} className="text-gray1200 text-[0.8rem] font-light">
Read More..
</Link>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { PathBuilder } from '@/shared/utils/routes';
import Link from 'next/link';
import React from 'react';

const InduceSignIn = () => {
return (
<p className="text-[1rem] ml-16">
<Link className="hover:underline hover:text-green700 text-green600" href="/sign-in">
<Link className="hover:underline hover:text-green700 text-green600" href={PathBuilder.buildLogin().getPath()}>
Sign in
</Link>{' '}
or{` `}
<Link className="hover:underline hover:text-green700 text-green600" href="/sign-up">
<Link className="hover:underline hover:text-green700 text-green600" href={PathBuilder.buildRegister().getPath()}>
sign up
</Link>{' '}
to add comments on this article.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { PathBuilder } from '@/shared/utils/routes';
import { Tag } from '@packages/ui';
import Link from 'next/link';
import React from 'react';
Expand All @@ -9,7 +10,7 @@ interface ArticleTagListProps {
const ArticleTagList = ({ tagList, slug }: ArticleTagListProps) => {
if (slug) {
return (
<Link className="flex gap-2 cursor-pointer" href={`/article/${slug}`}>
<Link className="flex gap-2 cursor-pointer" href={PathBuilder.buildArticle().addSlug(slug).getPath()}>
{tagList.map(label => (
<Tag key={label} label={label} variant="outlined" />
))}
Expand Down
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -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);

Expand Down
63 changes: 63 additions & 0 deletions apps/realworld/src/shared/utils/routes.test.ts
Original file line number Diff line number Diff line change
@@ -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);
}
});
});
});
74 changes: 74 additions & 0 deletions apps/realworld/src/shared/utils/routes.ts
Original file line number Diff line number Diff line change
@@ -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;
}
}
9 changes: 5 additions & 4 deletions apps/realworld/src/widgets/gnb/ui/gnb/gnb.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,30 @@
'use client';

import { responsiveWidth } from '@/shared/css/responsive-width';
import { PathBuilder } from '@/shared/utils/routes';
import Link from 'next/link';
import React from 'react';

const GNB = () => {
return (
<nav className="flex items-center justify-center w-full h-56">
<div className={`flex justify-between ${responsiveWidth}`}>
<Link data-testid="gnbLogo" href={'/'} className="no-underline ">
<Link data-testid="gnbLogo" href={PathBuilder.buildHome().getPath()} className="no-underline ">
<p className="text-2xl font-bold text-green600">conduit</p>
</Link>
<ul className="flex gap-16">
<li>
<Link data-testid="gnbHome" href={'/'} className="no-underline">
<Link data-testid="gnbHome" href={PathBuilder.buildHome().getPath()} className="no-underline">
<p className="text-black">Home</p>
</Link>
</li>
<li>
<Link data-testid="gnbSignIn" href={'/sign-in'} className="no-underline">
<Link data-testid="gnbSignIn" href={PathBuilder.buildLogin().getPath()} className="no-underline">
<p className="text-black">Sign in</p>
</Link>
</li>
<li>
<Link data-testid="gnbSignUp" href={'/sign-up'} className="no-underline">
<Link data-testid="gnbSignUp" href={PathBuilder.buildRegister().getPath()} className="no-underline">
<p className="text-black">Sign up</p>
</Link>
</li>
Expand Down

0 comments on commit e396603

Please sign in to comment.