Skip to content

Commit

Permalink
[feat] 기업 자동응답 목록 추가 페이지 구현 및 테스트 코드 작성
Browse files Browse the repository at this point in the history
  • Loading branch information
kkkkkSE committed Aug 15, 2023
1 parent 853868f commit a53218c
Show file tree
Hide file tree
Showing 13 changed files with 310 additions and 5 deletions.
93 changes: 93 additions & 0 deletions src/components/auto-reply-company/AutoReplyAddForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { useEffect, useRef } from 'react';

import { useNavigate } from 'react-router-dom';

import { Controller, useForm } from 'react-hook-form';

import useAutoReplyFormStore from '../../hooks/useAutoReplyFormStore';

import { STATIC_ROUTES } from '../../constants/routes';

import TextArea from '../ui/TextArea';
import OperationButtons from '../ui/OperationButtons';
import ErrorMessage from '../ui/ErrorMessage';

const MAX_LENGTH = {
question: 60,
answer: 150,
};

export default function AutoReplyAddForm() {
const navigate = useNavigate();

const [{ done, errorMessage }, store] = useAutoReplyFormStore();

const questionRef = useRef<HTMLTextAreaElement | null>(null);

interface FormValues {
question: string,
answer: string,
}

const { control, handleSubmit } = useForm<FormValues>();

useEffect(() => {
if (done) {
store.reset();

navigate(STATIC_ROUTES.AUTO_REPLIES);
}
}, [done]);

const onSubmit = (data: FormValues) => {
store.addAutoReply(data.question, data.answer.trim());
};

return (
<div>
<form
onSubmit={handleSubmit(onSubmit)}
data-testid="auto-reply-form"
>
<Controller
control={control}
name="question"
render={({ field: { value, onChange } }) => (
<TextArea
label="질문"
value={value}
onChange={onChange}
maxLength={MAX_LENGTH.question}
showLength
ref={questionRef}
/>
)}
/>

<Controller
control={control}
name="answer"
render={({ field: { value, onChange } }) => (
<TextArea
label="답변"
value={value}
onChange={onChange}
maxLength={MAX_LENGTH.answer}
showLength
fixHeight
/>
)}
/>

<OperationButtons
primaryType="submit"
primaryName="저장하기"
/>

{errorMessage && (
<ErrorMessage>{errorMessage}</ErrorMessage>
)}
</form>
</div>
);
}
1 change: 1 addition & 0 deletions src/components/auto-reply-company/AutoReplyRow.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { screen } from '@testing-library/react';

import fixtures from '../../../fixtures';

