From 3ed45404acbde0272d5065da79ff672a80c87d68 Mon Sep 17 00:00:00 2001 From: saseungmin Date: Sat, 12 Dec 2020 01:22:31 +0900 Subject: [PATCH] [Feature] Approval and cancellation logic for study applicants - Click the "Approve" button to change to the "Cancel" button - Click the "Cancel" button to change to the "Approve" button --- .../introduce/ModeratorViewButton.jsx | 3 +- .../introduce/modals/ParticipantList.jsx | 14 +- .../introduce/modals/ParticipantList.test.jsx | 27 +++ .../introduce/modals/ParticipantListModal.jsx | 5 +- .../modals/ParticipantListModal.test.jsx | 71 +++++-- .../introduce/IntroduceHeaderContainer.jsx | 11 +- .../IntroduceHeaderContainer.test.jsx | 179 +++++++++++------- 7 files changed, 218 insertions(+), 92 deletions(-) diff --git a/src/components/introduce/ModeratorViewButton.jsx b/src/components/introduce/ModeratorViewButton.jsx index 0b28b10..4310672 100644 --- a/src/components/introduce/ModeratorViewButton.jsx +++ b/src/components/introduce/ModeratorViewButton.jsx @@ -3,7 +3,7 @@ import React, { useState } from 'react'; import StyledApplyStatusButton from '../../styles/StyledApplyStatusButton'; import ParticipantListModal from './modals/ParticipantListModal'; -const ModeratorViewButton = ({ group, user }) => { +const ModeratorViewButton = ({ group, user, onUpdateConfirm }) => { const [ListModal, setListModal] = useState(false); const { moderatorId, participants } = group; @@ -32,6 +32,7 @@ const ModeratorViewButton = ({ group, user }) => { index !== 0)} /> diff --git a/src/components/introduce/modals/ParticipantList.jsx b/src/components/introduce/modals/ParticipantList.jsx index fd7a7d1..f995ff9 100644 --- a/src/components/introduce/modals/ParticipantList.jsx +++ b/src/components/introduce/modals/ParticipantList.jsx @@ -10,12 +10,12 @@ const ParticipantListWrapper = styled.div` grid-template-columns: 260px 186px 148px; justify-items: center; align-items: center; - margin-bottom: 0.5rem; + margin-bottom: 0.7rem; min-height: 0; min-width: 0; `; -const ParticipantList = ({ participant }) => { +const ParticipantList = ({ participant, onUpdate }) => { const [viewApplyModal, setViewApplyModal] = useState(false); const { id, confirm } = participant; @@ -42,11 +42,17 @@ const ParticipantList = ({ participant }) => {
{confirm === true ? ( - + 취소하기 ) : ( - + 승인하기 )} diff --git a/src/components/introduce/modals/ParticipantList.test.jsx b/src/components/introduce/modals/ParticipantList.test.jsx index 4797e6f..9618c5a 100644 --- a/src/components/introduce/modals/ParticipantList.test.jsx +++ b/src/components/introduce/modals/ParticipantList.test.jsx @@ -5,9 +5,16 @@ import { fireEvent, render } from '@testing-library/react'; import ParticipantList from './ParticipantList'; describe('ParticipantList', () => { + const handleUpdate = jest.fn(); + + beforeEach(() => { + handleUpdate.mockClear(); + }); + const renderParticipantList = (participant) => render(( )); @@ -23,6 +30,16 @@ describe('ParticipantList', () => { expect(container).toHaveTextContent(props.id); expect(container).toHaveTextContent('취소하기'); }); + + it('click cancel button', () => { + const { getByText } = renderParticipantList(props); + + const button = getByText('취소하기'); + + fireEvent.click(button); + + expect(handleUpdate).toBeCalled(); + }); }); context('without confirm', () => { @@ -37,6 +54,16 @@ describe('ParticipantList', () => { expect(container).toHaveTextContent(props.id); expect(container).toHaveTextContent('승인하기'); }); + + it('click confirm button', () => { + const { getByText } = renderParticipantList(props); + + const button = getByText('승인하기'); + + fireEvent.click(button); + + expect(handleUpdate).toBeCalled(); + }); }); describe('click "View application" button', () => { diff --git a/src/components/introduce/modals/ParticipantListModal.jsx b/src/components/introduce/modals/ParticipantListModal.jsx index 394c1fd..54d9251 100644 --- a/src/components/introduce/modals/ParticipantListModal.jsx +++ b/src/components/introduce/modals/ParticipantListModal.jsx @@ -88,7 +88,9 @@ const StyledButton = styled(Button)` } `; -const ParticipantListModal = ({ visible, onClose, participants }) => { +const ParticipantListModal = ({ + visible, onClose, participants, onUpdate, +}) => { if (!visible) { return null; } @@ -107,6 +109,7 @@ const ParticipantListModal = ({ visible, onClose, participants }) => { onUpdate(participant.id)} /> ))} diff --git a/src/components/introduce/modals/ParticipantListModal.test.jsx b/src/components/introduce/modals/ParticipantListModal.test.jsx index f0c1e1b..36c40f4 100644 --- a/src/components/introduce/modals/ParticipantListModal.test.jsx +++ b/src/components/introduce/modals/ParticipantListModal.test.jsx @@ -6,40 +6,81 @@ import ParticipantListModal from './ParticipantListModal'; describe('ParticipantListModal', () => { const handleClose = jest.fn(); + const handleUpdate = jest.fn(); + + beforeEach(() => { + jest.clearAllMocks(); + }); const renderParticipantListModal = ({ visible, participants }) => render(( )); context('with visible', () => { - const modal = { - visible: true, - participants: [{ + const visible = true; + + describe('Contents on the screen', () => { + const participants = [{ confirm: false, id: 'test', - }], - }; + }]; - it('renders Modal text', () => { - const { container } = renderParticipantListModal(modal); + it('renders Modal text', () => { + const { container } = renderParticipantListModal({ visible, participants }); + + expect(container).toHaveTextContent('스터디 신청자 목록 🙋‍♂️'); + expect(container).toHaveTextContent('신청서 보기'); + expect(container).toHaveTextContent('test'); + }); + + it('click button call close', () => { + const { getByText } = renderParticipantListModal({ visible, participants }); + + const button = getByText('닫기'); + + fireEvent.click(button); - expect(container).toHaveTextContent('스터디 신청자 목록 🙋‍♂️'); - expect(container).toHaveTextContent('신청서 보기'); - expect(container).toHaveTextContent('test'); + expect(handleClose).toBeCalled(); + }); }); - it('click button call close', () => { - const { getByText } = renderParticipantListModal(modal); + context('When confirm is true', () => { + const participants = [{ + confirm: true, + id: 'test', + }]; + + it('event is called with user ID after clicking cancel button', () => { + const { getByText } = renderParticipantListModal({ visible, participants }); + + const button = getByText('취소하기'); + + fireEvent.click(button); + + expect(handleUpdate).toBeCalledWith('test'); + }); + }); + + context('When confirm is false', () => { + const participants = [{ + confirm: false, + id: 'test', + }]; + + it('renders confirm button', () => { + const { getByText } = renderParticipantListModal({ visible, participants }); - const button = getByText('닫기'); + const button = getByText('승인하기'); - fireEvent.click(button); + fireEvent.click(button); - expect(handleClose).toBeCalled(); + expect(handleUpdate).toBeCalledWith('test'); + }); }); }); diff --git a/src/containers/introduce/IntroduceHeaderContainer.jsx b/src/containers/introduce/IntroduceHeaderContainer.jsx index e4d0691..6f2f4b0 100644 --- a/src/containers/introduce/IntroduceHeaderContainer.jsx +++ b/src/containers/introduce/IntroduceHeaderContainer.jsx @@ -5,7 +5,11 @@ import { useDispatch, useSelector } from 'react-redux'; import { getAuth, getGroup } from '../../util/utils'; import { - changeApplyFields, clearApplyFields, deleteParticipant, updateParticipant, + changeApplyFields, + clearApplyFields, + deleteParticipant, + updateConfirmParticipant, + updateParticipant, } from '../../reducers/groupSlice'; import IntroduceHeader from '../../components/introduce/IntroduceHeader'; @@ -42,6 +46,10 @@ const IntroduceHeaderContainer = () => { dispatch(clearApplyFields()); }, [dispatch]); + const onUpdateConfirmParticipant = useCallback((id) => { + dispatch(updateConfirmParticipant(id)); + }, [dispatch]); + if (!group) { return ( @@ -61,6 +69,7 @@ const IntroduceHeaderContainer = () => { onChangeApplyFields={onChangeApplyFields} /> diff --git a/src/containers/introduce/IntroduceHeaderContainer.test.jsx b/src/containers/introduce/IntroduceHeaderContainer.test.jsx index 8041c34..a056068 100644 --- a/src/containers/introduce/IntroduceHeaderContainer.test.jsx +++ b/src/containers/introduce/IntroduceHeaderContainer.test.jsx @@ -74,118 +74,157 @@ describe('IntroduceHeaderContainer', () => { }); }); - context('with group & user', () => { - given('group', () => (STUDY_GROUP)); - given('user', () => ('user')); - given('applyFields', () => ({ - reason: 'reason', - wantToGet: 'wantToGet', - })); + context('When the logged-in user is not the author', () => { + context('with group & user', () => { + given('group', () => (STUDY_GROUP)); + given('user', () => ('user')); + given('applyFields', () => ({ + reason: 'reason', + wantToGet: 'wantToGet', + })); + + it('click event dispatches action call updateParticipant', () => { + const { getByText } = renderIntroduceContainer(); + + const button = getByText('신청하기'); + + expect(button).not.toBeNull(); + + fireEvent.click(button); + + fireEvent.click(getByText('확인')); + + expect(dispatch).toBeCalledTimes(1); + }); + + it('dispatches action calls changeApplyFields', () => { + const form = { + name: 'reason', + value: '내용', + }; + + const { getByText, getByLabelText } = renderIntroduceContainer(); + + const button = getByText('신청하기'); + + fireEvent.click(button); - it('click event dispatches action call updateParticipant', () => { - const { getByText } = renderIntroduceContainer(); + const input = getByLabelText('신청하게 된 이유'); - const button = getByText('신청하기'); + fireEvent.change(input, { target: form }); - expect(button).not.toBeNull(); + expect(dispatch).toBeCalledWith({ + type: 'group/changeApplyFields', + payload: form, + }); + }); + + it('click cancel dispatches call action clearApplyFields', () => { + const { getByText } = renderIntroduceContainer(); - fireEvent.click(button); + const button = getByText('신청하기'); - fireEvent.click(getByText('확인')); + expect(button).not.toBeNull(); + + fireEvent.click(button); - expect(dispatch).toBeCalledTimes(1); + fireEvent.click(getByText('취소')); + + expect(dispatch).toBeCalledWith({ + type: 'group/clearApplyFields', + }); + }); }); - it('dispatches action calls changeApplyFields', () => { - const form = { - name: 'reason', - value: '내용', + describe(`When the application date is earlier than the deadline + date and the application deadline is not reached`, () => { + const nowDate = new Date(); + const tomorrow = nowDate.setDate(nowDate.getDate() + 1); + + const group = { + ...STUDY_GROUP, + applyEndDate: tomorrow, + participants: [ + { id: 'user2' }, + { id: 'user' }, + ], + personnel: 3, }; - const { getByText, getByLabelText } = renderIntroduceContainer(); + given('group', () => (group)); + given('user', () => ('user')); + given('applyFields', () => ({ + reason: '', + wantToGet: '', + })); - const button = getByText('신청하기'); + context('click confirm', () => { + it('click event dispatches action call deleteParticipant', () => { + const { getByText } = renderIntroduceContainer(); - fireEvent.click(button); + const button = getByText('신청 취소'); - const input = getByLabelText('신청하게 된 이유'); + expect(button).not.toBeNull(); - fireEvent.change(input, { target: form }); + fireEvent.click(button); - expect(dispatch).toBeCalledWith({ - type: 'group/changeApplyFields', - payload: form, + fireEvent.click(getByText('확인')); + + expect(dispatch).toBeCalledTimes(1); + }); }); - }); - it('click cancel dispatches call action clearApplyFields', () => { - const { getByText } = renderIntroduceContainer(); + context('click cancel', () => { + it("doesn't click event dispatches action call deleteParticipant", () => { + const { getByText } = renderIntroduceContainer(); - const button = getByText('신청하기'); + const button = getByText('신청 취소'); - expect(button).not.toBeNull(); + expect(button).not.toBeNull(); - fireEvent.click(button); + fireEvent.click(button); - fireEvent.click(getByText('취소')); + fireEvent.click(getByText('취소')); - expect(dispatch).toBeCalledWith({ - type: 'group/clearApplyFields', + expect(dispatch).not.toBeCalled(); + }); }); }); }); - describe(`When the application date is earlier than the deadline - date and the application deadline is not reached`, () => { - const nowDate = new Date(); - const tomorrow = nowDate.setDate(nowDate.getDate() + 1); - - const group = { + context('When the logged-in user is the author', () => { + given('group', () => ({ ...STUDY_GROUP, - applyEndDate: tomorrow, participants: [ - { id: 'user2' }, - { id: 'user' }, + { + confirm: true, + id: 'test1', + }, + { + confirm: false, + id: 'test2', + }, ], - personnel: 3, - }; - - given('group', () => (group)); - given('user', () => ('user')); + })); + given('user', () => ('user2')); given('applyFields', () => ({ reason: '', wantToGet: '', })); - context('click confirm', () => { - it('click event dispatches action call deleteParticipant', () => { + describe('Click "Approve to participate in the study" button and then click "Approve" button', () => { + it('dispatches call action "updateConfirmParticipant"', () => { const { getByText } = renderIntroduceContainer(); - const button = getByText('신청 취소'); + const button = getByText('스터디 참여 승인하기'); expect(button).not.toBeNull(); fireEvent.click(button); - fireEvent.click(getByText('확인')); - - expect(dispatch).toBeCalledTimes(1); - }); - }); - - context('click cancel', () => { - it("doesn't click event dispatches action call deleteParticipant", () => { - const { getByText } = renderIntroduceContainer(); - - const button = getByText('신청 취소'); - - expect(button).not.toBeNull(); - - fireEvent.click(button); - - fireEvent.click(getByText('취소')); + fireEvent.click(getByText('승인하기')); - expect(dispatch).not.toBeCalled(); + expect(dispatch).toBeCalled(); }); }); });