diff --git a/fixtures/study-group.js b/fixtures/study-group.js index bc72214..41ba0ba 100644 --- a/fixtures/study-group.js +++ b/fixtures/study-group.js @@ -3,9 +3,7 @@ const studyGroup = { title: '스터디를 소개합니다.2', moderatorId: 'user2', applyEndDate: '2020-12-23', - participants: [ - { id: 'user2' }, - ], + participants: [], personnel: 2, contents: '우리는 이것저것 합니다.2', tags: [ diff --git a/src/components/introduce/ApplicantViewButton.jsx b/src/components/introduce/ApplicantViewButton.jsx new file mode 100644 index 0000000..c715f92 --- /dev/null +++ b/src/components/introduce/ApplicantViewButton.jsx @@ -0,0 +1,92 @@ +import React, { useState } from 'react'; + +import { changeDateToTime, isCheckedTimeStatus } from '../../util/utils'; + +import ApplyStatusButton from './ApplyStatusButton'; +import ApplicationFormModal from './modals/ApplicationFormModal'; +import AskApplyCancelModal from './modals/AskApplyCancelModal'; +import AskLoginModal from './modals/AskLoginModal'; + +const ApplicantViewButton = ({ + group, onApply, user, realTime, onApplyCancel, onChangeApplyFields, applyFields, clearForm, +}) => { + const [loginCheckModal, setLoginCheckModal] = useState(false); + const [applyCancelModal, setApplyCancelModal] = useState(false); + const [modalForm, setModalForm] = useState(false); + + const { + moderatorId, participants, applyEndDate, + } = group; + + const applyEndTime = changeDateToTime(applyEndDate); + + const handleApplyCancelConfirmClick = () => { + setApplyCancelModal(true); + }; + + const handleLoginCheckCancel = () => { + setLoginCheckModal(false); + }; + + const handleApplyCancel = () => { + setApplyCancelModal(false); + }; + + const handleApplyCancelConfirm = () => { + setApplyCancelModal(false); + onApplyCancel(); + }; + + const handleApply = () => { + if (!user) { + setLoginCheckModal(true); + return; + } + + setModalForm(true); + }; + + const handleFormSubmit = () => { + setModalForm(false); + onApply(applyFields); + }; + + const handleFormCancel = () => { + setModalForm(false); + clearForm(); + }; + + if (moderatorId === user) { + return null; + } + + return ( + <> + id === user)} + timeStatus={isCheckedTimeStatus({ ...group, time: realTime, applyEndTime })} + /> + + + + + ); +}; + +export default ApplicantViewButton; diff --git a/src/components/introduce/ApplicantViewButton.test.jsx b/src/components/introduce/ApplicantViewButton.test.jsx new file mode 100644 index 0000000..c69d81e --- /dev/null +++ b/src/components/introduce/ApplicantViewButton.test.jsx @@ -0,0 +1,253 @@ +import React from 'react'; + +import { fireEvent, render } from '@testing-library/react'; + +import ApplicantViewButton from './ApplicantViewButton'; + +import STUDY_GROUP from '../../../fixtures/study-group'; + +describe('ApplicantViewButton', () => { + const handleApply = jest.fn(); + const handleApplyCancel = jest.fn(); + const handleClearForm = jest.fn(); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + const renderApplicantViewButton = ({ group, time, user }) => render(( + + )); + + context('When the host and logged in user are the same', () => { + const group = { + ...STUDY_GROUP, + moderatorId: 'user', + }; + + it('nothing renders', () => { + const { container } = renderApplicantViewButton({ group, user: 'user' }); + + expect(container).toBeEmptyDOMElement(); + }); + }); + + context('When the organizer and the logged-in user are different', () => { + context(`When the application date is earlier than the + deadline date and the application deadline is not reached`, () => { + const time = Date.now(); + + const nowDate = new Date(); + const tomorrow = nowDate.setDate(nowDate.getDate() + 1); + + const group = { + ...STUDY_GROUP, + applyEndDate: tomorrow, + participants: [ + { id: 'user2' }, + { id: 'user' }, + ], + personnel: 3, + }; + + context('click confirm', () => { + it('Call the cancel application button.', () => { + const { getByText } = renderApplicantViewButton({ group, user: 'user', time }); + + const button = getByText('신청 취소'); + + expect(button).not.toBeNull(); + + fireEvent.click(button); + + fireEvent.click(getByText('확인')); + + expect(handleApplyCancel).toBeCalled(); + }); + }); + + context('click cancel', () => { + it("doesn't call the cancel application button.", () => { + const { getByText } = renderApplicantViewButton({ group, user: 'user', time }); + + const button = getByText('신청 취소'); + + expect(button).not.toBeNull(); + + fireEvent.click(button); + + fireEvent.click(getByText('취소')); + + expect(handleApplyCancel).not.toBeCalled(); + }); + }); + }); + + context('When the author and the logged-in user have the same ID', () => { + it("doesn't renders apply button", () => { + const { container } = renderApplicantViewButton({ group: STUDY_GROUP, user: 'user2' }); + + expect(container).not.toHaveTextContent('신청하기'); + }); + }); + + context('When the study recruitment is closed', () => { + const time = Date.now(); + + describe('current time is after the recruitment deadline', () => { + const nowDate = new Date(); + const yesterday = nowDate.setDate(nowDate.getDate() - 1); + + const group = { + ...STUDY_GROUP, + applyEndDate: yesterday, + participants: [ + { id: 'user2' }, + ], + personnel: 2, + }; + + it('renders recruitment closed text', () => { + const { container } = renderApplicantViewButton({ group, time }); + + expect(container).toHaveTextContent('모집 마감'); + }); + }); + + describe('When the number of study group participants equals the maximum number of participants', () => { + const nowDate = new Date(); + const tomorrow = nowDate.setDate(nowDate.getDate() + 1); + + const group = { + ...STUDY_GROUP, + applyEndDate: tomorrow, + participants: [ + { id: 'user2' }, + { id: 'user3' }, + ], + personnel: 2, + }; + + it('renders recruitment closed text', () => { + const { container } = renderApplicantViewButton({ group, time }); + + expect(container).toHaveTextContent('모집 마감'); + }); + }); + + describe('When the user clicks the Apply button without logging in', () => { + const nowDate = new Date(); + const tomorrow = nowDate.setDate(nowDate.getDate() + 1); + + const group = { + ...STUDY_GROUP, + applyEndDate: tomorrow, + participants: [ + { id: 'user2' }, + ], + personnel: 2, + }; + + it('renders modal window appears and application failure message', () => { + const { container, getByText } = renderApplicantViewButton({ group, time }); + + const button = getByText('신청하기'); + + expect(button).not.toBeNull(); + + fireEvent.click(button); + + expect(handleApply).not.toBeCalled(); + + expect(container).toHaveTextContent('로그인 후 신청 가능합니다.'); + + fireEvent.click(getByText('확인')); + + expect(container).not.toHaveTextContent('로그인 후 신청 가능합니다.'); + }); + }); + }); + + context('When the study recruitment is opened', () => { + const time = Date.now(); + + describe(`current time is before the recruitment deadline and + when the number of study group participants is less than the maximum number of participants`, () => { + const nowDate = new Date(); + const tomorrow = nowDate.setDate(nowDate.getDate() + 1); + + const group = { + ...STUDY_GROUP, + applyEndDate: tomorrow, + participants: [ + { id: 'user2' }, + ], + personnel: 2, + }; + + it('renders recruitment apply text', () => { + const { container } = renderApplicantViewButton({ group, time }); + + expect(container).toHaveTextContent('신청하기'); + expect(container).not.toHaveTextContent('모집마감'); + }); + }); + + describe('When the user clicks the Apply button after logging in', () => { + const nowDate = new Date(); + const tomorrow = nowDate.setDate(nowDate.getDate() + 1); + + const group = { + ...STUDY_GROUP, + applyEndDate: tomorrow, + participants: [ + { id: 'user2' }, + ], + personnel: 2, + }; + + context('Click confirm Study participation application', () => { + it('renders modal window appears and application failure message', () => { + const { container, getByText } = renderApplicantViewButton({ group, time, user: 'user' }); + + const button = getByText('신청하기'); + + expect(button).not.toBeNull(); + + fireEvent.click(button); + + fireEvent.click(getByText('확인')); + + expect(handleApply).toBeCalled(); + + expect(container).not.toHaveTextContent('로그인 후 신청 가능합니다.'); + }); + }); + + context('Click cancel Study participation application', () => { + it('renders modal window appears and application failure message', () => { + const { getByText } = renderApplicantViewButton({ group, time, user: 'user' }); + + const button = getByText('신청하기'); + + expect(button).not.toBeNull(); + + fireEvent.click(button); + + fireEvent.click(getByText('취소')); + + expect(handleApply).not.toBeCalled(); + }); + }); + }); + }); + }); +}); diff --git a/src/components/introduce/ApplyStatusButton.jsx b/src/components/introduce/ApplyStatusButton.jsx index 0f7c301..7719b06 100644 --- a/src/components/introduce/ApplyStatusButton.jsx +++ b/src/components/introduce/ApplyStatusButton.jsx @@ -1,97 +1,52 @@ import React from 'react'; -import styled from '@emotion/styled'; - -import palette from '../../styles/palette'; - -const ApplyStatusButtonWrapper = styled.button` - display: inline-flex; - align-items: center; - margin: .5rem 0 .5rem 0; - padding: 0.25rem 5rem; - font-size: 1.5em; - line-height: 0; - font-family: 'Gamja Flower', cursive; - border-radius: 0.4rem; - border: none; - outline: none; - - &.deadline { - cursor: not-allowed; - background: ${palette.gray[3]}; - color: ${palette.gray[5]}; - } - - &.apply-cancel { - cursor: pointer; - background: ${palette.orange[4]}; - color: white; - &:hover { - background: ${palette.orange[3]}; - } - } - - &.apply-complete { - background: ${palette.gray[1]}; - border: 2px solid #a5d8ff; - color: #74c0fc; - } - - &.apply { - color: white; - cursor: pointer; - background: ${palette.teal[5]}; - &:hover { - background: ${palette.teal[4]}; - } - } -`; +import StyledApplyStatusButton from '../../styles/StyledApplyStatusButton'; const ApplyStatusButton = ({ timeStatus, onApply, applyStatus, onCancel, }) => { if (!timeStatus && applyStatus) { return ( - 신청 취소 - + ); } if (applyStatus) { return ( - 신청 완료 - + ); } if (timeStatus) { return ( - 모집 마감 - + ); } return ( - 신청하기 - + ); }; diff --git a/src/components/introduce/IntroduceHeader.jsx b/src/components/introduce/IntroduceHeader.jsx index 0c0de51..c1fc798 100644 --- a/src/components/introduce/IntroduceHeader.jsx +++ b/src/components/introduce/IntroduceHeader.jsx @@ -1,14 +1,8 @@ -import React, { useState } from 'react'; +import React from 'react'; import styled from '@emotion/styled'; import palette from '../../styles/palette'; -import { changeDateToTime, isCheckedTimeStatus } from '../../util/utils'; - -import ApplyStatusButton from './ApplyStatusButton'; -import AskLoginModal from './modals/AskLoginModal'; -import AskApplyCancelModal from './modals/AskApplyCancelModal'; -import ApplicationFormModal from './modals/ApplicationFormModal'; const IntroduceHeaderWrapper = styled.div` border-bottom: 2px solid ${palette.gray[4]}; @@ -16,6 +10,7 @@ const IntroduceHeaderWrapper = styled.div` margin-bottom: 2rem; display: flex; justify-content: space-between; + h1 { font-size: 2.3rem; line-height: 1.5; @@ -23,85 +18,13 @@ const IntroduceHeaderWrapper = styled.div` } `; -const IntroduceHeader = ({ - group, onApply, user, realTime, onApplyCancel, onChangeApplyFields, applyFields, clearForm, -}) => { - const [loginCheckModal, setLoginCheckModal] = useState(false); - const [applyCancelModal, setApplyCancelModal] = useState(false); - const [modalForm, setModalForm] = useState(false); - - const { - title, moderatorId, participants, applyEndDate, - } = group; - - const applyEndTime = changeDateToTime(applyEndDate); - - const handleApplyCancelConfirmClick = () => { - setApplyCancelModal(true); - }; - - const handleLoginCheckCancel = () => { - setLoginCheckModal(false); - }; - - const handleApplyCancel = () => { - setApplyCancelModal(false); - }; - - const handleApplyCancelConfirm = () => { - setApplyCancelModal(false); - onApplyCancel(); - }; - - const handleApply = () => { - if (!user) { - setLoginCheckModal(true); - return; - } - - setModalForm(true); - }; - - const handleFormSubmit = () => { - setModalForm(false); - onApply(applyFields); - }; - - const handleFormCancel = () => { - setModalForm(false); - clearForm(); - }; +const IntroduceHeader = ({ group, children }) => { + const { title } = group; return (

{title}

- {moderatorId !== user && ( - <> - id === user)} - timeStatus={isCheckedTimeStatus({ ...group, time: realTime, applyEndTime })} - /> - - - - - )} + {children}
); }; diff --git a/src/components/introduce/IntroduceHeader.test.jsx b/src/components/introduce/IntroduceHeader.test.jsx index 124c174..8a4ef70 100644 --- a/src/components/introduce/IntroduceHeader.test.jsx +++ b/src/components/introduce/IntroduceHeader.test.jsx @@ -1,29 +1,15 @@ import React from 'react'; -import { fireEvent, render } from '@testing-library/react'; +import { render } from '@testing-library/react'; import IntroduceHeader from './IntroduceHeader'; import STUDY_GROUP from '../../../fixtures/study-group'; describe('IntroduceHeader', () => { - const handleApply = jest.fn(); - const handleApplyCancel = jest.fn(); - const handleClearForm = jest.fn(); - - beforeEach(() => { - jest.clearAllMocks(); - }); - - const renderIntroduceHeader = ({ group, time, user }) => render(( + const renderIntroduceHeader = ({ group }) => render(( )); @@ -32,213 +18,4 @@ describe('IntroduceHeader', () => { expect(container).toHaveTextContent('스터디를 소개합니다.2'); }); - - context(`When the application date is earlier than the - deadline date and the application deadline is not reached`, () => { - const time = Date.now(); - - const nowDate = new Date(); - const tomorrow = nowDate.setDate(nowDate.getDate() + 1); - - const group = { - ...STUDY_GROUP, - applyEndDate: tomorrow, - participants: [ - { id: 'user2' }, - { id: 'user' }, - ], - personnel: 3, - }; - - context('click confirm', () => { - it('Call the cancel application button.', () => { - const { getByText } = renderIntroduceHeader({ group, user: 'user', time }); - - const button = getByText('신청 취소'); - - expect(button).not.toBeNull(); - - fireEvent.click(button); - - fireEvent.click(getByText('확인')); - - expect(handleApplyCancel).toBeCalled(); - }); - }); - - context('click cancel', () => { - it("doesn't call the cancel application button.", () => { - const { getByText } = renderIntroduceHeader({ group, user: 'user', time }); - - const button = getByText('신청 취소'); - - expect(button).not.toBeNull(); - - fireEvent.click(button); - - fireEvent.click(getByText('취소')); - - expect(handleApplyCancel).not.toBeCalled(); - }); - }); - }); - - context('When the author and the logged-in user have the same ID', () => { - it("doesn't renders apply button", () => { - const { container } = renderIntroduceHeader({ group: STUDY_GROUP, user: 'user2' }); - - expect(container).not.toHaveTextContent('신청하기'); - }); - }); - - context('When the study recruitment is closed', () => { - const time = Date.now(); - - describe('current time is after the recruitment deadline', () => { - const nowDate = new Date(); - const yesterday = nowDate.setDate(nowDate.getDate() - 1); - - const group = { - ...STUDY_GROUP, - applyEndDate: yesterday, - participants: [ - { id: 'user2' }, - ], - personnel: 2, - }; - - it('renders recruitment closed text', () => { - const { container } = renderIntroduceHeader({ group, time }); - - expect(container).toHaveTextContent('모집 마감'); - }); - }); - - describe('When the number of study group participants equals the maximum number of participants', () => { - const nowDate = new Date(); - const tomorrow = nowDate.setDate(nowDate.getDate() + 1); - - const group = { - ...STUDY_GROUP, - applyEndDate: tomorrow, - participants: [ - { id: 'user2' }, - { id: 'user3' }, - ], - personnel: 2, - }; - - it('renders recruitment closed text', () => { - const { container } = renderIntroduceHeader({ group, time }); - - expect(container).toHaveTextContent('모집 마감'); - }); - }); - - describe('When the user clicks the Apply button without logging in', () => { - const nowDate = new Date(); - const tomorrow = nowDate.setDate(nowDate.getDate() + 1); - - const group = { - ...STUDY_GROUP, - applyEndDate: tomorrow, - participants: [ - { id: 'user2' }, - ], - personnel: 2, - }; - - it('renders modal window appears and application failure message', () => { - const { container, getByText } = renderIntroduceHeader({ group, time }); - - const button = getByText('신청하기'); - - expect(button).not.toBeNull(); - - fireEvent.click(button); - - expect(handleApply).not.toBeCalled(); - - expect(container).toHaveTextContent('로그인 후 신청 가능합니다.'); - - fireEvent.click(getByText('확인')); - - expect(container).not.toHaveTextContent('로그인 후 신청 가능합니다.'); - }); - }); - }); - - context('When the study recruitment is opened', () => { - const time = Date.now(); - - describe(`current time is before the recruitment deadline and - when the number of study group participants is less than the maximum number of participants`, () => { - const nowDate = new Date(); - const tomorrow = nowDate.setDate(nowDate.getDate() + 1); - - const group = { - ...STUDY_GROUP, - applyEndDate: tomorrow, - participants: [ - { id: 'user2' }, - ], - personnel: 2, - }; - - it('renders recruitment apply text', () => { - const { container } = renderIntroduceHeader({ group, time }); - - expect(container).toHaveTextContent('신청하기'); - expect(container).not.toHaveTextContent('모집마감'); - }); - }); - - describe('When the user clicks the Apply button after logging in', () => { - const nowDate = new Date(); - const tomorrow = nowDate.setDate(nowDate.getDate() + 1); - - const group = { - ...STUDY_GROUP, - applyEndDate: tomorrow, - participants: [ - { id: 'user2' }, - ], - personnel: 2, - }; - - context('Click confirm Study participation application', () => { - it('renders modal window appears and application failure message', () => { - const { container, getByText } = renderIntroduceHeader({ group, time, user: 'user' }); - - const button = getByText('신청하기'); - - expect(button).not.toBeNull(); - - fireEvent.click(button); - - fireEvent.click(getByText('확인')); - - expect(handleApply).toBeCalled(); - - expect(container).not.toHaveTextContent('로그인 후 신청 가능합니다.'); - }); - }); - - context('Click cancel Study participation application', () => { - it('renders modal window appears and application failure message', () => { - const { getByText } = renderIntroduceHeader({ group, time, user: 'user' }); - - const button = getByText('신청하기'); - - expect(button).not.toBeNull(); - - fireEvent.click(button); - - fireEvent.click(getByText('취소')); - - expect(handleApply).not.toBeCalled(); - }); - }); - }); - }); }); diff --git a/src/containers/introduce/IntroduceFormContainer.jsx b/src/containers/introduce/IntroduceFormContainer.jsx new file mode 100644 index 0000000..b56cb8f --- /dev/null +++ b/src/containers/introduce/IntroduceFormContainer.jsx @@ -0,0 +1,31 @@ +import React, { useState } from 'react'; + +import { useInterval } from 'react-use'; +import { useSelector } from 'react-redux'; + +import { getGroup } from '../../util/utils'; + +import StudyIntroduceForm from '../../components/introduce/StudyIntroduceForm'; + +const IntroduceFormContainer = () => { + const [realTime, setRealTime] = useState(Date.now()); + + const group = useSelector(getGroup('group')); + + useInterval(() => { + setRealTime(Date.now()); + }, 1000); + + if (!group) { + return null; + } + + return ( + + ); +}; + +export default React.memo(IntroduceFormContainer); diff --git a/src/containers/introduce/IntroduceFormContainer.test.jsx b/src/containers/introduce/IntroduceFormContainer.test.jsx new file mode 100644 index 0000000..507fbcc --- /dev/null +++ b/src/containers/introduce/IntroduceFormContainer.test.jsx @@ -0,0 +1,58 @@ +import React from 'react'; + +import { useSelector } from 'react-redux'; + +import { render } from '@testing-library/react'; + +import { MemoryRouter } from 'react-router-dom'; + +import IntroduceFormContainer from './IntroduceFormContainer'; + +describe('IntroduceFormContainer', () => { + beforeEach(() => { + useSelector.mockImplementation((state) => state({ + groupReducer: { + group: given.group, + applyFields: given.applyFields, + }, + })); + }); + + const renderIntroduceContainer = () => render(( + + + + )); + + context('with group', () => { + given('group', () => ({ + id: 1, + moderatorId: 'user1', + title: '스터디를 소개합니다. 1', + personnel: 7, + participants: [], + contents: '우리는 이것저것 합니다.1', + tags: [ + 'JavaScript', + 'React', + 'Algorithm', + ], + })); + + it('renders study group title and contents', () => { + const { container } = renderIntroduceContainer(1); + + expect(container).toHaveTextContent('우리는 이것저것 합니다.1'); + }); + }); + + context('without group ', () => { + given('group', () => (null)); + + it('nothing renders', () => { + const { container } = renderIntroduceContainer(1); + + expect(container).toBeEmptyDOMElement(); + }); + }); +}); diff --git a/src/containers/introduce/IntroduceContainer.jsx b/src/containers/introduce/IntroduceHeaderContainer.jsx similarity index 74% rename from src/containers/introduce/IntroduceContainer.jsx rename to src/containers/introduce/IntroduceHeaderContainer.jsx index 472a274..e4d0691 100644 --- a/src/containers/introduce/IntroduceContainer.jsx +++ b/src/containers/introduce/IntroduceHeaderContainer.jsx @@ -1,29 +1,26 @@ -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useCallback, useState } from 'react'; import { useInterval } from 'react-use'; import { useDispatch, useSelector } from 'react-redux'; import { getAuth, getGroup } from '../../util/utils'; import { - changeApplyFields, clearApplyFields, deleteParticipant, loadStudyGroup, updateParticipant, + changeApplyFields, clearApplyFields, deleteParticipant, updateParticipant, } from '../../reducers/groupSlice'; -import StudyIntroduceForm from '../../components/introduce/StudyIntroduceForm'; -import GroupContentLoader from '../../components/introduce/GroupsContentLoader'; import IntroduceHeader from '../../components/introduce/IntroduceHeader'; +import ApplicantViewButton from '../../components/introduce/ApplicantViewButton'; +import ModeratorViewButton from '../../components/introduce/ModeratorViewButton'; +import GroupContentLoader from '../../components/introduce/GroupsContentLoader'; -const IntroduceContainer = ({ groupId }) => { +const IntroduceHeaderContainer = () => { const [realTime, setRealTime] = useState(Date.now()); const dispatch = useDispatch(); - const applyFields = useSelector(getGroup('applyFields')); - const group = useSelector(getGroup('group')); const user = useSelector(getAuth('user')); - - useEffect(() => { - dispatch(loadStudyGroup(groupId)); - }, [dispatch, groupId]); + const group = useSelector(getGroup('group')); + const applyFields = useSelector(getGroup('applyFields')); useInterval(() => { setRealTime(Date.now()); @@ -52,8 +49,8 @@ const IntroduceContainer = ({ groupId }) => { } return ( - <> - + { clearForm={clearApplyForm} onChangeApplyFields={onChangeApplyFields} /> - - + ); }; -export default React.memo(IntroduceContainer); +export default React.memo(IntroduceHeaderContainer); diff --git a/src/containers/introduce/IntroduceContainer.test.jsx b/src/containers/introduce/IntroduceHeaderContainer.test.jsx similarity index 79% rename from src/containers/introduce/IntroduceContainer.test.jsx rename to src/containers/introduce/IntroduceHeaderContainer.test.jsx index 4c5609f..8041c34 100644 --- a/src/containers/introduce/IntroduceContainer.test.jsx +++ b/src/containers/introduce/IntroduceHeaderContainer.test.jsx @@ -1,16 +1,15 @@ import React from 'react'; +import { MemoryRouter } from 'react-router-dom'; import { useDispatch, useSelector } from 'react-redux'; import { fireEvent, render } from '@testing-library/react'; -import { MemoryRouter } from 'react-router-dom'; - import STUDY_GROUP from '../../../fixtures/study-group'; -import IntroduceContainer from './IntroduceContainer'; +import IntroduceHeaderContainer from './IntroduceHeaderContainer'; -describe('IntroduceContainer', () => { +describe('IntroduceHeaderContainer', () => { const dispatch = jest.fn(); beforeEach(() => { @@ -29,9 +28,9 @@ describe('IntroduceContainer', () => { })); }); - const renderIntroduceContainer = ({ id }) => render(( + const renderIntroduceContainer = () => render(( - + )); @@ -54,17 +53,10 @@ describe('IntroduceContainer', () => { wantToGet: '', })); - it('renders study group title and contents', () => { - const { container } = renderIntroduceContainer(1); + it('renders study group title', () => { + const { container } = renderIntroduceContainer(); expect(container).toHaveTextContent('스터디를 소개합니다. 1'); - expect(container).toHaveTextContent('우리는 이것저것 합니다.1'); - }); - - it('call dispatch actions', () => { - renderIntroduceContainer(1); - - expect(dispatch).toBeCalled(); }); }); @@ -76,7 +68,7 @@ describe('IntroduceContainer', () => { })); it('renders "loading.." text', () => { - const { container } = renderIntroduceContainer(1); + const { container } = renderIntroduceContainer(); expect(container).toHaveTextContent('Loading...'); }); @@ -91,9 +83,7 @@ describe('IntroduceContainer', () => { })); it('click event dispatches action call updateParticipant', () => { - const { getByText } = renderIntroduceContainer(1); - - expect(dispatch).toBeCalledTimes(1); + const { getByText } = renderIntroduceContainer(); const button = getByText('신청하기'); @@ -103,7 +93,7 @@ describe('IntroduceContainer', () => { fireEvent.click(getByText('확인')); - expect(dispatch).toBeCalledTimes(2); + expect(dispatch).toBeCalledTimes(1); }); it('dispatches action calls changeApplyFields', () => { @@ -112,9 +102,7 @@ describe('IntroduceContainer', () => { value: '내용', }; - const { getByText, getByLabelText } = renderIntroduceContainer(1); - - expect(dispatch).toBeCalledTimes(1); + const { getByText, getByLabelText } = renderIntroduceContainer(); const button = getByText('신청하기'); @@ -129,6 +117,22 @@ describe('IntroduceContainer', () => { payload: form, }); }); + + it('click cancel dispatches call action clearApplyFields', () => { + const { getByText } = renderIntroduceContainer(); + + const button = getByText('신청하기'); + + expect(button).not.toBeNull(); + + fireEvent.click(button); + + fireEvent.click(getByText('취소')); + + expect(dispatch).toBeCalledWith({ + type: 'group/clearApplyFields', + }); + }); }); describe(`When the application date is earlier than the deadline @@ -155,9 +159,7 @@ describe('IntroduceContainer', () => { context('click confirm', () => { it('click event dispatches action call deleteParticipant', () => { - const { getByText } = renderIntroduceContainer(1); - - expect(dispatch).toBeCalledTimes(1); + const { getByText } = renderIntroduceContainer(); const button = getByText('신청 취소'); @@ -167,15 +169,13 @@ describe('IntroduceContainer', () => { fireEvent.click(getByText('확인')); - expect(dispatch).toBeCalledTimes(2); + expect(dispatch).toBeCalledTimes(1); }); }); context('click cancel', () => { it("doesn't click event dispatches action call deleteParticipant", () => { - const { getByText } = renderIntroduceContainer(1); - - expect(dispatch).toBeCalledTimes(1); + const { getByText } = renderIntroduceContainer(); const button = getByText('신청 취소'); @@ -185,7 +185,7 @@ describe('IntroduceContainer', () => { fireEvent.click(getByText('취소')); - expect(dispatch).toBeCalledTimes(1); + expect(dispatch).not.toBeCalled(); }); }); }); diff --git a/src/pages/IntroducePage.jsx b/src/pages/IntroducePage.jsx index c285fac..1869bc9 100644 --- a/src/pages/IntroducePage.jsx +++ b/src/pages/IntroducePage.jsx @@ -1,12 +1,15 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import { useParams } from 'react-router-dom'; +import { useDispatch } from 'react-redux'; import styled from '@emotion/styled'; import Responsive from '../styles/Responsive'; +import { loadStudyGroup } from '../reducers/groupSlice'; -import IntroduceContainer from '../containers/introduce/IntroduceContainer'; +import IntroduceFormContainer from '../containers/introduce/IntroduceFormContainer'; +import IntroduceHeaderContainer from '../containers/introduce/IntroduceHeaderContainer'; const IntroducePageWrapper = styled(Responsive)` margin-top: 6em; @@ -15,9 +18,16 @@ const IntroducePageWrapper = styled(Responsive)` const IntroducePage = ({ params }) => { const { id } = params || useParams(); + const dispatch = useDispatch(); + + useEffect(() => { + dispatch(loadStudyGroup(id)); + }, [dispatch, id]); + return ( - + + ); }; diff --git a/src/pages/IntroducePage.test.jsx b/src/pages/IntroducePage.test.jsx index 78e8684..9eb380c 100644 --- a/src/pages/IntroducePage.test.jsx +++ b/src/pages/IntroducePage.test.jsx @@ -8,8 +8,10 @@ import { render } from '@testing-library/react'; import IntroducePage from './IntroducePage'; describe('IntroducePage', () => { + const dispatch = jest.fn(); + beforeEach(() => { - const dispatch = jest.fn(); + dispatch.mockClear(); useDispatch.mockImplementation(() => dispatch); @@ -47,6 +49,8 @@ describe('IntroducePage', () => { )); + expect(dispatch).toBeCalledTimes(1); + expect(container).toHaveTextContent('스터디를 소개합니다. 1'); }); }); @@ -59,6 +63,8 @@ describe('IntroducePage', () => { , ); + expect(dispatch).toBeCalledTimes(1); + expect(container).toHaveTextContent('스터디를 소개합니다. 1'); }); }); diff --git a/src/reducers/groupSlice.js b/src/reducers/groupSlice.js index 7cf5185..0466f4b 100644 --- a/src/reducers/groupSlice.js +++ b/src/reducers/groupSlice.js @@ -119,12 +119,14 @@ export const writeStudyGroup = () => async (dispatch, getState) => { const { user } = authReducer; - const groupId = await postStudyGroup(produce(groupReducer.writeField, (draft) => { - draft.moderatorId = user; - draft.participants.push({ - id: user, - }); - })); + const groupId = await postStudyGroup( + produce(groupReducer.writeField, (draft) => { + draft.moderatorId = user; + draft.participants.push({ + id: user, + }); + }), + ); dispatch(successWrite(groupId)); dispatch(clearWriteFields()); diff --git a/src/styles/StyledApplyStatusButton.jsx b/src/styles/StyledApplyStatusButton.jsx new file mode 100644 index 0000000..13d947f --- /dev/null +++ b/src/styles/StyledApplyStatusButton.jsx @@ -0,0 +1,64 @@ +/* eslint-disable react/jsx-props-no-spreading */ +import React from 'react'; + +import styled from '@emotion/styled'; + +import palette from './palette'; + +const StyledApplyStatusButtonWrapper = styled.button` + display: inline-flex; + align-items: center; + margin: .5rem 0 .5rem 0; + padding: 0.25rem 5rem; + font-size: 1.5em; + line-height: 0; + font-family: 'Gamja Flower', cursive; + border-radius: 0.4rem; + border: none; + outline: none; + + &.deadline { + cursor: not-allowed; + background: ${palette.gray[3]}; + color: ${palette.gray[5]}; + } + + &.apply-cancel { + cursor: pointer; + background: ${palette.orange[4]}; + color: white; + &:hover { + background: ${palette.orange[3]}; + } + } + + &.apply-complete { + background: ${palette.gray[1]}; + border: 2px solid #a5d8ff; + color: #74c0fc; + } + + &.apply { + color: white; + cursor: pointer; + background: ${palette.teal[5]}; + &:hover { + background: ${palette.teal[4]}; + } + } + + &.confirm { + color: white; + cursor: pointer; + background: #4dabf7; + &:hover { + background: #74c0fc; + } + } +`; + +const StyledApplyStatusButton = (props) => ( + +); + +export default StyledApplyStatusButton;