Skip to content

Commit

Permalink
Merge pull request #106 from GDSC-PKNU-21-22/feat/#105
Browse files Browse the repository at this point in the history
Feat/#105: 건의사항 작성 시 한번더 확인하기 구현 & 모달 전역상태로 변경
  • Loading branch information
hwinkr authored Jul 25, 2023
2 parents 7f63170 + c4fc82c commit 6be6e48
Show file tree
Hide file tree
Showing 21 changed files with 544 additions and 112 deletions.
8 changes: 8 additions & 0 deletions src/@types/modals.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { FunctionComponent } from 'react';

export type Modals =
| Array<{
Component: FunctionComponent<any>;
props: object;
}>
| [];
3 changes: 2 additions & 1 deletion src/@types/styles/icon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ export type IconKind =
| 'suggest'
| 'right'
| 'checkedRadio'
| 'uncheckedRadio';
| 'uncheckedRadio'
| 'cancel';
42 changes: 33 additions & 9 deletions src/components/Card/InformCard/index.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import AlertModal from '@components/Modal/AlertModal';
import ModalsProvider from '@components/ModalsProvider';
import { MODAL_MESSAGE } from '@constants/modal-messages';
import MajorContext from '@contexts/major';
import useModals from '@hooks/useModals';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import Major from '@type/major';
Expand All @@ -12,8 +16,8 @@ const ICON = 'notification';
const TITLE = '공지사항';
const PATH = '/announcement';

