diff --git a/src/App.test.jsx b/src/App.test.jsx index b6fe691..3d85f28 100644 --- a/src/App.test.jsx +++ b/src/App.test.jsx @@ -30,6 +30,10 @@ describe('App', () => { writeField: { tags: [], }, + applyFields: { + reason: '', + wantToGet: '', + }, }, authReducer: { register: { diff --git a/src/components/introduce/IntroduceHeader.jsx b/src/components/introduce/IntroduceHeader.jsx index 29328a2..a48a2ba 100644 --- a/src/components/introduce/IntroduceHeader.jsx +++ b/src/components/introduce/IntroduceHeader.jsx @@ -24,7 +24,7 @@ const IntroduceHeaderWrapper = styled.div` `; const IntroduceHeader = ({ - group, onApply, user, realTime, onApplyCancel, + group, onApply, user, realTime, onApplyCancel, onChangeApplyFields, applyFields, }) => { const [loginCheckModal, setLoginCheckModal] = useState(false); const [applyCancelModal, setApplyCancelModal] = useState(false); @@ -96,6 +96,8 @@ const IntroduceHeader = ({ visible={modalForm} onCancel={handleFormCancel} onConfirm={handleFormSubmit} + onChangeApply={onChangeApplyFields} + fields={applyFields} /> )} diff --git a/src/components/introduce/IntroduceHeader.test.jsx b/src/components/introduce/IntroduceHeader.test.jsx index a3ba9aa..c905cc1 100644 --- a/src/components/introduce/IntroduceHeader.test.jsx +++ b/src/components/introduce/IntroduceHeader.test.jsx @@ -18,6 +18,7 @@ describe('IntroduceHeader', () => { { personnel: 2, }; - it('renders modal window appears and application failure message', () => { - const { container, getByText } = renderIntroduceHeader({ group, time, user: 'user' }); + 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('신청하기'); + const button = getByText('신청하기'); - expect(button).not.toBeNull(); + expect(button).not.toBeNull(); - fireEvent.click(button); + fireEvent.click(button); - // TODO: 이 부분은 추후 변경해야된다 현재 스터디 참여 신청서 모달창이 나타남. - fireEvent.click(getByText('확인')); + // TODO: 이 부분은 추후 변경해야된다 현재 스터디 참여 신청서 모달창이 나타남. + fireEvent.click(getByText('확인')); - expect(handleApply).toBeCalled(); + expect(handleApply).toBeCalled(); - expect(container).not.toHaveTextContent('로그인 후 신청 가능합니다.'); + 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/components/introduce/modals/ApplicationFormModal.jsx b/src/components/introduce/modals/ApplicationFormModal.jsx index 4f65519..37c6947 100644 --- a/src/components/introduce/modals/ApplicationFormModal.jsx +++ b/src/components/introduce/modals/ApplicationFormModal.jsx @@ -40,7 +40,7 @@ const ApplicationFormModalWrapper = styled.div` const ModalBoxWrapper = styled.div` display: flex; flex-direction: column; - height: 570px; + height: 575px; width: 400px; background: white; padding: 1.5rem; @@ -84,13 +84,17 @@ const ContentTextareaWrapper = styled.textarea` padding: 5px; resize: none; outline: none; - border: 1px solid ${palette.gray[3]}; + border: 2px solid #D7E2EB; border-radius: 3px; font-weight: bold; color: rgb(33, 37, 41); margin-bottom: 0.7rem; - &:hover, &:focus { - border: 2px solid ${palette.gray[4]}; + transition-duration: 0.08s; + transition-property: all; + transition-timing-function: ease-in-out; + transition-delay: initial; + &:focus { + border: 2px solid ${palette.teal[5]}; } `; @@ -100,7 +104,17 @@ const StyledButton = styled(Button)` } `; -const ApplicationFormModal = ({ visible, onCancel, onConfirm }) => { +const ApplicationFormModal = ({ + visible, onCancel, onConfirm, onChangeApply, fields, +}) => { + const { reason, wantToGet } = fields; + + const handleChange = (e) => { + const { name, value } = e.target; + + onChangeApply({ name, value }); + }; + if (!visible) { return null; } @@ -111,11 +125,25 @@ const ApplicationFormModal = ({ visible, onCancel, onConfirm }) => {

스터디 참여 신청서 📚

- + - - + +
취소 diff --git a/src/components/introduce/modals/ApplicationFormModal.test.jsx b/src/components/introduce/modals/ApplicationFormModal.test.jsx index cdb1f00..bb9479a 100644 --- a/src/components/introduce/modals/ApplicationFormModal.test.jsx +++ b/src/components/introduce/modals/ApplicationFormModal.test.jsx @@ -7,10 +7,13 @@ import ApplicationFormModal from './ApplicationFormModal'; describe('ApplicationFormModal', () => { const handleCancel = jest.fn(); const handleConfirm = jest.fn(); + const handleChange = jest.fn(); - const renderApplicationFormModal = ({ visible }) => render(( + const renderApplicationFormModal = ({ visible, fields }) => render(( @@ -19,6 +22,10 @@ describe('ApplicationFormModal', () => { context('with visible', () => { const modal = { visible: true, + fields: { + reason: '', + wantToGet: '', + }, }; it('renders Modal text', () => { @@ -48,11 +55,25 @@ describe('ApplicationFormModal', () => { expect(handleCancel).toBeCalled(); }); + + it('change apply form fields', () => { + const { getByLabelText } = renderApplicationFormModal(modal); + + const input = getByLabelText('신청하게 된 이유'); + + fireEvent.change(input, { target: { name: 'reason', value: '내용' } }); + + expect(handleChange).toBeCalled(); + }); }); context('without visible', () => { const modal = { visible: false, + fields: { + reason: '', + wantToGet: '', + }, }; it("doesn't renders Modal text", () => { diff --git a/src/containers/introduce/IntroduceContainer.jsx b/src/containers/introduce/IntroduceContainer.jsx index 1a8b470..bf636fb 100644 --- a/src/containers/introduce/IntroduceContainer.jsx +++ b/src/containers/introduce/IntroduceContainer.jsx @@ -4,7 +4,9 @@ import { useInterval } from 'react-use'; import { useDispatch, useSelector } from 'react-redux'; import { getAuth, getGroup } from '../../util/utils'; -import { deleteParticipant, loadStudyGroup, updateParticipant } from '../../reducers/groupSlice'; +import { + changeApplyFields, deleteParticipant, loadStudyGroup, updateParticipant, +} from '../../reducers/groupSlice'; import StudyIntroduceForm from '../../components/introduce/StudyIntroduceForm'; import GroupContentLoader from '../../components/introduce/GroupsContentLoader'; @@ -15,6 +17,7 @@ const IntroduceContainer = ({ groupId }) => { const dispatch = useDispatch(); + const applyFields = useSelector(getGroup('applyFields')); const group = useSelector(getGroup('group')); const user = useSelector(getAuth('user')); @@ -34,6 +37,10 @@ const IntroduceContainer = ({ groupId }) => { dispatch(deleteParticipant()); }, [dispatch]); + const onChangeApplyFields = useCallback(({ name, value }) => { + dispatch(changeApplyFields({ name, value })); + }, [dispatch]); + if (!group) { return ( @@ -46,8 +53,10 @@ const IntroduceContainer = ({ groupId }) => { user={user} group={group} realTime={realTime} + applyFields={applyFields} onApply={onApplyStudy} onApplyCancel={onApplyCancel} + onChangeApplyFields={onChangeApplyFields} /> { }, groupReducer: { group: given.group, + applyFields: given.applyFields, }, })); }); @@ -48,6 +49,10 @@ describe('IntroduceContainer', () => { 'Algorithm', ], })); + given('applyFields', () => ({ + reason: '', + wantToGet: '', + })); it('renders study group title and contents', () => { const { container } = renderIntroduceContainer(1); @@ -65,6 +70,10 @@ describe('IntroduceContainer', () => { context('without group ', () => { given('group', () => (null)); + given('applyFields', () => ({ + reason: '', + wantToGet: '', + })); it('renders "loading.." text', () => { const { container } = renderIntroduceContainer(1); @@ -76,6 +85,10 @@ describe('IntroduceContainer', () => { context('with group & user', () => { given('group', () => (STUDY_GROUP)); given('user', () => ('user')); + given('applyFields', () => ({ + reason: '', + wantToGet: '', + })); it('click event dispatches action call updateParticipant', () => { const { getByText } = renderIntroduceContainer(1); @@ -93,6 +106,30 @@ describe('IntroduceContainer', () => { expect(dispatch).toBeCalledTimes(2); }); + + it('dispatches action calls changeApplyFields', () => { + const form = { + name: 'reason', + value: '내용', + }; + + const { getByText, getByLabelText } = renderIntroduceContainer(1); + + expect(dispatch).toBeCalledTimes(1); + + const button = getByText('신청하기'); + + fireEvent.click(button); + + const input = getByLabelText('신청하게 된 이유'); + + fireEvent.change(input, { target: form }); + + expect(dispatch).toBeCalledWith({ + type: 'group/changeApplyFields', + payload: form, + }); + }); }); describe(`When the application date is earlier than the deadline @@ -112,6 +149,10 @@ describe('IntroduceContainer', () => { given('group', () => (group)); given('user', () => ('user')); + given('applyFields', () => ({ + reason: '', + wantToGet: '', + })); context('click confirm', () => { it('click event dispatches action call deleteParticipant', () => { diff --git a/src/pages/IntroducePage.test.jsx b/src/pages/IntroducePage.test.jsx index b4d3666..78e8684 100644 --- a/src/pages/IntroducePage.test.jsx +++ b/src/pages/IntroducePage.test.jsx @@ -28,6 +28,10 @@ describe('IntroducePage', () => { 'Algorithm', ], }, + applyFields: { + reason: '', + wantToGet: '', + }, }, authReducer: {}, })); diff --git a/src/reducers/groupSlice.js b/src/reducers/groupSlice.js index 7f8b84f..1ce9978 100644 --- a/src/reducers/groupSlice.js +++ b/src/reducers/groupSlice.js @@ -20,6 +20,11 @@ const writeInitialState = { tags: [], }; +const applyInitialState = { + reason: '', + wantToGet: '', +}; + const { actions, reducer } = createSlice({ name: 'group', initialState: { @@ -27,6 +32,7 @@ const { actions, reducer } = createSlice({ group: null, groupId: null, writeField: writeInitialState, + applyFields: applyInitialState, }, reducers: { @@ -45,15 +51,9 @@ const { actions, reducer } = createSlice({ }, changeWriteField(state, { payload: { name, value } }) { - const { writeField } = state; - - return { - ...state, - writeField: { - ...writeField, - [name]: value, - }, - }; + return produce(state, (draft) => { + draft.writeField[name] = value; + }); }, clearWriteFields(state) { @@ -70,6 +70,12 @@ const { actions, reducer } = createSlice({ groupId, }; }, + + changeApplyFields(state, { payload: { name, value } }) { + return produce(state, (draft) => { + draft.applyFields[name] = value; + }); + }, }, }); @@ -79,6 +85,7 @@ export const { changeWriteField, clearWriteFields, successWrite, + changeApplyFields, } = actions; export const loadStudyGroups = (tag) => async (dispatch) => { diff --git a/src/reducers/groupSlice.test.js b/src/reducers/groupSlice.test.js index aa92267..b944649 100644 --- a/src/reducers/groupSlice.test.js +++ b/src/reducers/groupSlice.test.js @@ -14,6 +14,7 @@ import reducer, { successWrite, updateParticipant, deleteParticipant, + changeApplyFields, } from './groupSlice'; import STUDY_GROUPS from '../../fixtures/study-groups'; @@ -40,6 +41,10 @@ describe('reducer', () => { personnel: '1', tags: [], }, + applyFields: { + reason: '', + wantToGet: '', + }, }; it('returns initialState', () => { @@ -128,6 +133,24 @@ describe('reducer', () => { expect(state.groupId).toBe('1'); }); }); + + describe('changeApplyFields', () => { + it('changes a field of study participation application form', () => { + const initialState = { + applyFields: { + reason: '', + wantToGet: '', + }, + }; + + const state = reducer( + initialState, + changeApplyFields({ name: 'reason', value: '참여합니다.' }), + ); + + expect(state.applyFields.reason).toBe('참여합니다.'); + }); + }); }); describe('async actions', () => {