Merge pull request #153 from DmitryTsepelev/user-info-redux

UserInfo uses redux store
This commit is contained in:
Igor Adamenko
2018-09-29 22:51:21 +03:00
committed by GitHub
12 changed files with 221 additions and 41 deletions
@@ -0,0 +1,31 @@
import store from 'common/store';
import { getThreadIsCollapsed } from './thread.getters';
describe('collapsedThreads', () => {
const notScoredComment = { id: 1 };
const goodComment = { id: 1, score: 3 };
const badComment = { id: 1, score: -1 };
it('takes value from the state', () => {
const state = { collapsedThreads: { [notScoredComment.id]: true } };
const collapsed = getThreadIsCollapsed(state, notScoredComment);
expect(collapsed).toEqual(true);
});
beforeAll(() => {
const config = { critical_score: 2 };
store.set('config', config);
});
it('returns true when score is less then critical_score', () => {
const state = { collapsedThreads: {} };
const collapsed = getThreadIsCollapsed(state, badComment);
expect(collapsed).toEqual(true);
});
it('returns true when score is less then critical_score', () => {
const state = { collapsedThreads: {} };
const collapsed = getThreadIsCollapsed(state, goodComment);
expect(collapsed).toEqual(false);
});
});
@@ -1,29 +1,18 @@
import { setCollapse } from './thread.actions';
import { collapsedThreads } from './thread.reducers';
import { getThreadIsCollapsed } from './thread.getters';
describe('collapsedThreads', () => {
const comment = { id: 1 };
it('should set collapsed to true', () => {
const collapsed = true;
const action = setCollapse(comment, collapsed);
const newState = {
collapsedThreads: collapsedThreads({}, action),
};
expect(getThreadIsCollapsed(newState, comment)).toEqual(collapsed);
const action = setCollapse(comment, true);
const newState = collapsedThreads({}, action);
expect(newState).toEqual({ [comment.id]: true });
});
it('should set collapsed to false', () => {
const collapsed = false;
const action = setCollapse(comment, collapsed);
const newState = {
collapsedThreads: collapsedThreads({}, action),
};
expect(getThreadIsCollapsed(newState, comment)).toEqual(collapsed);
const action = setCollapse(comment, false);
const newState = collapsedThreads({}, action);
expect(newState).toEqual({ [comment.id]: false });
});
});
+4
View File
@@ -1,3 +1,7 @@
import { userComments, isLoadingUserComments } from './user-info.reducers';
export const userInfoReducers = { userComments, isLoadingUserComments };
export { default } from './user-info';
require('./user-info.scss');
@@ -0,0 +1,12 @@
export const USER_INFO_FETCH_COMMENTS = 'USER_INFO/FETCH_COMMENTS';
export const fetchComments = userId => ({
type: USER_INFO_FETCH_COMMENTS,
userId,
});
export const USER_INFO_COMPLETE_FETCH_COMMENTS = 'USER_INFO/COMPLETE_FETCH_COMMENTS';
export const completeFetchComments = (userId, comments) => ({
type: USER_INFO_COMPLETE_FETCH_COMMENTS,
userId,
comments,
});
@@ -0,0 +1,2 @@
export const getUserComments = (state, userId) => state.userComments[userId] || null;
export const getIsLoadingUserComments = (state, userId) => state.isLoadingUserComments[userId] || false;
@@ -0,0 +1,40 @@
import { getUserComments, getIsLoadingUserComments } from './user-info.getters';
describe('getUserComments', () => {
const userId = 1;
const comments = [{ id: 1 }];
it('returns null when comments have not been loaded', () => {
const state = { userComments: {} };
const userComments = getUserComments(state, userId);
expect(userComments).toEqual(null);
});
it('returns comments when comments have been loaded', () => {
const state = { userComments: { [userId]: comments } };
const userComments = getUserComments(state, userId);
expect(userComments).toEqual(comments);
});
});
describe('getIsLoadingUserComments', () => {
const userId = 1;
it('returns false when state is empty', () => {
const state = { isLoadingUserComments: {} };
const loading = getIsLoadingUserComments(state, userId);
expect(loading).toEqual(false);
});
it('returns false when comments are not loading', () => {
const state = { isLoadingUserComments: { [userId]: false } };
const loading = getIsLoadingUserComments(state, userId);
expect(loading).toEqual(false);
});
it('returns true when comments are loading', () => {
const state = { isLoadingUserComments: { [userId]: true } };
const loading = getIsLoadingUserComments(state, userId);
expect(loading).toEqual(true);
});
});
+31 -16
View File
@@ -1,29 +1,31 @@
/** @jsx h */
import { h, Component } from 'preact';
import { connect } from 'preact-redux';
import api from 'common/api';
import LastCommentsList from './last-comments-list';
import Avatar from 'components/avatar-icon';
import { fetchComments, completeFetchComments } from './user-info.actions';
import { getUserComments, getIsLoadingUserComments } from './user-info.getters';
class UserInfo extends Component {
constructor(props) {
super(props);
this.state = {
comments: [],
isLoading: true,
};
}
componentWillMount() {
const {
user: { id },
comments,
isLoading,
fetchComments,
completeFetchComments,
} = this.props;
api
.getUserComments({ user: id, limit: 10 })
.then(({ comments = [] }) => this.setState({ comments }))
.finally(() => this.setState({ isLoading: false }));
if (!comments && !isLoading) {
fetchComments(id);
api
.getUserComments({ user: id, limit: 10 })
.then(({ comments }) => completeFetchComments(id, comments))
.catch(() => completeFetchComments(id, []));
}
document.addEventListener('keydown', this.globalOnKeyDown);
}
@@ -40,9 +42,12 @@ class UserInfo extends Component {
}
}
render(props, { comments, isLoading }) {
render(props) {
const {
user: { name, id, isDefaultPicture, picture },
comments = [],
isLoading,
onClose,
} = props;
return (
@@ -51,10 +56,20 @@ class UserInfo extends Component {
<p className="user-info__title">Last comments by {name}</p>
<p className="user-info__id">{id}</p>
<LastCommentsList isLoading={isLoading} comments={comments} />
{!!comments && <LastCommentsList isLoading={isLoading} comments={comments} />}
<span {...getHandleClickProps(onClose)} className="user-info__close">
Close &#10006;
</span>
</div>
);
}
}
export default UserInfo;
export default connect(
(state, props) => ({
comments: getUserComments(state, props.user.id),
isLoading: getIsLoadingUserComments(state, props.user.id),
}),
{ fetchComments, completeFetchComments }
)(UserInfo);
@@ -0,0 +1,37 @@
import { USER_INFO_FETCH_COMMENTS, USER_INFO_COMPLETE_FETCH_COMMENTS } from './user-info.actions';
export const userComments = (state = {}, action) => {
switch (action.type) {
case USER_INFO_FETCH_COMMENTS: {
return {
...state,
[action.userId]: [],
};
}
case USER_INFO_COMPLETE_FETCH_COMMENTS:
return {
...state,
[action.userId]: action.comments,
};
default:
return state;
}
};
export const isLoadingUserComments = (state = {}, action) => {
switch (action.type) {
case USER_INFO_FETCH_COMMENTS: {
return {
...state,
[action.userId]: true,
};
}
case USER_INFO_COMPLETE_FETCH_COMMENTS:
return {
...state,
[action.userId]: false,
};
default:
return state;
}
};
@@ -0,0 +1,45 @@
import { fetchComments, completeFetchComments } from './user-info.actions';
import { userComments, isLoadingUserComments } from './user-info.reducers';
const userId = 1;
const comments = [{ id: 1 }];
describe('userComments', () => {
it('should return {} by default', () => {
const action = { type: 'OTHER' };
const newState = userComments({}, action);
expect(newState).toEqual({});
});
it('should return [] on USER_INFO_FETCH_COMMENTS', () => {
const action = fetchComments(userId);
const newState = userComments({}, action);
expect(newState).toEqual({ [userId]: [] });
});
it('should return comments on USER_INFO_COMPLETE_FETCH_COMMENTS', () => {
const action = completeFetchComments(userId, comments);
const newState = userComments({}, action);
expect(newState).toEqual({ [userId]: comments });
});
});
describe('isLoadingUserComments', () => {
it('should return {} by default', () => {
const action = { type: 'OTHER' };
const newState = isLoadingUserComments({}, action);
expect(newState).toEqual({});
});
it('should return [] on USER_INFO_FETCH_COMMENTS', () => {
const action = fetchComments(userId);
const newState = isLoadingUserComments({}, action);
expect(newState).toEqual({ [userId]: true });
});
it('should return comments on USER_INFO_COMPLETE_FETCH_COMMENTS', () => {
const action = completeFetchComments(userId, comments);
const newState = isLoadingUserComments({}, action);
expect(newState).toEqual({ [userId]: false });
});
});
+10 -8
View File
@@ -23,12 +23,6 @@ loadPolyfills().then(() => {
}
});
const Main = () => (
<Provider store={reduxStore}>
<Root />
</Provider>
);
function init() {
const node = document.getElementById(NODE_ID);
@@ -59,13 +53,21 @@ function init() {
render(
<div id={NODE_ID}>
<div className="root root_user-info">
<UserInfo user={user} />
<Provider store={reduxStore}>
<UserInfo user={user} onClose={onClose} />
</Provider>
</div>
</div>,
node.parentElement,
node
);
} else {
render(<Main />, node.parentElement, node);
render(
<Provider store={reduxStore}>
<Root />
</Provider>,
node.parentElement,
node
);
}
}
+2
View File
@@ -2,9 +2,11 @@ import { createStore, applyMiddleware } from 'redux';
import { combineReducers } from 'redux';
import { threadReducers, threadMiddlewares } from './components/thread';
import { userInfoReducers } from './components/user-info';
const reducers = combineReducers({
...threadReducers,
...userInfoReducers,
});
const middlewares = applyMiddleware(...threadMiddlewares);
+1
View File
@@ -6,6 +6,7 @@
"start": "webpack-dev-server --progress --hot --inline --config ./webpack.config.js",
"lint": "eslint --ext=.js,.jsx .",
"test": "jest",
"test:coverage": "jest --coverage",
"prettier": "prettier --write \"./**/*.{js,jsx,scss}\"",
"precommit": "./node_modules/.bin/lint-staged"
},