const setMajorMock = (mode: string) => {
const mockMajor: Major = mode === 'modal' ? null : '컴퓨터공학과';
const setMajorMock = (isRender: boolean) => {
const mockMajor: Major = isRender ? null : '컴퓨터공학과';
const mockSetMajor = jest.fn();

jest.mock('react', () => ({
Expand All @@ -33,15 +37,29 @@ jest.mock('react-router-dom', () => ({
useNavigate: () => mockRouterTo,
}));

jest.mock('@hooks/useModals', () => {
const modalsMock = {
modals: [],
openModal: jest.fn(),
closeModal: jest.fn(),
};
return {
__esModule: true,
default: () => modalsMock,
};
});

describe('InformCard 컴포넌트 테스트', () => {
afterEach(() => {
jest.clearAllMocks();
});

it('전역상태가 설정 됐을 경우, 페이지 이동 테스트', async () => {
render(
<MajorContext.Provider value={{ ...setMajorMock('not-modal') }}>
<InformCard icon={ICON} title={TITLE} path={PATH} />
<MajorContext.Provider value={{ ...setMajorMock(false) }}>
<ModalsProvider>
<InformCard icon={ICON} title={TITLE} path={PATH} />
</ModalsProvider>
</MajorContext.Provider>,
{
wrapper: MemoryRouter,
Expand All @@ -58,8 +76,10 @@ describe('InformCard 컴포넌트 테스트', () => {

it('전역상태가 설정 안됐을 경우, 모달 렌더링 테스트', async () => {
render(
<MajorContext.Provider value={{ ...setMajorMock('modal') }}>
<InformCard icon={ICON} title={TITLE} path={PATH} />
<MajorContext.Provider value={{ ...setMajorMock(true) }}>
<ModalsProvider>
<InformCard icon={ICON} title={TITLE} path={PATH} />
</ModalsProvider>
</MajorContext.Provider>,
{
wrapper: MemoryRouter,
Expand All @@ -71,8 +91,12 @@ describe('InformCard 컴포넌트 테스트', () => {
await userEvent.click(card);
});

expect(
screen.getByText('아직 학과를 알려주지 않았어요'),
).toBeInTheDocument();
expect(useModals().openModal).toHaveBeenCalledWith(AlertModal, {
message: MODAL_MESSAGE.ALERT.SET_MAJOR,
buttonMessage: '전공선택하러 가기',
iconKind: 'plus',
onClose: expect.any(Function),
routerTo: expect.any(Function),
});
});
});
36 changes: 22 additions & 14 deletions src/components/Card/InformCard/index.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import Icon from '@components/Icon';
import MajorModal from '@components/Modal/MajorModal';
import AlertModal from '@components/Modal/AlertModal';
import { MODAL_MESSAGE } from '@constants/modal-messages';
import { css } from '@emotion/react';
import styled from '@emotion/styled';
import useMajor from '@hooks/useMajor';
import useModals from '@hooks/useModals';
import useRouter from '@hooks/useRouter';
import { THEME } from '@styles/ThemeProvider/theme';
import { IconKind } from '@type/styles/icon';
import { setSize } from '@utils/styles/size';
import { useState } from 'react';

interface InformCardProps {
icon: IconKind & ('school' | 'notification');
Expand All @@ -17,26 +18,33 @@ interface InformCardProps {

const InformCard = ({ icon, title, path }: InformCardProps) => {
const { major } = useMajor();
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);

const { routerTo } = useRouter();
const routerToPath = (path: string) => routerTo(path);
const routerToMajorDecision = () => routerTo('/major-decision');

const { openModal, closeModal } = useModals();

const onClick = () => {
if (!major) {
setIsModalOpen((prev) => !prev);
const handleMajorModal = () => {
if (major) {
routerToPath(path);
return;
}
routerTo(path);
openModal(AlertModal, {
message: MODAL_MESSAGE.ALERT.SET_MAJOR,
buttonMessage: '전공선택하러 가기',
iconKind: 'plus',
onClose: () => closeModal(AlertModal),
routerTo: () => {
closeModal(AlertModal);
routerToMajorDecision();
},
});
};

return (
<>
{isModalOpen && (
<MajorModal
onClose={() => setIsModalOpen((prev) => !prev)}
routerTo={() => routerTo('major-decision')}
/>
)}
<Card data-testid="card" icon={icon} onClick={onClick}>
<Card data-testid="card" icon={icon} onClick={handleMajorModal}>
<Icon
kind={icon}
color={icon === 'school' ? THEME.TEXT.GRAY : THEME.TEXT.WHITE}
Expand Down
2 changes: 2 additions & 0 deletions src/components/Icon/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
MdOutlineQuestionAnswer,
MdRadioButtonUnchecked,
MdRadioButtonChecked,
MdOutlineCancel,
} from 'react-icons/md';

const ICON: { [key in IconKind]: IconType } = {
Expand All @@ -30,6 +31,7 @@ const ICON: { [key in IconKind]: IconType } = {
suggest: MdOutlineQuestionAnswer,
checkedRadio: MdRadioButtonChecked,
uncheckedRadio: MdRadioButtonUnchecked,
cancel: MdOutlineCancel,
};

interface IconProps {
Expand Down
65 changes: 49 additions & 16 deletions src/components/List/DepartmentList/index.test.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,35 @@
import DepartmentList from '@components/List/DepartmentList';
import MajorProvider from '@components/MajorProvider';
import ConfirmModal from '@components/Modal/ConfirmModal';
import ModalsProvider from '@components/ModalsProvider';
import { MODAL_MESSAGE } from '@constants/modal-messages';
import useMajor from '@hooks/useMajor';
import { render, act, screen, findByText } from '@testing-library/react';
import useModals from '@hooks/useModals';
import { render, act, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { MemoryRouter, Route, Routes } from 'react-router-dom';

const routerToMock = jest.fn();

jest.mock('@hooks/useMajor');
global.alert = jest.fn();

jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useNavigate: () => routerToMock,
}));

jest.mock('@hooks/useModals', () => {
const modalsMock = {
modals: [],
openModal: jest.fn(),
closeModal: jest.fn(),
};
return {
__esModule: true,
default: () => modalsMock,
};
});

describe('학과선택 테스트', () => {
const mockUseMajor = useMajor as jest.MockedFunction<typeof useMajor>;
const mockSetMajor = jest.fn();
Expand All @@ -28,11 +44,16 @@ describe('학과선택 테스트', () => {
it('버튼 활성화 테스트', async () => {
const collegName = '정보융합대학';
render(
<MemoryRouter initialEntries={[`/major-decision/${collegName}`]}>
<Routes>
<Route path="/major-decision/:college" element={<DepartmentList />} />
</Routes>
</MemoryRouter>,
<ModalsProvider>
<MemoryRouter initialEntries={[`/major-decision/${collegName}`]}>
<Routes>
<Route
path="/major-decision/:college"
element={<DepartmentList />}
/>
</Routes>
</MemoryRouter>
</ModalsProvider>,
);

const confirmButton = await screen.findByRole('button', {
Expand All @@ -51,14 +72,21 @@ describe('학과선택 테스트', () => {
expect(confirmButton).toBeEnabled();
});

it('버튼 클릭 로직 테스트', async () => {
it('버튼 클릭시 전공선택 확인 모달 렌더링 테스트', async () => {
const collegName = '정보융합대학';
render(
<MemoryRouter initialEntries={[`/major-decision/${collegName}`]}>
<Routes>
<Route path="/major-decision/:college" element={<DepartmentList />} />
</Routes>
</MemoryRouter>,
<MajorProvider>
<ModalsProvider>
<MemoryRouter initialEntries={[`/major-decision/${collegName}`]}>
<Routes>
<Route
path="/major-decision/:college"
element={<DepartmentList />}
/>
</Routes>
</MemoryRouter>
</ModalsProvider>
</MajorProvider>,
);

const confirmButton = await screen.findByRole('button', {
Expand All @@ -68,10 +96,15 @@ describe('학과선택 테스트', () => {
await act(async () => {
await userEvent.click(college);
});
await userEvent.click(confirmButton);
await act(async () => {
await userEvent.click(confirmButton);
});

expect(mockSetMajor).toBeCalledWith('컴퓨터인공지능학부');
expect(routerToMock).toBeCalledWith('/');
expect(useModals().openModal).toHaveBeenCalledWith(ConfirmModal, {
message: MODAL_MESSAGE.CONFIRM.SET_MAJOR,
onCancelButtonClick: expect.any(Function),
onConfirmButtonClick: expect.any(Function),
});
});

it('학과 이름에 스페이스가 있는 경우 (학부, 전공이 모두 있는경우) 테스트', async () => {
Expand Down
49 changes: 35 additions & 14 deletions src/components/List/DepartmentList/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import http from '@apis/http';
import Button from '@components/Button';
import Icon from '@components/Icon';
import AlertModal from '@components/Modal/AlertModal';
import ConfirmModal from '@components/Modal/ConfirmModal';
import { MODAL_MESSAGE } from '@constants/modal-messages';
import styled from '@emotion/styled';
import useMajor from '@hooks/useMajor';
import useModals from '@hooks/useModals';
import useRouter from '@hooks/useRouter';
import { THEME } from '@styles/ThemeProvider/theme';
import React, { useEffect, useState } from 'react';
Expand All @@ -15,6 +19,7 @@ const DepartmentList = () => {
const { routerTo, goBack } = useRouter();
const { setMajor } = useMajor();
const { college } = useParams();
const { openModal, closeModal } = useModals();

const fetchData = async () => {
const result = await http.get(`/api/majorDecision/${college}`);
Expand All @@ -23,26 +28,42 @@ const DepartmentList = () => {
}
setDepartmentList(result.data);
};
useEffect(() => {
fetchData();
}, []);

const routerToHome = () => {
closeModal(AlertModal);
routerTo('/');
};

const handlerMajorSetModal = () => {
closeModal(ConfirmModal);
localStorage.setItem('major', selected);
setMajor(selected);

openModal(AlertModal, {
message: MODAL_MESSAGE.SUCCEED.SET_MAJOR,
buttonMessage: '홈으로 이동하기',
onClose: () => routerToHome(),
routerTo: () => routerToHome(),
});
};

const handleMajorConfirmModal = () => {
openModal(ConfirmModal, {
message: MODAL_MESSAGE.CONFIRM.SET_MAJOR,
onConfirmButtonClick: () => handlerMajorSetModal(),
onCancelButtonClick: () => closeModal(ConfirmModal),
});
};

const onClick: React.MouseEventHandler<HTMLElement> = (e) => {
if (e.currentTarget.textContent === null) return;
setSelected(e.currentTarget.textContent);
setButtonDisable(false);
};

const buttonClick: React.MouseEventHandler<HTMLElement> = (e) => {
if (e.target !== e.currentTarget) return;
const afterSpace = selected.substring(selected.indexOf(' ') + 1);
localStorage.setItem('major', afterSpace);
setMajor(afterSpace);
alert('전공 선택 완료 !');
routerTo('/');
};

useEffect(() => {
fetchData();
}, []);

return departmentList ? (
<ListContainer>
<Title>학과 선택하기</Title>
Expand All @@ -58,7 +79,7 @@ const DepartmentList = () => {
</ListWrapper>
))}
<ButtonContainer>
<Button disabled={buttonDisable} onClick={buttonClick}>
<Button disabled={buttonDisable} onClick={handleMajorConfirmModal}>
선택완료
</Button>
</ButtonContainer>
Expand Down
Loading

0 comments on commit 6be6e48

Please sign in to comment.