Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 28 additions & 12 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,41 @@
import React from 'react';

import { useDispatch } from 'react-redux';
import { Switch, Route } from 'react-router-dom';

import { loadItem } from './services/storage';
import { setUser } from './reducers/slice';

import MainPage from './pages/MainPage';
import WritePage from './pages/WritePage';
import IntroducePage from './pages/IntroducePage';
import LoginPage from './pages/LoginPage';
import RegisterPage from './pages/RegisterPage';
import HeaderContainer from './containers/common/HeaderContainer';

const App = () => (
<>
<HeaderContainer />
<Switch>
<Route exact path="/" component={MainPage} />
<Route path="/introduce/:id" component={IntroducePage} />
<Route component={LoginPage} path="/login" />
<Route component={RegisterPage} path="/register" />
<Route path="/write" component={WritePage} />
</Switch>
</>
);
const App = () => {
const dispatch = useDispatch();

const user = loadItem('user');

if (user) {
const { email } = user;

dispatch(setUser(email));
}
Comment on lines +21 to +25

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useEffect로 감싸줘도 되지 않을까요?


return (
<>
<HeaderContainer />
<Switch>
<Route exact path="/" component={MainPage} />
<Route path="/introduce/:id" component={IntroducePage} />
<Route component={LoginPage} path="/login" />
<Route component={RegisterPage} path="/register" />
<Route path="/write" component={WritePage} />
</Switch>
</>
);
};

export default App;
25 changes: 23 additions & 2 deletions src/App.test.jsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import React from 'react';

import { useDispatch, useSelector } from 'react-redux';

import { MemoryRouter } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';

import { render } from '@testing-library/react';

import App from './App';

import { loadItem } from './services/storage';

import STUDY_GROUPS from '../fixtures/study-groups';
import STUDY_GROUP from '../fixtures/study-group';

jest.mock('react-redux');
jest.mock('./services/storage');

describe('App', () => {
const dispatch = jest.fn();
Expand Down Expand Up @@ -87,4 +89,23 @@ describe('App', () => {
expect(container).toHaveTextContent('회원가입');
});
});

context('when logged in', () => {
const user = {
email: 'seungmin@naver.com',
};

beforeEach(() => {
loadItem.mockImplementation(() => user);
});

it('calls dispatch with "setUser" action', () => {
renderApp({ path: '/' });

expect(dispatch).toBeCalledWith({
type: 'application/setUser',
payload: user.email,
});
});
});
});
16 changes: 11 additions & 5 deletions src/components/common/Header.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,21 @@ const Spacer = styled.div`
height: 6rem;
`;

const Header = () => (
const Header = ({ user }) => (
<>
<HeaderWrapper>
<Wrapper>
<TitleWrapper to="/">제목(미정)</TitleWrapper>
<div>
<LinkWrapper to="/login">로그인</LinkWrapper>
<LinkWrapper to="/register">회원가입</LinkWrapper>
</div>
{user ? (
<div>
<button type="button">로그아웃</button>
</div>
) : (
<div>
<LinkWrapper to="/login">로그인</LinkWrapper>
<LinkWrapper to="/register">회원가입</LinkWrapper>
</div>
)}
</Wrapper>
</HeaderWrapper>
<Spacer />
Expand Down
30 changes: 28 additions & 2 deletions src/containers/auth/LoginFormContainer.jsx
Original file line number Diff line number Diff line change
@@ -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, requestLogin } from '../../reducers/slice';

import AuthForm from '../../components/auth/AuthForm';

const LoginFormContainer = () => {
const dispatch = useDispatch();
const history = useHistory();

const login = useSelector(get('login'));
const user = useSelector(get('user'));
const authError = useSelector(get('authError'));

const onChangeLoginField = useCallback(({ name, value }) => {
dispatch(
Expand All @@ -22,11 +26,33 @@ const LoginFormContainer = () => {
);
});

const onSubmit = useCallback(() => {
// TODO: 로그인 validation 체크 로직 추가

dispatch(requestLogin());
}, [dispatch]);

useEffect(() => {
if (user) {
history.push('/');
}

if (authError) {
// TODO: error 처리 추가
console.error(authError);
}
}, [user, authError]);

useEffect(() => () => {
dispatch(clearAuth());
}, [dispatch]);

return (
<AuthForm
type="login"
fields={login}
onChange={onChangeLoginField}
onSubmit={onSubmit}
/>
);
};
Expand Down
46 changes: 46 additions & 0 deletions src/containers/auth/LoginFormContainer.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,31 @@ import { render, fireEvent } from '@testing-library/react';

import LoginFormContainer from './LoginFormContainer';

const mockPush = jest.fn();

jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useHistory() {
return { push: mockPush };
},
}));

