From 434d3185657247a9f367a58bbee2c0c7c229cd89 Mon Sep 17 00:00:00 2001 From: saseungmin Date: Mon, 30 Nov 2020 02:23:35 +0900 Subject: [PATCH] [Feature] Implement logout function - with firebase auth and redux - change header status --- src/components/common/Header.jsx | 10 ++- src/components/common/Header.test.jsx | 26 ++++++-- src/containers/common/HeaderContainer.jsx | 16 ++++- .../common/HeaderContainer.test.jsx | 64 +++++++++++++++++-- src/reducers/slice.js | 18 +++++- src/reducers/slice.test.js | 32 +++++++++- src/services/__mocks__/api.js | 2 + src/services/api.js | 4 +- src/services/api.test.js | 6 +- 9 files changed, 153 insertions(+), 25 deletions(-) diff --git a/src/components/common/Header.jsx b/src/components/common/Header.jsx index 762f183..d72b8c3 100644 --- a/src/components/common/Header.jsx +++ b/src/components/common/Header.jsx @@ -43,14 +43,20 @@ const Spacer = styled.div` height: 6rem; `; -const Header = ({ user }) => ( +const Header = ({ user, onLogout }) => ( <> 제목(미정) {user ? (
- + {user} +
) : (
diff --git a/src/components/common/Header.test.jsx b/src/components/common/Header.test.jsx index b453f41..4aafe31 100644 --- a/src/components/common/Header.test.jsx +++ b/src/components/common/Header.test.jsx @@ -6,17 +6,29 @@ import { render } from '@testing-library/react'; import Header from './Header'; describe('Header', () => { - const renderHeader = () => render(( + const renderHeader = (user) => render(( -
+
)); - it('renders Header text', () => { - const { container } = renderHeader(); + context('with user', () => { + const user = 'seungmin@naver.com'; - expect(container).toHaveTextContent('제목(미정)'); - expect(container).toHaveTextContent('로그인'); - expect(container).toHaveTextContent('회원가입'); + it('renders Header text', () => { + const { container } = renderHeader(user); + + expect(container).toHaveTextContent('로그아웃'); + expect(container).toHaveTextContent(user); + }); + }); + + context('without user', () => { + it('renders Header text', () => { + const { container } = renderHeader(); + + expect(container).toHaveTextContent('로그인'); + expect(container).toHaveTextContent('회원가입'); + }); }); }); diff --git a/src/containers/common/HeaderContainer.jsx b/src/containers/common/HeaderContainer.jsx index c711dc3..e9a8e31 100644 --- a/src/containers/common/HeaderContainer.jsx +++ b/src/containers/common/HeaderContainer.jsx @@ -1,17 +1,29 @@ -import React from 'react'; +import React, { useCallback } from 'react'; -import { useSelector } from 'react-redux'; +import { useHistory } from 'react-router-dom'; +import { useDispatch, useSelector } from 'react-redux'; import { get } from '../../util/utils'; +import { requestLogout } from '../../reducers/slice'; import Header from '../../components/common/Header'; const HeaderContainer = () => { + const dispatch = useDispatch(); + const history = useHistory(); + const user = useSelector(get('user')); + const onLogout = useCallback(() => { + dispatch(requestLogout()); + + history.push('/'); + }, [dispatch, history]); + return (
); }; diff --git a/src/containers/common/HeaderContainer.test.jsx b/src/containers/common/HeaderContainer.test.jsx index 0900540..dba5880 100644 --- a/src/containers/common/HeaderContainer.test.jsx +++ b/src/containers/common/HeaderContainer.test.jsx @@ -1,20 +1,74 @@ import React from 'react'; -import { render } from '@testing-library/react'; - import { MemoryRouter } from 'react-router-dom'; +import { useDispatch, useSelector } from 'react-redux'; + +import { fireEvent, render } from '@testing-library/react'; + import HeaderContainer from './HeaderContainer'; +const mockPush = jest.fn(); + +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useHistory() { + return { push: mockPush }; + }, +})); + describe('HeaderContainer', () => { + const dispatch = jest.fn(); + + beforeEach(() => { + dispatch.mockClear(); + mockPush.mockClear(); + + useDispatch.mockImplementation(() => dispatch); + + useSelector.mockImplementation((selector) => selector({ + user: given.user, + })); + }); + const renderHeaderContainer = () => render(( )); - it('renders Header Title', () => { - const { container } = renderHeaderContainer(); + context('with user', () => { + given('user', () => ('seungmin@naver.com')); + + it('renders Header text', () => { + const { container } = renderHeaderContainer(); + + expect(container).toHaveTextContent('제목(미정)'); + expect(container).toHaveTextContent('로그아웃'); + }); + + it('logout event calls dispatch actions and redirection go to main page', () => { + const { getByText } = renderHeaderContainer(); + + const button = getByText('로그아웃'); + + expect(button).not.toBeNull(); + + fireEvent.click(button); + + expect(dispatch).toBeCalled(); + expect(mockPush).toBeCalledWith('/'); + }); + }); + + context('without user', () => { + given('user', () => (null)); + + it('renders Header text', () => { + const { container } = renderHeaderContainer(); - expect(container).toHaveTextContent('제목(미정)'); + expect(container).toHaveTextContent('제목(미정)'); + expect(container).toHaveTextContent('로그인'); + expect(container).toHaveTextContent('회원가입'); + }); }); }); diff --git a/src/reducers/slice.js b/src/reducers/slice.js index 4d6e757..26eaee8 100644 --- a/src/reducers/slice.js +++ b/src/reducers/slice.js @@ -7,9 +7,10 @@ import { getStudyGroups, postStudyGroup, postUserLogin, + postUserLogout, postUserRegister, } from '../services/api'; -import { saveItem } from '../services/storage'; +import { removeItem, saveItem } from '../services/storage'; const writeInitialState = { title: '', @@ -140,6 +141,12 @@ const { actions, reducer } = createSlice({ user, }; }, + logout(state) { + return { + ...state, + user: null, + }; + }, }, }); @@ -155,6 +162,7 @@ export const { setAuthError, clearAuth, setUser, + logout, } = actions; export const loadStudyGroups = (tag) => async (dispatch) => { @@ -218,4 +226,12 @@ export const requestLogin = () => async (dispatch, getState) => { } }; +export const requestLogout = () => async (dispatch) => { + await postUserLogout(); + + removeItem('user'); + + dispatch(logout()); +}; + export default reducer; diff --git a/src/reducers/slice.test.js b/src/reducers/slice.test.js index dc41840..176fc0b 100644 --- a/src/reducers/slice.test.js +++ b/src/reducers/slice.test.js @@ -20,12 +20,14 @@ import reducer, { requestRegister, requestLogin, setUser, + logout, + requestLogout, } from './slice'; import STUDY_GROUPS from '../../fixtures/study-groups'; import STUDY_GROUP from '../../fixtures/study-group'; import WRITE_FORM from '../../fixtures/write-form'; -import { postUserLogin, postUserRegister } from '../services/api'; +import { postUserLogin, postUserLogout, postUserRegister } from '../services/api'; const middlewares = [thunk]; const mockStore = configureStore(middlewares); @@ -285,6 +287,18 @@ describe('reducer', () => { expect(user).toBe(userEmail); }); }); + + describe('logout', () => { + const initialState = { + user: 'seungmin@naver.com', + }; + + it('after logout clear user', () => { + const { user } = reducer(initialState, logout()); + + expect(user).toBe(null); + }); + }); }); describe('async actions', () => { @@ -434,4 +448,20 @@ describe('async actions', () => { }); }); }); + + describe('requestLogout', () => { + beforeEach(() => { + store = mockStore({}); + }); + + postUserLogout.mockImplementationOnce(() => ({})); + + it('dispatches requestLogout action success to logout', async () => { + await store.dispatch(requestLogout()); + + const actions = store.getActions(); + + expect(actions[0]).toEqual(logout()); + }); + }); }); diff --git a/src/services/__mocks__/api.js b/src/services/__mocks__/api.js index 4f4819b..e981e42 100644 --- a/src/services/__mocks__/api.js +++ b/src/services/__mocks__/api.js @@ -7,3 +7,5 @@ export const postStudyGroup = async () => {}; export const postUserRegister = jest.fn(); export const postUserLogin = jest.fn(); + +export const postUserLogout = jest.fn(); diff --git a/src/services/api.js b/src/services/api.js index 36b69b9..29ba0b4 100644 --- a/src/services/api.js +++ b/src/services/api.js @@ -42,7 +42,5 @@ export const postUserLogin = async ({ userEmail, password }) => { }; export const postUserLogout = async () => { - const response = await auth.signOut(); - - return response; + await auth.signOut(); }; diff --git a/src/services/api.test.js b/src/services/api.test.js index ae8ddd1..6c171f4 100644 --- a/src/services/api.test.js +++ b/src/services/api.test.js @@ -94,13 +94,11 @@ describe('api', () => { describe('postUserLogout', () => { beforeEach(() => { - auth.signOut = jest.fn().mockResolvedValue(true); + auth.signOut = jest.fn(); }); it('returns true after success logout', async () => { - const response = await postUserLogout(); - - expect(response).toBe(true); + await postUserLogout(); }); }); });