import { render } from '../../test-helper';
Expand Down
2 changes: 1 addition & 1 deletion src/components/auto-reply-company/AutoReplyRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export default function AutoReplyRow({
const Container = styled.li`
display: flex;
border-bottom: 1px solid ${(props) => props.theme.colors.gray2.default};
&:last-child {
border-bottom: none;
}
Expand Down
2 changes: 1 addition & 1 deletion src/constants/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export const STATIC_ROUTES = {
MY_PROFILE: '/profile',
MY_PROFILE_EDIT: '/profile/edit',
AUTO_REPLIES: '/auto-replies',
AUTO_REPLIES_ADD: '/auto-replies/new',
AUTO_REPLIES_NEW: '/auto-replies/new',
OPEN_PROFILES: '/open-profiles',
ACCOUNT: '/account',
DELETE_ACCOUNT: '/account/delete',
Expand Down
13 changes: 13 additions & 0 deletions src/hooks/useAutoReplyFormStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { container } from 'tsyringe';

import { useStore } from 'usestore-ts';

import AutoReplyFormStore from '../stores/AutoReplyFormStore';

const useAutoReplyFormStore = () => {
const store = container.resolve(AutoReplyFormStore);

return useStore(store);
};

export default useAutoReplyFormStore;
10 changes: 9 additions & 1 deletion src/mocks/handlers/company.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,10 +182,18 @@ const companyHandlers = [
ctx.json({ autoReplies }),
);
}),
rest.post(`${BASE_URL}/company/auto-replies`, (req, res, ctx) => {
rest.post(`${BASE_URL}/company/auto-replies`, async (req, res, ctx) => {
const { question, answer } = await req.json();

const authorization = req.headers.get('Authorization');
const accessToken = authorization ? authorization.split(' ')[1] : '';

autoReplies.push({
id: autoReplies.length + 1,
question,
answer,
});

if (!authorization || !isValidAccessToken(userType, accessToken)) return res(ctx.status(401));

return res(ctx.status(201));
Expand Down
3 changes: 2 additions & 1 deletion src/pages/AutoReplyAdminPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import { STATIC_ROUTES } from '../constants/routes';
function AutoReplyAdminPage() {
const navigate = useNavigate();

// TODO : 등록 최대 개수 초과 시 안내 모달창 띄우기
const handleClickAdd = () => {
navigate(STATIC_ROUTES.AUTO_REPLIES_ADD);
navigate(STATIC_ROUTES.AUTO_REPLIES_NEW);
};

return (
Expand Down
14 changes: 14 additions & 0 deletions src/pages/AutoReplyNewPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import AutoReplyAddForm from '../components/auto-reply-company/AutoReplyAddForm';
import ContentLayout from '../components/layout/ContentLayout';

export default function AutoReplyNewPage() {
return (
<ContentLayout
pageHeader="자동응답 추가"
testId="auto-reply-new"
enableBack
>
<AutoReplyAddForm />
</ContentLayout>
);
}
10 changes: 9 additions & 1 deletion src/routes.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -135,11 +135,19 @@ describe('routes', () => {
});

context('when the current path is "/auto-replies"', () => {
it('renders <AutoReplyAdminList />', () => {
it('renders <AutoReplyAdminListPage />', () => {
setupRouterProvider(STATIC_ROUTES.AUTO_REPLIES);

screen.getByTestId(/auto-reply-list/);
});
});

context('when the current path is "/auto-replies/new"', () => {
it('renders <AutoReplyNewPage />', () => {
setupRouterProvider(STATIC_ROUTES.AUTO_REPLIES_NEW);

screen.getByTestId(/auto-reply-new/);
});
});
});
});
2 changes: 2 additions & 0 deletions src/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import ProfileEditPage from './pages/ProfileEditPage';
import ProfilePage from './pages/ProfilePage';
import SignUpPage from './pages/SignUpPage';
import AutoReplyAdminPage from './pages/AutoReplyAdminPage';
import AutoReplyNewPage from './pages/AutoReplyNewPage';

const routes = [
{
Expand All @@ -28,6 +29,7 @@ const routes = [
{ path: STATIC_ROUTES.MY_PROFILE, element: <ProfilePage /> },
{ path: STATIC_ROUTES.MY_PROFILE_EDIT, element: <ProfileEditPage /> },
{ path: STATIC_ROUTES.AUTO_REPLIES, element: <AutoReplyAdminPage /> },
{ path: STATIC_ROUTES.AUTO_REPLIES_NEW, element: <AutoReplyNewPage /> },
],
},
];
Expand Down
11 changes: 11 additions & 0 deletions src/services/ApiService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,17 @@ export default class ApiService {
return data;
}

async createAutoReply({
question, answer,
} : {
question : string;
answer: string;
}) {
await this.instance.post(STATIC_API_PATHS.AUTO_REPLIES_BY_COMPANY, {
question, answer,
});
}

async deleteAutoReply({ id } : {id : number}) {
await this.instance.delete(DYNAMIC_API_PATHS.AUTO_REPLIES_FOR_COMPANY(id));
}
Expand Down
86 changes: 86 additions & 0 deletions src/stores/AutoReplyFormStore.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import AutoReplyFormStore from './AutoReplyFormStore';

const context = describe;

const createAutoReply = jest.fn();

jest.mock('../services/ApiService', () => ({
get apiService() {
return {
createAutoReply,
};
},
}));

describe('ChatRoomStore', () => {
let store: AutoReplyFormStore;

beforeEach(() => {
jest.clearAllMocks();

store = new AutoReplyFormStore();
});

describe('add data', () => {
const params = {
question: '',
answer: '',
};

context('empty question', () => {
const errorMessage = '질문을 작성해주세요';

it(`errorMessage set to ${errorMessage}`, async () => {
await store.addAutoReply(params.question, params.answer);

expect(store.errorMessage).toBe(errorMessage);
});
});

context('empty answer', () => {
const errorMessage = '답변을 작성해주세요';

beforeEach(() => {
params.question = 'question';
});

it(`errorMessage set to ${errorMessage}`, async () => {
await store.addAutoReply(params.question, params.answer);

expect(store.errorMessage).toBe(errorMessage);
});
});

context('when API responds with success', () => {
beforeEach(() => {
params.answer = 'answer';
});

it('done set to true', async () => {
await store.addAutoReply(params.question, params.answer);

expect(createAutoReply).toBeCalledWith(params);

expect(store.done).toBe(true);
expect(store.errorMessage).toBe('');
});
});

context('when API responds with error', () => {
const errorMessage = 'Error Message';

beforeEach(() => {
createAutoReply.mockRejectedValue(Error(errorMessage));
});

it('errorMessage set to error message', async () => {
await store.addAutoReply(params.question, params.answer);

expect(createAutoReply).toBeCalledWith(params);

expect(store.done).toBe(false);
expect(store.errorMessage).toBe(errorMessage);
});
});
});
});
68 changes: 68 additions & 0 deletions src/stores/AutoReplyFormStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { AxiosError } from 'axios';

import { singleton } from 'tsyringe';

import { Action, Store } from 'usestore-ts';

import { apiService } from '../services/ApiService';

@singleton()
@Store()
export default class AutoReplyFormStore {
errorMessage = '';

done = false;

@Action()
reset() {
this.errorMessage = '';

this.done = false;
}

@Action()
setDone() {
this.done = true;

this.errorMessage = '';
}

@Action()
setErrorMessage(message: string) {
this.errorMessage = message;

this.done = false;
}

@Action()
async addAutoReply(
question: string,
answer: string,
) {
try {
if (!question) {
throw Error('질문을 작성해주세요');
}

if (!answer) {
throw Error('답변을 작성해주세요');
}

await apiService.createAutoReply({ question, answer });

this.setDone();
} catch (e) {
if (e instanceof AxiosError) {
if (e.response?.status === 440) {
this.setErrorMessage(e.response?.data);

return;
}
}

if (e instanceof Error) {
this.setErrorMessage(e.message);
}
}
}
}

0 comments on commit a53218c

Please sign in to comment.