describe('LoginFormContainer', () => {
const dispatch = jest.fn();

beforeEach(() => {
dispatch.mockClear();
mockPush.mockClear();

useDispatch.mockImplementation(() => dispatch);

useSelector.mockImplementation((selector) => selector({
login: {
userEmail: '',
password: '',
},
user: given.user,
authError: given.authError,
}));
});

Expand Down Expand Up @@ -59,5 +72,38 @@ describe('LoginFormContainer', () => {
});
});
});

it('submit event calls dispatch', () => {
const { getByTestId } = renderLoginFormContainer();

const button = getByTestId('auth-button');

expect(button).not.toBeNull();

fireEvent.submit(button);

expect(dispatch).toBeCalled();
});
});

describe('actions after login', () => {
context('when success auth to login', () => {
given('user', () => ({
user: 'seungmin@naver.com',
}));

it('redirection go to main page', () => {
renderLoginFormContainer();

expect(mockPush).toBeCalledWith('/');
});
});

// TODO: 현재 authError는 콘솔 출력
context('when failure auth to login', () => {
given('authError', () => ({
authError: 'error',
}));
});
});
});
19 changes: 14 additions & 5 deletions src/containers/auth/RegisterFormContainer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const RegisterFormContainer = () => {

const register = useSelector(get('register'));
const auth = useSelector(get('auth'));
const user = useSelector(get('user'));
const authError = useSelector(get('authError'));

const onChangeRegisterField = useCallback(({ name, value }) => {
Expand All @@ -27,23 +28,31 @@ const RegisterFormContainer = () => {
}, [dispatch]);

const onSubmit = useCallback(() => {
// TODO: 회원가입 validation 체크 로직 추가

dispatch(requestRegister());
}, [dispatch]);

useEffect(() => {
if (user) {
history.push('/');
}
}, [user]);

useEffect(() => {
if (auth) {
history.push('/login');
}

if (authError) {
// TODO: 추 후 error 처리
// TODO: error 처리 추가
console.error(authError);
}
}, [auth, authError]);

return () => {
dispatch(clearAuth());
};
}, [dispatch, auth, authError]);
useEffect(() => () => {
dispatch(clearAuth());
}, [dispatch]);
Comment on lines +53 to +55

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분이 호출되는 시점이 어떻게 될까요?
unmount 되는 시점에 auth 정보들을 clear해주는 목적이라면, react-use 라이브러리의 useUnmount를 사용해도 좋을 것 같아요~


return (
<AuthForm
Expand Down
21 changes: 18 additions & 3 deletions src/containers/auth/RegisterFormContainer.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,19 @@ describe('RegisterFormContainer', () => {

beforeEach(() => {
dispatch.mockClear();
mockPush.mockClear();

useDispatch.mockImplementation(() => dispatch);

useSelector.mockImplementation((selector) => selector({
user: given.user,
auth: given.auth,
authError: given.authError,
register: {
userEmail: '',
password: '',
passwordConfirm: '',
},
auth: given.auth,
authError: given.authError,
}));
});

Expand Down Expand Up @@ -90,7 +93,7 @@ describe('RegisterFormContainer', () => {
});
});

describe('action after signing up', () => {
describe('actions after signing up', () => {
context('when success auth to register', () => {
given('auth', () => ({
auth: 'seungmin@naver.com',
Expand All @@ -110,4 +113,16 @@ describe('RegisterFormContainer', () => {
}));
});
});

describe('action after login', () => {
given('user', () => ({
user: 'seungmin@naver.com',
}));

it('redirection go to main page', () => {
renderRegisterFormContainer();

expect(mockPush).toBeCalledWith('/');
});
});
});
17 changes: 13 additions & 4 deletions src/containers/common/HeaderContainer.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
import React from 'react';

import { useSelector } from 'react-redux';

import { get } from '../../util/utils';

import Header from '../../components/common/Header';

const HeaderContainer = () => ((
<Header />
)
);
const HeaderContainer = () => {
const user = useSelector(get('user'));

return (
<Header
user={user}
/>
);
};

export default HeaderContainer;
Loading