UserInfo uses redux store
This commit is contained in:
@@ -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 });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -1,36 +1,40 @@
|
||||
/** @jsx h */
|
||||
import { h, Component } from 'preact';
|
||||
import { connect } from 'preact-redux';
|
||||
|
||||
import api from 'common/api';
|
||||
import { getHandleClickProps } from 'common/accessibility';
|
||||
|
||||
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, []));
|
||||
}
|
||||
}
|
||||
|
||||
render(props, { comments, isLoading }) {
|
||||
render(props) {
|
||||
const {
|
||||
user: { name, id, isDefaultPicture, picture },
|
||||
comments = [],
|
||||
isLoading,
|
||||
onClose,
|
||||
} = props;
|
||||
|
||||
@@ -40,7 +44,7 @@ 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 ✖
|
||||
@@ -50,4 +54,10 @@ class UserInfo extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
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
@@ -22,12 +22,6 @@ loadPolyfills().then(() => {
|
||||
}
|
||||
});
|
||||
|
||||
const Main = () => (
|
||||
<Provider store={reduxStore}>
|
||||
<Root />
|
||||
</Provider>
|
||||
);
|
||||
|
||||
function init() {
|
||||
const node = document.getElementById(NODE_ID);
|
||||
|
||||
@@ -63,13 +57,21 @@ function init() {
|
||||
render(
|
||||
<div id={NODE_ID}>
|
||||
<div className="root root_user-info">
|
||||
<UserInfo user={user} onClose={onClose} />
|
||||
<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,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);
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -1,128 +0,0 @@
|
||||
Arguments:
|
||||
/Users/dmitrytsepelev/.nvm/versions/node/v9.2.0/bin/node /Users/dmitrytsepelev/.nvm/versions/node/v9.2.0/bin/yarn start
|
||||
|
||||
PATH:
|
||||
/anaconda2/bin:/Users/dmitrytsepelev/.rvm/gems/ruby-2.5.1/bin:/Users/dmitrytsepelev/.rvm/gems/ruby-2.5.1@global/bin:/Users/dmitrytsepelev/.rvm/rubies/ruby-2.5.1/bin:/usr/local/heroku/bin:/Users/dmitrytsepelev/.nvm/versions/node/v9.2.0/bin:./bin:bin:/Users/dmitrytsepelev/bin:/usr/local/bin:/usr/local/sbin:/usr/local/share/npm/bin:/usr/bin:/bin:/usr/sbin:/sbin::/Users/dmitrytsepelev/.rvm/bin:/usr/local/go/bin:/Users/dmitrytsepelev/go/bin:/Users/dmitrytsepelev/Library/Android/sdk/tools:/Users/dmitrytsepelev/Library/Android/sdk/platform-tools:/Users/dmitrytsepelev/Library/Android/sdk/tools/proguard/bin
|
||||
|
||||
Yarn version:
|
||||
1.5.1
|
||||
|
||||
Node version:
|
||||
9.2.0
|
||||
|
||||
Platform:
|
||||
darwin x64
|
||||
|
||||
npm manifest:
|
||||
{
|
||||
"name": "remark-ui",
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"build": "cross-env NODE_ENV=production webpack --config ./webpack.config.js",
|
||||
"start": "webpack-dev-server --progress --hot --inline --config ./webpack.config.js",
|
||||
"lint": "eslint --ext=.js,.jsx .",
|
||||
"test": "jest",
|
||||
"prettier": "prettier --write \"./**/*.{js,jsx,scss}\"",
|
||||
"precommit": "./node_modules/.bin/lint-staged"
|
||||
},
|
||||
"lint-staged": {
|
||||
"./**/*.{js,jsx}": [
|
||||
"eslint --fix",
|
||||
"git add"
|
||||
],
|
||||
"./**/*.scss": [
|
||||
"prettier --write",
|
||||
"git add"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^7.2.6",
|
||||
"babel-core": "^6.26.3",
|
||||
"babel-eslint": "^8.2.5",
|
||||
"babel-loader": "^7.1.4",
|
||||
"babel-plugin-syntax-dynamic-import": "^6.18.0",
|
||||
"babel-plugin-transform-object-rest-spread": "^6.26.0",
|
||||
"babel-plugin-transform-react-jsx": "^6.24.1",
|
||||
"babel-preset-env": "^1.7.0",
|
||||
"clean-webpack-plugin": "^0.1.19",
|
||||
"copy-webpack-plugin": "^4.5.1",
|
||||
"core-js": "^2.5.7",
|
||||
"cross-env": "^5.2.0",
|
||||
"css-loader": "^0.28.11",
|
||||
"eslint": "^4.19.1",
|
||||
"eslint-config-prettier": "^2.9.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.1.0",
|
||||
"eslint-plugin-prettier": "^2.6.1",
|
||||
"eslint-plugin-react": "^7.10.0",
|
||||
"extract-text-webpack-plugin": "^3.0.2",
|
||||
"file-loader": "^0.11.1",
|
||||
"html-webpack-plugin": "^2.30.1",
|
||||
"husky": "^0.14.3",
|
||||
"jest": "^23.1.0",
|
||||
"jest-localstorage-mock": "^2.2.0",
|
||||
"lint-staged": "^7.2.0",
|
||||
"postcss-calc": "^6.0.1",
|
||||
"postcss-csso": "^2.0.0",
|
||||
"postcss-for": "^2.1.1",
|
||||
"postcss-loader": "^2.1.5",
|
||||
"postcss-nested": "^3.0.0",
|
||||
"postcss-simple-vars": "^4.1.0",
|
||||
"postcss-url": "^6.3.1",
|
||||
"postcss-wrap": "0.0.4",
|
||||
"preact-redux": "^2.0.3",
|
||||
"prettier": "^1.13.7",
|
||||
"redux": "^4.0.0",
|
||||
"style-loader": "^0.19.1",
|
||||
"webpack": "^3.12.0",
|
||||
"webpack-bundle-analyzer": "^2.13.1",
|
||||
"webpack-dev-server": "^2.7.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.18.0",
|
||||
"bem-react-helper": "^1.1.2",
|
||||
"preact": "^8.2.9"
|
||||
},
|
||||
"eslintIgnore": [
|
||||
"public"
|
||||
],
|
||||
"jest": {
|
||||
"transform": {
|
||||
"^.+\\.jsx?$": "<rootDir>/fileTransformer.js"
|
||||
},
|
||||
"setupFiles": [
|
||||
"<rootDir>/injectGlobalVariable.js",
|
||||
"jest-localstorage-mock"
|
||||
],
|
||||
"moduleDirectories": [
|
||||
"node_modules",
|
||||
"<rootDir>/app"
|
||||
],
|
||||
"testMatch": [
|
||||
"<rootDir>/**/*.test.js"
|
||||
]
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
}
|
||||
|
||||
yarn manifest:
|
||||
No manifest
|
||||
|
||||
Lockfile:
|
||||
No lockfile
|
||||
|
||||
Trace:
|
||||
Error: Command failed.
|
||||
Exit code: 1
|
||||
Command: sh
|
||||
Arguments: -c webpack-dev-server --progress --hot --inline --config ./webpack.config.js
|
||||
Directory: /Users/dmitrytsepelev/dev/remark/web
|
||||
Output:
|
||||
|
||||
at ProcessTermError.MessageError (/Users/dmitrytsepelev/.nvm/versions/node/v9.2.0/lib/node_modules/yarn/lib/cli.js:186:110)
|
||||
at new ProcessTermError (/Users/dmitrytsepelev/.nvm/versions/node/v9.2.0/lib/node_modules/yarn/lib/cli.js:226:113)
|
||||
at ChildProcess.<anonymous> (/Users/dmitrytsepelev/.nvm/versions/node/v9.2.0/lib/node_modules/yarn/lib/cli.js:30281:17)
|
||||
at ChildProcess.emit (events.js:159:13)
|
||||
at maybeClose (internal/child_process.js:943:16)
|
||||
at Process.ChildProcess._handle.onexit (internal/child_process.js:220:5)
|
||||
Reference in New Issue
Block a user