diff --git a/src/components/auth/AuthForm.jsx b/src/components/auth/AuthForm.jsx
index cb0c1a8..3f9a0ff 100644
--- a/src/components/auth/AuthForm.jsx
+++ b/src/components/auth/AuthForm.jsx
@@ -11,7 +11,9 @@ const FORM_TYPE = {
register: '회원가입',
};
-const AuthForm = ({ type, onChange, fields }) => {
+const AuthForm = ({
+ type, fields, onChange, onSubmit,
+}) => {
const formType = FORM_TYPE[type];
const { userEmail, password } = fields;
@@ -22,35 +24,49 @@ const AuthForm = ({ type, onChange, fields }) => {
onChange({ name, value });
};
+ const handleSubmit = (e) => {
+ e.preventDefault();
+
+ onSubmit();
+ };
+
return (
{formType}
-
-
- {type === 'register' && (
+
);
};
diff --git a/src/components/auth/AuthForm.test.jsx b/src/components/auth/AuthForm.test.jsx
index 48b0665..5b5f90f 100644
--- a/src/components/auth/AuthForm.test.jsx
+++ b/src/components/auth/AuthForm.test.jsx
@@ -6,9 +6,11 @@ import AuthForm from './AuthForm';
describe('AuthForm', () => {
const handleChange = jest.fn();
+ const handleSubmit = jest.fn();
beforeEach(() => {
handleChange.mockClear();
+ handleSubmit.mockClear();
});
const renderAuthForm = ({ type, fields }) => render((
@@ -16,6 +18,7 @@ describe('AuthForm', () => {
type={type}
fields={fields}
onChange={handleChange}
+ onSubmit={handleSubmit}
/>
));
@@ -94,5 +97,17 @@ describe('AuthForm', () => {
expect(handleChange).toBeCalled();
});
});
+
+ it('listens event call submit', () => {
+ const { getByTestId } = renderAuthForm(register);
+
+ const button = getByTestId('auth-button');
+
+ expect(button).not.toBeNull();
+
+ fireEvent.submit(button);
+
+ expect(handleSubmit).toBeCalled();
+ });
});
});
diff --git a/src/containers/auth/RegisterFormContainer.jsx b/src/containers/auth/RegisterFormContainer.jsx
index ecdbc9c..036d1e9 100644
--- a/src/containers/auth/RegisterFormContainer.jsx
+++ b/src/containers/auth/RegisterFormContainer.jsx
@@ -1,16 +1,20 @@
-import React, { useCallback } from 'react';
+import React, { useCallback, useEffect } from 'react';
+import { useHistory } from 'react-router-dom';
import { useSelector, useDispatch } from 'react-redux';
import { get } from '../../util/utils';
-import { changeAuthField } from '../../reducers/slice';
+import { changeAuthField, clearAuth, requestRegister } from '../../reducers/slice';
import AuthForm from '../../components/auth/AuthForm';
const RegisterFormContainer = () => {
const dispatch = useDispatch();
+ const history = useHistory();
const register = useSelector(get('register'));
+ const auth = useSelector(get('auth'));
+ const authError = useSelector(get('authError'));
const onChangeRegisterField = useCallback(({ name, value }) => {
dispatch(
@@ -20,13 +24,33 @@ const RegisterFormContainer = () => {
value,
}),
);
- });
+ }, [dispatch]);
+
+ const onSubmit = useCallback(() => {
+ dispatch(requestRegister());
+ }, [dispatch]);
+
+ useEffect(() => {
+ if (auth) {
+ history.push('/login');
+ }
+
+ if (authError) {
+ // TODO: 추 후 error 처리
+ console.error(authError);
+ }
+
+ return () => {
+ dispatch(clearAuth());
+ };
+ }, [dispatch, auth, authError]);
return (
);
};
diff --git a/src/containers/auth/RegisterFormContainer.test.jsx b/src/containers/auth/RegisterFormContainer.test.jsx
index 09bfc35..f7149e3 100644
--- a/src/containers/auth/RegisterFormContainer.test.jsx
+++ b/src/containers/auth/RegisterFormContainer.test.jsx
@@ -1,11 +1,21 @@
import React from 'react';
+import { MemoryRouter } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';
import { render, fireEvent } from '@testing-library/react';
import RegisterFormContainer from './RegisterFormContainer';
+const mockPush = jest.fn();
+
+jest.mock('react-router-dom', () => ({
+ ...jest.requireActual('react-router-dom'),
+ useHistory() {
+ return { push: mockPush };
+ },
+}));
+
describe('RegisterFormContainer', () => {
const dispatch = jest.fn();
@@ -19,11 +29,15 @@ describe('RegisterFormContainer', () => {
password: '',
passwordConfirm: '',
},
+ auth: given.auth,
+ authError: given.authError,
}));
});
const renderRegisterFormContainer = () => render((
-
+
+
+
));
it('renders register form text', () => {
@@ -62,5 +76,38 @@ describe('RegisterFormContainer', () => {
});
});
});
+
+ it('submit event calls dispatch', () => {
+ const { getByTestId } = renderRegisterFormContainer();
+
+ const button = getByTestId('auth-button');
+
+ expect(button).not.toBeNull();
+
+ fireEvent.submit(button);
+
+ expect(dispatch).toBeCalled();
+ });
+ });
+
+ describe('action after signing up', () => {
+ context('when success auth to register', () => {
+ given('auth', () => ({
+ auth: 'seungmin@naver.com',
+ }));
+
+ it('go to login page', () => {
+ renderRegisterFormContainer();
+
+ expect(mockPush).toBeCalledWith('/login');
+ });
+ });
+
+ // TODO: 현재 authError는 콘솔 출력
+ context('when failure auth to register', () => {
+ given('authError', () => ({
+ authError: 'error',
+ }));
+ });
});
});
diff --git a/src/reducers/slice.js b/src/reducers/slice.js
index dcb2ae4..dedcf5b 100644
--- a/src/reducers/slice.js
+++ b/src/reducers/slice.js
@@ -6,6 +6,7 @@ import {
getStudyGroup,
getStudyGroups,
postStudyGroup,
+ postUserRegister,
} from '../services/api';
const writeInitialState = {
@@ -39,6 +40,8 @@ const { actions, reducer } = createSlice({
writeField: writeInitialState,
register: authInitialState.register,
login: authInitialState.login,
+ auth: null,
+ authError: null,
},
reducers: {
@@ -95,6 +98,38 @@ const { actions, reducer } = createSlice({
draft[form][name] = value;
});
},
+
+ clearAuthFields(state) {
+ const { register, login } = authInitialState;
+
+ return {
+ ...state,
+ register,
+ login,
+ };
+ },
+
+ setAuth(state, { payload: user }) {
+ return {
+ ...state,
+ auth: user,
+ };
+ },
+
+ setAuthError(state, { payload: error }) {
+ return {
+ ...state,
+ authError: error,
+ };
+ },
+
+ clearAuth(state) {
+ return {
+ ...state,
+ auth: null,
+ authError: null,
+ };
+ },
},
});
@@ -105,6 +140,10 @@ export const {
clearWriteFields,
successWrite,
changeAuthField,
+ clearAuthFields,
+ setAuth,
+ setAuthError,
+ clearAuth,
} = actions;
export const loadStudyGroups = (tag) => async (dispatch) => {
@@ -135,4 +174,18 @@ export const writeStudyGroup = () => async (dispatch, getState) => {
dispatch(clearWriteFields());
};
+export const requestRegister = () => async (dispatch, getState) => {
+ const { register: { userEmail, password } } = getState();
+
+ try {
+ const { user } = await postUserRegister({ userEmail, password });
+
+ dispatch(setAuth(user.email));
+
+ dispatch(clearAuthFields());
+ } catch (error) {
+ setAuthError(error);
+ }
+};
+
export default reducer;
diff --git a/src/reducers/slice.test.js b/src/reducers/slice.test.js
index 158aa62..96e4e62 100644
--- a/src/reducers/slice.test.js
+++ b/src/reducers/slice.test.js
@@ -13,11 +13,17 @@ import reducer, {
clearWriteFields,
successWrite,
changeAuthField,
+ clearAuthFields,
+ setAuth,
+ setAuthError,
+ clearAuth,
+ requestRegister,
} from './slice';
import STUDY_GROUPS from '../../fixtures/study-groups';
import STUDY_GROUP from '../../fixtures/study-group';
import WRITE_FORM from '../../fixtures/write-form';
+import { postUserRegister } from '../services/api';
const middlewares = [thunk];
const mockStore = configureStore(middlewares);
@@ -48,6 +54,8 @@ describe('reducer', () => {
userEmail: '',
password: '',
},
+ auth: null,
+ authError: null,
};
it('returns initialState', () => {
@@ -197,6 +205,69 @@ describe('reducer', () => {
});
});
});
+
+ describe('clearAuthFields', () => {
+ const initialState = {
+ register: {
+ userEmail: 'seungmin@naver.com',
+ password: '1234',
+ passwordConfirm: '1234',
+ },
+ login: {
+ userEmail: 'seungmin@naver.com',
+ password: '1234',
+ },
+ };
+
+ it('auth form is all cleared', () => {
+ const { register, login } = reducer(initialState, clearAuthFields());
+
+ expect(register.userEmail).toBe('');
+ expect(login.userEmail).toBe('');
+ });
+ });
+
+ describe('setAuth', () => {
+ const initialState = {
+ auth: null,
+ };
+
+ it('authentication success', () => {
+ const userEmail = 'seungmin@naver.com';
+
+ const { auth } = reducer(initialState, setAuth(userEmail));
+
+ expect(auth).toBe(userEmail);
+ });
+ });
+
+ describe('setAuthError', () => {
+ const initialState = {
+ authError: null,
+ };
+
+ it('authentication failure', () => {
+ const error = 'error message';
+
+ const { authError } = reducer(initialState, setAuthError(error));
+
+ expect(authError).toBe(error);
+ });
+ });
+
+ describe('clearAuth', () => {
+ const initialState = {
+ auth: 'seungmin@naver.com',
+ authError: 'error',
+ };
+
+ it('Clean up to auth and authError', () => {
+ const { auth, authError } = reducer(initialState, clearAuth());
+
+ expect(auth).toBe(null);
+ expect(authError).toBe(null);
+ });
+ });
});
describe('async actions', () => {
@@ -250,4 +321,51 @@ describe('async actions', () => {
expect(actions[1]).toEqual(clearWriteFields(undefined));
});
});
+
+ describe('requestRegister', () => {
+ const register = {
+ userEmail: 'seungmin@naver.com',
+ password: '123456',
+ };
+
+ beforeEach(() => {
+ store = mockStore({
+ register,
+ });
+ });
+
+ context('without auth error', () => {
+ const { userEmail } = register;
+
+ postUserRegister.mockImplementationOnce(() => ({
+ user: {
+ email: userEmail,
+ },
+ }));
+ it('dispatches requestRegister action success to return user email', async () => {
+ await store.dispatch(requestRegister());
+
+ const actions = store.getActions();
+
+ expect(actions[0]).toEqual(setAuth(userEmail));
+ expect(actions[1]).toEqual(clearAuthFields());
+ });
+ });
+
+ context('with auth error', () => {
+ postUserRegister.mockImplementationOnce(() => {
+ throw new Error('error');
+ });
+
+ it('dispatches requestRegister action failure to return error', async () => {
+ try {
+ await store.dispatch(requestRegister());
+ } catch (error) {
+ const actions = store.getActions();
+
+ expect(actions[0]).toEqual(setAuthError(error));
+ }
+ });
+ });
+ });
});
diff --git a/src/services/__mocks__/api.js b/src/services/__mocks__/api.js
index 55c13c7..2dee810 100644
--- a/src/services/__mocks__/api.js
+++ b/src/services/__mocks__/api.js
@@ -3,3 +3,5 @@ export const getStudyGroups = async () => [];
export const getStudyGroup = async () => {};
export const postStudyGroup = async () => {};
+
+export const postUserRegister = jest.fn();
diff --git a/src/services/api.js b/src/services/api.js
index 3500951..36b69b9 100644
--- a/src/services/api.js
+++ b/src/services/api.js
@@ -1,4 +1,4 @@
-import db from './firebase';
+import { db, auth } from './firebase';
export const getStudyGroups = async () => {
const response = await db.collection('groups').get();
@@ -26,3 +26,23 @@ export const postStudyGroup = async (post) => {
return id;
};
+
+export const postUserRegister = async ({ userEmail, password }) => {
+ const response = await auth
+ .createUserWithEmailAndPassword(userEmail, password);
+
+ return response;
+};
+
+export const postUserLogin = async ({ userEmail, password }) => {
+ const response = await auth
+ .signInWithEmailAndPassword(userEmail, password);
+
+ return response;
+};
+
+export const postUserLogout = async () => {
+ const response = await auth.signOut();
+
+ return response;
+};
diff --git a/src/services/api.test.js b/src/services/api.test.js
index 2a07906..ae8ddd1 100644
--- a/src/services/api.test.js
+++ b/src/services/api.test.js
@@ -1,7 +1,10 @@
-import * as firebase from 'firebase';
+import { db, auth } from './firebase';
import {
postStudyGroup,
+ postUserRegister,
+ postUserLogin,
+ postUserLogout,
} from './api';
import STUDY_GROUP from '../../fixtures/study-group';
@@ -14,7 +17,7 @@ describe('api', () => {
describe('postStudyGroup', () => {
const add = jest.fn((group) => group);
const collection = jest.spyOn(
- firebase.firestore(), 'collection',
+ db, 'collection',
).mockReturnValue({ add });
it('write a study recruitment article', async () => {
@@ -25,4 +28,79 @@ describe('api', () => {
expect(add).toHaveBeenCalledWith(STUDY_GROUP);
});
});
+
+ describe('postUserRegister', () => {
+ const register = {
+ user: {
+ email: 'seungmin@naver.com',
+ password: '123456',
+ },
+ };
+
+ beforeEach(() => {
+ auth.createUserWithEmailAndPassword = jest.fn().mockResolvedValue(register);
+ });
+
+ it('email returns after user sign up', async () => {
+ const { user } = await postUserRegister(register);
+
+ const { user: { email } } = register;
+
+ expect(user.email).toBe(email);
+ });
+ });
+
+ describe('postUserRegister', () => {
+ const register = {
+ user: {
+ email: 'seungmin@naver.com',
+ password: '123456',
+ },
+ };
+
+ beforeEach(() => {
+ auth.createUserWithEmailAndPassword = jest.fn().mockResolvedValue(register);
+ });
+
+ it('email returns after user sign up', async () => {
+ const { user } = await postUserRegister(register);
+
+ const { user: { email } } = register;
+
+ expect(user.email).toBe(email);
+ });
+ });
+
+ describe('postUserLogin', () => {
+ const login = {
+ user: {
+ email: 'seungmin@naver.com',
+ password: '123456',
+ },
+ };
+
+ beforeEach(() => {
+ auth.signInWithEmailAndPassword = jest.fn().mockResolvedValue(login);
+ });
+
+ it('email returns after user login', async () => {
+ const { user } = await postUserLogin(login);
+
+ const { user: { email } } = login;
+
+ expect(user.email).toBe(email);
+ });
+ });
+
+ describe('postUserLogout', () => {
+ beforeEach(() => {
+ auth.signOut = jest.fn().mockResolvedValue(true);
+ });
+
+ it('returns true after success logout', async () => {
+ const response = await postUserLogout();
+
+ expect(response).toBe(true);
+ });
+ });
});